Command Line Interface Guidelines

An open-source guide to help you write better command-line programs, taking traditional UNIX principles and updating them for the modern day.

Authors

Aanand Prasad
Engineer at Squarespace, co-creator of Docker Compose.
@aanandprasad

Ben Firshman
Co-creator Replicate, co-creator of Docker Compose.
@bfirsh

Carl Tashian
Developer Advocate at Smallstep, first engineer at Zipcar, co-founder Trove.
tashian.com @tashian

Eva Parish
Technical Writer at Squarespace, O’Reilly contributor.
evaparish.com @evpari

Design by Mark Hurrell. Thanks to Andreas Jansson for early contributions, and Andrew Reitz, Ashley Williams, Brendan Falk, Chester Ramey, Dj Walker-Morgan, Jacob Maine, James Coglan, Michael Dwan, and Steve Klabnik for reviewing drafts.

Join us on Discord if you want to discuss the guide or CLI design.

서문

1980년대에 개인용 컴퓨터가 당신을 위해 무언가를 해주기를 바란다면 C:\>~$에 직면했을 때 무엇을 입력해야 하는지 알아야 했습니다. 도움말은 나선형으로 묶인 두꺼운 설명서의 형태로 제공되었습니다. 에러 메시지들은 이해하기 어려웠습니다. 거기에는 당신을 구해줄 스택오버플로우도 없었습니다. 그러나 만약에 인터넷에 접근할 수 있을 만큼 충분히 운이 좋았다면 당신은 당신처럼 답답했던 사람들로 차있는 인터넷 커뮤니티, 유즈넷으로부터 도움을 구할 수 있었을 것입니다. 그들은 문제를 해결하는 데 도움을 주거나 혹은 최소한, 교훈과 동료애를 제공할 수 있습니다.

40년 후, 컴퓨터들은 종종 로우-레벨 사용자 제어를 희생하며 모두에게 쉽게 더 접근할 수 있게 되었습니다. 많은 기기들에 커맨드라인 액세스가 전혀 없습니다. 부분적으로 클로즈드 플랫폼과 앱 스토어의 기업 이익에 어긋나기 때문입니다.

오늘날 대부분 사람들은 커맨드라인이 무엇인지, 왜 그들이 그것을 걱정하기를 원하는지도 모릅니다. 컴퓨팅의 선구자 Alan Kay가 2017년 인터뷰에서 말하길, 사람들은 컴퓨팅이 무엇에 대한 것인지 이해하지 못했기 때문에 아이폰에 컴퓨팅이 있다고 생각하고, 그 환성은 ‘기타 히어로’가 진짜 기타와 같다는 것만큼 나쁜 것입니다.”

Kay의 “진짜 기타”는 정확히 CLI는 아닙니다. 그는 CLI의 힘을 제공하고 소프트웨어를 텍스트 파일들로 작성하는 것을 초월하는 컴퓨터 프로그래밍하는 방법들에 대해서 이야기했습니다. Kay의 제자들 사이에는 우리가 수십 년간 살아온 텍스트 기반의 로컬 최대치를 벗어나야 하는 믿음이 있습니다.

우리가 컴퓨터를 매우 다르게 프로그래밍하는 미래를 상상하는 것은 굉장히 신 나는 일입니다. 심지어 오늘날에도, 스프레드시트는 가장 유명한 프로그래밍 언어이고, 노-코드 움직임은 재능있는 프로그래머들에 대한 심한 수요를 대체하려는 노력에 따라 빠르게 시작되고 있습니다.

아직 삐걱거리는 소리, 수십 년 전 제약들과 설명할 수 없는 우연들과 함께, 커맨드 라인은 여전히 컴퓨터 중 가장 다재다능한 부분입니다. 이를 통해 커튼 뒤로 젖히고, 실제 상황을 확인하고, GUI가 감당할 수 없는 수준의 정교함과 깊이로 기계와 창의적으로 의사소통할 수 있습니다. 그것은 거의 모든 노트북에서나, 그것을 배우고 싶어하는 모두가 가능합니다. 그것은 대화식으로 사용되거나 자동화될 수 있습니다. 그리고 시스템의 다른 부분만큼 빠르게 바뀌지 않습니다. 그것의 안정성에 창의적인 가치가 있습니다.

그래서 우리가 아직 그것을 가지고 있는 동안, 우리는 그것의 유용성과 접근성을 최대화하는 시도를 해야 합니다.

그 초창기 이후로 우리가 어떻게 컴퓨터를 프로그래밍하는 방법에 대해 많은 것이 바뀌었습니다. 과거의 커맨드 라인은 머신-퍼스트 였습니다. REPL 스크립트 플랫폼의 위에 있는 REPL에 불과합니다. 그러나 다용 목적의 인터프리터 언어들이 흥함에 따라, 쉘 스크립트의 역할은 줄어들었습니다. 오늘날의 커맨드라인은 휴먼-퍼스트 입니다. 모든 종류의 도구, 시스템, 플랫폼에 접근을 제공하는 텍스트 기반의 UI입니다. 과거에는 에디터가 터미널 안에 있었지만, 오늘날에 터미널은 그저 에디터의 한 기능일 뿐입니다. 그리고 많은 git 스러운 다양한 커맨드 도구들이 확산되었습니다. 커맨드 안의 커맨드, 그리고 원자적인 기능 대신 전체 워크플로우를 구성하는 하이레벨 커맨드.

보다 쾌적하고 접근성 있는 CLI 환경을 장려하는데 관심을 두고 프로그래머로서의 경험을 바탕으로 전통 UNIX 철학에 영감을 받아, 우리는 커맨드라인 프로그램을 만들기 위한 베스트 프랙티스와 설계 원칙을 다시 살펴봐야 할 때라고 결정했습니다.

커맨드 라인이 영원하길!

소개

