This skill should be used when the user asks to "add a CLI command", "create a subcommand", "add a new command", or mentions "new CLI command", "new subcommand", or "command-line tool". Provides scaffolding guidance for implementing a new CLI subcommand with argument parsing, validation, and tests.
Installs into .claude/skills of the current project.
Are you the author of Add Command?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/standardbeagle-add-command)
---
name: add-command
description: This skill should be used when the user asks to "add a CLI command", "create a subcommand", "add a new command", or mentions "new CLI command", "new subcommand", or "command-line tool". Provides scaffolding guidance for implementing a new CLI subcommand with argument parsing, validation, and tests.
---
<!-- Generated by dev-standards plugin. Customize as needed. -->
# Add CLI Subcommand
Scaffold a new CLI subcommand using cobra. Follow the side-effect isolation pattern: pure function for command logic, action objects for I/O operations.
## Pre-Flight Checks
Before writing any code:
1. Confirm the command name, arguments, flags, and purpose with the user
2. Read the project's architecture decisions rule (`.claude/rules/architecture.md`) to verify active patterns and constraints
3. Identify the existing command directory structure by examining neighboring commands
4. Review the root command or command group this subcommand belongs to
## Step 1 -- Define the Command
Create the command definition using cobra conventions.
### Command Definition
- Place the command where existing commands live (e.g., `cmd/`, `commands/`, `cli/`)
- Match the naming convention of neighboring commands
- Register the command with its parent command or root
### Arguments and Flags
Define all inputs explicitly:
- **Positional arguments**: required values in a fixed order
- **Required flags**: named parameters that must be provided
- **Optional flags**: named parameters with sensible defaults
- **Boolean flags**: on/off toggles (default to false)
For each argument/flag, specify:
- Name (short form and long form for flags, e.g., `-o`, `--output`)
- Type (string, int, bool, path, enum)
- Default value (for optional flags)
- Description (shown in help text)
- Validation rules (required, valid values, file must exist, etc.)
### Help Text
Write clear help text following these guidelines:
- One-line description: what the command does (imperative form)
- Usage line showing required arguments and flags
- Examples section with at least two real-world invocations
- Long description if the command has non-obvious behavior
```
# Example help structure
command-name - Brief description of what this command does
Usage:
tool command-name [flags] <required-arg>
Examples:
tool command-name --output report.json data.csv
tool command-name --format table --verbose input/
Flags:
-o, --output string Output file path (default: stdout)
-f, --format string Output format: json, table, csv (default: json)
-v, --verbose Enable verbose output
```
## Step 2 -- Validate Inputs
Validate all arguments and flags before executing logic.
### Validation Guidelines
- Validate immediately after parsing, before any processing
- Return clear, actionable error messages (tell the user what to fix)
- Check file paths exist and are accessible (when applicable)
- Validate enum values against allowed options
- Validate numeric ranges
- Exit with non-zero status code on validation failure
### Validation as Pure Function
```
# Pure function: validates parsed inputs, returns validated config or errors
validate_inputs(args, flags) -> ValidatedConfig | list[ValidationError]:
errors = []
check each constraint
return errors if any, else ValidatedConfig
```
## Step 3 -- Implement Command Logic (Pure Function)
Implement the core logic as a pure function that receives validated inputs and produces action objects.
### Side-Effect Isolation Pattern
```
# Pure function: decides WHAT to do
compute_actions(validated_config, input_data) -> list[Action] | CommandError:
process input_data according to config
return actions describing output to write, API calls to make, etc.
# Action objects (plain data)
WriteFile(path, content)
PrintOutput(text, format)
HttpRequest(method, url, body)
```
### Logic Guidelines
- Accept only the validated configuration and any input data (already loaded)
- Return action objects describing all side effects
- Return typed errors for failure cases, not exceptions
- Never read files, call APIs, or produce output directly from the pure function
- Handle edge cases: empty input, large input, malformed data
## Step 4 -- Implement the Executor
Create a thin executor that performs the side effects described by action objects.
### Executor Guidelines
- One executor function that iterates over action objects and dispatches each
- Handle I/O errors at this layer (file write failures, network errors)
- Report progress to stderr (never stdout -- stdout is for command output)
- Exit with appropriate status codes (0 = success, 1 = general error, 2 = usage error)
```
# Executor: performs side effects
execute(actions) -> ExitCode:
for action in actions:
match action:
WriteFile: write file, handle permission errors
PrintOutput: write to stdout in requested format
HttpRequest: make request, handle timeout/errors
return exit_code
```
## Step 5 -- Write Unit Tests for Command Logic
Test the pure logic function and input validation directly -- no mocks needed.
### Test Cases to Cover
- **Input validation**: each valid input accepted, each invalid input rejected with clear error
- **Happy path**: valid config + valid data produces correct actions
- **Edge cases**: empty input, single item, maximum size, special characters
- **Error handling**: malformed data produces typed error, not crash
- **Output formats**: each supported format produces correct action objects
### Test Structure
```
describe("validate_inputs")
it("accepts valid arguments and flags")
it("rejects missing required argument")
it("rejects invalid enum value for --format")
it("rejects negative value for --limit")
describe("compute_actions for process-data")
it("returns WriteFile action with correct content for JSON format")
it("returns PrintOutput action for table format")
it("returns error when input data is empty")
it("handles large input without excessive memory use")
```
Run with: `tman run -- go test ./internal/cli/...`
## Step 6 -- Write Integration Tests
Test the command end-to-end through the CLI interface.
### Integration Test Guidelines
- Invoke the command as a subprocess or through cobra's test utilities
- Verify stdout output matches expected format and content
- Verify stderr output for progress/error messages
- Verify exit codes: 0 for success, non-zero for each error category
- Test with real files (create temp files in test fixtures)
- Test piped input if the command supports stdin
### Test Scenarios
- Full happy path with all flags
- Minimal invocation with only required arguments
- Each error case produces correct exit code and error message
- Help flag (`--help`) produces usage text
- Version flag (`--version`) if applicable
## Step 7 -- Integration Checklist
Before considering the command complete:
- [ ] Command is registered and discoverable via the CLI tool's help
- [ ] All arguments and flags have clear names, types, defaults, and descriptions
- [ ] Help text includes a description, usage line, and at least two examples
- [ ] Input validation happens before any processing and returns actionable errors
- [ ] Core logic is a pure function returning action objects -- no I/O
- [ ] Executor handles I/O errors gracefully and sets correct exit codes
- [ ] Progress output goes to stderr, command output goes to stdout
- [ ] Unit tests cover validation and logic without mocks
- [ ] Integration tests verify end-to-end behavior including exit codes
- [ ] No debug statements, no commented-out code, no placeholder data in tests