cli-development

Best practices for building CLI applications across languages. Covers CLI design principles (Unix philosophy, command structure, subcommands vs flags), argument parsing (required/optional args, flags, environment variables, config files, precedence), user interface (help text, version info, progress indicators, color output, interactive prompts), output formatting (human-readable vs machine-readable JSON/YAML, exit codes), error handling (clear messages, suggestions, debug mode), cross-platform considerations (paths, line endings, terminal capabilities), testing strategies (integration tests, output verification, exit codes), documentation (README, man pages, built-in help), and language-specific libraries. Activate when working with CLI applications, command-line tools, argument parsing, CLI utilities, argument handling, commands, subcommands, CLI frameworks, or building command-line interfaces.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "cli-development" with this command: npx skills add ilude/claude-code-config/ilude-claude-code-config-cli-development

CLI Development Guidelines

Best practices for designing and implementing command-line interface (CLI) applications across programming languages.

CLI Design Principles

Unix Philosophy

Follow the core Unix philosophy for robust, composable CLIs:

  • Do one thing well - Each command should have a single, well-defined responsibility
  • Make it composable - Design output to work as input to other programs
  • Handle text streams - CLIs work with stdin/stdout/stderr
  • Exit cleanly - Use appropriate exit codes (0 for success, non-zero for errors)
  • Fail fast - Detect and report problems immediately
  • Be scriptable - All functionality must be accessible from non-interactive contexts

Command Structure

Verb-Noun Pattern

Organize commands using verb-noun relationships:

# Good: clear action and object
git add <file>           # verb: add, noun: file
docker run <image>       # verb: run, noun: image
npm install <package>    # verb: install, noun: package

# Avoid: unclear structure
git foo                  # ambiguous
docker something         # unclear what happens

Subcommands vs Flags

Use subcommands when:

  • Commands have distinct behaviors or workflows
  • Different sets of options apply to different operations
  • You want clear logical grouping
git branch                    # subcommand
git branch --list             # subcommand with flag
git branch --delete <name>    # subcommand with flag and argument

Use flags when:

  • Modifying behavior of a single operation
  • Toggling optional features
  • Providing configuration
ls --color          # flag modifies ls behavior
grep -r --include   # flags modify search behavior

Never mix deeply:

# Avoid: unclear hierarchy
tool --verbose subcommand --debug --mode strict

# Better: clear structure
tool subcommand --verbose --debug --mode strict

Command Hierarchy

Keep hierarchy shallow (max 2-3 levels):

# Good: clear, discoverable
aws s3 ls
aws ec2 describe-instances

# Avoid: too deep
cloud provider storage list all buckets

Argument Parsing

Argument Types

Positional Arguments

  • Required by default
  • Appear in specific positions
  • Use for primary input/object
cp <source> <destination>   # Two required positional arguments
docker run <image>          # One required positional argument

Optional Arguments (with defaults)

  • Used less frequently
  • Should have sensible defaults
  • Clearly documented
npm install [directory]     # Optional, defaults to current directory
grep [options] <pattern> [file]  # File argument optional

Flags and Options

Short Flags (-f)

  • Single letter, used frequently
  • For common operations
ls -l          # long format
grep -r        # recursive
tar -xvf       # combined flags

Long Flags (--flag)

  • Verbose, self-documenting
  • For less-common operations
  • Preferred in scripts
docker run --detach --name myapp
npm install --save-dev
grep --recursive --include="*.js"

Flag Styles

# Boolean flags
--verbose              # boolean true/false
--color=always         # explicit value

# Flags with values
--output file.txt      # space-separated
--output=file.txt      # equals-separated

# Accept both styles when possible
--timeout 30 or --timeout=30

Environment Variables

Convention: Use SCREAMING_SNAKE_CASE for environment variables

export DEBUG=1
export LOG_LEVEL=debug
export API_TOKEN=secret

Hierarchy (high to low precedence):

  1. Command-line flags (highest priority - most explicit)
  2. Environment variables (middle - applies to multiple invocations)
  3. Configuration file (lower - shared settings)
  4. Built-in defaults (lowest priority - fallback)

Example precedence:

# If user runs with flag, it takes precedence
tool --timeout 10       # Uses 10, ignores TIMEOUT env var

# If no flag, check env var
export TIMEOUT=5
tool                    # Uses 5 from TIMEOUT

