accorder

A CLI for building, sharing, and federating Calibre library catalogs — over HTTP, Tor, or S3-compatible storage.

Download binaries v0.24.4 — linux · macOS · windows, amd64 · arm64, with and without Tor

When everyone is librarian, library is everywhere

Throughout, alice is a librarian (an operator-hosted federation member) and bob another (a peer member); fed is a federation, and op is the operator's host (it appears in serve URLs like http://op:8800).


Verbose setup, lean run

The CLI is lean: you declare a thing once, then refer to it by name.

Most commands read the same way: a resource, the action on it, then a name for this run of that action:

The resource (lib or fedlib) and verb (build or share) say what to do; the trailing name is yours to choose, so you remember what this run was for.

That name is the action alias — a memorable context for the action. The first time, you spell the action out in flags and accorder saves them under the name. (A few per-run switches are better left one-off, so they are not saved — see below.)

Once you have configured the action with flags, you run it bare — the saved config is what executes.

For the lib resource — a Calibre library — the action alias names one of your libraries. With only one, you need no name; it takes the implicit default. With several, the name tells them apart: research, comicbooks, fiction…

If you run:

accorder lib build alice --librarian "Alice Wonderland" --calibrepath ~/Calibre/alice

both the librarian name and the path to Alice's Calibre library are saved.

Next time it is enough to run just:

accorder lib build alice

and both values are read from the configuration, as if the flags were provided.

Change your mind at any point by passing just the one flag you want different: it overwrites that single value, the rest stays put, and you can forget again.

Add private mode on top of the already-saved librarian (Alice Wonderland) and Calibre path (~/Calibre/alice):

accorder lib share live alice --mode private

It runs in private mode and saves the mode for future runs.

Next time all three flags are implicitly present:

accorder lib share live alice

Changing one flag runs with its new value plus the saved ones, and saves the new value for future bare runs:

accorder lib share live alice --calibrepath ~/CalibreLibraries/alice

The invariant to hold in your head: a bare run replays the latest values you set — omitting a flag means "what I last set," not a fresh default. This covers the alias-persisted flags; deliberate per-run switches — --offline, --only-http, --only-tor, --insecure — are never saved and must be given each time.

