You are working on the CLI shell — the entry point that wires Commander subcommands, prints the welcome screen, warns about missing Claude hooks, dispatches to handlers, and translates CliError into a clean exit. Also the public programmatic surface re-exported from src/index.js.
Domain purpose
This layer is what a user actually invokes (aspens …) and what programmatic consumers import. It owns argument parsing, top-level error handling, and the welcome UX. All real work lives in src/commands/*.js — the shell only routes.
Business rules / invariants
- Handlers must throw
CliError, never callprocess.exit(). The top-level handler inbin/cli.js:250catches it, printsError: <message>(unlesslogged: true) in red, and exits witherr.exitCode(default 1). PlainErrorfalls through to the same printer but always exits 1. logged: truemeans "I already printed a user-friendly message" — top level then exits silently with the given code. Use it when the handler rendered a clackoutroor multi-line failure already.checkMissingHooks(repoPath)runs beforedoc sync,add, andcustomize— warns (does not throw) when.claude/skills/exists but.claude/hooks/skill-activation-prompt.shor.claude/skills/skill-rules.jsonis absent. Skipped entirely when.claude/skills/is missing (nothing to activate).- No-command invocation shows
showWelcome()— listing essential commands, generate/sync, Claude add-ons, utilities, options, typical workflow, and target notes. Adding a new subcommand requires updating this screen too. - Template counts in the welcome are filesystem-derived —
countTemplates(subdir)readssrc/templates/{agents,commands,hooks}and filters dotfiles; returns'?'on read failure (never throws). - Version comes from
package.jsonat runtime viareadFileSync; falls back to'0.0.0'silently if parse/read fails. Do not hardcode. - Numeric option parsers throw
InvalidArgumentError(Commander-native) —parsePositiveIntrejects ≤0/NaN;parseCommitsadditionally caps at 50. - Signal handlers exit with conventional codes — SIGINT→130, SIGTERM→143. Used to clean up spawned
claude -p/codex execchildren.
Non-obvious behaviors
- Action wrappers chain
checkMissingHooksbefore the handler fordoc sync,add,customize— done inline via arrow(args, options) => { checkMissingHooks(resolve(path)); return handler(...) }. Don't move this into the handler — the warning should fire even if the handler later fails or short-circuits. program.parseAsync()is required (not.parse()) — handlers are async;.catch()on the returned promise is the only place plain errors are surfaced.src/index.jsis the public programmatic API — only re-exportsscanRepo,runClaude,loadPrompt,parseFileOutput,writeSkillFiles,buildContext,buildBaseContext,buildDomainContext,analyzeImpact. Adding/removing a re-export is a breaking change for embedders; treat it as such.
Critical files
bin/cli.js— Commander setup, option parsers, welcome screen, signal handlers, top-levelCliErrorcatch.src/lib/errors.js—CliErrorclass withexitCodeandloggedoptions (plus optionalcause).src/index.js— Stable programmatic surface for library consumers.
Critical Rules
- New subcommand → register on
program(or thedocsubgroup) and add it toshowWelcome()so users discover it. - Never swallow a handler error in the action wrapper — let it bubble to
program.parseAsync().catch(). - When a handler renders its own failure UX (clack/picocolors), throw
new CliError(msg, { logged: true, exitCode })so the top level does not double-print.
Last Updated: 2026-05-11


