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.
Level up your Claude Code workflow
50 battle-tested prompt templates you can copy-paste into Claude Code right now.
Free PDF: Claude Code Cheatsheet in 5 Minutes
Key commands, shortcuts, and prompt examples on a single printable page.
About the Author
Masa
Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.
Related Posts
How to Supercharge Your Side Projects with Claude Code [With Examples]
How to Supercharge Your Side Projects with Claude Code [With Examples]. A practical guide with code examples.
How to Automate Refactoring with Claude Code
Learn how to automate refactoring using Claude Code. Includes practical code examples and step-by-step guidance.
Complete CORS Configuration Guide with Claude Code
A complete CORS configuration guide using Claude Code. Practical tips and code examples included.