cli-patterns
予測可能な動作と解析可能な出力を持ち、エージェントワークフローに対応した実用的なCLIツールを構築する際のパターンを提供するSkill。
📜 元の英語説明(参考)
Patterns for building production-quality CLI tools with predictable behavior, parseable output, and agentic workflows. Triggers: cli tool, command line tool, build cli, cli patterns, agentic cli, cli design, typer cli, click cli.
🇯🇵 日本人クリエイター向け解説
予測可能な動作と解析可能な出力を持ち、エージェントワークフローに対応した実用的なCLIツールを構築する際のパターンを提供するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-17
- 取得日時
- 2026-05-17
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
エージェントワークフローのための CLI パターン
AI アシスタントやパワーユーザーが連携、解析、信頼できる CLI ツールを構築するためのパターンです。
哲学
エージェントワークフローのために CLI を構築します。これは、コマンドを連結し、出力をプログラムで解析し、予測可能な動作を期待する AI アシスタントやパワーユーザーを指します。
コア原則
| 原則 | 意味 | 重要性 |
|---|---|---|
| 自己文書化 | --help は包括的で常に最新です |
LLM は外部ドキュメントなしで機能を発見できます |
| 予測可能 | すべてのコマンドで同じパターンを使用します | 一度学習すれば、どこでも使用できます |
| 構成可能 | Unix の哲学 - 一つのことをうまく行います | ツールは自然に連結されます |
| 解析可能 | --json は常に利用可能で、常に有効です |
解析ハックなしで機械が消費できます |
| デフォルトで静か | データのみで、要求されない限り装飾はありません | スクリプトは予期しない出力で壊れません |
| 高速失敗 | 無効な入力 = 即時エラー | サイレントな失敗や部分的な結果はありません |
設計公理
- stdout は神聖です - データのみです。進行状況、ログ、装飾は決して含みません。
- stderr は人間向けです - プログレスバー、色、テーブル、警告はここに表示されます。
- 終了コードには意味があります - スクリプトは失敗モードに応じて分岐できます。
- ヘルプには例が含まれます - 理解への最速パスです。
- JSON の形状は予測可能です - すべてのコマンドで同じ構造です。
コマンドアーキテクチャ
構造パターン
<tool> [global-options] <resource> <action> [options] [arguments]
すべての CLI はこの階層に従います。
<tool>
├── --version, --help # グローバルフラグ
├── auth # 認証 (必要な場合)
│ ├── login
│ ├── status
│ └── logout
└── <resource> # ドメインリソース (複数形の名詞)
├── list # 複数取得
├── get <id> # ID で一つ取得
├── create # 新規作成 (サポートされている場合)
├── update <id> # 既存の変更 (サポートされている場合)
├── delete <id> # 削除 (サポートされている場合)
└── <custom-action> # ドメイン固有の動詞
命名規則
| 要素 | 規則 | 有効な例 | 無効な例 |
|---|---|---|---|
| ツール名 | 小文字、2-12 文字 | mytool, datactl |
MyTool, my-tool-cli |
| リソース | 複数形の名詞、小文字 | invoices, users |
Invoice, user |
| アクション | 動詞、小文字 | list, get, sync |
listing, getter |
| ロングフラグ | kebab-case | --dry-run, --output-format |
--dryRun, --output_format |
| ショートフラグ | 一文字 | -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 | 出力形式 | varies |
フラグ動作ルール
- ブーリアンフラグは値をとりません:
--jsonであり--json=trueではありません - ショートフラグは組み合わせることができます:
-vqは-v -qと同じです - 不明なフラグはエラーです: サイレントに無視することはありません
- 繰り返されるフラグ: 最後の値が優先されます (または不適切な場合はエラー)
出力仕様
ストリーム分離
これが最も重要なルールです。
| ストリーム | 内容 | タイミング |
|---|---|---|
| stdout | データのみ | 常に |
| stderr | その他すべて | 対話モード |
stdout は以下を受け取ります。
--jsonが設定されている場合は JSON- 対話モードでの最小限のテキスト出力
- その他は一切ありません。
stderr は以下を受け取ります。
- 進行状況インジケーター (スピナー、バー)
- ステータスメッセージ ("Fetching...", "Done")
- 警告
- リッチなフォーマットのテーブル
- 色と装飾
- デバッグ情報 (
--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 | stdout に最小限/JSON |
リダイレクト (> file) |
False | stdout に最小限 |
--json フラグ |
Any | stdout に JSON、stderr のノイズを抑制 |
JSON 出力スキーマ
完全な JSON レスポンスパターンについては、references/json-schemas.md を参照してください。
主な規則:
- リストレスポンス:
{"data": [...], "meta": {...}} - 単一アイテム:
{"data": {...}} - エラー:
{"error": {"code": "...", "message": "..."}} - ISO 8601 日付、10 進数のお金、文字列 ID
終了コード
スクリプトが信頼できるセマンティックな終了コードです。
| コード | 名前 | 意味 | タイミング |
|---|---|---|---|
| 0 | SUCCESS | 操作が完了しました | すべてがうまくいった場合 |
| 1 | ERROR | 一般/不明なエラー | 予期しない失敗 |
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
CLI Patterns for Agentic Workflows
Patterns for building CLI tools that AI assistants and power users can chain, parse, and rely on.
Philosophy
Build CLIs for agentic workflows - AI assistants and power users who chain commands, parse output programmatically, and expect predictable behavior.
Core Principles
| Principle | Meaning | Why It Matters |
|---|---|---|
| Self-documenting | --help is comprehensive and always current |
LLMs discover capabilities without external docs |
| Predictable | Same patterns across all commands | Learn once, use everywhere |
| Composable | Unix philosophy - do one thing well | Tools chain together naturally |
| Parseable | --json always available, always valid |
Machine consumption without parsing hacks |
| Quiet by default | Data only, no decoration unless requested | Scripts don't break on unexpected output |
| Fail fast | Invalid input = immediate error | No silent failures or partial results |
Design Axioms
- stdout is sacred - Only data. Never progress, never logging, never decoration.
- stderr is for humans - Progress bars, colors, tables, warnings live here.
- Exit codes have meaning - Scripts can branch on failure mode.
- Help includes examples - The fastest path to understanding.
- JSON shape is predictable - Same structure across all commands.
Command Architecture
Structural Pattern
<tool> [global-options] <resource> <action> [options] [arguments]
Every CLI follows this hierarchy:
<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
Naming Conventions
| Element | Convention | Valid Examples | Invalid Examples |
|---|---|---|---|
| Tool name | lowercase, 2-12 chars | mytool, datactl |
MyTool, my-tool-cli |
| Resource | plural noun, lowercase | invoices, users |
Invoice, user |
| Action | verb, lowercase | list, get, sync |
listing, getter |
| Long flags | kebab-case | --dry-run, --output-format |
--dryRun, --output_format |
| Short flags | single letter | -n, -q, -v |
-num, -quiet |
Standard Resource Actions
| Action | HTTP Equiv | Returns | Idempotent |
|---|---|---|---|
list |
GET /resources | Array | Yes |
get <id> |
GET /resources/:id | Object | Yes |
create |
POST /resources | Created object | No |
update <id> |
PATCH /resources/:id | Updated object | Yes |
delete <id> |
DELETE /resources/:id | Confirmation | Yes |
search |
GET /resources?q= | Array | Yes |
Flags & Options
Mandatory Flags
Every command MUST support:
| Flag | Short | Behavior | Output |
|---|---|---|---|
--help |
-h |
Show help with examples | Help text to stdout, exit 0 |
--json |
Machine-readable output | JSON to stdout |
Root command MUST additionally support:
| Flag | Short | Behavior | Output |
|---|---|---|---|
--version |
-V |
Show version | <tool> <version> to stdout, exit 0 |
Recommended Flags
| Flag | Short | Type | Purpose | Default |
|---|---|---|---|---|
--quiet |
-q |
bool | Suppress non-essential stderr | false |
--verbose |
-v |
bool | Increase detail level | false |
--dry-run |
bool | Preview without executing | false | |
--limit |
-n |
int | Max results to return | 20 |
--output |
-o |
path | Write output to file | stdout |
--format |
-f |
enum | Output format | varies |
Flag Behavior Rules
- Boolean flags take no value:
--jsonnot--json=true - Short flags can combine:
-vqequals-v -q - Unknown flags are errors: Never silently ignore
- Repeated flags: Last value wins (or error if inappropriate)
Output Specification
Stream Separation
This is the most critical rule:
| Stream | Content | When |
|---|---|---|
| stdout | Data only | Always |
| stderr | Everything else | Interactive mode |
stdout receives:
- JSON when
--jsonis set - Minimal text output when interactive
- Nothing else. Ever.
stderr receives:
- Progress indicators (spinners, bars)
- Status messages ("Fetching...", "Done")
- Warnings
- Rich formatted tables
- Colors and decoration
- Debug information (
--verbose)
Interactive Detection
import sys
def is_interactive() -> bool:
"""True if connected to a terminal, not piped."""
return sys.stdout.isatty() and sys.stderr.isatty()
| Context | stdout.isatty() | Behavior |
|---|---|---|
| Terminal | True | Rich output to stderr, summary to stdout |
Piped (\| jq) |
False | Minimal/JSON to stdout |
Redirected (> file) |
False | Minimal to stdout |
--json flag |
Any | JSON to stdout, suppress stderr noise |
JSON Output Schema
See references/json-schemas.md for complete JSON response patterns.
Key conventions:
- List responses:
{"data": [...], "meta": {...}} - Single item:
{"data": {...}} - Errors:
{"error": {"code": "...", "message": "..."}} - ISO 8601 dates, decimal money, string IDs
Exit Codes
Semantic exit codes that scripts can rely on:
| Code | Name | Meaning | When |
|---|---|---|---|
| 0 | SUCCESS | Operation completed | Everything worked |
| 1 | ERROR | General/unknown error | Unexpected failures |
| 2 | AUTH_REQUIRED | Not authenticated | No token, token expired |
| 3 | NOT_FOUND | Resource missing | ID doesn't exist |
| 4 | VALIDATION | Invalid input | Bad arguments, failed validation |
| 5 | FORBIDDEN | Permission denied | Authenticated but not authorized |
| 6 | RATE_LIMITED | Too many requests | API throttling |
| 7 | CONFLICT | State conflict | Concurrent modification, duplicate |
Usage
# 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
Implementation
# 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)
Error Handling
Error Output Format
With --json, errors output structured JSON to stdout AND a message to stderr:
stderr:
Error: Item not found
stdout:
{
"error": {
"code": "NOT_FOUND",
"message": "Item not found",
"details": {
"item_id": "bad-id"
}
}
}
Error Codes
| Code | Exit | Meaning |
|---|---|---|
AUTH_REQUIRED |
2 | Must authenticate first |
TOKEN_EXPIRED |
2 | Token needs refresh |
FORBIDDEN |
5 | Insufficient permissions |
NOT_FOUND |
3 | Resource doesn't exist |
VALIDATION_ERROR |
4 | Invalid input |
INVALID_ARGUMENT |
4 | Bad argument value |
MISSING_ARGUMENT |
4 | Required argument missing |
RATE_LIMITED |
6 | Too many requests |
CONFLICT |
7 | State conflict |
ALREADY_EXISTS |
7 | Duplicate resource |
INTERNAL_ERROR |
1 | Unexpected error |
API_ERROR |
1 | Upstream API failed |
NETWORK_ERROR |
1 | Connection failed |
Implementation Pattern
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 System
Help Requirements
Every --help output MUST include:
- Brief description (one line)
- Usage syntax
- Options with descriptions
- Examples (critical for discovery)
Help Format Template
<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]'
Examples Are Critical
Examples should show:
- Basic usage - Simplest invocation
- Common filters - Most-used options
- JSON piping - How to chain with
jq - Real-world scenarios - Actual use cases
Authentication
Auth Commands
Tools requiring authentication MUST implement:
<tool> auth login # Interactive authentication
<tool> auth status # Check current state
<tool> auth logout # Clear credentials
Credential Storage Priority
Recommended: OS keyring with fallbacks for maximum security
-
Environment variable (CI/CD, testing)
MYTOOL_API_TOKENor similar- Highest priority, overrides all other sources
-
OS Keyring (primary storage - secure)
- Windows: Credential Manager
- macOS: Keychain
- Linux: Secret Service (GNOME Keyring, KWallet)
- Encrypted at rest, per-user isolation
-
.env file (development fallback)
- Plain text in current directory
- Convenient for local development
- Must be in
.gitignore
Dependencies:
dependencies = [
"keyring>=24.0.0", # OS keyring access
"python-dotenv>=1.0.0", # .env file support
]
Simple alternative: Just config file in ~/.config/<tool>/
- Good for tools without sensitive credentials
- Or when OS keyring adds too much complexity
See references/implementation.md for complete credential storage implementations.
Unauthenticated Behavior
When auth is required but missing:
$ 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
Data Conventions
Date Handling
Input (Flexible): Accept multiple formats for user convenience
| Format | Example | Interpretation |
|---|---|---|
| ISO date | 2025-01-15 |
Exact date |
| ISO datetime | 2025-01-15T10:30:00Z |
Exact datetime |
| Relative | today, yesterday, tomorrow |
Current/previous/next day |
| Relative | last, this (with context) |
Previous/current period |
Output (Strict): Always output ISO 8601
{
"created_at": "2025-01-15T10:30:00Z",
"due_date": "2025-02-15",
"month": "2025-01"
}
Money
- Store as decimal number, not cents
- Include currency when ambiguous
- Never format (no "$" or "," in JSON)
{
"total": 1250.50,
"currency": "USD"
}
IDs
- Always strings (even if numeric)
- Preserve exact format from source
{
"id": "abc_123",
"legacy_id": "12345"
}
Enums
- UPPER_SNAKE_CASE in JSON
- Case-insensitive input
# All equivalent
--status DRAFT
--status draft
--status Draft
{"status": "IN_PROGRESS"}
Filtering & Pagination
Common Filter Patterns
# 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
Pagination
# Limit results
--limit 50
-n 50
# Offset-based
--page 2
--offset 20
# Cursor-based
--cursor "eyJpZCI6MTIzfQ=="
--after "item_123"
Implementation
See references/implementation.md for complete Python implementation templates including:
- CLI skeleton with Typer
- Client pattern with httpx
- Error handling
- Authentication flows
- Testing patterns
Anti-Patterns
❌ Output Pollution
# 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"}]
❌ Interactive Prompts
# 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
❌ Inconsistent Flags
# BAD: Different flags for same concept
$ tool1 list -j
$ tool2 list --format=json
# GOOD: Same flags everywhere
$ tool1 list --json
$ tool2 list --json
❌ Silent Failures
# 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
Quick Reference
Must-Have Checklist
- [ ]
<tool> --version - [ ]
<tool> --helpwith examples - [ ]
<tool> <resource> list [--json] - [ ]
<tool> <resource> get <id> [--json] - [ ] Semantic exit codes (0, 1, 2, 3, 4, 5, 6, 7)
- [ ] Errors to stderr, data to stdout
- [ ] Valid JSON on
--json - [ ] Stream separation (stdout = data, stderr = UI)
Recommended Additions
- [ ] Authentication commands (
auth login,auth status,auth logout) - [ ] Create/Update/Delete operations
- [ ]
--quietand--verbosemodes - [ ]
--dry-runfor mutations - [ ] Pagination (
--limit,--page) - [ ] Filtering (status, date range, search)
- [ ] Automated tests
Framework Choice
Typer (preferred for new tools):
- Type hints provide automatic validation
- Built-in help generation
- Rich integration for beautiful output
- Less boilerplate than Click
Click (acceptable for existing tools):
- Typer is built on Click (100% compatible)
- Well-structured Click code doesn't need migration
- Both must follow same output conventions
# 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