# Picopackage A command line tool for installing and updating [picopackages](https://picopackage.org): 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 ```bash gem install picopackage ``` ## Usage ```bash 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: ```ruby 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 `.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](https://github.com/standardrb/standard). `bin/console` gives you a prompt. To release: update `lib/picopackage/version.rb`, then `bundle exec rake release`. ## Licence MIT.