The guidelines
These guidelines have been formulated since the first CLI and have continued to evolve through the many years of developer and user experience. Following these guidelines will increase your chances of CLI success; however, there may be times when you decide to go your own way and follow an anti-pattern. There could be many reasons to choose an unconventional route. Remember that these are just guidelines and there are no hard and fast rules.
For life, and building CLIs, to be fun, we must allow a little chaos and the freedom necessary to be creative.
Name
The name of the CLI holds significant weight as the name may convey symbolic ideas beyond the initial intention. People do not like to think more than necessary, so it’s best to choose a name that is simple, memorable, and easy to pronounce. It’s amazing how many CLI program names have been chosen so arbitrarily without much thought.
There have been studies that support the linguistic Heisenberg principle: labeling a concept changes how people perceive it.
Hence, keep it short and easy to type. Use entirely lowercase variables in the name and only use dashes when absolutely necessary.
Some of my favorite application names are clear in the way that they plainly describe the application’s purpose in a creative manner. For example, Homebrew is a package manager for installing applications on macOS. A brew is a concoction of various ingredients, like a recipe, or in this particular case, a formula, to describe how to install an application. Another great example is imagemagick
, a command-line application that lets you read, process, or create images magically! Truly, as Arthur C. Clark writes, “Any sufficiently advanced technology is indistinguishable from magic.” Other internal commands we are familiar with are mkdir
, for make directory, rm
, for remove, and mv
, for move. Their popularity is partially a result of the transparent nature of their names, rendering them nearly unforgettable.
Help and documentation
One of the tenets of the UNIX philosophy is transparency, possible mainly through the help and documentation present within the CLI. For new users of the CLI that are in discovery mode, the help and documentation are one of the first sections they will visit. There are a few guidelines to make the help and documentation more easily accessible to the user.
Help
It is a good practice to display help by default when just the command name is entered or with either the -h
or –help
flag. When you display the help text, make sure it’s formatted and concise with the most frequently used arguments and flag options at the top. Offer usage examples, and if a user misuses the CLI, the program can guess what the user tried to attempt, providing suggestions and next steps.
Documentation
Provide either man pages or terminal-based or web-based documentation, which can provide additional examples of usage. These types of documentation may be linked from the help page as an extension of the resources for gaining an understanding of how the CLI works.
Support
Oftentimes, users will have suggestions or questions on how to use the CLI. Providing a support path for feedback and questions will allow users to give the CLI designer a new perspective on the usage of their CLI. When collecting analytics, be transparent and don’t collect users’ address, phone, or usage data without consent.
Input
There are several ways a CLI retrieves input, but mainly through arguments, flags, and subcommands. There is a general preference for using flags over arguments and making the default the right thing for most users.
Flags
The guideline for flags is that ideally, there exists a full-length version for all flags. For example, -h
has --help
. Only use –
, a single dash, or shorthand notation for commonly used flags and use standard names where there is one.
The following is a list of some standard flags that already exist:
Flag |
Usage |
|
All |
|
Debug |
|
Force |
|
Display JSON output |
|
Help |
|
Disable prompt and interactivity |
|
Output file |
|
Port |
|
Quiet mode |
|
User |
|
Version |
|
Version or verbose |
|
Verbose |
Table 1.1: Standard flags
Arguments
Multiple arguments are fine for simple actions taken on several files. For example, the rm
command runs against more than one file. Although, if there exist two or more arguments for different things, you might need to rethink the structure of your command and choose a flag option over an additional argument.
Subcommands
The guidelines for subcommands are that they remain consistent and unambiguous. Be consistent with the structure of subcommands; either noun-verb or verb-noun order works, but noun-verb is much more common. Sometimes, a program offers ambiguous subcommands, such as apt update
versus apt upgrade
, which causes many, including myself, confusion. Try to avoid this!
Validate the user’s input early, and if it’s invalid, fail early before anything bad happens. Later in this book, we will guide you through using Cobra, a popular and highly recommended command-line parser for Go, to validate user input.
Output
Because CLIs are built for humans and machines, we need to consider that output must be easily consumed by both. I will break down guidelines for both stdout
and stderr
streams for both humans and machines. Standard output, stdout
, is the default file descriptor where a process can write output, and standard error, stderr
, is the default file descriptor where a process can write error messages:
- stdout
A guideline for standard output for humans is to make the responses clear, brief, and comprehensible for the user. Utilize ASCII art, symbols, emojis, and color to improve information density. Finally, consider simple machine-readable output where usability is not impacted.
A guideline for standard output for machines is to extract any extraneous substance from the above response so that it is simply formatted machine-readable text to be piped into another command. When simple machine-readable text is not output by default, utilize the -q
flag to suppress non-essential output and --plain
to display machine-readable text. Disable color with the --no-color
option, by setting the NO_COLOR
environment variable, or a custom MYAPP_NO_COLOR
environment variable specific to your program.
Additionally, don’t use animations in stdout
because it’s not an interactive terminal.
- stderr
Things can go wrong during the execution of a command, but it doesn’t have to feel like a catastrophic event. Sometimes loud full stack traces are the response to a command failure, and that can make the heart skip a beat. Catch errors and gracefully respond to the user with rewritten error messages that can offer a clear understanding of what happened and suggestions for the next steps. Make sure there’s no irrelevant or noisy output, considering we want it to be easy to understand the error. Also, provide users with additional debug and traceback information and an option to submit bugs. Non-error messages should not go to stderr
, and debug and warning messages should go to stdout
instead.
Note
As for general guidelines for CLI output, return a zero exit code on success and a non-zero code that the machine can interpret as not just a failure but even a particular type of failure on which to take further action.
Configuration
Users may configure their CLI by using flags, environment variables, and files to determine how specifically to invoke the command and stabilize it across different users and environments:
- Flags and environment variables
By using flags or environment variables, users may configure how to run a command.
Examples include the following:
- A specified level of debug output
- Dry run commands
Alternatively, they can be used to configure between different environments.
Examples include the following:
- Providing a non-default path to files required for the program to execute
- Specifying the type of output (text versus JSON)
- Specifying an HTTP proxy server to route requests through
When using environment variables in configuration, set names appropriately, using a combination of all uppercase text, numbers, and underscores, and take the time to ensure you are not using the name of an already-existing environment variable.
- XDG Spec
Configure stability across multiple environments by following the XDG Spec (X Desktop Group, freedesktop.org), which specifies the location for base directories where configuration files may be located. This spec is supported by many popular tools, such as Yarn, Emacs, and tmux, to name a few.
Security
Do not store secrets and passwords in environment variables or pass them in via an argument or flag. Instead, store them in a file and use the --password-file
argument to allow the secret to be passed in discretely.
Open source community
Once your CLI is complete and ready to be distributed, there are several guidelines to follow. If possible, distribute it within a single binary targeted to a user’s specific platform and architecture. If the user no longer wants or needs your program, make sure it’s easy to uninstall too!
Since you’ll be writing your CLI in Go, it would be great to encourage contributions to your program. You may offer a contribution guideline doc that guides users toward commit syntax, code quality, required tests, and other standards. You could also choose to allow users to extend the CLI by writing plugins that can work with your CLI, break up functionality across more modular components, and increase composability.
Software lifespan and robustness
To ensure your CLI will continue to work well in the future, there are a few guidelines to follow specific to robustness to make sure your program has a long lifespan:
- Future-proofing
When you make any changes to your CLI, over time, it’s best to make these changes additive, but if not, warn your users of the change. Changing human-readable output is usually fine, but it’s best to keep the machine-readable output stable. Consider how external dependencies may cut short your program’s lifespan and think of ways to make your application stable amidst external dependency failures.
- Robustness
For a CLI to achieve maximum robustness, the CLI must be designed with full transparency. The program needs to feel responsible for the user; so, show progress if something takes a long time and don’t let the program hang. Make programs timeout when they are taking a long time. When the user inputs a command, validate the usage immediately, giving clear feedback when there’s apparent misuse. When there’s a failure due to some transient reason, exit the program immediately upon failure or interruption. When the program is invoked again, it should pick up immediately where it left off.
- Empathy
Adding some thoughtful detail to the command-line design will create a pleasant experience for the user. CLIs should be fun to use! Even when things go wrong, with a supportive design, the users can feel encouraged on their pathway to successfully using the CLI. The modern CLI philosophy and guidelines reflect a level of empathy toward humans already and, thank goodness, we’ve come a long way from the initial command-line tools and will continue to do better.