mcpField
C
cli-ux-patterns

cli-ux-patterns

github.com/geoffjayデザイン・UI838 回インストール51 週間前に更新
リポジトリ

cli-ux-patterns について

cli-ux-patternsは、CLIアプリを開発する人向けのコマンドラインUX設計スキルです。エラー表示、出力、対話、アクセシビリティ、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 パターンのスキル

魅力的なコマンドラインユーザー体験を実現するためのベストプラクティスとパターンです。

エラーメッセージのパターン

優れたエラーメッセージを構成する3つの要素
  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!");

対話型プロンプトのパターン

プロンプトを表示するか、エラーにするか

プロンプトを表示する場合:

  • UX の向上に役立つ任意の情報
  • 既知の選択肢からの選択
  • 破壊的な操作の確認
  • 初回のセットアップ/初期化

エラーにする場合:

  • 必須情報がない
  • 非対話型環境(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

ベストプラクティス
  • 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);
}
色覚多様性に関する考慮事項
  • 色だけに頼らない
  • 色とともに記号/アイコンを使う
  • 色覚シミュレーターでテストする
  • テキストによる代替表現を用意する

12-Factor CLI の原則

  1. 優れたヘルプ - 包括的で、見つけやすい
  2. 引数よりフラグを優先する - より明示的にする
  3. POSIX を尊重する - 慣習に従う
  4. 出力には stdout を使う - パイプ接続を可能にする
  5. メッセージには stderr を使う - 出力をクリーンに保つ
  6. シグナルを処理する - Ctrl+C に適切に対応する
  7. デフォルトでは静かにする - 詳細度はユーザーが制御する
  8. 速やかに失敗する - 早い段階で検証する
  9. --help と --version をサポートする - 常に対応する
  10. 明示的にする - 予想外の動作を避ける
  11. 一貫性を保つ - パターンに従う
  12. 使いやすくする - 適切なデフォルト値と明確なエラー

参考資料

コメント