From agent-almanac
Scaffolds a new CLI command using Commander.js with standardized options, three output modes (human-readable, quiet, JSON), and optional ceremony variant. Covers naming, option design, context patterns, error handling, and integration testing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/agent-almanac:scaffold-cli-commandThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Add a new command to a Commander.js CLI application with consistent option handling, three output modes, and integration tests.
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 skillnpx claudepluginhub pjt222/agent-almanacDesigns CLI surfaces including args/flags/subcommands/help/output/errors/config for new tools. Audits existing CLIs for consistency, composability, and agent ergonomics.
Guides the design, implementation, review, and testing of CLI tools including flags, config precedence, terminal UX, error handling, and documentation.
Builds CLI tools with argument parsing, interactive prompts, progress bars, spinners, and shell completions using commander, click, typer, or cobra.