이 문서는 높은 수준의 디자인 철학과 구체적인 가이드를 모두 포함합니다. 실무자로써 우리의 철학이 그렇게 까지 철학적이지 않기 때문에 가이드에 더 가깝습니다. 우리는 예제를 통해서 배우는 것을 믿고 때문에 우리는 많은 에제들을 제공했습니다.

이 가이드는 vim과 emacs같은 전체화면 터미널 프로그램을 포함하지 않습니다. 전체화면 프로그램은 틈새 프로젝트입니다. 우리 중 극소수만이 이를 설계할 수 있습니다.

이 가이드는 또한 프로그래밍 언어와 일반적인 도구들에 대해 구애받지 않습니다.

이 가이드는 누구를 위한 것인가요? - 만약 당신이 CLI 프로그램을 만들고 있고 당신이 UI 디자인을 위한 원칙과 구체적인 베스트 프랙티스를 찾고 있다면, 이 가이드는 당신을 위한 것입니다. - 만약 당신이 “전문적인 CLI UI 디자이너”라면 그건 정말 놀랍습니다. 우리가 당신에게 배우고 싶네요. - 만약 당신이 40년간의 CLI 디자인 규칙에 어긋나는 실수를 피하고 싶다면, 이 가이드는 당신을 위한 것입니다. - 만약 당신이 만드는 프로그램의 좋은 디자인과 유용한 도움말로 사람들을 기쁘게 해주고 싶다면, 이 가이드는 확실히 당신을 위한 것입니다. - 만약 당신이 GUI 프로그램을 만들고 있다면, 이 가이드는 당신을 위한 것이 아닙니다. 어쨋든 당신이 읽기로 결정했다면 GUI의 안티패턴들을 배울 수도 있습니다. - 만약 당신이 마인크래프트의 풀스크린 CLI 포팅 버전을 설계하고 있다면, 이 가이드는 당신을 위한 것이 아닙니다. (하지만 우리는 그것을 빨리 보고 싶습니다!)

철학

이것들은 우리가 좋은 CLI 디자인의 기본 원칙이라고 생각하는 것입니다.

사람 우선 디자인

전통적으로 UNIX 커맨드들은 주로 다른 프로그램들에 의해 사용된다는 가정하에 작성되었습니다. 그것들은 그래픽 어플리케이션들에 비해 프로그래밍 언어의 함수와 더 많은 공통점이 있습니다.

오늘날에 많은 CLI 프로그램들이 주로 (혹은 독점적으로) 사람들에 의해서 사용됨에도 불구하고, 많은 인터랙션 디자인들이 여전히 과거의 짐을 갖고 다닙니다. 이 짐들을 버릴 시점입니다. 만약에 커맨드에 주로 사람들에 의해 사용될 것이라면 그건 사람을 우선해서 디자인되어야 합니다.

함께 동작하는 작은 부품들

원래 유닉스 철학의 핵심은 큰 시스템을 만들기 위해 깔끔한 인터페이스의 작고 간단한 프로그램들이 조합할 수 있다는 아이디어입니다. 이러한 프로그램에 점점 더 많은 기능을 넣는 대신 필요에 따라 다시 결합할 수 있을 만큼 충분히 모듈화된 프로그램을 만듭니다.

옛날에는 파이프와 쉘 스크립트는 중대한 프로그램들을 같이 엮는 과정에 중대한 역할을 했었습니다. 그것들의 역할은 다용도 인터프리터 언어들의 흥행으로 감소해 갔지만 완전히 사라지지는 않았습니다. 또한 CI/CD, 오케스트레이션 그리고 설정관리 등의 형태로 큰 크기의 자동화들이 번성해왔습니다. 프로그램을 조합 가능하게 만드는 것은 그 어느 때보다 중요합니다.

운이 좋게도, 오랫동안 확립된 유닉스 환경의 컨벤션은 이 목적을 위해 디자인 되었고 오늘날에도 우리를 도와주고 있습니다. 표준 입력, 표준 출력, 표준 에러, 시그널, 종료 코드 및 다른 메커니즘들은 다른 프로그램들이 함께 잘 클릭되도록 합니다. 평문, 줄 기반의 텍스트는 두 커맨드간에 파이프로 넘기기에 좋습니다. JSON은 더 최근의 생겨났고, 필요할 때 더 많은 구조를 제공하고, 명령어 도구와 웹을 쉽게 통합할 수 있게 해줍니다.

어떤 소프트웨어를 만들던, 사람들이 예상하지 않은 방법으로 사용할 것임을 절대적으로 확신할 수 있습니다. 여려분의 소프트웨어가 큰 프로그램의 일부가 될 것이고, 당신의 유일한 선택은 그것이 제대로 작동하는 부분이 될 것인지에 달려 있습니다.

가장 중요한 것은 조합성을 디자인 하는 것은 사람 우선 디자인에 상충할 필요가 없다는 것입니다. 이 문서의 많은 충고들은 그 둘을 어떻게 달성할 것인지에 대한 것들입니다.

프로그램 간의 일관성

터미널의 컨벤션은 우리들의 손가락에 내장되어 있습니다. 우리는 커맨드 라인 문법, 플래그, 환경 변수들에 대해 배우는 초기 비용을 지불해야 하지만 프로그램들이 일관성있는 한 장기적으로 효율적입니다

가능하다면 CLI는 이미 있는 것들의 패턴을 따라야 합니다. 이것이 CLI를 직관적이고 추측 가능하게 만드는 것입니다. 그것이 사용자를 효율적으로 만드는 것입니다.

그것은 때때로 일관성이 사용성과 충돌할 때도 있습니다. 예를 들어, 많은 오랫동안 자리 잡은 UNIX 커맨드들은 기본적으로 많은 정보를 출력하지 않고 커맨드 라인에 덜 익숙한 사람들에게 혼란 혹은 걱정을 유발할 수 있습니다.

규칙을 따르는 것이 프로그램의 사용성을 손상시킬 때 그것을 중단할 때가 되었을 수도 있지만 그러한 결정은 신중하게 내려야 합니다.

