Best Practices Building a CLI Tool for Your Service

Adam DuVander
Adam DuVander / February 16, 2017

To some, the command line is a relic of computing’s past. But as a developer tool, the command line is essential. Managing local processes and source control are often done through Command Line Interfaces (CLIs). In recent years, another kind of CLI has become popular, and it extends beyond the local system. The command line has become a powerful way for developers to interact with cloud services. Cloud computing services, continuous integration products, and some APIs have their own CLIs.

Developers can get a lot more done by using a well-designed CLI. If you’re building one to support your service, here are some things to keep in mind, tips we've learned while building our own CLI for Zapier.

Provide an Excellent Help Screen

Getting started with a CLI is unlike using other software for the first time. There is not always a welcome screen, no confirmation email with a link to documentation. Only through the command itself can developers explore what’s possible.

Zapier support engineer Jacob Blakely advises that usability and discovery are paramount in a CLI application. That experience begins in the help screen, accessible via a command in your CLI application. Ideally, you should include multiple ways to come across this screen in your tool. For example, the version control system git provides git help and git --help which both show you the same screen:
git --help

Even better, make the screen accessible through short flags, such as -h or -?. These and git's --help are all standard ways of opening documentation in a CLI—and we'll look more at such defaults below.

The CLI help screen is essentially your getting started documentation for the command line. List out the possible commands in logical groups, with each group in alphabetical order. Along with each command, give a quick and thorough explanation. It’s a balancing act of saying enough without saying too much.

Some CLIs have more involved help systems that let developers query beyond the basic help screen. JavaScript package manager NPM, for example, has a terse initial help screen, which is great for double checking commands. Then developers can explore further with npm <cmd> -h or read a full NPM overview (more like a man page) with npm help npm.

Even better, take your help screen out of the help screen. Ever enter a typo in git?

Helpful git error message

You’ll get just-in-time help, plus a suggestion to try the main help page for more information.

Here are some other features that can take the help out of the help screen:

  • Tab completion. Document your commands in a format understood by popular shells, as we did in Zapier's CLI for bash and zsh.
  • Sane defaults. If your CLI shows logs, for example, choose a reasonable limit by default, then share the flag developers can use to change that value.

There are many other ways to make your CLI more usable, most of which parallel best practices in other user interfaces, such as giving good error messages.

“Steal, Steal, Steal”

When asked advice for others creating CLIs, Zapier platform engineer Brian Cooksey—one of the lead developers behind Zapier's CLI—recommended looking at the good patterns in Linux and other tools you respect. See how others who have gone before you solved problems. More importantly, consider the conventions others have already created, as these become expectations for the users of your CLI. Then, in the words of Cooksey, "Steal, steal, steal."

One such convention is to always support short and long format for flags, says Brightwork’s Phil Taylor. Descriptive, long format flags can be guessed, something that's increasingly easy when many CLI builders stick to conventions. The shorter formats, though, save typing for your most frequent users. Here are a few common flags:

  • -h or --help: Display the help screen
  • -v or --verbose: Show less succinct output of the command, usually for debugging purposes. This one may be contentious, as some CLIs use -v for version.
  • -V or --version: It’s important to know which version of the CLI you’re using, but not as often as you want verbose output.

CLIs that support a service probably need to authenticate to that service. There are many ways to achieve this, but if you want to take a username and password, here are some common conventions:

  • login: Kick off interactive session to provide credentials, like Heroku
  • --username and --password flags: Send credentials with initial call, as svn does
  • -u un:pw: Supply credentials with every call ala cURL (remove :pw to be prompted for a password)

While two of those are flags, login is a command. Commands are also a place to steal from other CLIs. Commands are the actions you want to take, as opposed to flags, which usually are modifiers on those actions. Verbs are good choices, since they describe actions. Here are some common commands:

  • config: Update settings, variables, or other configuration.
  • help: Show info and documentation. As part of providing an excellent help screen, include this as a command, as well as a flag.
  • init or startproject: Use this to start the developer with the stubs necessary for an empty project.
  • push: Send your changes to the server.
  • status: Provide local or server status

If your CLI is particularly complex, you might choose to have multiple levels of commands. In this case, noun/verb pairs work well. For example, heroku apps:info will get you the metadata for the current directory’s Heroku app.

Notably, the Amazon Web Services CLI does not carry over many of the conventions mentioned here. Instead, it arranges its commands by service, each with long descriptive commands separated by dashes. Yet, it does borrow patterns for arguments to its commands. This is something you can do in your CLI, as well. For example, Amazon S3 uses Unix commands for copying (cp) and moving (mv), and is sure to adhere to the ordering of arguments, too: source/local file first, destination second.

