mcpField
C
cli-build

cli-build

github.com/crafter-station開発11336 回インストール8 週間前に更新
リポジトリ

cli-build について

人が監督するエージェント向けCLIの計画と構築を支援するスキルです。機械可読な契約、非対話実行時の安全性、リスクに応じた保護策、人向け出力を重視します。

主な機能

  1. 01コマンド契約の定義または調査
  2. 02JSON出力とschemaコマンド
  3. 03停止しない構造化エラー
  4. 04変更操作へのリスク別保護策
  5. 05名詞・動詞形式のコマンド設計
  6. 06人向け出力と機械向け出力の分離

活用シーン

  1. 01エージェント駆動のCLIを設計
  2. 02外部APIや既存システムをラップ
  3. 03影響の大きい書き込み操作を実装
  4. 04安定したJSON出力契約を定義

インストール

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

$npx skills add https://github.com/crafter-station/skills --skill cli-build

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

cases/
conventions.md
osmo.md
portfolio-shape.md
README.md
sunat-cli-renta.md
references/
audit-log-patterns.md
auth-patterns.md
build-and-runtime.md
compounding-surface.md
human-output.md
json-contract.md
testing.md
trust-ladder-patterns.md

詳細

cli-build

主なユーザーがエージェントで、監督者が人間である CLI を構築します。これは通常のデフォルト設定を逆転させます。機械可読な出力は後から追加するフラグではなく、非対話型の状況ではプロンプトが失敗の原因となり、書き込みのたびに記録が残ります。

フェーズ 0:契約の出どころはどこか

作成するすべてのコマンドは、何らかの形状を持つものを読み取るか変更します。このフェーズでは、その形状の所有者を明らかにします。所有者によって、契約を探しに行くのか、自分で作成するのかが決まります。

発見型。 CLI が、制御できないシステムをラップする場合です。サードパーティー API、ログインが必要なポータル、デバイス、文書化されていないファイル形式などが該当します。契約は存在しますが、検証されていないため、推測するよりも調査して把握するほうが時間を節約できます。まず surface-recon を実行し、そのレポートを出発点にしてください。

定義型。 自分でモデルを所有する場合です。独自データベースの CRUD、スキャフォルダー、ローカル変換、すでに型を把握しているライブラリのラッパーなどが該当します。リバースエンジニアリングするものはないため、surface-recon の入力はなく、スキップしても問題ありません。代わりに契約を作成し、フェーズ 1 の前に一度で仕上げてください。エンティティ、そのフィールドと型、各エンティティに許可される操作、そして書き込みが取り消し不能になる条件を記述します。この成果物は、調査レポートから得られるはずだったものです。

混合型。 独自データベースに対する CLI が、決済プロバイダーも呼び出す場合、境界部分は発見型で、中核部分は定義型です。調査するのは発見型の部分だけにしてください。外部への呼び出しが 1 つあるからといって、ビルド全体を調査作業にしないでください。

フェーズ 1 の前に、どの種類かをはっきり宣言し、friction.md に記録してください。分類が明示されていない場合は発見型として扱われます。そのため、定義型のビルドが、誰も作成できないレポートを待って停止することになります。

完了条件: 起源が明示され、調査レポートが存在するか、エンティティと操作が文書化されています。

今すぐ friction.md をコードの横に開き、作業しながら追記してください。

却下したブロックとその理由、このドメインには当てはまらなかった規約、内容が乏しかった参考資料などを記録します。最後にそれらをケースにまとめます。これが、このスキルを改善するための情報になります。以前のバージョンから引き継がれた 7 つの主張は、実際のソースには当てはまりませんでした。それが明らかになったのは、合致しなかったことが記録されたからです。

エージェントファーストが実際に意味すること

例外なく、すべての CLI で次を実現します。

  • --json を用意し、TTY でない場合はフラグがなくても自動的に JSON を出力します。 3 つの CLI がそれぞれ独立してこの方式にたどり着いており、ここで最も価値の高いデフォルトです。
  • 非対話型の実行でプロンプトが処理を止めることはありません。 プロンプトが必要な処理は、ハングする代わりに構造化されたエラーで失敗します。
  • バージョンフィールドを備えた schema コマンドを用意します。 これにより、エージェントは --help を解析せず、実行時に情報を取得できます。調査対象の CLI 14 個のうち 3 個に実装されており、最も価値の高い規約でありながら、最も採用されていません。
  • 意味のある終了コードを返します。 成功時はゼロになり、ユーザーエラーとシステム障害を区別できます。
  • データは常に stdout に、診断情報は常に stderr に出力します。 バナーも例外ではありません。そのため、banner は console.log ではなくブロックになっています。
  • ターミナルから読み取ったシークレットは、画面にエコーされません。 また、TTY がない場合はハングせずに null を返します。これが prompt-secret の役割です。

これらは今や説明文を信頼するのではなく、surfacer リポジトリでテストとして強制されています。このリポジトリは recon 記述子から CLI をコンパイルします。各テストには確認するルールが明記されているので、両者を合わせて読むことができます。

この取り組みで何がわかったかを知っておく価値があります。同じような問題は、このリストが文書に書かれているだけの場所ならどこでも起こりえます。生成器に対してルールをテストにしたところ、3 件の違反が見つかりました。そのうち 2 件は、ここで最も価値の高いデフォルトでした。次に同じリストを生成器自身の CLI に適用すると、ルールを強制するように教えたばかりのツールで、さらに 4 件の違反が見つかりました。エージェントファーストな CLI を生成するコンパイラー自身がエージェントファーストではない、というのは最も見落としやすいケースです。テストはすべて通っており、どのテストも生成された CLI だけを対象にしていたからです。

したがって、このリストは二重に適用されます。テストが 2 つのファイルに分かれているのは、ある crate のバイナリーを別の crate からテストで実行できないためだけです。生成された CLI は crates/surfacer-emit-cli/tests/agent_first_rules.rs、生成を行うツールは crates/surfacer-app/tests/agent_first_rules.rs で確認します。CLI を生成するツールを作る場合は、生成元もこのリストに照らして確認してください。説明文ではビルドは失敗しません。他者には適用しているルールほど、自分には確認しなくなるものです。

ドメインに実質的な影響がある場合(金銭、取り消せない書き込み、第三者への副作用、個人データ):

  • コマンドをリスク別に分類する信頼レベルの段階を設けます。
  • すべての変更操作に --dry-run を用意し、実行した場合に何が起きるかを返します。
  • ネットワーク呼び出しの後ではなく、前に追記専用の監査ログを書き込みます。
  • 人間が別経路から作動させられる緊急停止スイッチを用意します。

API が非同期の場合: ジョブ台帳により、ポーリングが強制終了されても状態を保持し、送信を行うすべてのコマンドに --wait を用意します。ポーリングループを呼び出し元に任せると、エージェントは微妙に誤ったバックオフロジックを自分で書くことになります。compounding-surface.md を参照してください。

損害に応じて負担を調整し、「なし」も正当な選択肢にします。 ひとつの誤操作で何が起きるかを考えてから選びます。公開データセットを読み取るだけの CLI なら、--json と schema コマンドだけで十分です。同じクエリーを 2 回実行しても誰にも損害はありません。インテントトークンが必要なのは、誤操作によって金銭的損失が生じる場合です。誰にも危害を加えられないツールに完全な段階を導入するのは慎重さではなく、呼び出すたびにエージェントが負担するコストであり、人間に内容を読まずに承認する習慣をつけさせます。

問題となるのは距離ではなく、損害です。自分のローカルデータベースに対する delete は、ネットワークも金銭も第三者も関係なく、取り消せません。そのため、読み取り操作には何も追加しなくても、破壊的な動詞には --dry-run と確認を設ける価値があります。実際に保有しているバックアップから復元できる書き込みなら、対策はより軽くて済みます。プロジェクトではなく、コマンド単位で評価してください。trust-ladder-patterns.md を参照してください。

