Four Specs That Make Any CLI Agent-First
Four Specs That Make Any CLI Agent-First
I ship a lot of CLIs. grepapi is a GTM lead engine. mago runs autonomous agent workers. remotecmd relays shell commands over WebSocket. chatsnip is a chatbot toolkit. automaintainer ships verified PRs.
Every one of them needed the same four things:
- Output an agent can parse — stdout is data, stderr is context, exit codes are semantic, errors are typed.
- A way to learn the tool — not just a command list, but the mental model, the loop, the gotchas.
- A way to report bugs — from the agent or the human, to the tool's own endpoint and a central relay.
- A way to stay current — self-update without re-running an installer, with a nudge when stale.
After implementing these ad hoc in five different tools across two languages, I extracted the patterns into four open specs. Each is a copy-pasteable convention with a normative protocol, a recipe with code snippets, a JSON schema, and a drop-in agent skill. Adopt any subset in about 30 minutes per spec.
The four specs
1. cli-output-spec — the output contract
This is the foundational one. Six rules:
- stdout = data only. JSON or line-based. No progress, no logs, no ANSI when piped. stdout is a versioned API contract.
- stderr = context only. Progress, warnings, nudges. Never data. An agent that ignores stderr entirely still gets the full result.
- Semantic exit codes. 0 = success. 80-89 = input/validation. 90-99 = precondition/resource. 100-109 = external/integration. 110-119 = internal/bug. Agents branch on
$?. - Typed errors.
{"ok":false,"error":{"code":90,"type":"not_authenticated","message":"not logged in","recoverable":false,"suggestions":["myapp signup"]}}. The code matches the exit code.recoverabletells the agent whether to retry.suggestionsgives the exact fix command. - No internal retries. The tool reports the error; the agent decides whether to retry, back off, or escalate.
- help-json. A machine-readable command catalog. Agents query capabilities; they don't read docs.
2. cli-guide-spec — the embedded mental model
help lists commands. help-json lists flags. Neither teaches when to use what, what the daily loop looks like, or what will bite you.
The guide command bakes the full operator manual into the binary itself — the model, the loop, the concepts, the commands, the examples, and the gotchas — as a JSON object embedded at compile time. An agent reads it once and drives the tool with no external context.
Tools that run a server also serve GET /guide and GET /llms.txt (a short breadcrumb that points at /guide). /llms.txt is the agent's front door.
3. cli-feedback-spec — the dual-write relay
Agent-operated tools have no "report a problem" button and no human watching a dashboard. Feedback — from the human or from the agent itself — has nowhere to go.
The feedback command dual-writes: to the tool's own endpoint (each app owns its feedback) and to a central relay (one inbox across every tool). Same idempotency key on both writes. Never fails the caller. Open submission, admin-gated reads.
4. cli-update-spec — content-hash self-update
A stale CLI misses bug fixes, selector updates, and security patches. But agents can't re-run an installer, and humans forget.
The update command uses content-hash versioning (sha256[:12] of the distributed artifact — identical bytes never trigger an update, no version-bump discipline needed). It downloads, verifies the hash, smoke-tests the new binary (<tool> version must run), atomically swaps (current → .bak, new → in place), and writes the new version stamp. A passive stderr nudge appears on server-hitting commands when a newer version is available — throttled, best-effort, never blocking.
No silent auto-update. The default is manual (nudge only).
How they fit together
An agent lands on a fresh machine with only the binary:
myapp guide # learn the tool (embedded, no docs site)
myapp help-json # discover commands (machine-readable catalog)
myapp leads --json # drive the tool (data on stdout, errors typed)
myapp feedback "…" # report issues (dual-write to app + relay)
myapp update # stay current (content-hash, atomic swap)A tool that adopts all four is fully agent-first: learnable, drivable, reportable, and self-updating.
Why specs, not a library?
My tools are written in Go, machin (a custom language), and bash. A shared library would need to be ported to all three. A spec is a contract — the JSON shape and the behavior — not the implementation. Each tool implements it in its own language, and they all conform.
The same contract fit a Go binary with syscall.Exec in-place swap (mago), a panel-driven WebSocket update (automaintainer), and a bash CLI with tarball swap (grepapi) — without changing shape. That's the reason it's written down.
Each spec is independently adoptable
You don't need all four. If you only need self-update, adopt cli-update-spec alone. If you only need agent-friendly output, adopt cli-output-spec. The specs don't depend on each other.
But if you're building a new CLI from scratch, start with cli-output-spec (the output contract is foundational), then add the others as needed.
Reference implementations
| Tool | Language | output | guide | feedback | update |
|---|---|---|---|---|---|
| grepapi | machin + shell | ✓ | ✓ | ✓ | ✓ |
| mago | Go | ✓ | ✓ | — | ✓ |
| automaintainer | Go | ✓ | ✓ | — | ✓ |
| remotecmd | Go | ✓ | ✓ | ✓ | — |
| chatsnip | machin | ✓ | ✓ | ✓ | — |
grepapi is the first tool to adopt all four. It's the reference implementation for the full family.
The shared philosophy
All four specs follow the same principles:
- Small on purpose — one command, one endpoint, one JSON shape. Addable in 30 minutes.
- Language-neutral — the contract is the JSON shape and the behavior, not the implementation.
- Independent — each spec is adoptable alone. No cross-dependencies.
- Agent-first — JSON by default, stderr for context, semantic exit codes, self-describing.
- Unix-native — stdout is a pipe-friendly API, stderr is ignorable context, exit codes are actionable signals. The old philosophy, tightened for LLMs.
Adopt one
Each spec repo has the same structure: PROTOCOL.md (the normative contract), RECIPE.md (the how-to with snippets), llms.txt (the agent entry point), a JSON schema, and a SKILL.md (a drop-in agent skill).
- github.com/javimosch/cli-output-spec
- github.com/javimosch/cli-guide-spec
- github.com/javimosch/cli-feedback-spec
- github.com/javimosch/cli-update-spec
MIT licensed. Copy them, fork them, adopt them. If your tool implements one, add it to the reference implementations table — I'll merge the PR.