mcpField
A
agent-cli

agent-cli

github.com/glebis開発40177 回インストール1 日前に更新
リポジトリ

agent-cli について

agent-cliは、PythonのCLIをエージェントが扱いやすい形に変更するか、配布可能な`cli_utils`パッケージを作成するスキルです。任意指定の`--json`でNDJSONを出力し、既定では人向けの出力を維持します。

主な機能

  1. 01スクリプト出力をNDJSON用ヘルパーに置換
  2. 02任意指定の`--json`フラグを追加
  3. 03テスト付きPythonパッケージのひな形作成
  4. 04構造化ログとエラー処理用ヘルパー
  5. 05JSON出力の検証と対話プロンプトの制御

活用シーン

  1. 01Python CLIをエージェントで利用可能にする
  2. 02既存スクリプトに機械可読出力を追加
  3. 03再利用可能な`cli_utils`パッケージを作成

インストール

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

$npx skills add https://github.com/glebis/claude-skills --skill agent-cli

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

同梱ファイルevals/evals.jsonreferences/best-practices.md

詳細

エージェント向け CLI ビルダー

Python CLI スクリプトの人間向け出力をエージェントが利用できる NDJSON に変換するか、オープンソース配布に対応した完全な cli_utils パッケージを作成します。

2 つのモード

モード A:既存のスクリプトを変換する

ユーザーがスクリプトを指定して、「エージェント向けにする」または「--json を追加する」と指示した場合:

  1. スキャン:スクリプト内の出力箇所を確認します
  2. 生成:プロジェクトに cli_utils.py がなければ生成します
  3. 置換:すべての出力を構造化されたヘルパーに置き換えます
  4. 検証:JSON モードで生の出力が漏れないことを確認します
モード B:完全なパッケージを作成する

ユーザーが「cli_utils パッケージを作成する」と指示した場合、またはオープンソースライブラリを求めている場合:

  1. 作成:pyproject.toml、テスト、ライセンス、README を含む完全な Python パッケージを作成します
  2. 追加:すべてのヘルパー(json_log、json_error、die、log、add_json_flag、enable_json、is_json)を含めます
  3. 追加:完全なカバレッジを備えた pytest テストスイートを追加します
  4. 追加:MIT ライセンスを追加します(またはユーザーの希望を尋ねます)

基本アーキテクチャ

基本パターンは、すべてのスクリプトに --json フラグを設けることです。有効な場合、すべての stdout が改行区切り JSON(NDJSON)になります。各行は、標準エンベロープを持つ独立した JSON オブジェクトです。

NDJSON イベントエンベロープ

各 JSON 行には、最低限、次の内容が含まれます。

{"event": "ready", "ts": "2026-04-30T14:00:00+00:00", "pid": 1234, "port": 8765}
  • event — 発生したこと(snake_case の文字列)
  • ts — ISO 8601 形式の UTC タイムスタンプ
  • 追加フィールドはイベントごとの kwargs です
この設計を採用する理由
  • JSON 配列ではなく NDJSON:行単位で処理でき、1 行に問題があってもストリーム全体に影響せず、grep/jq で利用でき、長時間実行するプロセスでもメモリ使用量を抑えられます
  • デフォルトではなく --json を明示的に有効化:人間向けの DX を維持し、既存のスクリプトや習慣を壊しません
  • 呼び出しごとのチェックではなくグローバルモードフラグ:起動時に一度設定すれば、すべてのヘルパーが自動的に従います
  • 繰り返しの if/else ではなく die():if is_json(): json_error(); sys.exit(1) else: print(); sys.exit() というパターンは頻出します。die() を使えば 1 行にまとめられます

cli_utils.py のリファレンス実装

cli_utils.py を生成するときは、プロジェクト固有の要件がある場合に限って調整し、次の内容をそのまま生成してください。

"""Shared helpers for JSON CLI output."""
import json
import os
import sys
from datetime import datetime, timezone

_json_mode = False

def enable_json():
    global _json_mode
    _json_mode = True

def is_json():
    return _json_mode

def json_log(event: str, **kwargs):
    """Emit one NDJSON line to stdout."""
    obj = {"event": event, "ts": datetime.now(timezone.utc).isoformat(), **kwargs}
    print(json.dumps(obj, default=str), flush=True)

