# 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: