AIコーディングエージェントのCLI(コマンドラインインターフェース。ターミナル上で操作するツール)をデスクトップアプリ化しようとして、想定外の実装量に驚いた経験がある方に向けた内容です。xAIが公開しているRust製コーディングエージェント「grok-build」をTauri 2でラップしたgrok-guiというプロジェクトの構成から、CLIエージェントをGUI化する際に見落としやすい設計判断を整理します。
何が起きるか
ターミナルで動くAIコーディングエージェントは便利な反面、ブラウザやエディタとの行き来が発生します。これを解決しようとGUIラッパーを作る発想は自然です。
ところが、多くの実装者が最初にぶつかるのは「エージェントの中身を知らずにUIだけ被せる」という罠です。tmuxセッションをWebviewに埋め込む方法は、結局スクロールバック(過去の出力ログ)を読んでいるだけで、UXは改善しません。
もう一つのよくある罠は、OpenAIのChat Completions API(テキスト生成用のHTTP API)を直接ラップする実装です。コミュニティ製のWebラッパーの多くがこの方式を採用していますが、これだとツール呼び出し(エージェントがファイル操作やコマンド実行を行う処理)やプラン更新、権限確認リクエストといった、コーディングエージェント特有のイベントを取りこぼします。見た目はチャットUIでも、実際のエージェントループの状態は反映されません。
なぜ起きるか
原因を分解すると、CLIエージェントには2つの顔があることに気づきます。ひとつは「ユーザーに見せるTUI(ターミナル上のUI)」、もうひとつは「エージェントの思考・実行ループそのもの」です。
GUIを作る側が本当に必要としているのは後者への接続です。しかしTUIの見た目だけを真似ようとすると、前者を再現する作業に時間を溶かします。
grok-buildの場合、この問題を回避できたのはACP(Agent Client Protocol)と呼ばれるJSON-RPC 2.0ベースのプロトコルを標準搭載していたためです。エージェントはstdin/stdout(標準入出力)を通じて、テキストの差分・ツール呼び出し・プラン更新・権限リクエスト・セッションのライフサイクルといった通知をJSON-RPCの形式で流します。クライアント側はプロンプト送信・権限応答・モデル切り替え・セッション読み込みをリクエストとして送るだけで済みます。
つまりACPが提供されていれば、GUIを作る側はエージェントループを再実装せず、プロトコルのクライアントを書くだけで済みます。逆にACPのような構造化されたプロトコルがないエージェントをラップしようとすると、標準出力の生テキストをパースする独自実装が必要になり、アップデートのたびに壊れるリスクを抱えることになります。
自分のプロジェクトが該当するか確認する方法
CLIエージェントをGUI化しようとしている、あるいは既存のラッパーを検討しているなら、次の点を確認しておくと判断がしやすくなります。
- 対象のCLIに
--helpやagent stdioのようなサブコマンドがあるか(grok-buildはgrok agent stdioでJSON-RPCモードに入る) - CLIのリポジトリにACPやJSON-RPC 2.0への言及があるか(READMEやプロトコル仕様のドキュメントを検索する)
- 起動時に initialize ハンドシェイク(エージェントのバージョンや対応機能をやり取りする最初の通信)が存在するか
- session/new や session/load 相当のメソッドがプロトコル定義にあるか
これらが確認できない場合、そのCLIは構造化プロトコルを持たず、標準出力の文字列を直接パースする実装しか選択肢がない可能性があります。その場合は開発コストが大きく跳ね上がる前提で計画を立てる必要があります。
対策の手順
実際にgrok-guiが採用した構成は、Tauri 2(Rust製の軽量デスクトップアプリフレームワーク)とReactの組み合わせでした。手順として整理すると次のようになります。
1. 対象CLIのプロトコル仕様を確認する。ACP対応なら、initializeハンドシェイクの実装から始める
2. バックエンド言語を決める。CLI本体がRust製なら、子プロセスとして起動しstdin/stdoutを扱う都合上、ブリッジ側もRustにする方が自然(grok-guiではgrok_runtime.rsがこの役割を担う)
3. デスクトップシェルにTauriを採用するかElectronにするかを、配布サイズとメモリ使用量で判断する
4. 受信したJSON-RPCイベントを型付きのイベントとしてフロントエンドに流し、Reactコンポーネントで描画する
TauriとElectronの比較は次の通りです。
| 項目 | Tauri 2 | Electron |
|---|---|---|
| バイナリサイズ | 約8MB | 約150MB |
| アイドル時メモリ | 約80MB | 約300MB |
| Webviewの実体 | OSネイティブ(WebKit/WebView2) | バンドルされたChromium |
| バックエンド言語 | Rust | Node.js |
grok-guiがTauriを選んだ理由は3つ整理されています。まずCLI本体がRust製で、バックエンドをすでにRustにする必要があったため、Node.jsを別途持ち込むメリットが薄いこと。次に配布サイズの差が、コーディングツールのインストーラーとしては無視できない差になること。最後にOSネイティブのWebviewは、macOSであればSafariと同じレンダリング結果になるため、Webアプリではなくネイティブアプリらしい見た目に近づけられることです。
なお、TauriはWindows環境でMSVCビルドツールを要求するという制約があります。GitHub ActionsのWindows runnerには標準でプリインストールされているため、CI(継続的インテグレーション)のコストはElectronと変わらない点も確認しておくとよいでしょう。
タイトルバーの見た目を調整したい場合は、Tauriの設定ファイルtauri.conf.jsonでtitleBarStyle: "hiddenInset"やtrafficLightPositionを指定することで、ネイティブのウィンドウコントロールを保ちながらタイトルバーの視覚的な主張を抑えられます。macOS向けにネイティブアプリらしい見た目を作りたい場合は、この設定から確認するとよいでしょう。
まとめ
CLIエージェントのGUI化を検討する際は、まず対象のCLIがACPのような構造化プロトコルを話すかどうかを確認することが出発点になります。話すなら再実装は不要で、プロトコルクライアントを書くだけで済みます。
話さない場合は、標準出力のパースという茨の道になることを前提に工数を見積もる必要があります。フレームワーク選定では、Tauriのバイナリサイズとメモリ使用量の優位性、Electronのエコシステムの厚みという、それぞれのトレードオフを見比べて判断する材料になります。
実際に手を動かす際は、対象CLIのリポジトリで--helpやREADME内のプロトコル記述を確認するところから始めてみてください。