def json_error(message: str, **kwargs):
    """Emit a structured error event."""
    json_log("error", message=message, **kwargs)

def die(message: str, code: int = 1, **kwargs):
    """Print error and exit — JSON or human depending on mode."""
    if _json_mode:
        json_error(message, **kwargs)
    else:
        print(message, file=sys.stderr)
    sys.exit(code)

def add_json_flag(parser):
    """Add --json flag to an argparse parser."""
    parser.add_argument("--json", action="store_true",
                        help="NDJSON output for agent consumption")

def log(message: str, **json_kwargs):
    """Print human message normally, or emit JSON event if --json is active."""
    if _json_mode:
        json_log(json_kwargs.pop("event", "info"), message=message, **json_kwargs)
    else:
        print(message)

def json_ready(**kwargs):
    """Emit the readiness signal — only in JSON mode. Call early in daemon startup."""
    if _json_mode:
        json_log("ready", pid=os.getpid(), **kwargs)

スクリプトの変換 — 手順

手順 1:出力箇所をスキャンする

対象のスクリプトから、出力または終了を行う箇所をすべて検索します。

grep -n 'print(\|sys\.exit\|exit(\|input(\|os\.system.*say' TARGET.py

見つかった各箇所を分類します:

  • 情報を表示する print → log(message, event="descriptive_name") に置き換えます
  • エラーと終了 → die(message) に置き換えます
  • \r を使ったステータス行 → if is_json(): json_log("status", ...) else: print("\r...", end="", flush=True) に置き換えます
  • 対話的な input() → if not is_json(): で保護するか、--no-interactive フラグを追加します
  • 副作用(say、osascript、通知など) → if not is_json(): で保護します
  • インポート時のエラー(argparse の実行前)→ sys.exit("message") を使います(stderr に書き込みます)
手順 2:インポートとフラグを追加する

スクリプトの先頭で、既存のインポートの後に追加します:

from cli_utils import add_json_flag, enable_json, is_json, json_log, log, die

if __name__ == "__main__" ブロック内で、argparse に追加します:

add_json_flag(parser)
args = parser.parse_args()
if args.json:
    enable_json()
手順 3:各出力箇所を置き換える

手順 1 の分類に従います。主なパターンは次のとおりです。

シンプルな情報表示:

# Before
print(f"Connected to {device}")

# After
log(f"Connected to {device}", event="connected", device=device)

エラーと終了:

# Before
print("Device not found")
sys.exit(1)

# After
die("Device not found")

デーモンの準備完了(初期化後の最初の出力):

# Before
print(f"Server running on port {port}")

# After — json_ready() only emits in JSON mode, so always call it + human fallback
json_ready(port=port)
log(f"Server running on port {port}", event="ready", port=port)

ステータス行(\r による上書き):

# Before
print(f"\r  HR {hr} RMSSD {rmssd:.1f}", end="", flush=True)

# After
if is_json():
    json_log("status", hr=hr, rmssd=rmssd)
else:
    print(f"\r  HR {hr} RMSSD {rmssd:.1f}", end="", flush=True)

人間向け専用の出力(バナー、使用例):

if not is_json():
    print("Usage: send {\"type\": \"join\", \"name\": \"Alice\"}")
