Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EiyJC73Mz8xZyCTvCEY8qn
Picopackage
A command line tool for installing and updating picopackages: single files that carry their own provenance, so you can install one into your project, edit it, and still pull upstream fixes later.
For code that's too small to be a gem but copied often enough that pasting it between projects hurts.
Installation
gem install picopackage
Usage
ppkg install https://gist.github.com/you/abc123 # fetch into .
ppkg install https://example.com/retry.rb lib/ # fetch into lib/
ppkg update lib/retry.rb # re-fetch from its recorded url
ppkg package lib/mine.rb --url https://… # turn a local file into a picopackage
ppkg verify lib/retry.rb # check it against its checksum
ppkg scan . # list picopackages in a tree
ppkg inspect lib/retry.rb # print its metadata
What a picopackage looks like
An ordinary source file with a commented YAML block at the end:
def retry_with_backoff(attempts: 3)
# …
end
# @PICOPACKAGE_START
# ---
# url: https://gist.github.com/you/abc123
# filename: retry.rb
# payload_timestamp: '2026-08-12T14:06:38Z'
# payload_checksum: sha256:7b856b1b…
# base_checksum: sha256:7b856b1b…
# @PICOPACKAGE_END
Everything above the block is the payload. The block is regenerated on every write. Metadata keys the tool doesn't recognise are preserved as-is, so optional fields survive a tool that predates them.
Upstream doesn't need a block
A plain file with no metadata at all is a valid picopackage source. You can install someone's gist, blog snippet or raw file without them adopting anything — everything the block would have said is derived from the fetch.
When upstream does carry a block, it's opting in to extra guarantees, and only half of it is upstream's to assert:
| Fields | Who decides | |
|---|---|---|
| Claims | url, filename, payload_version, licence |
upstream — adopted as given |
| Records | payload_checksum, base_checksum |
your machine — always recomputed |
A claim is something only the author knows: the canonical URL that outlives the
mirror you fetched from, the version, the licence. A record describes one copy on
one disk, so upstream can't speak for yours. In particular an upstream
payload_checksum is never trusted — a checksum stored beside the content it
hashes proves nothing, and an author who edits without re-running the tool ships
a stale one. Adopting it would install a file that failed its own verification
and reported as locally modified before you touched it.
Updating a file you've edited
The point of a picopackage is that it's your file — so editing it has to be
allowed, and updates have to cope with that. ppkg update does a three-way
merge against the version you originally installed:
- Unchanged locally → fast-forwarded to upstream.
- Edited locally, upstream unchanged → nothing happens. No merge is attempted; upstream is still the version you branched from.
- Edited locally, no overlap with upstream's changes → merged, keeping both.
- Edited locally, overlapping changes → refused. Your file is left exactly
as it is and a copy with conflict markers is written to
<file>.picopackage-mergefor you to work from. --forcediscards local changes and takes upstream wholesale.
Two rules decide every case:
- The checksum decides, never the timestamp. An mtime says nothing about content — a file re-uploaded unchanged gets a fresh one.
- Merges run on the payload alone. The metadata block is regenerated afterwards, so it never conflicts.
The merge needs the common ancestor, which is kept in a content-addressed cache
under $XDG_CACHE_HOME/picopackage (override with PICOPACKAGE_CACHE). A file
installed on another machine, or one whose cache has been cleared, has no
ancestor available — ppkg says so and refuses rather than guessing.
git merge-file is used when available, diff3 otherwise.
Sources
| Source | Handling |
|---|---|
| GitHub Gist | Resolved through the Gist API |
| Opengist | Resolved via its .json endpoint |
| Any URL | Fetched directly |
| Local path | Read from disk |
Responses are capped at 1 MB and time out after 10s. Redirects are followed up
to 5 times and only to http/https.
What it deliberately doesn't do
- No dependencies. Each file stands alone. There is no solver, no version ranges, no transitive graph.
- No registry. The file's
urlis its identity; no central index exists. - No author verification. A checksum stored beside the content it hashes proves nothing about who wrote it — its job is detecting local modification. Signing (SSH signatures, TOFU-pinned — see notes.md) is designed, not implemented. Until then, the security model is that a picopackage is one file and you can read it.
Development
bin/setup installs dependencies, rake test runs the tests, rake runs tests
and Standard. bin/console gives you a
prompt.
To release: update lib/picopackage/version.rb, then bundle exec rake release.
Licence
MIT.