aiterm-mcp 0.4.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src=".github/og.svg" alt="aiterm-mcp — AI が握るローカル永続端末を stdio MCP サーバとして公開する" width="100%">
2
+ <img src=".github/og.svg" alt="aiterm-mcp — AI が握る 1 本の永続 MCP 端末。その中へ他のコーディングエージェント(Codex/Grok/Composer)を起動する(tmux ベースの stdio MCP サーバ)" width="100%">
3
3
  </p>
4
4
 
5
5
  # aiterm-mcp
@@ -12,69 +12,112 @@
12
12
 
13
13
  > *(English: [README.md](README.md))*
14
14
 
15
- > **Claude(や任意の MCP クライアント)に、本物の永続シェルを握らせる。** 端末は 1 個を開きっぱなしにし、`ssh`・`docker exec`・REPL は「その中へ送るただのテキスト」。だから AI はコマンドごとに接続し直さずに済む。読み取りはトークン削減つきで返る。
15
+ > **あなたの AI に、ほかの AI を操らせる。** Claude Code から——あるいは任意の MCP クライアントから——1 回の呼び出しで、コーディングエージェント(Codex・Grok・Composer)を永続端末の中に起動し、操作用のセッションを手渡す。何をしているかをトークン削減して読み、次の指示を送る。
16
+ >
17
+ > **これは何か:** AI が握る 1 本の永続 MCP 端末——その中に他のコーディングエージェントも起動できる。`ssh`・`docker exec`・REPL・別エージェントの TUI は、すべてその 1 本の端末の中へ「送るだけのテキスト」として入れ子になる。仕組みはあえて素朴——MCP クライアントが相手エージェントの端末を 1 ターンずつ操作するだけ。隠れたプロトコルも・共有メモリも・自律的な交渉も無い。
18
+ >
19
+ > **人が tmux に張り付く必要はない。** aiterm は MCP 越しにプログラムから駆動されるので、「AI が別のエージェントを起動して操作する」のに端末の前に誰も座らなくていい——オーケストレーションのループ・CI ステップ・cron から動かせる。
16
20
  >
17
21
  > *MCP = Model Context Protocol — Claude Code のようなツールが AI に機能を差し込むためのオープン標準。*
18
22
 
