Skip to main content

Crate choir_cli

Crate choir_cli 

Source
Expand description

Shared pieces of the choir command line.

The binary lives in main.rs; this library exists so the agent-facing surface can be described once as data and rendered into every place that documents it, with a test able to check that none of them have drifted.

§Examples

let usage = choir_cli::surface::usage();
assert!(usage.starts_with("usage:\n"));
// Grouped: a section heading, and the command names under it.
assert!(usage.contains("\nreview\n"));
assert!(usage.contains("verdict"));

§Where this sits

docs/architecture.md is the map of the whole workspace. This crate is the choir binary, and the surface table every generated document is rendered from.

It builds on choir_fs, choir_hash, [choir_identity], choir_node, choir_oplog and choir_view.

The complete surface, rendered from the table in surface and included here so the two cannot disagree:

§The choir CLI and the node’s HTTP API

The complete surface, rendered from one table shared with the CLI’s --help, AGENTS.md, /llms.txt and /api/schema. A staleness test fails the gate when they differ.

§Authentication and exit codes

Auth on the CLI is flags, not env:

choir --auth-file ~/.choir/auth --auth-user choir <command> ...

Exit codes: 0 accepted, 1 rejected (JSON body printed, see ERRORS.md), 2 usage.

The signed-operation API is the primary agent path: it carries actor identity and batches many operations behind one durability barrier. git push remains the compatibility and bulk-transfer path.

§Signed-operation CLI and API (primary agent path)

§HTTP endpoints

EndpointPurpose
POST /api/submitSubmit one signed operation (hex payload, hex signature)
POST /api/submit-batchSame, in array order; the primary path for agent workloads
GET /api/view?limit=N&offset=MThe materialized view plus the latest ref-state attestation, key bindings, T2 review outcomes, T3 concentration, T4 newcomer harm, view growth, the build commit, and the sequencer’s p99 against the 100 ms gate. Under an ACL you get your own slice; node-wide sections need a node-wide grant, and a missing repository is one you were not granted. Map-shaped sections are bounded: limit rows (200 default, 1000 max), offset, <section>_omitted, and paging.next
POST /api/appealRecord an appeal for a rejected newcomer attempt; requests operator adjudication and never changes privilege
GET /api/log?from=NOrdered log entries, the catch-up and sync primitive. Absolute from; evicted entries are served from the persisted log (source says which), and a node that cannot reach back answers 409. Each entry carries hash, parent and author signature; SYNC.md is the verification procedure
POST /api/workspaceProvision a CoW workspace; optional exact base/change binding makes retries idempotent
POST /api/workspace/archiveRecoverably archive a change-bound workspace and remove it from the active view
GET /api/reviews?reviewer=XOne actor’s pending review queue
GET /api/schemaThis surface, machine-readable and versioned, plus what this node will accept; the description an agent generates a client from (D17)
GET /api/search?q=X&in=code&repo=owner/name&rev=R&limit=NSearch repository contents, file names or commit messages across every repository you may read, each at HEAD; rev needs a single repo. Ungranted repositories are absent, or answered as nonexistent by name. Unindexed (git grep): limit bounds the results, matches counts everything, truncated says which
GET /api/profile?channel=XOne actor’s standing out of the view you may see: bound keys and their age, changes owned, reviews assigned and verdicts given, approvals slashed, checks reported, and vouches with direction. Two callers with different grants get different numbers. No score; time-locked grants (D66) live outside the log and are not counted
GET /llms.txtThis surface, as text, for an agent that has never seen choir
GET /sync.mdThe sync contract: cursor semantics and how to verify a page’s hash chain and author signatures
GET /api/reposWhich repositories this credential can see, read from the filesystem; narrowed says whether an ACL was applied
POST /api/repoCreate a repository on a running node with the pre-receive hook that sequences its pushes; needs a node-wide write grant; appends nothing to the log
GET /api/ref-agreementWhere the op log and the bare repos disagree about a ref, read-only
POST /api/accounts/inviteMint a single-use, expiring invite and the grants it will hold; needs a node-wide write grant and can never issue one; a grant may carry until=<unix seconds> (D66)
POST /api/accounts/redeemRedeem an invite, presented as the credential, for a token, once, and register an ssh key
POST /api/accounts/request/grantAnswer an access request (D72): turns it into an invite under the id and secret the asker already holds
POST /api/accounts/request/declineDrop a pending access request; their link then reads as never valid
POST /api/accounts/revokeDelete an account: its token stops authenticating on the next request, and its grants and keys go with it
GET /api/accountsWho holds an account, what they were granted, and which invites are outstanding; never a secret or its hash
POST /api/git-updateInternal: the pre-receive hook callback
POST /api/git-abortInternal: retracts a refused push’s already-accepted refs

