Creates a new CLI command following the Commander.js pattern in src/commands/. Handles command registration in src/cli.ts, telemetry tracking via tracked() wrapper, and option parsing. Use when user says add command, new CLI command, create subcommand, or adds files to src/commands/. Do NOT use for modifying existing commands or fixing bugs in existing commands.
72
88%
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
export async function myCommand(options?: OptionType). Never use default exports..command() chain in src/cli.ts, wrapped with tracked() for telemetry.throw new Error('__exit__') to exit gracefully without printing the error message. Use chalk for user-facing messages.Create the command file at src/commands/{commandName}.ts with named async export.
export async function {commandName}Command(options?: { optionName?: optionType }) { ... }Handle errors consistently: Wrap error-prone operations in try/catch. Distinguish between user errors and system errors:
console.error(chalk.red('message')); throw new Error('__exit__');throw new Error('Detailed error message'); — this will print and exit with code 1..fail() before throwing.Import and register in src/cli.ts in the correct location:
import { {commandName}Command } from './commands/{commandName}.js';sources)..command('{kebab-name}').description('...').option(...).action(tracked('{kebab-name}', {commandName}Command))sources add): sources.command('add').description(...).action(tracked('sources:add', sourcesAddCommand))tracked('{command-name}', handler) for automatic telemetry.Define options (if needed):
.option() chains before .action(): .option('--flag', 'Description') or .option('--opt <value>', 'Description').option('--opt <value>', 'Description', parseFunction).action(tracked('name', (opts) => command(opts)))Verify before proceeding:
new Error('__exit__') for user errors.User says: "Add a command to show config status"
Actions taken:
Result: caliber status displays config status; caliber status --json outputs JSON.
Code example:
import chalk from 'chalk';
import { loadConfig } from '../llm/config.js';
export async function statusCommand(options?: { json?: boolean }) {
const config = loadConfig();
if (options?.json) {
console.log(JSON.stringify({ configured: !!config }, null, 2));
return;
}
console.log(chalk.bold('Status'));
console.log(` LLM: ${chalk.green(config?.provider || 'Not configured')}`);
}Registration in src/cli.ts:
import { statusCommand } from './commands/status.js';
program
.command('status')
.description('Show config status')
.option('--json', 'Output as JSON')
.action(tracked('status', statusCommand));User says: "Add a sources add subcommand"
Actions taken:
Result: caliber sources add ../lib adds a source.
Code example:
export async function sourcesAddCommand(sourcePath: string) {
if (!fs.existsSync(sourcePath)) {
console.log(chalk.red(`Path not found: ${sourcePath}`));
throw new Error('__exit__');
}
const existing = loadSourcesConfig(process.cwd());
existing.push({ type: 'repo', path: sourcePath });
writeSourcesConfig(process.cwd(), existing);
console.log(chalk.green(`Added ${sourcePath}`));
}Registration:
const sources = program.command('sources');
sources
.command('add')
.argument('<path>', 'Path to add')
.action(tracked('sources:add', sourcesAddCommand));User says: "Add init with --agent flag supporting comma-separated values"
Actions taken:
Result: caliber init --agent claude,cursor passes parsed array to handler.
Parser code:
function parseAgentOption(value: string) {
const agents = value.split(',').map(s => s.trim().toLowerCase());
if (agents.length === 0) {
console.error('Invalid agent');
process.exit(1);
}
return agents;
}
program.command('init')
.option('--agent <type>', 'Agents (comma-separated)', parseAgentOption)
.action(tracked('init', initCommand));Issue: "SyntaxError: The requested module does not provide an export named 'myCommand'"
export async function myCommand(...) (not export default).Issue: Command appears in help but crashes when run
tracked() or function import mismatch.tracked('command-name', handler).Issue: "Error: exit" appears in output for user errors
console.error(chalk.red('message')); throw new Error('__exit__'); for user-facing errors.Issue: --dry-run flag not recognized
.option() or wrong camelCase in interface..option('--dry-run', 'Description') and ensure options interface has dryRun?: boolean.Issue: Subcommand crashes but parent command works
program.command() instead of groupVar.command() for subcommands.const sources = program.command('sources'); sources.command('add')...Issue: Telemetry not appearing
tracked() or wrong command name..action(tracked('{kebab-case}', handler)) wraps handler. Use colon for subcommands like 'sources:add'.Issue: "Cannot find module" with relative imports
.ts extension in imports..js extension: import { x } from '../lib/file.js' (required for ESM).7950921
Also appears in
since Jul 28, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.