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] [--label L] [--oidc <issuer> <claim>=<value>...] [--fingerprint SHA256:...], 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 that never meet. 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. For a name beneath a
server the same session has the operator's PDS mint a service-auth token,
and that token — never the session — is what the server sees. login
starts the deployment's own sign-in and keeps the session the deployment
minted, which estop and announce present. One is checked by the
operator's PDS, the other by the deployment; neither verb can use the
other's, and only the first admits anything.
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.