§The choir CLI

getting started

  • choir host [--domain <name> | --public [--ip <addr>] | --public-name <name>] [--port <n>] [--repo <owner/name.git>] [--invite <name>] [--state <dir>] [--yes] [--dry-run] [--foreground] [-- <daemon flags>]
    take this machine from nothing to a running node and print its URL; bare binds loopback, –domain issues a Let’s Encrypt certificate for a name you own, –public uses a magic-DNS name over this box’s address, –foreground execs the daemon instead of installing a unit
  • choir init [<state-dir>] [--port <n>] [--force]
    set up a node’s layout on this machine: repository root, credential at 0600, an actor key the node trusts, and .choir/config; refuses to overwrite what exists
  • choir key <key-file> [name]
    mint a key and print the line the operator registers; pass your channel name to print the bound form
  • choir git-credential <auth-file> [--auth-user <name>] get|store|erase
    git credential helper: hands git your token on stdin so it never lives in a remote URL; configure with git config credential.helper '!choir git-credential <auth-file>'
  • choir join <link> | <api> <invite-file> <key-file> [--user <name>] [--channel <name>] [--key-file <path>] [--ssh-key <path>] [--token-file <path>]
    redeem an invite link and set this machine up: actor key at ~/.choir/agent.key, token at ~/.choir/auth (0600), a git credential helper for that node, and the node URL in ~/.choir/config; –user names the account when the invite left it open, asked on the terminal otherwise; the three-argument form takes the invite from a file, answers JSON and touches neither git nor your home directory
  • choir docs [--open]
    build the book from docs/ with the API documentation inside it at book/api/; needs a checkout and mdbook, and names the install command if it is missing
  • choir skill install [--into <dir>]
    install the choir agent skill (default .claude/skills), rendered from this binary’s own surface table; re-run after upgrading

changing code

  • choir workspace <api> <owner/repo> <name> [--base <git-oid> --owner <channel> --key-file <path> --change <id> --idempotency-key <key>] [--path <prefix>]...
    provision a CoW workspace; advanced flags owner-sign an exact base and stable change, and each –path owner-signs a subtree
  • choir checkpoint <api> <key-file> <channel> <change-id> <workspace-id> <git-oid>
    publish an immutable change revision after committing and pushing its git object
  • choir propose [reviewer]... [--key-file <path>] [--channel <name>] [--api <url>] [--repo <owner/repo>] [--remote <name>] [--onto <branch>] [--change <id>] [--path <prefix>]...
    create a change, push its commits and request review, with no arguments; run from a git checkout, with the key and channel from ~/.choir, every value overridable by flag; re-running after an amend updates the same proposal; a leading <key-file> <channel> pair is still accepted
  • choir workspace-archive <api> <key-file> <channel> <owner/repo> <name> <change-id> <idempotency-key>
    owner-sign and recoverably archive a bound workspace; exact retries are idempotent
  • choir submit <api> <key-file> <channel> '<op-json>'
    sign and submit one raw operation
  • choir batch <api> <key-file> <channel> <ops-file>
    sign and submit many operations as one batch, the primary path for agent workloads; one op per line, - reads stdin, one result line per op
  • choir intent <api> <key-file> <channel> <subject> <kind> '<body>'
    publish a task spec or plan so other agents can see intent
  • choir state <api> <channel>
    list what you owe and what you are waiting on; every row carries the command that answers it and its risk

