こんにちは。自称ソフトウェアエンジニアの橋本(@hassaku_63)です。
今回は、社内で運用している構成管理ツールである "CMDBuild" の CLI cmdbuild-cli を自作した話を書きます。ツールの機能紹介というよりは、AI に使われることを意識した設計の話です。
- CMDBuild とは
- cmdbuild-cli でできること
- CLI を作った動機
- まず「作業結果を確認できる CLI」を作る
- 書き込み系操作の追加と --json フラグの採用
- 「どんな JSON を渡せばいい?」問題 - skeleton コマンドの追加
- skeleton への --schema フラグの追加
- 実際にどう使われたか
- よかったこと
- 今後の展望
- まとめ
CMDBuild とは
CMDBuild はオープンソースの CMDB(Configuration Management Database)製品です。IT 資産を「クラス」と「カード」という概念で管理し、クラス間のリレーションも定義できます。Salesforce のようにユーザー定義のデータ構造を自由に追加できる、非常に拡張性の高い製品です。「クラス」は RDB のテーブルに相当し、「カード」はレコードに相当します。
CMDBuild を操作する上で登場する主な概念は以下の5つです。
| 概念 | 説明 |
|---|---|
| クラス(Class) | 管理対象リソースの種類を定義する。RDB のテーブルに相当 |
| カード(Card) | クラスの実体(インスタンス)。RDB のレコードに相当 |
| 属性(Attribute) | クラスのフィールド定義。RDB のカラムに相当。文字列・数値・真偽値・参照など型がある |
| ドメイン(Domain) | クラス間のリレーションを定義する。カーディナリティや cascade 動作も設定できる |
| ルックアップ(Lookup) | いわゆる Enum。あらかじめ定義した値のセットで、属性の型として指定すると定義済みの値から選ぶ形になる |
以降で紹介する cmdbuild-cli の object サブコマンドはこの5概念に対応しています。
CMDBuild の操作は基本的に Web GUI から行います。クラスの追加・属性の定義・Lookup の作成といったスキーマ変更も、すべてフォームを埋めてボタンを押す作業です。
cmdbuild-cli でできること
本題に入る前に、cmdbuild-cli がどんなコマンドを持つかを先に整理しておきます。
主なサブコマンドは以下の通りです。
| コマンド | 用途 | AI 向けの設計ポイント |
|---|---|---|
snapshot |
スキーマ全体をファイルにダンプ | 並列取得・ファイル形式で before/after 比較を可能にする |
object list/get |
クラス・属性・Lookup などの参照 | snapshot と組み合わせてスキーマの事後確認を省力化 |
object create |
クラス・属性・Lookup などの作成 | --json がリクエストボディと 1:1 で AI が直接ペイロードを組める |
skeleton |
書き込み系操作の --json オプションに渡す JSON のテンプレート生成 |
何を埋めるかを示す(--schema と組み合わせて使う) |
skeleton --schema |
フィールド制約を JSON Schema で出力 | enum・required・description で AI が外部ドキュメント不要になる |
各コマンドの詳細と設計ポイントはこのあと順に説明します。
なお、今回のスキーマ変更はクラス・属性・Lookup の新規追加のみで、既存リソースへの変更は発生しませんでした。このため update 系・delete 系のコマンドは現時点で未実装です。
snapshot — CMDBuild のスキーマ全体をファイルにダンプします。--concurrency オプションで API リクエストを並列実行でき、出力はリソースごとのJSONファイルになっています。適用前後にスナップショットを取れば diff や jd でスキーマの変化を確認できます。
object(read 系 / write 系) — クラス・属性・Lookup・ドメインといった CMDBuild のリソースを操作します。read 系(list/get)はスキーマの確認に、write 系(create)は変更の適用に使います。write 系の --json フラグはリクエストボディと 1:1 対応しており、AI が API 仕様から直接ペイロードを組み立てられる設計にしています。
skeleton — write 系コマンドに渡す JSON の骨格を生成します。--schema フラグを付けると JSON Schema 形式でフィールドの制約も返します。AI がフィールドの有効値や必須項目を外部ドキュメントなしに把握できるようにする、この CLI の中心的な設計です。
各コマンドに「AI がどう使うか」という観点が含まれています。以降のセクションでそれぞれの設計判断を掘り下げます。
CLI を作った動機
CMDBuild の GUI 操作は、率直に言って物理的な動作が多く煩雑です。クラスを1つ作って属性を5個追加するだけでも、何度もフォームを開いて値を入力して保存する作業が繰り返されます。HTTP ベースの API は存在していますが、公式の API ラッパーや CLI ツールは提供されていませんでした。
自作の CLI を作ろうとした最初の動機ですが、「スキーマ変更の全自動化」ではありませんでした。適用作業は手作業でもいい、事後のチェックを省力化したいというのが当初の構想です。変更後に「意図どおりのスキーマになっているか」を確認するために、get/list 系の API 操作と snapshot コマンドを作ることを先に考えていました。
ところが、作業を進めるうちに想定外の問題が出てきました。Web UI の文字列長制限に抵触して、意図する値が入力できないケースがあるのです。これは Web UI 上だけに存在する制約で、API 経由なら回避できることがわかりました。CLI は当初「読み取り系だけ作る」というスコープでしたが、このことで「書き込み系も必要だ」と判断を変えました。
また、今回は私の大ボスにあたる上司から「できるだけ AI にやらせる方法を考えて、実行しなさい」という指令も出ておりました。それならこの機会に事後チェック以外の効率化も一緒にやってしまうか、と腹を括ったのが開発の経緯です。
本記事では、その過程でどのような設計判断をしたのかを順を追って説明します。
まず「作業結果を確認できる CLI」を作る
最初に実装したのは get/list 系のコマンド群と snapshot コマンドです。動機があくまで「事後チェックの省力化」だったので、まず確認できることを優先しました。
cmdbuild-cli object class list cmdbuild-cli object attribute list --class-id ECSCluster cmdbuild-cli object lookup list
中でも要になったのが snapshot コマンドです。CMDBuild のスキーマ全体(クラス・属性・ドメイン・Lookup)をまとめてファイルにダンプします。
$ cmdbuild-cli snapshot --help
Take a snapshot of the current CMDBuild schema to files
Usage:
cmdbuild-cli snapshot [flags]
Flags:
--concurrency int Maximum number of concurrent API requests (default 10)
--force Overwrite existing snapshot data
-h, --help help for snapshot
--output-dir string Directory to write snapshot files (required)
# .snapshots/before/ ディレクトリに現在参照している環境のスキーマ全体をダンプする
cmdbuild-cli snapshot --output-dir .snapshots/before/ --concurrency 10
--concurrency オプションで API リクエストを並列実行できるため、リソース数が多くてもそれほど時間がかかりません。スキーマ変更の前後でスナップショットを取っておけば、diff --brief でどのファイルが増えたか、jd で JSON レベルの差分が何かを確認できます。
diff -r --brief .snapshots/before/ .snapshots/after/ jd .snapshots/before/classes/ECSCluster_attributes.json \ .snapshots/after/classes/ECSCluster_attributes.json
「意図した変更だけが入っているか」をファイル差分として確認できる状態にする、これが当初 CLI を使ってやりたかったことでした。
ちなみに、比較アプローチについて補足しておきます。今回は before/after でそれぞれ別ディレクトリにスナップショットを取得し、diff コマンドで比較する方法を採りました。ただし個人的には、同じディレクトリにスナップショットを上書きして git diff で比較するやり方がよりベターだと思っています。追加・削除の変化が git の差分として可視化され、PR のレビューとしても確認しやすいためです。今回は第一回目の試みとして before/after とディレクトリを分離する方法を紹介しましたが、運用を重ねるなかで git ベースの比較に移行することも検討しています。
書き込み系操作の追加と --json フラグの採用
事後チェックの仕組みができたところで、次の問題が出てきました。Web UI にのみ存在する入力文字列長の制限があり、意図した値が入力できないケースが生じたのです。API 経由なら回避できることがわかり、write 系のコマンドも必要だと判断しました。
そこで write 系のコマンド(object class create、object attribute create など)を追加しました。
cmdbuild-cli object class create --json '{"name":"ECSCluster","description":"ECSクラスター","parent":"Resource"}'
cmdbuild-cli object attribute create --class-id ECSCluster --json '{"name":"ClusterName","type":"string","mode":"write","mandatory":true,"maxLength":255}'
設計上のこだわりが2点あります。それは、パラメータを個別のキーワードではなく --json フラグにまとめたこと、そして --json に渡す内容は HTTP リクエストボディと 1:1 対応させるようにしたことです。パスパラメータ(--class-id など)は専用フラグで受け取り、--json の中身はそのまま API に流れる、という構造です。
CMDBuild の REST API 仕様と CLI のインターフェースを一致させておくことで、「API ドキュメントを読めば CLI の使い方もわかる」状態を作れます。AI がコマンドを組み立てる場合も、API 仕様から直接ペイロードを構成できます。後述する skeleton コマンドおよび --schema オプションと組み合わせることで、AI が外部ドキュメントなしに正しい JSON を生成しやすくなります。
「どんな JSON を渡せばいい?」問題 - skeleton コマンドの追加
object コマンド群ができると、次の問題が浮上しました。それは --json に何を渡せばいいのか、ということです。特に今回は AI がこの CLI を使うことを想定していましたので、AI が正しい JSON を生成できるようにするための工夫が必要でした。
フィールド名は何か、型は何か、必須項目はどれか。CMDBuild の REST API にはこれらを一覧できる公式 CLI がなく、都度ドキュメントや実装を参照する必要があります。人間にとっても手間ですが、AI にとってはさらに厄介です。外部ドキュメントを参照させようとすると、コンテキストが膨らむ上に、参照先の形式が一定でないため解釈にブレが生じます。
CLI の先行開発の成果により、ドキュメントやソースコードを調べ、AI にも読みやすい markdown ドキュメントとしてレポジトリ内に資料を整備する仕組みは構築できていました。しかし、それは開発時に役立つ情報であっても、実際に CLI を使う上ではナビゲーションが不足しています。
そこで作ったのが skeleton コマンドです。リソースごとに「空のテンプレート」を生成します。これは AWS CLI にも存在する --generate-cli-skeleton オプションのアイデアを参考にしています。
$ cmdbuild-cli skeleton class
{
"active": true,
"description": "",
"name": "",
"parent": "Class",
"prototype": false,
"type": "standard"
}
テンプレートを取得してフィールドを埋め、そのまま --json に渡すワークフローが成立します。attribute には --type フラグを付けることで、型ごとに異なるフィールド構成のテンプレートも取得できます。
$ cmdbuild-cli skeleton attribute --type reference
{
"active": true,
"description": "",
"direction": "direct",
"domain": "",
"mandatory": false,
"mode": "write",
"name": "",
"showInGrid": true,
"type": "reference"
}
しかし、これだけではまだ不十分です。
このテンプレートを AI に渡してフィールドを埋めさせようとすると、AI は推測するしかない情報がいくつか残ります。テンプレートは「何を埋めるか」は教えてくれますが、「どう埋めるか」は教えてくれません。ここで2つの観点があります。
- 製品の API 仕様として、そのフィールドはどのような意味を持ち、どんな値が有効なのか
- 製品のユーザー自身のユースケースや運用ルールによって導出される、慣例的なルール
後者については、基礎となるツールが整えばあとは個別に Agent Skill を作って運用ルールを教え込むことができますので、それで十分と割り切りました(これは本記事の執筆時点で今後の展望です)。前者の話は今ここでカバーすべきと判断しました。その回答が次節になります。
skeleton への --schema フラグの追加
skeleton に --schema フラグを追加しました。テンプレートの代わりに、JSON Schema(draft-07)形式でフィールドの制約を返します。
$ cmdbuild-cli skeleton attribute --type string --schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["type", "name", "mode"],
"properties": {
"name": {
"type": "string",
"description": "Attribute name. Alphanumeric and underscore only."
},
"mode": {
"type": "string",
"enum": ["write", "read", "hidden", "immutable", "sysread", "syshidden"],
"description": "Write permission mode. Always specify explicitly; omitting causes undefined behavior. Typically 'write'."
},
"maxLength": {
"type": "integer",
"description": "Maximum number of characters."
},
...
}
}
AI にはこのスキーマ情報を読み込んで JSON を生成してもらいます。enum や required の情報があるため、AI が有効な値を選びやすくなります。特に required は重要で、必須項目を埋めずに生成してしまうミスを防げます。
--type reference のように型を指定すると、その型に固有のフィールドが追加されます。reference 型であれば domain(どのドメインを参照するか)と direction(direct / inverse)が required に加わります。
$ cmdbuild-cli skeleton attribute --type reference --schema
{
...
"required": ["type", "name", "mode", "domain", "direction"],
"properties": {
"domain": {
"type": "string",
"description": "Domain name that defines the relation. Use 'object domain list' to see available domains."
},
"direction": {
"type": "string",
"enum": ["direct", "inverse"],
"description": "Reference direction within the domain."
},
...
}
}
description の中に "Use 'object domain list' to see available domains." というヒントが入っています。これは、CMDBuild の製品特性的な部分も加味したものです。ユーザーの使い方次第で動的に変わる値(任意のユーザー定義のデータ構造)は静的な JSON Schema に埋め込めないため、「どのコマンドで調べるか」を自然言語で案内しています。AI はこのヒントを読んで object domain list を実行し、返ってきた候補から値を選ぶ、というオーケストレーションが成立しやすくなるように意図しました。
なぜ独立コマンドでなく --schema フラグか
skeleton --schema を設計する際、「schema という独立したサブコマンドにする」案も考えました。最終的に --schema フラグとして skeleton に統合した理由は2つあります。
1つはスケルトンとJSONスキーマがペアであることを明示するためです。skeleton class と skeleton class --schema が対になっていると、「テンプレートの制約情報を取得するコマンド」という意味が構造として伝わります。
もう1つは実装上の強制です。新しいリソースを skeleton に追加するとき、テンプレートとスキーマを同時に実装する義務が自然に課されます。独立したコマンドにしてしまうと、テンプレートだけ作ってスキーマを後回しにする、という対応漏れが起きやすくなります。エージェンティックコーディングの観点からも、テンプレートとスキーマはセットで提供されるべきだと考えました。
実際にどう使われたか
この CLI を使って、実際に今運用中の CMDBuild 環境に対するスキーマ変更を行いました。たとえば ECSCluster・ECSService というインベントリ情報を表現するクラスの作成がありましたので、それで例示します。
おおまかな流れは以下の通りです。
- 手作業で適用する前提で手順書を作成
- ローカル環境で適用
- snapshot でスキーマ取得し、パラメータレベルで結果確認
- AI が「ローカルで作業適用した後の snapshot の内容」「手順書で指示されている変更内容」「skeleton コマンドの実行結果」を読み、
--jsonに渡す具体的なパラメータを決定 - AI が手順書を CLI ベースに書き直す。渡す値は前のステップで決定されたものを使用
- 各環境に適用・差分確認
手順書と snapshot による確認
この CLI を実行する前段階として、関係者と合意した(詳細パラメータまでは記載していない)スキーマ案のドキュメントがあり、そこから手作業を前提とする手順書を作成していました。それを手元に用意した docker compose 環境で実際に試して齟齬がないことを確認しつつ、ステークホルダーとの合意に含まれない細部のパラメータを調整します。ローカル環境で反映したスキーマ情報は snapshot コマンドで吐き出して、その情報を AI に読ませました。この時点で、snapshot コマンドを使った AI による自動チェックの仕組みが完成しています。
AI によるコマンド生成
前節の作業で得られた情報(手順書・ローカル環境の snapshot・skeleton --schema の出力)を AI(Claude)に渡し、object create コマンドの --json 引数を生成させました。
ローカル環境のスナップショットから、詳細レベルのパラメータが得られます。これを AI に読ませて、さらに手作業での実施を前提に組んでいた手順書も読ませ、変更適用作業の部分をすべて自作の CLI に置き換えさせます。こうして、CLI 操作で完結し、かつ AI がスナップショット差分から変更内容の正しさを自動チェックしてくれる、新しいバージョンの手順書が完成します。これで異なる環境 (stg/prod など) への適用にも安心して対応できるようになります。
前節で取得した snapshot と手順書を AI に読み込ませた上で、変更が必要なリソースごとに skeleton --schema でフィールド仕様を確認しながらコマンドを組み立てます。以下はその例です。
# 前提: 前節で取得した snapshot と手順書を AI に読み込ませている
# $ cmdbuild-cli snapshot --output-dir .snapshots/local/ --concurrency 10
# フィールド制約を確認(skeleton --schema の出力)
$ cmdbuild-cli skeleton attribute --type reference --schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["type", "name", "mode", "domain", "direction"],
"properties": {
"domain": {
"type": "string",
"description": "Domain name that defines the relation. Use 'object domain list' to see available domains."
},
"direction": {
"type": "string",
"enum": ["direct", "inverse"],
"description": "Reference direction within the domain."
},
...
}
}
# AI が組み立てたコマンド。実際には手順書のステップ数の分だけ同様のコマンドが生成され、手順書の内容を置き換える。
# (snapshot/手順書/上記のペイロードスキーマ仕様も参照する前提のプロンプトを組み、生成されたコマンド)
$ cmdbuild-cli object domain create --json '{
"name": "ECSService_ECSCluster",
"source": "ECSService",
"destination": "ECSCluster",
"cardinality": "N:1",
"cascadeActionDirect": "setnull",
"cascadeActionInverse": "restrict",
"descriptionDirect": "クラスター:",
"descriptionInverse": "サービス:"
}'
$ cmdbuild-cli object attribute create --class-id ECSService --json '{
"name": "Cluster",
"description": "クラスター",
"type": "reference",
"mode": "write",
"mandatory": true,
"domain": "ECSService_ECSCluster",
"direction": "direct"
}'
上記のコマンド生成例において、AI が --schema の enum や required を参照しているため、cascadeActionDirect に無効な値を入れたり、domain の指定を忘れたりといったミスが起きません。実際には Agent Skill のような書式でプロンプト自体も外部ファイルに切り出して運用しているので厳密に上記の通りというわけではありませんが、だいたいの流れとしてはこのような形で AI にコマンドを生成させ、実行をしました。
なお、生成されたコマンドについては人間が手順書との対応関係をレビューしています。CLI 側にも簡易バリデーションを実装しており、製品仕様として許容されない値やキーは API リクエスト前に弾きます。それを通過した場合は API のエラーレスポンスをそのまま返す設計です。今回の試行では、--schema を活用した結果、スキーマ違反による生成ミスは発生しませんでした。
よかったこと
実際にこの CLI を使ってスキーマ変更を行ってみて、良かった点をまとめます。
まず、手順書の CLI 版をプロンプト1発で生成できた点が最も大きな成果です。これは skeleton コマンドと --schema オプションによってフィールド仕様を機械可読な形で提供できたことが大きいと考えています。今回の手順書は A〜D の4カテゴリ、45コマンドで構成されており、手順書全体は602行ありました。snapshot・手順書・skeleton --schema の出力を渡すだけで、すべてのコマンドとパラメータが正確に組み立てられ、期待通りの CLI 版の手順書が得られました。
次に、snapshot による差分確認が有効に機能しました。適用前後のスナップショットを比較することで、意図した変更のみが入っていることを機械的に確認できます。
また副次的なメリットとして、環境間の作業差異が発生しにくい体制を整えられました。CLI ベースの手順書に切り替えたことで、ローカルで検証した手順をそのまま他環境に適用できる再現性が確保されています。本番適用はこれからですが、手動操作に比べて環境ごとに手順がブレるリスクを下げられています。
今後の展望
skeleton --schema によって「API 仕様として何が有効か」を AI に伝える手段は解決しました。残る課題は「私たちの実環境での運用ルールを踏まえて、値をどう埋めるか」という側面です。この観点はユーザーごとに事情が異なる話なので、skeleton コマンドが出力する JSON Schema には書けません。こうした組織固有のナレッジの伝え方として、Agent Skill として定型化することを考えています。CLI 側の責務(何ができるか・何が有効か)と Agent Skill 側の責務(どう使うべきか)を分けて管理していくのが現時点での構想です。
また、今回の話は割と他の製品や業務でも応用が利く内容だと思っています。CMDBuild に限らず今回のアイデアは試していきたいですし、また同様に CMDBuild に対する変更の用事ができた際には Agent Skill の部分も含めた全体のワークフロー・オーケストレーションの実装にも取り掛かりたいと考えています。
まとめ
今回の取り組みを通じて感じたのは、「AI に使われる CLI は、人間に使われる CLI とは設計の視点が少し違う」ということ、そして「AI 時代においては、業務効率化の基礎土台となるツールの整備はより重要になる」ということです。
本記事のように、既存のツール・サービスに対して CLI を自作する機会がある方は、ぜひ「AI がこのコマンドをどう使うか」という視点を設計に取り入れてみてください。
今回の経験をもとに、AI 向け CLI 設計の観点を整理してみました。あくまで1回の実装から得た印象なので、普遍的なベストプラクティスとは言い切れませんが、参考程度に。
- [ ] write 系コマンドの入力は
--jsonでリクエストボディと 1:1 対応させているか - [ ] テンプレート生成コマンド(skeleton)を用意しているか
- [ ] フィールドの制約(有効値・必須項目・意味)を機械可読な形式で返せるか(例:JSON Schema)
- [ ] 動的に変わる値については、どこで調べるかのヒントをフィールドの説明に含めているか
ちなみに、この取り組みが始まる時点で CLI の開発はスタートしていましたが、当時は read 系機能しかありませんでした。write 系と skeleton は本記事で紹介した一連の作業の中で、企画から実装まで1日で仕上げました。コードはすべて AI が書いています。今日ではもはや言うまでもないことですが、エージェンティックコーディングの威力を改めて思い知りました。AI 以前であれば5倍以上の時間は軽くかかっていたと思います。
最後に、本記事が大いにインスパイアされた記事をご紹介しておきます。
CLI 設計には Rewrite Your CLI for Agents が参考になりました。書き込み系コマンドのパラメータを --json で統一したことや、skeleton サブコマンドと --schema オプションによるスキーマの機械可読な情報提供といった設計判断は、同記事のアイデアを cmdbuild-cli に落とし込んだものです。