Files
picopackage/README.md
T
Dan MilneandClaude Fable 5 6dd57e84f1 Version 0.3.0: resolver/merge/cache refactor, signing design notes
- Split the monolith into package/provider/fetch/merge/cache modules with
  the Resolver deciding install/adopt/update/merge/conflict outcomes
- Three-way merges via git merge-file/diff3 against a content-addressed
  merge-base cache; conflicts go to a .picopackage-merge sibling
- Tests for package, provider, resolver, merge, and cache
- notes.md: update UX and signing design — diff-by-default, SSH signature
  identity pinning (TOFU), key changes as a hard stop, exit-code contract
- Add CLAUDE.md; remove the pre-refactor exe/pppkg monolith and scratch files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EiyJC73Mz8xZyCTvCEY8qn
2026-08-13 21:18:19 +10:00

5.2 KiB

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-merge for you to work from.
  • --force discards local changes and takes upstream wholesale.

Two rules decide every case:

  1. The checksum decides, never the timestamp. An mtime says nothing about content — a file re-uploaded unchanged gets a fresh one.
  2. 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 url is 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.