# Agent CLI Builder

> Build, retrofit, or score an agent-native CLI for AI agents (Claude Code, Cursor, codex, opencode). Use when scaffolding a CLI for agent consumers, bringing a human-first CLI up to agent-first standards, or assessing an existing CLI's agent readiness. Do NOT use for general CLI style or one-off shell scripts.

- Skill: `zekai-zhao-321/agent-cli-builder` (Agent Skill, multi-file: 51 files)
- Install (CLI): `npx skillmds@latest add zekai-zhao-321/agent-cli-builder`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zekai-zhao-321/agent-cli-builder/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: Zekai-Zhao-321 (https://skillmd.com/u/zekai-zhao-321)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zekai-zhao-321/agent-cli-builder

---


# agent-cli-builder

Build an agent-native CLI: one an AI agent can invoke unattended, parse mechanically,
recover from when wrong, and learn progressively from a shipped skill — not from a giant
prompt.

> v0.5 is a production rewrite: every pattern here was either validated or corrected by
> building a large multi-service API CLI to this skill's spec, then auditing where
> v0.4's advice held, was incomplete, or was wrong. New pillars: spec-driven
> registration, the explain doctrine, enforcement-over-convention, the error taxonomy
> with structured recovery, and the research-first PRD method.

## What changes when an agent is the user

Six facts about the new user explain why the same CLI a human happily uses for an hour
can break agents in five turns. They motivate every pattern below.

1. **They pay per token.** Every byte of output costs context. A CLI for agents puts data
   on stdout and *only* data on stdout — everything else is a tax on the next decision.
2. **They retry.** Agents loop on failures. Failures must be classifiable from structured
   fields, not English — and the CLI itself must be retry-safe (a blind 5xx retry on a
   `POST /send` can double-send; see the idempotency policy below).
3. **They fail differently.** Agents hallucinate plausible inputs — path traversals in
   IDs, phantom endpoints, near-miss enum values. The CLI is the last line of defense
   against confidently-wrong input. Validation has to be mechanical, not advisory —
   and a typo'd `--scope` must exit 2, never silently fall through to a default or an
   upstream 404.
4. **They learn progressively.** A human reads `--help` once and remembers. An agent
   reloads knowledge every conversation unless it lives where it loads on demand: the
   skill for routing, `--help` for invocation, schema introspection for shapes.
5. **They — *you* — carry human-trained biases.** Your training data is full of
   human-friction-driven defaults that don't apply to you. Consult your own experience
   first: what wastes your context, what you confidently get wrong, where a wrapper
   would just be tax. See [references/think_like_an_agent.md](references/think_like_an_agent.md).
6. **They live in turn-based harnesses.** A command that blocks on a browser login or a
   90-second poll eats the turn. Auth needs split-flow (emit the URL/code, end the turn,
   resume later); long jobs need async handles — and *every handle the CLI emits must be
   consumable by the CLI itself* (a monitor URL with no `wait` verb is a dead end).

The architectural consequence is a three-layer split:

```
+----------------+     +----------------+     +----------------+
|     Skill      | --> |      CLI       | --> |      API       |
|  (the manual)  |     | (the contract) |     |  (the truth)   |
+----------------+     +----------------+     +----------------+
   routing, recipes,      stdout=data,           your service
   gotchas, negative      stderr=UX,
   space                  semantic exits
```

MCP is optional infrastructure: a second adapter over the same core, not a replacement.
Build the CLI first; see [references/mcp_layer.md](references/mcp_layer.md).

## See like an agent

The agent reading this skill is itself the kind of mind it's designing for — the world's
best ground truth on what an agent CLI needs. The standing test for every tool, flag, or
doc section: **"what friction is this addressing, and does the agent actually have that
friction?"** Don't wrap what the agent already speaks (SQL and query DSLs, JSON, pipes, dates).
Do wrap what it lacks (eyes for bulk content, memory across turns, protection from its
own confident errors).

Tool-loading models differ by harness (MCP-eager vs staged discovery vs CLI-via-shell vs
skills), and each era's advice assumed one of them — "minimum viable tool set" is
MCP-eager advice that does *not* transfer to CLI-via-shell + skills, where surface size
is nearly free. Full lens, case studies, and the temporal frame:
[references/think_like_an_agent.md](references/think_like_an_agent.md).

## The core patterns

Apply all of these; they hold regardless of domain.

**Stream-by-purpose.** Data on stdout, UX (progress, retry notices, warnings) on stderr.
`cli foo | jq` only works when stdout is exclusively the payload.

**Auto-JSON when piped.** Non-TTY stdout ⇒ JSON envelopes. Every major harness spawns
shell tools with plain pipes; this single heuristic detects "an agent ran me". Be honest
about what you actually implement: if there is no text renderer, document JSON-only and
delete the flag values that don't exist (a `--output table` that silently emits JSON is
a lie agents will trip on).

