# CLI Development Guidelines Reference
## Scope and sources
This reference is a *condensed, operational* guide for building well-behaved CLI tools.
- Primary source (adapted heavily): *Command Line Interface Guidelines* ()
- License: CC BY-SA 4.0
- Authors: Aanand Prasad, Ben Firshman, Carl Tashian, Eva Parish
- Additional sources: POSIX utility conventions, GNU standards, Heroku CLI style guide, 12-factor CLI apps, XDG base directory spec, NO_COLOR convention.
## Table of contents
- [Design principles](#design-principles)
- [The basics: being a good CLI citizen](#the-basics-being-a-good-cli-citizen)
- [Help and documentation](#help-and-documentation)
- [Output, formatting, and modes](#output-formatting-and-modes)
- [Errors and diagnostics](#errors-and-diagnostics)
- [Arguments, flags, and subcommands](#arguments-flags-and-subcommands)
- [Interactivity and safety](#interactivity-and-safety)
- [Configuration and environment variables](#configuration-and-environment-variables)
- [Secrets and sensitive data](#secrets-and-sensitive-data)
- [Robustness: timeouts, retries, signals](#robustness-timeouts-retries-signals)
- [Future-proofing](#future-proofing)
- [Distribution and lifecycle](#distribution-and-lifecycle)
- [Analytics and telemetry](#analytics-and-telemetry)
- [Implementation notes](#implementation-notes)
- [Further reading](#further-reading)
## Design principles
### Human-first, but composable
- Optimize the default UX for humans:
- Clear, calm messages
- Example-first help
- Progress indicators for long operations
- Still be *composable* in UNIX pipelines:
- Clean `stdout` for data
- Meaningful exit codes
- No unexpected prompts in scripts
### Consistency is a power tool
- Prefer established CLI conventions when possible.
- Be consistent within your tool:
- Same option names mean the same thing everywhere.
- Output formats don't randomly change between subcommands.
### Say *just* enough
- Too little:
- Silent hangs
- No confirmation that anything happened
- Too much:
- Verbose debug spew in normal mode
- Walls of text hiding the one important line
### Discovery beats memorization
- `--help` should teach quickly.
- Suggest the "next command" in multi-step workflows.
### CLI as a conversation
- Users will iterate: run → error → fix → run.
- Respond like a helpful conversational partner:
- Point out what went wrong
- Suggest the simplest fix
- Make it easy to learn the correct syntax
## The basics: being a good CLI citizen
### Use a parsing library
- Don't hand-roll parsing, help formatting, or error rendering.
- A good parser will usually also give you:
- Help output
- Unknown-flag handling
- Sometimes: typo suggestions
### Streams: stdout vs stderr
- `stdout`
- The command's primary output
- Machine-readable output (piped into the next command)
- `stderr`
- Errors
- Warnings
- Progress / status messages
- Human "what's happening" narration
### Exit codes
- `0` means success.
- Non-zero means failure.
- Prefer a small set of stable, documented failure codes over "random integers."
- Consider reserving `2` for argument/usage errors.
- If you need a more granular taxonomy, consider the BSD `sysexits` family (e.g., EX_USAGE = 64), but be aware that many tools simply use `1`/`2` in practice.
## Help and documentation
### Required behaviors
- `--help` shows help and exits successfully.
- Ideally also support `-h` (and do not overload it with a different meaning).
- If your CLI has subcommands:
- `tool subcmd --help`
- `tool help subcmd` (optional but common in `git`-like tools)
### Concise help by default (when invocation is incomplete)
If the user runs a command with missing required args/flags, print a concise help block:
- What the tool does (one line)
- 1–2 common examples
- The most important flags (or a pointer to full help)
- "Run `--help` for full usage"
### Full help when asked
Full help should include:
- Usage line(s)
- Description
- Commands (if any)
- Options
- Examples (lead with examples; users will copy-paste them)
- Support path (issues / repo)
- Link to web docs (especially to a subcommand anchor if you have it)
### Formatting guidance
- Use scan-friendly formatting:
- Uppercased section headings
- Alignment for options
- Avoid ANSI escape sequences if help is piped (your output should not become "escape soup")
### If stdin is required but not provided
If your tool expects piped input and `stdin` is a TTY, don't hang.
- Print help or a clear message.
- Exit non-zero.
## Output, formatting, and modes
### Human-readable output is the default
A practical heuristic:
- If output is going to a TTY, it's probably a human.
- If output is being captured/piped, it's probably a program.
### Provide machine-readable output when it doesn't harm usability
Common patterns:
- `--json` outputs structured JSON (stable shape, versioned if needed).
- `--plain` outputs simple line/tabular output with one record per line.
- Encourage scripts to use `--json`/`--plain` rather than scraping the human UI.
### Keep success output brief, but not mysterious
- Printing nothing can feel like "it hung."
- Printing too much becomes noise.
- If you changed state, tell the user *what changed*.
### Color and symbols
- Use color with intention:
- Red for errors
- Yellow for warnings
- Highlight important parts only
- Disable color when:
- The relevant stream is not a TTY
- `NO_COLOR` is set (non-empty)
- `TERM=dumb`
- User passes `--no-color`
- Consider supporting `FORCE_COLOR` (some ecosystems use it), but don't let it break logs.
### Animations and progress
- Never animate when output is not a TTY.
- If something takes "long," show progress.
- If parallel work is happening, avoid interleaving chaos (multi-progress-bar libs help).
### Paging
- If output is long and you're on a TTY, consider a pager.
- Respect `PAGER` if set.
- A common `less` default is: `less -FIRX`
- Doesn't page if one screen
- Keeps formatting, doesn't clear screen on exit
## Errors and diagnostics
### Rewrite expected errors for humans
Don't dump raw stack traces for normal user errors.
- Say what failed
- Say why it might have failed (likely causes)
- Say what to do next (actionable fix)
### Keep signal-to-noise high
- Group repetitive errors under one explanation.
- Put the most important info at the end (recency bias in terminals is real).
### Suggest corrections carefully
- Typo suggestions are great when safe:
- "Unknown command `pss`. Did you mean `ps`?"
- Avoid "DWIM" behavior that silently changes meaning for destructive operations.
### Unexpected errors
When something truly unexpected happens:
- Provide a short human summary
- Offer a way to get debug details:
- `--debug` or `--verbose`
- Optional log file path
- Provide a bug report path and include reproducibility info
## Arguments, flags, and subcommands
### Prefer flags to positional args (usually)
- Flags are self-documenting and easier to extend without breaking compatibility.
- Exception: "classic" two-arg patterns (`cp `) where brevity is worth it.
### Provide long forms for all flags
- If you have `-h`, also have `--help`.
- Long forms are friendlier in scripts and documentation.
### Reserve one-letter flags for truly common options
Short flags are a scarce resource. Spend them wisely.
### Standard flag names (use existing conventions)
Common conventions across CLI ecosystems:
- `-h`, `--help`
- `--version`
- `-v`, `--verbose` (but note ambiguity: sometimes `-v` is version)
- `-q`, `--quiet`
- `-d`, `--debug`
- `-f`, `--force`
- `-n`, `--dry-run`
- `--json`
- `--no-input`
- `--no-color`
- `-o`, `--output`
### Order independence (when feasible)
Users often add flags to the end of the previous command via ↑.
If possible, allow:
- `tool --flag subcmd`
- `tool subcmd --flag`
### Subcommand naming
- Avoid near-synonyms (`update` vs `upgrade`) unless the difference is extremely clear.
- For object/action CLIs, `noun verb` is common:
- `docker container create`
- Keep verbs consistent across objects:
- If you use `create`, also use `delete`/`list`/`get` consistently.
## Interactivity and safety
### Prompts only when stdin is a TTY
- If `stdin` is not a TTY:
- Fail with a clear message describing the required flag(s)
- Do not block waiting for input that will never arrive
### `--no-input` should disable prompts
- If required info is missing:
- Exit non-zero
- Tell the user how to provide it via flags or stdin
### Confirm dangerous operations
Different danger levels:
- Mild:
- Deleting an explicit file the user named
- Moderate:
- Bulk deletes, remote deletes, complex irreversible changes
- Severe:
- "Delete the whole app/account/project"
- Require explicit confirmation:
- Type the resource name, or
- `--confirm="exact-name"`
### Provide dry-run where it reduces fear
- `--dry-run` should describe intended changes without doing them.
## Configuration and environment variables
### Choose the right configuration surface
- Flags:
- High variability per invocation
- Environment variables:
- Varies by execution context (shell/session/CI)
- Project config file:
- Stable for a project and shareable in version control
- User config:
- Stable per machine/user
### Precedence (high → low)
A common, predictable precedence order:
- Flags
- Process environment
- Project config (`.env` / tool config in repo)
- User config
- System config
### XDG base directory spec
Prefer:
- Config: `$XDG_CONFIG_HOME` (default `~/.config`)
- Data: `$XDG_DATA_HOME` (default `~/.local/share`)
- Cache: `$XDG_CACHE_HOME` (default `~/.cache`)
### Environment variable naming
- Uppercase, numbers, underscores.
- Prefer tool-specific prefixes:
- `MYTOOL_FOO=1`
### `.env` is not a real config system
`.env` is useful for small "context knobs," but it's limited:
- Everything is a string
- Often not versioned
- Often abused for secrets
## Secrets and sensitive data
- Do *not* accept secrets via flags:
- Leaks into shell history and process listings (`ps`)
- Do *not* accept secrets via environment variables:
- Easy to leak into logs, `docker inspect`, systemd unit displays, etc.
Prefer:
- `--token-file path`
- `--password-stdin`
- OS keychains / secret managers
- Pipes and local IPC when appropriate
## Robustness: timeouts, retries, signals
### Responsive beats fast
- Aim to print *something* within ~100ms for operations that might take time:
- "Fetching…"
- "Computing…"
- "Connecting to …"
### Timeouts and retries
- Network requests should have timeouts.
- Consider retries for transient failures (with backoff).
- Make retries visible (don't silently hide minutes of retrying).
### Recoverability and idempotence
- If a command fails mid-way, a rerun should:
- Pick up where it left off, or
- Fail safely without corrupting state
### Ctrl-C behavior
- On SIGINT (Ctrl-C), stop quickly and say what happened.
- If cleanup is long:
- Allow a second Ctrl-C to force quit
- Don't hang forever in cleanup
## Future-proofing
- Treat the CLI as a public API:
- Commands, flags, output formats, config keys are all interfaces
- Prefer additive changes:
- New flags > changing behavior of old flags
- Deprecate explicitly:
- Warn when deprecated flags are used
- Tell the user the replacement
- Output for humans can evolve.
- Output for scripts should be stabilized via `--plain` or `--json`.
## Distribution and lifecycle
- Prefer a single binary distribution if reasonable.
- Make uninstall easy and documented.
- Provide version output (`--version`).
- Consider:
- Man pages
- Shell completions
- Web docs with deep links to subcommands
## Analytics and telemetry
- Don't "phone home" without consent.
- If you collect anything:
- Explain what, why, how anonymized, and retention period
- Make opting out easy
Consider alternatives:
- Instrument docs
- Measure downloads
- Talk to users
## Implementation notes
### Parser libraries (examples, not exhaustive)
- Go: Cobra, urfave/cli
- Rust: clap
- Python: argparse, Click, Typer
- Node: oclif, commander, yargs
- Java: picocli
- Kotlin: clikt
- Swift: swift-argument-parser
### Practical output design tip
When in doubt:
- Human UI = defaults when TTY
- Machine UI = explicit flags (`--json`, `--plain`)
- Debug UI = explicit flags (`--debug`, `--verbose`)
## Further reading
- CLI Guidelines (primary):
- POSIX Utility Conventions:
- GNU Coding Standards (Program Behavior, CLI conventions):
- Heroku CLI Style Guide:
- 12 Factor CLI Apps:
- NO_COLOR convention:
- XDG Base Directory Spec: