Building CLIs
Build professional command-line interfaces across Python, Go, and Rust using modern frameworks with robust argument parsing, configuration management, and shell integration.
When to Use This Skill
Use this skill when:
- Building developer tooling or automation CLIs
- Creating infrastructure management tools (deployment, monitoring)
- Implementing API client command-line tools
- Adding CLI capabilities to existing projects
- Packaging utilities for distribution (PyPI, Homebrew, binary releases)
Common triggers: "create a CLI tool", "build a command-line interface", "add CLI arguments", "parse command-line options", "generate shell completions"
Framework Selection
Quick Decision Guide
Python Projects:
- Typer (recommended): Modern type-safe CLIs with minimal boilerplate
- Click: Mature, flexible CLIs for complex command hierarchies
Go Projects:
- Cobra (recommended): Industry standard for enterprise tools (Kubernetes, Docker, GitHub CLI)
- urfave/cli: Lightweight alternative for simple CLIs
Rust Projects:
- clap v4 (recommended): Type-safe with derive API or builder API for runtime flexibility
For detailed framework comparison and selection criteria, see [references/framework-selection.md](references/framework-selection.md).
Core Patterns
Arguments vs. Options vs. Flags
Positional Arguments:
- Primary input, identified by position
- Use for required inputs (max 2-3 arguments)
- Example:
convert input.jpg output.png
Options:
- Named parameters with values
- Use for configuration and optional inputs
- Example:
--output file.txt, --config app.yaml
Flags:
- Boolean options (presence = true)
- Use for switches and toggles
- Example:
--verbose, --dry-run, --force
Decision Matrix:
| Use Case |
Type |
Example |
| Primary required input |
Positional Argument |
git commit -m "message" |
| Optional configuration |
Option |
--config app.yaml |
| Boolean setting |
Flag |
--verbose, --force |
| Multiple values |
Variadic Argument |
files... |
See [references/argument-patterns.md](references/argument-patterns.md) for comprehensive parsing patterns.
Subcommand Organization
Flat Structure (1 Level):
app command1 [args]
app command2 [args]
Use for: Small CLIs with 5-10 operations
Grouped Structure (2 Levels):
app group subcommand [args]
Use for: Medium CLIs with logical groupings (10-30 commands) Example: kubectl get pods, kubectl create deployment
Nested Structure (3+ Levels):
app group subgroup command [args]
Use for: Large CLIs with deep hierarchies (30+ commands) Example: gcloud compute instances create
See [references/subcommand-design.md](references/subcommand-design.md) for structuring strategies.
Configuration Management
Standard Precedence (Highest to Lowest):
- CLI Arguments/Flags (explicit user input)
- Environment Variables (session overrides)
- Config File - Local (
./config.yaml)
- Config File - User (
~/.config/app/config.yaml)
- Config File - System (
/etc/app/config.yaml)
- Built-in Defaults (hardcoded)
Best Practices:
- Document precedence in
--help
- Validate config files before execution
- Provide
--print-config to show effective configuration
- Use XDG Base Directory (
~/.config/app/) for config files
See [references/configuration-management.md](references/configuration-management.md) for implementation patterns across languages.
Output Formatting
Format Selection:
| Use Case |
Format |
When |
| Human consumption |
Colored text, tables |
Default interactive mode |
| Machine consumption |
JSON, YAML |
--output json, piping |
| Logging/debugging |
Plain text |
--verbose, stderr |
| Progress tracking |
Progress bars, spinners |
Long operations |
Best Practices:
- Default to human-readable output
- Provide
--output flag (json, yaml, table)
- Use stderr for logs, stdout for data
- Auto-detect TTY (disable colors if not interactive)
- Use exit codes: 0 = success, 1 = error, 2 = usage error
See [references/output-formatting.md](references/output-formatting.md) for formatting strategies.
Language-Specific Quick Starts
Python with Typer
Installation:
pip install "typer[all]" # Includes rich for colored output
Basic Example:
import typer
from typing import Annotated
app = typer.Typer()
@app.command()
def greet(
name: Annotated[str, typer.Argument(help="Name to greet")],
formal: Annotated[bool, typer.Option(help="Use formal greeting")] = False
):
"""Greet someone with a message."""
greeting = "Good day" if formal else "Hello"
typer.echo(f"{greeting}, {name}!")
if __name__ == "__main__":
app()
Key Features:
- Type hints for automatic validation
- Minimal boilerplate with decorators
- Auto-generated help text
- Rich integration for colored output
See [examples/python/](examples/python/) for complete working examples including subcommands, config management, and interactive features.
Go with Cobra
Installation:
go get -u github.com/spf13/cobra@latest
Basic Example:
var rootCmd = &cobra.Command{
Use: "greet [name]",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Hello, %s!\n", args[0])
},
}
rootCmd.Flags().Bool("formal", false, "Use formal greeting")
rootCmd.Execute()
Key Features:
- POSIX-compliant flags
- Viper integration for configuration
- Subcommand architecture
- Shell completion generation
See [examples/go/](examples/go/) for complete working examples including Viper config and multi-level subcommands.
Rust with clap
Installation (Cargo.toml):
[dependencies]
clap = { version = "4.5", features = ["derive"] }
Basic Example (Derive API):
use clap::Parser;
#[derive(Parser)]
#[command(about = "Greet someone")]
struct Cli {
/// Name to greet
name: String,
/// Use formal greeting
#[arg(long)]
formal: bool,
}
fn main() {
let cli = Cli::parse();
let greeting = if cli.formal { "Good day" } else { "Hello" };
println!("{}, {}!", greeting, cli.name);
}
Key Features:
- Compile-time type safety
- Derive API (declarative) or Builder API (programmatic)
- Comprehensive validation
- Performance optimized
See [examples/rust/](examples/rust/) for complete working examples including subcommands and builder API patterns.
Interactive Features
Progress Indicators
Python (rich):
from rich.progress import track
for _ in track(range(100), description="Processing..."):
time.sleep(0.01)
Go (progressbar):
import "github.com/schollz/progressbar/v3"
bar := progressbar.Default(100)
for i := 0; i < 100; i++ {
bar.Add(1)
}
Rust (indicatif):
use indicatif::ProgressBar;
let bar = ProgressBar::new(100);
for _ in 0..100 {
bar.inc(1);
}
Prompts and Confirmations
Python:
confirm = typer.confirm("Are you sure?")
if not confirm:
raise typer.Abort()
Go:
reader := bufio.NewReader(os.Stdin)
fmt.Print("Are you sure? (y/n): ")
response, _ := reader.ReadString('\n')
Rust:
use dialoguer::Confirm;
if Confirm::new().with_prompt("Are you sure?").interact()? {
// Proceed
}
Shell Completion
Generating Completions
Python (Typer):
_MYAPP_COMPLETE=bash_source myapp > ~/.myapp-complete.bash
_MYAPP_COMPLETE=zsh_source myapp > ~/.myapp-complete.zsh
Go (Cobra):
rootCmd.AddCommand(&cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
rootCmd.GenBashCompletion(os.Stdout)
case "zsh":
rootCmd.GenZshCompletion(os.Stdout)
}
},
})
Rust (clap):
use clap_complete::{generate, shells::Bash};
generate(Bash, &mut Cli::command(), "myapp", &mut io::stdout())
See [references/shell-completion.md](references/shell-completion.md) for installation instructions.
Distribution and Packaging
Python (PyPI)
pyproject.toml:
[project]
name = "myapp"
version = "1.0.0"
scripts = { myapp = "myapp.cli:app" }
Publish:
pip install build twine
python -m build
twine upload dist/*
Go (Homebrew)
Formula:
class Myapp < Formula
desc "My CLI application"
url "https://github.com/user/myapp/archive/v1.0.0.tar.gz"
def install
system "go", "build", "-o", bin/"myapp"
end
end
Rust (Cargo)
Publish:
cargo login
cargo publish
Installation:
cargo install myapp
See [references/distribution.md](references/distribution.md) for comprehensive packaging strategies including binary releases.
Best Practices
Universal CLI Conventions
Always Provide:
--help and -h for usage information
--version and -V for version display
- Clear error messages with actionable suggestions
Argument Handling:
- Use
-- separator for options vs. positional args
- Support both short (
-v) and long (--verbose) forms
- Validate and sanitize all user inputs
Error Handling:
- Exit code 0 for success
- Exit code 1 for general errors
- Exit code 2 for usage errors
- Write errors to stderr, data to stdout
Interactivity:
- Detect TTY (interactive vs. piped input)
- Provide
--yes/--force to skip prompts for automation
- Show progress for operations longer than 2 seconds
Configuration Best Practices
File Formats:
- Use YAML, TOML, or JSON consistently
- Separate files per environment (dev, staging, prod)
- Validate configuration in CI/CD with
--check-config
Secret Management:
- Never commit secrets to config files
- Use environment variables or secret managers
- Document required environment variables
Precedence:
- CLI args > env vars > config file > defaults
- Document precedence in help text
- Provide
--print-config to show effective configuration
Integration with Other Skills
testing-strategies:
- CLI testing with mocks and fixtures
- Integration tests for end-to-end workflows
- See [examples/python/testcli.py](examples/python/testcli.py)
building-ci-pipelines:
- Binary builds for multiple platforms
- Automated releases via GitHub Actions
- See [references/distribution.md](references/distribution.md)
api-patterns:
- Building API client CLIs
- Authentication and token management
- Formatting API responses
secret-management:
- Secure credential storage
- Environment variable integration
- Vault/secrets manager integration
Reference Files
Decision Frameworks:
- [framework-selection.md](references/framework-selection.md) - Which framework to choose
- [argument-patterns.md](references/argument-patterns.md) - Arguments vs. options vs. flags
- [subcommand-design.md](references/subcommand-design.md) - Structuring command hierarchies
Implementation Guides:
- [configuration-management.md](references/configuration-management.md) - Config files and precedence
- [output-formatting.md](references/output-formatting.md) - Human vs. machine-readable output
- [shell-completion.md](references/shell-completion.md) - Generating completions
- [distribution.md](references/distribution.md) - Packaging and releasing CLIs
Code Examples:
- [examples/python/](examples/python/) - Typer examples (basic, subcommands, config, interactive)
- [examples/go/](examples/go/) - Cobra examples (basic, subcommands, Viper integration)
- [examples/rust/](examples/rust/) - clap examples (derive, builder, subcommands)
Quick Reference
Framework Recommendations:
- Python: Typer (modern) or Click (mature)
- Go: Cobra (enterprise) or urfave/cli (simple)
- Rust: clap v4 (derive or builder)
Common Patterns:
- Arguments: Primary inputs (max 2-3)
- Options: Named parameters with values
- Flags: Boolean switches
- Subcommands: Group related operations
- Config: CLI args > env vars > files > defaults
Output Standards:
- Default: Human-readable (colored, tables)
- Machine: JSON/YAML via
--output flag
- Errors: stderr, data: stdout
- Exit: 0 = success, 1 = error, 2 = usage
Distribution:
- Python: PyPI (
pip install)
- Go: Homebrew, binary releases
- Rust: Cargo (
cargo install), binary releases