cli-ops について
AI アシスタントや上級ユーザーが組み合わせて使うコマンドラインツールの開発者向け設計ガイドです。予測可能な JSON 出力、一貫したエラー処理、組み合わせやすいコマンドを扱います。
主な機能
- 01コマンド構成と命名規則
- 02JSON 出力とストリーム分離の規則
- 03終了コードとエラー形式
- 04ヘルプと使用例の要件
- 05認証・フィルタリング・ページネーションのパターン
活用シーン
- 01スクリプトで解析しやすい出力の設計
- 02サブコマンド間のフラグ統一
- 03エラー種別に応じた自動処理の分岐
- 04認証コマンドと認証情報保存の計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ 0xdarkmatter/ claude-mods --skill cli-opsインストール後、Claude Code で/cli-opsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/.gitkeepreferences/implementation.mdjson-schemas.mdscripts/.gitkeep詳細
エージェント型ワークフローの CLI パターン
AI アシスタントやパワーユーザーが連結、解析でき、信頼して使える CLI ツールを構築するためのパターンです。
哲学
エージェント型ワークフロー向けに CLI を構築します。対象は、コマンドを連結し、出力をプログラムで解析し、予測可能な動作を期待する AI アシスタントやパワーユーザーです。
基本原則
| 原則 | 意味 | 重要な理由 |
|---|---|---|
| 自己説明的 | --help の内容が充実しており、常に最新である | LLM が外部ドキュメントなしで機能を見つけられます |
| 予測可能 | すべてのコマンドで同じパターンを使う | 一度覚えれば、どこでも使えます |
| 組み合わせ可能 | Unix の哲学に基づき、1つのことを適切に行う | ツールを自然に連結できます |
| 解析可能 | 常に --json を利用でき、常に有効な JSON を返す | 解析のための小細工なしで、機械から利用できます |
| デフォルトでは静か | 要求されない限り、データのみを出力し、装飾を加えない | 予期しない出力によってスクリプトが壊れるのを防ぎます |
| 迅速に失敗する | 無効な入力は直ちにエラーにする | サイレントな失敗や部分的な結果を防ぎます |
設計原則
- stdout は神聖です - データのみを出力します。進捗、ログ、装飾は一切出力しません。
- stderr は人間向けです - プログレスバー、色、テーブル、警告はここに出力します。
- 終了コードには意味があります - スクリプトは失敗の種類に応じて処理を分岐できます。
- ヘルプには例を含めます - 理解するための最短経路です。
- JSON の形は予測可能です - すべてのコマンドで同じ構造を使います。
コマンドアーキテクチャ
構造パターン
<tool> [global-options] <resource> <action> [options] [arguments]
すべての CLI は次の階層に従います。
<tool>
├── --version, --help # Global flags
├── auth # Authentication (if required)
│ ├── login
│ ├── status
│ └── logout
└── <resource> # Domain resources (plural nouns)
├── list # Get many
├── get <id> # Get one by ID
├── create # Make new (if supported)
├── update <id> # Modify existing (if supported)
├── delete <id> # Remove (if supported)
└── <custom-action> # Domain-specific verbs
命名規則
| 要素 | 規則 | 有効な例 | 無効な例 |
|---|---|---|---|
| ツール名 | 小文字、2~12文字 | mytool、datactl | MyTool、my-tool-cli |
| リソース | 複数形の名詞、小文字 | invoices、users | Invoice、user |
| アクション | 動詞、小文字 | list、get、sync | listing、getter |
| ロングフラグ | ケバブケース | --dry-run、--output-format | --dryRun、--output_format |
| ショートフラグ | 1文字 | -n、-q、-v | -num、-quiet |
標準的なリソースアクション
| アクション | HTTP 相当 | 戻り値 | 冪等性 |
|---|---|---|---|
list | GET /resources | 配列 | あり |
get <id> | GET /resources/:id | オブジェクト | あり |
create | POST /resources | 作成されたオブジェクト | なし |
update <id> | PATCH /resources/:id | 更新されたオブジェクト | あり |
delete <id> | DELETE /resources/:id | 確認 | あり |
search | GET /resources?q= | 配列 | あり |
フラグとオプション
必須フラグ
すべてのコマンドは、次をサポートしなければなりません。
| フラグ | ショート | 動作 | 出力 |
|---|---|---|---|
--help | -h | 例を含むヘルプを表示 | ヘルプテキストを stdout に出力し、終了コード 0 |
--json | 機械可読形式で出力 | JSON を stdout に出力 |
ルートコマンドは、さらに次をサポートしなければなりません。
| フラグ | ショート | 動作 | 出力 |
|---|---|---|---|
--version | -V | バージョンを表示 | <tool> <version> を stdout に出力し、終了コード 0 |
推奨フラグ
| フラグ | ショート | 型 | 用途 | デフォルト |
|---|---|---|---|---|
--quiet | -q | bool | 必須ではない stderr 出力を抑制 | false |
--verbose | -v | bool | 詳細度を上げる | false |
--dry-run | bool | 実行せずに結果をプレビュー | false | |
--limit | -n | int | 返す結果の最大数 | 20 |
--output | -o | path | 出力をファイルに書き込む | stdout |
--format | -f | enum | 出力形式 | コマンドによる |
フラグの動作規則
- ブールフラグに値は不要です:
--json。--json=trueではありません。 - ショートフラグは連結できます:
-vqは-v -qと同じです。 - 未知のフラグはエラーにします:黙って無視してはいけません。
- フラグの繰り返し:最後の値を使用します(適切でない場合はエラーにします)。
出力仕様
ストリームの分離
最も重要なルールです。
| ストリーム | 内容 | タイミング |
|---|---|---|
| stdout | データのみ | 常に |
| stderr | その他すべて | 対話モード |
stdout に出力されるもの:
--jsonが設定されている場合の JSON- 対話モードでの最小限のテキスト出力
- それ以外は何も出力しません。例外はありません。
stderr に出力されるもの:
- 進行状況インジケーター(スピナー、バー)
- 状態メッセージ(「取得中…」、「完了」)
- 警告
- リッチな書式のテーブル
- 色と装飾
- デバッグ情報(
--verbose)
対話モードの検出
import sys
def is_interactive() -> bool:
"""True if connected to a terminal, not piped."""
return sys.stdout.isatty() and sys.stderr.isatty()
| コンテキスト | stdout.isatty() | 動作 |
|---|---|---|
| ターミナル | True | リッチな出力を stderr に、概要を stdout に出力 |
パイプ接続(| jq) | False | 最小限の出力/JSON を stdout に出力 |
リダイレクト(> file) | False | 最小限の出力を stdout に出力 |
--json フラグ | 任意 | JSON を stdout に出力し、stderr のノイズを抑制 |
JSON 出力スキーマ
JSON 応答パターンの詳細は、references/json-schemas.md を参照してください。
主な規約:
- リスト応答:
{"data": [...], "meta": {...}} - 単一項目:
{"data": {...}} - エラー:
{"error": {"code": "...", "message": "..."}} - ISO 8601 形式の日付、10 進数形式の金額、文字列形式の ID
終了コード
スクリプトで利用できる、意味のある終了コードです。
| コード | 名前 | 意味 | 発生条件 |
|---|---|---|---|
| 0 | SUCCESS | 操作が完了しました | すべて正常に動作した場合 |
| 1 | ERROR | 一般/不明なエラー | 予期しない失敗 |
| 2 | AUTH_REQUIRED | 認証されていません | トークンがない、または期限切れ |
| 3 | NOT_FOUND | リソースが見つかりません | ID が存在しない |
| 4 | VALIDATION | 入力が無効です | 引数が不正、または検証に失敗 |
| 5 | FORBIDDEN | アクセスが拒否されました | 認証済みだが認可されていない |
| 6 | RATE_LIMITED | リクエストが多すぎます | API によるスロットリング |
| 7 | CONFLICT | 状態の競合 | 同時変更、重複 |
使用方法
# Script can branch on exit code
mytool items get item-001 --json
case $? in
0) echo "Success" ;;
2) echo "Need to authenticate" && mytool auth login ;;
3) echo "Item not found" ;;
*) echo "Error occurred" ;;
esac
実装
# Constants
EXIT_SUCCESS = 0
EXIT_ERROR = 1
EXIT_AUTH_REQUIRED = 2
EXIT_NOT_FOUND = 3
EXIT_VALIDATION = 4
EXIT_FORBIDDEN = 5
EXIT_RATE_LIMITED = 6
EXIT_CONFLICT = 7
# Usage
raise typer.Exit(EXIT_NOT_FOUND)
エラー処理
エラー出力形式
--json を指定した場合、エラーは構造化 JSON として stdout に出力され、stderr にもメッセージが出力されます。
stderr:
Error: Item not found
stdout:
{
"error": {
"code": "NOT_FOUND",
"message": "Item not found",
"details": {
"item_id": "bad-id"
}
}
}
エラーコード
| コード | 終了コード | 意味 |
|---|---|---|
AUTH_REQUIRED | 2 | 最初に認証が必要です |
TOKEN_EXPIRED | 2 | トークンの更新が必要です |
FORBIDDEN | 5 | 権限が不足しています |
NOT_FOUND | 3 | リソースが存在しません |
VALIDATION_ERROR | 4 | 入力が無効です |
INVALID_ARGUMENT | 4 | 引数の値が不正です |
MISSING_ARGUMENT | 4 | 必須の引数がありません |
RATE_LIMITED | 6 | リクエストが多すぎます |
CONFLICT | 7 | 状態の競合 |
ALREADY_EXISTS | 7 | リソースが重複しています |
INTERNAL_ERROR | 1 | 予期しないエラー |
API_ERROR | 1 | 上流 API でエラーが発生しました |
NETWORK_ERROR | 1 | 接続に失敗しました |
実装パターン
def _error(
message: str,
code: str = "ERROR",
exit_code: int = EXIT_ERROR,
details: dict = None,
as_json: bool = False,
):
"""Output error and exit."""
error_obj = {"error": {"code": code, "message": message}}
if details:
error_obj["error"]["details"] = details
if as_json:
print(json.dumps(error_obj, indent=2))
# Always print human message to stderr
console.print(f"[red]Error:[/red] {message}")
raise typer.Exit(exit_code)
ヘルプシステム
ヘルプの要件
すべての --help 出力には、必ず次の内容を含めてください。
- 簡単な説明(1 行)
- 使用構文
- 説明付きのオプション
- 例(機能を知るうえで重要)
ヘルプ形式のテンプレート
<one-line description>
Usage: <tool> <resource> <action> [OPTIONS] [ARGS]
Arguments:
<arg> Description of positional argument
Options:
-s, --status TEXT Filter by status
-n, --limit INTEGER Max results [default: 20]
--json Output as JSON
-h, --help Show this help
Examples:
<tool> <resource> <action>
<tool> <resource> <action> --status active
<tool> <resource> <action> --json | jq '.[0]'
例は重要です
例には、次の内容を示してください。
- 基本的な使用方法 - 最も簡単な実行方法
- 一般的なフィルター - 最もよく使われるオプション
- JSON のパイプ接続 -
jqと連携する方法 - 実際のシナリオ - 実際のユースケース
認証
認証コマンド
認証が必要なツールには、必ず次の機能を実装してください。
<tool> auth login # Interactive authentication
<tool> auth status # Check current state
<tool> auth logout # Clear credentials
認証情報の保存優先順位
推奨: 最大限のセキュリティを確保するため、フォールバック付きの OS キーリングを使用します。
-
環境変数(CI/CD、テスト)
MYTOOL_API_TOKENなど- 最優先で、ほかのすべてのソースを上書きします
-
OS キーリング(プライマリストレージ — 安全)
- Windows:Credential Manager
- macOS:Keychain
- Linux:Secret Service(GNOME Keyring、KWallet)
- 保存時に暗号化され、ユーザーごとに分離されます
-
.env ファイル(開発時のフォールバック)
- 現在のディレクトリにプレーンテキストで保存します
- ローカル開発に便利です
.gitignoreに含める必要があります
依存関係:
dependencies = [
"keyring>=24.0.0", # OS keyring access
"python-dotenv>=1.0.0", # .env file support
]
シンプルな代替案: ~/.config/<tool>/ に設定ファイルのみを配置します
- 機密性の高い認証情報を必要としないツールに適しています
- OS キーリングが複雑さを増しすぎる場合にも適しています
認証情報の保存方法の完全な実装については、references/implementation.md を参照してください。
未認証時の動作
認証が必要にもかかわらず、認証情報がない場合:
$ mytool items list
Error: Not authenticated. Run: mytool auth login
# exit code: 2
$ mytool items list --json
# stderr: Error: Not authenticated. Run: mytool auth login
{"error": {"code": "AUTH_REQUIRED", "message": "Not authenticated. Run: mytool auth login"}}
# exit code: 2
データ規約
日付の処理
入力(柔軟): ユーザーの利便性のため、複数の形式を受け付けます
| 形式 | 例 | 解釈 |
|---|---|---|
| ISO 日付 | 2025-01-15 | 正確な日付 |
| ISO 日時 | 2025-01-15T10:30:00Z | 正確な日時 |
| 相対 | today、yesterday、tomorrow | 現在/前日/翌日 |
| 相対 | last、this(文脈に応じて) | 前の期間/現在の期間 |
出力(厳密): 常に ISO 8601 形式で出力します
{
"created_at": "2025-01-15T10:30:00Z",
"due_date": "2025-02-15",
"month": "2025-01"
}
金額
- セントではなく、小数として保存します
- 曖昧な場合は通貨を含めます
- 書式設定はしません(JSON に「$」や「,」を含めません)
{
"total": 1250.50,
"currency": "USD"
}
ID
- 常に文字列にします(数値の場合も同様)
- ソースの形式をそのまま保持します
{
"id": "abc_123",
"legacy_id": "12345"
}
列挙型
- JSON では UPPER_SNAKE_CASE を使用します
- 入力では大文字と小文字を区別しません
# All equivalent
--status DRAFT
--status draft
--status Draft
{"status": "IN_PROGRESS"}
フィルタリングとページネーション
一般的なフィルターパターン
# By status
--status DRAFT
--status active,pending # Multiple values
# By date range
--from 2025-01-01 --to 2025-01-31
--month 2025-01
--month last
# By related entity
--user "Alice"
--project "Project X"
# Text search
--search "keyword"
-q "keyword"
# Boolean filters
--archived
--no-archived
--include-deleted
ページネーション
# Limit results
--limit 50
-n 50
# Offset-based
--page 2
--offset 20
# Cursor-based
--cursor "eyJpZCI6MTIzfQ=="
--after "item_123"
実装
Typer を使った CLI のスケルトン、httpx を使ったクライアントパターン、エラー処理、認証フロー、テストパターンを含む Python 実装テンプレート一式については、references/implementation.md を参照してください。
避けるべきパターン
❌ 出力の混入
# BAD: Progress to stdout
$ bad-tool items list --json
Fetching items...
[{"id": "1"}]
Done!
# GOOD: Only JSON to stdout
$ good-tool items list --json
[{"id": "1"}]
❌ 対話型プロンプト
# BAD: Prompts in non-interactive context
$ bad-tool items create
Enter name: _
# GOOD: Fail fast with required flags
$ good-tool items create
Error: --name is required
❌ 一貫性のないフラグ
# BAD: Different flags for same concept
$ tool1 list -j
$ tool2 list --format=json
# GOOD: Same flags everywhere
$ tool1 list --json
$ tool2 list --json
❌ サイレントな失敗
# BAD: Success exit code on failure
$ bad-tool items delete bad-id
Item not found
$ echo $?
0
# GOOD: Semantic exit code
$ good-tool items delete bad-id
Error: Item not found: bad-id
$ echo $?
3
クイックリファレンス
必須チェックリスト
-
<tool> --version - 例を含む
<tool> --help -
<tool> <resource> list [--json] -
<tool> <resource> get <id> [--json] - 意味のある終了コード(0、1、2、3、4、5、6、7)
- エラーは stderr、データは stdout に出力
-
--jsonで有効な JSON を出力 - ストリームの分離(stdout = データ、stderr = UI)
推奨される追加項目
- 認証コマンド(
auth login、auth status、auth logout) - 作成/更新/削除の操作
-
--quietモードと--verboseモード - 変更を伴う操作に対する
--dry-run - ページネーション(
--limit、--page) - フィルタリング(ステータス、日付範囲、検索)
- 自動テスト
フレームワークの選択
Typer(新しいツールに推奨):
- 型ヒントによる自動バリデーション
- ヘルプの自動生成機能を内蔵
- 美しい出力を実現する Rich との統合
- Click よりボイラープレートが少ない
Click(既存のツールでは許容):
- Typer は Click を基盤として構築されています(100%互換)
- 適切に構成された Click コードは移行する必要がありません
- どちらも同じ出力規約に従う必要があります
# Typer (preferred)
import typer
from rich.console import Console
app = typer.Typer()
console = Console(stderr=True) # UI to stderr
# Click (acceptable)
import click
from rich.console import Console
console = Console(stderr=True) # Same pattern