フェーズ 1:まず配布先を決める

ここから始めます。配布先によって書くコードが制約され、後から変更するのは大変だからです。

インストールする人対象方法
ソースからインストールする自分だけランタイムの shebang、ビルドなし最も素早く反復でき、パッケージ化も不要
JS エコシステムの開発者Node 向けにビルドし、npm に公開追加インストールなしで npx が使える
ランタイムを持たないすべての人ネイティブバイナリにコンパイル前提条件は一切不要

3 つすべてを実現するためのルール:ランタイム固有の API ではなく、Node の API サーフェス(fs、path、process、http)に合わせて書きます。そうすれば、ターゲットは書き直しではなく、ビルド時に決められます。

完了条件: 対象ユーザーが明確になっており、好みではなく、そのユーザーから配布先が決まっていること。

これが見た目以上に重要な理由:インストールする人が持っていないランタイムを shebang で指定したパッケージを公開しても、何も防いでくれません。このスキルの元になったコーパスで公開済みの 4 パッケージのうち 3 つが、まさにそれをしています。そのランタイムを持たない人がインストールするまでは問題なく動作し、インストールすると原因も対処法も示さない env: <runtime>: No such file or directory というメッセージが表示されるため、パッケージは前提条件の説明不足ではなく、壊れているように見えます。build-and-runtime.md を参照してください。

フェーズ 2:コマンドの構成を決める

一貫して名詞・動詞の順にし、スタイルガイドで合意するのではなく、CI で徹底します。エージェントは CLI の一般的なモデルを持っています。他のすべてが get と言っているところで、あるツールだけが info と言うと、エージェントが --help にトークンを費やした後、ようやく成功します。コーパス内の 2 つの CLI は、別々に --json に入力という意味を持たせていました。チェックする仕組みがなければ、こういうことが起こります。compounding-surface.md を参照してください。

{cli} {noun} {verb} [args] [flags]

{cli} order preview --ticker AAPL --amount 100
{cli} order submit --intent-token <token>

最も一般的な操作 1 つには、短縮形を用意してください。利用の 90 パーセントが 1 つのコマンドに集中するなら、そのコマンドは最も短く入力できるものにします。

コマンドにするものを選別します。 UI の操作をすべてコマンドにする必要はありません。繰り返し使われ、スクリプト化の利点があり、入力と出力が明確で、他のツールと組み合わせられる操作を残します。本質的に視覚的、または探索的な操作は除きます。

コードを書く前に、コマンドごとの JSON 契約を定義します。 フィールド名、型、ネスト構造を決めます。これはエージェントが依存する API であり、後から変更すると、エージェントは気づかないまま動作しなくなります。慣例として、--json は出力モードを意味します。JSON 入力 が必要な場合は、別のフラグ名を付けてください。コーパス内の 2 つの CLI は --json を入力フラグとして使い、エージェントが想定する慣例に反していました。json-contract.md を参照してください。

完了条件: すべてのコマンドが名詞・動詞の順の名前を持ち、JSON 出力の形が文書化されていること。出力の形が未決定のコマンドは、まだ構成が定まっていません。

構造化出力に nextSteps を追加します。 次に実行できる操作をレスポンス内でエージェントに伝えることで、--help では防げない、エージェントが次の手を探しあぐねる状況を防げます。

人間向けの表示は別に設計します。 機械向けモードでは、エージェントがフィルタリングするため、すべてを返します。人間向けモードでは、人が求めたものを返します。両方に同じ出力を使っても、どちらにも役立ちません。読みやすさ、読者が想定する指標の方向、尺度のしきい値の配置、繰り返しを見出しにするタイミングなど、すべてについては human-output.md を参照してください。このスキルの他のチェックでは、読みにくい表を検出できません。これを使って最初に構築した CLI は、すべてのチェックに合格したにもかかわらず、その晩に何が再生されているかを尋ねた人に 275 行を表示しました。

