Introduction
Command-line tools remain essential for developer productivity. A well-designed CLI can become a daily driver for users. This guide covers building CLI applications with modern frameworks, handling arguments, and creating delightful experiences.
A great CLI is discoverable, consistent, and provides helpful feedback.
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CLI Architecture โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโ โ
โ โ Entry โ โ
โ โ Point โ โ
โ โโโโโโโโฌโโโโโโโ โ
โ โ โ
โ โโโโโโโโผโโโโโโโ โ
โ โ Command โ โ
โ โ Parser โ โ
โ โโโโโโโโฌโโโโโโโ โ
โ โ โ
โ โโโโโโโโผโโโโโโโ โ
โ โ Commands โ โ
โ โโโโโโโโฌโโโโโโโ โ
โ โ โ
โ โโโโโโโโผโโโโโโโ โ
โ โ Business โ โ
โ โ Logic โ โ
โ โโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Python CLI with Click
import click
from typing import Optional
@click.group()
@click.version_option(version="1.0.0")
def cli():
"""A sample CLI application."""
pass
@cli.command()
@click.argument("name")
@click.option(
"--count", "-c",
default=1,
type=int,
help="Number of greetings"
)
@click.option(
"--excited",
is_flag=True,
help="Add excitement"
)
def hello(name: str, count: int, excited: bool):
"""Greet NAME with enthusiasm."""
exclamation = "!" if excited else "."
for _ in range(count):
if excited:
click.echo(f"Hello, {name}{exclamation * 3}")
else:
click.echo(f"Hello, {name}{exclamation}")
@cli.command()
@click.argument("file", type=click.Path(exists=True))
@click.option("--lines", "-n", type=int, help="Number of lines")
def tail(file: str, lines: Optional[int]):
"""Display the last lines of FILE."""
with open(file) as f:
content = f.readlines()
start = -lines if lines else -10
for line in content[start:]:
click.echo(line.rstrip())
@cli.group()
def config():
"""Manage configuration."""
pass
@config.command("set")
@click.argument("key")
@click.argument("value")
def config_set(key: str, value: str):
"""Set a configuration value."""
click.echo(f"Setting {key}={value}")
@config.command("get")
@click.argument("key")
def config_get(key: str):
"""Get a configuration value."""
click.echo(f"{key}=some_value")
if __name__ == "__main__":
cli()
Go CLI with Cobra
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var (
rootCmd = &cobra.Command{
Use: "app",
Short: "A sample CLI application",
Long: `A longer description that spans multiple lines.`,
}
name string
excited bool
count int
)
func init() {
rootCmd.AddCommand(helloCmd)
helloCmd.Flags().StringVarP(&name, "name", "n", "World", "Name to greet")
helloCmd.Flags().BoolVarP(&excited, "excited", "e", false, "Add excitement")
helloCmd.Flags().IntVarP(&count, "count", "c", 1, "Number of greetings")
}
var helloCmd = &cobra.Command{
Use: "hello",
Short: "Greet someone",
Run: func(cmd *cobra.Command, args []string) {
exclamation := "."
if excited {
exclamation = "!!!"
}
for i := 0; i < count; i++ {
fmt.Printf("Hello, %s%s\n", name, exclamation)
}
},
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Interactive Prompts
import click
from click import prompt
@cli.command()
def create_project():
"""Create a new project."""
name = prompt("Project name", default="my-project")
# With validation
framework = prompt(
"Framework",
type=click.Choice(["react", "vue", "angular", "svelte"]),
default="react"
)
# Boolean confirmation
use_typescript = click.confirm("Use TypeScript?", default=True)
# Password input
api_key = click.prompt("API Key", hide_input=True)
# Display summary
click.echo(f"\nCreating project: {name}")
click.echo(f"Framework: {framework}")
click.echo(f"TypeScript: {use_typescript}")
Progress and Feedback
import click
import time
@cli.command()
def process_files():
"""Process multiple files with progress."""
files = [f"file_{i}.txt" for i in range(10)]
with click.progressbar(files, label="Processing files") as bar:
for file in bar:
# Simulate processing
time.sleep(0.1)
@cli.command()
def long_task():
"""A long-running task with spinner."""
with click_spinner.spinner():
# Do work
time.sleep(3)
click.echo("Done!")
@cli.command()
def download():
"""Download with progress bar."""
with click.progressbar(length=100, label="Downloading") as bar:
for i in range(10):
time.sleep(0.1)
bar.update(10)
Configuration
import click
from click import Config
@click.command()
@click.option("--config", type=click.Path(), help="Config file path")
@click.option("--debug", is_flag=True, help="Enable debug mode")
def main(config: str, debug: bool):
"""CLI application with config."""
pass
# Using click's built-in config
# Create ~/.myapp.yaml
# Automatically loads from config file
Best Practices
- Use subcommands: Organize functionality
- Provide defaults: Sensible defaults reduce friction
- Validate input: Clear error messages
- Show progress: For long operations
- Color output: Use colors for errors and success
- Document thoroughly: Help text for every command
Conclusion
Great CLI tools empower users to be productive. By following these patterns and using modern frameworks, you can create CLI applications that are a joy to use.
Comments