10 Principles for Agent-Native CLIs - by Trevin Chow
Trevin’s Notes
SubscribeSign in
10 Principles for Agent-Native CLIs<br>Designing CLIs when agents are the primary user
Trevin Chow<br>May 01, 2026
Share
Last month I wrote 7 Principles for Agent-Friendly CLIs. Since then I’ve been deep in CLI work, watching agents use them, and seeing them break in interesting ways.<br>Mid-April, Cloudflare published The CLI for all of Cloudflare, describing how they rebuilt Wrangler around a TypeScript schema that generates the CLI, the SDKs, the Terraform provider, and the MCP server from one source. Their Code Mode MCP serves their entire ~3,000-operation API in under 1,000 tokens. They added /cdn-cgi/explorer/api, an OpenAPI-shaped runtime endpoint for agents. And they enforce naming rules across the entire CLI surface: always get, never info; always --force, never --skip-confirmations; always --json. Their framing for why: “manually enforcing consistency through reviews is Swiss cheese.”<br>Shortly after, HeyGen launched their CLI, and I’ve been using it heavily since. Generating videos through agents, polling jobs, routing artifacts to webhooks. The practical experience is what earned it a spot here. Plenty of companies ship CLIs; this one’s been the most agent-pleasant I’ve used.<br>The original 7 principles I wrote about were defensive: the things a CLI has to get right, or agents pay for it on every call. Don’t hang on a TTY check, return JSON, make errors actionable, bound the output. That layer is still necessary but not enough.<br>The next layer is about compounding instead of not breaking. The CLI gets more useful the more agents use it, because agents come with persistent identity, asynchronous workflows, output that has to land somewhere, and friction that maintainers should hear about.<br>The 10 principles below come from my own CLI work (new project coming soon!) alongside what Cloudflare and HeyGen have published. Organized into 2 tiers. Five condense the original 7; five are new.<br>Thanks for reading Trevin’s Notes! Subscribe for free to receive new posts and support my work.
Subscribe
Tier 1: Table Stakes
Don’t break the agent. Agents are good at figuring things out, but when these aren’t met, the deck is stacked against them. Every gap costs more tokens, more retries, and more failure modes that don’t surface until production.<br>1. Non-interactive by default
Commands have to run without interactive prompts when an agent invokes them. When a subagent spawns a background process, there’s nothing answering the prompt. The command hangs.<br># Hangs forever waiting for a confirmation that will never come<br>$ mycli post delete post_8f2a What good looks like: --no-input or equivalent on every command that might prompt; honest TTY detection that treats non-TTY as headless; --yes for confirmation bypass; structured input via flags or files for anything you used to collect through interactive menus. Cloudflare standardizes on --force for destructive bypass and explicitly bans --skip-confirmations. Pick the convention, then enforce it.<br>Silent hanging on a prompt is a blocker; inconsistent prompt-bypass behavior across subcommands is friction; a comprehensive non-interactive mode the agent can rely on without per-command lookups is the optimization target.<br>2. Structured, parseable output
A nicely aligned table with ANSI colors is for humans. An agent extracting a post ID needs JSON.<br># Data on stdout, parseable directly with jq<br>$ mycli post list --json | jq ‘.posts[0].id’<br>“post_8f2a”<br># Errors go to stderr, exit codes signal failure class<br>$ mycli post get post_does_not_exist --json<br>$ echo $?<br># stderr → “error: post not found: post_does_not_exist”What good looks like: --json on every data-returning command; exit code 0 for success, non-zero for failure with a stable taxonomy if you can manage it; results to stdout, diagnostics to stderr; ANSI suppressed when output isn’t a terminal. The newer wrinkle, from Cloudflare: pick one flag. Always --json, not --format=json for some commands and --output json for others. Inconsistency at this layer is its own category of brokenness.<br>No structured output at all is a blocker; coverage gaps where some commands are JSON-capable and others aren’t is friction; uniform --json across the CLI with clean stdout/stderr separation and a documented exit code taxonomy is the optimization target.<br>3. Errors that teach, and enumerate
The original principle was “fail fast with actionable errors.” That still holds, with one refinement I missed the first time. When the failure is “you passed an invalid value for X,” the error should include the valid set.<br># Unhelpful: agent has to read --help, parse, guess, retry<br>$ mycli post create --json --visibility=secret --content=”hi”<br>error: invalid visibility<br># Better: error names the valid set, agent self-corrects in one retry<br>$ mycli post create --json --visibility=secret --content=”hi”<br>error: --visibility must be one of: public, private, unlisted (got:...