mcpField
C
cli-ux-patterns

cli-ux-patterns

github.com/geoffjay设计与 UI838 次安装51 周前更新
代码仓库

关于 cli-ux-patterns

cli-ux-patterns 是面向 CLI 应用开发者的命令行体验设计技能,涵盖错误提示、输出、交互、无障碍设计及 CLI 惯例。

主要功能

  1. 01可操作的错误提示模式
  2. 02语义化颜色与 NO_COLOR 指引
  3. 03进度条与旋转指示器
  4. 04交互式提示与智能默认值
  5. 05面向人类和机器的输出
  6. 06无障碍设计与 12-Factor CLI 原则

适用场景

  1. 01设计清晰的错误与恢复步骤
  2. 02为操作选择进度指示方式
  3. 03组织适用于管道和脚本的输出
  4. 04提升终端状态信息的无障碍性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/geoffjay/claude-plugins --skill cli-ux-patterns

安装后在 Claude Code 中输入/cli-ux-patterns确认已加载。

详细介绍

CLI UX Patterns Skill

打造出色命令行用户体验的最佳实践与模式。

错误消息模式

优质错误消息的三个组成部分
  1. 出了什么问题 - 清晰描述错误
  2. 为何重要 - 说明操作背景
  3. 如何修复 - 提供可执行的建议
bail!(
    "Failed to read config file: {}\n\n\
     The application needs a valid configuration to start.\n\n\
     To fix this:\n\
     1. Create a config file: myapp init\n\
     2. Or specify a different path: --config /path/to/config.toml\n\
     3. Check file permissions: ls -l {}",
    path.display(),
    path.display()
);
使用 miette 提供丰富的诊断信息
#[derive(Error, Debug, Diagnostic)]
#[error("Configuration error")]
#[diagnostic(
    code(config::invalid),
    url("https://docs.example.com/config"),
    help("Check the syntax of your configuration file")
)]
struct ConfigError {
    #[source_code]
    src: String,

    #[label("invalid value here")]
    span: SourceSpan,
}

颜色使用模式

语义颜色
  • 红色 - 错误、失败、破坏性操作
  • 黄色 - 警告、注意事项
  • 绿色 - 成功、完成、安全操作
  • 蓝色 - 信息、提示、链接
  • 青色 - 突出显示、强调
  • 暗色/灰色 - 次要信息、元数据
use owo_colors::OwoColorize;

// Status indicators with colors
println!("{} Build succeeded", "✓".green().bold());
println!("{} Warning: using default", "⚠".yellow().bold());
println!("{} Error: file not found", "✗".red().bold());
println!("{} Info: processing 10 files", "ℹ".blue().bold());
遵循 NO_COLOR 规范
use owo_colors::{OwoColorize, Stream};

fn print_status(message: &str, is_error: bool) {
    let stream = if is_error { Stream::Stderr } else { Stream::Stdout };

    if is_error {
        eprintln!("{}", message.if_supports_color(stream, |text| text.red()));
    } else {
        println!("{}", message.if_supports_color(stream, |text| text.green()));
    }
}

进度提示模式

何时使用进度条
  • 文件下载/上传
  • 已知数量的批量处理
  • 多步骤流程
  • 已知总量且耗时超过 2 秒的操作
use indicatif::{ProgressBar, ProgressStyle};

let pb = ProgressBar::new(items.len() as u64);
pb.set_style(
    ProgressStyle::default_bar()
        .template("{spinner:.green} [{bar:40}] {pos}/{len} {msg}")?
        .progress_chars("=>-")
);

for item in items {
    pb.set_message(format!("Processing {}", item.name));
    process(item)?;
    pb.inc(1);
}

pb.finish_with_message("Complete!");
何时使用旋转指示器
  • 时长未知的操作
  • 等待外部资源
  • 耗时少于 2 秒的操作
  • 无法确定进度的操作
let spinner = ProgressBar::new_spinner();
spinner.set_style(
    ProgressStyle::default_spinner()
        .template("{spinner:.green} {msg}")?
);

spinner.set_message("Connecting to server...");
// Do work
spinner.finish_with_message("Connected!");

交互式提示模式

何时提示,何时报错退出

在以下情况提示:

  • 可选信息有助于改善用户体验
  • 从已知选项中进行选择
  • 破坏性操作需要确认
  • 首次设置/初始化

在以下情况报错退出:

  • 缺少必需信息
  • 非交互式环境(CI/CD)
  • 输入/输出通过管道传递
  • 已提供 --yes 标志
use dialoguer::Confirm;