**Structured envelopes.** Success: `{ok: true, data, metadata: {source, identity?}}`.
Error: `{ok: false, error: {code, exit_code, type, subtype, message, hint, suggestions,
details}, metadata}`. The shape is uniform across commands — an agent that learned
`.data` once never relearns it. Truncation is self-describing (`_truncated`), pagination
carries `next_page` in-band.

**Errors agents can branch on.** A closed **category** set (`type`) is the sole source of
exit codes; a declared **subtype** registry refines it; **structured `details`** carry
the machine recovery data: `retryable`, `retry_after_ms`, `missing_scopes[]`, `param`,
`valid_values[]`, `upstream_code`, `request_id`. `message` and `hint` are prose an agent
must never need to parse. Batch commands have a **third outcome**: partial failure —
`ok:false` that still carries per-item results in `data`, so "retry only the failed
items" is mechanical. Exit-code *numbers* are convention-local (production agent CLIs
disagree on them); the portable contract is the structured fields. Full taxonomy:
[references/output_contract.md](references/output_contract.md).

**One spec, many surfaces.** Register commands from a declarative spec (name, flags,
risk, scopes, examples, a request builder, projection) and *generate* everything else:
the `--help` text, the schema entry, the safety gate, the dry-run plan, and the fixtures
your docs are linted against. Hand-written surfaces drift apart per file; generated
surfaces cannot. New commands become mostly data. This is the highest-leverage pattern
in this skill: [references/spec_driven_commands.md](references/spec_driven_commands.md).

**Presets reveal their encoding.** Compose execution server-side (batch endpoints, saved
queries); decompose *explanation* client-side. Every helper and preset supports
`--explain` / `--dry-run` emitting the exact upstream requests it would make — each one
replayable verbatim through the raw escape hatch — plus a `post_processing` note for
what happens client-side. The graduation path (preset → `--explain` → customized raw
call) makes the whole surface self-teaching, and the same plan object is the safety
preview *and* the wire-test contract. See
[references/spec_driven_commands.md](references/spec_driven_commands.md).

**Safety by blast radius.** Classify writes by consequence, not by file age or habit:
irreversible or visible-to-other-principals ⇒ a confirmation gate (distinct exit code;
the *plan* is the preview; the agent re-runs the same argv with `--yes` after consent);
reversible self-scoped writes ⇒ `--dry-run` floor, no gate; exactly-invertible pairs
(flag/unflag, pin/unpin, react/unreact) need no gate at all. Batch writes declare counts
("would delete 234 messages"). And the HTTP layer is part of safety: writes never
blind-retry on 5xx (429/throttle is the only safe automatic retry for a write; offer an
explicit `idempotent` opt-in), honor `Retry-After` in both its forms, and send
idempotency keys/transaction ids on creates when the API supports them — *your own retry
layer is why*. See [references/safety_and_async.md](references/safety_and_async.md).

