Designing a delightful command line interface


Speed, creativity and interaction – all superpowers for developers. Heroku’s Nahid Samsami and Jeff Dickey explain how a well-designed command line interface (CLI) can deliver these powers, offering examples from CLIs used by millions of developers and the experience of building oclif, the Open CLI Framework at Heroku. They spoke at DevXCon San Francisco 2018.


Nahid: We’re here to talk about designing a delightful command line interface. I am Nahid Samsami, I am a Director of Products at Heroku, and I’m here with Jeff Dickey, who is our principal CLI Engineer. And we’re really passionate about building command line interfaces. Jeff’s been working on our Heroku command line interface for over four years and I’ve been working on it for just over half of that. It’s one of the primary ways that our users interact with Heroku, it has over 100,000 users. And we’ve also abstracted it into an open source framework called oclif. You’ll see we’re both wearing matching oclif T-shirts today.

Oclif enables anyone to build their CLI and to use a load of what we’ve already built for the Heroku CLI, including many of the user experience things that we’ve already worked on. And currently the Salesforce CLI is built on oclif, the open CLI framework, and we have a lot of developers in the community building on it as well. Okay, so I’m gonna start off talking a little bit about differences between CLI and browser user experience. Then I’m gonna jump into some of the things that we’ve learned, the principles for building a really delightful CLI experience. And then we’re gonna end with Jeff walking you through getting set up and starting projects in oclif, the Open CLI Framework.

CLIs have special superpowers

So, at Heroku where there’s two main ways our users interact with us. One is the terminal or the CLI and one is the dashboard, which is in the browser. And there are pretty clear usability differences between the two. So you can see on the left, the CLI, you have a pretty…it’s really an input-output sort of mechanism. It’s up to the user to kind of, like, create the starting point and to put in something and get a response.

And in the dashboard that we have, which is in the browser, there’s just so much visual real estate. There’s so much, you know, there’s a top navigation bar, there’s dropdowns, there’s menus. It’s a lot easier to guide the user, to point the user in the right direction, to steer them towards things. And so building a user experience this way presents some challenges. So why is our user CLI so widely used and why, you know, why build a CLI at all? Well, CLIs have a lot of special superpowers. They enable developers to work really, really quickly. Once our developers have that muscle memory of using our CLI they love to use it and they’re able to do things really fast.

So it’s a huge tool for our power users. And then beyond that, CLIs have their own special superpowers, being able to do repeatable tasks, composable tasks, creating machine readable outputs, scripting and then, which is very important, interacting with an API. So, you know, we live in an API economy where there’s been a lot of talks at this conference about APIs and API design and… You know, interacting with an API using curl can be a little tricky. Using a CLI makes it really easy.

And then from a developer perspective, from me and Jeff’s perspective, building for a CLI takes a fraction of the effort of a browser. So if you’re building, you know, you don’t have to worry about CSS or JavaScript on the front-end. If you want to prototype something quickly, if you wanna build internal or admin tools, it’s a really great form to do that in. A lot of our support tools that we use internally are only built in the CLI.

Introducing our style guide

Okay, so jumping to the second part of the talk, the principles we’ve developed to create a delightful CLI for our users. We put a lot of these into a style guide that you can find in our dev center, that has a lot of detail and has some stuff that we won’t have time to cover today. We’ve broken it down into, really, four things. One is a mission statement, structure and navigation, input and output, and making the best use of the CLI superpowers.

So first, mission statements. We believe that every CLI should have a mission statement of what you are trying to do with that CLI. And this can really help guide you as you make design decisions, because you have sort of a framework to point to to say this is our mission for it. And for us it’s that this Heroku CLI is all about usability, and it’s about humans before machines, which means that while we want, you know, a base level of machine compatibility when we’re making a design decision, we’ll optimize with the human experience. We try to make input and output as consistent as possible, so that the user can easily learn how to interact with new commands.

Making it easier for developers

Okay, so let’s talk about structure and navigation. So as you saw, you know, in the browser there’s this very visual navigation, in the CLI you don’t have that. The tool that you have in the CLI is words. And so language becomes really important, and naming the command well is a thing. So our CLI is made up of topics, which are categories, which are the top level, and then commands. So for example, you know, heroku apps, apps is the topic and the command is create, which is under apps. And so the topics are nouns and the commands are verbs.

