mcpField
G
go-cli

go-cli

github.com/eduardo-sl開発8395 回インストール2 週間前に更新
リポジトリ

go-cli について

Go 開発者向けのコマンドライン設計スキルです。ユーザーとスクリプトの双方で扱いやすい、テスト可能なツールを作るため、標準ストリーム、フラグ、終了コード、キャンセル、テスト、バージョン表示、シェル補完を扱います。

主な機能

  1. 01テスト可能な run 関数の構成
  2. 02stdout と stderr の使い分け
  3. 03フラグ、引数、終了コード
  4. 04シグナルによるコンテキストキャンセル
  5. 05人と機械向けの出力
  6. 06テスト、バージョン情報、補完

活用シーン

  1. 01直接ユニットテストできる CLI の構成
  2. 02パイプ出力と診断情報の分離
  3. 03Ctrl+C によるコマンドのキャンセル
  4. 04CLI の動作と終了コードの検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-cli

インストール後、Claude Code で/go-cliを入力して読み込まれたか確認します。

同梱ファイルreferences/testing-and-versioning.md

詳細

Go CLI 設計

優れた CLI は、Unix の作法を守ります。魔法のような動作よりフラグを優先し、データは stdout に、診断情報は stderr に出力し、スクリプトから信頼できる終了コードを返し、Ctrl+C で確実に停止します。

詳細なリファレンス資料は、必要に応じて読み込みます。

  • references/testing-and-versioning.md — main での終了コードの対応付け、ゴールデンファイルテストと testscript テスト、ビルド情報からのバージョン取得、補完。

以下の概要だけでは不十分な場合に限り、リファレンスファイルを読んでください。

1. 構成:テスト可能な main

func main() {
    ctx, stop := signal.NotifyContext(context.Background(),
        os.Interrupt, syscall.SIGTERM)
    defer stop()

    if err := run(ctx, os.Args[1:], os.Stdin, os.Stdout, os.Stderr); err != nil {
        fmt.Fprintln(os.Stderr, "error:", err)
        os.Exit(1)
    }
}

func run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
    fs := flag.NewFlagSet("mytool", flag.ContinueOnError)
    fs.SetOutput(stderr)
    verbose := fs.Bool("v", false, "verbose output")
    out := fs.String("o", "-", "output file (- for stdout)")
    if err := fs.Parse(args); err != nil {
        return err
    }
    // ...
    _ = verbose
    _ = out
    return nil
}
  • signal.NotifyContext を使うと、Ctrl+C でコンテキストがキャンセルされます。長時間かかる処理にはすべて ctx を渡し、正常に停止できるようにします。コンテキストが完了したら stop() を呼び出し、2 回目の Ctrl+C で停止しないプログラムをすぐに終了させます。
  • run は引数とストリームを受け取ります。テストでは strings.Reader/bytes.Buffer を使って直接呼び出せるため、サブプロセスは必要ありません。
  • os.Exit を使うのは main だけにします(os.Exit は defer を実行しません)。

2. stdout と stderr

  • stdout:プログラムの出力。データ、結果、パイプに渡す内容です。
  • stderr:ログ、進行状況、警告、使用方法に関するエラーです。
  • --json またはパイプの検出(!term.IsTerminal(int(os.Stdout.Fd())))では装飾を抑制します。ただし、データは決して変更しません。
// ✅ Good — result to stdout, progress to stderr
fmt.Fprintf(stderr, "processed %d files\n", n)
fmt.Fprintln(stdout, result)

// ❌ Bad — mixing both into stdout breaks every pipe
fmt.Printf("processing...\ndone: %s\n", result)

3. 終了コード

コード意味
0成功
1一般的な実行時エラー
2使用方法のエラー(フラグ/引数の誤り)— flag パッケージの慣例
>2ツール固有の、文書化された意味(例:grep の 1 は一致なし)

エラーとコードの対応付けは、os.Exit の呼び出しをあちこちに散らさず、1 か所(main)で行います。flag.ContinueOnError では、-h は flag.ErrHelp を返します。これに対しては 0 で終了し、解析エラーは使用方法エラー型でラップして、main が 2 で終了できるようにします。Ctrl+C の後は 130 で終了します。スクリプトで異なる失敗ごとに処理を分岐する場合は、センチネルエラーを定義して変換します:errors.Is(err, ErrNoMatch) → 1。

