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.
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 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 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-configto show effective configuration - Use XDG Base Directory (
~/.config/app/) for config files
See 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
--outputflag (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 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/ 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/ 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/ 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 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 for comprehensive packaging strategies including binary releases.
Best Practices
Universal CLI Conventions
Always Provide:
--helpand-hfor usage information--versionand-Vfor 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/--forceto 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-configto 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/test_cli.py
building-ci-pipelines:
- Binary builds for multiple platforms
- Automated releases via GitHub Actions
- See 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 - Which framework to choose
- argument-patterns.md - Arguments vs. options vs. flags
- subcommand-design.md - Structuring command hierarchies
Implementation Guides:
- configuration-management.md - Config files and precedence
- output-formatting.md - Human vs. machine-readable output
- shell-completion.md - Generating completions
- distribution.md - Packaging and releasing CLIs
Code Examples:
- examples/python/ - Typer examples (basic, subcommands, config, interactive)
- examples/go/ - Cobra examples (basic, subcommands, Viper integration)
- 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
--outputflag - 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