Five open conventions that make any CLI fully agent-first: learnable, drivable, reportable, and self-updating — and finally, measurable. Adopt any subset in 30 minutes per spec.
An agent lands on a fresh machine with only the binary. Six commands, six specs.
Each is independently adoptable. Together they make a CLI fully agent-first.
The output contract. stdout = data only (JSON, parseable, versioned). stderr = context only (progress, logs). Semantic exit codes (80–119). Typed errors with recoverable and suggestions. help-json introspection. No internal retries.
The embedded mental model. <tool> guide bakes the full operator manual into the binary — the model, the loop, the concepts, the gotchas. An agent reads it once and drives the tool with no external docs. Optional HTTP: GET /guide + GET /llms.txt.
The dual-write relay. <tool> feedback sends feedback to the tool's own endpoint AND a central relay — same idempotency key, never fails the caller. Open submission, admin-gated reads. FEEDBACK_RELAY=off to opt out.
Content-hash self-update. <tool> update downloads, verifies sha256[:12], smoke-tests, atomically swaps (current → .bak, new → in place). Passive stderr nudge when stale. No silent auto-update — manual by default. v1.1 adds <tool> install/uninstall — relocate or remove a binary already in hand (default ~/.local/bin, idempotent, never sudo).
An honest usage signal. Downloads, clones and stars all report the same zero whether nobody wants your tool or the install command has been broken since the first release — and an agent-operated CLI has no human who will ever file an issue. One POST, seven non-identifying fields, disclosed on stderr before the first send, and <tool> telemetry prints the real next payload so a reviewer verifies instead of trusting. DO_NOT_TRACK=1 honoured; CI defaults to off.
Daemon lifecycle. <tool> serve --host --port binds loopback by default — never 0.0.0.0. GET /_health + POST /_shutdown, token-gated the instant the bind isn't loopback. <tool> daemon start|stop|status polls health instead of a fixed sleep, fully idempotent. Formalizes what chatsnip and machin-cms had already reinvented independently.
Every spec ends with “a tool conforms if it…”. cli-spec-conformance turns that summary into assertions it runs against your binary.
$ cli-spec-conformance check ./mytool
[pass] cli-output-spec/help-json-exit (MUST)
[FAIL] cli-output-spec/unknown-command-exit (MUST) — exit 1 — expected 80-89
[FAIL] cli-guide-spec/guide-json (MUST) — `guide` stdout is not JSON
…
$ echo $?
90
Black-box, so it is language-neutral by construction: it reads stdout, stderr and exit codes, and for the daemon spec it starts a real server on a free loopback port and talks to it. The loopback-default rule is checked against the kernel’s listen table, not the tool’s own claim. The embedded-guide rule is checked by re-running guide behind a dead proxy — a guide that fetches at runtime fails the rule that says it must not.
Covers three of the six: output, guide and daemon — the three that need nothing but your binary. feedback, update and telemetry each depend on an external service (a relay, a release host, a collector), so conformance there is still checked by hand.
Same contract, six tools, each a worked example — and, below, the same contract implemented fifteen times over in fifteen languages.
| Tool | Language | output | guide | feedback | update | telemetry |
|---|---|---|---|---|---|---|
| grepapi | machin + shell | ✓ | ✓ | ✓ | ✓ | — |
| mago | Go | ✓ | ✓ | — | ✓ | — |
| automaintainer | Go | ✓ | ✓ | — | ✓ | — |
| remotecmd | Go | ✓ | ✓ | ✓ | ✓ | — |
| machin-secure | machin | ✓ | ✓ | ✓ | ✓ | ✓ |
| chatsnip | machin | ✓ | ✓ | ✓ | — | — |
“Language-neutral” is easy to claim and hard to show. The CLI+UI boilerplate family builds one reference app — a CLI with an embedded web UI — in fifteen languages, and every one of them scores 28/28 on cli-spec-conformance in its own CI.
Five of those — Nim, V, Crystal, Dart and .NET — were written straight from the protocol text with no compiler on hand, and passed on the first CI run. That is the strongest evidence available that the contract is behavioural rather than shaped around one runtime. Conformance scores and binary measurements are produced by boilerplate-cli-ui-fleet.
The old Unix philosophy, tightened for LLMs.
One command, one endpoint, one JSON shape. Addable in 30 minutes.
The contract is the JSON shape and the behavior, not the implementation.
Each spec is adoptable alone. No cross-dependencies.
JSON by default, stderr for context, semantic exit codes, self-describing.
stdout is a pipe-friendly API. stderr is ignorable context. Exit codes are actionable.
Copy them, fork them, adopt them. No attribution required.