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
71
89%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Complete Commander.js framework guidance for building robust command-line interfaces with proper argument parsing, subcommands, options, and TypeScript support.
Read individual reference files for detailed guidance:
Each reference file contains:
import { Command } from 'commander';
const program = new Command();
program
.name('my-cli')
.description('CLI tool description')
.version('1.0.0');
program
.command('build')
.description('Build the project')
.option('-o, --output <path>', 'Output directory')
.option('-w, --watch', 'Watch for changes')
.action((options) => {
console.log('Building with options:', options);
});
await program.parseAsync(process.argv);--help and invalid inputs to verify argument parsing and error handlingprogram
.argument('<source>', 'Source file')
.argument('[destination]', 'Destination file')
.option('-f, --force', 'Force overwrite')
.action((source, destination, options) => {
// implementation
});program
.command('init')
.description('Initialize project')
.action(() => { /* ... */ });
program
.command('deploy')
.description('Deploy application')
.option('-e, --env <name>', 'Environment')
.action((options) => { /* ... */ });// types/build-options.ts
export interface BuildOptions {
outDir: string;
minify: boolean;
watch: boolean;
}
// services/build-service.ts
import type { BuildOptions } from '../types/build-options';
export const buildProject = (options: BuildOptions): void => {
console.log('Building to:', options.outDir);
// All options available with type safety
};
// 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('-o, --out-dir <path>', 'Output directory', 'dist')
.option('-m, --minify', 'Minify output', false)
.option('-w, --watch', 'Watch mode', false)
.action((options: BuildOptions) => {
// Pass complete typed object to service
buildProject(options);
});
// index.ts
import { buildCommand } from './commands/build';
program.addCommand(buildCommand);✓ ALWAYS pass complete typed options objects to services (never individual properties)
✓ Define TypeScript interfaces for all options (e.g., BuildOptions, DeployOptions)
✓ Export commands as Command instances from subcommand modules
✓ Use .addCommand() to attach subcommands to parent Command
✓ Use parseAsync() for async action handlers
✓ Validate options in action handlers
✓ Provide clear descriptions for all commands/options
✓ Use TypeScript for type safety
✓ Handle errors gracefully with try/catch
✓ Use kebab-case for option names
✓ Provide sensible defaults for optional options
✓ Create barrel exports for commands (commands/index.ts)
✓ Test CLI with different argument combinations
✓ Use .exitOverride() for testing
✓ Document expected argument formats
✗ NEVER pass individual option properties to services (pass complete typed object)
✗ DON'T pass options piecemeal (e.g., service(opts.a, opts.b, opts.c))
✗ Don't use parse() with async handlers (use parseAsync())
✗ Don't ignore error handling in action handlers
✗ Don't use camelCase in CLI flags (use kebab-case)
✗ Don't forget to specify option argument types (<required> vs [optional])
✗ Don't mix positional arguments with options ambiguously
✗ Don't forget to call program.parse() or program.parseAsync()
✗ Don't use global state in action handlers
✗ Don't suppress built-in help text without good reason
✗ Don't forget to version your CLI
✗ Don't make all options required (use sensible defaults)
process.argv directly when Commander.js is availableconst url = process.argv[2] alongside Commander.js commands.program.argument('<url>', 'Target URL')..action() callback without handling errorsprogram.command('fetch').action(async (opts) => { await riskyOp(); })program.error(err.message) for Commander-formatted error output..parseAsync() without awaitawait program.parseAsync() for async actions; without await, the process exits before async actions complete.program.parseAsync(process.argv) without await.await program.parseAsync(process.argv) inside an async IIFE or main function.--option values as positional arguments when options are not consumed before positional parsing.program.argument('<file>').option('--format <fmt>') with ambiguous ordering in usage examples.program.opts() to read option values inside a subcommandprogram.opts() in a subcommand returns the parent options, not the subcommand options.program.opts().verbose inside a program.command('deploy').action().command.opts() (the action's first argument when using .action((opts) =>)) to access subcommand-specific options.