Complete Commander.js CLI framework guidance covering command structure, options, arguments, subcommands, action handlers, version management, and TypeScript integration. Use when: building CLI tools, parsing command-line arguments, implementing subcommands, handling options/flags, creating interactive CLIs, or migrating from other CLI frameworks. Keywords: Commander.js, CLI, command-line, arguments, options, flags, subcommands, action handlers, version, help text, TypeScript, yargs, meow, program, parseAsync, opts, args, variadic, required options, default values, custom help, error handling
Overall
score
99%
Does it follow best practices?
Validation for skill structure
Complete guide to structuring commands and subcommands in Commander.js.
Always pass options as typed objects to downstream services/modules.
program
.command('build')
.option('-e, --env <name>', 'Environment')
.action(({ env }) => {
// ❌ BAD: Type information lost, unclear what's passed
buildProject(env);
});// types/build-options.ts
export interface BuildOptions {
env: string;
minify: boolean;
outDir: string;
}
// services/build-service.ts
export const buildProject = (options: BuildOptions): void => {
console.log('Building with:', options);
// Service knows exactly what it receives
};
// commands/build.ts
import { Command } from 'commander';
import { buildProject } from '../services/build-service';
import type { BuildOptions } from '../types/build-options';
export const buildCommand = new Command('build')
.description('Build the project')
.option('-e, --env <name>', 'Environment', 'development')
.option('-m, --minify', 'Minify output', false)
.option('-o, --out-dir <path>', 'Output directory', './dist')
.action((options: BuildOptions) => {
// ✅ GOOD: Full typed object passed to service
buildProject(options);
});program
.command('build')
.description('Build the project')
.action(() => {
console.log('Building...');
});program
.command('init')
.description('Initialize project')
.action(() => {
console.log('Initializing...');
});
program
.command('build')
.description('Build project')
.action(() => {
console.log('Building...');
});
program
.command('deploy')
.description('Deploy project')
.action(() => {
console.log('Deploying...');
});
await program.parseAsync(process.argv);program
.command('deploy')
.description('Deploy to environment')
.argument('<environment>', 'Target environment')
.action((environment) => {
console.log('Deploying to:', environment);
});
// Usage: my-cli deploy productionprogram
.command('copy')
.description('Copy files')
.argument('<source>', 'Source file')
.argument('<destination>', 'Destination file')
.action((source, destination) => {
console.log(`Copying ${source} to ${destination}`);
});
// Usage: my-cli copy src.txt dst.txtprogram
.command('log')
.description('Show logs')
.argument('[lines]', 'Number of lines', '10')
.action((lines) => {
console.log(`Showing ${lines} lines`);
});
// Usage: my-cli log
// or: my-cli log 50program
.command('add')
.description('Add files')
.argument('<files...>', 'Files to add')
.action((files) => {
console.log('Adding files:', files);
});
// Usage: my-cli add file1.js file2.js file3.jsprogram
.command('build')
.description('Build project')
.option('-e, --env <name>', 'Environment', 'development')
.option('-w, --watch', 'Watch mode')
.option('-m, --minify', 'Minify output')
.action((options) => {
console.log('Building with options:', options);
});
// Usage: my-cli build --env production --minifyCreate subcommands as exported Command instances, with typed options passed to services:
// types/build-options.ts
export interface BuildOptions {
env: string;
minify: boolean;
outDir: string;
}
// services/build-service.ts
import type { BuildOptions } from '../types/build-options';
export const buildProject = (options: BuildOptions): void => {
console.log('Building with:', options);
// Implementation receives fully typed options
};
// commands/build.ts
import { Command } from 'commander';
import { buildProject } from '../services/build-service';
import type { BuildOptions } from '../types/build-options';
export const buildCommand = new Command('build')
.description('Build project')
.option('-e, --env <name>', 'Environment', 'development')
.option('-m, --minify', 'Minify output', false)
.option('-o, --out-dir <path>', 'Output directory', './dist')
.action((options: BuildOptions) => {
// Pass complete typed options object to service
buildProject(options);
});
// types/deploy-options.ts
export interface DeployOptions {
branch: string;
force: boolean;
}
// services/deploy-service.ts
import type { DeployOptions } from '../types/deploy-options';
export const deployApp = (environment: string, options: DeployOptions): void => {
console.log(`Deploying ${options.branch} to ${environment}`);
// Service receives environment + typed options
};
// commands/deploy.ts
import { Command } from 'commander';
import { deployApp } from '../services/deploy-service';
import type { DeployOptions } from '../types/deploy-options';
export const deployCommand = new Command('deploy')
.description('Deploy application')
.argument('<environment>', 'Target environment')
.option('-b, --branch <name>', 'Branch', 'main')
.option('-f, --force', 'Force deployment', false)
.action((environment: string, options: DeployOptions) => {
// Pass all arguments + typed options to service
deployApp(environment, options);
});
// index.ts
import { Command } from 'commander';
import { buildCommand } from './commands/build';
import { deployCommand } from './commands/deploy';
const program = new Command();
program
.name('my-cli')
.version('1.0.0')
.addCommand(buildCommand)
.addCommand(deployCommand);
await program.parseAsync(process.argv);src/
├── types/
│ ├── build-options.ts # BuildOptions interface
│ ├── deploy-options.ts # DeployOptions interface
│ └── index.ts # Barrel exports
├── services/
│ ├── build-service.ts # buildProject(options: BuildOptions)
│ ├── deploy-service.ts # deployApp(env, options: DeployOptions)
│ └── index.ts # Barrel exports
├── commands/
│ ├── build.ts # Export buildCommand
│ ├── deploy.ts # Export deployCommand
│ └── index.ts # Barrel exports
└── index.ts # Main program// commands/index.ts
export { buildCommand } from './build';
export { deployCommand } from './deploy';
export { testCommand } from './test';
// index.ts
import { Command } from 'commander';
import { buildCommand, deployCommand, testCommand } from './commands';
const program = new Command();
program
.name('my-cli')
.version('1.0.0')
.addCommand(buildCommand)
.addCommand(deployCommand)
.addCommand(testCommand);
await program.parseAsync(process.argv);Subcommands follow the same pattern: types → services → commands:
// types/migrate-options.ts
export interface MigrateOptions {
rollback: boolean;
steps?: number;
}
// types/seed-options.ts
export interface SeedOptions {
clear: boolean;
}
// services/database-service.ts
import type { MigrateOptions } from '../types/migrate-options';
import type { SeedOptions } from '../types/seed-options';
export const runMigrations = (options: MigrateOptions): void => {
if (options.rollback) {
console.log('Rolling back migration');
} else {
console.log('Running migrations');
}
};
export const seedDatabase = (env: string, options: SeedOptions): void => {
console.log('Seeding database for:', env);
if (options.clear) {
console.log('Clearing existing data first');
}
};
// commands/db/migrate.ts
import { Command } from 'commander';
import { runMigrations } from '../../services/database-service';
import type { MigrateOptions } from '../../types/migrate-options';
export const migrateCommand = new Command('migrate')
.description('Run database migrations')
.option('--rollback', 'Rollback last migration', false)
.option('--steps <number>', 'Number of migrations')
.action((options: MigrateOptions) => {
runMigrations(options);
});
// commands/db/seed.ts
import { Command } from 'commander';
import { seedDatabase } from '../../services/database-service';
import type { SeedOptions } from '../../types/seed-options';
export const seedCommand = new Command('seed')
.description('Seed database')
.argument('[env]', 'Environment', 'development')
.option('--clear', 'Clear existing data', false)
.action((env: string, options: SeedOptions) => {
seedDatabase(env, options);
});
// commands/db/index.ts
import { Command } from 'commander';
import { migrateCommand } from './migrate';
import { seedCommand } from './seed';
export const dbCommand = new Command('db')
.description('Database operations')
.addCommand(migrateCommand)
.addCommand(seedCommand);
// index.ts
import { Command } from 'commander';
import { dbCommand } from './commands/db';
const program = new Command();
program
.name('my-cli')
.version('1.0.0')
.addCommand(dbCommand);
await program.parseAsync(process.argv);
// Usage: my-cli db migrate --rollback
// my-cli db seed production --clearconst db = program.command('db');
db.description('Database commands');
db.command('migrate')
.description('Run migrations')
.action(() => {
console.log('Running migrations...');
});
db.command('seed')
.description('Seed database')
.action(() => {
console.log('Seeding database...');
});
// Usage: my-cli db migrate
// my-cli db seed// commands/docker/container/start.ts
import { Command } from 'commander';
export const startCommand = new Command('start')
.description('Start container')
.argument('<name>', 'Container name')
.action((name) => {
console.log('Starting container:', name);
});
// commands/docker/container/stop.ts
import { Command } from 'commander';
export const stopCommand = new Command('stop')
.description('Stop container')
.argument('<name>', 'Container name')
.action((name) => {
console.log('Stopping container:', name);
});
// commands/docker/container/index.ts
import { Command } from 'commander';
import { startCommand } from './start';
import { stopCommand } from './stop';
export const containerCommand = new Command('container')
.description('Container operations')
.addCommand(startCommand)
.addCommand(stopCommand);
// commands/docker/index.ts
import { Command } from 'commander';
import { containerCommand } from './container';
export const dockerCommand = new Command('docker')
.description('Docker operations')
.addCommand(containerCommand);
// index.ts
import { Command } from 'commander';
import { dockerCommand } from './commands/docker';
const program = new Command();
program
.name('my-cli')
.version('1.0.0')
.addCommand(dockerCommand);
await program.parseAsync(process.argv);
// Usage: my-cli docker container start my-app
// my-cli docker container stop my-appexport const installCommand = new Command('install')
.alias('i')
.description('Install dependencies')
.action(() => {
console.log('Installing...');
});
// Usage: my-cli install
// or: my-cli iexport const serveCommand = new Command('serve')
.description('Start server')
.action(() => {
console.log('Starting server...');
});
program.addCommand(serveCommand, { isDefault: true });
// Usage: my-cli
// (runs serve command by default)export const debugCommand = new Command('debug')
.description('Debug mode')
.hideCommand()
.action(() => {
console.log('Debug mode enabled');
});
// Hidden from help textexport const deployCommand = new Command('deploy')
.description('Deploy application')
.argument('<environment>', 'Target environment')
.option('-d, --dry-run', 'Dry run')
.addHelpText('after', `
Examples:
$ my-cli deploy production
$ my-cli deploy staging --dry-run
`);// commands/build.ts
import { Command } from 'commander';
interface BuildOptions {
env: string;
minify: boolean;
watch: boolean;
}
export const buildCommand = new Command('build')
.description('Build project')
.option('-e, --env <name>', 'Environment', 'development')
.option('-m, --minify', 'Minify output', false)
.option('-w, --watch', 'Watch mode', false)
.action((options: BuildOptions) => {
console.log('Building with options:', options);
});// commands/build.ts
import { Command } from 'commander';
export const buildCommand = new Command('build')
.description('Build project')
.option('-e, --env <name>', 'Environment', 'development')
.option('-w, --watch', 'Watch mode')
.action((options) => {
console.log('Building for:', options.env);
});
// commands/deploy.ts
import { Command } from 'commander';
export const deployCommand = new Command('deploy')
.description('Deploy application')
.argument('<environment>', 'Target environment')
.option('-b, --branch <name>', 'Git branch', 'main')
.option('-d, --dry-run', 'Dry run')
.action((environment, options) => {
console.log(`Deploying ${options.branch} to ${environment}`);
if (options.dryRun) {
console.log('Dry run - no changes made');
}
});
// commands/db/migrate.ts
import { Command } from 'commander';
export const migrateCommand = new Command('migrate')
.description('Run migrations')
.option('--rollback', 'Rollback last migration')
.action((options) => {
if (options.rollback) {
console.log('Rolling back migration');
} else {
console.log('Running migrations');
}
});
// commands/db/seed.ts
import { Command } from 'commander';
export const seedCommand = new Command('seed')
.description('Seed database')
.argument('[env]', 'Environment', 'development')
.action((env) => {
console.log('Seeding database for:', env);
});
// commands/db/index.ts
import { Command } from 'commander';
import { migrateCommand } from './migrate';
import { seedCommand } from './seed';
export const dbCommand = new Command('db')
.description('Database operations')
.addCommand(migrateCommand)
.addCommand(seedCommand);
// commands/index.ts
export { buildCommand } from './build';
export { deployCommand } from './deploy';
export { dbCommand } from './db';
// index.ts
import { Command } from 'commander';
import { buildCommand, deployCommand, dbCommand } from './commands';
const program = new Command();
program
.name('project-cli')
.description('Project management CLI')
.version('1.0.0')
.addCommand(buildCommand)
.addCommand(deployCommand)
.addCommand(dbCommand);
await program.parseAsync(process.argv);src/
├── commands/
│ ├── build.ts # buildCommand export
│ ├── deploy.ts # deployCommand export
│ ├── db/
│ │ ├── migrate.ts # migrateCommand export
│ │ ├── seed.ts # seedCommand export
│ │ └── index.ts # dbCommand export
│ └── index.ts # Barrel exports all commands
└── index.ts # Main program + addCommand().addCommand() to attach to parent Commandcommands/index.tsInstall with Tessl CLI
npx tessl i pantheon-ai/commanderjs@0.1.0