人間向けに仕上げた出力を標準の範囲とします。 ユーザーが MVP、最小構成、ヘッドレス、または機械専用の CLI を明示的に求めている場合を除き、視覚的な階層、意味に応じた色、読みやすい表、時間のかかる処理の進捗表示、人間向けの表示があるコマンドでの TTY 限定バナーを含めます。コマンド数が少ないことは、質素な出力でリリースする理由にはなりません。表示をコンパクトに保つ理由にすぎません。

フェーズ 3:実績のあるブロックを組み合わせる

どの CLI にも同じ基本要素が必要です。フラグの解析、設定ファイルのパス、アトミックな書き込み、監査ログ、TTY の検出、承認ゲート、エラー形式です。これらを毎回一から書くことに時間がかかり、バグも潜みます。

TypeScript CLI のインフラには、まず cligentic を検討してください。 そのレジストリからインストールされるのは通常の TypeScript コードで、インストール後はプロジェクトが所有します。ブロックとパッケージの推移的な依存関係は解決され、cligentic のランタイム依存はありません。components.json がないプロジェクトでは、package.json に名前空間を登録するため、コンポーネントツールは前提条件ではなくなります。

正式な導入手順はcligentic のスキルに記載されています。すでに cligentic を利用できる場合は、ブロックの調査、各ブロックの採否判断、インストール、接続、検証の手順に従ってください。利用できず、実装が作業範囲に含まれる場合は、ファイルを変更する前にインストールを提案してください。

bunx --bun skills add Railly/cligentic --skill cligentic

スキルのインストールは、プロジェクトまたはユーザーの環境を変更するため、同意を得てから進めてください。ユーザーが断った場合、作業が読み取り専用の場合、またはスキルをインストールできない場合は、GitHub から正式なスキルを読み、インストールせずにその導入手順に従ってください。

ブロックごとに、理由を添えて選択してください。 各ブロックは、記憶を頼りに書くはずの処理を置き換えることで採用の価値が生まれます。人が使う TypeScript CLI では、ユーザーが明示的に範囲を縮小しない限り、detect、style、banner を表示用のデフォルトセットとします。実際の置き換えでは、7 つのブロックをそのまま採用し、2 つはハイブリッドとして残し、7 つは理由を添えて見送りました。最も重要な見送りは、エージェント向けにすでに公開されていたエンベロープと形式が競合する出力ヘルパーでした。公開済みの契約は、共有ブロックより優先されます。

完了の条件: 各ブロックについて、採用、ハイブリッド化、または見送りのいずれかを決定し、その理由をリポジトリに記録します。見送りの理由を記さないと、次に関わる人には見落としのように受け取られます。

TypeScript プロジェクトではない場合や、インストール方法が適さない場合は、ブロックのソースを読み、パターンを再実装してください。重要なのはパターンであり、ファイルは作業を短縮する手段です。

フェーズ 4:損害に見合った安全対策

まず、対象外となる条件を確認します。対象範囲内のどのコマンドも、データを失わせたり、金銭を使わせたり、第三者に影響を与えたりする可能性がない場合は、このフェーズではその事実を friction.md に1行で記録して、次に進みます。読み取りのみの CLI や、上書き可能なファイルだけを書き込む CLI には、制御すべき対象がありません。見落としたのではなく、判断したのだと次の読者に伝わるよう、調査結果を記録してください。

それ以外の場合は、trust-ladder-patterns.md を読み、適切な形を選んでください。実際のコードには、実質的に異なるものが3種類あります。どれを選ぶかは流行ではなく、ドメインによって決まります。

影響を伴うドメインでは、次の事項を必ず守ってください。

事後ではなく、事前に監査する。 保留中の記録を書き込み、処理を実行し、同じ ID で最終記録を書き込みます。処理の途中でプロセスが終了しても、何も記録されないのではなく、監査可能な保留中のエントリーが残ります。日単位で分割した JSONL 形式とし、追記専用にして、ファイル権限を制限してください。