**Fail loud on partial degradation.** A composite read (home-screen view, multi-source
briefing) must report which sections failed (`_partial: [{section, status, code}]`) —
an agent cannot distinguish "you have no direct reports" from "the directReports call
403'd", and silent empties make it confidently report wrong facts.

**Views as cache.** The default read is a one-call "home screen": one batched request
returning every ID and enough context to act, with counts, validated `--scope`s, and
caps from a single limits table. The view is the agent's situational awareness — it is
always fresh, stores nothing, and mints every ID the next command needs (a create verb
that requires an ID no view provides is a broken loop).

**Identity is a first-class dimension.** When the CLI can act as more than one principal
(user vs app/bot, profiles, tenants), echo the acting identity in every envelope,
diagnose empty-result confusion by identity first, and make permission-error remediation
identity-aware. Deployment-generality rule: declared scopes are *metadata* (docs,
previews, skill tables) — never gates; nothing is hidden per tenant; denial surfaces at
call time as a typed error. Tenant-specific facts live in skill gotchas as
default-profile notes, never in code. See
[references/auth_strategies.md](references/auth_strategies.md) for the auth menu and the
turn-aware split-flow login.

**Raw-payload pathway + raw escape hatch.** Every mutating command accepts `--json` /
`--params-file` / stdin with the full upstream payload (schema-validated so malformed
input exits as validation, with field-level errors — never a vague upstream 400). And
ship `cli api <METHOD> <path>`: agents speak the upstream API natively; the escape hatch
is what makes `--explain` output replayable.

**Schema introspection at runtime.** `cli schema show <method>` (request/response
shapes), `cli schema output` (the literal envelope), and — with spec-driven registration
— `cli schema commands [path]` (risk, scopes, flags, runnable examples straight from the
registry). The skill must never restate any of this; it links here.

**Context-window discipline.** Default-small, enrichment-by-default + hydration-on-demand
(project lists to the fields an agent acts on; `--raw` escapes), plain-text forms for
rich content, noise filtered with opt-outs (`--include-system`), content over ~10 KB to
`--out <file>` with a `{path, size_bytes}` receipt — never inline in the envelope.

**Input hardening.** Reject traversals/control chars in IDs, URL-encode every path
segment, sandbox output paths, validate enums with `valid_values` in the error. Build
like the agent is adversarial — not malicious, just confidently wrong. Know your
argument-parser's sharp edges (camelCase storage, parent-flag capture, silent unknown
values) and ban the foot-guns by lint, not convention.

**Enforcement over convention.** Any rule that *can* be checked by lint, test, or
generation *must* be — a rule that lives only as prose is a rule that drifts. In
production this is the difference between a contract and a wish: syntax bans for the
known foot-guns, import-boundary tests with self-expiring exception tables, doc/registry
drift tests, and **executable examples** — CI runs every example your specs and skill
ship, because a wrong example is worse than none (agents copy it verbatim; a test suite
can even enshrine a hallucinated endpoint by mocking it). See
[references/contract_enforcement.md](references/contract_enforcement.md).

**Async-tasks split + closed loops.** Anything >5s gets an async handle (harness
timeouts are real: some default to 10 seconds). Corollary proven twice in production:
**never emit a URL, operation id, or monitor handle the CLI cannot itself consume** —
pair every async response with a `wait`/poll verb or bounded auto-poll with an honest
"still running, here's the replayable poll" receipt.