fn delete_resource(name: &str, force: bool) -> Result<()> {
    if !force && atty::is(atty::Stream::Stdin) {
        let confirmed = Confirm::new()
            .with_prompt(format!("Delete {}? This cannot be undone", name))
            .default(false)
            .interact()?;

        if !confirmed {
            println!("Cancelled");
            return Ok(());
        }
    }

    // Perform deletion
    Ok(())
}
智能默认值
use dialoguer::Input;

fn get_project_name(current_dir: &Path) -> Result<String> {
    let default = current_dir
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("my-project");

    Input::new()
        .with_prompt("Project name")
        .default(default.to_string())
        .interact_text()
}

输出格式模式

面向用户的输出与机器可读输出
#[derive(Parser)]
struct Cli {
    #[arg(long)]
    json: bool,

    #[arg(short, long)]
    verbose: bool,
}

fn print_results(results: &[Item], cli: &Cli) {
    if cli.json {
        // Machine-readable
        println!("{}", serde_json::to_string_pretty(&results).unwrap());
    } else {
        // Human-readable
        for item in results {
            println!("{} {} - {}",
                if item.active { "✓".green() } else { "✗".red() },
                item.name.bold(),
                item.description.dimmed()
            );
        }
    }
}
表格输出
use comfy_table::{Table, Cell, Color};

fn print_table(items: &[Item]) {
    let mut table = Table::new();
    table.set_header(vec!["Name", "Status", "Created"]);

    for item in items {
        let status_color = if item.active { Color::Green } else { Color::Red };
        table.add_row(vec![
            Cell::new(&item.name),
            Cell::new(&item.status).fg(status_color),
            Cell::new(&item.created),
        ]);
    }

    println!("{table}");
}

详细程度模式

渐进式呈现
fn log_message(level: u8, quiet: bool, message: &str) {
    match (level, quiet) {
        (_, true) => {}, // Quiet mode: no output
        (0, false) => {}, // Default: only errors
        (1, false) => println!("{}", message), // -v: basic info
        (2, false) => println!("INFO: {}", message), // -vv: detailed
        _ => println!("[DEBUG] {}", message), // -vvv: everything
    }
}
静默模式
#[derive(Parser)]
struct Cli {
    #[arg(short, long)]
    quiet: bool,

    #[arg(short, long, action = ArgAction::Count, conflicts_with = "quiet")]
    verbose: u8,
}

确认模式

破坏性操作
// Always require confirmation for:
// - Deleting data
// - Overwriting files
// - Production deployments
// - Irreversible operations

fn deploy_to_production(force: bool) -> Result<()> {
    if !force {
        println!("{}", "WARNING: Deploying to PRODUCTION".red().bold());
        println!("This will affect live users.");

        let confirmed = Confirm::new()
            .with_prompt("Are you absolutely sure?")
            .default(false)
            .interact()?;

        if !confirmed {
            return Ok(());
        }
    }

    // Deploy
    Ok(())
}

标准输出与标准错误

最佳实践
  • stdout - 程序输出、数据、结果
  • stderr - 错误、警告、进度、诊断信息
// Correct usage
println!("result: {}", data); // stdout - actual output
eprintln!("Error: {}", error); // stderr - error message
eprintln!("Processing..."); // stderr - progress update

// This allows piping output while seeing progress:
// myapp process file.txt | other_command
// (progress messages don't interfere with piped data)

无障碍注意事项

便于屏幕阅读器使用
// Always include text prefixes, not just symbols
fn print_status(level: Level, message: &str) {
    let (symbol, prefix) = match level {
        Level::Success => ("✓", "SUCCESS:"),
        Level::Error => ("✗", "ERROR:"),
        Level::Warning => ("⚠", "WARNING:"),
        Level::Info => ("ℹ", "INFO:"),
    };

    // Both symbol and text for accessibility
    println!("{} {} {}", symbol, prefix, message);
}
色盲注意事项
  • 不要仅依赖颜色
  • 配合颜色使用符号/图标
  • 使用色盲模拟器进行测试
  • 提供文本替代内容

CLI 的 12-Factor 原则

  1. 完善的帮助信息 - 全面且易于发现
  2. 优先使用标志而非参数 - 表意更明确
  3. 遵循 POSIX - 遵循惯例
  4. 使用 stdout 输出 - 支持管道传递
  5. 使用 stderr 发送消息 - 保持输出整洁
  6. 处理信号 - 妥善响应 Ctrl+C
  7. 默认保持静默 - 由用户控制详细程度
  8. 快速失败 - 尽早验证
  9. 支持 --help 和 --version - 始终支持
  10. 明确直白 - 避免令人意外的行为
  11. 保持一致 - 遵循既定模式
  12. 让操作简单 - 提供良好的默认值和清晰的错误信息

参考资料

评论

支持 Markdown