ドライランでは実際の経路を実行する。 固定値の形式を返すだけの --dry-run では、何も検証できません。コーパスにある実装の中で最も優れているものは、プロバイダー自身のプレビューエンドポイントを呼び出し、その結果を返します。

同意のゲートは、信頼のゲートより厳格にする。 法的な同意には、実際の TTY を必須とし、--yes による回避手段を一切設けないようにしてください。エージェントが人間の代わりに規約へ同意できるなら、そのゲートは形だけです。

応答を使って処理する前に、応答内容を検証する。 コーパスにあるある CLI は、プロバイダーのプレビュー画面に期待する名称、金額、通貨が文字どおり含まれていない限り、送信を拒否します。このチェックにより、プロバイダーのバグと、リクエスト構築時の誤りの両方を検出できます。

完了条件: すべての変更を伴うコマンドに信頼レベルが設定され、ドメインに基づいてラダーの形が選ばれていること。または、対象範囲内に影響を伴うコマンドがないことが friction.md に記録されていること。

第三者からの応答テキストは、信頼できない入力として扱う。 外部 API のフリーテキストには、出力を読むエージェントを狙ったプロンプトインジェクションが含まれている可能性があります。指示として解釈される場所に届く前に、エスケープしてください。

フェーズ 5:名前を挙げた安全機能をすべて接続する

機能は、呼び出し箇所が存在して初めて接続済みになります。それまでは定義にすぎず、--help に機能が記載されていても、コードが裏付けていない主張です。

インポートされているものの一度も呼び出されない監査ログモジュール。どのコマンド本体からも読み取られないフラグオブジェクトに解析される --dry-run。両方を宣伝する --help。このような状態は、今日でもインストール可能なパッケージで実際に使われています。そして、まったく機能がない場合よりも明らかに悪い状態です。オペレーターである人間やエージェントが、実際には何もないのに、記録とプレビューがあると信じてしまうためです。

確認方法:呼び出し箇所を grep で探します。定義箇所にしかヒットしない場合、未接続です。

完了条件: --help またはドキュメントで名前を挙げたすべての安全機能に、grep で確認できる呼び出し箇所があること。

同じコーパスから、公開済みパッケージで実際に見つかったものをさらに2つ紹介します。

  • テストが1つもない状態で公開されている。 最低限、認証フロー、コマンドごとの JSON 出力仕様、署名や暗号処理のコードをテストしてください。
  • 名称変更前の製品のデッドコードに、エントリーポイントからまだ到達できる。 非推奨とは削除済みであることを意味し、単に言及されていないだけでは不十分です。

フェーズ 6:検証してから記録する

まずグローバルにリンクします。 何かを検証する前に、CLI を PATH に追加し、ユーザーと同じ方法で起動できるようにします。

bun link          # or npm link
which mycli && mycli --help

bun run src/cli.ts の実行で検証できるのはファイルです。mycli の実行で検証できるのは、実際に配布されるものです。bin エントリー、シバン、解決済みの依存関係、バナーが対象になります。このうち 3 つはソースファイルからの起動では確認できません。また、bin のパスが壊れていると、ユーザーが最初にインストールしたときに問題が発生します。

完了の定義は、観察した動作です。 コマンドを実行し、出力を読みます。そして、正確さを確かめるために一度、ドメインに詳しくない人の目線でもう一度読みます。最大の数値は何を意味するのか、最初に目に留まるのは何か、すべての行で同じ内容になっている列はないかを確認します。human-output.md を参照してください。

人間向けの CLI では、完了とする前に実際の TTY 経由の動作を調べ、次のすべてを確認してください。

  • バナーと診断情報は stderr に出力され、機械可読データは stdout に出力されること。
  • --json、パイプ経由の出力、その他の機械向けモードに ANSI エスケープシーケンスが含まれないこと。
  • NO_COLOR によって、内容を変えずにスタイルが無効になること。
  • 色付きの列が、文字列の長さではなく可視幅に基づいて整列されること。
  • 時間のかかる処理で、タイマーアニメーションではなく、完了した作業に基づく実際の進捗が報告されること。