**Ship a SKILL.md — and keep it honest.** The skill routes intent and encodes judgment
(recipes, gotchas, negative space, cross-routing); `--help` owns invocation with 2–3
executable examples; schema owns shapes; per-command affordance ("avoid when / needs
first") renders into `--help`. Partition by *question answered + load time*, never by
topic; each example exists in exactly one place. Version-stamp the skill against the
binary and emit an in-band `_notice` when they drift. See
[references/shipping_skills.md](references/shipping_skills.md).

## Build against the API's reality, not its docs' vibes

Before deepening any service surface, write a research-first PRD: mine the official API
docs (including per-operation permission tables for least-privileged scopes), inventory
the human client (open-source clients and even Electron *wrappers* of the web app are
gold — their selectors enumerate the real feature surface), record **negative space as a
deliverable** ("this has no API" — with evidence — is a shippable outcome that stops
agents from hunting), and put every tenant-dependent or docs-ambiguous claim in a
**probe-pending table** with the exact command to verify. Docs can be stale and even
self-contradictory; cite every claim, and treat live probes as a validation checklist —
not an implementation blocker. Method, templates, and the 15-dimension maturity
scorecard: [references/research_first_prd.md](references/research_first_prd.md).

## Domain-determined choices

These are *choices*, not invariants — apply the lens per tool:

- **Tool granularity: narrow-many vs wide-one.** A docs reader earns 11 narrow tools
  (manufactured progressive disclosure); a SQL-shaped CLI earns one `cli sql` plus
  presets (the query language is friction-free for you). Presets are fine *because* they
  reveal their encoding — a preset that can't explain itself is a dead end; one that can
  is an on-ramp.
- **Helper tools vs raw API.** Compound helpers win when a recurring workflow saves
  multi-turn coordination; raw passthrough wins when the capability is already fluent.
  Ship both; let `--explain` bridge them.
- **Read-tool vs write-tool weighting.** Read-heavy CLIs live or die on retrieval shape
  (projection, hydration, truncation); write-heavy ones on the safety ladder (gates,
  plans, idempotency). Weight your effort by the actual mix.

Worked case studies: [references/think_like_an_agent.md](references/think_like_an_agent.md).

## Choose your path

| You're trying to... | Read |
|---|---|
| Build a new CLI from scratch | [references/build_path.md](references/build_path.md) |
| Deepen a service surface against a real API | [references/research_first_prd.md](references/research_first_prd.md) |
| Design the command layer (specs, explain, plans) | [references/spec_driven_commands.md](references/spec_driven_commands.md) |
| Make the contract un-driftable | [references/contract_enforcement.md](references/contract_enforcement.md) |
| Bring a human-first CLI up to standard | [references/retrofit_playbook.md](references/retrofit_playbook.md) |
| Score an existing CLI | [references/evaluation.md](references/evaluation.md) |
| Add an MCP adapter (share-core) | [references/mcp_layer.md](references/mcp_layer.md) |
| Author the shipped SKILL.md | [references/shipping_skills.md](references/shipping_skills.md) |

Templates live in [`templates/`](templates/) (contract code only; domain patterns in
[`templates/RECIPES.md`](templates/RECIPES.md)).

## Decision points the agent must surface

- **Raw payloads or convenience flags?** Both; raw payloads are the agent contract.
- **Do we also need an MCP server?** Default CLI-only; share-core MCP only for a named
  shell-less consumer. Never MCP-by-shelling-out; never MCP-only.
- **Errors on stdout or stderr?** Pick one and document it. Stdout-JSON gives one stream
  to parse but complicates predicate commands and `2>/dev/null` hygiene; stderr
  envelopes keep the answer stream pure. Both ship in production CLIs; mixing per
  command does not.
- **Async or blocking for long jobs?** Async-first, always — with the closed-loop rule.

## Anti-patterns

Push back if the user proposes any of these:

- Interactive prompts as the default path.
- Stdout polluted with banners, spinners, progress text.
- Undocumented exit codes / "exit 1 means error" / branching on `message` prose.
- Unknown enum values silently falling back to defaults (typos must exit as validation).
- A single huge `list everything` with no filters, projection, or pagination.
- Skills that restate the API surface (they rot; link to `cli schema`).
- Examples that have never been executed (they rot *worse* — agents copy them).
- `--force`/`-y` as the only safety control; gates without plan previews.
- Blind retry of non-idempotent writes; emitting async handles with no poll verb.
- Silent partial degradation (empty arrays where a section actually failed).
- Conventions enforced by prose ("the agent should never…") instead of lint/tests.
- Per-tenant behavior branches; capability hidden because consent *might* be missing.

## Reference index

- [references/think_like_an_agent.md](references/think_like_an_agent.md) — **the lens**: cognitive profile, friction classes, granularity case studies, the temporal frame
- [references/research_first_prd.md](references/research_first_prd.md) — docs mining, client inventory, negative space, probe-pending tables, the maturity scorecard
- [references/spec_driven_commands.md](references/spec_driven_commands.md) — one spec → help/schema/gate/plan/tests; the explain doctrine; parser foot-guns
- [references/contract_enforcement.md](references/contract_enforcement.md) — drift machines: lint bans, boundary tests, drift tests, executable examples
- [references/build_path.md](references/build_path.md) — 12-step cold-start checklist, intake interview, language picker
- [references/cold_start_research.md](references/cold_start_research.md) — researching the user's repos before intake
- [references/output_contract.md](references/output_contract.md) — envelopes, the error taxonomy (categories/subtypes/details), partial failure, exit codes
- [references/input_and_payloads.md](references/input_and_payloads.md) — input pathways, schema introspection, `--include`, `--help` discipline
- [references/safety_and_async.md](references/safety_and_async.md) — blast-radius gates, plans, idempotency-aware retry, async closure, prompt-injection hygiene
- [references/auth_strategies.md](references/auth_strategies.md) — auth menu, split-flow device code, identity duality, secret masking
- [references/mcp_layer.md](references/mcp_layer.md) — share-core thin-adapter rule
- [references/shipping_skills.md](references/shipping_skills.md) — the doc-surface partition, skill-lint, drift notices, the five drift tests
- [references/retrofit_playbook.md](references/retrofit_playbook.md) — human-first → agent-first in shippable diffs
- [references/evaluation.md](references/evaluation.md) — agent-readiness rubric + real-task evals

## Reference CLIs worth studying

| CLI | Domain shape | Patterns worth studying |
|---|---|---|
| **[`lark-cli`](https://github.com/larksuite/cli)** (Lark/Feishu) | Platform CLI, 200+ commands, 26 shipped skills | The most complete production error contract published (RFC-7807-style categories + declared subtypes + lint-enforced registry + `hint`/`param`/`missing_scopes`); `_notice` in-band update/skill-drift channel; per-command affordance files rendered into help; split-flow device-code auth written for turn-based harnesses; identity duality (`--as user\|bot`) with strict-mode pruning; dry-run E2E test tier |
| **[`gws`](https://github.com/googleworkspace/cli)** (Google Workspace) | Platform CLI over a large multi-service API | Dynamic schema from the Discovery doc; layered skills; raw-payload-first; NDJSON pagination; structured dry-run |
| **[`cf`](https://blog.cloudflare.com/cf-cli-local-explorer/)** (Cloudflare) | ~3,000 operations | Schema-as-source-of-truth: one schema generates CLI, SDKs, Terraform, MCP — the industrial-scale version of spec-driven registration |
| **[`ant`](https://github.com/anthropics/anthropic-cli)** (Claude Platform) | API CLI generated from OpenAPI | 7-format output with `--format-error`; GJSON `--transform`; `@file://` substitution; 5-tier credential precedence; careful pipe detection |
| **[`heygen-cli`](https://github.com/heygen-com/heygen-cli)** (video jobs) | Single-product, long-running jobs | `--request-schema`/`--response-schema` offline; JSON receipts for binary downloads; `--wait` with backoff |

Each solves a different shape. The right reference is the one whose domain resembles
yours — and none of them is canon: this skill's v0.5 exists because building against
v0.4 revealed real gaps. Treat v0.5 the same way.

