12 Factor CLI Apps
From charlesreid1
A guide from Heroku containing 12 principles for creating great CLI apps: https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46
Contents
- 1 Rule 1: Great help is essential
- 2 Rule 2: Prefer flags to arguments
- 3 Rule 3: Make version number easy to find
- 4 Rule 4: Mind stdout and stderr
- 5 Rule 5: Handle things going wrong
- 6 Rule 6: Be fancy, know when to be fancy
- 7 Rule 7: Accept input from user
- 8 Rule 8: Use tables
- 9 Rule 9: Be speedy
- 10 Rule 10: Encourage contributions
- 11 Rule 11: Be clear about subcommands
- 12 Rule 12: Use XDG
- 13 Flags
Rule 1: Great help is essential
show help with all of these:
# list all commands $ mycli $ mycli --help $ mycli help $ mycli -h # get help for subcommand $ mycli subcommand --help $ mycli subcommand -h
Also consider shell completion!
Auto-documentation:
Rule 2: Prefer flags to arguments
Two ways for user to provide input: (positional) arguments, and flags.
Flags require a bit more typing, but make the CLI much clearer.
Example:
$ heroku fork --from FROMAPP --to TOAPP
Rule of thumb: 1 type of argument is fine, 2 types are very suspect, and 3 are never good.
If you pass off flags to another process, use --
argument to denote it should stop parsing arguments there and pass the rest of the arguments to the other process.
Rule 3: Make version number easy to find
Make all of these print the version:
$ mycli version # multi only $ mycli --version $ mycli -V
Rule 4: Mind stdout and stderr
Important to direct errors and messages to correct place
Example:
$ myapp > foo.txt Warning: something went wrong
Don't want warning to get buried, or to have a problem with structured output.
In short: stdout is for output, stderr is for messaging.
If you wrap a subcommand, always display the stderr from that command to the user.
Rule 5: Handle things going wrong
Things go wrong a lot, especially with CLIs
Error messages should contain:
- error code
- error title
- error description
- how to fix
- url for more info
Example:
$ myapp dump -o myfile.out Error: EPERM - Invalid permissions on myfile.out Cannot write to myfile.out, file does not have write permissions. Fix with: chmod +w myfile.out https://github.com/jdxcode/myapp
Rule 6: Be fancy, know when to be fancy
Colors and dimming highlight important info
Spinners and progress bars inform user that program is still working
But be able to fall back to basic behavior (and know when to)
If user's stdout or stderr are not connected to tty, probably piping to file, so no colors. Also no spinny things. (they use ansi codes, which you don't want to output to a file.)
Let the user turn off fancy stuff with:
TERM=dumb
NO_COLOR
being set--no-color
flag being usedMYAPP_NOCOLOR=1
environment variable for your CLI application alone
Rule 7: Accept input from user
If the stdin is a tty, then you can accept input from the user. This should not be required, though, to ensure the tool can be automated/scripted.
A good practice is to require user input for confirmation dialogs, if the action being taken is dangerous.
Check boxes and radio buttons can give user menus and make things a lot easier to use
Rule 8: Use tables
Not the fancy tables with borders. Something you can parse with cut and grep and other command line utilities.
Tables require thinking about screen width.
By default, only show a few columns. Give the user a --columns
flag to control that, where they pass a comma separated list of columns.
Cut off rows that will spill over, and provide a --no-truncate
option.
Show column headers but provide a --no-headers
option.
Implement a --filter
command line option to filter specific columns
Allow sorting by column with --sort
Rule 9: Be speedy
Use the time
utility to time your CLI
<100ms: very fast (sadly, not feasible for scripting languages) 100ms–500ms: fast enough, aim here 500ms-2s: usable, but not going to impress anyone 2s+: languid, users will prefer to avoid your CLI at this point
Rule 10: Encourage contributions
Open source, contributions file, license, code of conduct, etc.
Rule 11: Be clear about subcommands
CLIs can be single command (like cp or grep)
Multi commands are like git, which have a subcommand as the first arg and then trailing arguments after that.
One basic task => single command CLI
Most CLIs would benefit from subcommands
If user does not pass any arguments:
- list subcommands for multi-command CLIs
- display the help for single-CLIs
- don't take a default action
If you need subcommands below your subcommands, those are called "topics".
Git uses spaces:
$ git submodule add git@github.com:rainbow-mind-machine/rainbow-mind-machine
Heroku uses colons:
$ heroku domains:add www.myapp.com
Rule 12: Use XDG
XDG-spec: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
XDG is a spec for figuring out where to put files.
Environment variables like XDG_CONFIG_HOME allow you to specify a config or data file location
If not specified, use ~/.config/myapp
for config files, and ~/.local/share/myapp
for data files.
Use ~/.cache/myapp
for cache files on Unix. Use ~/Library/Caches/myapp
on Mac. Use %LOCALAPPDATA%\myapp
on Windows.