Use Cases

How to Develop CLI Tools with Claude Code

Learn how to develop CLI tools using Claude Code. Includes practical code examples and step-by-step guidance.

Accelerating CLI Tool Development With Claude Code

When you want to build your own CLI tool, Claude Code is the best partner you can have. From argument parsing and subcommand design to interactive I/O, all you need to do is describe the tool you want.

Initial Project Setup

> Create a TypeScript CLI tool project.
> Parse arguments with commander, and configure ESLint and Prettier.
> Make sure I can run it with `npx ts-node src/index.ts`.

Argument Parsing and Subcommands

The basic structure of a CLI built with Commander.

#!/usr/bin/env node
import { Command } from "commander";
import { version } from "../package.json";

const program = new Command();

program
  .name("mytool")
  .description("A project management CLI tool")
  .version(version);

program
  .command("init")
  .description("Initialize a project")
  .option("-t, --template <name>", "Template name", "default")
  .option("-d, --dir <path>", "Target directory", ".")
  .action(async (options) => {
    console.log(`Initializing with template "${options.template}"...`);
    await initProject(options.template, options.dir);
    console.log("Done!");
  });

program
  .command("generate <type> <name>")
  .alias("g")
  .description("Generate a file (component, hook, page)")
  .option("--dry-run", "Preview without actually creating files")
  .action(async (type, name, options) => {
    if (options.dryRun) {
      console.log(`[dry-run] would generate ${type} "${name}"`);
      return;
    }
    await generateFile(type, name);
  });

program
  .command("check")
  .description("Check the project's health")
  .action(async () => {
    await runHealthCheck();
  });

program.parse();

Interactive Input

An interactive prompt implementation using the Inquirer library.

import inquirer from "inquirer";
import chalk from "chalk";

interface ProjectConfig {
  name: string;
  framework: string;
  features: string[];
  packageManager: string;
}

async function interactiveInit(): Promise<ProjectConfig> {
  const answers = await inquirer.prompt([
    {
      type: "input",
      name: "name",
      message: "Project name:",
      validate: (input: string) =>
        /^[a-z0-9-]+$/.test(input) || "Only lowercase letters, digits, and hyphens are allowed",
    },
    {
      type: "list",
      name: "framework",
      message: "Framework:",
      choices: ["React", "Next.js", "Astro", "Vue"],
    },
    {
      type: "checkbox",
      name: "features",
      message: "Additional features:",
      choices: [
        { name: "TypeScript", checked: true },
        { name: "ESLint", checked: true },
        { name: "Prettier", checked: true },
        { name: "Testing (Vitest)" },
        { name: "CI/CD (GitHub Actions)" },
      ],
    },
    {
      type: "list",
      name: "packageManager",
      message: "Package manager:",
      choices: ["npm", "pnpm", "yarn"],
    },
  ]);

  console.log(chalk.green("\nConfiguration:"));
  console.log(chalk.cyan(`  Project name: ${answers.name}`));
  console.log(chalk.cyan(`  Framework: ${answers.framework}`));
  console.log(chalk.cyan(`  Features: ${answers.features.join(", ")}`));

  return answers;
}

Progress Bars and Spinners

Visually display the progress of an operation.

import ora from "ora";
import cliProgress from "cli-progress";

async function processFiles(files: string[]) {
  const bar = new cliProgress.SingleBar({
    format: "Processing |{bar}| {percentage}% | {value}/{total} files",
    barCompleteChar: "█",
    barIncompleteChar: "░",
  });

  bar.start(files.length, 0);

  for (const file of files) {
    await processFile(file);
    bar.increment();
  }

  bar.stop();
  console.log(chalk.green("All files processed successfully!"));
}

async function installDependencies(packages: string[]) {
  const spinner = ora("Installing dependencies...").start();

  try {
    await execAsync(`npm install ${packages.join(" ")}`);
    spinner.succeed("Dependencies installed");
  } catch (error) {
    spinner.fail("Installation failed");
    throw error;
  }
}

Implementing Tests

You can also ask Claude Code to write tests for your CLI tool.

import { describe, it, expect } from "vitest";
import { execSync } from "child_process";

describe("mytool CLI", () => {
  it("prints the version", () => {
    const output = execSync("npx ts-node src/index.ts --version").toString();
    expect(output.trim()).toMatch(/^\d+\.\d+\.\d+$/);
  });

  it("prints help", () => {
    const output = execSync("npx ts-node src/index.ts --help").toString();
    expect(output).toContain("A project management CLI tool");
    expect(output).toContain("init");
    expect(output).toContain("generate");
  });

  it("errors on unknown commands", () => {
    expect(() => {
      execSync("npx ts-node src/index.ts unknown 2>&1");
    }).toThrow();
  });
});

For how to publish as an npm package, see publishing npm packages. For Claude Code basics, see the getting started guide, and for productivity boosters, see 10 productivity tips that 3x your output.

Summary

With Claude Code, you can develop a CLI tool that covers argument parsing, interactive input, progress display, and tests in a short time. Just describe the commands you want in natural language, and you’ll have a working tool right away.

For details, see the official Claude Code documentation.

#Claude Code #CLI #Node.js #Commander #development tools

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

Free PDF: Claude Code Cheatsheet in 5 Minutes

Key commands, shortcuts, and prompt examples on a single printable page.

Download PDF
M

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.