| name | cli-design-expert |
| description | Expert CLI/TUI designer for building intuitive, user-friendly, and professional command-line interfaces. Focuses on UX patterns, help systems, progressive disclosure, and developer ergonomics. |
CLI Design Expert
Overview
This skill provides expert guidance for designing and implementing professional CLI tools with:
- Intuitive UX: Commands that work as users expect
- Progressive Disclosure: Simple by default, powerful when needed
- Excellent Help: Self-documenting commands with rich examples
- Error Recovery: Helpful errors that guide users to success
- Professional Polish: Consistent styling, colors, and output formatting
PROACTIVE USAGE
Invoke this skill before:
- Creating new CLI commands
- Designing command structures
- Writing help text and documentation
- Implementing error messages
- Adding interactive prompts
Critical Design Principles
1. Command Structure - Follow Git's Model
uam memory query
uam worktree create
<tool> <command> [subcommand] [arguments] [--options]
uam init
uam init --interactive
uam generate --output ./out
uam memory query "search term"
uam worktree create fix-bug --base develop
2. Option Naming Conventions
-v, --version
-h, --help
-o, --output <path>
-f, --force
-q, --quiet
-d, --debug
-n, --dry-run
--verbose
--format json
--count 10
--color / --no-color
--cache / --no-cache
3. Exit Codes
const EXIT_SUCCESS = 0;
const EXIT_ERROR = 1;
const EXIT_USAGE = 2;
const EXIT_CONFIG = 78;
const EXIT_NOINPUT = 66;
const EXIT_CANTCREAT = 73;
process.exit(EXIT_SUCCESS);
process.exit(EXIT_ERROR);
Help System Design
1. Three Levels of Help
$ uam --help
Universal Agent Memory - AI agent memory and workflow system
Usage: uam [command] [options]
Commands:
init Initialize a new project
generate Generate CLAUDE.md and agent files
memory Manage agent memory (short-term and long-term)
worktree Git worktree management for isolated development
Options:
-v, --version Show version number
-h, --help Show help
Run 'uam <command> --help' for more information on a command.
$ uam memory --help
Manage agent memory systems
Usage: uam memory <subcommand> [options]
Subcommands:
query Search long-term memory
store Store a new memory
status Show memory system status
start Start memory services
Examples:
uam memory query "redis caching"
uam memory store lesson "Always check network policies" --tags networking --importance 8
$ uam memory query --help
Search long-term memory using semantic similarity
Usage: uam memory query <search-term> [options]
Arguments:
search-term Keywords to search for
Options:
-l, --limit <n> Maximum results (default: 10)
-t, --tags <tags> Filter by tags (comma-separated)
--min-score <n> Minimum similarity score (0-1, default: 0.5)
--json Output as JSON
Examples:
uam memory query "authentication flow"
uam memory query "database" --tags postgres,migration --limit 5
uam memory query "API design" --json | jq '.results[0]'
2. Example-Driven Documentation
const command = new Command('generate')
.description('Generate CLAUDE.md and agent configuration files')
.option('-o, --output <path>', 'Output directory', '.')
.option('--dry-run', 'Preview without writing files')
.addHelpText('after', `
Examples:
# Generate with defaults
$ uam generate
# Generate to specific directory
$ uam generate --output ./docs
# Preview what would be generated
$ uam generate --dry-run
# Generate for specific platform
$ uam generate --platform factory
Common Issues:
If generation fails, ensure you have a .uam.json config file.
Run 'uam init' to create one interactively.
`);
Error Message Design
1. Helpful Error Format
throw new Error('ENOENT');
console.error(`
${chalk.red('Error:')} Configuration file not found
Looking for: ${chalk.cyan('.uam.json')}
Searched in: ${chalk.dim(process.cwd())}
${chalk.yellow('How to fix:')}
Run ${chalk.cyan('uam init')} to create a configuration file.
${chalk.dim('For more help: uam init --help')}
`);
2. Error Categories
interface CLIError {
code: string;
message: string;
suggestion?: string;
docs?: string;
}
const ERROR_MESSAGES: Record<string, CLIError> = {
CONFIG_NOT_FOUND: {
code: 'CONFIG_NOT_FOUND',
message: 'Configuration file .uam.json not found',
suggestion: 'Run `uam init` to create a configuration file',
docs: 'https://github.com/DammianMiller/universal-agent-memory#configuration',
},
INVALID_CONFIG: {
code: 'INVALID_CONFIG',
message: 'Configuration file is invalid',
suggestion: 'Check the JSON syntax and required fields',
docs: 'https://github.com/DammianMiller/universal-agent-memory#configuration',
},
GIT_NOT_FOUND: {
code: 'GIT_NOT_FOUND',
message: 'Not a git repository',
suggestion: 'Initialize git with `git init` or run from a git repository',
},
};
function formatError(error: CLIError): void {
console.error(chalk.(), error.);
(error.) {
.(chalk.(), error.);
}
(error.) {
.(chalk.(), error.);
}
}
3. Validation Errors
function validateConfig(config: unknown): ValidationResult {
const errors: string[] = [];
if (!config || typeof config !== 'object') {
return { valid: false, errors: ['Configuration must be an object'] };
}
const c = config as Record<string, unknown>;
if (!c.project) {
errors.push('Missing required field: project');
}
if (!c.project?.name) {
errors.push('Missing required field: project.name');
}
if (c.memory?.shortTerm?.maxEntries && typeof c.memory.shortTerm.maxEntries !== 'number') {
errors.push('Invalid type: memory.shortTerm.maxEntries must be a number');
}
return { valid: errors.length === 0, errors };
}
function showValidationErrors(): {
.(chalk.());
errors.( {
.(chalk.(), err);
});
.(chalk.());
}
Interactive Prompts
1. Inquirer.js Patterns
import inquirer from 'inquirer';
async function initInteractive(): Promise<Config> {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'Project name:',
default: basename(process.cwd()),
validate: (input) => input.length > 0 || 'Project name is required',
},
{
type: 'input',
name: 'description',
message: 'Description (optional):',
},
{
type: 'list',
name: 'platform',
message: 'Primary AI platform:',
choices: [
{ name: 'Claude Code (Desktop)', value: 'claudeCode' },
{ name: 'Factory.AI', value: 'factory' },
{ name: 'VS Code', value: },
{ : , : },
],
},
{
: ,
: ,
: ,
: ,
},
{
: ,
: ,
: ,
: [
{ : , : },
{ : , : },
{ : , : },
{ : , : },
],
: answers.,
},
]);
(answers);
}
2. Confirmation for Destructive Actions
async function handleDestructiveAction(
action: string,
details: string,
execute: () => Promise<void>
): Promise<void> {
console.log(chalk.yellow(`\n⚠️ ${action}\n`));
console.log(chalk.dim(details));
const { confirmed } = await inquirer.prompt([{
type: 'confirm',
name: 'confirmed',
message: 'Are you sure you want to proceed?',
default: false,
}]);
if (!confirmed) {
console.log(chalk.dim('Cancelled.'));
return;
}
await execute();
}
await handleDestructiveAction(
'Delete worktree and branch',
`This will delete:\n - Worktree: .worktrees/123-feature\n - Branch: feature/123-feature`,
async () => await deleteWorktree(id)
);
Output Formatting
1. Tables
function printTable(headers: string[], rows: string[][]): void {
const widths = headers.map((h, i) =>
Math.max(h.length, ...rows.map(r => (r[i] || '').length))
);
console.log(headers.map((h, i) => h.padEnd(widths[i]!)).join(' '));
console.log(widths.map(w => '─'.repeat(w)).join(' '));
for (const row of rows) {
console.log(row.map((cell, i) => (cell || '').padEnd(widths[i]!)).join(' '));
}
}
(
[, , , ],
[
[, , , ],
[, , , ],
]
);
2. JSON Output for Scripting
interface CommandOptions {
json?: boolean;
quiet?: boolean;
}
function output<T>(data: T, options: CommandOptions): void {
if (options.json) {
console.log(JSON.stringify(data, null, 2));
return;
}
if (options.quiet) {
if (Array.isArray(data)) {
data.forEach(item => console.log(item.id || item));
} else {
console.log((data as { id?: string }).id || data);
}
return;
}
prettyPrint(data);
}
3. Progress Indicators
import ora from 'ora';
const spinner = ora('Processing...').start();
try {
await doWork();
spinner.succeed('Done!');
} catch (e) {
spinner.fail('Failed');
throw e;
}
async function runPipeline(steps: Array<{ name: string; run: () => Promise<void> }>): Promise<void> {
for (let i = 0; i < steps.length; i++) {
const step = steps[i]!;
const prefix = chalk.dim(`[${i + 1}/${steps.length}]`);
const spinner = ora(`${prefix} ${step.name}`).start();
try {
await step.run();
spinner.succeed(`${prefix} ${step.name}`);
} (e) {
spinner.();
e;
}
}
}
Color Usage
import chalk from 'chalk';
const colors = {
success: chalk.green,
error: chalk.red,
warning: chalk.yellow,
info: chalk.cyan,
primary: chalk.blue,
secondary: chalk.dim,
highlight: chalk.bold,
path: chalk.cyan,
command: chalk.cyan,
code: chalk.yellow,
url: chalk.underline.blue,
};
const symbols = {
success: chalk.green('✔'),
error: chalk.red('✖'),
warning: chalk.(),
: chalk.(),
: chalk.(),
: chalk.(),
};
Shell Completion
program
.command('completion')
.description('Generate shell completion script')
.argument('<shell>', 'Shell type (bash, zsh, fish)')
.action((shell: string) => {
switch (shell) {
case 'bash':
console.log(generateBashCompletion());
break;
case 'zsh':
console.log(generateZshCompletion());
break;
case 'fish':
console.log(generateFishCompletion());
break;
default:
console.error(`Unknown shell: ${shell}`);
process.exit(1);
}
});
function generateBashCompletion(): string {
return `
_uam_completions() {
local cur="\${COMP_WORDS[COMP_CWORD]}"
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
case "\${prev}" in
uam)
COMPREPLY=($(compgen -W "init generate memory worktree droids" -- "\${cur}"))
;;
memory)
COMPREPLY=($(compgen -W "query store status start stop" -- "\${cur}"))
;;
worktree)
COMPREPLY=($(compgen -W "create list pr cleanup" -- "\${cur}"))
;;
esac
}
complete -F _uam_completions uam
`;
}
Review Checklist
Before releasing any CLI command: