The Startup
Published in

The Startup

How to have your Personal CLI with oclif

Photo by Alex Knight on Unsplash

We know about Jarvis from Iron Man. He is very reliable indeed. Although, we can pleasantly want to own a Jarvis, we can start creating simple CLI that you can customize for your own simple needs. With that, we can use oclif. oclif is a framework for building CLIs in Node. It is extensible and customizable to cater developers’ needs either for tooling or just for fun. We will witness some of its prowess in a short while.

To begin, let me show the priorities that we will be covering for this article —

  • Multi-command CLI
  • Working on different commands
  • Running commands within a command

Getting Started

In backend development, when we mention “CLI”, we could easily think of git npx etc., this time, I’d like to build and call my CLI dxc . But you can change it with your own name preference as long as it conforms to npm naming guidelines. To start, let’s install oclif first and create our oclif framework

$ npm install -g oclif 
$ oclif multi dxc

We have just initiated a multi-command CLI. You will then be prompted with several questions about your CLI. Please see how I setup the framework here —

Note! Change dxc with your own CLI name preference

Once that’s done, you can have a try with the pre-created command hello with this —

$ cd dxc
$ ./bin/run hello

You will see a simple logged message hello world from ./src/commands/hello.ts! . You can also try running ./bin/run help or ./bin/run hello help to know what’s the CLI about and how to use your CLI commands. As you may noticed, we use ./bin/run to run our commands, but it is actually the same using dxc when users installs this CLI. But for convenience let’s globally add it by running

npm link

so then we can use dxc to run our commands, example— dxc hello

Dependencies

For this CLI, we’ll be creating three commands: inspire, advice and share.

inspire command, when run will share a quote to hopefully inspire you. advice command, well, it is self explanatory, it will give you an advice. share command will reuse the two commands, just to show that it is possible. With these, we will need certain dependencies that will help us in achieving our goals.

  • Quotable API — our source for our random quotes which is free
  • Advice Slip API — our source for our random advice which is also free
  • node-fetch — will be use for accessing the APIs
  • @types/node — since we will be using typescript, we have to register node types
  • chalk — you will love chalk, as it makes your CLI text colorful

Now let’s add these dependencies to our project —

npm install node-fetch chalk @types/node

We also need to update tsconfig.json to resolve issues with using node module types. Make it look like this —

{
"compilerOptions": {
"declaration": false,
"importHelpers": true,
"module": "commonjs",
"outDir": "lib",
"rootDir": "src",
"strict": false,
"target": "es2018",
"typeRoots": ["node_modules/@types"]
},
"include": [
"src/**/*"
]
}

The “Advice” Command

Let’s delete src/commands/hello.ts since we won’t be needing it.

Now let’s dig in on the process of creating the advice command —

  1. Create a new command by using oclif generator
oclif command advice

2. As a new file src/commands/advice.ts was created, you should update it and have it look like this —

 import {Command} from '@oclif/command'
import {AdviceSlip, AdviceService} from '../lib/advice'
import {hostname} from 'os'
import * as chalk from 'chalk'

export default class Advice extends Command {
static description = 'Shares an advice for you.'

async run() {
const name = hostname()
this.log(`Hello ${name}, here's an advice I can give you.\n`)
const a: AdviceSlip = await AdviceService.getAdvice()
this.log(' ' + chalk.green.italic(`${a.advice} \n`))
}
}

Let’s dissect the code. So what we have in there are:

  • We have imported library classes AdviceSlip and AdviceService (which we will define and discuss later), os.hostname which we have use for our message greeting, and chalk which we have use to change the color of the advice text to green.
  • We have our new class Advice that extends Command from oclif for us to utilize Command methods and options and make our new command advice work.
  • We added class property description to describe what advice command do.
  • We added class method run , which is mandatory, for it will be the method that will be called when a command is being ran. In there, we got our user name using the os.hostname() , and then used it to show a message greeting. We then tried to get an advice with await AdviceService.getAdvice() and show the result when it’s successfully fetched.

3. In our src/commands/advice.ts we have utilized src/lib/advice.ts . Now let’s create the file, but before that, we should also create the lib folder inside src

mkdir src/lib && touch src/lib/advice.ts

Open the file and add the following content —

 import fetch from 'node-fetch'

export class AdviceSlip {
id: number;
advice: string;
}
export class AdviceService {
static getAdvice(): AdviceSlip {
return fetch('https://api.adviceslip.com/advice')
.then(res => res.json())
.then(res => res.slip)
}
}

In this file, we have exported an interface AdviceSlip which will define what an advice should have. In there, an advice must have two properties id of number type and advice which is a string type. We also have exported class AdviceService and has a method getAdvice which basically fetch an advice from https://api.adviceslip.com/advice api endpoint.

