agentleFS
pantheon-orgpantheon-org/tekhne/skills/development/commanderjs/AGENTS.md

tekhne / commanderjs

AGENTS.md10 starsChanged 39 days agoMITView on GitHub

Complete reference for building command-line interfaces with Commander.js. Always export Command instances from modules and use .addCommand(): Always pass typed option objects from commands to service layer: Why: Type safety prevents errors, documents API contracts, enables refactoring.

The file

We split this AGENTS.md into the rules it gives an agent

8 rules. About 0 are common, 6 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Always export Command instances from modules and use .addCommand():

    line 9
  2. Distinctive:

    Always pass typed option objects from commands to service layer:

    line 38
  3. Distinctive:

    Export Command instances: - Never use inline command definitions for large CLIs

    line 194
  4. Distinctive:

    One command per file: - Follow single function per module pattern

    line 197
  5. Very specific:

    Use parseAsync(): - Always use async parsing for async handlers

    line 198
  6. Distinctive:

    Handle errors: - Use try/catch in action handlers

    line 200
  7. Distinctive:

    Validate inputs: - Check arguments and options in handlers

    line 201
  8. Distinctive:

    Use kebab-case: - All CLI flags should use kebab-case

    line 202

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

Commander.js Reference Guide

Complete reference for building command-line interfaces with Commander.js.

Core Patterns

Modular Command Structure (Recommended)

Always export Command instances from modules and use .addCommand():

// commands/build.ts
import { Command } from 'commander';

export const buildCommand = new Command('build')
  .description('Build project')
  .option('-e, --env <name>', 'Environment', 'development')
  .action((options) => {
    console.log('Building:', options);
  });

// index.ts
import { Command } from 'commander';
import { buildCommand } from './commands/build';
import packageJson from './package.json' with { type: 'json' };

const program = new Command();
program
  .name('my-cli')
  .version(packageJson.version)
  .addCommand(buildCommand);

await program.parseAsync(process.argv);

Typed Options Pattern (REQUIRED)

Always pass typed option objects from commands to service layer:

// types/build-options.ts
export type BuildOptions = {
  env: string;
  output: string;
  sourcemap: boolean;
};

// services/build-service.ts
export const buildProject = (options: BuildOptions): void => {
  console.log('Building with:', 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('-o, --output <dir>', 'Output directory', 'dist')
  .option('--sourcemap', 'Generate sourcemaps', false)
  .action((options: BuildOptions) => {
    buildProject(options); // Type-safe!
  });

Why: Type safety prevents errors, documents API contracts, enables refactoring.

Subcommands with Barrel Exports

// commands/db/migrate.ts
export const migrateCommand = new Command('migrate')
  .description('Run migrations')
  .action(() => { /* ... */ });

// commands/db/seed.ts
export const seedCommand = new Command('seed')
  .description('Seed database')
  .action(() => { /* ... */ });

// 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 { dbCommand } from './commands/db';
program.addCommand(dbCommand);

Essential Patterns

Program Setup

import { Command } from 'commander';
import packageJson from './package.json' with { type: 'json' };

const program = new Command();

program
  .name('my-cli')
  .description('CLI tool')
  .version(packageJson.version); // Load from package.json

await program.parseAsync(process.argv);

Options

program
  .option('-p, --port <number>', 'Port', '3000')
  .option('-f, --force', 'Force operation')
  .requiredOption('-c, --config <path>', 'Config file');

Arguments

program
  .argument('<source>', 'Source file')
  .argument('[destination]', 'Destination file')
  .argument('<files...>', 'Files to process');

Action Handlers (Async)

program
  .command('deploy')
  .action(async (options) => {
    try {
      await deploy(options);
    } catch (error) {
      console.error('Deployment failed:', error.message);
      process.exit(1);
    }
  });

await program.parseAsync(process.argv); // Must use parseAsync

TypeScript Types with Service Layer

// types/build-options.ts
export type BuildOptions = {
  env: string;
  minify: boolean;
};

// services/build-service.ts
export const buildProject = (options: BuildOptions): void => {
  console.log('Building:', options);
};

// commands/build.ts
import type { BuildOptions } from '../types/build-options';
import { buildProject } from '../services/build-service';

export const buildCommand = new Command('build')
  .option('-e, --env <name>', 'Environment', 'development')
  .option('-m, --minify', 'Minify', false)
  .action((options: BuildOptions) => {
    buildProject(options); // Pass all options to service
  });

Directory Structure

src/
├── commands/
│   ├── build.ts          # Export buildCommand
│   ├── deploy.ts         # Export deployCommand
│   ├── db/
│   │   ├── migrate.ts    # Export migrateCommand
│   │   ├── seed.ts       # Export seedCommand
│   │   └── index.ts      # Export dbCommand
│   └── index.ts          # Barrel exports
└── index.ts              # Main program

Key Rules

  1. Export Command instances - Never use inline command definitions for large CLIs
  2. Use .addCommand() - Attach subcommands using this method
  3. Create barrel exports - Export commands from commands/index.ts
  4. One command per file - Follow single function per module pattern
  5. Use parseAsync() - Always use async parsing for async handlers
  6. Type your options - Define TypeScript interfaces for options
  7. Handle errors - Use try/catch in action handlers
  8. Validate inputs - Check arguments and options in handlers
  9. Use kebab-case - All CLI flags should use kebab-case
  10. Provide defaults - Give sensible defaults for optional options

Common Anti-Patterns

❌ Inline subcommands (harder to test/maintain):

const db = program.command('db');
db.command('migrate').action(() => {});

✅ Modular subcommands:

export const migrateCommand = new Command('migrate').action(() => {});
export const dbCommand = new Command('db').addCommand(migrateCommand);

❌ Using parse() with async:

program.action(async () => {});
program.parse(process.argv); // Wrong

✅ Using parseAsync():

program.action(async () => {});
await program.parseAsync(process.argv); // Correct

❌ No error handling:

program.action(async () => {
  await deploy(); // What if this throws?
});

✅ Proper error handling:

program.action(async () => {
  try {
    await deploy();
  } catch (error) {
    console.error('Error:', error.message);
    process.exit(1);
  }
});

Testing Pattern

import { Command } from 'commander';

export const createProgram = (): Command => {
  const program = new Command();
  program.exitOverride(); // Throw instead of exit
  
  // Add commands
  
  return program;
};

// In tests
describe('CLI', () => {
  it('should parse options', async () => {
    const program = createProgram();
    await program.parseAsync(['node', 'test', '--port', '8080']);
    expect(program.opts().port).toBe('8080');
  });
});

Reference Files

For detailed documentation, see:

  • references/core-basics.md - Program setup, parsing, help
  • references/options-flags.md - Options, defaults, validation
  • references/commands-structure.md - Modular commands, subcommands
  • references/actions-handlers.md - Async handlers, error handling
  • references/typescript-setup.md - TypeScript integration, types
  • references/practices-patterns.md - Patterns, testing, best practices

Quick Reference

ConceptPatternExample
Programnew Command()const program = new Command()
CommandExport from moduleexport const cmd = new Command('name')
Attach.addCommand()program.addCommand(buildCommand)
Option.option().option('-p, --port <n>', 'Port', '3000')
Required.requiredOption().requiredOption('-c, --config <path>')
Argument.argument().argument('<source>', 'Source file')
Action.action().action(async (opts) => { /* ... */ })
ParseparseAsync()await program.parseAsync(process.argv)
TypeInterfaceaction((opts: BuildOptions) => {})

External Resources

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.