review

  • choir review <api> <key-file> <channel> <id> <git-oid> [--ref <repo:ref>] [reviewer]...
    request review on a commit; name no reviewers and the node draws them
  • choir verdict <api> <key-file> <reviewer> <id> approve|request-changes [note]
    answer a review you were assigned
  • choir comment <api> <key-file> <channel> <review-id> <comment-id> '<body>'
    say something on a review; append-only, and the comment id is your retry identity
  • choir viewed <api> <key-file> <viewer> <review-id>
    record that you read a review; first read only, resubmitting is refused
  • choir slash <api> <node-key-file> <id> <reviewer> '<reason>'
    invalidate one reviewer’s approval; operator-only and never moves a ref
  • choir abandon <api> <node-key-file> <id>
    archive a stale incomplete review as lapsed, settling it unapproved; operator-only and never moves a ref
  • choir reviews <api> <reviewer>
    your pending review queue

checks

  • choir check <api> <key-file> <channel> <git-oid> <name> passed|failed|running|errored [evidence] [--ref <repo:ref>]
    report one automated check’s outcome on a commit; any runner or person can report by signing, and the node never runs the check
  • choir checks <api> <git-oid>
    every check reported on a commit, and one verdict; exits 0 passed, 1 failed or unreported, 3 still running, 4 could not be run

trust

  • choir witness <api> <key-file> <channel>
    cosign the node’s current ref-state attestation (D67); the snapshot id is read from the view, and the node may not witness its own
  • choir vouch <api> <key-file> <channel> <subject> [note]
    vouch for another operator; both ends need a key bound in the log, and it authorizes nothing on its own
  • choir unvouch <api> <key-file> <channel> <subject> '<reason>'
    withdraw a vouch; both ops stay in the log, and vouching again starts a fresh clock

reading the node

  • choir schema <api>
    print this node’s machine-readable API description and its live capabilities
  • choir log <api> [--from <n>] [--verify] [--keys <file>]
    read log entries from a cursor; –verify checks continuity, recomputes every hash and verifies the signatures whose keys you hold
  • choir appeal <api> <attempt-id>
    appeal a rejected newcomer attempt for operator adjudication; never grants privilege
  • choir profile <api> <channel>
    what the log records about one actor: keys and their age, changes owned, verdicts given, checks reported
  • choir search <api> <term> [--in files|code|commits] [--repo owner/name] [--rev R] [--limit N]
    find a literal term across every repository you may read
  • choir triage <api>
    every review and change in a bucket (landed, awaiting verdicts, changes requested, approved awaiting landing), most actionable first, capped, with truncation marked in-band
  • choir funnel <api>
    the contribution funnel from admission to first verdict and the steepest drop between stages; counts what this credential may read, and reports an unmeasured stage as null
  • choir view <api> [--limit <n>] [--offset <n>]
    read the materialized view, its ref-state attestation and the node’s health counters; map-shaped sections page 200 rows at a time, with <section>_omitted and paging.next

