Looking for more podcasts? Tune in to the Salesforce Developer podcast to hear short and insightful stories for developers, from developers.
13. oclif: An Open Source CLI Framework
Hosted by Nahid Samsami, with guest Jeff Dickey.
As developers, we spend a lot of our time running commands in a terminal, whether for a local project or to manage a service in the cloud. Jeff Dickey of DropBox joins us to talk about his process for building oclif, an open-source framework for building command-line programs, used by Heroku, Netlify, Apollo, and many others.
For many years, Jeff Dickey was a lead architect for Heroku's CLI tool, which was used by application developers to get their apps deployed to Heroku's platform. He muses on his history with CLIs with Nahid Samsami, a director of product at Heroku, as the two of them worked together on oclif.
The project has been incredibly popular, both through internal adoption at Heroku and Salesforce, to its reception and use from other companies, as well as the active contributions made on GitHub. The episode concludes with some theories about the future of CLI tooling. PowerShell, for example, is a fully object-oriented environment which a developer can program against. Jeff is also interested in better integration between the terminal and UI elements.
Links from this episode
Nahid: Hello, I am Nahid Samsami. I'm a director of product at Heroku, and I am here with Jeff Dickey. He's an engineer at Dropbox, and for many years Jeff was a CLI engineer here at Heroku and a lead engineer on the oclif project, which is the open CLI framework, and we worked together on that. So I'm super happy to be here with Jeff today and we're gonna talk a bit about oclif and command-line interfaces. Welcome, Jeff.
Jeff: Thanks, Nahid. I'm happy to be here.
Nahid: Okay. So my first question, which I myself don't know the answer to, even though we worked together for a few years was how did you come to work on command line interfaces?
Jeff: Commands like
heroku logs and learning how to pipe that to grep was one of my earlier uses for the terminal that really taught me a lot about how to leverage the CLI in useful ways, and
heroku run as a way to just run ad hoc commands inside of a living server was also a really great way to interface with the platform. So I really admired both the Heroku CLI, and CLIs in general. Actually I have a funny story, I was at a conference that Heroku put on, they did a couple of these conferences called WAZA, and I was at one of them and I met who was then the current maintainer of the Heroku CLI, this was probably about three or four years before I worked on it myself, and I was star struck.
Jeff: I couldn't believe I was talking to the guy that worked on the Heroku CLI, because it was such a cool product, and so I grabbed his ear for the whole night, probably annoyed him a little bit, trying to get all the stories I could out of him about what it was like to do full time CLI development. And then a few years later I saw the job posting that Heroku had, and I thought I'll take a chance on this, and thankfully I made it. It was a lot of fun for several years to work on this product.
Nahid: Awesome, that's a really cool story. So when you got here to Heroku, you'd been thinking about the Heroku CLI for a while, when you got here, was anything surprising about the code under the hood, how it looked and how the CLI worked?
Jeff: Yeah, there's a lot of things that are different about writing CLI code versus writing web and backend code or desktop code, whatever other software you want to write. I think at a code level, the biggest difference is that the process only lives for a few hundred milliseconds ideally. There's some exceptions to that with
heroku run and
heroku logs, but most commands just they run and then they exit. And so in that environment the typical things you optimize for when writing code being CPU bound and memory bound performance, really don't apply with CLI development because the memory, especially not. Because even if you're leaking memory like mad, there's only so much you could leak over a couple hundred milliseconds then CPU just doesn't tend to be doing sort of order end sort of operations that would grow.
Jeff: Yeah. So I came to Heroku with a mandate to rewrite the CLI. The Ruby as a language did not work well. I'll start with why Ruby was not good for us. One, it was slow especially on Windows, I don't know how the performance is now, this would have been in 2014 when this was going on. At that time, our benchmarks showed that Ruby on Windows was about half the speed that it was on UNUnixIX.
Nahid: Why was it slow on Windows?
Jeff: I don't know, my guess is that Ruby makes a solid attempt to be compatible with Windows. The people who develop with it, but nobody's writing servers on Windows, so I think the performance generally is not that big of an issue.
Jeff: If you're running a Rails app or something then how performant Ruby is is not as important as it is to me writing a CLI that's in Ruby. So performance is one, and again that may be better now, I'm not familiar with how Ruby's development has gone. Another one was dependencies. So we had this plugin framework that allowed developers to extend the Heroku CLI and add their own functionality. And when you do that, you might want to add dependencies, like a different HTTP client for example.
Jeff: So for example, the HTTP client we used with Ruby and the plugins was excon. The problem was excon was delivered inside the CLI so if you wanted to add any dependencies on your own you couldn't do it. And also the version of excon that we used had to be global, had to be the same version of excon for all of the plugins that we used it. Which made updating it virtually impossible because in order to do it I had to coordinate not only all the plugins that we use but every other plugin to make sure they were compatible with both the version it was coming from and to, which made it so difficult that we just really didn't use any dependencies.
Jeff: And writing everything in pure Ruby's okay but there's a lot of things that we just wanted to use, things like a Redis client would have been really nice. Or doing stuff with the websockets was another thing that just seemed impossible. So dependency is another thing that Ruby didn't really allow for because the dependencies are sort of global. Like you add one gem to the project and that's the only version you can have in the whole code base. And then also the runtime dependency. Ruby being a scripting language requires that you have Ruby installed on the machine when you've run the CLI. That's okay because Ruby is available on say all Macs and what we did is we just shipped Ruby with the CLI and Windows as well but then, problems would arise if the version of Ruby that you're using wasn't the one that we supported.
Jeff: And this basically means that we had to support every version of Ruby, so that was a big problem. So to sort of wrap that up that's sort of the problems with Ruby and why we wanted to do language. So sort of the favorite language I think at Heroku now is Go and that was the ideal candidate when I started was to write it in Go. So we did work on that and that was the initial direction I held, but the problem with Go as a language is it doesn't have any library support, you didn't have any dynamic libraries.
Jeff: So there was no way to extend the code base you know everything is packaged into a single binary. There is no way to add anything on to it so you know plugins was the big problem with that. Today Go actually does support libraries but they're not supported on Windows which means even today we wouldn't be able to use what they have. We did spike out a couple of solutions that look sort of like, have a directory of separate binaries that could act like libraries. That may have worked, there were issues with that approach like where are they going to get built, things like that. So I thought it be great to use Node for the plugins only. So that was the original idea and why we had you know a Go Node to CLI. Was we had Go for like all the core stuff, Node for a plugin framework.
Jeff: And so went down that approach and it worked pretty well, the next problem we ran into was, it was nice to have the core code look the same as the plugin code. You know like let's say I'm making modification to Heroku apps colon create, the command that creates an app. If I want to make a modification to that I'd really need the existing code as reference and if the existing code is going to be all in Go then I'm going to need to write this in Node, that's not helpful to me at all. So that was the area where we shifted more of this to Node. So basically what we came up with was all app code you know things that work with Heroku, this is pre-oclif but oclif was sort of an idea in the back of our mind that we mind want to release a framework at this time.
Jeff: We would do all the Heroku command development in Node and then we'd have Go as sort of like a fast way to run everything. This was, from the user's perspective worked really well: we were able to keep the Node binary available and the right version so we didn't have the same runtime dependency problems that we had with Ruby. The help and everything was able to be displayed very quickly and instantaneously because we didn't have any overhead running the CLI it was a Go CLI and it knew what all the commands were and so it could display the help instantaneously. The problem was just the complexity. A lot of the same code that we had you know things like dealing with standard out closing in the case of like piping to head-n1 or something like that.
Jeff: You have some similar code in both the Go and the Node places, dealing with configuration files if they exist, updating, it's just an endless areas where we had to have the same code. And we just felt like we were really duplicating efforts and also the build script to actually create this Frankenstein of two languages was something that nobody on the team wanted to touch it was only me and basically, it was a Makefile that was my first time seriously using make and it was probably a 500 line monster, there was no way anybody could comprehend that.
Jeff: So we wanted a similar approach and we also thought you know if we're ever going to release as a framework this is not going to work. So that's when we started the effort to move to pure Node and that was our v6 the CLI. And that was a big win I think that's when everything coalesced and all these different avenues that we took to try different things--it wasn't just the language it was a lot of internal things the we were doing at the same time.
Jeff: Everything really coalesced around Node the CLI v6 and it got to a place where we were very happy with the code base.
Nahid: Yeah so it sounds like you went from Ruby to Node and Go and then in v6 all of the Go code was gone and it was purely Node. Were there any special challenges with writing it in Node besides the one that you talked about with the startup time and the mitigations, were there other drawbacks or mitigations or things that you had to do to make it workable?
Jeff: I think the promise of Go and its single binary approach isn't as great as it sounds, I think that's one reason why--so Go works fantastically well for a CLI it's just fast. But the fact it's a single binary in my opinion is a downside. Because one, you're going to have to have other files there, if you're going to have a CLI of good size you're going to need other things like for us that might be a package of certs, it might be a folder for auto-updating to work, that you know Readme, just text files, configuration files. So you're going to have a tarball no matter what.
Jeff: And having a single binary inside of that doesn't really matter, like for us it's a bash file and a Node file and it just lives there and its happy. The other thing about using a scripting language like Ruby or Ruby or Node or Python, is that when we get a support ticket we can have the customer, because our customers they're smart people, they're developers, so if somethings not going right, rather than us trying to diagnose it we can have them go right into the code and edit it.
Jeff: It's like go to this line, we can show them on Github say, I want you to edit this lime to print this thing out and then the turnaround time for us to be able to fix things ends up being really quickly. And if we just have a single binary that's not possible. You know instead what you have to do is if something that only can be reproduced only on the customer's machine it involves adding some debug information and then shipping that release having the customer update and then going through and asking them again it takes way too long. It's frustrating for everybody so anyway single binary is not all it's cracked up to be frankly.
Jeff: They usually have had to write it at some point or another and so you know whether or not it's somebody's favorite language they can jump right into the CLI code base and start contributing right away.
Nahid: Okay let's talk a little bit about oclif, the open CLI framework, so you mentioned it was v6 of the Heroku CLI which was all in Node and then I remember that the Salesforce--one of the Salesforce teams came to us--Heroku is part of Salesforce. And the Salesforce team came to us and they were looking into building a CLI and they wanted to see if they could build it on top of the Heroku CLI. Initially we talked a bit about having to build it as a plugin and then it turned out there were some differences between what made sense for their use case and ours. And so I remember you suggested the idea of having, extracting the Heroku CLI and so they'd be able to build that CLI but to be able to customize it for what they needed. And then we went a step beyond that into an open framework that anyone could build a CLI on.
Nahid: So can you talk a little bit about what inspired you to come up with that idea of making oclif and extracting our Heroku CLI into an open source framework.
Jeff: Yeah so as I said from the beginning having the mandate to write a new CLI, I thought that it was really important for us to release this as an open source framework if we could. It was going to take a lot of resources from Heroku to build just the Heroku CLI in the way that we wanted it to. Heroku's willing to devote a lot more resources I think than would be normal to build a CLI because, the CLI is so fundamental to the user's experience and the customer directly interacts with the CLI it's one of the top selling points about our platform.
Jeff: And so it was crucially important that that be as good as possible and so building something like that where we really got the runway to do really amazing things and spend the time to get it right, I thought was something that other people could use too. Other organizations where they're not going to have as much time to be able to devote to this, they could really use the work that I've done. The trouble's getting me there because I didn't think that I'd be able to do it right off the bat, originally the Heroku CLI was just one repository. It wasn't a framework and it was hard enough job to figure out just what the Heroku CLI should look like, not one generalization level beyond that where you think about how am I going to design a framework so that somebody can build a CLI. I had to do the CLI first.
Jeff: So when Salesforce came along they wanted to build their CLI on the Heroku CLI that was really the perfect opportunity for us to start thinking about what an open source server could look like. It was far from certain that we would go down that route. First we started out with just a templatized Heroku CLI where they could have like a build step be run on top of it to transform it into their CLI. And that wasn't ideal for a lot of reasons but we got to iron out all the areas where we wanted to do things differently.
Jeff: Salesforce had a lot of different requirements that weren't important to us or would have just changed the experience in ways that we didn't like. Things that maybe a customer might not even notice unless they were looking for like, how the flags are structured when you look at the help, like what order they're shown in and how much spacing is here and it's little things to a user but when you're thinking about CLI experience you get very attached to these things and you have a certain way that you want things to look and you get very eager to have the line count be short and different things.
Nahid: And these are things that are specific to different CLIs?
Jeff: Yeah, yeah. So it was an easy guess for us that we would--if Salesforce is wanting to do these things differently then other people will probably want to do them differently as well. So we really learned how to build a framework at that stage and so we spent probably a good year or two on that and then as that was wrapping up we then spent another six months on oclif as a project itself to say okay, now we've got the fundamentals of like what a framework could look like let's, we give it a name, that was a struggle I'm sure you remember Nide.
Nahid: Yeah. I could talk a little bit about that, we were ready to release oclif and it didn't have a name, you had a working name for it CLI-Engine-
Jeff: CLI-Engine was the working name for a long time but actually I didn't like the name, I think everybody else liked the name CLI-Engine but I hated it because it had a dash in it. And I just thought when you're running the generator and stuff it's going to be annoying to have that dash there. So I was very insistent that the name be as few characters as possible without any special characters.
Nahid: Yes I remember that and we had a Google Doc or a Quickdoc where we had everyone list a whole bunch of different naming possibilities for it and I'm really glad we chose oclif in the end because there were some very random things in there. Yeah I think oclif the name open CLI framework really sums it up.
Jeff: Yeah, I think that one was one that we, I remember the naming process I feel like it took like two maybe three months and longer than we wanted it to take, we really wanted to launch it.
Nahid: I think it took a week but it felt like two or three months to you.
Jeff: Maybe. I remember there was one--Crane, that I loved, but it had to be like available on Github and NPM and I don't think it was available on either but. But maybe in a alternate universe it could've been named Crane.
Nahid: Yeah so then we released the framework and you know it was interesting so that we can talk a little bit about the reception that it received. You know when we released it was interesting that a lot of developers had actually been following CLI-engine and they began following oclif as well.
Jeff: So anyways like there was a lot of change, there was a lot of toil, moreso than you would have in a normal project when you're just trying to get a product out the door but when it is code that other people need to interact with I think it's important to spend the time on that. So yeah we got great feedback and people jumped on it I wasn't really sure how a CLI framework, how popular that kind of thing would be but it does seem like we hit the nail on the head we had a lot of people using it.
Jeff: It's actually the top open source project at Salesforce in terms of GitHub stars I think we've got I think over 3000 now, so yeah it's done very well.
Nahid: It's done very well, the thing that makes me really happy is seeing when people, helping people with building, CLIs and oclif both companies and individuals. Everything from Netlify CLI to-
Jeff: Apollo and what's the Scripto one?
Nahid: Lisk. And then there's you know individuals building CLIs too I think there's a couple of World Cup ones that are really popular?
Jeff: Yeah, yeah there's a lot of those and I think, and there's a lot of stuff we don't hear about because I think probably the most common use case for CLIs is people doing internal tools. You know it's just a lot easier to have admin stuff be a CLI then be a web thing, and so we get a lot of stuff about that and the only unfortunate thing is that we just never know when people are using it. But we know just through people reaching out to us and letting us know what they're working on.
Nahid: And we take a look at NPM and at GitHub so it gives us a sense but-
Jeff: It gives us an idea yeah.
Nahid: Yeah. So we have a pretty active community in Spectrum and we get a lot of questions and feedback in there from developers building on oclif. And I'm curious based on all their feedback and questions we've gotten, going back is there anything that you would build differently based on the feedback we've gotten since launch? If hindsight was 20-20 and you could change something?
Jeff: I mean there was a lot of that along the way you know like using Go, if I could have known what I know now and stuck with TypeScript and just pure Node from the beginning that would've been really good. But we got there I think, I will say that the change was, the pace that I modified, the code base and the toil that I went through was not something that went over well internally.
Jeff: Because we had internal people using the CLI to build things and when I would make changes that would break a lot of our internal tools. So that was a judgment call I think that we had to make like, we felt that it was more important to have a really great framework than the cost of this of breaking things now internally.
Nahid: Yeah and I think when we released the documentation for oclif more officially I think that was a line in the sand of, we're not going to break things anymore.
Jeff: Yeah and I think that's another thing that maybe isn't clear to people but the framework is something that we think is stable you know, when we came out with that v1 we did not want to make any changes at all. Because we knew how much it hurt to make even simple changes early on, even if they were better changes the fact that you're changing something and having some kind of required change that might break something was extremely painful so a big goal for us was for the open source community that uses oclif to never have to deal with those problems.
Jeff: So thankfully we're still in good shape there we haven't had anything come up, I thought there might be something that came up that we're like oh we should have done this differently maybe now we need to do a breaking change. I think we've made the right decisions but you know it's been about a year to the day actually.
Nahid: Oh wow.
Jeff: I think it was March actually of 2018 so little over a year and it's still the case but I'm sure it'll come. My hope is that when it comes to breaking change it'll be tied around the LTS releases of Node. So right now we support Node eight and above, Node eight will be end of life in April of 2020. I hope that we can batch up any breaking change that might come between now and then and use that as an opportunity both to move to Node 10. In oclif 2.0 and also add any breaking change that we feel are necessary, as long as we're not too difficult for people.
Nahid: Okay we've talked a lot about the history of the Heroku command line interface and of oclif the open CLI framework, let's talk a little bit more now about the future. So you've spent a lot of time thinking about CLIs if you were to think about what command line interfaces would look like in five years, 10 years, maybe 20 years time. What do you think the command line interface of the future will be like?
Jeff: Oh it looks like PowerShell, Microsoft's solved this problem. PowerShell is incredibly good it's like this object oriented shell where you can, it's amazing, it supports all sorts of great things that I wish we have in Unix world.
Nahid: What kind of things?
Jeff: The object oriented stuff so like you can have an output of a CLI Bn object that you can then like run methods and get properties on and that's really cool. It was actually a little bit of an inspiration for SuperTABLE one of the tools we more recently launched in the CLI-ux where the end user can have more control over what they're being displayed you know so the use case that I always like to give is, I should be able to go into CLI and say, give me a list of all of my Ruby apps, ordered by the last time they were deployed.
Jeff: I think PowerShell you could not only do that but then you could then take it a step further and say give me all of the Ruby apps ordered by the date of last deploy and get the specific configure from them. Which is something that you could do with the right level of -Xrs and awk and things but it would be incomprehensible and PowerShell is able to do these things much more natively.
Nahid: And what do you think about artificial intelligence and how that could impact on the future command line interface?
Jeff: That's not something I've thought a lot about but I would say one thing I think that is missing in CLI is help. Help is just part of using a CLI in a way that using the web is not, like you never read the help part of a web app unless you're really stuck whereas with using a CLI you're reading the help even if, I read the help for the Heroku CLI even though I worked on it for five years and I wrote all the code myself because I just forget, oh what is this flag named again?
Jeff: So that's just part of the experience is using the help so I think if that could be brought into the experience of using it so you know I'm talking auto complete but like a level above that, like when I start typing something it should, below where I'm typing it should show like what I'm doing what I'm working with if it's valid if it's not valid, things like that.
Jeff: So the area I can see AI getting involved is maybe with some, I'm thinking like Splunk, it's been awhile since I've used Splunk but I think Splunk has like, when you do the search it sort of like guesses what you might be wanting to do. Maybe that's an area where AI could help guide you along to figure out what are some common tasks I don't know.
Nahid: Yeah I think that AI is all about predicting what users are going to do and I think that when in more visual interfaces things are getting served up more and more and more versus you kind of having to go out and you know find them whereas the CLI right now is pretty much you know, you still have to type in the command yourself and so I wonder if there's like a stage we can get to where the CLI sort of knows what you're going to type before you even think of it and then you basically say yep that's what I wanted to do.
Jeff: I also think and this sort of is an odd thing to think about but a next level CLI I think should use the mouse and I guess at that level is technically not a CLI it's a GUI. But you know I think Slack is really good with this like early Gmail actions where like you know, you can click to unsubscribe from a newsletter or RSVP for an event without even opening an email.
Jeff: Slack has like the button and stuff where you can perform actions I think like for confirmation dialogue that could be really good. Notifications and things I think for you know, select one of these three things I think sometimes could be done a lot better in a CLI than having to click. Obviously you need also the way to script things that's really important to CLI. Not use those things if you don't have to but yeah I think that the CLI world could be really amazing, even better than it is now but-
Nahid: I'm not sure I follow. You mean the mean the mouse where you would use your mouse and something would happen with the CLI or?
Jeff: Do you know Slack buttons or Slack actions I think they call it? But it's like you copy and paste a URL in a Slack and then for everybody in the room, like a poll or something? Like you can click to like vote inside of that. Like you could conceive a CLI having a similar sort of experience where like you run a command that needs like prompt you for something and you could just click a button and set it to directly run a command again with a different flag or something like that.
Nahid: Yes okay I see what you're saying that would be really cool. Okay so I think we're running out of time, let's talk a little bit oclifconf which is the first oclif conference that's happening May the 31st here in San Francisco and it's for CLI builders the focus is on oclif. But it's for all CLA builders really. Yeah but tell me a bit more about what you're hoping to get out of oclif conf?
Jeff: Main thing I want to see is how people are using oclif and how they're building the CLI experiences. You know I've had a lot of experiences myself building a CLI but you know I want to see what other people are doing and I want to learn from the community and figure out what maybe oclif could look like if there's anything that's missing that we haven't thought about. Just see how people are using it.
Nahid: Yeah that's what I'm excited about as well and meeting some of the people developing it in person too and it's been interesting watching everything happen on GitHub it'll be great to sort of see the faces of all the people who are developing on it and to get their feedback. I think sometimes it's easier to get feedback in person so, perhaps there will be more feedback that way.
Nahid: Great well thanks so much for joining us Jeff.
Jeff: Yeah thanks for having me, it's been fun.
Nahid: You can learn more about oclif at oclif.io, thanks.
A podcast brought to you by the developer advocate team at Heroku, exploring code, technology, tools, tips, and the life of the developer.
Product Manager, Heroku
Nahid is a product manager with a passion for creating delightful developer experiences.
More episodes from Code[ish]
Innocent Bindura and Greg Nokes
How do you know an application is performing well beyond the absence of crash reports? Innocent Bindura, a senior developer at Raygun, shares the company's tools and utilities, discusses the importance of monitoring P99 latency, and talks... →
Robert Blumen and Marcus Blankenship
How can developers learn from catastrophic errors such as airline disasters? Learn how understanding the root causes of failure in complex systems can prevent their recurrence. →
Karan Gupta and Marcus Blankenship
How can applying the right technology choices at the right time impact your coding and business choices? Karan Gupta explains how practicing “pragmatic engineering” can have an oversized impact on business and business efficiency. →