Since conventions can be specific to types of CLIs, it’s important to pay special attention to the ecosystem of tools you are building in. Version control is going to be different for platform management commands, for example.

Test Different Setups

Zapier’s developer machines are all Macs, which is convenient for our team’s productivity. When we know everyone has essentially the same machine, it’s much easier to make tooling decisions that ensure everyone has the same experience. However, when you ship your CLI to developers, you can’t count on the same homogeneity.

Backchannel during user testing

When we first started looking for beta testers of the Zapier CLI, we figured that Unix/Linux variants would be the bulk of the testers. We were wrong. Windows testing was a must, even at that early stage. CLI team members Bryan Helmig and Steve Molitor found out how important testing is when they joined UX researcher Eileen Ruberto for user testing. The Slack backchannel caught the team’s pain for posterity as they watched a Windows user struggle through something we hadn’t tested. (We fixed Windows support about 90 minutes later).

While platform differences are manageable, developer environment setups can vary widely. Devs will often have very specific customizations that make their individual workflow much faster. Those sort of differences should be embraced. That sense of exploration is part of the reason the dev is going to try out your CLI, too.

It’s important to get the CLI in front of as many other developers as soon as you can, so you get more feedback. But you want it to be the right kind of feedback that you can take action upon. It’s not always feasible to cover all the bases all the time, so when you’re looking for testers, be clear about what you’re looking to test so you can get the feedback you need at that moment. And embrace spontaneous, random tests—that just might be how you uncover flaws as we did.

Security Still Matters

It may be tempting to see security as less important when dealing with a developer’s local machine. When CLIs support services, though, they’re making calls to an external API. With few exceptions, those API requests are authenticated, so they should be treated with the same security practices as the rest of your application.

To start, don’t store raw credentials, like a username and password. Yes, the developer is responsible for their own machine’s security, but there are better methods that ensure your CLI and user info will stay secure. Let’s learn a lesson from Twitter’s early API, which only used Basic Auth, opening its users to potential hacked accounts through shared passwords. In 2010, the company switched to requiring OAuth tokens. Similar approaches can work with your CLI.

With the Zapier CLI, developers use zapier login to send a username and password over HTTPS. The returned access key is stored in the .zapierrc file. If the key falls into the wrong hands, it can be revoked without affecting the user’s primary login.

Similarly, other CLIs use JavaScript Web Tokens for access keys. Heroku stores the API token returned from heroku login in the common Unix network tool .netrc file.

Regardless of the specific authentication mechanism, the common factors of only storing a token/key and using a location that is less likely to be accidentally exposed, like a hidden file, help keep your CLI tool secure.

Allow Developers to Customize Their CLI Experience

In UX for Wizards, user experience designer Randall Hansen says “using the command line is all about controlling a computer at the speed of thought.” CLIs aren’t typically seen on the same level as other interfaces. Yet, they share the commonality that a good interface helps users get things done.

Using CLI tools help developers move faster. Part of that magic is ensuring that the tool can meet the developer’s exact needs, which come down to customization.

We mentioned sane defaults in an earlier section. Being able to alter those defaults is an example of customization. Setting limits with a flag, for example, is helpful, but overwriting the default goes a step farther. For that, you need some kind of configuration file. These are common with shells: bash has .bashrc and .bash_profile; zsh has .zshrc. You’ll likely have a dot file like this for account information, so you might as well extend it to other settings.

“Allow the user to save arguments they would otherwise have to put on every call,” says Phil from Brightwork. If there’s a non-default setting that the developer wants to be a default, provide a way to make it default. That way, they can omit it on future calls.

That’s what HTTPie, an HTTP request tool, does with the default_options array in its config.json file. You can set style and output options, for example, so it always looks like you expect. But the feature goes beyond how the CLI looks, since any command line flags can be included in your configuration. For example, you can enable sessions automatically, so subsequent API calls automatically include the auth details.

Depending on your service, you might make your dot file global to the machine, specific to a project’s directory, or both. The key is to understand how developers want to use your CLI, and offer ways to customize that experience.

Try the Zapier CLI

Are you ready to see how well we follow our own advice? Try the Zapier CLI to connect an API to the Zapier Platform. With a little bit of code, you can give non-developers the power to use your API. Even better, your app will immediately integrate with 750+ other services, saving you thousands of hours of development time and maintenance.

Join the Zapier CLI Beta now

Load Comments...

Comments powered by Disqus