Ai-setup adding-a-command
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 asks to add files to src/commands/. Do NOT use for modifying existing commands or refactoring command logic.
install
source · Clone the upstream repo
git clone https://github.com/caliber-ai-org/ai-setup
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/caliber-ai-org/ai-setup "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.agents/skills/adding-a-command" ~/.claude/skills/caliber-ai-org-ai-setup-adding-a-command && rm -rf "$T"
manifest:
.agents/skills/adding-a-command/SKILL.mdsource content
Adding a Command
Critical
- Every command must be wrapped with
— this enables telemetry. Without it, the command will not report usage.tracked() - Commands are registered in
, not auto-discovered. You must manually import and attach each command.src/cli.ts - Use Commander.js patterns exactly —
for flags,.option()
for positional args,.argument()
for the handler..action() - Always provide a
— it appears in.description()
.caliber --help
Instructions
-
Create the command file in
.src/commands/{{command-name}}.ts- Signature:
export async function {{commandName}}(options: OptionType, ...args: any[]) { ... } - Use async/await for all async operations (DB, LLM, file I/O).
- Import
fromtracked
:src/lib/hooks.tsimport { tracked } from '../lib/hooks.js'; - Verify the command exports the handler function before proceeding.
- Signature:
-
Define the OptionType interface (if needed).
- Place above the handler function in the same file.
- Example from
:score.tsinterface ScoreOptions { json?: boolean; verbose?: boolean; } - Verify the interface matches all
calls in Step 3..option()
-
Register in
.src/cli.ts- Import:
import { {{commandName}} } from './commands/{{command-name}}.js'; - Attach to program:
program.command('{{cmd-name}}').description('...').option('--flag', '...').action(tracked({{commandName}})); - Use
for optional flags,.option()
for required positional args..argument() - Verify the import path includes
extension (ESM)..js
- Import:
-
Add tests in
.src/commands/__tests__/{{command-name}}.test.ts- Use Vitest +
for LLM, file I/O, and hooks.vi.mock() - Test both success and error paths.
- Verify tests pass:
.npm run test -- {{command-name}}.test.ts
- Use Vitest +
-
Validate telemetry.
- Confirm
wraps the action handler.tracked() - Verify the command fires an event in PostHog.
- Confirm
Examples
User says: "Add a 'debug' command that prints the fingerprint JSON."
Actions taken:
- Create
:src/commands/debug.ts
import { tracked } from '../lib/hooks.js'; import { collectFingerprint } from '../fingerprint/index.js'; export async function debug(options: { verbose?: boolean }) { const fp = await collectFingerprint(); console.log(JSON.stringify(fp, null, 2)); }
- Register in
:src/cli.ts
import { debug } from './commands/debug.js'; program .command('debug') .description('Print fingerprint JSON') .option('--verbose', 'Include timing info') .action(tracked(debug));
- Test in
:src/commands/__tests__/debug.test.ts
vi.mock('../fingerprint/index.js'); it('should print fingerprint', async () => { const consoleSpy = vi.spyOn(console, 'log'); await debug({ verbose: false }); expect(consoleSpy).toHaveBeenCalled(); });
Result: Running
caliber debug prints the fingerprint and logs a telemetry event.
Common Issues
- "tracked is not defined" — Verify import:
withimport { tracked } from '../lib/hooks.js';
extension..js - "Command not found" — Check
for the import andsrc/cli.ts
attachment..action(tracked(...)) - "options is undefined" — Ensure the handler signature is
and the Commander action passes options as the first parameter.async function(options: Type, ...args: any[]) - "PostHog event not firing" — Confirm
wraps the action handler; without it, events won't emit.tracked() - ESM import errors — Always include
extensions in import paths (e.g.,.js
not../lib/hooks.js
).../lib/hooks