新機能の検証では、同じストリームを使う既存機能も調べることになります。 新しいバナーを追加しても stdout がクリーンなままだと確認したところ、そこに 810 バイト出力されていましたが、バナーは含まれていませんでした。単独で起動すると、前のラウンドからヘルプテキストが stdout に出力され、終了コードもゼロ以外になっていたのです。単独起動はテストされるケースではないため、誰も確認していませんでした。

テストスイートがグリーンでも、見かけほど多くのことは証明されません。見落とされる点と、それぞれのギャップを埋める方法については、testing.md を参照してください。

CLI と一緒に、エージェント用のマニュアルを配布します。 CLI のリポジトリ内に skills/<name>/SKILL.md を用意し、コマンド、JSON エンベロープ、重要なフラグ、組み合わせる価値のあるワークフローを記載します。これがなければ、すべてのエージェントが --help を通じてインターフェースを再発見することになります。それをなくすために、このスキルが存在します。コードと一緒に管理することで内容の乖離を防ぎ、npx skills add <owner>/<repo> で CLI をインストール可能にします。

次に、ビルドの記録を cases/ に残します。 対象、現場、配布方法の選択、採用または却下したブロックとその理由、発生した問題、別の方法を取るならどうするかを記録します。friction.md の内容も取り込みます。繰り返し見られる知見を conventions.md にまとめ、全体像については portfolio-shape.md を読んでください。

他人のコードではなく、自分のコードを名指しします。 自分が書いた CLI の事例では、何が壊れたのかを具体的に記述できます。他人のパッケージについての知見では、主体を伏せ、欠陥だけを記述します。境界についての詳細は cases/README.md を参照してください。

完了条件: CLI がグローバルにリンクされ、独自の名前で実行されて実際の出力が確認されていること。有効なフロントマターを含む skills/<name>/SKILL.md が存在し、friction.md の内容を取り込んだ事例ファイルが存在すること。

npx skills add <owner>/<repo> は、リモートが存在するまで確認できません。また、未公開のリポジトリで失敗した場合は、エラーメッセージを読まない限り、SKILL.md の形式が不正な場合と区別できません。有効なフロントマターであることはローカルで確認できますが、インストール可能かどうかを確認できるのはプッシュ後です。

ここは誰もが飛ばす部分であり、飛ばすからこそ同じ教訓が何度も再発見されます。誰かがコーパスに記録する前、4 つの CLI がそれぞれ独自に同じ監査ログ設計を作り直していました。

参考資料

  • cligentic skill:実行時の検出、導入、配線、検証、ブロックの作成
  • trust-ladder-patterns.md:ドメインに応じて選ぶ、実際の3つのパターン
  • json-contract.md:出力モード、TTY 検出、実装されている NDJSON
  • audit-log-patterns.md:2段階の書き込みと、デッドコードの落とし穴
  • auth-patterns.md:確認された4つのアーキテクチャ、ローテーション、シークレットの扱い
  • build-and-runtime.md:配布マトリックスの詳細
  • testing.md:テストスイートが成功しても証明できないこと
  • human-output.md:対象は監督者ではなく読者です。指標の方向性、調整済みの尺度、人間向けのデフォルト設定について
  • cases/README.md:ケースの書き方と、ケースで名前を挙げてよい人について
  • compounding-surface.md:非同期の --wait、CI で強制される用語、--deliver と feedback。コーパスではなく公開済みの成果物に基づいており、その旨が明記されています

境界

上記の各フェーズでは、何をすべきかを説明しています。フェーズが明らかに当てはまらない場合にも守るべき境界が2つあります。

識別子を保存し、シークレットは環境変数から取得します。 パスワードはセッション中にメモリ上に保持するか、キーチェーンに保存します。永続化される設定に含めるのは、アカウント ID とユーザー名です。

実装したことを主張します。 NDJSON、日単位の監査ログ、ドライランについて言及するのは、それらをコードで実装してからです。

コメント