Jonatan Matajonmatum.com
conceptsnotesexperimentsessays
© 2026 Jonatan Mata. All rights reserved.v2.1.1
Concepts

CLI Design

Principles for designing intuitive, consistent, and productive command-line interfaces that developers enjoy using.

evergreen#cli#design#terminal#developer-experience#ux#tooling

What it is

CLI (Command Line Interface) design is the art of creating command-line tools that developers use with pleasure and efficiency. A well-designed CLI follows established Unix conventions, provides clear feedback, and is discoverable without reading extensive documentation.

Unlike graphical interfaces, CLIs prioritize speed, composability, and automation. They are the preferred interface for developers, CI/CD scripts, and system administration tasks because they enable fast and repeatable operations.

The best CLIs combine power with simplicity: they offer advanced functionality through flags and subcommands, but keep common use cases simple and straightforward.

Fundamental principles

PrincipleDescriptionImplementation
Unix conventionsFollow established standards-v/--verbose, exit codes, pipes
Immediate feedbackCommunicate status and progressProgress bars, spinners, colors
DiscoverabilityEasy to learn and exploreUseful --help, command suggestions
ComposabilityCompatible with pipes and scriptsJSON output, stdin/stdout
IdempotencyConsistent resultscreate --if-not-exists
Graceful degradationWork in limited environmentsDetect TTY, colorless fallbacks

Implementation with Click (Python)

import click
from typing import Optional
 
@click.group()
@click.version_option()
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
@click.pass_context
def cli(ctx: click.Context, verbose: bool) -> None:
    """Modern CLI tool for project management."""
    ctx.ensure_object(dict)
    ctx.obj['verbose'] = verbose
 
@cli.command()
@click.argument('name')
@click.option('--template', '-t', 
              type=click.Choice(['basic', 'advanced']), 
              default='basic',
              help='Project template to use')
@click.option('--force', is_flag=True, 
              help='Overwrite existing project')
@click.pass_context
def create(ctx: click.Context, name: str, template: str, force: bool) -> None:
    """Create a new project."""
    if ctx.obj['verbose']:
        click.echo(f"Creating project '{name}' with template '{template}'")
    
    # Validation with clear messages
    if not force and project_exists(name):
        click.echo(f"Error: Project '{name}' already exists. Use --force to overwrite.", err=True)
        ctx.exit(1)
    
    # Progress feedback
    with click.progressbar(range(100), label='Setting up project') as bar:
        for i in bar:
            setup_step(i)
    
    click.echo(f"✅ Project '{name}' created successfully!")
 
@cli.command()
@click.option('--format', type=click.Choice(['table', 'json']), 
              default='table', help='Output format')
def list(format: str) -> None:
    """List all projects."""
    projects = get_projects()
    
    if format == 'json':
        click.echo(json.dumps(projects))
    else:
        # Formatted table for humans
        for project in projects:
            click.echo(f"{project['name']:<20} {project['status']}")
 
if __name__ == '__main__':
    cli()

Help design patterns

Contextual help

# General help
$ myapp --help
 
# Subcommand-specific help
$ myapp create --help
 
# Error suggestions
$ myapp creat project1
Error: No such command 'creat'. Did you mean 'create'?

Help message structure

Usage: myapp [OPTIONS] COMMAND [ARGS]...

  Modern CLI tool for project management.

Options:
  -v, --verbose    Enable verbose output
  --version        Show version and exit
  --help           Show this message and exit

Commands:
  create  Create a new project
  list    List all projects
  deploy  Deploy project to environment

Interactive prompts

@cli.command()
def setup() -> None:
    """Interactive setup wizard."""
    # Prompt with validation
    name = click.prompt('Project name', 
                       type=str,
                       value_proc=lambda x: x.strip().lower())
    
    # Confirmation with default
    use_git = click.confirm('Initialize git repository?', default=True)
    
    # Multiple choice
    template = click.prompt(
        'Choose template',
        type=click.Choice(['basic', 'web', 'api']),
        show_choices=True
    )
    
    # Hidden password
    if click.confirm('Set up authentication?'):
        token = click.prompt('API token', hide_input=True)
    
    click.echo(f"Setting up '{name}' with {template} template...")