So that leads to commands like heroku apps:create, heroku apps:destroy, heroku apps:transfer. And then we also have rules that you can’t have, you know, it has to be lowercase words, single words, you can’t have hyphens, you can’t have underscores or special characters. And this helps, because we’re trying to help our users remember these commands. And so it helps with the muscle memory if they just have to remember that word, and there aren’t any sort of special characters or symbols that they also have to remember.

And when we’re building the CLI, we build it so that the folders, you know…as we’re building it, it looks the same for the developer building the CLI as it does for the end user. So on the left hand side you’ll see what our folder structure looks like, so each folder is a topic. So in this case, this topic is config and then each command is a file underneath that topic. And so as the developer’s working on the CLI, on the CLI they can easily find the right part of the code. And then on the right you’ll see, like, a typical topic like heroku apps, which has a full list of commands underneath it.

Okay, so next I’ll talk about command autocompletion, which we released pretty recently and is really, really helpful in guiding developers. Command autocompletion lets you tab to complete a command, which means that if a developer can’t remember all of the commands that are underneath the topic or they can’t remember, you know, the exact, you know, the exact phrase, they can just start the command and then they tab and it completes the command. And here you can see the user completing heroku apps as a command and seeing all of the commands that are underneath the apps topic. We also complete values such as app names, which means that you don’t have to run multiple commands to grab information from one command to the other.

Okay, now let’s talk about documentation. So for a long time in our CLI we had consistent documentation, which was under this help command, so you would run whatever you wanted to run, like heroku apps, heroku addons, and you would stick help at the end and then you would get a pretty consistent output in the CLI which would tell you, this is what the command does, here’s an example of it, these are the flags that you can use.

And we ran some UX studies, and we saw that most of our developers were doing that, but there was a pretty sizable subset that were developing with the browser there, and the terminal there, and they were going to Google. And they were trying to find more information by doing a search. And so, you know, we thought, you know, we could try to, you know, better educate users so that they all know that the information is in the command line, or you can go to where the users are. And, you know, we can, you know, do the model that they’re already…go to where they are already…go to the model that they are already using. So we’d created something so that all of our documentation lives in our dev center identically to in the CLI, and so everything is now searchable, and so we have both mechanisms. And this is something we’ve also abstracted into our open CLI framework, so if you use our CLI framework you’re able to replicate this, kind of, in-browser documentation with it.

Inputs, outputs and safeguards

Okay, now we get to really the meat of the CLI, which is input and output. We have a little gif that shows the user trying to transfer a selection of apps. A user can have, you know, hundreds or thousands of apps, and in this case we have decided to show all of those apps there so that they can select one or they can select many to transfer over. And this is the kind of component which is quite a lot of work to build, but it comes for free with our open CLI framework so that people don’t have to rebuild the thing that we’ve already built.

Okay, so now let’s talk about safeguards against risky actions. So every app, every product including ours has some actions that a user can take that are pretty destructive. In our case it would be deleting your app, deleting your database. There’s a lot of things where in a regular browser experience you would get a popup that would say, “Are you sure you really want to do this?” And you would say yes or no. You know, how do you replicate that experience in the command line? So what we do is, when you run what we determine to be a potentially risky action, such as deleting an app, we ask the user to confirm it. And they can confirm it in two ways, either they can enter the app name, or they can either enter… So you can see it’s being prompted here. They can either enter the app name when they’re prompted or they can run the command with –confirm in the app name. And that’s consistent across our CLI. It’s that little reminder, as you’re doing it, that makes the user think and make sure that they’re taking the action they want to take.

In the browser you’ll usually see lots of, you know, loading screens and bars that complete, and the equivalent of that in our CLI are these little spinners, these animations. On the left hand side is the typical one that you’ll see when you’re waiting for something to happen for a few seconds. On the right there’s one which has states that change to a checkbox or a cross depending on if something succeeds or fails.

Getting useful feedback

So what’s been amazing to us when we rolled out these little animations, these little spinners, was just how much positive feedback we got on these. Yeah, I’ve seen clapping in the room. Everyone loves these little animations. I’d say, like, 50% of the positive feedback we get about our CLI is still around these little things. And it’s been kind of amazing to see, because we weren’t expecting it, but I think it’s that it makes the CLI really come alive for the user. It’s those, like, little touches that sort of delights the user, that makes it, you know, a joy to use. And so even though it’s small, it makes a really big difference.

So if something’s gonna take a bit longer than a few seconds to complete, such as provisioning a database, which can take, say, 10 minutes if it’s a big database or whatever reason, we have a slightly different mechanism, which is notifications. So, you know, the user might have, you know, this running in one window and they’ll open another terminal window and they’ll want to know once this process is completed and their database is provisioned. And we use this notification that pops up on their desktop.

