From agent-almanac
Scaffolds new CLI commands using Commander.js with standard options, action handlers, three output modes (human-readable/quiet/JSON), optional ceremony variant, error handling, and integration tests. Use for extending existing CLIs or standardizing multi-command structure.
npx claudepluginhub pjt222/agent-almanacThis skill is limited to using the following tools:
Add a new command to a Commander.js CLI application with consistent option handling, three output modes, and integration tests.
Designs CLI surfaces including args/flags/subcommands/help/output/errors/config for new tools. Audits existing CLIs for consistency, composability, and agent ergonomics.
Builds fast CLI tools in Node.js, Python, Go: argument parsing, subcommands, interactive prompts, progress bars, shell completions.
Builds CLI tools with subcommands, flags, prompts, progress bars, shell completions using commander (Node.js), click/typer (Python), cobra (Go). Ensures fast startup, error handling, cross-platform support.
Share bugs, ideas, or general feedback.
Add a new command to a Commander.js CLI application with consistent option handling, three output modes, and integration tests.
gather, audit, sync)cli/index.js)<name> or [names...])Select a verb that communicates the command's action. Group commands into categories:
| Category | Verbs | Pattern |
|---|---|---|
| CRUD | install, uninstall, list, search | Operates on content |
| Lifecycle | init, sync, audit | Manages project state |
| Ceremony | gather, scatter, tend, campfire | Warm narrative output |
Naming conventions:
install-skill — let options specify what)<required> or [optional] or [variadic...]program
.command('gather <name>')
.description('Gather a team around the campfire')
Expected: A command name, description, and positional args defined.
On failure: If the verb overlaps with an existing command, either compose them (add an option to the existing command) or differentiate clearly in the description.
Every command should support a standard set of shared options plus command-specific ones.
Standard options (include as needed):
.option('-n, --dry-run', 'Preview without making changes')
.option('-q, --quiet', 'Suppress human-readable output')
.option('--json', 'Output as JSON')
.option('-f, --framework <id>', 'Target specific framework')
.option('-g, --global', 'Use global scope')
.option('--scope <scope>', 'Scope: project, workspace, global', 'project')
.option('--source <path>', 'Path to tool root directory')
Command-specific options — add only what the command needs:
.option('--ceremonial', 'Show each item arriving individually')
.option('--only <items>', 'Comma-separated subset to include')
.option('-y, --yes', 'Skip confirmation prompts')
Design rules:
-n) for frequently used options--dry-run) for clarityExpected: A complete option chain with both standard and custom options.
On failure: If too many options accumulate (>8), consider splitting into subcommands or grouping related options.
The action handler follows a consistent pattern:
.action(async (name, options) => {
// 1. Get shared context (registries, adapters, paths)
const ctx = getContext(options);
// 2. Resolve what to operate on
const items = resolveItems(ctx, name, options);
if (!items || items.length === 0) {
reporter.error('Nothing found.');
process.exit(1);
}
// 3. Preview if dry-run
if (options.dryRun) reporter.printDryRun();
// 4. Execute the operation
const results = await executeOperation(items, ctx, options);
// 5. Output results (3 modes)
if (options.json) {
console.log(JSON.stringify(results, null, 2));
} else if (options.quiet) {
reporter.printResults(results);
} else {
printHumanOutput(results, options);
}
})
The getContext() shared helper centralizes:
Expected: An action handler that follows the 5-step pattern: context → resolve → preview → execute → output.
On failure: If the command doesn't fit the resolve-then-execute pattern (e.g., it's purely informational like detect), simplify to: context → compute → output.
Every command should support three output modes:
Default (human-readable):
Installing 3 item(s) to Claude Code...
+ create-skill [claude-code] .claude/skills/create-skill
+ write-tests [claude-code] .claude/skills/write-tests
= commit-changes [claude-code] (skipped)
2 installed, 1 skipped
Quiet (--quiet):
Standard reporter output — concise lines with status icons (+, -, =, !), no ceremony, no decoration.
JSON (--json):
{
"command": "install",
"items": 3,
"installed": 2,
"skipped": 1,
"failed": 0
}
Implementation pattern:
if (options.json) {
console.log(JSON.stringify(data, null, 2));
return;
}
if (options.quiet) {
reporter.printResults(results);
return;
}
// Default: human-readable output
printHumanReadable(results, options);
Expected: All three modes produce useful output. JSON is parseable. Quiet is concise. Default is informative.
On failure: If the command has no meaningful JSON representation (e.g., detect), skip the JSON mode and document why.
For commands that benefit from warm, narrative output instead of transactional reporting:
if (options.json) {
ceremonyReporter.printJson(data);
} else if (options.quiet) {
reporter.printResults(results);
} else {
ceremonyReporter.printArrival({
teamId: name,
agents,
results: { installed, skipped, failed },
ceremonial: options.ceremonial || false,
});
}
Ceremony output follows voice rules:
See the design-cli-output skill for detailed terminal output patterns.
Expected: Ceremony output that follows all voice rules and produces warm, informative narratives.
On failure: If the ceremony output feels forced or doesn't add information beyond the standard output, skip it. Not every command needs a ceremony variant.
// Unknown item
if (!item) {
reporter.error(`Unknown: ${name}. Use 'tool list' to browse.`);
process.exit(1);
}
// Confirmation for destructive actions
if (!options.yes && !options.quiet && !options.dryRun) {
const answer = await askYesNo('Proceed?');
if (!answer) {
console.log(' Cancelled.');
return;
}
}
// State validation
if (!state.fires[name]) {
reporter.error(`Not active. Nothing to remove.`);
process.exit(1);
}
Error design principles:
process.exit(1) for unrecoverable errors--yes)Expected: All error paths produce helpful messages. Destructive operations require confirmation.
On failure: If confirmation prompts interfere with scripting, ensure --yes and --quiet both bypass them.
import { describe, it, after } from 'node:test';
import assert from 'node:assert/strict';
import { execSync } from 'child_process';
const CLI = 'node cli/index.js';
function run(args) {
return execSync(`${CLI} ${args}`, { encoding: 'utf8', timeout: 10000 });
}
describe('new-command', () => {
after(() => { /* cleanup created files/state */ });
it('dry-run shows preview', () => {
const out = run('new-command arg --dry-run');
assert.match(out, /DRY RUN/);
});
it('--json outputs valid JSON', () => {
const out = run('new-command arg --json');
const start = out.indexOf('{');
const data = JSON.parse(out.slice(start));
assert.equal(data.command, 'new-command');
});
it('rejects unknown input', () => {
assert.throws(() => run('new-command nonexistent'), /Unknown/);
});
});
See the test-cli-application skill for comprehensive CLI testing patterns.
Expected: At least 3 tests: dry-run, JSON output, error case. More for complex commands.
On failure: If execSync times out, increase the timeout or check for interactive prompts blocking the command.
--help--dry-run, --quiet, --json) work correctly--yes)--json even if the command seems interactive-only.--yes for destructive commands and ensure --quiet suppresses prompts.process.exit(1) for all errors. Tools that parse CLI output check exit codes first.--scope should have sensible defaults so users don't need to specify them every time.--quiet flag means "minimal output for machines." If ceremony text leaks into quiet mode, scripts will break on unexpected output.build-cli-plugin — build the adapter/plugin that commands operate ontest-cli-application — comprehensive CLI testing patterns beyond the basics in Step 7design-cli-output — terminal output design for all verbosity levelsinstall-almanac-content — example of a well-structured CLI command skill