# If no flag or env var, use config file default
tool --config config.yaml  # Reads timeout from config.yaml

# If nothing specified, use built-in default
tool                    # Uses hardcoded default (e.g., 30)

Configuration Files

Location convention:

  • Linux/macOS: ~/.config/app/config.yaml or ~/.apprc
  • Windows: %APPDATA%\App\config.yaml or ~\AppData\Local\App\config.yaml
  • All platforms: ./config.yaml (current directory, highest priority)

Format preference: YAML > TOML > JSON > INI

  • YAML is human-readable and concise
  • TOML is simpler than YAML but supports nested structures
  • JSON is universal but verbose
  • INI is legacy

Example config file:

# ~/.config/myapp/config.yaml
debug: false
log_level: info
timeout: 30
output_format: json
color: true

api:
  endpoint: https://api.example.com
  timeout: 10

Load with proper precedence:

# Python example
import os
from pathlib import Path
import yaml

def load_config():
    # Built-in defaults
    config = {
        'debug': False,
        'timeout': 30,
        'color': True
    }

    # Load from config file
    config_path = Path.home() / '.config' / 'myapp' / 'config.yaml'
    if config_path.exists():
        with open(config_path) as f:
            file_config = yaml.safe_load(f)
            config.update(file_config)

    # Override with environment variables
    if os.getenv('DEBUG'):
        config['debug'] = os.getenv('DEBUG').lower() == 'true'
    if os.getenv('TIMEOUT'):
        config['timeout'] = int(os.getenv('TIMEOUT'))

    # Note: Command-line args handled by argument parser, applied after
    return config

User Interface

Help Text

Provide comprehensive help:

$ tool --help
Usage: tool [OPTIONS] COMMAND [ARGS]...

A brief description of what this tool does.

Options:
  -v, --verbose         Increase output verbosity
  -q, --quiet          Suppress non-error output
  -h, --help           Show this message and exit
  --version            Show version and exit

Commands:
  add                  Add a new item
  list                 List all items
  delete              Delete an item
  config              Manage configuration

Examples:
  tool add myitem
  tool list --format json
  tool delete --force

For more information: https://docs.example.com

Help text structure:

  • Usage line: Usage: tool [OPTIONS] COMMAND [ARGS]...
  • Brief description (1-2 lines)
  • Options section (sorted: common first)
  • Commands section (if subcommands exist)
  • Examples section (show common patterns)
  • Additional resources (docs link, contact info)

Per-command help:

$ tool add --help
Usage: tool add [OPTIONS] NAME

Add a new item to the collection.

Options:
  --description TEXT  Item description
  --tags TEXT        Comma-separated tags
  --priority INT     Priority level (1-10)
  -h, --help        Show this message and exit

Examples:
  tool add "My Item"
  tool add "Task" --priority 5 --tags work,urgent

Version Information

Provide version with --version / -v:

$ tool --version
tool 1.2.3

# For complex tools, show component versions
$ tool --version
tool 1.2.3
  dependency-a: 2.1.0
  dependency-b: 5.4.3

Store version in configuration:

# __version__.py or __init__.py
__version__ = "1.2.3"

# In main CLI file
import sys
from . import __version__

@click.command()
@click.option('--version', is_flag=True, help='Show version and exit')
def main(version):
    if version:
        click.echo(f"tool {__version__}")
        sys.exit(0)

Progress Indicators

For long operations, show progress:

# Python: using rich library
from rich.progress import track
import time

for i in track(range(100), description="Processing..."):
    time.sleep(0.1)  # Long operation

Output format:

Processing... [████████░░] 80%
or
Processing... ⠏ (spinning indicator)

Spinners and Progress Bars

Use spinners for indeterminate progress:

Connecting to server... ⠋
Uploading file... ⠙
Waiting for response... ⠹

Use progress bars for determinate progress:

Downloading [████████░░░░░░] 40% (2.1 MB / 5.3 MB)
Processing  [██████████████████░░] 90%

Implement responsibly:

  • Disable in non-interactive environments (detect piped output)
  • Respect --no-progress flag
  • Never output progress to stdout (use stderr)

Color Output

Support color with disable option:

# Python: using rich or click
import click

@click.command()
@click.option('--color', type=click.Choice(['always', 'auto', 'never']),
              default='auto', help='Color output mode')