4. Now that we have everything we need for advice command, we can now try it —

dxc advice

and see something like this —

Nice huh! I hope you have some smiles by now, but let’s move on to the next command — inspire

The “Inspire” Command

Basically for inspire command, we will have the same process with what we did in creating the advice command. So with that, I hope it’s okay that I won’t be digging too much with the code for this time.

  1. We should also generate the inspire command
oclif command inspire

2. Then let’s update the command file src/commands/inspire.ts to have this —

 import {Command} from '@oclif/command'
import {Quote, QuoteService} from '../lib/quote'
import {hostname} from 'os'
import * as chalk from 'chalk'

export default class Inspire extends Command {
static description = 'Shares a quote for you.'

async run() {
this.log(`Hello ${hostname()}, here's your dose of quote to inspire you.\n`)
const q: Quote = await QuoteService.getQuote()
this.log(' ' + chalk.yellow.italic(`"${q.content}" - ${q.author} \n`))
}
}

This is pretty similar to the previous command we tackle, the only difference are the usage of quote library, the command description and user greeting and also the color of the text message of the quote which was set to yellow

3. Now, let’s create the quote library

touch src/lib/quote.ts

Then, update the file with this —

import fetch from 'node-fetch'

export class Quote {
content: string;
author: string;

}
export class QuoteService {
static getQuote(): Quote {

return fetch('http://api.quotable.io/random')
.then(res => res.json())
}
}

In there, we have also an interface, but defines two properties: content and author both of type string. The service class QuoteService consumes the http://api.quotable.io/random to fetch a random quote.

4. Since our inspire command was set, we can now then try —

dxc inspire

and see this kind of result —

Yay! So far so good! Next, the share command.

Bonus! The “Share” Command

oclif does allow running commands within a command and that is what we will be doing this time. Now here it is —

  1. Generate share command.
oclif command share

2. Then, update the newly create command file src/commands/share.ts with the following —

 import {Command, flags} from '@oclif/command'
import Advice from './advice'
import Inspire from './inspire'

const SHARE_OPTIONS = ['advice', 'inspire']

export default class Share extends Command {
static description = 'Shares an advice or inspires you with a quote.'

static flags = {
// flag with a value (-n, --name=VALUE)
name: flags.string({
char: 'n',
description: 'What to share',
options: SHARE_OPTIONS,
}),
}

async run() {
const {flags} = this.parse(Share)

const name = flags.name ?? SHARE_OPTIONS[Math.floor(Math.random() * SHARE_OPTIONS.length)]
switch (name) {
case 'advice':
await Advice.run()
break
case 'inspire':
await Inspire.run()
break
}
}
}

This time, these are the things we put into:

  • You can see that we have flags property for the share command. We use flags to pass an argument or you could say data to the command. Flag options are non-positional arguments and that they can take a value, or not if it’s a boolean flag. The only flag we provide here is the -n, --name . This flag uses the n character and takes advice and inspire options from a constant variable SHARE_OPTIONS .
  • We imported the two command classes Advice and Inspire . When share command is ran, it will decide if it should run advice or inspire command depending on what was passed on the name flag. In case name flag is not provided. It will randomly select which command to run — const name = flags.name ?? SHARE_OPTIONS[Math.floor(Math.random() * SHARE_OPTIONS.length)]

And that’s it, you can try the share command with the following commands:

  • let the share command decide what command to run — advice or inspire
dxc share
  • use the -n or --name flag
$ dxc share -n=advice
$ dxc share --name=advice
$ dxc share -n=inspire
$ dxc share --name=inspire

I guess, it’s now safe to say that somehow you have your simple version of “Jarvis”! You may opt to publish this CLI package and allow others to use it. I have mine at https://www.npmjs.com/package/@datay/dxc

Possibilities

In the number of years I’ve been a web developer, I think I can say, CLI development is not so apparent. I myself sometimes neglect the power of tooling and end up with tedious, repetitive process, which is something I should change. My first interest with CLI development is just recently. It is when I have to create a tool for a work-related process on a certain git flow but that was created using bash . I could say, it was really fun! And now, when I come to know oclif, it becomes more clear that there are so many possibilities with CLI development and how it may improve every developer’s daily routine. As I mentioned, it could help your git flow or perhaps, you can create one for telling the weather forecast, trending news, or even showing stocks updates. Anything that is within your interest. But for this article, I personally like receiving quotes and advices. I hope you enjoyed it and you would consider using it too.

Note! oclif is not limited to the things I showed for this article. You may want to visit oclif an explore it more.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store