accorder config show alice prints the saved flag tree for one alias — the stored values a bare run replays (run-time defaults, environment values, and descriptor/credential resolution aren't part of that picture). accorder config list names the aliases you've built up so far (alice, bob, fed) and the action families saved under each.

Some flags span a resource's actions: give --calibrepath once (at your first lib command, step 1) and the alias's other shared-group lib actions — browse, build, import, join, publish, status, sync, upload, share live — inherit it (lib download is the exception: its --calibrepath is a plain destination directory, set on that action).

--librarian and --libraryID are adopted before they are generated: if any action under the alias already carries one, that value is taken and spread to the rest; only when none does is a new one minted. An empty field on the action you happen to run is not the same as the library having no identity.

Federations do the same through the descriptor (fedlib config set) — but note the positional there is a federation id, not an action alias: fedlib config set, fedlib members invite, and fedlib members offer key the descriptor by that id (a separate store from the aliases above). fedlib build and fedlib share live take a normal action alias; when it names a descriptor (as fed does here), they recall its bucket, S3, paths, and keys-file.


0 — Reader (a web browser, no accorder)

Browse, search, and download from the web — no catalog to maintain or re-share. The reader surfs the web:

https://library.memoryoftheworld.org

That page is the public face of a federation aggregate. Once the operator has set it up (step 2), serving it is a single line — the descriptor and alias remember the bucket, S3, paths, and listen address:

accorder fedlib share live motw

And it is more than a web page: in this most-public mode a browser gets plain browsing, but pointing accorder at the same endpoint turns the reader into a librarian — caching the index, selecting offline, rendering into your own Calibre, re-sharing. That is the rest of this guide.


1 — Reader → Librarian (accorder lib browse)

A friend is a librarian, keen to share their library with you. Or, as with Memory of the World, a group of friends — amateur librarians — run an aggregate of many libraries and share it with the world.

You browse a shared library and render your selection into your own Calibre library. The catalog-to-be needs two things: a Calibre path and a librarian name. A library name is optional — it labels your rendered catalog and travels to anyone who later browses yours. After running:

accorder lib browse alice --endpoint https://library.memoryoftheworld.org

the shared library's index is downloaded, cached, and bookmarked as alice's last source. A bare lib browse alice reopens it.

Each browsed source is also registered by name (lib index list shows them), and any of them can be browsed with --from <name> without disturbing the bookmark.

The lib browse command opens a web browser on the shared library.

Until the Calibre path and librarian name are set, the header's SETTINGS step collects them first, and the other steps stay locked.

From there the header walks you through three steps:

Both the Calibre path and the librarian name can be handed over on the command line (-c and -l), so a fully-flagged browse skips SETTINGS entirely — only an optional library name is left to set there. Both are saved and shared with every later lib action:

accorder lib browse alice --endpoint https://library.memoryoftheworld.org --calibrepath ~/Calibre/alice --librarian alice

If the aggregate is shared in private mode, alice needs an invite from the operator and passes an --invite blob in place of the URL:

accorder lib browse alice --invite 'accorder-invite:…'

Browse offline

Reopen alice's last-browsed source with no network: the cached index gives you full browsing and search offline. A selection lives only in the running session — to fetch files, come back online and download from QUEUE; then, as always, the header's last step — the one named after your catalog — renders the downloaded books into it.

accorder lib browse alice --offline

Build your catalog (accorder lib build)

Building a catalog needs only two flags: --librarian and --calibrepath. Coming from lib browse above, both are already on the alias — each from its flag (-c, -l) or SETTINGS, the librarian auto-generated (as is the UUID) when left unset — so alice builds with:

accorder lib build alice

The browse route is not required: starting from an already existing Calibre library, the same build just needs those two flags spelled out:

accorder lib build alice --librarian alice --calibrepath ~/Calibre/alice

Or the books are in Zotero. lib import syncs a BetterBibTeX export into the Calibre library — adding what is new, updating what changed, leaving the rest alone — and --calibrepath is already shared, so only the export directory is new:

accorder lib import alice --zotero-path /tmp/betterbibtex-from-zotero/

Re-run it whenever the export changes. A book Zotero no longer carries is reported, not removed, until you ask:

accorder lib import alice --prune

--prune and --yes are per-run like the switches above — never saved — so a bare lib import alice cannot delete anything you did not ask for.

After the library changes, alice rebuilds with just:

accorder lib build alice

Share it (accorder lib share live)

Serve the library so others can lib browse it. In a -tags tor build it comes up on both a Tor onion and HTTP (--only-http narrows to clearnet); the default build serves HTTP only. --mode gates access.

accorder lib share live alice --only-http --listen 0.0.0.0:8900 --mode private

A bare restart replays the saved listen and mode:

accorder lib share live alice

--only-http is per-run and never saved, so on a Tor-enabled build repeat it (the default build is HTTP-only anyway).

While it serves, alice's terminal prints a ready-made command for her friend. She does not run it — she copies it and sends it to bob:

accorder lib browse local-librarian --from remote-alice --endpoint http://192.168.1.24:8900/

The two names say which side each belongs to: local- is the friend's own context, remote- is alice's library. Alice cannot know who will receive the line, so the local half stays the generic local-librarian — a word inviting replacement.

bob puts his own alias in place of the suggested one and runs it:

accorder lib browse local-bob --from remote-alice --endpoint http://192.168.1.24:8900/

Read it as three answers:

Next time bob needs only his alias:

accorder lib browse local-bob

Or he can reach alice's library while working under a different alias — a separate local Calibre library, but the same source library and index:

accorder lib browse local-bob-2 --from remote-alice

Both names were only suggestions. Bob can use any alias — omitting it means default — rename the source with lib index rename, or drop --from once it is saved. On a loopback bind, no such line is printed — nothing off-machine could reach it.

Granting a friend access prints an invite blob; they browse your share into their own library — step 1, from the other side.

Alice prepares the invitation for her friend bob:

accorder lib share grant alice --friend bob --endpoint http://alice-host:8900

bob gets his invitation from alice and can then build his own library:

accorder lib browse bob --invite 'accorder-invite:…' --calibrepath ~/Calibre/bob

2 — Operate a federation (accorder fedlib)

Stand up an aggregate and serve it; this comes before joining because the invite/offer minted here is what a librarian joins with. The descriptor (fedlib config set) is remembered, so the rest stays short.

accorder fedlib config set fed --bucket motw/fed --s3-provider Wasabi --s3-endpoint s3.wasabisys.com --keys-file keys/op.s3.keys --mode public --fedlib-path ~/agg --upload-prefix aggregate

fedlib members invite mints an operator-hosted member's blob. Auto-minting the member's scoped S3 creds needs the operator's IAM root keys — not the descriptor's keys/op.s3.keys, which is the aggregate's own S3 creds. Drop them in operator_root.keys (or wasabi_root.keys) in the config dir, or pass --operator-root-keys-file; with root keys in place you pass neither creds nor bucket.

accorder fedlib members invite fed alice --prefix alice

fedlib members offer mints a peer's claim URL:

accorder fedlib members offer fed --prefix bob --librarian bob --endpoint http://op:8800

fedlib build bootstraps the aggregate once; the positional pulls bucket, S3, output path, and keys-file from the descriptor, so it's credless too.

accorder fedlib build fed

Then fedlib share live serves it and rebuilds automatically as members change:

accorder fedlib share live fed --only-http --poll-interval 5 --listen 0.0.0.0:8800

A restart replays the saved configuration; as with lib share live, --only-http is per-run, so repeat it on a Tor-enabled build:

accorder fedlib share live fed --only-http

Join a federation (accorder lib join)

Each librarian joins with what the operator minted above — alice with the operator-hosted membership blob (accorder-fedlib-invite:…), bob with his peer-offer claim URL. The alias is the first word; the blob or URL goes to --invite.

Alice joins under the alias she has been building with all along. That alias carries a library UUID she minted locally in step 1, and the invite carries the federation's — so the join adopts the federation's identity in its place. It can, because nothing was ever published under the local one: the alias holds a catalog, not a membership. Her calibrepath and librarian name are already on the alias, so the join needs neither, and it builds and publishes into the bucket in one go (it confirms first when run from a terminal). Later, lib publish alice republishes after changes.

accorder lib join alice --invite 'accorder-fedlib-invite:…'

An alias that is already a membership — one carrying a federation prefix, a dict ID, a publish bucket or stored write credentials — refuses a conflicting invite instead, and says which of those it found. Re-inviting an existing member, where the invite carries the UUID the alias already holds, is not a conflict and simply refreshes the credentials. Refusals that can be decided from the alias and the offer's prefix happen before the claim, so a refused join leaves a single-use offer unspent.

bob — browsing into his own Calibre since step 1 — hosts his library himself. First he builds it:

accorder lib build bob

Then he shares it, and the share keeps serving in its terminal:

accorder lib share live bob --only-http --listen 0.0.0.0:8900 --mode private

From a second terminal he claims the peer offer. The operator pulls from his live share; his --libraryID comes from his alias:

accorder lib join bob --invite 'http://op:8800/invite/…' --endpoint http://bob-host:8900 --insecure

Re-run lib build bob after changes — the operator's poll picks it up.


Federation of federations

A fedlib aggregate is itself a library — today you can lib browse it from the top, render any member's books into your Calibre, and re-share (step 1, one level up); what you rendered can join a federation via lib join (step 2). Aggregates joining other aggregates is the direction, not yet a command — lib join operates on a librarian's Calibre library, not on a fedlib. Books already move catalog → catalog the whole way:

accorder lib browse alice --endpoint https://<a-fedlib>