def main(color):
    # auto: color if terminal supports it
    # always: force color (for piping to less -R)
    # never: disable color
    use_color = color == 'always' or (color == 'auto' and is_terminal())

Environment variable override:

# User can disable via environment
NO_COLOR=1 tool           # Disable color
FORCE_COLOR=1 tool        # Force color

Terminal detection:

import sys
import os

def supports_color():
    # Check NO_COLOR env var
    if os.getenv('NO_COLOR'):
        return False

    # Check FORCE_COLOR env var
    if os.getenv('FORCE_COLOR'):
        return True

    # Check if stdout is a TTY
    return sys.stdout.isatty()

Interactive Prompts

Use for confirmations and user input:

# Python: using click
import click

@click.command()
@click.option('--force', is_flag=True, help='Skip confirmations')
def delete_item(force):
    if not force:
        if not click.confirm('Are you sure you want to delete?'):
            click.echo("Cancelled")
            return

    # Proceed with deletion
    click.echo("Deleted")

# Interactive selection
choice = click.prompt(
    'Choose an option',
    type=click.Choice(['a', 'b', 'c']),
    default='a'
)

Guidelines:

  • Ask for confirmation before destructive operations
  • Provide sensible defaults
  • Allow bypassing with --force flag
  • Never prompt in non-interactive contexts (detect piped input)

Output Formatting

Human-Readable Output

Default format for interactive use:

$ tool list
Name          Status    Modified
────────────────────────────────
Project A     Active    2 hours ago
Project B     Inactive  3 days ago
Project C     Active    1 week ago

Characteristics:

  • Table format with clear columns
  • Natural language (e.g., "2 hours ago")
  • Colors for emphasis (when supported)
  • Sorted intelligently

Machine-Readable Output

JSON format for scripting:

$ tool list --format json
[
  {
    "name": "Project A",
    "status": "active",
    "modified": "2024-01-15T14:30:00Z"
  },
  {
    "name": "Project B",
    "status": "inactive",
    "modified": "2024-01-12T09:15:00Z"
  }
]

YAML format (compact, readable):

$ tool list --format yaml
- name: Project A
  status: active
  modified: 2024-01-15T14:30:00Z
- name: Project B
  status: inactive
  modified: 2024-01-12T09:15:00Z

CSV format (for spreadsheets):

$ tool list --format csv
name,status,modified
"Project A",active,2024-01-15T14:30:00Z
"Project B",inactive,2024-01-12T09:15:00Z

Implementation:

import click
import json
import csv
import io

@click.command()
@click.option('--format', type=click.Choice(['table', 'json', 'yaml', 'csv']),
              default='table', help='Output format')
def list_items(format):
    items = get_items()

    if format == 'json':
        click.echo(json.dumps(items, indent=2))
    elif format == 'csv':
        output = io.StringIO()
        writer = csv.DictWriter(output, fieldnames=items[0].keys())
        writer.writeheader()
        writer.writerows(items)
        click.echo(output.getvalue())
    elif format == 'yaml':
        import yaml
        click.echo(yaml.dump(items, default_flow_style=False))
    else:  # table
        display_table(items)

Exit Codes

Standard exit codes:

0   - Success, no errors
1   - General error
2   - Misuse of shell command (invalid arguments)
3-125 - Application-specific errors
126 - Command cannot execute
127 - Command not found
128+130 - Fatal signal "N"
130 - Script terminated by Ctrl+C

Common application codes:

0   - Success
1   - General/unspecified error
2   - Misuse of command syntax
64  - Bad input data
65  - Data format error
66  - No input file
69  - Service unavailable
70  - Internal software error
71  - System error
77  - Permission denied
78  - Configuration error

Implementation:

import click
import sys

@click.command()
def main():
    try:
        result = do_work()
        if not result:
            sys.exit(1)  # Error condition
    except ValueError as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(2)  # Bad input
    except PermissionError as e:
        click.echo(f"Permission denied: {e}", err=True)
        sys.exit(77)
    except Exception as e:
        click.echo(f"Internal error: {e}", err=True)
        sys.exit(70)

    sys.exit(0)  # Success

Always exit explicitly:

# Good: script can detect success/failure
tool && echo "Success" || echo "Failed"

# Bad: unclear exit status
tool
echo "Done"  # Prints regardless of success

Error Handling

Clear Error Messages