(그냥) 충분히 말하기

터미널은 순수 정보들의 세게입니다.

정보가 인터페이스이며, 다른 인터페이스와 마찬가지로 정보가 너무 많거나 너무 적다고 주장할 수 있습니다.

어떤 커맨드는 몇 분이나 걸리고 유저들이 그게 고장난 것이 궁금해하기 시작할 때 너무 작게 말합니다. 어떤 커맨드는 디버깅 출력의 페이지와 페이지를 덤프할 때 너무 많은 말을 해서 느슨한 쓰레기의 바다에서 진정으로 중요한 것을 익사시킵니다. 결론은 다음과 같다: 명확성의 부족은 유저에게 혼란과 짜증을 남긴다.

이 균형을 잘 가지는 것은 어려울 수 있지만 사용자들에게 소프트웨어를 제공하고 권한을 주기 위해서는 절대적으로 중요하다.

검색 용이성

When it comes to making functionality discoverable, GUIs have the upper hand. Everything you can do is laid out in front of you on the screen, so you can find what you need without having to learn anything, and perhaps even discover things you didn’t know were possible.

It is assumed that command-line interfaces are the opposite of this—that you have to remember how to do everything. The original Macintosh Human Interface Guidelines, published in 1987, recommend “See-and-point (instead of remember-and-type),” as if you could only choose one or the other.

These things needn’t be mutually exclusive. The efficiency of using the command-line comes from remembering commands, but there’s no reason the commands can’t help you learn and remember.

Discoverable CLIs have comprehensive help texts, provide lots of examples, suggest what command to run next, suggest what to do when there is an error. There are lots of ideas that can be stolen from GUIs to make CLIs easier to learn and use, even for power users.

Citation: The Design of Everyday Things (Don Norman), Macintosh Human Interface Guidelines

Conversation as the norm

GUI design, particularly in its early days, made heavy use of metaphor: desktops, files, folders, recycle bins. It made a lot of sense, because computers were still trying to bootstrap themselves into legitimacy. The ease of implementation of metaphors was one of the huge advantages GUIs wielded over CLIs. Ironically, though, the CLI has embodied an accidental metaphor all along: it’s a conversation.

Beyond the most utterly simple commands, running a program usually involves more than one invocation. Usually, this is because it’s hard to get it right the first time: the user types a command, gets an error, changes the command, gets a different error, and so on, until it works. This mode of learning through repeated failure is like a conversation the user is having with the program.

Trial-and-error isn’t the only type of conversational interaction, though. There are others:

Acknowledging the conversational nature of command-line interaction means you can bring relevant techniques to bear on its design. You can suggest possible corrections when user input is invalid, you can make the intermediate state clear when the user is going through a multi-step process, you can confirm for them that everything looks good before they do something scary.

The user is conversing with your software, whether you intended it or not. At worst, it’s a hostile conversation which makes them feel stupid and resentful. At best, it’s a pleasant exchange that speeds them on their way with newfound knowledge and a feeling of achievement.

Further reading: The Anti-Mac User Interface (Don Gentner and Jakob Nielsen)

Robustness

Robustness is both an objective and a subjective property. Software should be robust, of course: unexpected input should be handled gracefully, operations should be idempotent where possible, and so on. But it should also feel robust.

You want your software to feel like it isn’t going to fall apart. You want it to feel immediate and responsive, as if it were a big mechanical machine, not a flimsy plastic “soft switch.”

Subjective robustness requires attention to detail and thinking hard about what can go wrong. It’s lots of little things: keeping the user informed about what’s happening, explaining what common errors mean, not printing scary-looking stack traces.

As a general rule, robustness can also come from keeping it simple. Lots of special cases and complex code tend to make a program fragile.

Empathy

Command-line tools are a programmer’s creative toolkit, so they should be enjoyable to use. This doesn’t mean turning them into a video game, or using lots of emoji (though there’s nothing inherently wrong with emoji 😉). It means giving the user the feeling that you are on their side, that you want them to succeed, that you have thought carefully about their problems and how to solve them.

There’s no list of actions you can take that will ensure they feel this way, although we hope that following our advice will take you some of the way there. Delighting the user means exceeding their expectations at every turn, and that starts with empathy.

Chaos

The world of the terminal is a mess. Inconsistencies are everywhere, slowing us down and making us second-guess ourselves.

Yet it’s undeniable that this chaos has been a source of power. The terminal, like the UNIX-descended computing environment in general, places very few constraints on what you can build. In that space, all manner of invention has bloomed.

It’s ironic that this document implores you to follow existing patterns, right alongside advice that contradicts decades of command-line tradition. We’re just as guilty of breaking the rules as anyone.

The time might come when you, too, have to break the rules. Do so with intention and clarity of purpose.

“Abandon a standard when it is demonstrably harmful to productivity or user satisfaction.” — Jef Raskin, The Humane Interface

Guidelines

This is a collection of specific things you can do to make your command-line program better.

The first section contains the essential things you need to follow. Get these wrong, and your program will be either hard to use or a bad CLI citizen.

The rest are nice-to-haves. If you have the time and energy to add these things, your program will be a lot better than the average program.

The idea is that, if you don’t want to think too hard about the design of your program, you don’t have to: just follow these rules and your program will probably be good. On the other hand, if you’ve thought about it and determined that a rule is wrong for your program, that’s fine. (There’s no central authority that will reject your program for not following arbitrary rules.)

Also—these rules aren’t written in stone. If you disagree with a general rule for good reason, we hope you’ll propose a change.

The Basics

There are a few basic rules you need to follow. Get these wrong, and your program will be either very hard to use, or flat-out broken.

Use a command-line argument parsing library where you can. Either your language’s built-in one, or a good third-party one. They will normally handle arguments, flag parsing, help text, and even spelling suggestions in a sensible way.

