id: cli
title: One didbot command, and a binary for each place the code runs
status: shipped
crates: [didbot-cli, didbot-dispatch, didbot-operator, didbot-agentd]
dependsOn: []
exitCriterion: >
didbot <verb> runs the verb's binary from PATH with argv and no
credential inherited from the shell; a first-party verb whose binary is
absent names what to install; didbot --list shows every installed
didbot-* with the version it reports; and the verb binaries parse, exit
and render errors by didbot-cli's conventions. #
cli #
A person types didbot. What runs is one binary per place the code runs,
each versioned and installed on its own:
| Ships | Runs where | Verbs |
|---|---|---|
didbot-pds |
the server, in the image | its own flags; no verb reaches it |
didbot |
operator laptop, agent host | dispatcher only: --list, --version, help <verb> |
didbot-operator |
operator laptop | didbot operate <name> [<operator>] [--server <hostname>] [--kind K] [--oidc <issuer> <claim>=<value>...] [--fingerprint SHA256:...] [--creates [K...]] [--creates-beneath [K...]], didbot operate --check <name>, didbot login, didbot estop, didbot announce |
didbot-oauth |
agent host | didbot oauth pending, didbot oauth show, didbot oauth approve, didbot oauth decline, each with --token-file <path> or --token-stdin |
didbot-register |
the machine that will be the account | didbot register <kind> <name> [--under <parent>] [--token-file <path> | --token-stdin] [--server <hostname>] [--timeout <seconds>], the server from --server or DIDBOT_PDS |
didbot-agentd |
agent host, a service | none; a daemon the unit starts |
Three install sets follow from the table. An operator installs didbot
and didbot-operator. An agent host installs didbot and didbot-agentd,
which ships didbot-oauth and didbot-register beside the daemon. A
developer builds the workspace and reaches everything through scripts/.
This epic is about the entry point, not what the verbs do. Each verb has its own epic; this one says how a person reaches it.
Four rules #
The dispatcher passes argv and nothing else. didbot <verb> … execs
the verb's binary with the remaining arguments. It adds nothing to the
environment and, before the exec, removes every variable in
didbot_cli::env::CREDENTIALS. Each verb authenticates for itself:
didbot-operator with its own session files, didbot-oauth over the
daemon's socket. A program on PATH that a typo on didbot- can reach
therefore inherits no credential from the shell, and neither does a
first-party verb — which is why didbot-oauth's daemonless mode, the one
the environment configures, is reached by running that binary by name.
Each binary is independently versioned and self-describing. Every
didbot-* answers --version with <name> <version> and --help on
stdout. didbot --list scans PATH for didbot-*, asks each, and prints
name, version and path; a binary that does not answer is listed with ?.
Skew is visible and never fatal. didbot help <verb> runs the verb's own
--help.
Shared conventions live in one library crate, didbot-cli. clap
parsing through didbot_cli::parse, the --server <HOSTNAME> and --json
flags, the exit statuses — 0 did it, 1 refused or failed, 2 did not
understand — and didbot_cli::finish, the one line a refusal is written
as. First-party verbs use it so they look like one tool.
First-party verb names are known to the dispatcher even when their code
is absent. didbot carries a table, didbot_dispatch::verbs::FIRST_PARTY:
operate, login, estop and announce run didbot-operator <verb>;
oauth runs didbot-oauth and register runs didbot-register, each with
what followed. Every row names the crate that ships its binary, so a
first-party verb whose binary is not on PATH answers "install
didbot-operator" or "install didbot-agentd" and exits 1.
Any other word is external dispatch: didbot foo runs didbot-foo from
PATH with argv passed through, and when there is none answers "unknown
command foo; didbot --list shows what is installed" and exits 2. The
table is the mechanism; there are no links between binaries.
Two credentials in one binary, kept apart #
didbot-operator holds two sign-ins. operate is the claim: it
authenticates to the operator's own atproto account through
jacquard-oauth and writes bot.did.operator into their own repository,
keeping that session in didbot-authstore's file. login starts the
deployment's own sign-in and keeps the session the deployment minted, which
estop, account and announce present. For a name beneath a server,
operate writes the record with the first and presents the second to the
server's create. One is checked by the operator's PDS, the other by the
deployment.
didbot register and the daemon hold a third thing that is not a
credential at all: a key that never leaves the machine. The server sees
its public half, and tokens it signs.