
Create Cli
- 33 installs
- 269 repo stars
- Updated June 11, 2026
- gupsammy/claudest
Specify an agent-friendly CLI (flags, subcommands, TTY/NDJSON output, structured errors) before you implement it.
About
create-cli is a reference skill that walks solo builders through a complete CLI specification using the `snapr` filesystem snapshot tool as a worked example. It is meant for anyone shipping a command-line tool alongside Claude Code, Codex, or Cursor who needs predictable `--help`, versioning, quiet/verbose modes, and machine-readable list output—not ad-hoc argument parsing buried in chat. The document structures deliverables into named sections (identity, usage block, subcommand matrix, global flags) so an agent can implement or review the binary against a single source of truth. Emphasis on agent-aware patterns (NDJSON, structured stderr errors with remediation hints) reduces rework when scripts parse CLI output in headless environments. Use it when you are scoping a new CLI or refactoring an existing one for automation; it does not replace implementation, testing, or packaging.
- Worked example CLI spec (`snapr`) covering name, one-liner, USAGE, and subcommand tables
- Documents idempotent vs destructive subcommands (`snapshot`, `restore`, `list`, `delete`)
- Global flags matrix: `--help`, `--version`, `--quiet`, `--verbose`
- Agent-aware output patterns: TTY auto-detection, NDJSON list output, compound output
- Structured errors with executable hints for automation and CI
Create Cli by the numbers
- 33 all-time installs (skills.sh)
- Ranked #341 of 550 CLI & Terminal skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gupsammy/claudest --skill create-cliAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 33 |
|---|---|
| repo stars | ★ 269 |
| Security audit | 1 / 3 scanners passed |
| Last updated | June 11, 2026 |
| Repository | gupsammy/claudest ↗ |
What it does
Specify an agent-friendly CLI (flags, subcommands, TTY/NDJSON output, structured errors) before you implement it.
Files
Create CLI
Design CLI surface area (syntax + behavior), agent-aware, human-friendly.
Phase 1 — Prepare
Read ${CLAUDE_PLUGIN_ROOT}/skills/create-cli/references/cli-guidelines.md. Apply it as the default CLI rubric, including the Agent Ergonomics section.
For new CLI designs, also read ${CLAUDE_PLUGIN_ROOT}/skills/create-cli/references/language-selection.md to inform the language recommendation in Phase 2. Skip it for audits — the language is already chosen.
Proceed when cli-guidelines.md is loaded.
Phase 2 — Clarify
Determine whether this is a new design or an audit from the user's trigger.
New design
Ask, then proceed with best-guess defaults if user is unsure:
- Command name + one-sentence purpose.
- Primary consumer: agent/LLM, human at a terminal, scripted automation, or mixed.
- Input sources: args vs stdin; files vs URLs; secrets (never via flags).
- Output contract: human text by default,
--jsonfor structured output, exit codes. - Backend/data layer: does it wrap an existing API? Source of the surface — OpenAPI/docs, live URL,
or HAR capture (undocumented/browser-fed)? (cli-guidelines.md → Wrapping an existing API.)
- Interactivity: prompts allowed? need
--no-input? confirmations for destructive ops? - Config model: flags/env/config-file; precedence; XDG vs repo-local.
- Language & distribution: ask for the user's preferred implementation language, or offer to
recommend one. Ask whether a single binary (no runtime needed on target machine) is required, or whether a runtime dependency is acceptable. Apply language-selection.md to recommend if the user is unsure. Platform: macOS/Linux/Windows.
If an existing CLI spec or tool description is provided, read it first — skip questions already answered by it.
Data Layer gate — required when the tool reads from a backend. Run the Data Layer Decision scorecard (cli-guidelines.md → Stateful CLIs); record adopt cache or stateless + a one-line rationale. Decide this before drawing the command tree — it changes whether sync/local-read subcommands exist at all.
Audit
Ask:
- CLI name and source location (repo path, or provide
--helpoutput). - Primary consumer: agent, human, or mixed.
- Known pain points or specific areas to focus on.
Then explore the codebase: use Glob/Grep to find command definitions, flag registrations, output formatting, and error handling. Run <cli> --help via Bash to capture actual behavior.
Proceed when answers are confirmed or user is unsure — use best-guess defaults.
Phase 3 — Conventions
Apply the conventions from cli-guidelines.md (loaded in Phase 1), including the Agent Ergonomics section. The rules below are the key conventions to enforce — cli-guidelines.md provides the full rubric for edge cases.
If primary consumer is human-only, the Errors and Reduce Tool Calls subsections are optional — apply them only if the user wants script-friendliness.
Output
- Default output is human-readable text; an explicit
--json/--plainflag sets the data format and overrides TTY state. Cosmetics (color, spinners) may TTY-detect; the data format must not rely on it (see cli-guidelines.md → Agent Ergonomics for the TTY/PTY rationale). - List commands in
--jsonmode use NDJSON (one JSON object per line) — enables streaming andjqpiping without buffering. For paginated results with metadata, a JSON object with anitemsarray is acceptable. If the CLI extends an existing ecosystem that uses JSON arrays (kubectl, aws, gh), match the ecosystem convention. - Primary data to stdout; diagnostics/errors to stderr.
- Suppress ANSI codes, progress spinners, and decorative output when
--jsonis passed or when stdout is not a TTY.
Errors (agent/mixed consumers only)
- When
--jsonis active, emit error objects on stderr:{"error": "<snake_case_code>", "message": "...", "hint": "<exact CLI invocation or null>"}— so agent callers can route recovery logic without parsing free-text stderr. Thehintfield must be an executable command, not prose. - Exit codes:
0success,1runtime error,2invalid usage; for agent/mixed consumers, extend with the typed table from cli-guidelines.md → Exit codes (typed) (3not-found,4auth,5upstream,7conflict) — apply identically across all subcommands so agents branch on the code.
Flags
-h/--helpalways shows help; ignores other args.--versionprints version to stdout.--jsonpreferred for structured output.--output json/-o jsonacceptable when the CLI needs multiple output formats (yaml, table, csv) under a single flag. Pick one and apply consistently.- For commands an agent calls in a loop, offer
--compact(opt-in): same JSON shape, minimal whitespace, essential fields only —--jsonstays the full-fidelity default. See cli-guidelines.md → Output defaults. - Consistent flag names across all subcommands for the same concept (
--id,--force,--json) — agents learn the naming pattern once and apply it everywhere without guessing. - Prompts only when stdin is a TTY;
--no-inputdisables prompts.--non-interactiveacceptable if the ecosystem already uses it. - Destructive operations: interactive confirmation; non-interactive requires
--force. - Respect
NO_COLOR,TERM=dumb; provide--no-color. - Handle Ctrl-C: exit fast; bounded cleanup; crash-only when possible.
Reduce Tool Calls (agent/mixed consumers only)
- Compound output: operations return enough data to avoid a follow-up call.
createreturns the new resource's ID and key fields.deleteechoes what was removed. - Rich JSON defaults: in
--jsonmode, return full objects not just IDs. - Bounded lists: list commands default to a safe limit (e.g., 50 items) with
--limitto adjust. In JSON mode, includehas_more(bool) and optionallynext_cursorfor keyset pagination. Unbounded output wastes tokens and risks context overflow for agent callers. - Idempotent by default: where possible, commands are safe to repeat; document preconditions explicitly — agents rely on safe retries for error recovery without human intervention.
Apply all applicable conventions, then proceed to Phase 4.
Phase 4 — Deliver
Audits
Evaluate the existing CLI against every Phase 3 subsection. For each convention, state: what the CLI does today, whether it conforms, and what to change. Also check:
- Flag naming consistency across subcommands.
- Help text quality (examples present, common flags first, fits one screen).
- Config precedence (flags > env > project config > user config > defaults).
- Destructive-op safety (confirmations, --force, --dry-run).
- Shell completion availability.
- Data layer fit: if the CLI reads from a backend, run the Data Layer Decision scorecard; flag when it re-fetches live data that a local cache + compound queries would serve (cli-guidelines.md → Stateful CLIs).
Produce a gap report organized by severity: Breaking (requires API change), Major (agent-breaking or convention violation), Minor (cosmetic/polish). Each finding: current behavior, convention violated, recommended fix with migration risk (none/low/breaking).
New designs
Produce a compact spec the user can implement. Include all relevant sections:
- Command tree + USAGE synopsis.
- Args/flags table (types, defaults, required/optional, examples).
- Subcommand semantics (what each does; idempotence; state changes).
- Output rules: stdout vs stderr;
--jsonfor structured output;--quiet/--verbose. - Error + exit code map (top failure modes).
- Safety rules:
--dry-run, confirmations,--force,--no-input. - Config/env rules + precedence (flags > env > project config > user config > system).
- Data layer decision:
adopt cache|statelessverdict + rationale (required when the tool reads from a backend). - API provenance (when wrapping an existing API): source + endpoint→command mapping; for HAR, the secret/auth/coverage/fragility/ToS checks.
- Shell completion story (if relevant): install/discoverability; generation command or bundled scripts.
- 5–10 example invocations (common flows; include piped/stdin examples).
Use this skeleton, dropping irrelevant sections:
0. Language & distribution: Go · cobra · single binary · goreleaser for CI (Omit if language was not determined.) 1. Name: mycmd 2. One-liner: ... 3. USAGE:
mycmd [global flags] <subcommand> [args]
4. Subcommands:
mycmd init ...mycmd run ...
5. Global flags:
-h, --help--version-q, --quiet/-v, --verbose(define exactly)--json(structured JSON output; NDJSON for list commands)
6. I/O contract:
- stdout:
- stderr:
7. Exit codes:
0success1generic failure2invalid usage (parse/validation)- (add command-specific codes only when actually useful)
8. Env/config:
- env vars:
- config file path + precedence:
9. Data Layer Decision (required when the tool reads from a backend; omit if no backend): adopt cache | stateless + one-line rationale. If adopt: sync command, local schema sketch, local-vs-live reads, write invalidation note. 10. API Provenance (only when wrapping an existing API; omit for from-scratch tools): source (OpenAPI/URL/HAR); subcommand ← method+path mapping; for HAR, the five checks. 11. Examples:
- …
See ${CLAUDE_PLUGIN_ROOT}/skills/create-cli/examples/example-cli-spec.md for a complete worked example.
If the spec is destined for a skill body or CLAUDE.md, omit unused sections entirely (do not mark them "N/A") and limit examples to ≤5 invocations that each demonstrate multiple patterns.
Phase 5 — Verify
For new specs: confirm the spec covers all applicable sections from the Phase 4 skeleton. Verify the examples section demonstrates at least: --json output, error recovery (if agent/mixed consumer), and one piped/stdin usage.
For backend/API-wrapping specs: confirm a Data Layer Decision verdict is recorded with a rationale (not left implicit), and — when the source is a HAR/undocumented API — that no secret values appear anywhere in the spec (only env-var names) and the coverage/fragility caveats are stated.
For audits: confirm the gap report addresses every Phase 3 subsection and includes at least one example invocation showing the recommended fix for each Major finding.
Skill is complete when verification passes.
Notes
- Once language is selected (Phase 2), include the idiomatic parsing library in the spec (see language-selection.md). If language remains undetermined, omit the library.
- If the request is "design parameters", do not drift into implementation.
Example CLI Spec: snapr
A complete worked example covering all deliverable sections, including agent-aware patterns: TTY auto-detection, NDJSON list output, structured errors with executable hints, and compound output. Use as a reference for output format and level of detail.
---
1. Name
snapr
2. One-liner
Take and restore filesystem snapshots.
3. USAGE
snapr [global flags] <subcommand> [args]
snapr snapshot <path> [--name <name>] [--tag <tag>]
snapr restore <snapshot-id> <target-path> [--force] [--dry-run]
snapr list [--tag <tag>]
snapr delete <snapshot-id> [--force]4. Subcommands
| Subcommand | Description | Idempotent? |
|---|---|---|
snapshot <path> | Capture a versioned archive of <path>. Returns snapshot ID + metadata. | Creates a new snapshot each time |
restore <id> <target> | Restore snapshot to <target>. Fails if target non-empty without --force. | No — overwrites data |
list | List all snapshots; filter by tag. | Yes |
delete <id> | Delete a snapshot. Prompts for confirmation unless --force. | No |
5. Global flags
| Flag | Type | Default | Description |
|---|---|---|---|
-h, --help | bool | — | Show help; ignore all other args |
--version | bool | — | Print version to stdout, exit 0 |
-q, --quiet | bool | false | Suppress progress output; errors still go to stderr |
-v, --verbose | bool | false | Emit debug output to stderr |
--json | bool | false | Structured JSON output (NDJSON for list commands) |
--no-color | bool | false | Disable ANSI color; also respected via NO_COLOR env var |
--config <path> | string | ~/.snapr/config.toml | Path to config file |
Subcommand-specific flags:
| Flag | Applies to | Description |
|---|---|---|
--name <name> | snapshot | Human label; defaults to ISO8601 timestamp |
--tag <tag> | snapshot, list | Group/filter snapshots |
--dry-run | restore | Show what would be overwritten without writing |
--force | restore, delete | Skip confirmation; required for non-interactive use |
6. I/O contract
Output mode: Default is human-readable text. --json gives structured JSON. Agents pass --json explicitly — no TTY sniffing, no surprises.
stdout: Snapshot objects, list output (NDJSON in --json mode — one JSON object per line), version string. snapshot always returns the created snapshot's ID and metadata fields on stdout, even in quiet mode, so callers don't need a follow-up list call.
stderr: Progress messages, verbose debug, warnings. When --json is active, errors are emitted as a structured JSON object (see §7). Never mixes with stdout.
stdin: restore accepts - as <target> to pipe restored content to stdout (single-file snapshots only).
7. Exit codes and error format
| Code | Meaning |
|---|---|
0 | Success |
1 | Runtime error (snapshot not found, I/O failure, permission denied) |
2 | Invalid usage (unknown flag, missing required arg, bad type) |
3 | Target conflict — restore target is non-empty and --force not passed |
Non-TTY error object (emitted on stderr):
{"error": "not_found", "message": "Snapshot 'abc123' does not exist.", "hint": "snapr list --json"}The three fields are always present: error (snake_case machine code), message (one human-readable sentence), hint (exact CLI invocation the caller can run to recover, or null if no recovery action applies).
8. Env/config
Environment variables:
| Variable | Overrides | Notes |
|---|---|---|
SNAPR_DIR | default snapshot dir (~/.snapr/snapshots/) | Set in CI to a shared volume |
SNAPR_CONFIG | default config path | Fallback for --config flag; flag takes precedence when both are set |
NO_COLOR | --no-color | Standard; respected automatically |
Config file (~/.snapr/config.toml; project-local .snapr.toml in CWD also checked):
snapshot_dir = "~/.snapr/snapshots"
default_tag = ""
retention_days = 30Precedence (high → low): flags > env vars > project config (.snapr.toml) > user config (~/.snapr/config.toml) > built-in defaults.
9. Examples
# Create a snapshot — --json returns full object; agent can read ID without a follow-up call
snapr snapshot ./src --name "before-refactor" --tag "dev" --json
# {"id":"abc123","name":"before-refactor","tag":"dev","path":"./src","created_at":"2026-02-23T14:00:00Z"}
# List all snapshots — NDJSON in --json mode, one object per line; pipeable without buffering
snapr list --tag "dev" --json | jq -r '.id'
# abc123
# def456
# Restore non-interactively in CI — --force skips confirmation; exit code signals outcome
snapr restore abc123 ./src --force --quiet
# exit 0 on success; exit 3 if target non-empty (agent checks exit code, not stderr)
# Preview a restore — dry-run shows what would change
snapr restore abc123 ./src --dry-run --json
# Agent error recovery pattern — hint field contains the exact next command to run
snapr restore xyz999 ./src --force --json
# stderr: {"error":"not_found","message":"Snapshot 'xyz999' does not exist.","hint":"snapr list --json"}Command Line Interface Guidelines (condensed)
Source + contribution:
- Full guide: https://clig.dev/
- Propose changes: https://github.com/cli-guidelines/cli-guidelines
Table of contents:
- Foreword
- Introduction
- Philosophy
- Human-first design
- Simple parts that work together
- Consistency across programs
- Saying (just) enough
- Ease of discovery
- Conversation as the norm
- Robustness
- Empathy
- Chaos
- Guidelines
- The Basics
- Help
- Documentation
- Output
- Errors
- Arguments and flags
- Interactivity
- Subcommands
- Robustness
- Future-proofing
- Signals and control characters
- Configuration
- Environment variables
- Naming
- Distribution
- Analytics
- Further reading
- Authors
This is a practical rubric for designing CLI interfaces (args/flags/subcommands/help/output/errors/config). Keep humans first, but preserve composability and scriptability.
Foreword
- CLI still uniquely powerful: inspect/control systems; works interactively and in automation.
- Modern CLI = human-first text UI, not just a machine-first REPL veneer.
- Goal: maximize utility + accessibility; design for humans and composition.
Introduction
- This guide mixes philosophy + concrete rules; bias: examples over theorizing.
- Out of scope: full-screen TUIs (vim/emacs-like).
- Language/tooling agnostic: apply principles regardless of implementation stack.
Philosophy
Human-first design
- Optimize for humans by default; scripts still work via stable modes (
--json,--plain, exit codes). - Don't leak developer-only output to normal users; reserve for verbose/debug.
Simple parts that work together
- Assume your output becomes someone else's input.
- Respect stdio, exit codes, signals; keep primary output on stdout.
- Prefer line-oriented plain text for piping; add JSON for structured needs.
Consistency across programs
- Follow common conventions unless they harm usability.
- Reuse standard flag names (
--help,--version,--json,--dry-run, …).
Saying (just) enough
- Too little: "hangs" with no feedback. Too much: noisy debug spew.
- Make progress/status visible, but keep success output brief.
Ease of discovery
- Help text is part of UX. Put examples first; suggest next commands.
- When user errs, help them recover: point to the right syntax/flag.
Conversation as the norm
- Expect trial-and-error loops. Design for repeated invocations.
- Provide safe "dry run"/preview; show intermediate state; confirm scary actions.
Robustness
- Be correct and feel robust: responsive, clear, no scary traces by default.
- Handle bad input gracefully; validate early; clear errors.
Empathy
- Be on the user's side. Make success likely; make failure informative.
- Character is fine; clutter is not.
Chaos
- Terminal ecosystem inconsistent; follow norms, but break them intentionally when needed.
- If you diverge, do it with clarity and document it.
Guidelines
The Basics
- Use a real argument parsing library when possible (built-in or reputable OSS).
- Exit codes:
0on success, non-zero on failure; map a few important failure modes. - Stdout for primary output (and machine-readable output). Stderr for messages/logs/errors.
Help
- Always support
-h/--help. Do not overload-h. - If run with missing required args, show concise help + 1–2 examples + "use --help".
- Git-like CLIs: support
mycmd help,mycmd help subcmd,mycmd subcmd --help. - Link to a support path (repo/issues/docs). Prefer deep links per subcommand (when you have web docs).
- Lead with examples; show common flags/commands first; keep formatting readable without escape-char soup.
Documentation
- Provide web docs (searchable, linkable).
- Provide terminal docs (
mycmd help ...); consider man pages where sensible.
Output
- Humans first, machines second: detect TTY to choose formatting.
- If fancy human output breaks parsing, offer
--plain(stable, line-based) and/or--json. - On success: usually print something, but keep it brief; add
-q/--quietwhen useful. - If you change state, say what changed and what the new state is.
- Suggest "next commands" in workflowy tools.
- Use color sparingly; disable when not a TTY,
NO_COLORset,TERM=dumb, or--no-color. - No animations/progress bars when stdout isn't a TTY.
- Use a pager for long output only when interactive; common
lessopts:-FIRX.
Errors
- Catch and rewrite expected errors for humans; avoid stack traces by default.
- Keep signal-to-noise high; group repeated errors.
- Put the most important info last; use red intentionally (don't drown the user).
- For unexpected crashes: provide a path to debug info + bug report instructions; write logs to a file if large.
Arguments and flags
- Prefer flags over positional args for clarity and future flexibility.
- Provide long versions of all flags; use one-letter flags only for the most common.
- Multiple args ok for repeated simple items (
rm a b c); avoid "2+ different positional concepts". - Standard flag names (common set):
-h, --helphelp--versionversion-q, --quietless output-v, --verbosemore output (avoid-vmeaning version)-d, --debugdebug output-f, --forceskip confirmation / force-n, --dry-runpreview only--jsonstructured output-o, --output <file>output path--no-inputdisable prompts- Default should be right for most users (don't rely on everyone aliasing a flag).
- Support
-for stdin/stdout when input/output is a file. - Avoid secrets in flags; prefer
--password-fileor stdin. - Prefer order independence for flags/subcommands where the parser allows.
Interactivity
- Prompt only if stdin is a TTY.
--no-input: never prompt; if required input missing, fail with an actionable message.- Password prompts: disable echo.
- Make escape hatch obvious (Ctrl-C, or explicit "press q", etc).
Subcommands
- Use subcommands for complexity; share global flags/config/help.
- Be consistent across subcommands: naming, flags, output, formatting.
- Consider noun-verb (
docker container create) or verb-noun; pick one and stick to it. - Avoid ambiguous pairs (
updatevsupgrade) unless sharply differentiated. - Avoid implicit "catch-all" subcommands; don't allow arbitrary abbreviations (future-proofing trap).
Robustness
- Validate early; fail fast with good error messages.
- Be responsive: print something in <100ms (especially before network I/O).
- Show progress for long tasks (interactive only); don't interleave logs confusingly.
- Use timeouts for network calls; allow configuration.
- Make reruns safe: idempotent where possible; recoverable; "crash-only" where feasible.
Future-proofing
- Interfaces are contracts: args, flags, subcommands, config, env vars, output modes.
- Keep changes additive; deprecate loudly + early; provide migration paths.
- Allow human output to evolve; keep scripts stable by encouraging
--plain/--json.
Signals and control characters
- Ctrl-C: exit quickly; say something immediately; bound cleanup.
- Second Ctrl-C: optionally force; tell user what it does.
- Assume cleanup might not run; design for crash-only recovery.
Configuration
- Pick the right mechanism:
- Per-invocation: flags (and sometimes env).
- Per-user/machine: flags + env; possibly config file.
- Per-project (checked in): config file in repo.
- Follow XDG base directories for user-level config when applicable.
- Precedence (high → low): flags > process env > project config > user config > system config.
- Don't silently modify other programs' config; ask consent; prefer new files over editing existing ones.
Environment variables
- Names: uppercase + digits + underscores; single-line values preferred.
- Respect common vars when relevant:
NO_COLOR,DEBUG,EDITOR,PAGER, proxy vars,TERM,TMPDIR,HOME,COLUMNS/LINES. .envcan be useful for per-project non-secret knobs; don't use it as a full config system.- Don't accept secrets via env vars by default; prefer files/pipes/sockets/secret managers.
Naming
- Command name: simple, memorable, lowercase; avoid too-generic collisions.
- Keep it short but not cryptic; easy to type matters.
Distribution
- Prefer single binary when practical; otherwise use native packaging for uninstallability.
- Make uninstall easy; include instructions.
Analytics
- Never phone home without explicit consent; explain what/why/how/retention.
- Prefer opt-in; if opt-out, make it obvious and easy to disable.
- Consider alternatives: docs instrumentation, download metrics, talking to users.
Further reading
- POSIX Utility Conventions
- GNU Coding Standards (esp. flags/help conventions)
- 12 Factor CLI Apps
- Heroku CLI Style Guide
Agent Ergonomics
These guidelines extend the human-first philosophy for tools where the primary caller is an AI agent (LLM-based coding assistant, automated pipeline, etc.). Agents are trained on standard CLI conventions and will use them — the goal is not a new interface but rigorous application of existing conventions with agent consumption in mind. The approach is agent-aware, not agent-first: three pillars are token-efficient output, fewer tool calls through good UX, and structured errors that enable programmatic recovery.
This is not a formal schema spec (no OpenAPI/JSON Schema for CLI interfaces) — principles and strong conventions, not machine-readable contracts. The goal is fewer tool calls, not shortest possible output; rich compound output often uses more tokens per call but fewer calls total.
Output defaults
- Data format (JSON vs human text) follows an explicit
--json/--plainflag that overrides
TTY state — never TTY alone. Agents pass the flag and must not rely on auto-detection: some harnesses allocate a PTY, which would silently flip an auto-detecting tool to human tables and break the parser. TTY-detecting the default (table on a TTY, JSON when piped) is fine for human-primary tools, but only with the explicit override as the documented stable contract.
- Cosmetics (color, spinners, progress, pagers, decorative output) TTY-detect always; suppress
when piped, NO_COLOR, TERM=dumb, or --json.
- List commands in
--jsonmode: NDJSON (one object per line) — enables streaming andjq
piping without buffering. For paginated results with metadata, a JSON object with an items array is acceptable.
- For high-frequency agent calls, offer
--compact: same JSON shape, minimal whitespace, only
essential fields (drop verbose/derived ones). --json stays the full-fidelity default; --compact is the opt-in mode for commands an agent calls in a loop, where per-call token cost compounds.
Structured errors
- Error objects on stderr when
--jsonis active:
{"error": "<snake_case_code>", "message": "<sentence>", "hint": "<exact CLI invocation or null>"}
hintmust be an executable command the agent can run directly — not a prose suggestion.
Good: "hint": "snapr list --json". Bad: "hint": "Check available snapshots first.".
Exit codes (typed)
- Extend the base set (
0success,1generic runtime,2usage/validation) with a small
fixed table applied identically across every subcommand, so agents branch on the code instead of parsing stderr: 3 not-found, 4 auth/permission, 5 upstream/network, 7 conflict/precondition (6 is reserved — skip it). Skip codes that don't apply; never renumber once shipped — the table is a contract.
- Pair each non-zero code with the
hintcommand an agent can run to recover.
Reduce tool calls
- Compound output:
createreturns the new resource's ID and key fields;deleteechoes
what was removed. Agents should not need a follow-up call to discover the result.
- Rich JSON defaults: in
--jsonmode, return full objects. Include enough context that a
second call is rarely needed.
- Bounded lists: default to a safe limit (e.g., 50 items) with
--limitto adjust. In JSON
mode, include has_more (bool) and optionally next_cursor for keyset pagination.
- Consistent patterns: same flag names across subcommands for the same concept. Agents learn
the pattern once and apply it everywhere.
- Idempotency: document which commands are safe to repeat. Agents rely on idempotency for
error recovery without human intervention.
Stateful CLIs (local cache)
Most CLIs are stateless wrappers — one command, one backend call. When the same data is queried repeatedly, or the useful questions span entities the backend can't join in a single call, a local cache earns its complexity:
syncpulls from the backend incrementally (cursor / updated-since) into local SQLite.
Subsequent list/search/filter read local — fast, offline, rate-limit-free, deterministic within a snapshot.
- Full-text search (SQLite FTS5) and compound queries (joins/aggregates the API has no single
endpoint for, e.g. "stale items whose blockers sat >7d") collapse N round-trips into one local command — the largest token and tool-call saving available.
- Cost: staleness, local storage, sync/cursor logic, schema design. Adopt only when the access
pattern justifies it; a pure pass-through CLI stays stateless.
Data Layer Decision (required scorecard). For any CLI that reads from a backend, score these five signals and record the verdict in the spec — a stateless verdict must be as deliberate as an adopt one:
| # | Signal | Weight |
|---|---|---|
| 1 | Read-heavy (reads ≫ writes) | support |
| 2 | Same data feeds many questions over time | support |
| 3 | Compound / cross-entity / cross-period queries the API has no single endpoint for | STRONG |
| 4 | Fetches expensive or rate-limited | support |
| 5 | Staleness-tolerant (NOT must-be-live) | GATE |
- Signal 5 = NO → stateless (live correctness wins; stop here).
- Signal 3 = YES and 5 = YES → adopt cache (the decisive combo).
- Otherwise: 2+ support signals and 5 = YES → adopt; else stateless.
If adopt, the spec must specify: the sync command (incremental cursor), a local schema sketch (tables + FTS5 if search is needed), which reads go local vs stay live, and that writes always go live with a read-after-write invalidation note.
Wrapping an existing API (input provenance)
When the CLI wraps an existing backend, the command surface is derived from an API rather than designed from scratch. Record where that surface came from:
- API source: documented (OpenAPI / docs), a live URL, or a HAR capture (DevTools → Network →
Export) that surfaces an undocumented, browser-fed API where no official spec exists.
- Map the endpoint inventory to the command tree and keep a provenance appendix: each
subcommand ← method + path + a sample request.
A HAR / undocumented source adds five non-negotiable design checks:
- Secrets are radioactive. A HAR contains live cookies, tokens, and PII. Extract structure
only; parameterize every credential as an env-var NAME, never echo a value into the spec.
- Auth model. Identify cookie / bearer / CSRF and how it enters the CLI (env var, never a
flag); note token expiry.
- Coverage. A HAR is sampled — only endpoints you exercised appear. Mark which surfaces are
covered vs unknown; don't imply completeness.
- Fragility. Undocumented endpoints have no contract and can change without notice. Isolate
the endpoint-mapping layer so a break is a one-line patch.
- ToS/legal. Replaying a private API may violate terms. Surface this to the user; never assume.
Discovery
- Compact top-level
--help: tight enough that the agent reads it once and maps the full
surface area without nested help-diving.
- The spec document (not
--help) is the primary agent reference when the tool is used in a
skill or CLAUDE.md context. Design it to be read by a model, not just by a person.
Spec compactness
The CLI spec produced by this skill typically lives inside a skill body or as an embedded reference block, not as a standalone file in the user's repo. This means the spec must be compact enough to fit in an agent's context budget. Redundant sections should be omitted (not just marked optional), and examples should be dense — demonstrate multiple patterns in a single invocation rather than one-pattern-per-line.
Authors
Original "Command Line Interface Guidelines" authors (and many contributors): Aanand Prasad, Ben Firshman, Carl Tashian, Eva Parish. Design by Mark Hurrell.
Language Selection for CLI Tools
Default: Go
Single binary, fast startup, no runtime dependency on target machine, strong stdlib, mature CLI ecosystem (cobra, flag). Start here unless a push factor applies.
Push Factors (when to deviate)
| Condition | Use instead |
|---|---|
| Key library only exists in Python (data, ML, media processing) | Python |
| Embedding into an existing Python codebase | Python |
| CLI wraps a JS/TS library directly, or must distribute via npm | TypeScript/Node |
| Embedding into an existing JS/TS codebase | TypeScript/Node |
| Startup latency < 10ms or memory footprint is critical | Rust |
| Simple glue script (< ~80 lines), CI-only, or max Unix portability | Bash |
| Team has no Go experience and the CLI is short-lived | Match team language |
Parser Quick Reference
| Language | Parser | Notes |
|---|---|---|
| Go | cobra | subcommands; flag stdlib for simple flags-only tools |
| Python | argparse | stdlib, no dep; click for decorator-style; typer for type-annotated |
| TypeScript/Node | yargs | batteries-included; commander for lightweight |
| Rust | clap | derive macros; full-featured |
| Bash | getopts | POSIX built-in; argbash for complex flag parsing |
Distribution Model
Single binary — one compiled file, no runtime needed on target machine. User downloads and runs it directly. Native to Go and Rust.
Runtime-dependent — requires an interpreter (Python, Node.js, JVM) installed first, then package installation. Native to Python, TypeScript/Node, Java.
Bundled binary — interpreter + code packed into one file by a tool (PyInstaller, pkg, deno compile). Produces a large binary (50–150 MB) vs native binaries (5–15 MB). Works, but has edge cases with native extensions.
When to prioritize single binary
- Target users are non-developers, or machine state is unknown (CI, remote servers)
- Public distribution — "download and run" is the expected UX
- Container size matters (Go binary: ~10 MB; Python image with deps: 200+ MB)
- Must work reliably across diverse OS/arch combinations
When runtime dependency is acceptable
- All target users already have the runtime (data scientists → Python; web devs → Node)
- Critical libraries only exist in that ecosystem (media processing, ML, etc.)
- Internal tools in a controlled environment where the runtime is mandated
- Development speed outweighs distribution polish
Distribution Quick Reference
| Language | Model | Default distribution |
|---|---|---|
| Go | Single binary | compiled binary; goreleaser for cross-platform CI |
| Rust | Single binary | compiled binary; cargo install or GitHub Releases |
| Python | Runtime-dependent | pip install / pipx install; pyinstaller for bundled |
| TypeScript/Node | Runtime-dependent | npm install -g / npx; pkg / esbuild for bundled |
| Bash | Script | single .sh file; no build step |
Related skills
FAQ
Is Create Cli safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.