Here are some that we like: * Multi-platform: docopt * Bash: argbash * Go: Cobra, cli * Haskell: optparse-applicative * Java: picocli * Node: oclif * PHP: console, CLImate * Python: Argparse, Click, Typer * Ruby: TTY * Rust: clap, structopt * Swift: swift-argument-parser

Return zero exit code on success, non-zero on failure. Exit codes are how scripts determine whether a program succeeded or failed, so you should report this correctly. Map the non-zero exit codes to the most important failure modes.

Send output to stdout. The primary output for your command should go to stdout. Anything that is machine readable should also go to stdout—this is where piping sends things by default.

Send messaging to stderr. Log messages, errors, and so on should all be sent to stderr. This means that when commands are piped together, these messages are displayed to the user and not fed into the next command.

Help

Display help text when passed no options, the -h flag, or the --help flag.

Display a concise help text by default. If you can, display help by default when myapp or myapp subcommand is run. Unless your program is very simple and does something obvious by default (e.g. ls), or your program reads input interactively (e.g. cat).

The concise help text should only include:

jq does this well. When you type jq, it displays an introductory description and an example, then prompts you to pass jq --help for the full listing of flags:

$ 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.

Show full help when -h and --help is passed. All of these should show help:

$ myapp
$ myapp --help
$ myapp -h

Ignore any other flags and arguments that are passed—you should be able to add -h to the end of anything and it should show help. Don’t overload -h.

If your program is git-like, the following should also offer help:

$ myapp help
$ myapp help subcommand
$ myapp subcommand --help
$ myapp subcommand -h

Provide a support path for feedback and issues. A website or GitHub link in the top-level help text is common.

In help text, link to the web version of the documentation. If you have a specific page or anchor for a subcommand, link directly to that. This is particularly useful if there is more detailed documentation on the web, or further reading that might explain the behavior of something.

Lead with examples. Users tend to use examples over other forms of documentation, so show them first in the help page, particularly the common complex uses. If it helps explain what it’s doing and it isn’t too long, show the actual output too.

You can tell a story with a series of examples, building your way toward complex uses.

If you’ve got loads of examples, put them somewhere else, in a cheat sheet command or a web page. It’s useful to have exhaustive, advanced examples, but you don’t want to make your help text really long.

For more complex use cases, e.g. when integrating with another tool, it might be appropriate to write a fully-fledged tutorial.

Display the most common flags and commands at the start of the help text. It’s fine to have lots of flags, but if you’ve got some really common ones, display them first. For example, the Git command displays the commands for getting started and the most commonly used subcommands first:

$ git
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>]
           <command> [<args>]

These are common Git commands used in various situations:

start a working area (see also: git help tutorial)
   clone      Clone a repository into a new directory
   init       Create an empty Git repository or reinitialize an existing one

work on the current change (see also: git help everyday)
   add        Add file contents to the index
   mv         Move or rename a file, a directory, or a symlink
   reset      Reset current HEAD to the specified state
   rm         Remove files from the working tree and from the index

examine the history and state (see also: git help revisions)
   bisect     Use binary search to find the commit that introduced a bug
   grep       Print lines matching a pattern
   log        Show commit logs
   show       Show various types of objects
   status     Show the working tree status
…

Use formatting in your help text. Bold headings make it much easier to scan. But, try to do it in a terminal-independent way so that your users aren’t staring down a wall of escape characters.


$ 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   [email protected]

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

Note: When heroku apps --help is piped through a pager, the command emits no escape characters.

If the user did something wrong and you can guess what they meant, suggest it. For example, brew update jq tells you that you should run brew upgrade jq.

You can ask if they want to run the suggested command, but don’t force it on them. For example:

$ heroku pss
 ›   Warning: pss is not a heroku command.
Did you mean ps? [y/n]:

Rather than suggesting the corrected syntax, you might be tempted to just run it for them, as if they’d typed it right in the first place. Sometimes this is the right thing to do, but not always.

Firstly, invalid input doesn’t necessarily imply a simple typo—it can often mean the user has made a logical mistake, or misused a shell variable. Assuming what they meant can be dangerous, especially if the resulting action modifies state.

Secondly, be aware that if you change what the user typed, they won’t learn the correct syntax. In effect, you’re ruling that the way they typed it is valid and correct, and you’re committing to supporting that indefinitely. Be intentional in making that decision, and document both syntaxes.

Further reading: “Do What I Mean”

If your command is expecting to have something piped to it and stdin is an interactive terminal, display help immediately and quit. This means it doesn’t just hang, like cat. Alternatively, you could print a log message to stderr.

Documentation

The purpose of help text is to give a brief, immediate sense of what your tool is, what options are available, and how to perform the most common tasks. Documentation, on the other hand, is where you go into full detail. It’s where people go to understand what your tool is for, what it isn’t for, how it works and how to do everything they might need to do.

Provide web-based documentation. People need to be able to search online for your tool’s documentation, and to link other people to specific parts. The web is the most inclusive documentation format available.

Provide terminal-based documentation. Documentation in the terminal has several nice properties: it’s fast to access, it stays in sync with the specific installed version of the tool, and it works without an internet connection.

Consider providing man pages. man pages, Unix’s original system of documentation, are still in use today, and many users will reflexively check man mycmd as a first step when trying to learn about your tool. To make them easier to generate, you can use a tool like ronn (which can also generate your web docs).

However, not everyone knows about man, and it doesn’t run on all platforms, so you should also make sure your terminal docs are accessible via your tool itself. For example, git and npm make their man pages accessible via the help subcommand, so npm help ls is equivalent to man npm-ls.

NPM-LS(1)                                                            NPM-LS(1)

NAME
       npm-ls - List installed packages

SYNOPSIS
         npm ls [[<@scope>/]<pkg> ...]

         aliases: list, la, ll

DESCRIPTION
       This command will print to stdout all the versions of packages that are
       installed, as well as their dependencies, in a tree-structure.

       ...

Output