Now this was an interesting one, because we got very mixed feedback. Some users loved it and some users really, really, really hated it. They did not want this popup happening. And it was one of those things where we gave users the control to be able to remove this if they wanted to. And that’s what we’ve generally found in the CLI is, you know, users wanna have control and feel some ownership over it. And especially where there’s something that may feel a little bit intrusive to a user, it’s really important that they have the ability to turn the thing off. And we did the same thing with CLI autocomplete, where we knew that a lot of our users would really love autocomplete, but that some users would want to have it, and so we made it optional.

Just one example of a CLI superpower

Okay, last, CLI special powers. I’m just gonna talk about one, so hopefully you can see, on the left hand side is the output you used to get when you ran heroku regions. So you would basically get two lists of regions, one was the common runtime ones and one was the private spaces ones. And it’s, you know, nice and easy to read but it’s not a table. And that means that you couldn’t run grep on this. So we changed the output to the second one, the second box. We added a column for the runtime and made it into a table, and now you can filter using grep.

And in this case, you know, there’s not a lot of regions here, but you can imagine that for some commands where you have hundreds of things, being able to filter using the CLI gives you the power to create a list which is filtered. And that’s something that you can’t do in the dashboard. You know, it’s much harder to create, like, an output which is very specific to what you want and to filter on it.

Okay, so these were the main areas that I’m gonna talk about today. Mission statement, structure navigation, input and output and superpowers, but there’s a lot more on our site. You can read our CLI style guides. And then you can also learn more by going to oclif, which is our Open CLI Framework. So all of the things that I’ve talked about today, about user experience I’ve talked about, is incorporated into the Open CLI Framework so that it’s really easy for developers who want to build a CLI to get up and running really quickly and build a simple or a complex CLI using the framework and to follow the same patterns that we’ve developed. And I’m gonna hand over to Jeff from our team, who’s gonna talk a bit more about oclif.

The development of oclif

Jeff: Yeah, so you know, we… When I started working on the Heroku CLI, it wasn’t long after that… You know, Heroku’s owned by Salesforce, and they were in the process of building a CLI to interact with the Salesforce platform and really wanted to use a lot of the developer experience that we had built with the Heroku CLI. So we went through a lot of work to make the Heroku CLI work with both our CLI and the Salesforce CLI. This was just internal. And once we got to that point, we realized that it wouldn’t take that much more work for us to make it work for anybody’s CLI. And we thought it was important for us to do that, to give back to the community. So we spent probably six months to do the extraction effort, get the docs written, make sure that the interfaces were exactly what we want, because we’ll have very little opportunity to make changes in the future, and we built oclif.

So oclif lets you build a CLI that’s just as powerful as the ones that we have. I think probably the killer feature that we have is the plugin interface. Plugins are really used for two things, one, it’s a way that a CLI developer could add functionality that somebody else wrote. So like if you want to add autocomplete to your CLI you just add an autocomplete plugin. If you want to add auto-updating, add the autoupdate plugin. If you wanna add a fancy, like, command not found message, you got a plugin for that. And then users can do that too. So they can add in their own plugins or runtimes, sort of like Atom editor for CLI’s.

Running a demo

So I wanna show a quick demo. So this is using the oclif generator, and if you’ve used, like, Rails New in the past, same idea. It just builds up a templated CLI and it can either be in JavaScript or TypeScript. We like TypeScript at Heroku so that’s what I’m writing this in today. And you can see it comes with just like a standard hello command and you can get the help for it, run that command. And what this CLI’s gonna do is it’s gonna show the GitHub stars from the GitHub repo.

So you can see I’m just demoing, like showing arguments, just adding in a axios, which is a http client for Node. And I’ll generate a new command called stars. Go in, edit that, and here I’m just gonna quickly, very quickly, delete a lot of the sample code. I’m gonna add an argument for the user to pass in which repository they want to get the stars from and add in some help text to make it clear what this command does. And then this is just the implementation of the command. So run axios to fetch GitHub API, use the argument the user passed in, iterate over each of those users, and log out their username.

So now if I run the help for this, we can see the help with arguments in the CLI, we get some usage and stuff. This can get really complicated. And then we run it. So there we have it. So yeah, if any of you are curious about building CLIs or wanna build a CLI, either personal use or at your company, you know, I think this kind of thing works especially well for people building APIs that want to provide a better user interface for developers on your platform, let us know, let us know what you’re working on.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.