TS-16: Command Line Interfaces (CLIs)
🚧 DRAFT
This technical standard covers the design of command line interfaces (CLIs). The focus here is on designing CLI utilities for Unix-based environments, but the guidelines are intended to be applicable to the CLIs of programs running in all kinds of environments – Windows, Node.js, Python, Java, etc.
These technical standards build on the Command Line Interface Guidelines, an excellent open source guide to writing better command line programs. Other references are listed at the end of this document.
CLIs are a type of UI, and therefore this technical standard can be seen as an extension of TS-15. Terminal user interfaces (TUIs), which are full-screen terminal programs like Vim and Emacs, are not addressed by this standard. This standard is all about designing interactive command-oriented CLI utilities.
See also TS-62 for guidance on Makefiles, which are commonly used to expose a project’s scripts as a discoverable, CLI-like set of targets.
Principles
Making good quality CLI programs requires giving attention to all the little design details that affect the user experience. It’s about keeping the user informed at all times about what’s happening. It’s about explaining clearly why errors happened and how they can be resolved. It’s about good validation, sensible defaults, reasonable timeouts, and so on.
This section sets out some design principles that will help you to think through all the little details of your CLI programs.
Terminology
This standard uses several terms – terminal, shell, tty, console, and terminal emulator – that are closely related and often used loosely as synonyms. They are not the same thing, and the distinction matters when reasoning about I/O, signals, and environment detection.
Shell. The command-line interpreter a user interacts with directly – Bash, Zsh, cmd.exe, PowerShell, etc. Its
primary purpose is to read commands and start other programs. A CLI program is normally invoked from a shell, but is not
itself a shell.
Terminal, in the Unix sense, and tty. A text input/output environment: the thing that reads keystrokes and displays
output. In Unix terminology, "terminal" is synonymous with tty – a particular kind of device file supporting a set of
control operations (ioctl\s) beyond plain reads and writes. Some ttys are backed directly by a hardware device or a
kernel-level virtual console; others, called pseudo-ttys (ptys), are provided through a thin kernel layer by a terminal
emulator.
Terminal emulator. A program that provides a pseudo-tty and renders it as a window or a remote session – for example
Xterm, screen, tmux, or an SSH client connecting a local terminal to programs running on a remote machine.
"Terminal," in its everyday sense, most often refers to this kind of program: a window with a keyboard and a display,
standing in for the physical teleprinters the terminology originally described.
Console. A terminal in the physical sense – conventionally, the primary terminal directly connected to a machine. It appears to the operating system as a tty like any other, so the distinction from "terminal" is about physical/primary status, not about the underlying mechanism.
A CLI program’s own I/O model – reading from standard input, writing to standard output and standard error, detecting
whether it is attached to a tty (see Piping) – is the same regardless of which of these sits behind it. The terms
matter mainly for correctly describing where a behavior applies: a NO_COLOR-style check is a tty check on the
process’s streams, not a check for any particular shell or terminal emulator.
Simplicity
Above all, robustness is achieved by keeping the program simple. Complex code and special-case handling make a program fragile and unpredictable: every branch is a place where an assumption can turn out to be wrong, and every edge case handled is an edge case that must be maintained. A CLI with fewer moving parts is easier to reason about, easier to test exhaustively, and less likely to surprise its users.
Favor a small number of well-understood behaviors over a large number of special-cased ones. Where a feature would only handle a rare combination of inputs, consider whether the CLI is better off rejecting that combination outright, with a clear error, than trying to handle it correctly.
Composability
Design CLI programs to be composable. This means that each program and subcommand should be designed do a small amount of work independently, but that it should be possible to combine multiple simple programs and commands to compose more complex operations. Your CLI programs might even be used by others in ways you did not anticipate.
This design principle is a key tenet of the UNIX philosophy. At a time of large-scale automation, in the form of CI/CD pipelines and container orchestration, this principle is as important today as it has ever been.
Composable CLIs are achieved by following standard conventions for inputs and outputs for the program’s runtime environment. In Unix-like environments, this means using standard in/out/err, signals, exit codes, arguments and flags, environment variables and other long-established conventions for input and output.
Design CLIs as conversations
It is tempting to think about CLIs as programmatic interfaces, used in scripting and automation, rather than human interfaces. But we should design CLIs primarily with human users in mind. Even if a program is intended for use in automation, its client programs will still be developed by real people. Those people, the developers of the client programs, are the users.
The user experience of a CLI should be like having a conversation with the computer.
In CLIs, users provide input through a sequence of commands, each of which performs a specific task within a larger
operation. For example, you git add files before you git commit them. The output of one command should guide the
user to their next command. When the user inputs invalid data or uses an unsupported subcommand, the program should
provide helpful suggestions or error messages to guide the user.
If the user enters an invalid command name, the program SHOULD suggest valid commands with similar spellings or
semantics. For example, if the user inputs upgrade, the output might suggest the user try update or install
instead. If the user inputs ststus, the output might suggest they meant status.
But you MUST NOT run the alternative operation without the user’s explicit approval, unless it is a documented alias. Making assumptions about a user’s intention can be dangerous, especially where operations may result in modified state.
Make CLIs discoverable
CLIs should be self-documenting. Good help tests, with lots of examples, and suggestions on what commands the user might like to run next, and how they can fix errors – all of these things help users to discover a program for themselves.
Communicate state changes
If the program changes state, inform the user. This is especially important when a program enters intermediate state, while waiting for further user input, or when a failure mode puts the system into an invalid state.
Make it easy for the user to inspect the current state of CLI programs. The Git operation git status is a good model
for this.
Prefer explicit actions
Reading or writing files that the user did not explicitly pass as arguments, and talking to a remote server – for example, to check for updates or download a file – SHOULD usually be an explicit, visible action. Don’t surprise the user with side effects they didn’t ask for.
The exception is storing internal program state, such as a cache or a telemetry queue (see Analytics). Users don’t need to be told every time a program reads its own cache directory.
Analytics
CLI programs MUST NOT phone home usage or crash data without the user’s consent. Where a program does collect telemetry, be explicit with users about what is collected, why it is collected, how it is anonymized, and how long it is retained.
Prefer an opt-in model. Where telemetry is opt-out instead, tell users clearly that data is being collected and make it
easy to disable – for example, via a --no-analytics flag or a [APP]_NO_ANALYTICS environment variable, mirroring the
NO_COLOR convention (see Formatting).
Consider alternatives to in-program telemetry before reaching for it: download counts from your package registry or website analytics on your documentation site often answer the same questions without instrumenting the CLI itself. And talking to users directly – support channels, issue trackers, user interviews – surfaces the "why", which telemetry cannot.
Control output
An essential design choice for CLI programs is the amount of output they produce by default. The optimum balance between too much and too little output will vary depending on the use cases of the program.
Users should be able to control the amount of output, for example by using --quiet and/or --verbose flags. It may
also be desirable to allow users to control output formats, too. Flags such as --plain and --json are commonly used
for this purpose.
Be responsive
CLIs should respond quickly to user input. This means providing feedback in under 100ms and keeping the user informed of the progress of long operations, eg. using progress bars.
If you need to make a network request, try to do it in a non-blocking way. If that is not possible or desirable, print something before you initialize the request, so the UI doesn’t hang and look broken if the network request times out. Set sensible defaults for network timeouts, and allow the user to override the default configuration.
A responsive program feels robust and dependable.
Responsiveness also covers cold-start time – how long it takes the program to begin doing useful work after invocation, before any of its own feedback can be shown. As a rough guide: under 100ms feels instant; 100–500ms is a reasonable target for most CLIs; 500ms–2s is usable but noticeable; beyond 2s, users start to avoid the tool, scripting around it or reaching for alternatives. Startup time matters most for CLIs invoked frequently in tight loops – shell prompts, git hooks, editor integrations – where even a few hundred milliseconds compounds quickly.
Parallelism
Do stuff in parallel where you can, to speed up operations. But only do this if it can be done reliably. It is more important to be responsive and robust, than to be fast.
Crash-only
Design your CLI as a "crash-only" program. This means that the program should exit immediately on failure or user
interruption (Ctrl+C).
Besides being safer, this also makes CLI programs feel more responsive and robust.
Idempotency
Make requests idempotent where possible. This means that if a command fails, the user should be able to retry the
command – simply by pressing Up and Enter – without causing unintended side effects.
Defensive programming
In designing a CLI, think through all the ways that users could misuse it. Ask yourself, how does the program perform when:
- it is used in a script;
- when the user has a bad network connection;
- when the user runs multiple instances of it at once;
- when it runs in an unsupported environment.
Plan and test for these use case scenarios. Fail gracefully whenever the program cannot handle the user’s input or is otherwise unable to guarantee correct operation for whatever reason.
Future proofing
In software of any kind, it is important that interfaces do not change erratically. This is especially important in CLI programs, which are commonly dependencies of other programs.
Subcommands, arguments, flags, configuration files, environment variables – these are all interfaces, and once your program starts using them you are committing to keeping them stable.
Major versions SHOULD endure with non-breaking changes for as long as possible – preferably indefinitely, for the whole lifespan of the program.
Use Semantic Versioning for CLI programs. Reserve major version bumps for breaking changes to the interface – removed or renamed subcommands, flags, or environment variables; changed defaults; changed exit codes – so that users and downstream scripts can tell from the version number alone whether upgrading is safe.
We do not break userspace.
– Linus Torvalds
Keep changes additive where you can. For example, rather than modifying the behavior of a flag (which would be backwards-incompatible), prefer instead to add a new flag. To avoid bloating the interface, the old flag can be marked as deprecated – but not removed until the next major version bump.
Warn your users about deprecated operations. This gives them a chance to update their clients before they are broken by the next major release of your program. A good deprecation warning does three things: tells the user, at the moment they use the deprecated flag or subcommand, that it is going to change or be removed; shows them the future-proof alternative to switch to; and, where possible, detects when they have already updated their usage elsewhere and stops showing the warning, so it doesn’t nag users who have nothing left to fix.
Avoid creating time-bombs. Think about the future – how might your program work 5, 10, 20 years from now? Can you guarantee it will still work in the same way? If you cannot guarantee this – due to dependencies on external components or services, for example – be sure to clearly document the reasons why. Don’t build in a blocking call to a third-party analytics service, for example – if that service is ever discontinued or blocks the request, your program should not hang or fail because of it.
Naming
When naming CLI commands, subcommands, and options, follow these guidelines:
- Make the name memorable and easy to type.
- Keep it short, but not too generic – to avoid conflicts.
- Reserve the shortest, most generic names for standard tools.
- Avoid superfluous words such as "tool", "util", and "kit".
- Don’t name commands after any standard, protocol, or file format, such as "openssl" or "ffmpeg". The exception is
where the name is both literal and niche enough that no other meaning could be confused for it, eg.
mkfs. Conversely, a deliberately meaningless but easy-to-type name (eg.emacs) is a sound choice when the tool’s solution domain is expected to evolve over time – naming it after today’s domain would only date it. - Don’t put emoji in a command name. It’s technically possible, but it defeats memorability and typeability, which matter more for a command name than for output (see Formatting).
- Don’t suffix a command name with a version number, eg.
python3.7m. A version-suffixed name is usually a sign that the ecosystem has failed to support multiple coexisting versions of the tool cleanly.
Follow the naming conventions of the target runtime system. For Unix-based systems, the convention is to use only lower
case ASCII letters and delimit words with single dashes. The user SHOULD NOT need to press their Shift key to type
your commands: VirtualBox and easy_install break this rule. Numbers MAY be included but they SHOULD NOT be the first
character of a command name.
Don’t pollute the global namespace with dozens of commands. If your package consists of a suite of utilities, consider
implementing them as subcommands of a single program namespace. Git does this brilliantly: git [subcmd].
Subcommands can be generic words like "update" and "status". But globally-scoped program names MUST NOT be – to avoid
conflicts with other programs. Both ImageMagick and Windows used the command convert — oops!
The very shortest, most generic names should be reserved for standard system tools, or things people use all the time
like cd and ls. The more niche the command, the longer its name should be.
Consistency across programs
Naming conventions, common flags, and interaction patterns are not only about consistency within your own program – they
are also about consistency across programs. Where possible, follow patterns that already exist elsewhere in the CLI
ecosystem your users work in. That’s a large part of what makes CLIs intuitive and guessable: a user who already knows
--force and --dry-run from other tools shouldn’t have to relearn the concept for yours.
This isn’t absolute. Where following an existing convention would compromise the usability of your specific program, it may be time to break with it – but do so deliberately, and be prepared to explain the deviation, since every departure from convention is a small tax on users who bring expectations from elsewhere.
Distribution
CLI programs MUST be easy to uninstall. Where uninstall requires its own instructions, put them at the bottom of the install instructions – right after installing is one of the most common times a user wants to know how to remove something, whether because they installed the wrong tool or are comparing alternatives.
Where possible, CLI programs SHOULD be distributed as either binaries or via the platform’s native package management system. Binaries and native packages can be easily removed.
A single self-contained binary is the distribution ideal: it has no runtime dependencies to install separately, and it can be removed by deleting one file. For languages that don’t compile to a native binary, use a bundler (eg. PyInstaller for Python) to package the interpreter and the program together into something that behaves like a single binary for installation and removal purposes.
The exception is a language-specific tool – a linter or formatter for a particular language, for example – where the
user can safely be assumed to already have that language’s interpreter or runtime installed. Such a tool MAY instead be
distributed through the language’s own package manager (eg. npm, pip), since that is where its users already look
for tools in that ecosystem.
Options
There are several mechanisms for passing options to CLI programs. By convention, the order of precedence, from highest to lowest, is as follows:
- Arguments and flags
- Local configuration files
- Environment variables
- User-level configuration files
- System-wide configuration files
Different classes of options are suited to different mechanisms of input.
Defaults
It is important for CLI programs to have good defaults. Making things configurable is good. But most users crave convenience above all.
The most commonly-used options SHOULD form the basis of a command’s default configuration.
You can’t always predict how your program will actually be used. ls was originally designed for terse, scriptable
output, but in practice it is most commonly run interactively as ls -lhF. Where you have evidence – from support
requests, from how your own team uses the tool, from documentation that always shows a command with the same flags – let
that evidence override your assumptions about what the default should be.
It is also acceptable to have a group of options that can never have their defaults adjusted, in order to keep some behavior consistent across every environment the program runs in. Not every option needs to be configurable; some are part of the contract the program makes with its users and callers, and making them adjustable would undermine that.
Arguments and flags
Arguments and flags are options that are inputted directly to commands. These options MUST override options configured via all other input mechanisms – environment variables and all types of configuration files.
Not all input options need to be implemented as arguments and flags. Only the options that are the most likely to vary between invocations should be implemented as arguments and flags.
Use a command-line argument-parsing library – whether the language’s built-in one or a well-established third-party one
– rather than hand-rolling argument parsing. A good library handles flags, abbreviated and combined short flags,
--flag=value and --flag value forms, help-text generation, and "did you mean" spelling suggestions consistently,
which is easy to get subtly wrong by hand and tedious to re-implement in every program.
By convention, arguments given as ordinary file-path parameters SHOULD be treated as input files only. Where a command
produces an output file, that output SHOULD be specified via an option – preferably -o/--output – rather than
another bare argument. Even where accepting an output file as an argument is kept for compatibility with an existing
convention, an equivalent option SHOULD also be provided, so scripts can be explicit about which argument is which.
Arguments and flags are distinct from one another:
- Arguments or args are positioned parameters to a command. For example, the file paths you provide to
cpare arguments. Their order is significant:cp foo baris not equivalent tocp bar foo. (Arguments look similar to, but are distinct from, subcommands. A command cannot have both arguments and subcommands.) - Flags are named parameters. Unlike arguments, the order of flags should not affect program behavior. In Unix-like systems, flags take one of these two formats:
--<name>, where<name>is a word, eg.--recursive, or hyphenated phrase, eg.--no-recursive. *-<x>, where<x>is a single-letter abbreviation, eg.-r
As a general rule, arguments SHOULD be used for required options and flags for optional ones. But there are plenty of exceptions to this rule. In some CLIs, it will make perfect sense to have optional arguments and mandatory flags.
Where an input option could be implemented as either an argument or a flag, prefer the flag. Flags take more typing, but they make for a clearer self-documenting API, they are more flexible because they can be applied in any order, and they are more future-proof because we can more easily add new flags in a backwards compatible manner.
Arguments SHOULD be used only for tightly-scoped, single-purpose operations that are likely to be frequently run by your
users, and that will unlikely ever change in the lifetime of the tool. Good examples include cp [source] [destination]
and rm [file1] [file2] ….
Good examples of use cases for flags include enabling "safe mode" or "dry run" operations, and "forcing" destructive and dangerous operations to complete without user confirmation (to support non-interactive environments). In fact, these are the sorts of options that SHOULD only be inputted via flags, and not via other mechanisms such as configuration files.
All flags MUST be implemented using the long-form notation, eg. --help. Err on the side of clarity over brevity every
time. Short-form flags MAY be implemented as shorthand aliases of the long-form versions. It is RECOMMENDED to reserve
short-form aliases for a subset of flags that are the most useful for human users, such as -h for --help. There
should not be too many short-form flags; there is a finite number of one-letter flags you can add to a program, so be
wary of polluting this particular namespace.
Normally, flags behave as boolean toggles. Alternatively, flags may take an input value. In Unix, the syntax is
--flag value or --flag=value. For flags that accept a value, try to design the flag so that the value is optional –
have a sensible default. For some use cases, you might consider allowing a special word like "none" to refer to no value
at all. For example, ssh -F accepts an optional filename (an alternative path to ssh_config) but ssh -F none runs
SSH with no config file at all.
Follow existing patterns and choose standard and conventional names for flags. Here’s a list of common flags used in Unix programs:
Flag | Abbr. | Description | Examples |
|---|---|---|---|
|
| All |
|
|
| Show debugging output | |
|
| Describe what changes the command would make, without making them |
|
|
| Force a destructive or dangerous operation without confirmation |
|
| Display JSON output | ||
|
| Show help text | |
| Non-interactive mode | ||
|
| Output file |
|
|
| Port |
|
|
| Quiet mode (display less output or none at all) | |
|
| User |
|
|
| Version | |
| Verbose output |
Note that -v is varyingly used as an abbreviation for "version" and "verbose". To avoid confusion, it is best to not
use this short-form flag at all. Ideally, when this flagged is used, suggest what other flags the user can try instead.
Where a version shorthand is offered, prefer -V (uppercase) instead, which avoids the ambiguity entirely.
$ my-tool -v The flag "-v" is not recognized. Did you mean "--version"?
--version output
The --version flag SHOULD produce a predictable, parseable format: a first line giving the program’s canonical name –
a constant string, not derived from argv[0], so it reads the same regardless of how the program was invoked – followed
by the version number, then optionally a copyright line and a license/no-warranty line. For example:
$ my-tool --version my-tool 2.4.1 Copyright (C) 2026 Example Org
--version is also a reasonable place to print additional debugging information (build metadata, platform, dependency
versions), and – for a program that makes HTTP requests – to source the string sent as the User-Agent header, which
helps with server-side debugging of client issues. Version SHOULD also be reachable via a version subcommand, in
addition to --version/-V.
As with --help (see Help text), once --version is seen, all other options and arguments SHOULD be ignored, and
the program SHOULD exit successfully after printing the version.
The -- end-of-options delimiter
Support the bare -- delimiter to mark the end of options. Everything after it is treated as a positional argument,
even if it looks like a flag. This is the standard POSIX convention, and it exists for two reasons.
First, it lets a value that begins with a hyphen be passed unambiguously, eg. grep — -foo file.txt searches for the
literal string -foo rather than trying to parse it as a flag.
Second, it lets your program pass its remaining arguments through to a subprocess without your own flag parser trying to interpret them. For example:
$ heroku run -a myapp -- myscript.sh -a arg1
Here, -a myapp is consumed by heroku run, and everything after -- – including its own -a arg1 – is passed
through untouched to myscript.sh.
Inputting secrets via arguments and flags
In most environments, programs SHOULD NOT read secrets from arguments or flags such as --password. In Unix, secrets
passed as arguments and flags will leak into the output of the ps command, and potentially the shell history too.
Best practice is for CLI programs to accept secrets only via files or stdin.
So, a --password-file flag is better than --password. A --password-file flag, in which only a local file path is
passed to the program, is more secure. Passwords are not leaked into the command history, and the user has control over
the permissions on the file that stores the password.
(Note, in Unix shells it’s possible to pass a file’s contents into an argument, eg --password $(< password.txt), but
this has the same security concerns as typing the password directly into the command.)
It is also safe to prompt users for passwords and other secrets, so programs capture them from stdin. But to support non-interactive environments you SHOULD provide an alternative means for secrets to be inputted non-interactively.
Configuration files
Configuration files can exist at three levels in the user’s filesystem:
- Localized (directory-scoped)
- The user level
- The system level (aka. global)
Configuration files have precedence in that order, from highest to lowest. Configuration files under the user’s home directory take priority over system-wide configurations, and local configuration files – ie. configurations set within the scope of the execution of an instance of the software program – take precedence over both system-wide and user-level configs.
User-level and system-wide configuration
Options that are very likely to stay consistent from one invocation to the next, on the same computer, SHOULD be configurable via centralized user-level or system-wide configuration files.
User-level configurations, which are normally stored in the user’s home directory, tend to be used for things like specifying how color should be used in output, providing non-default paths to dependencies, and configuring HTTP proxy servers to route all requests through – things that are preferences or requirements of individual users.
For both system-wide and user-level configurations on Unix-like systems, it is RECOMMENDED to follow the XDG Base
Directory specification. It specifies the location of base directories where config files may be located. A goal of this
standard is to limit the proliferation of dotfiles in a user’s home directory by supporting a general-purpose
~/.config folder. The XDG Base Directory specification is supported by a wide range of programs including yarn,
fish, wireshark, emacs, neovim, and tmux.
The XDG Base Directory specification also covers data and cache files, not just configuration. Use
~/.local/share/<app> for data files your program needs to persist (eg. a local database, downloaded assets) and
~/.cache/<app> for files that are safe to delete at any time without loss of functionality (eg. a rebuildable index).
On platforms that don’t follow the XDG specification, use the platform’s conventional equivalents instead – for example,
~/Library/Caches/<app> on macOS and %LOCALAPPDATA%\<app> on Windows.
Local configuration
Configurations that are scoped to the execution of a program within a particular directory are called local
configurations. Local configuration files include Makefile, package.json, docker-compose.yml, and .env files.
For example, the existence of a Makefile in a project directory controls the behavior of make within that directory.
Local configuration files are commonly used to control the behavior of tools used to automate software development processes within the context of a project directory. These files are typically committed to version control systems, so the configurations can be shared and installed consistently in multiple environments.
Typically, local configuration files override environment variables, user-level configurations, and system-wide (global) configurations. The purpose of local configuration files is to be able to share the same configuration of a tool among multiple users and environments, so the tool behaves consistently in different contexts.
It is RECOMMENDED to allow individual users to provide a custom file name or path for the local configuration file, via
an optional flag and/or an environment variable (eg. --config <path> or [APP]_CONFIG). Teams sometimes need to
maintain multiple configuration profiles within the same project directory, and a hardcoded file name prevents that.
External configurations
If your program needs to modify a configuration file that does not belong to your program, it MUST ask for the user’s
consent before doing this. But prefer instead to create a new config file (eg. /etc/cron.d/my-app) rather than
appending to an existing ones (eg. /etc/crontab).
Environment variables
Environment variables SHOULD be used to vary behavior of commands based on the context in which they are run. In the case of Unix CLI programs, the environment is the terminal session.
For maximum portability, environment variable identifiers MUST contain only upper case ASCII letters, with underscores delimiting words. Numbers MAY be included, but identifiers MUST start with a letter.
It is RECOMMENDED to repurpose standard, general-purpose environment variables where appropriate. The following are useful to know about:
Name | Description |
|---|---|
| Disable colorful output |
| Enable more verbose output |
| The user’s preferred program for editing files or inputting multiple lines of text |
| Check these when performing network operations |
| Open interactive sessions in the user’s preferred shell |
| Use this directory to store temporary files |
| The user’s home directory, used to locate user-level configuration files |
| The default tool to enable paged output |
| Use these to adjust output based on screen size |
| Check these before emitting terminal-specific escape sequences |
SHELL names the user’s preferred shell for opening an interactive session – for example, when your program drops the
user into a subshell. If instead you need to execute a shell script or a one-off shell command, use a specific,
predictable interpreter such as /bin/sh rather than $SHELL. The user’s preferred interactive shell may not be
POSIX-compatible, and scripting against it makes your program’s behavior depend on the user’s personal environment.
For environment variables that are specific to your program, it is RECOMMENDED to prefix the env vars with a unique identifier for your program, to avoid conflicts with other applications. In particular, avoid clashes with POSIX-standard environment variables, which are listed here.
Environment variables have a disadvantage of being limited to one data type: string. Multi-line string values are
possible, but programmers should aim for configurations to be composed of online single-line string values. Multi-line
strings create interoperability problems (eg. with some .env file parsers) and usability issues (eg. with some env
commands).
.env files
Environment files, which are normally named .env, are a convention for declaring environment variables through local
configuration files. This convention is widely supported, with most mainstream programming languages having built-in
libraries for reading .env files.
Unlike most other types of local configuration, .env files tend not to be committed to version control.
The purpose of .env files is to give users the convenience of being able to set environment variables on a
directory-by-directory basis. Where there are valid use cases for adjusting the configuration of a tool on a
directory-by-directory basis (eg. project-specific configurations), you SHOULD consider making the tool recognize .env
files.
All options that can be set in a .env file MUST be settable via conventional environment variables, too. When a .env
file is loaded, its settings SHOULD override actual environment variables with the same identifiers, but only within the
scope of the .env file’s directory path.
You MUST NOT use .env files as a substitute for proper configuration files, which are more versatile (eg. supporting
more types) and can be stored more securely (eg. outside of version controlled directories).
Inputting secrets via environment variables
We MUST NOT design CLI programs to read secrets from environment variables. This will encourage users to store sensitive information in environment variables, which are prone to leakage, for the following reasons:
- Exported environment variables are sent to every process, and from there can easily leak into logs.
- Shell substitutions like
curl -H "Authorization: Bearer $BEARER_TOKEN"will leak into globally-readable process state. - Docker container environment variables can be viewed by anyone with Docker daemon access, via
docker inspect. - Environment variables in systemd units are globally readable via
systemctl show.
As explained in the section on arguments and flags, best practice is to accept secrets only via credential files, pipes
(stdin), AF_UNIX sockets, or a dedicated secret-management service or other IPC mechanism.
Note
This rule applies to CLI programs that are widely distributed. Environment variables are actually a great way of injecting secrets into applications at runtime, when those applications are deployed to environments that you control.
Subcommands
If you have several tools that are closely related, you can make them easier to use by combining them under a single command. It means you have a single program that can share configuration, storage, flags and arguments, and help text. Therefore, overall complexity is greatly reduced. This is what Git and many other programs do very well.
Be consistent. Use the same arguments and flags across all the subcommands. Have similar output formatting and error handling. Prompt for input in the same way. And so on. Subcommands SHOULD feel like they’re all parts of a cohesive program, rather than standalone programs under a common namespace.
Avoid ambiguous subcommands, and avoid having two subcommands that have similar names. Avoid, for example, having both
update and upgrade subcommands. This is quite confusing. Disambiguate with extra words, eg. update-dependencies,
upgrade-latest.
In complex programs with lots of objects, and lots of operations that can be performed on each of those objects, it is a
common pattern to use two levels of subcommands, where one is a noun (referring to the object) and one is a verb
(referring to the operation). You can use the noun verb or the verb noun pattern. The first seems to be the more
common pattern — eg
.docker container create — but either is fine as long as you are consistent.
The choice has a maintenance dimension worth considering as your command set grows. If every noun will always support
the same set of verbs, verb noun scales easily – you add a new verb once, and it applies uniformly. If different nouns
support very different verbs, noun verb (grouping every verb under its noun) tends to be lower-maintenance, since each
noun’s verb set can evolve independently without the top-level verb list becoming a grab-bag of noun-specific
operations.
Where subcommands are given a second level using a namespace-like delimiter rather than a space – eg.
heroku domains:add instead of heroku domains add – the colon form lets the topic command (domains) itself accept
arguments while still having subcommands, something a purely space-delimited parser cannot disambiguate. Whichever
delimiter you choose, never create a *:list-style command purely to list the nouns under a topic – the topic root
itself (heroku domains, with no subcommand) should list them.
Where subcommands are used in combination with flags, the positioning of flags within the subcommand structure SHOULD NOT be significant. By way of example, the following two commands SHOULD be equivalent. However, it is acknowledged that this is not always possible. You may be constrained by the runtime environment or the capabilities of your argument parser.
$ my-tool --flag subcmd $ my-tool subcmd --flag
It is not recommended to design in default or "catch-all" subcommands. If you have a subcommand that’s likely to be used
the most, you might be tempted to let people omit it entirely for brevity’s sake. For example, say you have a run
command that wraps an arbitrary shell command:
$ mycmd run echo "hello world"
You could make it so that, if the first argument to mycmd is not the name of a subcommand, you assume the user means
run:
mycmd echo "hello world"
This has a serious drawback. Now you can never add a subcommand named echo — or anything at all — without risking
breaking existing usages. If there’s a script out there that uses mycmd echo, it will do something entirely different
after that user upgrades to the new version of your tool. Oops. It is for this reason that you should avoid default or
"catch-all" subcommands. Instead, require all subcommands to be explicitly invoked.
Also, don’t allow arbitrary abbreviations of subcommands. For example, say your tool has an install subcommand. When
you added it, you wanted to save users some typing, so you allowed them to type any non-ambiguous prefix, like
mycmd ins, or even just mycmd i. Now you’re stuck. You can’t add any more commands beginning with i. There’s
nothing in principle wrong with aliases — saving on typing is good — but they should be explicit and remain stable.
When a subcommand rename or restructuring is unavoidable, consider going beyond a deprecation warning and shipping an automated migration command that rewrites a user’s own shell scripts to use the new subcommand names. This is more work than a warning, but for a widely-scripted CLI it removes the manual-update burden from every downstream user at once, rather than leaving each of them to find and fix their own scripts.
Interactivity
Prompts
Never build CLI commands that require the user be prompted for input. It MUST be possible to run CLIs non-interactively. This means it MUST be possible to input all invocation-specific parameters via arguments or flags.
Prompts and other interactive elements MUST be enabled only if stdin is an interactive terminal (a TTY). In Unix-like systems, this is a pretty reliable way to tell whether you’re piping data into a command or whether it’s being run in a script. If the program is not being run in an interactive terminal, prompts MUST NOT be used. Instead, the program MUST return errors when required input parameters are missing.
# Check if stdin (file descriptor 0) is a TTY. if [ -t 0 ]; then echo "Running in a TTY" else echo "Not running in a TTY (redirected or piped)" fi
Consider designing in the --no-input flag to disable prompts. This MUST force the program to run in non-interactive
mode.
In interactive mode, when a user does not pass a required argument or flag, the program SHOULD prompt for the missing input, if possible. In most use cases, this is a better experience for the user than receiving an error.
In interactive mode, always prompt for confirmation before doing anything dangerous or highly destructive. A common
convention is to require the user to type y or yes. In non-interactive environments, confirmation SHOULD be done by
passing the --force/-f flag. Consider also offering users a --dry-run mode, which will show them the consequences
of their action before they commit to it.
Not every dangerous action deserves the same level of friction. Grade the confirmation to the severity of what’s about to happen:
- Mild. A small, local change – for example, deleting one file the user named explicitly. This MAY need no prompt at all, since the user’s own command already made their intent explicit.
- Moderate. Deleting a directory, a change to a remote system, or a bulk modification that can’t be easily undone.
This SHOULD prompt for confirmation by default, and offering a
--dry-runfirst is especially valuable here. - Severe. Deleting something complex and hard to recreate – an entire remote application or environment, for example.
Make confirmation hard to trigger by accident: ask the user to type something non-trivial, such as the name of the
thing being deleted, rather than a bare
y/yes. Still allow this to be scripted, via a flag that supplies the same value non-interactively (eg.--confirm=<name>), so severe operations remain usable in automation without weakening the safeguard for humans.
When prompting for passwords, don’t print the password as the user types. In Unix-like systems, this is done by turning off echo in the terminal. Most other systems will have helper functions to support this.
For choosing between several options, consider a richer interactive selection UI – an arrow-key list, checkboxes, or radio buttons – rather than asking the user to type a value exactly. Presenting the choices visually removes typos and the need to remember exact option spellings. As with any prompt, this MUST be gated on stdin being a TTY (see above), with an equivalent flag-based way to supply the same choice non-interactively.
Signals
Let users escape from operations by typing Ctrl+C (the INT signal). Exit as soon as possible, and return a
confirmation of the exit before you start clean-up. Make the exit path obvious – the user should never be left wondering
how to get out of the program. Don’t trap the user in a mode with no visible way back to the shell, the way some
full-screen terminal programs do.
For a program that wraps execution of something else – an SSH session, a tmux client, a telnet session – Ctrl+C
may be consumed by the wrapped program instead of reaching your CLI. Make the escape route clear in these cases,
following the precedent of SSH’s ~ escape sequences (eg. ~. to terminate the connection), and document it in the
program’s help text.
Remember to add a timeout to any clean-up operation, so it doesn’t hang forever. If the user types Ctrl+C again
during the clean-up operation, cancel the clean-up. Design your programs to start in situations where clean-up of prior
operations has not been completed.
Where the second Ctrl+C would trigger something destructive – Docker Compose, for example, forces its containers to
stop immediately on a second Ctrl+C rather than waiting for a graceful shutdown – tell the user what will happen if
they press it again, at the moment clean-up begins. Don’t let a destructive shortcut be a surprise.
Piping
In Unix CLIs, if either the input or output is a file, the CLI SHOULD support use of - to read from stdin or write to
stdout. This is known as piping. It lets the output of another command be the input to your command, and vice versa,
without needing to redirect data through a temporary file.
For example, tar can extract files from stdin, like this:
$ curl https://example.com/something.tar.gz | tar xvf -
If your command is expecting to have something piped to it when stdin is an interactive terminal, and no input is provided, you have two options:
- Display help text and quit immediately.
- Or print an error message to stderr.
But don’t do nothing. Don’t let the operation just hang, like cat does.
Exit codes
In Unix-like environments, exit codes are how scripts determine whether a command succeeded or failed. You MUST report these correctly.
Return a zero exit code on success and any non-zero exit code on failure.
Error codes SHOULD be positive integers of 1 or greater. Reserve 1 for a general, undocumented error. Map other errors codes (of 2 or higher) to the various failure modes of your program, and document these.
Thus, there SHOULD be a unique exit code for each type of failed operation in your program. Each of these failure modes MUST be fastidiously documented. One of the reasons to consult documentation is to fix errors. That is why all types of error from a program MUST be documented.
Error messages SHOULD be written to stderr when a command returns a non-zero exit code. See the section on errors for more guidance on how error messages can be used to support debugging of failure modes.
Output
Standard output
In Unix-like environments, the primary output from most CLI programs goes to stdout. For most commands, this should be a human-readable text stream. In most cases, output should be in a human-readable format by default.
Since stdout is what is piped to the next command, it is RECOMMENDED to support flags that allow the user to toggle the
output into alternative machine-readable formats. For example, use --plain to support piping into tools such as grep
or awk, and --json for use with jq.
Human-readable output is not a stable interface, and it is fine to iterate on its wording and layout between releases.
Machine-readable output (--plain/--json) is different: once a script depends on it, changing its shape is a
breaking change (see Future proofing). Encourage users to build scripts against --plain/--json rather than
parsing the human-readable default, and treat the human-readable format as free to evolve.
Keep human-readable output grep-parseable: aim for one record per line, so a user can filter it with grep and count
matches with wc without your output format getting in the way. Avoid multi-section, grouped-header formats – a single
tabular row per item, as described in Tables, is easier to filter than output broken up under several headings.
If your program or command is expected to be used mostly in automation scenarios, it may make more sense for the default output format to be machine-readable. If so, you can still have your program automatically switch to a human-readable format by checking if the standard output stream is a terminal (TTY).
#!/bin/bash # Check if stdout (file descriptor 1) is a TTY. if [ -t 1 ]; then echo "stdout is a TTY" else echo "stdout is not a TTY" fi
In most cases, something SHOULD be sent to stdout on success, even if it is just a simple confirmation message that the
operation has finished successfully. Some commands such as cp don’t print anything, which has come to be regarded as
bad practice. The user SHOULD be given explicit feedback on all operations, and there should be something to pipe into
the next program.
Expect the output of every program to become the input to another, as yet unknown, program.
– Doug McIlroy
cite title
To support commands being used in an automated way via scripts, and to avoid clumsy redirection to /dev/null, provide
a --quiet/-q flag to run your program in "quiet mode", which will suppress all non-essential output and help
declutter log files.
Tables
When output is naturally tabular – a list of resources, for example – emit one record per row, so the output stays
grep-parseable (see Standard output). Never emit table borders or box-drawing characters in the default output; they
add visual noise and break grep/awk filtering.
It is RECOMMENDED to support conventional flags for tabular output:
--columns– choose which columns to display.--no-truncate– don’t truncate column values to fit the terminal width.--no-headers– omit the header row, useful for scripting.--filter– filter rows by a field value.--sort– sort rows by a field.--csv– emit CSV instead of the default aligned-column format.
Pagers
If you are outputting a lot of text, use a pager like less. git diff does this behind the scenes.
Be careful with your implementation. Pagers can cause unexpected behaviors and, implemented badly, can make the user experience worse. You MUST NOT use a pager if stdin or stdout is not an interactive terminal.
A sensible set of options for less is -FIRX, which does not page if the output fits within one screen, leaves the
output on screen when the user quits less, ignores case when the user searches the output, and enables colors and
formatting.
Formatting
Do make use of color and symbols in your programs' output. But don’t overdo it. If everything is colorful, the benefits are lost. And not everyone likes colorful output.
You SHOULD provide a flag for the user to disable color formatting (--no-color is recommended), and color formatting
SHOULD be disabled by default in the following circumstances:
- When stdout is not an interactive terminal (a TTY).
- When an environment variable named
NO_COLORexists. - When an environment variable named
[APP]_NO_COLORexists, where[APP]is a prefix identifying your program. - When the
TERMenvironment variable is set todumb, which signals a terminal with no color or escape-sequence support.
Conversely, it is RECOMMENDED to support FORCE_COLOR as the counterpart to NO_COLOR: when set, it forces color
output on even where TTY detection would otherwise disable it – for example, when output is being piped into another
program that itself renders color, such as a pager.
Restrained use of symbols and emoji can be beneficial. Characters such as ✅ and ❌ can be useful to draw the user’s attention to successes and failures respectively.
Focus on good formatting over symbols and colors. Work to increase information density while decreasing visual noise and
making judicious use of space to improve readability. The output from ls is a good example of this. An awful lot of
information is communicated in a very compact way, and consistent patterns in the output allow you to quickly pick out
the information you need and ignore the rest.
-rw-r--r-- 1 root root 68 Aug 22 23:20 resolv.conf lrwxrwxrwx 1 root root 13 Mar 14 20:24 rmt -> /usr/sbin/rmt drwxr-xr-x 4 root root 4.0K Jul 20 14:51 security drwxr-xr-x 2 root root 4.0K Jul 20 14:53 selinux -rw-r----- 1 root shadow 501 Jul 20 14:44 shadow -rw-r--r-- 1 root root 116 Jul 20 14:43 shells drwxr-xr-x 2 root root 4.0K Jul 20 14:57 skel -rw-r--r-- 1 root root 0 Jul 20 14:43 subgid -rw-r--r-- 1 root root 0 Jul 20 14:43 subuid
Animations
If stdout is not an interactive terminal, do not display any animations. This will stop progress bars turning into Christmas trees in log files.
Progress bars, spinners, and other "action" output are out-of-band information about what the program is doing, not the
program’s actual result. Send this output to stderr, not stdout, so that stdout can still be redirected or piped while
the user continues to see progress. curl follows this convention – its progress meter goes to stderr, leaving stdout
free for the downloaded content.
If a progress bar gets stuck in the same place for a long time, the user cannot tell whether work is still happening or the program has crashed or hung. Show an estimated time remaining, or keep some part of the display animated (a spinner character, a pulsing bar), so the user has continuous evidence that the program is still alive.
Reporting progress for work done in parallel is harder than for sequential work: naively interleaved output from multiple concurrent tasks is confusing and hard to read. Use a library designed for parallel progress reporting where one is available, rather than writing raw progress lines from multiple workers directly to the terminal.
Hiding detailed logs behind a progress bar or spinner, while things are going well, keeps output easy to follow. But if an error occurs, print the hidden logs – otherwise the failure is very hard to debug, because the information the user needs was suppressed exactly when they needed it most.
For a task that runs long enough that the user is likely to switch away to another window, consider triggering an OS-level desktop notification when it completes, in addition to the terminal output.
Errors
Errors SHOULD go to stderr. This means that, when commands are piped together, the error messages will get displayed to the user but the standard output will get piped into the next command.
Error messages MUST be sent to stderr whenever a command returns a non-zero exit code. The error message MUST provide sufficient information to help the user fix the problem identified by the exit code.
Error formatting
Different platforms have different error formatting conventions. It is RECOMMENDED to follow the prevailing conventions of the platform. For example, in Unix shells the classic shape is:
<program-name>: <error message>
Or, when location matters:
<program-name>: <file>:<line>:<column>: <error message>
Keeping the error format predictable matters more than the exact punctuation.
Interactive programs – ones that read commands from a terminal in a loop, such as a REPL – SHOULD NOT include the
program name in error messages; identity is already conveyed by the prompt or the surrounding screen layout, so
repeating it is redundant. The same program, when it reads from a non-terminal (a script feeding it commands, for
example), SHOULD switch to the noninteractive <program-name>: <message> style described above, since there is no
prompt to carry that context.
For tools that report source locations – linters, compilers, formatters – error positions SHOULD include both column and
line numbers, calculated with tab stops every 8 columns and assuming equal-width ASCII characters, or the correct
Unicode character widths in UTF-8 locales. Where an error spans a range rather than a single point, use a start-and-end
position format such as file:line1.column1-line2.column2, or, for a span across multiple files,
file1:line1.column1-file2:line2.column2.
Error message content
A good error message communicates three things: what went wrong, where, and what to do next. For example:
permission denied: cannot write to /etc/foo - try running with sudo or use --output to choose a writable path
is better than:
permission denied
For errors that are complex enough to warrant it, structure the message around four parts: an error code, a short error title, an optional longer description, and how to fix it – linking to the stable error-code page (see Supporting bug reporting) for more detail rather than dumping everything into the terminal.
Do not treat stderr like a log file. Don’t print log-level labels here — "ERROR", "WARN", etc. — or any other extraneous contextual information, unless your program was explicitly invoked in verbose mode.
Where a program does write an error log file for post-mortem debugging, each entry MUST include a timestamp, and the file MUST be truncated or rotated occasionally so it does not grow without bound. Log files MUST NOT contain ANSI color codes – they are written for later reading with tools that don’t render them, and the stray escape sequences make the file harder to grep.
On wording, follow these conventions:
- Start with lowercase (unless starting with a proper name).
- No trailing period or exclamation marks.
- Write one sentence per error so messages compose cleanly when wrapped.
Example of good wording:
can't write to file.txt - you might need to make it writable, try running 'chmod +w file.txt'
Error discovery
If the user enters an invalid command name, the program SHOULD suggest valid commands with similar spellings. This user interface convention was poplularized by Git, and it makes for an excellent user experince. Example:
unknown command "buidl" — did you mean "build"?
Signal-to-noise ratio
Signal-to-noise ratio is crucial. The more irrelevant output you produce, the longer it’s going to take the user to figure out what went wrong. If your program produces multiple errors of the same type, consider grouping them under a single explanatory header instead of printing many similar-looking lines.
Consider where the user will look first. Put the most important information at the end of the error output, not the start.
The eye will be drawn to red text, so use it intentionally and sparingly.
If there is an unexpected or unexplainable error, provide debug and traceback information, and instructions on how to
submit a bug. That said, don’t forget about the signal-to-noise ratio. You don’t want to overwhelm the user with
information they don’t understand. Not all users are developers! Consider, for example, writing scary-looking stack
traces to a debug log, instead of printing it to the terminal. Hide stack traces and internal details behind a
--verbose or --debug flag, or an equivalent DEBUG environment variable – the environment variable is convenient
for wrapping a command in a script or CI job without having to change the invocation itself.
Error presentation
It is RECOMMENDED to use only a limited amount of color in stderr output, or no color at all. Prefer to use only indentation and other plain-text formatting to give the output structure. However, it can be beneficial to use some color to draw the user’s attention to important information. The convention for errors is to use red.
When using colors and symbols (such as ❌ for failures), follow these rules:
- Use color and symbols only when stderr is an interactive terminal (a TTY).
- Disable colors in the stderr output in these circumstances:
- When stderr is not an interactive terminal.
- When an environment variable named
NO_COLORexists. - When an environment variable named
[APP]_NO_COLORexists, where[APP]is a prefix identifying your program.
- Do not emit progress bars, spinners, or other animations to stderr if the output is being piped or logged.
Distinguishing error types
Distinguish user errors (bad flags, missing files) from internal errors ("this shouldn’t happen, please report at …") so people know whether to fix their command or file a bug.
Exit codes play an important role here. While there is no single canonical standard, the BSD sysexits.h conventions
assign meanings to codes 64–78 (EX_USAGE=64, EX_DATAERR=65, EX_NOINPUT=66, etc.). At minimum:
- 0 for success
- 1 for general failure
- 2 for usage errors
- 130 when interrupted by Ctrl-C (128 + SIGINT)
Scripts depend on exit codes, so be deliberate and document them thoroughly.
Supporting bug reporting
Make it effortless to submit bug reports. Provide a URL where users can report issues, and have the bug submission form pre-populated with as much information as possible.
If your CLI has a website or documentation, consider linking to a stable error code page (for example,
https://example.com/errors/E042). This lets you keep the in-terminal message short while still being thorough.
References
The GNU coding standards have more guidance on formatting error messages. The Command Line Interface Guidelines and the Rust and Go style guides offer coherent approaches, too.
Documentation
CLI API signature notation
The POSIX specification defines a notation for describing the APIs — the subcommands, arguments and flags — of command line utilities. Most CLI environments, including Node.js, adopt this notation for documentation purposes.
This notation is perhaps best described using a real example — from Git:
$ git --help
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
[--super-prefix=<path>] [--config-env=<name>=<envvar>]
<command> [<args>]Another useful notation is the use of an ellipsis … to indicate repetition:
<arg>…: one or more arguments.[--flag <value>]…: option can be repeated.
A convention – but not part of the POSIX spec – is to use [options…] to indicate that multiple unspecified options
can be passed in.
Use the | notation to indicate mutually-exclusive options, eg. [-p | --paginate | -P | --no-pager] from the Git
example above means exactly one of these four options may be given, not several at once. Where a command has multiple
mutually-exclusive sets of arguments – for example, a subcommand that accepts either a name or an ID, but combines with
different additional flags depending on which – show each set on its own synopsis line, rather than trying to express
the whole combination with | and [] alone.
This notation MUST be used to document CLI APIs, whether the documentation is outputted from --help/-h flags or from
man pages, or whether it is written in Markdown or AsciiDoc files, or published online.
Help text
User documentation MUST be built-in to all CLI apps. Help text is triggered through the --help/-h flags.
It is not necessary for help text to document every option in a command’s signature, only the most useful and most
widely used options. Common options that behave the same way across every command – --version and --help themselves,
for example – can be documented once, elsewhere, with help text simply linking to the "full documentation" rather than
repeating their descriptions in every command’s output. It is RECOMMENDED that extended user documentation be published
online, in which lesser-used commands and options are fully documented, too. For public applications, this online
documentation MUST be indexable by public search engines, so users can find it without already knowing the tool’s name.
Built-in documentation, accessible directly from the tool, has two advantages over online documentation: it always matches the installed version exactly – there’s no risk of reading docs for a newer or older release than the one in front of you – and it works offline, which matters for tools used in restricted or air-gapped environments.
Consider also providing your help texts via man pages. These are UNIX’s original system of documentation, and they’re
still in use today. Many users reflexively check man some-tool as a first step when trying to learn a new tool.
However, not everyone knows about man, and it doesn’t work on all platforms. For this reason, man pages are an
optional extra, and all important documentation MUST be accessible via the tool directly. Tools such as help2man and
ronn can generate man pages directly from a command’s --help output, which is a much lower-effort path than writing
and maintaining man pages by hand.
If a command or sub-command requires one or more arguments or flags, you SHOULD design the command to return abbreviated
help text by default, when no arguments or flags are provided. So my-tool cmd would produce an abbreviated version of
the output from my-tool cmd --help and my-tool cmd -h. Obviously, this is not possible for simple commands that do
exactly one operation, and for programs that read input interactively (eg. cat).
The abbreviated help text SHOULD include:
- A description of what the program or command does.
- The command’s API signature, or one or two usage examples.
- Descriptions of the most useful flags.
- Instructions to use the
--help/-hflag to get more detailed information.
jq does this well. When you type jq without any options, it shows abbreviated help text, and suggests using
jq --help to get the full help text.
$ jq
jq - commandline JSON processor [version 1.6]
Usage: jq [options] <jq filter> [file...]
jq [options] --args <jq filter> [strings...]
jq [options] --jsonargs <jq filter> [JSON_TEXTS...]
jq is a tool for processing JSON inputs, applying the given filter to
its JSON text inputs and producing the filter's results as JSON on
standard output.
The simplest filter is ., which copies jq's input to its output
unmodified (except for formatting, but note that IEEE754 is used
for number representation internally, with all that that implies).
For more advanced filters see the jq(1) manpage ("man jq")
and/or https://stedolan.github.io/jq
Example:
$ echo '{"foo": 0}' | jq .
{
"foo": 0
}
For a listing of options, use jq --help.When --help/-h flags are passed, all other flags and arguments SHOULD be ignored, and an extended help text MUST be
outputted. This SHOULD include the following:
- A more detailed description of what the program or command does.
- The command’s API signature.
- OPTIONALLY, example invocations of common use cases. Users tend to use examples over other forms of documentation, so it is best to print these early in the help text. If it is useful, include the program’s expected output in the examples, too.
- Descriptions of all available subcommands.
- Descriptions of all the available flags. If there are too many of these, list the most useful, and provide a link to online documentation of the rest. Fit descriptions within an 80-character width, begin with lowercase, and do not end with a period – the same wording conventions as Error message content.
- OPTIONALLY, a link to an online version of the documentation. If included, this MUST be versioned documentation, corresponding to the installed version of the program.
- OPTIONALLY, a link to provide feedback, log issues, or request support.
Help texts MAY be formatted. Make headings bold, for example, so the text is easier to scan. But do this only if it can be done in a terminal-independent way, so no user sees escape characters.
In addition to --help/-h flags, consider offering a help subcommand – myapp help for the top-level help text,
and myapp help subcommand equivalent to myapp subcommand --help. This pattern was popularized by Git and npm (npm help ls is the same as man npm-ls), and some users reach for it instinctively before trying a flag.
For CLIs with a very large number of commands, listing every command in a single flat help text becomes a wall of text
nobody can scan. Prefer structured help: show only the immediate subcommands of whatever the user just typed, rather
than the whole tree at once, and group related commands visually under headings – the heroku --help example above
already does this, listing top-level topics rather than every heroku apps:* subcommand.
Shell completion
Provide shell completion scripts for the shells your users are likely to use (bash, zsh, fish, PowerShell). Completion
is one of the most effective aids to discoverability and correct usage: typing --app <tab><tab> and seeing the valid
values removes the guesswork of remembering flag names and value formats, and it catches typos before the command is
ever run. Document how to install the completion script as part of your installation instructions.
The heroku app has some of the best examples of CLI help texts. The whole API is very easily discoverable through the
help texts. There’s no color, except some of the less important text is a mid-grey. It mostly uses plain text
formatting, with indentation providing structure. Here are a few examples:
$ heroku --help CLI to interact with Heroku VERSION heroku/7.60.1 linux-x64 node-v14.19.0 USAGE $ heroku [COMMAND] COMMANDS access manage user access to apps addons tools and services for developing, extending, and operating your app apps manage apps on Heroku auth check 2fa status authorizations OAuth authorizations autocomplete display autocomplete installation instructions buildpacks scripts used to compile apps certs a topic for the ssl plugin ci run an application test suite on Heroku clients OAuth clients on the platform config environment variables of apps container Use containers to build and deploy Heroku apps domains custom domains for apps drains forward logs to syslog or HTTPS features add/remove app features git manage local git repository for app help display help for heroku keys add/remove account ssh keys labs add/remove experimental features local run Heroku app locally logs display recent log output maintenance enable/disable access to app members manage organization members notifications display notifications orgs manage organizations pg manage postgresql databases pipelines manage pipelines plugins list installed plugins ps Client tools for Heroku Exec psql open a psql shell to the database redis manage heroku redis instances regions list available regions for deployment releases display the releases for an app reviewapps manage reviewapps in pipelines run run a one-off process inside a Heroku dyno sessions OAuth sessions spaces manage heroku private spaces status status of the Heroku platform teams manage teams update update the Heroku CLI webhooks list webhooks on an app
$ heroku apps --help list your apps USAGE $ heroku apps OPTIONS -A, --all include apps in all teams -p, --personal list apps in personal account when a default team is set -s, --space=space filter by space -t, --team=team team to use --json output in json format EXAMPLES $ heroku apps === My Apps example example2 === Collaborated Apps theirapp other@owner.name COMMANDS apps:create creates a new app apps:destroy permanently destroy an app apps:errors view app errors apps:favorites list favorited apps apps:info show detailed app information apps:join add yourself to a team app apps:leave remove yourself from a team app apps:lock prevent team members from joining an app apps:open open the app in a web browser apps:rename rename an app apps:stacks show the list of available stacks apps:transfer transfer applications to another user or team apps:unlock unlock an app so any team member can join
$ heroku logs --help display recent log output USAGE $ heroku logs OPTIONS -a, --app=app (required) app to run command against -d, --dyno=dyno only show output from this dyno type (such as "web" or "worker") -n, --num=num number of lines to display -r, --remote=remote git remote of app to use -s, --source=source only show output from this source (such as "app" or "heroku") -t, --tail continually stream logs --force-colors force use of colors (even on non-tty output) DESCRIPTION disable colors with --no-color, HEROKU_LOGS_COLOR=0, or HEROKU_COLOR=0 EXAMPLES $ heroku logs --app=my-app $ heroku logs --num=50 $ heroku logs --dyno=web --app=my-app $ heroku logs --app=my-app --tail
References
- Command Line Interface Guidelines. Command Line Interface Guidelines.
- Dickey, J (2018). 12 Factor CLI Apps.
- Heroku. Heroku CLI Style Guide.
- Stallman, R et al. GNU Coding Standards.
- Starich, J (2019). User Experience, CLIs, and Breaking the World.
- Tashian, C. The Poetics of CLI Command Names.
- The Open Group. Utility Conventions.
- Unix & Linux Stack Exchange (2010). What is the exact difference between a "terminal", a "shell", a "tty" and a "console"?.