手順 4:検証する
  1. python script.py --help を実行し、--json フラグが表示されることを確認します
  2. python script.py --json を実行し、最初の行が有効な JSON であることを確認します
  3. 残っている生の print( 呼び出しを grep で検索し、それぞれが保護されているか、意図的に残されていることを確認します

イベント名の規則

snake_case を使い、内容を具体的に表し、grep で検索しやすい名前にします。

カテゴリイベント
ライフサイクルready、shutdown、connected、disconnected
データhr、status、metric、heartbeat
エラーerror、retry
アクションrecording_started、recording_stopped、preset_change
進行状況scanning、connecting、downloading、importing

オープンソースパッケージのスキャフォールディング

ユーザーが配布可能なパッケージを求めている場合は、次の構成をスキャフォールディングします。

cli-utils-agent/
├── pyproject.toml
├── LICENSE                  # MIT by default, ask user
├── README.md
├── src/
│   └── cli_utils_agent/
│       ├── __init__.py      # re-exports all public API
│       └── core.py          # the implementation
├── tests/
│   ├── __init__.py
│   ├── test_json_log.py
│   ├── test_die.py
│   ├── test_log.py
│   └── test_add_json_flag.py
└── .github/
    └── workflows/
        └── test.yml         # CI with pytest
__init__.py — 公開 API を再エクスポートする
# src/cli_utils_agent/__init__.py
from .core import (
    enable_json, is_json, json_log, json_error, die,
    add_json_flag, log, json_ready,
)

__all__ = [
    "enable_json", "is_json", "json_log", "json_error", "die",
    "add_json_flag", "log", "json_ready",
]
README.md のテンプレート

次の内容を含む README を生成します。プロジェクト名、1 行の説明、インストール手順(pip install cli-utils-agent)、add_json_flag と enable_json、log() を使った簡単な使用例、すべてのエクスポートと 1 行の説明を記載した API リファレンステーブル、調査背景へのリンク。

pyproject.toml のテンプレート
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "cli-utils-agent"
version = "0.1.0"
description = "Add agent-friendly --json NDJSON output to any Python CLI"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Topic :: Software Development :: Libraries",
]

[project.urls]
Homepage = "https://github.com/USER/cli-utils-agent"

[tool.hatch.build.targets.wheel]
packages = ["src/cli_utils_agent"]
テストスイート

標準出力のキャプチャには capsys を、die() のテストには pytest.raises(SystemExit) を使用します。例:

# tests/test_json_log.py
import json
from cli_utils_agent import json_log, enable_json, is_json

def test_json_log_writes_ndjson(capsys):
    json_log("ready", port=8765, pid=42)
    line = capsys.readouterr().out.strip()
    obj = json.loads(line)
    assert obj["event"] == "ready"
    assert obj["port"] == 8765
    assert "ts" in obj

# tests/test_die.py
import json
import pytest
from cli_utils_agent import die, enable_json
from cli_utils_agent import core as _core

def test_die_human_mode(capsys):
    _core._json_mode = False
    with pytest.raises(SystemExit) as exc:
        die("something broke")
    assert exc.value.code == 1
    assert "something broke" in capsys.readouterr().err

def test_die_json_mode(capsys):
    _core._json_mode = True
    try:
        with pytest.raises(SystemExit):
            die("something broke", code=10)
        obj = json.loads(capsys.readouterr().out.strip())
        assert obj["event"] == "error"
        assert obj["message"] == "something broke"
    finally:
        _core._json_mode = False

次をテストします。json_log、json_error、die、log、add_json_flag、enable_json/is_json、json_ready。

GitHub Actions CI
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12", "3.13"]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install -e ".[dev]"
      - run: pytest -v

pyproject.toml に開発用の依存関係を追加します。

[project.optional-dependencies]
dev = ["pytest>=8.0"]

チェックリスト — 完了を宣言する前に実行

スクリプトを変換するかパッケージを作成した後:

  • --help に --json フラグが表示される
  • --json を指定して実行すると、有効な NDJSON が出力される(各行が解析可能な JSON である)
  • デーモンの最初の JSON 行に "event": "ready" が含まれる
  • エラー発生時に、終了コードが 0 以外で "event": "error" が出力される
  • --json が有効なときに、生の print() が実行されない
  • インポート時のエラー(依存関係の不足)には print() ではなく sys.exit("message") を使用する
  • 対話型プロンプトがガードされている
  • 副作用(音声、通知)がガードされている
  • --json を指定していない場合は、人間向けの出力が維持される
  • 未処理の例外によって JSON モードでトレースバックが標準出力に漏れない(main を try/except で囲み、json_error を出力する)
  • テストが成功する(パッケージモードの場合)
  • パッケージが問題なくビルドできる:python -m build(パッケージモードの場合)
  • クリーンな venv にインストールし、インポートが動作する(パッケージモードの場合)

さらに詳しく

次の情報が必要な場合は、references/best-practices.md を読んでください。

  • 稼働確認のためのハートビートパターン(セクション 2.4)
  • 終了コードの規約と文字列エラーコード(セクション 2.3)
  • --schema を使ったスキーマのイントロスペクション(セクション 2.5)
  • CLI と MCP の選択マトリックス(セクション 2.7)
  • エージェントが利用する際のトークン効率化のヒント(セクション 5)

コメント