Shell completion

# Bash completion
eval "$(_MYAPP_COMPLETE=bash_source myapp)"
 
# Zsh completion  
eval "$(_MYAPP_COMPLETE=zsh_source myapp)"
 
# Fish completion
eval (env _MYAPP_COMPLETE=fish_source myapp)

Click implementation:

# Custom autocompletion
def complete_projects(ctx, param, incomplete):
    """Autocomplete existing project names."""
    projects = get_projects()
    return [p['name'] for p in projects if p['name'].startswith(incomplete)]
 
@click.argument('project', autocompletion=complete_projects)
def deploy(project: str) -> None:
    """Deploy project."""
    pass

Frameworks by language

LanguageFrameworkStrengths
PythonClickDecorators, types, testing
PythonTyperType hints, async support
Node.jsCommander.jsLightweight, flexible
Node.jsoclifEnterprise, plugins
GoCobraPerformance, distribution
RustclapType safety, performance
RubyThorExpressive DSL

Common anti-patterns

# ❌ Inconsistent flags
myapp --verbose create
myapp deploy -v  # Should be --verbose too
 
# ❌ Unparseable output
myapp list
Project: web-app (status: running, created: yesterday)
 
# ✅ Structured output
myapp list --json
{"projects": [{"name": "web-app", "status": "running", "created": "2026-03-18"}]}
 
# ❌ Vague error messages
Error: Something went wrong
 
# ✅ Actionable error messages
Error: Project 'web-app' not found. Run 'myapp list' to see available projects.

Scalable CLI architecture

Loading diagram...

Why it matters

A well-designed CLI is the difference between a tool that gets widely adopted and one that gets abandoned. Senior developers evaluate CLIs in seconds: if they don't follow Unix conventions, if error messages are vague, or if there's no autocompletion, they look for alternatives.

Developer experience is defined by these details: response time, message clarity, and automation capability. An excellent CLI reduces cognitive friction and enables more efficient workflows.

In large organizations, poorly designed internal CLIs generate constant support tickets and reduce team productivity. Investing in good CLI design is investing in team development velocity.

References

  • Command Line Interface Guidelines — Comprehensive guide to CLI best practices.
  • GitHub - tj/commander.js: node.js command-line interfaces made easy — TJ Holowaychuk, 2024. Popular framework for Node.js CLIs.
  • Welcome to Click — Click Documentation (8.3.x) — Pallets Team, 2024. Python framework for CLIs with decorators.
  • clap - Rust — Rust community, 2024. Argument parser for Rust with derive macros.
  • GitHub - spf13/cobra: A Commander for modern Go CLI interactions — Steve Francia, 2024. CLI framework used by kubectl, hugo and others.
  • GitHub - oclif/oclif: CLI for generating, building, and releasing oclif CLIs. Built by Salesforce. — Salesforce, 2024. Enterprise framework for CLIs with plugin system.
  • argparse — Parser for command-line options, arguments and subcommands — Python 3.14.3 documentation — Python Software Foundation, 2024. Python standard library for argument parsing.

Related content

  • Developer Experience

    Discipline focused on optimizing developer productivity, satisfaction, and effectiveness through well-designed tools, processes, and environments.

  • SDK Design

    Principles for designing development kits that are intuitive, consistent, and facilitate service integration across multiple programming languages.

  • API Design

    Principles and practices for designing clear, consistent, and evolvable programming interfaces that facilitate integration between systems.

  • Terminal UI

    Terminal-style design system with Matrix and TRON themes, Konami code integration, and micro frontend support. Published on npm as @jonmatum/terminal-ui.

Concepts