Human-readable output is paramount. Humans come first, machines second. The most simple and straightforward heuristic for whether a particular output stream (stdout or stderr) is being read by a human is whether or not it’s a TTY. Whatever language you’re using, it will have a utility or library for doing this (e.g. Python, Node, Go).

Further reading on what a TTY is.

Have machine-readable output where it does not impact usability. Streams of text is the universal interface in UNIX. Programs typically output lines of text, and programs typically expect lines of text as input, therefore you can compose multiple programs together. This is normally done to make it possible to write scripts, but it can also help the usability for humans using programs. For example, a user should be able to pipe output to grep and it should do what they expect.

“Expect the output of every program to become the input to another, as yet unknown, program.” — Doug McIlroy

If human-readable output breaks machine-readable output, use --plain to display output in plain, tabular text format for integration with tools like grep or awk. In some cases, you might need to output information in a different way to make it human-readable. For example, if you are displaying a line-based table, you might choose to split a cell into multiple lines, fitting in more information while keeping it within the width of the screen. This breaks the expected behavior of there being one piece of data per line, so you should provide a --plain flag for scripts, which disables all such manipulation and outputs one record per line.

Display output as formatted JSON if --json is passed. JSON allows for more structure than plain text, so it makes it much easier to output and handle complex data structures. jq is a common tool for working with JSON on the command-line, and there is now a whole ecosystem of tools that output and manipulate JSON.

It is also widely used on the web, so by using JSON as the input and output of programs, you can pipe directly to and from web services using curl.

Display output on success, but keep it brief. Traditionally, when nothing is wrong, UNIX commands display no output to the user. This makes sense when they’re being used in scripts, but can make commands appear to be hanging or broken when used by humans. For example, cp will not print anything, even if it takes a long time.

It’s rare that printing nothing at all is the best default behavior, but it’s usually best to err on the side of less.

For instances where you do want no output (for example, when used in shell scripts), to avoid clumsy redirection of stderr to /dev/null, you can provide a -q option to suppress all non-essential output.

If you change state, tell the user. When a command changes the state of a system, it’s especially valuable to explain what has just happened, so the user can model the state of the system in their head—particularly if the result doesn’t directly map to what the user requested.

For example, git push tells you exactly what it is doing, and what the new state of the remote branch is:

$ git push
Enumerating objects: 18, done.
Counting objects: 100% (18/18), done.
Delta compression using up to 8 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (10/10), 2.09 KiB | 2.09 MiB/s, done.
Total 10 (delta 8), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (8/8), completed with 8 local objects.
To github.com:replicate/replicate.git
 + 6c22c90...a2a5217 bfirsh/fix-delete -> bfirsh/fix-delete

Make it easy to see the current state of the system. If your program does a lot of complex state changes and it is not immediately visible in the filesystem, make sure you make this easy to view.

For example, git status tells you as much information as possible about the current state of your Git repository, and some hints at how to modify the state:

$ git status
On branch bfirsh/fix-delete
Your branch is up to date with 'origin/bfirsh/fix-delete'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   cli/pkg/cli/rm.go

no changes added to commit (use "git add" and/or "git commit -a")

Suggest commands the user should run. When several commands form a workflow, suggesting to the user commands they can run next helps them learn how to use your program and discover new functionality. For example, in the git status output above, it suggests commands you can run to modify the state you are viewing.

Actions crossing the boundary of the program’s internal world should usually be explicit. This includes things like:

Increase information density—with ASCII art! For example, ls shows permissions in a scannable way. When you first see it, you can ignore most of the information. Then, as you learn how it works, you pick out more patterns over time.

-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

Use color with intention. For example, you might want to highlight some text so the user notices it, or use red to indicate an error. Don’t overuse it—if everything is a different color, then the color means nothing and only makes it harder to read.

Disable color if your program is not in a terminal or the user requested it. These things should disable colors:

Further reading: no-color.org, 12 Factor CLI Apps

If stdout is not an interactive terminal, don’t display any animations. This will stop progress bars turning into Christmas trees in CI log output.

Use symbols and emoji where it makes things clearer. Pictures can be better than words if you need to make several things distinct, catch the user’s attention, or just add a bit of character. Be careful, though—it can be easy to overdo it and make your program look cluttered or feel like a toy.

For example, yubikey-agent uses emoji to add structure to the output so it isn’t just a wall of text, and a ❌ to draw your attention to an important piece of information:

$ yubikey-agent -setup
🔐 The PIN is up to 8 numbers, letters, or symbols. Not just numbers!
❌ The key will be lost if the PIN and PUK are locked after 3 incorrect tries.

Choose a new PIN/PUK: 
Repeat the PIN/PUK: 

🧪 Retriculating splines …

✅ Done! This YubiKey is secured and ready to go.
🤏 When the YubiKey blinks, touch it to authorize the login.

🔑 Here's your new shiny SSH public key:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCEJ/
UwlHnUFXgENO3ifPZd8zoSKMxESxxot4tMgvfXjmRp5G3BGrAnonncE7Aj11pn3SSYgEcrrn2sMyLGpVS0=

💭 Remember: everything breaks, have a backup plan for when this YubiKey does.

By default, don’t output information that’s only understandable by the creators of the software. If a piece of output serves only to help you (the developer) understand what your software is doing, it almost certainly shouldn’t be displayed to normal users by default—only in verbose mode.

Invite usability feedback from outsiders and people who are new to your project. They’ll help you see important issues that you are too close to the code to notice.

Don’t treat stderr like a log file, at least not by default. Don’t print log level labels (ERR, WARN, etc.) or extraneous contextual information, unless in verbose mode.

Use a pager (e.g. less) if you are outputting a lot of text. For example, git diff does this by default. Using a pager can be error-prone, so be careful with your implementation such that you don’t make the experience worse for the user. You shouldn’t use a pager if stdin or stdout is not an interactive terminal.

A good sensible set of options to use for less is less -FIRX. This does not page if the content fills one screen, ignores case when you search, enables color and formatting, and leaves the contents on the screen when less quits.

