Building an AI-Based CLI Tool in 2024

You're in your terminal, frantically Googling "how to kill process on port 3000" for the fifth time this week. Or maybe you're trying to remember the exact syntax for that Docker command you use once a month. Your coffee's cold, your patience is thin, and you're wondering why your CLI can't just read your mind. Enter Howitzer: my AI-powered tool that turns your frustrated mumbles into precise commands. No more memorizing arcane syntax or scouring man pages. Just ask, and execute.

The Problem

When I set out to build a CLI tool this year, I noticed a significant gap in the available resources. Most articles on building CLI tools were outdated, dating back to the era of Node.js 0.4. This lack of up-to-date information inspired me to create both a talk and content around building cross-OS compatible CLIs using Node.js.

Enter Howitzer

Howitzer is a simple yet powerful CLI tool that leverages the GPT API to provide users with command-line instructions based on natural language queries. Here's a quick demo of how it works:

$ how to get my system specs
To get your system specs, you can use the following command:

system_profiler SPHardwareDataType

Do you want to run this command? (Y/n)

The tool takes your query, sends it to the GPT API, and returns a relevant command. You can then choose to execute the command directly from the CLI. Check out the YT video below to see it in action.

Building the Tool: Challenges and Solutions

1. TypeScript Woes

One of the first challenges I faced was dealing with TypeScript. While TypeScript offers great type safety, it often requires a build step, which I wanted to avoid for simplicity. The solution? ES modules.

ES modules have been supported in Node.js since 2019 and offer a great alternative to TypeScript for simple projects. They allow you to use import statements and even provide some type safety through JSDoc comments.

2. Adding Terminal Functionality

To transform our simple Node.js script into a full-fledged CLI tool, we need to add some terminal functionality. This is where libraries like commander and inquirer come in handy.

Here's a simple example of how to use commander:

import { Command } from 'commander';

const program = new Command();

program
  .version('1.0.0')
  .description('An AI-powered CLI tool')
  .argument('<query>', 'The query to process')
  .action((query) => {
    // Process the query
  });

program.parse();

3. Making It a Proper CLI Tool

To make our tool installable and executable as a CLI command, we need to add a bin property to our package.json:

{
  "bin": {
    "how": "index.mjs"
  }
}

However, this leads us to our next challenge...

4. The Shebang Problem

When we try to run our newly installed CLI tool, we might encounter an error where the system tries to execute our JavaScript file as a bash script. The solution? Add a shebang at the top of our entry file:

#!/usr/bin/env node

// Rest of your code

This tells the system to use Node.js to execute the file.

5. Cross-OS Compatibility

To make our tool work across different operating systems, we need to consider the user's environment. We can use process.env.SHELL and process.platform to get information about the user's shell and OS:

import { platform } from 'os';

const userShell = process.env.SHELL;
const userOS = platform();

// Use this information in your GPT prompt

6. Relative Imports in Installed Packages

When our tool is installed globally, relative imports can break. To solve this, we can use import.meta.url along with the path module:

import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));

This allows us to reliably read files relative to our script, regardless of where it's installed.

Conclusion

Building a CLI tool in 2024 comes with its own set of challenges, but with the right approach, it's entirely doable. By leveraging ES modules, modern Node.js features, and a bit of creativity, we can create powerful tools that enhance our development workflows.

The key is to keep things simple, avoid unnecessary build steps, and always consider all of the devices your users might try to run the CLI from.