Good error messages:

  • Concise (one line if possible)
  • Specific about what went wrong
  • Suggest how to fix it
# Good
$ tool add --priority invalid
Error: --priority must be a number (1-10), got 'invalid'

# Bad
$ tool add --priority invalid
Error: Invalid argument

# Good
$ tool delete nonexistent
Error: Item 'nonexistent' not found. Use 'tool list' to see available items.

# Bad
$ tool delete nonexistent
File not found

Error format:

Error: [what happened]. [How to fix it or where to learn more].

Suggestions for Fixes

Provide actionable suggestions:

import click

@click.command()
@click.argument('command')
def main(command):
    valid_commands = ['add', 'list', 'delete']

    if command not in valid_commands:
        # Suggest closest match
        from difflib import get_close_matches
        suggestions = get_close_matches(command, valid_commands, n=1)

        msg = f"Unknown command '{command}'."
        if suggestions:
            msg += f" Did you mean '{suggestions[0]}'?"
        else:
            msg += f" Available commands: {', '.join(valid_commands)}"

        click.echo(f"Error: {msg}", err=True)
        raise SystemExit(1)

Debug Mode

Provide --debug flag for detailed output:

import click
import sys
import traceback

@click.command()
@click.option('--debug', is_flag=True, help='Show detailed error information')
def main(debug):
    try:
        do_work()
    except Exception as e:
        if debug:
            # Show full traceback
            traceback.print_exc(file=sys.stderr)
        else:
            # Show user-friendly message
            click.echo(f"Error: {str(e)}", err=True)
            click.echo("Use --debug to see details", err=True)
        sys.exit(1)

Example output:

$ tool --debug
Error: Connection failed
Traceback (most recent call last):
  File "tool.py", line 42, in main
    connect_to_server()
  File "tool.py", line 15, in connect_to_server
    raise ConnectionError("Timeout after 30s")
ConnectionError: Timeout after 30s

Cross-Platform Considerations

Path Separators

Always use proper path handling:

# Good: use pathlib (cross-platform)
from pathlib import Path

config_path = Path.home() / '.config' / 'app' / 'config.yaml'
output_path = Path('output') / 'result.txt'

# Also good: use os.path
import os
config_path = os.path.join(os.path.expanduser('~'), '.config', 'app', 'config.yaml')

# Avoid: hardcoded separators
config_path = '~/.config/app/config.yaml'  # Wrong on Windows

Node.js example:

const path = require('path');
const os = require('os');

const configPath = path.join(os.homedir(), '.config', 'app', 'config.yaml');
const outputPath = path.join('output', 'result.txt');

Line Endings

Normalize line endings:

# Python: open files with universal newline support (default)
with open('file.txt', 'r') as f:  # Automatically handles \r\n, \n, \r
    content = f.read()

# When writing, use default newline handling
with open('file.txt', 'w') as f:
    f.write(content)  # Uses platform default newline

Git configuration:

# Set in .gitattributes to normalize line endings
* text=auto
*.py text eol=lf
*.json text eol=lf

Terminal Capabilities

Detect terminal capabilities:

import sys
import os

def get_terminal_width():
    """Get terminal width, default to 80."""
    try:
        return os.get_terminal_size().columns
    except (AttributeError, ValueError):
        return 80

def supports_unicode():
    """Check if terminal supports Unicode."""
    encoding = sys.stdout.encoding or 'utf-8'
    return encoding.lower() in ('utf-8', 'utf8')

def supports_color():
    """Check if terminal supports colors."""
    # Already covered above
    return True

# Use capabilities to adjust output
if supports_unicode():
    symbol = '✓'  # Check mark
else:
    symbol = '✔'  # Alternative

Avoid assumptions:

# Bad: assumes capabilities
output = "✓ Success\n"

# Good: checks capabilities
symbol = '✓' if supports_unicode() else '✔'
output = f"{symbol} Success\n"

Testing CLI Applications

Integration Tests

Test the complete CLI, not just functions:

import subprocess
import json

def test_list_command():
    """Test list command output."""
    result = subprocess.run(
        ['tool', 'list', '--format', 'json'],
        capture_output=True,
        text=True
    )

    assert result.returncode == 0
    output = json.loads(result.stdout)
    assert isinstance(output, list)