There might be libraries in your language that are more robust than piping to less. For example, pypager in Python.

Errors

One of the most common reasons to consult documentation is to fix errors. If you can make errors into documentation, then this will save the user loads of time.

Catch errors and rewrite them for humans. If you’re expecting an error to happen, catch it and rewrite the error message to be useful. Think of it like a conversation, where the user has done something wrong and the program is guiding them in the right direction. Example: “Can’t write to file.txt. You might need to make it writable by running ‘chmod +w file.txt’.”

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 they did 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 output. 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. Consider writing the debug log to a file instead of printing it to the terminal.

Make it effortless to submit bug reports. One nice thing you can do is provide a URL and have it pre-populate as much information as possible.

Arguments and flags

A note on terminology:

Prefer flags to args. It’s a bit more typing, but it makes it much clearer what is going on. It also makes it easier to make changes to how you accept input in the future. Sometimes when using args, it’s impossible to add new input without breaking existing behavior or creating ambiguity.

Citation: 12 Factor CLI Apps.

Have full-length versions of all flags. For example, have both -h and --help. Having the full version is useful in scripts where you want to be verbose and descriptive, and you don’t have to look up the meaning of flags everywhere.

_Citation: GNU Coding Standards._

Only use one-letter flags for commonly used flags, particularly at the top-level when using subcommands. That way you don’t “pollute” your namespace of short flags, forcing you to use convoluted letters and cases for flags you add in the future.

Multiple arguments are fine for simple actions against multiple files. For example, rm file1.txt file2.txt file3.txt. This also makes it work with globbing: rm *.txt.

If you’ve got two or more arguments for different things, you’re probably doing something wrong. The exception is a common, primary action, where the brevity is worth memorizing. For example, cp <source> <destination>.

Citation: 12 Factor CLI Apps.

Use standard names for flags, if there is a standard. If another commonly used command uses a flag name, it’s best to follow that existing pattern. That way, a user doesn’t have to remember two different options (and which command it applies to), and users can even guess an option without having to look at the help text.

Here’s a list of commonly used options:

Make the default the right thing for most users. Making things configurable is good, but most users are not going to find the right flag and remember to use it all the time (or alias it). If it’s not the default, you’re making the experience worse for most of your users.

For example, ls has terse default output to optimize for scripts and other historical reasons, but if it were designed today, it would probably default to ls -lhFGT.

Prompt for user input. If a user doesn’t pass an argument or flag, prompt for it. (See also: Interactivity)

Never require a prompt. Always provide a way of passing input with flags or arguments. If stdin is not an interactive terminal, skip prompting and just require those flags/args.

Confirm before doing anything dangerous. A common convention is to prompt for the user to type y or yes if running interactively, or requiring them to pass -f or --force otherwise.

“Dangerous” is a subjective term, and there are differing levels of danger:

Consider whether there are non-obvious ways to accidentally destroy things. For example, imagine a situation where changing a number in a configuration file from 10 to 1 means that 9 things will be implicitly deleted—this should be considered a severe risk, and should be difficult to do by accident.

If input or output is a file, support - to read from stdin or write to stdout. This lets the output of another command be the input of your command and vice versa, without using a temporary file. For example, tar can extract files from stdin:

$ curl https://example.com/something.tar.gz | tar xvf -

If a flag can accept an optional value, allow a special word like “none.” For example, ssh -F takes an optional filename of an alternative ssh_config file, and ssh -F none runs SSH with no config file. Don’t just use a blank value—this can make it ambiguous whether arguments are flag values or arguments.

If possible, make arguments, flags and subcommands order-independent. A lot of CLIs, especially those with subcommands, have unspoken rules on where you can put various arguments. For example a command might have a --foo flag that only works if you put it before the subcommand:

mycmd --foo=1 subcmd
works

$ mycmd subcmd --foo=1
unknown flag: --foo

This can be very confusing for the user—especially given that one of the most common things users do when trying to get a command to work is to hit the up arrow to get the last invocation, stick another option on the end, and run it again. If possible, try to make both forms equivalent, although you might run up against the limitations of your argument parser.

Do not read secrets directly from flags. When a command accepts a secret, eg. via a --password argument, the argument value will leak the secret into ps output and potentially shell history. And, this sort of flag encourages the use of insecure environment variables for secrets.

Consider accepting sensitive data only via files, e.g. with a --password-file argument, or via STDIN. A --password-file argument allows a secret to be passed in discreetly, in a wide variety of contexts.

(It’s possible to pass a file’s contents into an argument in Bash by using --password $(< password.txt). This approach has the same security issue of leaking the file’s contents into the output of ps. It’s best avoided.)

Interactivity

Only use prompts or interactive elements if stdin is an interactive terminal (a TTY). 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, in which case a prompt won’t work and you should throw an error telling the user what flag to pass.

If --no-input is passed, don’t prompt or do anything interactive. This allows users an explicit way to disable all prompts in commands. If the command requires input, fail and tell the user how to pass the information as a flag.

If you’re prompting for a password, don’t print it as the user types. This is done by turning off echo in the terminal. Your language should have helpers for this.

Let the user escape. Make it clear how to get out. (Don’t do what vim does.) If your program hangs on network I/O etc, always make Ctrl-C still work. If it’s a wrapper around program execution where Ctrl-C can’t quit (SSH, tmux, telnet, etc), make it clear how to do that. For example, SSH allows escape sequences with the ~ escape character.

Subcommands

If you’ve got a tool that’s sufficiently complex, you can reduce its complexity by making a set of subcommands. If you have several tools that are very closely related, you can make them easier to use and discover by combining them into a single command (for example, RCS vs. Git).

They’re useful for sharing stuff—global flags, help text, configuration, storage mechanisms.

Be consistent across subcommands. Use the same flag names for the same things, have similar output formatting, etc.

