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
Foundational Commander.js concepts and program setup.
import { Command } from 'commander';
const program = new Command();
program
.name('my-cli')
.description('Description of your CLI tool')
.version('1.0.0');Always load version from package.json to keep it synchronized:
// index.ts or main CLI file
import { Command } from 'commander';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
// ESM - Get package.json path
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageJson = JSON.parse(
readFileSync(join(__dirname, '../package.json'), 'utf-8')
);
const program = new Command();
program
.name('my-cli')
.description('Description of your CLI tool')
.version(packageJson.version); // Load from package.jsonWith Bun (simpler import):
import { Command } from 'commander';
import packageJson from '../package.json' with { type: 'json' };
const program = new Command();
program
.name('my-cli')
.description('My CLI tool')
.version(packageJson.version); // Load from package.jsonCommonJS (if not using ESM):
const { Command } = require('commander');
const packageJson = require('../package.json');
const program = new Command();
program
.name('my-cli')
.description('My CLI tool')
.version(packageJson.version);Benefits:
npm version commands--version flag always accurateAlways use parseAsync() for async action handlers:
// Good - async support
await program.parseAsync(process.argv);
// Avoid - no async support
program.parse(process.argv);import { Command } from 'commander';
const program = new Command();
program
.name('my-tool')
.description('My CLI tool')
.version('1.0.0')
.option('-d, --debug', 'Enable debug mode')
.option('-c, --config <path>', 'Config file path')
.action((options) => {
console.log('Options:', options);
});
await program.parseAsync(process.argv);// Inside action handler - options passed as parameter
program
.option('-p, --port <number>', 'Port number', '3000')
.action((options) => {
console.log('Port:', options.port); // Camel-cased
});
// Outside action handler - use .opts()
await program.parseAsync(process.argv);
const options = program.opts();
console.log('Options:', options);Commander automatically generates help text:
my-cli --helpCustomize help:
program
.addHelpText('before', 'Custom text before help')
.addHelpText('after', 'Custom text after help');program.version('1.0.0');
// Enables --version flagCustom version flag:
program.version('1.0.0', '-v, --version', 'Display version');For testing, override default exit behavior:
program.exitOverride();
try {
await program.parseAsync(['node', 'test', '--invalid']);
} catch (err) {
// Handle error
}Access via process.env:
program
.option('-p, --port <number>', 'Port', process.env.PORT || '3000')
.action((options) => {
const port = parseInt(options.port);
});program
.usage('[options] <command>');program
.alias('i'); // npm install -> npm iprogram
.hook('preAction', (thisCommand, actionCommand) => {
console.log('Before action runs');
})
.hook('postAction', (thisCommand, actionCommand) => {
console.log('After action runs');
});import { Command } from 'commander';
const program = new Command();
program
.name('deploy')
.description('Deploy application to production')
.version('1.0.0')
.option('-e, --env <name>', 'Environment', 'production')
.option('-d, --dry-run', 'Run without making changes')
.option('-v, --verbose', 'Verbose output')
.hook('preAction', () => {
console.log('Starting deployment...');
})
.action(async (options) => {
console.log('Deploying to:', options.env);
if (options.dryRun) {
console.log('Dry run mode - no changes made');
return;
}
// Deployment logic
});
await program.parseAsync(process.argv);Install with Tessl CLI
npx tessl i pantheon-ai/commanderjs@0.1.1