def test_list_command_human_readable():
    """Test list command with default format."""
    result = subprocess.run(
        ['tool', 'list'],
        capture_output=True,
        text=True
    )

    assert result.returncode == 0
    assert 'Name' in result.stdout  # Table header

Test with Click (Python):

from click.testing import CliRunner
from tool.cli import main

def test_list_command():
    runner = CliRunner()
    result = runner.invoke(main, ['list', '--format', 'json'])

    assert result.exit_code == 0
    output = json.loads(result.output)
    assert isinstance(output, list)

Output Verification

Verify output format and content:

import subprocess

def test_add_command_success():
    """Test successful add operation."""
    result = subprocess.run(
        ['tool', 'add', 'Test Item', '--priority', '5'],
        capture_output=True,
        text=True
    )

    assert result.returncode == 0
    assert 'Added' in result.stdout or 'Success' in result.stdout

def test_add_command_invalid_priority():
    """Test validation of priority argument."""
    result = subprocess.run(
        ['tool', 'add', 'Test Item', '--priority', 'invalid'],
        capture_output=True,
        text=True
    )

    assert result.returncode != 0
    assert 'Error' in result.stderr
    assert 'priority' in result.stderr.lower()

Exit Code Checks

Verify proper exit codes:

import subprocess

def test_help_flag_exit_code():
    """Test --help returns success."""
    result = subprocess.run(['tool', '--help'], capture_output=True)
    assert result.returncode == 0

def test_invalid_command_exit_code():
    """Test invalid command returns error code."""
    result = subprocess.run(['tool', 'invalid'], capture_output=True)
    assert result.returncode != 0

def test_missing_required_arg():
    """Test missing required argument returns error."""
    result = subprocess.run(['tool', 'delete'], capture_output=True)
    assert result.returncode == 2  # Misuse of command syntax

Documentation

README

CLI tools need clear README documentation:

# Tool Name

Brief description of what the tool does.

## Installation

Installation instructions (package manager, build from source, etc.)

## Usage

### Basic Usage

```bash
tool [COMMAND] [OPTIONS] [ARGUMENTS]

Examples

# List all items
tool list

# Add new item
tool add "My Item" --priority 5

# Delete with confirmation
tool delete "My Item"

# Suppress confirmation
tool delete "My Item" --force

Commands

  • add [NAME] - Add a new item
  • list - List all items
  • delete [NAME] - Delete an item
  • config - Manage configuration

Options

  • -v, --verbose - Increase output verbosity
  • --format [json|yaml|csv] - Output format
  • --debug - Show debug information
  • -h, --help - Show help message
  • --version - Show version

Configuration

Settings can be configured via:

  1. Command-line flags (highest priority)
  2. Environment variables
  3. Config file at ~/.config/tool/config.yaml
  4. Built-in defaults

Environment Variables

  • DEBUG - Enable debug mode
  • TOOL_CONFIG - Path to config file
  • NO_COLOR - Disable colored output

Configuration File

Create ~/.config/tool/config.yaml:

debug: false
format: table
color: true

Troubleshooting

Common Issues

Issue: "command not found"

  • Ensure tool is installed and in PATH
  • Check: which tool

Issue: Permission denied

  • Make script executable: chmod +x tool

Development

Build and test instructions.


### Man Pages

**Create man pages for Unix systems:**

TOOL(1) User Commands TOOL(1)

NAME tool - A tool that does one thing well

SYNOPSIS tool [OPTIONS] COMMAND [ARGS]...

DESCRIPTION Detailed description of what the tool does.

COMMANDS add NAME Add a new item list List all items delete NAME Delete an item

OPTIONS -v, --verbose Increase output verbosity --format FMT Output format (json, yaml, csv) -h, --help Show help message --version Show version

EXAMPLES Add a new item: tool add "My Item"

List items as JSON:
    tool list --format json

EXIT STATUS 0 Success 1 General error 2 Invalid arguments

FILES ~/.config/tool/config.yaml Configuration file

SEE ALSO tool-add(1), tool-delete(1)


### Built-in Help

**Make help accessible and comprehensive:**

```python
@click.group()
def main():
    """Main CLI tool."""
    pass

@main.command()
def help():
    """Show detailed help information."""
    click.echo(click.get_current_context().get_help())

Common CLI Libraries

Python

Click - Most popular, decorator-based

import click

