From agent-almanac
Designs terminal output for CLI tools using chalk colors, Unicode glyphs, verbosity levels (human, verbose, quiet, JSON), and voice rules. For building reporter modules, standardizing output, or adding narrative styles.
npx claudepluginhub pjt222/agent-almanacThis skill is limited to using the following tools:
Design consistent, multi-level terminal output for a command-line tool.
Provides CLI design patterns for arguments/flags/subcommands and TUI patterns for frameworks in Go/Python/JS/Rust, interactions, and colors in terminal apps.
Designs, reviews, and improves CLI user interfaces: command structures, subcommands, flags, arguments, help text, and terminal output formatting. For new CLI tools or usability enhancements.
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.
Share bugs, ideas, or general feedback.
Design consistent, multi-level terminal output for a command-line tool.
Use chalk to create a named palette object:
Standard palette (transactional output):
let chalk;
try { chalk = (await import('chalk')).default; }
catch { chalk = new Proxy({}, { get: () => (s) => s }); }
// Status colors
const ok = chalk.green; // success
const fail = chalk.red; // errors
const warn = chalk.yellow; // warnings
const info = chalk.cyan; // identifiers, names
const dim = chalk.dim; // secondary info, paths
const bold = chalk.bold; // headers
Warm palette (ceremony/narrative output):
const C = {
flame: chalk.hex('#FF6B35'), // active elements, fire
amber: chalk.hex('#FFB347'), // arriving items, warm highlights
spark: chalk.hex('#FFF4E0'), // individual items (sparks/skills)
ember: chalk.hex('#8B4513'), // cold/dormant states
warm: chalk.hex('#D4A574'), // neutral warm text
dim: chalk.dim, // background, secondary
fail: chalk.red, // errors stay red (honest)
};
Palette design rules:
chalk.hex('#FF6B35'))Expected: A palette object with named entries and a no-color fallback.
On failure: If chalk is unavailable (piped output, CI), the Proxy fallback returns strings unchanged. Test with NO_COLOR=1 environment variable.
Select Unicode glyphs or ASCII characters for status communication:
ASCII (maximum compatibility):
+ created/installed (green)
- removed/deleted (red)
= skipped/unchanged (dim)
! error/warning (red)
Unicode (richer, needs UTF-8 terminal):
✦ item/skill/practice (spark)
◉ active/burning state
◎ cooling/embers state
○ cold/dormant state
◌ available/not installed
✗ failed item
✓ success (use sparingly — not all terminals render it well)
Selection criteria:
--ascii flag or NO_COLOR detectionExpected: A glyph set that communicates status at a glance without relying on color alone.
On failure: If a glyph renders as ? or a box in testing, replace with the ASCII equivalent. The +/-/=/! set works everywhere.
Every command should support four output levels:
| Level | Flag | Audience | Content |
|---|---|---|---|
| Default | (none) | Human at terminal | Formatted, colored, informative |
| Verbose | --verbose or --ceremonial | Human wanting detail | Per-item breakdown, arrival sequences |
| Quiet | --quiet | Scripts, CI | Minimal lines, status icons, no decoration |
| JSON | --json | Machine consumers | Structured, parseable, complete |
Implementation pattern:
function output(data, options) {
if (options.json) {
console.log(JSON.stringify(data, null, 2));
return;
}
if (options.quiet) {
for (const item of data.items) {
const icon = item.ok ? '+' : '!';
console.log(`${icon} ${item.id}`);
}
return;
}
// Default (or verbose) human output
printFormatted(data, { verbose: options.verbose });
}
JSON output rules:
Expected: Four clear output levels with consistent behavior across commands.
On failure: If verbose mode is too noisy, make it opt-in (--ceremonial) rather than a graduated verbosity level.
Define the tone and style that all output functions follow. This prevents inconsistency across commands.
Example voice rules (from the campfire reporter):
Voice rules for standard (non-ceremony) output:
Expected: A written set of 3-7 voice rules that output functions must follow.
On failure: If rules feel arbitrary, test them: write the same output with and without each rule. If removing a rule doesn't change the output quality, the rule isn't needed.
Organize output into a reporter module with focused functions:
// reporter.js — standard output
export function printResults(results) { ... }
export function printItemTable(items) { ... }
export function printDetections(detections) { ... }
export function printAudit(auditResults) { ... }
export function printDryRun() { ... }
export function warn(msg) { ... }
export function error(msg) { ... }
export { chalk };
Each function follows the same structure:
For ceremony output, create a separate module:
// campfire-reporter.js — warm narrative output
export function printArrival({ teamId, agents, results, ceremonial }) { ... }
export function printScatter({ teamId, agents, results }) { ... }
export function printTend(fires) { ... }
export function printCampfireList({ teams, state, reg }) { ... }
export function printFireSummary({ team, fireData, reg }) { ... }
export function printJson(data) { ... }
Expected: Reporter functions that are independently usable — each handles its own formatting without depending on caller state.
On failure: If functions grow beyond ~50 lines, extract helpers. A reporter function should be easy to review in isolation.
Verify output renders correctly in different contexts:
# With colors (interactive terminal)
node cli/index.js list --domains
# Without colors (piped)
node cli/index.js list --domains | cat
# With NO_COLOR environment variable
NO_COLOR=1 node cli/index.js list --domains
# JSON mode (parseable)
node cli/index.js campfire --json | jq .
# In CI (typically no TTY)
CI=true node cli/index.js audit
Check for:
jq . to verify)Expected: Output is correct in all five contexts.
On failure: If ANSI codes leak, ensure chalk respects NO_COLOR. If Unicode breaks, provide an ASCII fallback mode.
jq--json mode, output only valid JSON. A single stray line (like "DRY RUN") breaks JSON parsers. If the command must show both, separate them clearly or suppress the human text in JSON mode.Math.max(...items.map(i => i.id.length)) to compute padding dynamically.+, OK, ERR).--quiet mode, it adds noise. Gate ceremony output behind explicit flags.scaffold-cli-command — the commands that use this outputtest-cli-application — testing that output matches expectationsbuild-cli-plugin — plugins report results through this output system