--- id: cli title: Six binaries with six naming conventions, and no way for anyone else to add a seventh status: open crates: [didbot-setup, didbot-hookd, didbot-serve, didbot-swarm, didbot-mcp] dependsOn: [] exitCriterion: > A single `didbot` command dispatches every first-party surface as a subcommand, and an unrecognised subcommand resolves to `didbot-` on `PATH` and runs with no credential inherited from the parent. --- # cli There is no `didbot` command. There are `didbot-hook`, `didbot-dev`, `didbot-setup`, `didbot-swarm` and `vibescrobble-mcp`, each its own binary under `crates/*/src/bin/`, named by whoever added it. A person who has installed this project has no single entry point to run, nothing to type to find out what is available, and no way to discover a surface they did not already know the name of. More surfaces are coming — `operator` is being built now, `moderator` is expected — and each one added the current way is another loose name on `PATH`. This epic is about the entry point, not about what the surfaces do. Each of those has its own epic; this one says how a person reaches them. ## Bake the first-party surfaces in; leave the door open Two shapes were considered. Cargo features on one binary select surfaces at compile time, which means a surface can only be added by rebuilding and reshipping this project's binary. External dispatch — `didbot foo` execs `didbot-foo` from `PATH`, the shape `git`, `cargo` and `kubectl` all converged on — lets someone add a subcommand in any language without touching this repository. The near-term decision is to **bake the first-party surfaces in**: one `didbot` binary with `hook`, `dev`, `setup`, `swarm` and `operator` as real `clap` subcommands, sharing argument conventions, help output and error formatting. That is the whole of the immediate work. External dispatch is the escape hatch, and the reason to decide its conventions now rather than when the first extension exists: a calling convention becomes load-bearing the moment anything depends on it, and the first extension written is the one every later extension is copied from. - [ ] **One `didbot` binary with the existing surfaces as subcommands.** `clap` derive, one crate owning the dispatcher, the surface crates keeping their libraries. Shared conventions for the flags that currently differ per binary. - [ ] **`allow_external_subcommands(true)`, with the exec path written and tested even while nothing ships an extension.** An unrecognised subcommand becomes `exec didbot-` with the remaining arguments passed through. Cheap to add now, and it is the thing that turns the convention below from a plan into something a test holds. - [ ] **`didbot --list`, scanning `PATH` for the `didbot-` prefix.** Without it an extension is undiscoverable, which is most of why a person would not write one. - [ ] **Decide what an extension is trusted with, and write it down.** An external subcommand is a program on `PATH` running as the user, and anything named `didbot-*` is reachable by a plausible typo. Two conventions are worth fixing before anything depends on them: **no credential is passed down** — not in argv, not in the environment, so an extension authenticates for itself the way the operator command does — and extensions get the public read surface and their own credential, not the signing keys. This is the same separation [services](services.md) wants between processes, applied at the extension boundary, where it would otherwise be handed straight back. - [ ] **Keep `didbot-hook` working under its own name.** `crates/didbot-setup/src/harness.rs` writes `command: "didbot-hook"` into harness settings files, and `crates/didbot-setup/src/check.rs` checks for that name on `PATH`. Both are already installed on machines. The name needs a stable alias, or a migration in `didbot-setup` that rewrites settings it wrote — and the alias is cheaper, because a harness settings file may be edited by hand or held under version control by its owner. - [ ] **Say what a single binary costs.** Baking the surfaces in means the binary on the server contains the operator's authentication flow, and the binary on a laptop contains the server's key handling. That is acceptable for first-party surfaces and it is not free: it is dead code in both directions, and it is the reason a later surface with a genuinely different threat model should ship as a separate binary reached through dispatch rather than as another baked-in subcommand. - [ ] **`vibescrobble-mcp` is not a `didbot` subcommand.** It is an MCP server a harness launches, not something a person types, and it is named for the other project. It should be left where it is or renamed on its own terms; folding it in would put a machine-launched process behind a human entry point. ## Done Nothing closed yet.