Use consistent names for multiple levels of subcommand. If a complex piece of software has lots of objects and operations that can be performed on those objects, it is a common pattern to use two levels of subcommand for this, where one is a noun and one is a verb. For example, docker container create. Be consistent with the verbs you use across different types of objects.

Either noun verb or verb noun ordering works, but noun verb seems to be more common.

Further reading: User experience, CLIs, and breaking the world, by John Starich.

Don’t have ambiguous or similarly-named commands. For example, having two subcommands called “update” and “upgrade” is quite confusing. You might want to use different words, or disambiguate with extra words.

Robustness

Validate user input. Everywhere your program accepts data from the user, it will eventually be given bad data. Check early and bail out before anything bad happens, and make the errors understandable.

Responsive is more important than fast. Print something to the user in <100ms. If you’re making a network request, print something before you do it so it doesn’t hang and look broken.

Show progress if something takes a long time. If your program displays no output for a while, it will look broken. A good spinner or progress indicator can make a program appear to be faster than it is.

Ubuntu 20.04 has a nice progress bar that sticks to the bottom of the terminal.

If the progress bar gets stuck in one place for a long time, the user won’t know if stuff is still happening or if the program’s crashed. It’s good to show estimated time remaining, or even just have an animated component, to reassure them that you’re still working on it.

There are many good libraries for generating progress bars. For example, tqdm for Python, schollz/progressbar for Go, and node-progress for Node.js.

Do stuff in parallel where you can, but be thoughtful about it. It’s already difficult to report progress in the shell; doing it for parallel processes is ten times harder. Make sure it’s robust, and that the output isn’t confusingly interleaved. If you can use a library, do so—this is code you don’t want to write yourself. Libraries like tqdm for Python and schollz/progressbar for Go support multiple progress bars natively.

The upside is that it can be a huge usability gain. For example, docker pull’s multiple progress bars offer crucial insight into what’s going on.

$ docker image pull ruby
Using default tag: latest
latest: Pulling from library/ruby
6c33745f49b4: Pull complete 
ef072fc32a84: Extracting [================================================>  ]  7.569MB/7.812MB
c0afb8e68e0b: Download complete 
d599c07d28e6: Download complete 
f2ecc74db11a: Downloading [=======================>                           ]  89.11MB/192.3MB
3568445c8bf2: Download complete 
b0efebc74f25: Downloading [===========================================>       ]  19.88MB/22.88MB
9cb1ba6838a0: Download complete 

One thing to be aware of: hiding logs behind progress bars when things go well makes it much easier for the user to understand what’s going on, but if there is an error, make sure you print out the logs. Otherwise, it will be very hard to debug.

Make things time out. Allow network timeouts to be configured, and have a reasonable default so it doesn’t hang forever.

Make it idempotent. If the program fails for some transient reason (e.g. the internet connection went down), you should be able to hit <up> and <enter> and it should pick up from where it left off.

Make it crash-only. This is the next step up from idempotence. If you can avoid needing to do any cleanup after operations, or you can defer that cleanup to the next run, your program can exit immediately on failure or interruption. This makes it both more robust and more responsive.

Citation: Crash-only software: More than meets the eye.

People are going to misuse your program. Be prepared for that. They will wrap it in scripts, use it on bad internet connections, run many instances of it at once, and use it in environments you haven’t tested in, with quirks you didn’t anticipate. (Did you know macOS filesystems are case-insensitive but also case-preserving?)

Future-proofing

In software of any kind, it’s crucial that interfaces don’t change without a lengthy and well-documented deprecation process. Subcommands, arguments, flags, configuration files, environment variables: these are all interfaces, and you’re committing to keeping them working. (Semantic versioning can only excuse so much change; if you’re putting out a major version bump every month, it’s meaningless.)

Keep changes additive where you can. Rather than modify the behavior of a flag in a backwards-incompatible way, maybe you can add a new flag—as long as it doesn’t bloat the interface too much. (See also: Prefer flags to args.)

Warn before you make a non-additive change. Eventually, you’ll find that you can’t avoid breaking an interface. Before you do, forewarn your users in the program itself: when they pass the flag you’re looking to deprecate, tell them it’s going to change soon. Make sure there’s a way they can modify their usage today to make it future-proof, and tell them how to do it.

If possible, you should detect when they’ve changed their usage and not show the warning any more: now they won’t notice a thing when you finally roll out the change.

Changing output for humans is usually OK. The only way to make an interface easy to use is to iterate on it, and if the output is considered an interface, then you can’t iterate on it. Encourage your users to use --plain or --json in scripts to keep output stable (see Output).

Don’t have a catch-all subcommand. If you have a subcommand that’s likely to be the most-used one, 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 isn’t the name of an existing subcommand, you assume the user means run, so they can just type this:

$ mycmd echo "hello world"

This has a serious drawback, though: 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.

Don’t allow arbitrary abbreviations of subcommands. For example, say your command 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, and have it be an alias for mycmd install. Now you’re stuck: you can’t add any more commands beginning with i, because there are scripts out there that assume i means install.

There’s nothing wrong with aliases—saving on typing is good—but they should be explicit and remain stable.

Don’t create a “time bomb.” Imagine it’s 20 years from now. Will your command still run the same as it does today, or will it stop working because some external dependency on the internet has changed or is no longer maintained? The server most likely to not exist in 20 years is the one that you are maintaining right now. (But don’t build in a blocking call to Google Analytics either.)

Signals and control characters

If a user hits Ctrl-C (the INT signal), exit as soon as possible. Say something immediately, before you start clean-up. Add a timeout to any clean-up code so it can’t hang forever.

If a user hits Ctrl-C during clean-up operations that might take a long time, skip them. Tell the user what will happen when they hit Ctrl-C again, in case it is a destructive action.

For example, when quitting Docker Compose, you can hit Ctrl-C a second time to force your containers to stop immediately instead of shutting them down gracefully.

$  docker-compose up
…
^CGracefully stopping... (press Ctrl+C again to force)