@click.command()
@click.option('--name', prompt='Your name', help='Name of person')
@click.option('--count', default=1, help='Number of greetings')
def hello(name, count):
    """Simple program that greets NAME COUNT times."""
    for _ in range(count):
        click.echo(f'Hello {name}!')

if __name__ == '__main__':
    hello()

Typer - Built on Click, modern with async support

import typer

app = typer.Typer()

@app.command()
def add(name: str, priority: int = typer.Option(5, min=1, max=10)):
    """Add a new item."""
    print(f"Added {name} with priority {priority}")

if __name__ == "__main__":
    app()

Argparse - Built-in to Python, more verbose

import argparse

parser = argparse.ArgumentParser(description='Process some integers')
parser.add_argument('--name', required=True, help='Name')
parser.add_argument('--count', type=int, default=1, help='Count')
args = parser.parse_args()

Node.js / JavaScript

Commander - Minimal and clean

const { Command } = require('commander');

const program = new Command();

program
  .command('add <name>')
  .option('--priority <number>', 'Item priority', '5')
  .action((name, options) => {
    console.log(`Added ${name} with priority ${options.priority}`);
  });

program.parse(process.argv);

Yargs - Feature-rich

const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

yargs(hideBin(process.argv))
  .command('add <name>', 'Add item', (yargs) => {
    return yargs.option('priority', { type: 'number', default: 5 });
  }, (argv) => {
    console.log(`Added ${argv.name} with priority ${argv.priority}`);
  })
  .argv;

Oclif - Full-featured framework for complex CLIs

const {Command, flags} = require('@oclif/command');

class AddCommand extends Command {
  static description = 'Add a new item';

  static args = [{ name: 'name' }];

  static flags = {
    priority: flags.integer({ default: 5 })
  };

  async run() {
    const { args, flags } = this.parse(AddCommand);
    this.log(`Added ${args.name} with priority ${flags.priority}`);
  }
}

module.exports = AddCommand;

Go

Cobra - Popular Go CLI framework

var rootCmd = &cobra.Command{
  Use:   "tool",
  Short: "A brief description",
  Long:  "A longer description...",
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("Hello World!")
  },
}

var addCmd = &cobra.Command{
  Use:   "add [name]",
  Short: "Add a new item",
  Args:  cobra.MinimumNArgs(1),
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Printf("Added %s\n", args[0])
  },
}

func init() {
  rootCmd.AddCommand(addCmd)
  addCmd.Flags().IntP("priority", "p", 5, "Priority level")
}

Rust

Clap - Powerful and flexible

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[clap(name = "tool")]
#[clap(about = "A tool that does one thing well")]
struct Cli {
    #[clap(subcommand)]
    command: Option<Commands>,

    #[clap(short, long)]
    debug: bool,
}

#[derive(Subcommand)]
enum Commands {
    Add {
        name: String,
        #[clap(short, long, default_value = "5")]
        priority: u8,
    },
    List,
}

fn main() {
    let cli = Cli::parse();
    // Handle commands...
}

Common Patterns and Examples

Pattern: Global Options vs Subcommand Options

# Global options apply to tool
tool --verbose add item

# Subcommand options apply to subcommand
tool add item --priority 5

# Mix of both
tool --verbose add item --priority 5

Pattern: Piping and Composition

# Pipe output to other tools
tool list --format json | jq '.[] | select(.status == "active")'

# Use as input to other commands
tool export > backup.json
tool import < backup.json

# Chain commands
tool list | grep important | while read item; do tool process "$item"; done

Pattern: Interactive vs Non-interactive

import sys
import click

@click.command()
@click.option('--force', is_flag=True, help='Skip confirmations')
def delete_item(force):
    if sys.stdin.isatty() and not force:
        # Interactive - prompt for confirmation
        if not click.confirm('Delete this item?'):
            click.echo('Cancelled')
            return

    # Non-interactive or confirmed
    perform_deletion()

Pattern: Timeout Handling

import signal
import sys

def timeout_handler(signum, frame):
    print("Operation timed out")
    sys.exit(124)

signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(30)  # 30 second timeout

try:
    result = long_running_operation()
finally:
    signal.alarm(0)  # Cancel alarm

Note: For project-specific CLI patterns, check .claude/CLAUDE.md in the project directory.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

claude-code-workflow

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

flask-workflow

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

container-workflow

No summary provided by upstream source.

Repository SourceNeeds Review