CtrlK
BlogDocsLog inGet started
Tessl Logo

pantheon-ai/commanderjs

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

Overview
Skills
Evals
Files

core-basics.mdreferences/

Core Basics

Foundational Commander.js concepts and program setup.

Program Initialization

Basic Setup

import { Command } from 'commander';

const program = new Command();

program
  .name('my-cli')
  .description('Description of your CLI tool')
  .version('1.0.0');

Loading Version from package.json (RECOMMENDED)

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.json

With 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.json

CommonJS (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:

  • Single source of truth for version
  • No manual version updates in multiple files
  • Automatic sync with npm version commands
  • --version flag always accurate

Parsing Arguments

Always use parseAsync() for async action handlers:

// Good - async support
await program.parseAsync(process.argv);

// Avoid - no async support
program.parse(process.argv);

Basic Structure

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);

Accessing Parsed Options

// 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);

Help Text

Commander automatically generates help text:

my-cli --help

Customize help:

program
  .addHelpText('before', 'Custom text before help')
  .addHelpText('after', 'Custom text after help');

Version Information

program.version('1.0.0');
// Enables --version flag

Custom version flag:

program.version('1.0.0', '-v, --version', 'Display version');

Exit Handling

For testing, override default exit behavior:

program.exitOverride();

try {
  await program.parseAsync(['node', 'test', '--invalid']);
} catch (err) {
  // Handle error
}

Environment Variables

Access via process.env:

program
  .option('-p, --port <number>', 'Port', process.env.PORT || '3000')
  .action((options) => {
    const port = parseInt(options.port);
  });

Usage Pattern

program
  .usage('[options] <command>');

Aliases

program
  .alias('i'); // npm install -> npm i

Hook Lifecycle

program
  .hook('preAction', (thisCommand, actionCommand) => {
    console.log('Before action runs');
  })
  .hook('postAction', (thisCommand, actionCommand) => {
    console.log('After action runs');
  });

Complete Example

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

references

actions-handlers.md

commands-structure.md

core-basics.md

options-flags.md

practices-patterns.md

typescript-setup.md

SKILL.md

tile.json