Your program should expect to be started in a situation where clean-up has not been run. (See Crash-only software: More than meets the eye.)

Configuration

Command-line tools have lots of different types of configuration, and lots of different ways to supply it (flags, environment variables, project-level config files). The best way to supply each piece of configuration depends on a few factors, chief among them specificity, stability and complexity.

Configuration generally falls into a few categories:

  1. Likely to vary from one invocation of the command to the next.

    Examples:

    • Setting the level of debugging output
    • Enabling a safe mode or dry run of a program

    Recommendation: Use flags. Environment variables may or may not be useful as well.

  2. Generally stable from one invocation to the next, but not always. Might vary between projects. Definitely varies between different users working on the same project.

    This type of configuration is often specific to an individual computer.

    Examples:

    • Providing a non-default path to items needed for a program to start
    • Specifying how or whether color should appear in output
    • Specifying an HTTP proxy server to route all requests through

    Recommendation: Use flags and probably environment variables too. Users may want to set the variables in their shell profile so they apply globally, or in .env for a particular project.

    If this configuration is sufficiently complex, it may warrant a configuration file of its own, but environment variables are usually good enough.

  3. Stable within a project, for all users.

    This is the type of configuration that belongs in version control. Files like Makefile, package.json and docker-compose.yml are all examples of this.

    Recommendation: Use a command-specific, version-controlled file.

Follow the XDG-spec. In 2010 the X Desktop Group, now freedesktop.org, developed a specification for the location of base directories where config files may be located. One goal was to limit the proliferation of dotfiles in a user’s home directory by supporting a general-purpose ~/.config folder. The XDG Base Directory Specification (full spec, summary) is supported by yarn, fish, wireshark, emacs, neovim, tmux, and many other projects you know and love.

If you automatically modify configuration that is not your program’s, ask the user for consent and tell them exactly what you’re doing. Prefer creating a new config file (e.g. /etc/cron.d/myapp) rather than appending to an existing config file (e.g. /etc/crontab). If you have to append or modify to a system-wide config file, use a dated comment in that file to delineate your additions.

Apply configuration parameters in order of precedence. Here is the precedence for config parameters, from highest to lowest:

Environment variables

Environment variables are for behavior that varies with the context in which a command is run. The “environment” of an environment variable is the terminal session—the context in which the command is running. So, an env var might change each time a command runs, or between terminal sessions on one machine, or between instantiations of one project across several machines.

Environment variables may duplicate the functionality of flags or configuration parameters, or they may be distinct from those things. See Configuration for a breakdown of common types of configuration and recommendations on when environment variables are most appropriate.

For maximum portability, environment variable names must only contain uppercase letters, numbers, and underscores (and mustn’t start with a number). Which means O_O and OWO are the only emoticons that are also valid environment variable names.

Aim for single-line environment variable values. While multi-line values are possible, they create usability issues with the env command.

Avoid commandeering widely used names. Here’s a list of POSIX standard env vars.

Check general-purpose environment variables for configuration values when possible:

Read environment variables from .env where appropriate. If a command defines environment variables that are unlikely to change as long as the user is working in a particular directory, then it should also read them from a local .env file so users can configure it differently for different projects without having to specify them every time. Many languages have libraries for reading .env files (Rust, Node, Ruby).

Don’t use .env as a substitute for a proper configuration file. .env files have a lot of limitations:

If it seems like these limitations will hamper usability or security, then a dedicated config file might be more appropriate.

Do not read secrets from environment variables. While environment variables may be convenient for storing secrets, they have proven too prone to leakage: - Exported environment variables are sent to every process, and from there can easily leak into logs or be exfiltrated - Shell substitions like curl -H "Authorization: Bearer $BEARER_TOKEN" will leak into globally-readable process state. (cURL offers the -H @filename alternative for reading sensitive headers from a file.) - 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

Secrets should only be accepted via credential files, pipes, AF_UNIX sockets, secret management services, or another IPC mechanism.

Naming

The name of your program is particularly important on the CLI: your users will be typing it all the time, and it needs to be easy to remember and type.

Make it a simple, memorable word. But not too generic, or you’ll step on the toes of other commands and confuse users. For example, both ImageMagick and Windows used the command convert.

Use only lowercase letters, and dashes if you really need to. curl is a good name, DownloadURL is not.

Keep it short. Users will be typing it all the time. Don’t make it too short: the very shortest commands are best reserved for the common utilities used all the time, such as cd, ls, ps.

Make it easy to type. Some words flow across the QWERTY keyboard much more easily than others, and it’s not just about brevity. plum may be short but it’s an awkward, angular dance. apple trips you up with the double letter. orange is longer than both, but flows much better.

Further reading: The Poetics of CLI Command Names

Distribution

If possible, distribute as a single binary. If your language doesn’t compile to binary executables as standard, see if it has something like PyInstaller. If you really can’t distribute as a single binary, use the platform’s native package installer so you aren’t scattering things on disk that can’t easily be removed. Tread lightly on the user’s computer.

If you’re making a language-specific tool, such as a code linter, then this rule doesn’t apply—it’s safe to assume the user has an interpreter for that language installed on their computer.

Make it easy to uninstall. If it needs instructions, put them at the bottom of the install instructions—one of the most common times people want to uninstall software is right after installing it.

Analytics

Usage metrics can be helpful to understand how users are using your program, how to make it better, and where to focus effort. But, unlike websites, users of the command-line expect to be in control of their environment, and it is surprising when programs do things in the background without telling them.

Do not phone home usage or crash data without consent. Users will find out, and they will be angry. Be very explicit about what you collect, why you collect it, how anonymous it is and how you go about anonymizing it, and how long you retain it for.

Ideally, ask users whether they want to contribute data (“opt-in”). If you choose to do it by default (“opt-out”), then clearly tell users about it on your website or first run, and make it easy to disable.

Examples of projects that collect usage statistics:

Consider alternatives to collecting analytics.

Further reading: Open Source Metrics

Further reading