operating a node

  • choir invite <api> <name> <owner/repo> [read|write]
    mint an invite and print the one link to send; the same thing the /people page does
  • choir asks <api>
    who has asked for access and is waiting on an answer (D72)
  • choir grant <api> <request-id> <owner/repo> [read|write]
    let one of them in; the link they already hold becomes their invite
  • choir decline <api> <request-id>
    drop a pending request; their link then reads as never valid
  • choir runner <config-file>
    drive one workspace lifecycle step for an orchestrator; JSON request on stdin, JSON result on stdout
  • choir bind <api> <node-key-file> <operator> <key-hex> [channel]
    record in the log that a key belongs to an operator; operator-only and never moves a ref
  • choir revoke <api> <node-key-file> <key-hex> '<reason>'
    withdraw a key binding; terminal, and the attribution row survives
  • choir acl render <api> <acl-file>
    rewrite an ACL file’s trailing comments to name the person behind each handle; grants are copied through unchanged
  • choir repo create <api> <owner/repo.git>
    create a repository on a running node, sequenced from its first push; needs a node-wide write grant, answers 409 when it already exists, and prints the clone URL
  • choir repo list <api>
    the repositories on a node this credential can read, one per line; an ACL narrows the list rather than refusing it
  • choir repo url <api> <owner/repo.git>
    the clone URL for a repository, and the one line of git configuration that makes pushing work; the credential is never put in the URL
  • choir node serve [--state <dir>] [--port <n>] [--create <owner/repo.git>] [-- <daemon flags>]
    run the node in this terminal, deriving root, credential and trusted keys from what choir init wrote; execs the daemon so signals and the exit code reach the real process
  • choir node install [--state <dir>] [--port <n>] [-- <daemon flags>]
    hand the node to launchd (macOS) or a systemd user unit (Linux) so it survives logout, crash and reboot; the unit runs choir node serve
  • choir node tls <domain> --user <account> [--port <n>] [--dry-run | --staging]
    obtain a Let’s Encrypt certificate for this node and wire up renewal: certbot, a deploy hook that re-projects the pair and restarts the node, and the marker node serve reads; the only command here that expects root, and --user is required
  • choir node stop
    stop the supervised node for this boot, leaving the unit in place; node uninstall is the one that ends it
  • choir node restart
    reload the unit and start it again, which is how a rebuilt binary reaches the running node
  • choir node uninstall
    stop the node and remove its unit; the state directory, with the keys, repositories and op log, is kept
  • choir node logs [<lines>] [--state <dir>]
    the tail of the node’s log; defaults to the last 30 lines
  • choir node status [<api>]
    health, the commit serving, the sequencer’s position and its p99 against the 100 ms gate, and how much this credential can see
  • choir doctor [<api>] [--state <dir>]
    check everything the other commands assume: the binaries shelled out to, the auth file and its mode, and whether a node answers; each failure prints the fix; on a hosting machine it adds bind address, TLS, certificate expiry, linger, unit state and whether the public URL answers
  • choir backup verify <backup-dir>
    whether a backup can be restored from: the four files, the manifest checksum, the hash chain, the policy archive and every git bundle, refusing a backup that carries a key or credential; every check local
  • choir backup restore <backup-dir> <target-root>
    turn a backup back into a node and prove it by accepting a real push: reads and refuses before writing, unbundles git objects before the first boot, rehearses on a port it picks; exit 3 means a secret only you can supply is missing
  • choir repair <log-file> --verify | --truncate-tail
    inspect a stopped node’s op log, or repair a tail that was still being written; --verify changes nothing, --truncate-tail quarantines the partial record before cutting, and damage anywhere but the tail is refused

Most commands take the node’s URL first. Put node = <url> in .choir/config, in the working directory or any parent, and it is filled in when omitted. choir <command> --help prints one command’s spec.

Exit codes: 0 accepted, 1 the node rejected (its JSON error body is printed), 2 usage error.

Live surface on a running node: GET /llms.txt. Sync verification: SYNC.md / GET /sync.md.

§MCP adapter

A synchronous stdio adapter mapping generated tools onto the same HTTP endpoints; no second implementation, no session state.

choir-mcp http://127.0.0.1:8417 --auth-file ~/.choir/auth --auth-user choir

Serves the legacy handshakes and the stateless 2026-07-28 request path. Tool order and schemas come from crates/choir-cli/src/surface.rs.

Modules§

acl
Regenerating the readable half of an ACL file (D46). Regenerating the readable half of an ACL file (D46).
backup
choir backup verify — is this copy restorable?
docs
choir docs: the book, and the API documentation inside it.
doctor
choir doctor — one command that answers “why did that fail?”.
host
choir host — a fresh machine to a running node, in one command.
init
choir init — from nothing to a node you can push to.
join
The first five minutes: the invite link, and what this machine is.
mcp
Synchronous stdio MCP adapter over a choir node’s HTTP API.
node
choir node — the operator half of the command line.
prompt
The one place in this binary that may ask a person a question.
propose
One-command proposal from a git checkout. One-command proposal: the inference that turns a git checkout into a change, a pushed ref and a review request.
restore
choir backup restore — turn a backup back into a node, and refuse to say it worked until the restored node has accepted a write.
runner
One runner seam: the part of an orchestrator adapter that is not about the orchestrator.
serve
choir node serve — run the daemon without spelling out its flags.
style
Colour, and the “did you mean” a refusal needs to be useful. Terminal styling for the parts of choir a person reads (D58).
supervise
choir node install — hand the node to whatever supervises services on this machine, so it survives a logout, a crash and a reboot.
surface
The agent-facing surface, as data, and the generators that render it.
tls
choir node tls — the one step that needs root, and nothing else does.
triage
Client-side triage and next-action derivation over /api/view: a branch-triage and agent-state view of what needs attention.
verify
SYNC.md’s checks over a served log page. SYNC.md’s three checks over one served page, as a function (D17).