4. フラグと引数

  • オプションにはフラグを、主なオペランドには位置引数を使います: mytool -v convert input.yaml。mytool --input=input.yaml ではありません。
  • すべてのフラグに使用方法の説明を付けます。-h/-help の出力は、主要な UX です。
  • ファイル引数では、- を「stdin/stdout」として受け付けます。
  • デフォルトは安全にします。破壊的な動作には明示的なフラグ(--force)を必須とし、デフォルトで有効にしてはいけません。
  • シークレットは環境変数またはファイルから読み込み、フラグからは読み込みません(ps により argv が漏えいします)。

5. サブコマンド

標準ライブラリは、コマンドが少数であれば適しています。

switch fs.Arg(0) {
case "serve":
    return runServe(ctx, fs.Args()[1:], stdout, stderr)
case "migrate":
    return runMigrate(ctx, fs.Args()[1:], stdout, stderr)
default:
    fmt.Fprintln(stderr, usage)
    return fmt.Errorf("unknown command %q", fs.Arg(0))
}

ネストされたコマンド、ヘルプ/補完の生成、多数のフラグが必要な場合は、Cobra を採用します。構成を整えるメリットは依存関係に見合います。

var rootCmd = &cobra.Command{Use: "mytool", SilenceUsage: true}

var serveCmd = &cobra.Command{
    Use:   "serve",
    Short: "Start the server",
    RunE: func(cmd *cobra.Command, args []string) error {
        return serve(cmd.Context(), addr) // RunE returns errors; no os.Exit
    },
}

func init() {
    serveCmd.Flags().StringVar(&addr, "addr", ":8080", "listen address")
    rootCmd.AddCommand(serveCmd)
}

Cobra のルール:必ず RunE を使います(Run と os.Exit の組み合わせは使いません)。SilenceUsage: true を設定して、実行時エラーでヘルプが表示されないようにします。cmd.Context() を下位へ渡します。Viper を追加するのは、階層化された設定(フラグ > 環境変数 > ファイル)が実際に必要な場合に限ります。ほとんどのツールでは flag + os.Getenv で十分です。

6. 人間と機械向けの出力

  • 機械での利用には --json フラグを用意し、人間向けにはテーブル/テキストをデフォルトにします。
  • stdout がターミナルでない場合、または NO_COLOR が設定されている場合は、ANSI カラーを出力しません。
  • 進行状況バー/スピナーは stderr に出力し、ターミナルの場合に限って表示します。

7. テスト

バッファを使って run を直接テストし、stdout をゴールデンファイルと比較します。ゴールデンファイルは -update で再生成できます。実際のバイナリ、終了コード、stdin を含むエンドツーエンドの動作には、Go コマンド自体のテストで使われている形式の testscript(.txtar スクリプト)を使います。

8. バージョン情報

mytool version では debug.ReadBuildInfo() を読み込みます。Go 1.24 以降、メインモジュールのバージョンは VCS タグから取得され、vcs.revision はビルド設定から取得されます。VCS メタデータなしでビルドする場合に限り、-ldflags "-X main.version=..." にフォールバックします。

9. シェル補完

Cobra は completion bash|zsh|fish|powershell を生成します。動的な値には ValidArgsFunction を追加します。Cobra を使わない場合は、静的な補完スクリプトをリリースに含めます。

セクション 3 と 7〜9 の例:references/testing-and-versioning.md。

検証チェックリスト

  1. run(ctx, args, stdin, stdout, stderr) パターン — サブプロセスなしでロジックをテスト可能
  2. signal.NotifyContext が接続され、長時間の処理が ctx のキャンセルを尊重する
  3. データは stdout に、診断情報は stderr に出力 — パイプで検証済み
  4. 終了コード:成功は 0、使い方の誤りは 2、それ以外は文書化されたコードを使用し、os.Exit は main でのみ使用
  5. すべてのフラグに使用方法の説明がある — -h の出力を確認済み
  6. ファイルを指定する箇所では、stdin/stdout に - を指定できる
  7. 破壊的な操作には明示的なフラグが必要
  8. argv を介してシークレットを渡さない
  9. Cobra(使用する場合):すべての箇所で RunE を使用し、SilenceUsage を設定して、context を伝播
  10. 非 TTY 環境および NO_COLOR の場合は、色表示とスピナーを無効化
  11. -h は 0 で終了し、使い方の誤りは 2 で終了し、Ctrl+C は 130 で終了
  12. version の出力元はビルド情報とし、CLI の表層をゴールデンテストまたは testscript テストでカバー

コメント