19
- `pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list` 6 ツールだけ。バックエンドは tmux なので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
23
+ 9 ツール: 6 つの **PTY ツール**(`pty_open` / `pty_send` / `pty_read` / `pty_key` / `pty_close` / `pty_list`)で 1 本の永続端末を開き・操作し・読む。加えて 3 つの **エージェント起動ツール**(`codex_agent` / `grok_agent` / `composer_agent`)が、別のコーディングエージェントの TUI を新しい端末の中に起動する。バックエンドは **tmux** なので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
24
+
25
+ **状態:** 開発継続中 · この分野では新参で、別の形に賭けている([既存手段との比較](#既存手段との比較)参照)· 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
26
+
27
+ ## なぜ今
20
28
 
21
- **状態:** 開発継続中 · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
29
+ 2026 のエージェントツールの多くはオーケストレーションへ寄っていっている——先導するモデルが機械的なリファクタを Codex に委ね、一括編集を Composer に走らせながら自分は diff をレビューし、1 つのタスクを複数エージェントに分散して自分のコンテキスト窓を守る。そうしたエージェントはどれも既に端末の中に住んでいる。aiterm はその端末を一級の・MCP ネイティブなツールにする——だから指揮するモデルは、**人がペインを配線しなくても他のエージェントを起動して操れる。**
22
30
 
23
- ## なぜ
31
+ ## 2 つの使い方
24
32
 
25
- AI にコマンドを 1 個ずつ投げる方式だと、SSH では 1 コマンドごとに「接続→認証→切断」になる。痛みは 3 つ **毎回再認証**(鍵のパスフレーズもワンタイムコードも毎回)、**短命なセッションが次々増殖**、そして接続が立て込むと**自分の防御に締め出される**(`fail2ban` で BAN、`MaxStartups`/`MaxSessions` で拒否、アカウントロック)。攻撃者を止めるための仕組みに、自分が締め出される。(自宅サーバで実際にやられた。この再認証地獄なしに自宅環境を Claude Code から操作したくて aiterm を作った。)
33
+ ### 1. SSH・コンテナ・REPL 1 本の永続端末で操作する土台
26
34
 
27
- aiterm **1 個の PTY を永続的に握り**、その中へ一度だけ `ssh host`(や `docker exec -it x bash`)で入る。以降のコマンドは同じ認証済みセッションを通る **認証は 1 回、セッションは 1 本、防御に引っかかる隙が無い**。セッション種別をツールで区別しない。
35
+ これが土台で、tmux だけで動く——他の CLI は要らない。`pty_open` がローカル端末を 1 個握り、`ssh host`・`docker exec -it x bash`・REPL は、その中へ `pty_send` で打ち込む「ただのテキスト」——**一度だけ**。以降のコマンドは同じ認証済みセッションを通る。セッション種別をツールで区別しない。
28
36
 
29
37
  ```
30
38
  pty_open() → ローカル端末を 1 個握る
31
39
  pty_send(id, "ssh 192.168.1.2") → その端末の中で一度だけ認証して入る
32
40
  pty_send(id, "uname -a") → 以降のコマンドは同じセッションを通る
33
- pty_read(id, { wait: true }) → 削減済みの出力を読む
41
+ pty_read(id, { wait: true }) → 削減済みの出力を読む(完了検出つき)
34
42
  ```
35
43
 
44
+ <sub>**起源.** これのために aiterm を作った。自宅サーバを Claude Code からコマンド 1 個ずつ叩くと、SSH コマンドは毎回「接続→認証→切断」になる——鍵のパスフレーズもワンタイムコードも毎回打ち直し、短命セッションが増殖し、やがて自分の防御(`fail2ban`・`MaxStartups`/`MaxSessions`・アカウントロック)に締め出される——攻撃者を止める仕組みに自分が止められる。1 本の認証済みセッションを握れば 3 つとも一度に消える。この痛みがこの永続端末が存在する理由で、その中に丸ごと別のエージェントを起動するのは、そこから育った姿だ。</sub>
45
+
46
+ ### 2. その端末の中に他のコーディングエージェントを起動する — オーケストレーションの旗艦
47
+
48
+ 同じ primitive が別エージェントの TUI を宿す。3 つの起動ツールが、各ベンダーの対話型コーディングエージェント TUI を新しい永続端末の中に起動し、`session_id` を返す。以後はどのシェルとも同じ `pty_read` / `pty_send` で操作する——出力をトークン削減して読み、次の一手を送る。(TUI は全画面アプリなので `pty_read({ screen: true })` で描画済みの画面が得られる。)これはベンダー自身の CLI が導入・認証済みであることが必要——[要件](#要件)参照。
49
+
50
+ ```text
51
+ codex_agent({ session_name: "codex1", cwd: "/repo",
52
+ prompt: "port test/legacy.py to vitest" })
53
+ → { session_id: "codex1", … } # Codex が永続端末で稼働開始
54
+ pty_read("codex1", { screen: true }) → 何をしているか読む(トークン削減)
55
+ pty_send("codex1", "also fix the imports it broke") → 作業の途中で操舵する——TUI を操作している
56
+ ```
57
+
58
+ モデルごとに 1 ツール=ツール名を見ればどのモデルか分かる:
59
+
60
+ | ツール | 起動するもの | 主な引数 |
61
+ | --- | --- | --- |
62
+ | `codex_agent` | Codex CLI(OpenAI・モデルは CLI の既定) | `prompt?`, `reasoning_effort?`, `cwd?`, `session_name?` |
63
+ | `grok_agent` | Grok Build の `grok-build` モデル(xAI) | `prompt?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`), `cwd?`, `session_name?` |
64
+ | `composer_agent` | Grok Build の `grok-composer-2.5-fast` モデル(xAI) | `grok_agent` と同じ |
65
+
66
+ 各ベンダーの CLI が導入・認証済みであること(`codex_agent` は `codex`、Grok 系 2 つは `grok`)。バイナリは `CODEX_BIN` / `GROK_BIN` → `~/.local/bin/codex` / `~/.grok/bin/grok` → `PATH` の順で解決する。前提は **session を作る前に**検証する——`grok_agent` / `composer_agent` は範囲外の `reasoning_effort` を先に弾き(値集合は固定=`low`/`medium`/`high`/`xhigh`/`max`)、CLI バイナリ不在・実在しない `cwd` は 3 つとも失敗する。前提(`reasoning_effort`・バイナリ・`cwd`)で弾かれた起動は **session の残骸をゼロ**にして返し、起動キーストロークを送れなければ session を畳む。(その先でベンダー CLI が実際に立ち上がったか・認証できたかは aiterm は検証しない——`openAgent` は起動コマンドを送った時点で返る。起動できたかは session を読んで確認する。)(`codex_agent` は Codex の受理値が版で変わるため、`reasoning_effort` を検証せず config override〔`-c model_reasoning_effort=…`〕として Codex CLI へ渡す。)`cwd` は絶対パスで渡す——`~` は展開されない。
67
+
68
+ **Claude launcher はあえて無い**し、エージェント間のプロトコルも無い: aiterm の役目は、あなたの Claude(や任意の MCP クライアント)が*他の*エージェントに手を伸ばして端末を操作できるようにすること。起動したエージェントは単なるもう 1 本の永続セッションなので、この README の他のすべて(トークン削減読取・完了検出・人の `attach` での監視/介入)がそのまま効く。
69
+
36
70
  ## デモ
37
71
 
38
- 実際のライブセッションから採取した本物の出力 — トークン削減も完了検出も実物で、モックではない。角括弧のメタ行は `pty_read` が実際に付けるもの。
72
+ <p align="center">
73
+ <img src=".github/demo.gif" alt="aiterm-mcp デモ: pty_open → トークン削減した grep 読取 → Python REPL へネスト、すべて 1 個の永続セッションで" width="100%">
74
+ </p>
39
75
 
40
- ノイズの多い `git log` を、トークン削減して読む(458 273 トークン):
76
+ リポジトリ内で今このツールに実際に通した本物の採取——数値も省略マーカーも各 `is_complete` の判定も、すべてツール自身の出力で、モックではない。角括弧のメタ行は `pty_read` が実際に付けるもの(この日本語 README ではメタ行を実出力のまま載せている。英語版は可読性のため訳出している)。
77
+
78
+ 長い出力を head+tail に畳む——中間を省いているのは私でなく reducer(166 → 56 トークン):
41
79
 
42
80
  ```text
43
- → pty_send("demo", "git log --oneline -12")
81
+ → pty_send("demo", "seq 1 150")
44
82
  → pty_read("demo", { wait: true })
45
- 3ce487e (HEAD -> main, origin/main) docs(readme): lead the Why with the SSH pain …
46
- 39a9668 (tag: v0.4.0) release: v0.4.0 — nested completion early-return …
47
- c1ed87b feat(completion): early-return nested status when nested + no until …
48
- 残り 9 コミット
49
- [aiterm demo: 13 / ~273 tok (raw 13 行 / ~458 tok)] [is_complete=True via quiescent]
83
+ 1
84
+ 2
85
+ 3
86
+ ⋮ (head 29 行目まで続く——この README では省略表示)
87
+ 〈102 行省略。全文は full=true、範囲は line_range="A:B"〉 … ← ツール自身のマーカー
88
+ ⋮ (tail は 132 行目から再開——この README では省略表示)
89
+ 149
90
+ 150
91
+ [aiterm demo: 51 行 / ~56 tok (raw 152 行 / ~166 tok); 102 行 hidden] [is_complete=True via quiescent]
50
92
  ```
51
93
 
52
- `grep` を、コマンド別 reducer でヒット行だけに畳む(127 → 46 トークン):
94
+ `grep` を、コマンド別 reducer で「件数ヘッダ+ヒット行だけ」に畳む:
53
95
 
54
96
  ```text
55
97
  → pty_send("demo", "grep -rn capture-pane src/ test/")
56
98
  → pty_read("demo", { wait: true, rtk: true })
57
99
  ← 2 matches in 1 files:
58
- src/core.ts:159: // maxBuffer … capture-pane(大きなスクロールバック)…
59
- src/core.ts:329: const args = ["capture-pane", "-p", "-J", "-t", name];
60
- [aiterm demo: rtk:grep 適用 / ~46 tok (raw ~127 tok)] [is_complete=True via quiescent]
100
+
101
+ src/core.ts:159:// maxBuffer は既定 1MiB。capture-pane(大きなスクロールバック)… (行はここで truncate)
102
+ src/core.ts:335:const args = ["capture-pane", "-p", "-J", "-t", name];
103
+ [aiterm demo: rtk:grep 適用 / ~46 tok (raw ~53 tok)] [is_complete=True via quiescent]
61
104
  ```
62
105
 
63
- ネストは「中へ送るただのテキスト」 — 同じ PTY の*中で* Python REPL に入る:
106
+ ネストは「中へ送るただのテキスト」——同じ PTY の*中で* Python REPL に入る(`ssh host`・`docker exec -it … bash`・起動したコーディングエージェントの TUI も、まったく同じ要領でネストする):
64
107
 
65
108
  ```text
66
109
  → pty_send("demo", "python3")
67
- → pty_read("demo", { until: ">>> " }) # 入れ子側のプロンプト = 「内側シェルが応答できる」
110
+ → pty_read("demo", { until: ">>>" }) # 入れ子側のプロンプト = 「内側シェルが応答できる」
68
111
  → pty_send("demo", "print(sum(range(1_000_000)))")
69
- → pty_read("demo", { until: ">>> " })
112
+ → pty_read("demo", { wait: true, until: ">>>" })
70
113
  ← 499999500000 [is_complete=True via until]
71
114
  ```
72
115
 
73
- `ssh host` `docker exec -it … bash` もまったく同じ要領でネストする([なぜ](#なぜ))。SSH 全行程の動く GIF は準備中だが、上はすべて本物の出力で、台本ではない。ネスト中は `until`(内側プロンプト)か `mark: true` を渡すこと — そこでは quiescence が原理的に効かないため([完了検出](#完了検出4-層) / [既知の制約](#既知の制約バグではなく仕様))。同じ tmux ソケットに人が `attach` すれば、これらをライブで覗ける([人が覗く](#人が覗く))。
116
+ 上の採取で私が触ったのは 2 本の `⋮` 行(長い head/tail を README 用に省略)と長すぎる grep 行 1 本の truncate だけ——`〈…〉` マーカー・トークン数・各 `is_complete` はツールが出した通り。(`until` は末尾スペース無しの `">>>"` を使う——採取されるプロンプトは末尾が削られるので `">>> "` だと外れて `timeout` に落ちる。)ネスト中は `until`(内側プロンプト)か `mark: true` を渡すこと——そこでは quiescence が原理的に効かないため([完了検出](#完了検出5-層) / [既知の制約](#既知の制約バグではなく仕様))。同じ tmux ソケットに人が `attach` すれば、これらをライブで覗ける([人が覗く](#人が覗く))。
74
117
 
75
118
  ## クイックスタート(約60秒)
76
119
 
77
- Claude Code なら 1 コマンドで登録 — clone もビルドも不要、`npx` が毎回取得して起動する:
120
+ Claude Code なら 1 コマンドで登録——clone もビルドも不要、`npx` が毎回取得して起動する:
78
121
 
79
122
  ```bash
80
123
  claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
@@ -83,10 +126,10 @@ claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
83
126
  Claude Code を再起動して、接続を確認:
84
127
 
85
128
  ```bash
86
- /mcp # aiterm が connected・6 ツール公開、と出る
129
+ /mcp # aiterm が connected・9 ツール公開、と出る
87
130
  ```
88
131
 
89
- 最初のセッション — 4 回の呼び出しで、1 個の永続端末:
132
+ 最初のセッション——4 回の呼び出しで、1 個の永続端末:
90
133
 
91
134
  ```text
92
135
  pty_open() → { session_id: "t1", attach: "tmux -S … attach -t t1" }
@@ -95,7 +138,7 @@ pty_read("t1", { wait: true }) → "hello" (トークン削減・完了
95
138
  pty_close("t1") → 端末を解放
96
139
  ```
97
140
 
98
- これだけ。`t1` の端末は本物で永続 — `ssh`・`docker exec`・REPL は、そこへ `pty_send` で打ち込む“ただのテキスト”。
141
+ これだけ。`t1` の端末は本物で永続——`ssh`・`docker exec`・REPL・起動したエージェントの TUI は、そこに住む「もの」に過ぎない。代わりにワーカーのエージェントを起動するのも 1 コール: `codex_agent()` が返す `session_id` を、同じ `pty_read` / `pty_send` で操作する。
99
142
 
100
143
  **グローバル導入や別クライアントが良い場合は:**
101
144
 
@@ -105,37 +148,58 @@ npm i -g aiterm-mcp
105
148
  claude mcp add --scope user --transport stdio aiterm -- aiterm-mcp
106
149
  ```
107
150
 
108
- `~/.claude.json` に登録され、初回に承認プロンプトが出る。**他の MCP クライアント**(Cursor / Cline / Claude Desktop …)でも、stdio で `npx -y aiterm-mcp`(または `aiterm-mcp`)を起動するだけ。**Node ≥ 18** と **tmux** が必要 — [要件](#要件)参照。
151
+ `~/.claude.json` に登録され、初回に承認プロンプトが出る。**他の MCP クライアント**(Cursor / Cline / Claude Desktop …)でも同様に動くはず——stdio で `npx -y aiterm-mcp`(または `aiterm-mcp`)を起動するだけ。**Node ≥ 18** と **tmux** が必要——[要件](#要件)参照。
152
+
153
+ ## ヘッドレス: 端末に人が居ない
154
+
155
+ MCP クライアントが aiterm を stdio 越しにプログラムから駆動するので、上のすべては **tmux に誰も座らないまま**動く。あなたの Claude Code セッションは、`codex_agent()` でタスクを起こし、`pty_read` で結果を読み、それを使って動ける——無人で。これは、人が操作する端末が向かない場所にこそ aiterm が合うということ:
156
+
157
+ - **複数エージェントのオーケストレーション** — 統括役がサブタスクを Codex / Grok / Composer に渡し、各々を専用の永続セッションに置き、全部を読み戻す。
158
+ - **CI** — ジョブのステップがエージェントを起こし、操作し、片付けられる。
159
+ - **cron** — スケジュール実行がエージェントを起動して出力を回収できる。
160
+
161
+ 端末は本物で共有されているので、人が*割り込むことも*できる([人が覗く](#人が覗く))——が、人を必要とはしない。
109
162
 
110
163
  ## 仕組み
111
164
 
112
165
  ```mermaid
113
166
  flowchart LR
114
- AI["AI / MCP client"] -->|"pty_send"| S["aiterm-mcp<br/>stdio MCP · 6 tools"]
167
+ AI["AI / MCP client<br/>(the orchestrator)"] -->|"pty_send · codex_agent<br/>grok_agent · composer_agent"| S["aiterm-mcp<br/>stdio MCP · 9 tools"]
115
168
  S -->|"pty_read<br/>token-reduced"| AI
116
- S -->|"tmux send-keys<br/>capture-pane"| P["one local PTY<br/>tmux · persistent"]
169
+ S -->|"tmux send-keys<br/>capture-pane"| P["persistent PTYs<br/>tmux · survive restarts"]
117
170
  P -->|"ssh · docker · repl"| R["nested<br/>remote · container · REPL"]
171
+ P -->|"launches a fresh PTY per agent"| A["another coding-agent TUI<br/>Codex · Grok · Composer"]
118
172
  ```
119
173
 
120
- プリミティブは「PTY を 1 個握る」ことだけ。SSH・コンテナ・REPL は、その中へ `pty_send` で打ち込む“ただのテキスト”に過ぎない。PTY は tmux 上にあるので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
174
+ primitive は「PTY を 1 個握る」ことだけ。それ以外——SSH・コンテナ・REPL・起動したエージェント TUI——は、永続端末の中で動く「対話的な何か」に過ぎず、同じ `pty_send` / `pty_read` で操作する。各起動ツールは自分専用の新しい PTY を開く。PTY は tmux 上にあるので、MCP サーバや AI クライアントが再起動してもセッションは生き残る。
121
175
 
122
176
  ## 既存手段との比較
123
177
 
124
- | | **aiterm-mcp** | 1 コマンド毎の往復<br/>(例: `mcp-server-commands`) | terminal / SSH / tmux MCP<br/>(例: `iterm-mcp`, `ssh-mcp`, `tmux-mcp`) |
125
- | --- | --- | --- | --- |
126
- | 永続セッション | ✅ tmux・再起動を跨ぐ | ❌ 毎回新シェル | ⚠️ まちまち |
127
- | SSH / コンテナ | `pty_send` 1 回でネスト | 毎コマンド接続し直し | ⚠️ ツールが分かれる/毎回接続しがち |
128
- | トークン削減読取 | ✅ コマンド別 reducer | ❌ 生出力 | ⚠️ ほぼ無し |
129
- | 完了検出 | 4 層: 終了 / `until` / 静止 / timeout | 無し(毎回ブロック) | ⚠️ プロンプト一致・脆い |
130
- | 人が同時操作 | ✅ 共有 tmux ソケット | ❌ | ⚠️ まちまち |
178
+ aiterm 2 つの系譜の交点にいる——端末を操作する MCP サーバと、より新しい「エージェント同士が共有端末越しに会話する」という発想([aiterm の立ち位置](#aiterm-の立ち位置)参照)。各軸の並びはこうなる——相手が強い所も含めて、正直に。
131
179
 
132
- ## 要件
180
+ | | **aiterm-mcp** | 1 コマンド毎の往復<br/>(例: `mcp-server-commands`) | terminal / SSH / tmux MCP<br/>(例: `iterm-mcp`, `ssh-mcp`, `tmux-mcp`) | 共有 tmux でエージェント同士<br/>(例: `smux`) |
181
+ | --- | --- | --- | --- | --- |
182
+ | 永続セッション | ✅ tmux・再起動を跨ぐ | ❌ 毎回新シェル | ⚠️ まちまち | ✅ tmux |
183
+ | SSH / コンテナ / REPL | `pty_send` 1 回でネスト | 毎コマンド接続し直し | ⚠️ ツールが分かれがち | ✅ tmux(人が操作) |
184
+ | 1 コールで別エージェント起動 | ✅ `codex_agent` / `grok_agent` / `composer_agent` | ❌ | ❌ | ⚠️ 人が動かす tmux に CLI + skills で参加 |
185
+ | ヘッドレス(人が tmux に居ない) | ✅ MCP 駆動・プログラム的 | ✅ | ⚠️ まちまち | ❌ 人が tmux に居る前提 |
186
+ | MCP ネイティブ(任意の MCP クライアント) | ✅ `claude mcp add` 1 行 | ✅ | ✅(MCP なので) | ❌ tmux 設定 + CLI + Agent Skills |
187
+ | トークン削減読取 | ✅ コマンド別 reducer | ❌ 生出力 | ⚠️ ほぼ無し | ❌ 生 tmux |
188
+ | 完了検出 | 5 層: 終了 / `mark` / `until` / 静止 / timeout | 無し(毎回ブロック) | ⚠️ プロンプト一致・脆い | ❌ エージェントがペインを読む |
189
+ | 破壊コマンド遮断 | ✅ tripwire(`force` で越える) | ❌ | ⚠️ まちまち | ❌ |
190
+ | 人が同時操作 | ✅ 共有 tmux ソケット(`attach`) | ❌ | ⚠️ まちまち | ✅(設計の芯) |
133
191
 
134
- - **Node.js >= 18**
135
- - **tmux**(実行時の前提。`tmux -V` で確認。未導入なら `apt install tmux` / `brew install tmux`)
136
- - **macOS / Linux / WSL2** tmux を直接使う。macOS は同梱されないので `brew install tmux` で導入する。MCP クライアントがターミナルでなく **GUI から起動**された場合、Homebrew の bin(Apple Silicon: `/opt/homebrew/bin`、Intel: `/usr/local/bin`)が `PATH` に入らないことがある。その場合 aiterm が自動で探索するか、**`AITERM_TMUX=/path/to/tmux`** で明示指定する。
137
- - **Windows ネイティブ**には tmux が無いため、aiterm は裏で **WSL の中の tmux** を透過的に使う。[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/) を導入・初期化し、**WSL のディストリ内に tmux を入れる**こと(`sudo apt install tmux`)。`wsl tmux -V` で確認できる。セッション・ソケット・人の `attach` はすべて WSL 側にあり、AI は Windows 側のコマンドから操作するだけ。(Windows のツールは SSH と同じく入れ子で握る: `pty_send "powershell.exe …"` で PowerShell に入る。)
138
- - 任意: [`rtk`](https://github.com/rtk-ai/rtk) バイナリ(`pty_send` の `rtk: true` 委譲で使う。無くても動く)
192
+ ## aiterm の立ち位置
193
+
194
+ 「エージェント同士が共有端末越しに会話する」は、それ自体が 1 つのカテゴリになりつつある——そして本当に良い発想だ。端末はどのコーディングエージェントも既に話せる普遍インタフェースで、専用のエージェント間プロトコルは要らない——シェル*そのもの*が共有面になる。`smux`(by @shawn_pana)はこの発想を、人が用意する 1 コマンドの共有 tmux 環境として広め、エージェントは `tmux-bridge` CLI Agent Skills でそこに参加する。人が輪の中にいる共有ペインのワークフローに強く、実際に支持を集めている。
195
+
196
+ aiterm は同じ核心の洞察——端末を出会いの場にする——を取り、あえて 3 つの違う選択をした:
197
+
198
+ 1. **ヘッドレスが前提。** aiterm は MCP 越しにプログラムから駆動されるので、「AI が別のエージェントを起動して操作する」のに *tmux に人が座らなくていい*——オーケストレーションのループ・CI ステップ・cron から動く。共有 tmux 系は人がキーボードの前に居ることを主軸にしていて(ドキュメントも対話的なペイン操作が中心)、無人運用は本来の姿ではない——aiterm はそれが本来の姿だ。
199
+ 2. **MCP ネイティブ=採用させるワークフローではない。** aiterm は stdio MCP サーバ: `claude mcp add` 1 行で、stdio を話す任意の MCP クライアントに構造化ツールとして刺さる(実機確認は Claude Code。Cursor / Cline / Claude Desktop も同じプロトコルなので同様に動くはず)。tmux 設定の採用も・ペイン操作の習得も・skills の導入も求めない——クライアントは既にツール呼び出しの仕方を知っている。
200
+ 3. **エージェント起動が 1 ツールコール=オーケストレーションの primitive。** `codex_agent()` が Codex を永続端末に起こし、すぐ操作できるセッションを返す。ペインを手で並べたり貼り付けたりしない——起動も・操舵も・読取も、指揮するモデルが自分で打てるツールコールだ。
201
+
202
+ その上に、生の tmux ブリッジには無い製品化レイヤが乗る: **トークン削減読取**・**5 層の完了検出**・**破壊コマンドの tripwire**。これらは人が tmux に居るモデルを否定しない——人がどこに立つかについての、別の・補完的な賭けだ。
139
203
 
140
204
  ## ツール
141
205
 
@@ -143,14 +207,26 @@ flowchart LR
143
207
  | --- | --- | --- |
144
208
  | `pty_open` | 端末を 1 個握り `session_id` を返す | `name?`, `shell="bash"` |
145
209
  | `pty_send` | テキスト(コマンド)を送る | `session_id`, `text`, `enter=true`, `mark`, `force`, `rtk`, `raw` |
146
- | `pty_read` | 出力を削減して読む(既定は増分) | `session_id`, `wait`, `until`, `timeout`, `screen`, `full`, `lines`, `line_range`, `raw`, `rtk` |
210
+ | `pty_read` | 出力を削減して読む(既定は増分) | `session_id`, `wait`, `until`, `until_regex`, `timeout`, `screen`, `full`, `lines`, `line_range`, `raw`, `rtk` |
147
211
  | `pty_key` | 制御キーを送る | `session_id`, `key`(`C-c`/`Enter`/`Up`…) |
148
212
  | `pty_close` | セッションを閉じる | `session_id` |
149
213
  | `pty_list` | セッション一覧 | (なし) |
150
214
 
151
- ### 完了検出(4 層)
215
+ ### 対話エージェント起動ツール
216
+
217
+ 各ツールは特定ベンダーの対話型コーディングエージェント TUI を新しい永続 PTY の中に起動し、`session_id` を返す。以後は他のセッションと同様に `pty_read` / `pty_send` で操作する。モデルごとに 1 ツール=ツール名を見ればどのモデルか分かる。TUI は全画面アプリなので、`pty_read({ screen: true })` で描画済みの画面を読む。
218
+
219
+ | ツール | 起動するもの | 主な引数 |
220
+ | --- | --- | --- |
221
+ | `codex_agent` | Codex CLI(OpenAI・モデルは CLI の既定) | `prompt?`, `reasoning_effort?`, `cwd?`, `session_name?` |
222
+ | `grok_agent` | Grok Build の `grok-build` モデル(xAI) | `prompt?`, `reasoning_effort?`(`low`/`medium`/`high`/`xhigh`/`max`), `cwd?`, `session_name?` |
223
+ | `composer_agent` | Grok Build の `grok-composer-2.5-fast` モデル(xAI) | `grok_agent` と同じ |
224
+
225
+ 各ベンダーの CLI が導入・認証済みであること(`codex_agent` は `codex`、Grok 系 2 つは `grok`)。バイナリは `CODEX_BIN` / `GROK_BIN` → `~/.local/bin/codex` / `~/.grok/bin/grok` → `PATH` の順で解決する。前提はセッション作成前に検証し(grok/composer は範囲外の `reasoning_effort` を弾く。CLI 不在・実在しない `cwd` は 3 つとも失敗)、起動に失敗してもセッションの残骸は残さない。詳細は [その端末の中に他のコーディングエージェントを起動する](#2-その端末の中に他のコーディングエージェントを起動する--オーケストレーションの旗艦)。`cwd` は絶対パスで渡す——`~` は展開されない。
226
+
227
+ ### 完了検出(5 層)
152
228
 
153
- `pty_read({ wait: true })` は、プロセス終了 / `until` 正規表現一致 / 出力静止 ∧ シェル復帰(quiescence)/ timeout の 4 層で「コマンドが終わったか」を判定する。ネスト中(SSH 先)はシェル復帰判定が効かないので `until` でプロンプトを指定すると綺麗に判定できる。
229
+ `pty_read({ wait: true })` は、プロセス終了 / `mark:true` sentinel の自動検出(後述)/ `until` 一致(**既定はリテラル部分一致**、`until_regex: true` で正規表現)/ 出力静止 ∧ シェル復帰(quiescence)/ timeout の 5 層で「コマンドが終わったか」を判定する。ネスト中(SSH・コンテナ・REPL・起動したエージェントの TUI の中)はシェル復帰判定が効かないので、`until` で内側プロンプトを指定するか、`mark: true` で送れば `pty_read({ wait: true })` が sentinel を自動検出する(until 不要・ネストでも効く)——全画面のエージェント TUI なら、出力が落ち着いた時点で `{ screen: true }` を読む。
154
230
 
155
231
  ### トークン削減
156
232
 
@@ -160,11 +236,31 @@ flowchart LR
160
236
 
161
237
  ### 安全
162
238
 
163
- `pty_send` は送信前に破壊的コマンド(`rm -rf /`, `mkfs`, `dd of=/dev/…`, `DROP TABLE` 等)を遮断し(`force: true` で越える)、ESC・ブラケットペースト終端などをサニタイズする。`pty_read` は制御文字を無害化して返す。
239
+ `pty_send` は送信前に破壊的コマンド(`rm -rf /`, `mkfs`, `dd of=/dev/…`, `DROP TABLE` 等)を遮断し(`force: true` で越える)、ESC・ブラケットペースト終端などをサニタイズする。`pty_read` は既定で制御文字を無害化して返す(`raw: true` はバイトをそのまま返す)。これは**サンドボックスではなく tripwire**([既知の制約](#既知の制約バグではなく仕様)参照)。
164
240
 
165
241
  ## 人が覗く
166
242
 
167
- セッションは共有 tmux ソケット上にある。`pty_open` の戻り値に表示される `tmux -S … attach -t <id>` で人間が同じ端末に入って介入できる(抜けるのは `Ctrl-b d`)。Windows ネイティブではセッションが WSL 内にあるため、表示は WSL 形(`wsl tmux -S … attach -t <id>`)になる。
243
+ セッションは共有 tmux ソケット上にある。`pty_open`(および各エージェント起動ツール)の戻り値に表示される `tmux -S … attach -t <id>` で人間が同じ端末に入って介入できる(抜けるのは `Ctrl-b d`)——起動した Codex/Grok/Composer のセッションが走るのを覗いたり、作業の途中で AI からキーボードを奪ったりも。Windows ネイティブではセッションが WSL 内にあるため、表示は WSL 形(`wsl tmux -S … attach -t <id>`)になる。
244
+
245
+ ## 要件
246
+
247
+ - **Node.js >= 18**
248
+ - **tmux**(実行時の前提。`tmux -V` で確認。未導入なら `apt install tmux` / `brew install tmux`)
249
+ - **macOS / Linux / WSL2** は tmux を直接使う。macOS は同梱されないので `brew install tmux` で導入する。MCP クライアントがターミナルでなく **GUI から起動**された場合、Homebrew の bin(Apple Silicon: `/opt/homebrew/bin`、Intel: `/usr/local/bin`)が `PATH` に入らないことがある。その場合 aiterm が自動で探索するか、**`AITERM_TMUX=/path/to/tmux`** で明示指定する。
250
+ - **Windows ネイティブ**には tmux が無いため、aiterm は裏で **WSL の中の tmux** を透過的に使う。[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/) を導入・初期化し、**WSL のディストリ内に tmux を入れる**こと(`sudo apt install tmux`)。`wsl tmux -V` で確認できる。セッション・ソケット・人の `attach` はすべて WSL 側にあり、AI は Windows 側のコマンドから操作するだけ。(Windows のツールは SSH と同じく入れ子で握る: `pty_send "powershell.exe …"` で PowerShell に入る。)
251
+ - **エージェント起動ツール**を使う場合: 対応するベンダー CLI が導入・認証済みであること——`codex_agent` は `codex`、`grok_agent` / `composer_agent` は `grok`。(PTY ツールだけ使うなら不要。)
252
+ - 任意: [`rtk`](https://github.com/rtk-ai/rtk) バイナリ(`pty_send` の `rtk: true` 委譲で使う。無くても動く)
253
+
254
+ ## 既知の制約(バグではなく仕様)
255
+
256
+ - **ネスト中(ssh / docker / REPL / 起動したエージェント TUI)は quiescence が原理的に効かない。** 前面コマンドがシェル集合(bash/sh/zsh/fish/dash)の外になるため。ネスト中で `until` も `mark` も無いときは、待っても完了を確定できる信号が無いので、`pty_read({ wait: true })` はフル `timeout` を空費せず出力静止時点で `is_complete=False via nested` と早期に返し、`until`(既定リテラル部分一致・`until_regex: true` で正規表現)か `mark: true`(終了コード付き sentinel・自動検出)の指定を促す。全画面のエージェント TUI なら、出力が落ち着いた時点で `{ screen: true }` を読む。
257
+ - **`is_complete=False` は失敗ではない。** 「timeout 内に完了を観測できなかった」という意味。長時間コマンドでは `timeout` を伸ばすか `until`/`mark` を使う。
258
+ - **破壊ゲートはサンドボックスではなく tripwire。** よくある破壊形だけを弾く。相対パスの `rm`、`$VAR` 展開後に危険化するもの、ssh 先で実行されるコマンドは捕捉しない——起動したコーディングエージェントが自分のセッション内で何をするかも取り締まらない。
259
+ - **エージェント起動ツールはベンダー TUI を起動するだけで、包んだり代理したりしない。** aiterm は前提を検証して CLI を永続 PTY で起動する——モデル・認証・挙動はベンダー CLI のもの。`claude` の起動ツールは無く、エージェント間のプロトコルも無い。「会話」とは、あなたの MCP クライアントが TUI を操作すること(入力を送り、出力を読む)だ。
260
+ - **`pty_send({ rtk: true })` は単行コマンドのみ+外部 `rtk` バイナリが必要**(無ければ素通し)。一方 `pty_read({ rtk: true })` の reducer は自前実装で rtk 非依存。
261
+ - **`pytest` reducer は件数・罫線・`FAILURES` ブロック整形が rtk 0.42.0 と byte 一致**(回帰テストで固定)。ただし `-ra`/`-rf` 時の `FAILED` 要約行の理由は**全文を保持する**(rtk 0.42.0 は最初の `" - "` 区切りで切るが、本実装は可読性優先で情報を残すため、この行は意図的に rtk と完全一致させない)。rtk が大出力時に付ける `[full output: …]`(tee ポインタ)行は read 側では再現しない。
262
+ - **tmux は `-f /dev/null` 起動**なので `~/.tmux.conf` を読まない(環境差を排除するため)。
263
+ - **全セッションが単一 socket(POSIX では `claude.sock`)上にある。** `tmux … kill-server` は全セッションを消す。
168
264
 
169
265
  ## 開発
170
266
 
@@ -175,17 +271,7 @@ npm test # build してから node:test 回帰スイート(tmux 必
175
271
  npm link # ローカルで `aiterm-mcp` を PATH に
176
272
  ```
177
273
 
178
- ロジックは `src/core.ts`(tmux 制御・削減・完了検出・安全)と `src/rtk.ts`(コマンド別 reducer)、公開は `src/index.ts`。設計の出発点と reducer の移植元(pytest reducer は本家 rtk 0.42.0 と出力が byte 一致するよう移植・回帰テストで固定)は `prototype/python/` を参照。
179
-
180
- ## 既知の制約(バグではなく仕様)
181
-
182
- - **ネスト中(ssh / docker / REPL)は quiescence が原理的に効かない。** 前面コマンドがシェル集合(bash/sh/zsh/fish/dash)の外になるため。ネスト中で `until` 未指定のときは、待っても完了を確定できる信号が無いので、`pty_read({ wait: true })` はフル `timeout` を空費せず出力静止時点で `is_complete=False via nested` と早期に返し、`until`(プロンプト等の正規表現)か `mark: true`(終了コード付き sentinel)の指定を促す。
183
- - **`is_complete=False` は失敗ではない。** 「timeout 内に完了を観測できなかった」という意味。長時間コマンドでは `timeout` を伸ばすか `until`/`mark` を使う。
184
- - **破壊ゲートはサンドボックスではなく tripwire。** よくある破壊形だけを弾く。相対パスの `rm`、`$VAR` 展開後に危険化するもの、ssh 先で実行されるコマンドは捕捉しない。
185
- - **`pty_send({ rtk: true })` は単行コマンドのみ+外部 `rtk` バイナリが必要**(無ければ素通し)。一方 `pty_read({ rtk: true })` の reducer は自前実装で rtk 非依存。
186
- - **`pytest` reducer は件数・罫線・`FAILURES` ブロック整形が rtk 0.42.0 と byte 一致**(回帰テストで固定)。ただし `-ra`/`-rf` 時の `FAILED` 要約行の理由は**全文を保持する**(rtk 0.42.0 は最初の `" - "` 区切りで切るが、本実装は可読性優先で情報を残すため、この行は意図的に rtk と完全一致させない)。rtk が大出力時に付ける `[full output: …]`(tee ポインタ)行は read 側では再現しない。
187
- - **tmux は `-f /dev/null` 起動**なので `~/.tmux.conf` を読まない(環境差を排除するため)。
188
- - **全セッションが単一 socket(`claude.sock`)上にある。** `tmux … kill-server` は全セッションを消す。
274
+ ロジックは `src/core.ts`(tmux 制御・削減・完了検出・安全・エージェント起動)と `src/rtk.ts`(コマンド別 reducer)、公開は `src/index.ts`。設計の出発点と reducer の移植元(pytest reducer は本家 rtk 0.42.0 と一致するよう移植・ただし上記の `FAILED` 行の差異は意図的・回帰テストで固定)は `prototype/python/` を参照。
189
275
 
190
276
  ## 試す
191
277
 
@@ -195,7 +281,7 @@ npm link # ローカルで `aiterm-mcp` を PATH に
195
281
  claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
196
282
  ```
197
283
 
198
- aiterm がトークンの往復を 1 回でも省けたなら、**[リポジトリに star](https://github.com/kitepon-rgb/aiterm-mcp)** を — 他の人に見つけてもらう一番安い方法です。
284
+ aiterm が、あなたの AI に別のエージェントへ仕事を渡させたなら——あるいはトークンの往復を 1 回でも省けたなら——**[リポジトリに star](https://github.com/kitepon-rgb/aiterm-mcp)** を。他の人に見つけてもらう一番安い方法です。
199
285
 
200
286
  - **npm:** https://www.npmjs.com/package/aiterm-mcp
201
287
  - **Issue / バグ報告:** https://github.com/kitepon-rgb/aiterm-mcp/issues