home / skills / bobmatnyc / claude-mpm-skills / clap

This skill helps you build production-grade Rust CLIs with Clap, including subcommands, config layering, validation, exit codes, and testable command surfaces.

npx playbooks add skill bobmatnyc/claude-mpm-skills --skill clap

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
4.5 KB
---
name: clap
description: "Build production Rust CLIs with Clap: subcommands, config layering, validation, exit codes, shell completions, and testable command surfaces"
version: 1.0.0
category: toolchain
author: Claude MPM Team
license: MIT
progressive_disclosure:
  entry_point:
    summary: "Create ergonomic, testable Rust CLIs using Clap derive, subcommands, and config layering (CLI + env + config file)"
    when_to_use: "When building Rust CLI tools that need reliable argument parsing, good help UX, strong validation, and automated tests"
    quick_start: "1. Add clap (derive) 2. Define Args + Subcommand 3. Parse once in main 4. Map errors to exit codes 5. Test with assert_cmd"
  token_estimate:
    entry: 120
    full: 4500
context_limit: 700
tags:
  - rust
  - cli
  - clap
  - config
  - testing
requires_tools: []
---

# Clap (Rust) - Production CLI Patterns

## Overview

Clap provides declarative command-line parsing with strong help output, validation, and subcommand support. Use it to build CLIs with predictable UX and testable execution paths.

## Quick Start

### Minimal CLI

✅ **Correct: derive Parser**
```rust
use clap::Parser;

#[derive(Parser, Debug)]
#[command(name = "mytool", version, about = "Example CLI")]
struct Args {
    /// Enable verbose output
    #[arg(long)]
    verbose: bool,

    /// Input file path
    #[arg(value_name = "FILE")]
    input: String,
}

fn main() {
    let args = Args::parse();
    if args.verbose {
        eprintln!("verbose enabled");
    }
    println!("input={}", args.input);
}
```

❌ **Wrong: parse multiple times**
```rust
fn main() {
    let _a = Args::parse();
    let _b = Args::parse(); // duplicate parsing and inconsistent behavior
}
```

## Subcommands (real tools)

Model multi-mode CLIs with subcommands and shared global flags.

✅ **Correct: global flags + subcommands**
```rust
use clap::{Parser, Subcommand, ValueEnum};

#[derive(Parser, Debug)]
struct Args {
    #[arg(long, global = true)]
    verbose: bool,

    #[arg(long, global = true, env = "MYTOOL_CONFIG")]
    config: Option<String>,

    #[command(subcommand)]
    cmd: Command,
}

#[derive(Subcommand, Debug)]
enum Command {
    Serve { #[arg(long, default_value_t = 3000)] port: u16 },
    Migrate { #[arg(long, value_enum, default_value_t = Mode::Up)] mode: Mode },
}

#[derive(Copy, Clone, Debug, ValueEnum)]
enum Mode { Up, Down }

fn main() {
    let args = Args::parse();
    match args.cmd {
        Command::Serve { port } => println!("serve on {}", port),
        Command::Migrate { mode } => println!("migrate: {:?}", mode),
    }
}
```

## Config layering (CLI + env + config file)

Prefer explicit precedence:

1. CLI flags
2. Environment variables
3. Config file
4. Defaults

✅ **Correct: merge config with CLI overrides**
```rust
use clap::Parser;
use serde::Deserialize;

#[derive(Parser, Debug)]
struct Args {
    #[arg(long, env = "APP_PORT")]
    port: Option<u16>,
}

#[derive(Deserialize)]
struct FileConfig {
    port: Option<u16>,
}

fn effective_port(args: &Args, file: &FileConfig) -> u16 {
    args.port.or(file.port).unwrap_or(3000)
}
```

## Exit codes and error handling

Map failures to stable exit codes. Return `Result` from command handlers and centralize printing.

✅ **Correct: command returns Result**
```rust
use std::process::ExitCode;

fn main() -> ExitCode {
    match run() {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => {
            eprintln!("{e}");
            ExitCode::from(1)
        }
    }
}

fn run() -> Result<(), String> {
    Ok(())
}
```

## Testing (assert_cmd)

Test the binary surface (arguments, output, exit codes) without coupling to internals.

✅ **Correct: integration test**
```rust
use assert_cmd::Command;

#[test]
fn shows_help() {
    Command::cargo_bin("mytool")
        .unwrap()
        .arg("--help")
        .assert()
        .success();
}
```

## Shell completions (optional)

Generate completions for Bash/Zsh/Fish.

✅ **Correct: emit completions**
```rust
use clap::{CommandFactory, Parser};
use clap_complete::{generate, shells::Zsh};
use std::io;

fn print_zsh_completions() {
    let mut cmd = super::Args::command();
    generate(Zsh, &mut cmd, "mytool", &mut io::stdout());
}
```

## Anti-Patterns

- Parse arguments in library code; parse once in `main` and pass a typed config down.
- Hide failures behind `unwrap`; return stable exit codes and structured errors.
- Overload one command with flags; use subcommands for distinct modes.

## Resources

- Clap: https://docs.rs/clap
- assert_cmd: https://docs.rs/assert_cmd
- clap_complete: https://docs.rs/clap_complete

Overview

This skill helps you build production-quality Rust command-line interfaces using Clap. It focuses on structured parsing, subcommands, config layering, predictable exit codes, shell completions, and testable command surfaces. The guidance favors a single parse in main, clear precedence for configuration, and patterns that make CLIs easy to test and maintain.

How this skill works

The skill explains how to define parsers with derive macros, model multi-mode applications with subcommands, and mark global flags. It shows how to merge CLI flags, environment variables, and config files with explicit precedence. It also demonstrates returning Results from handlers for stable exit codes, generating shell completions, and using assert_cmd to test the binary surface.

When to use it

  • You need a robust CLI with subcommands and shared global options.
  • You want deterministic config precedence across CLI, env, and file.
  • You require predictable exit codes and centralized error handling.
  • You plan to ship shell completions for Bash, Zsh, or Fish.
  • You want integration tests that exercise the compiled binary surface.

Best practices

  • Parse arguments once in main and pass a typed config into library code.
  • Use derive(Parser) and Subcommand for declarative, testable parsers.
  • Apply explicit config precedence: CLI > env > config file > defaults.
  • Return Result from command handlers and map failures to exit codes.
  • Write integration tests with assert_cmd to verify args, output, and exit codes.

Example use cases

  • A server binary with serve and migrate subcommands and global verbose/config flags.
  • A migration tool that accepts a mode enum via value_enum and validates input.
  • A CLI that reads defaults from a config file but allows env and CLI overrides.
  • Generating shell completion files during build or installation for user shells.
  • Integration tests that assert --help output, error messages, and nonzero exit codes.

FAQ

Should I parse arguments in library modules?

No. Parse once in main and pass the typed config into libraries to keep code testable and reusable.

How do I handle failures and exit codes?

Have command handlers return Result and map errors to stable numeric exit codes in main, printing user-friendly messages to stderr.