- 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
136 lines
5.2 KiB
Markdown
136 lines
5.2 KiB
Markdown
# 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
|
|
`<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](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.
|