aiterm-mcp 0.31.2 → 0.32.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/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.32.0] - 2026-09-09
11
+
12
+ ### Added
13
+
14
+ - `aiterm-setup`を追加した。公式package managerによる依存準備、MCP経由の端末実行、検出したClaude Code・Codex・Grok・Cursorへの登録と確認を製品が所有する。対応外・未検出・失敗は公開JSONで区別する。
15
+
16
+ ### Fixed
17
+
18
+ - Windowsでスラッシュ区切りのcwdを指定したGrok/Composerの完了通知と回答を取得できない問題を修正した。Grok CLIと同じ絶対パスへ正規化して両方の記録を読む。
19
+ - Grok/Composerの次turn開始後に、途中の回答を前turnの完了ID付きで返す問題を修正した。
20
+ - psmuxの最低版を実際のpsmux版数で検証し、winget導入直後の既存PATHからも公式配置を解決する。
21
+ - Windowsでrelease中のnpm起動がENOENTになる問題を修正し、起動元npmのJS入口をNodeで呼ぶ。
22
+
10
23
  ## [0.31.2] - 2026-09-08
11
24
 
12
25
  ### Fixed
@@ -1570,7 +1583,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
1570
1583
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
1571
1584
  provenance.
1572
1585
 
1573
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.2...HEAD
1586
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.32.0...HEAD
1587
+ [0.32.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.2...v0.32.0
1574
1588
  [0.31.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.1...v0.31.2
1575
1589
  [0.31.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.31.0...v0.31.1
1576
1590
  [0.31.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.30.0...v0.31.0
package/README.ja.md CHANGED
@@ -28,6 +28,24 @@
28
28
 
29
29
  ## MCPクライアントへ導入
30
30
 
31
+ 検出したClaude Code・Codex・Grok・Cursorのユーザー設定へ登録する標準入口:
32
+
33
+ ```bash
34
+ npm install -g aiterm-mcp@latest
35
+ aiterm-setup --json
36
+ ```
37
+
38
+ `aiterm-setup`は端末の依存準備、MCP経由の端末実行、登録と読戻しまでを一回で行う。
39
+ WindowsはwingetでPowerShell 7・Git for Windows・psmux、macOSはHomebrewでtmux、
40
+ Ubuntu/Debianはsudoとaptでtmuxを準備する。必要な公式package managerと実行権限は事前に必要。
41
+ 他のLinuxでも既存tmuxを利用できるが、自動導入は`unsupported`で停止する。
42
+ 既存設定の他サーバーを保持し、JSON設定は変更前の`.aiterm-backup`を残す。
43
+ 結果の`status`は`ready`/`unsupported`/`failed`。未検出のAIは`not_detected`とし、全AI未検出は成功にしない。
44
+ 登録先はglobal packageのNodeとMCP入口の絶対パスで、npm一時cacheやsource checkoutは登録しない。
45
+ 更新後も同じ入口を実行し、MCP clientを再起動する。npm install自体はユーザー設定を変更しない。
46
+ 公開JSONは`schema: "aiterm.setup-result.v1"`、全体の`status`、端末の`backend`、
47
+ AI別の`integrations`を持つ。失敗時は`reason_code`を付け、終了コードはreadyなら0、それ以外は2となる。
48
+
31
49
  cloneもビルドも不要。どのクライアントでも公開パッケージを次のコマンドで起動する:
32
50
 
33
51
  ```bash
@@ -153,13 +171,13 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
153
171
  場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
154
172
  Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
155
173
 
156
- **状態:** 開発継続中 · 現行公開版 **v0.31.2** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
174
+ **状態:** 開発継続中 · 現行公開版 **v0.32.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
157
175
 
158
176
  ### 更新と巻き戻し
159
177
 
160
178
  npm packageが単独配布の正本であり、dotagentsは介在しません。global installは
161
- `npm install -g aiterm-mcp@latest`で更新します。巻き戻す時は
162
- `npm install -g "aiterm-mcp@<known-good-version>"`のように既知の正常versionを明示し、MCP clientを再起動します。
179
+ `npm install -g aiterm-mcp@latest`で更新し、`aiterm-setup --json`を再実行します。巻き戻す時は
180
+ `npm install -g "aiterm-mcp@<known-good-version>"`のように既知の正常versionを明示します。setupを持つ版では同じ入口を再実行し、MCP clientを再起動します。
163
181
  `npx`設定では`aiterm-mcp@latest`へ変えると更新でき、`aiterm-mcp@<version>`へ変えると固定・巻き戻し
164
182
  できます。downgrade前に[変更履歴](CHANGELOG.md)でstate/schema互換を確認してください。maintainer向けの
165
183
  公開物とreleaseの巻き戻しは、製品所有の[release手順](docs/RELEASE.md)を正とします。
@@ -293,7 +311,7 @@ Throughline自体が不要である。
293
311
 
294
312
  ## 最初の実行(約60秒)
295
313
 
296
- Claude Code を再起動して、接続を確認:
314
+ `aiterm-setup --json`が`ready`になったら、利用するMCP clientを再起動して接続を確認する。Claude Codeの場合:
297
315
 
298
316
  ```bash
299
317
  /mcp # aiterm が connected・16 ツール公開、と出る
@@ -504,9 +522,9 @@ npm test # build してから node:test 回帰スイート(tmux ま
504
522
  npm link # ローカルで `aiterm-mcp` を PATH に
505
523
  ```
506
524
 
507
- 開発中は変更に直結するfocused testを先にローカルで実行します。GitHub Actionsはpushごとにself-hostedの
508
- `linux-workstation` 1環境で試験を回し、Windows固有ファイルを触った変更だけ`windows-native`を加え、
509
- 3環境(`macos-native`、`linux-workstation`、`windows-native`)の全テストは週1回の健康診断だけで回します。
525
+ 開発中は変更に直結する試験を先に実行する。GitHub Actionsは共通実装・CI自身・未分類の変更をMac・Linux・Windowsで検証し、
526
+ Windows固有だけの変更はLinuxとWindowsを選ぶ。版番号だけの変更はLinuxの配布情報・pack確認、文書だけなら文書検査を行う。
527
+ 試験内容とOSの選択、週次・手動実行の範囲は[公開手順](docs/RELEASE.md)に従う。
510
528
  `npm run release -- <version>`がversion同期・commit・tag・GitHub Releaseを一回で行い、tag起点のnpm公開は
511
529
  tagged commitが`origin/main`の祖先であることだけを確認して、他のCI結果を待ちません。
512
530
 
@@ -514,10 +532,11 @@ tagged commitが`origin/main`の祖先であることだけを確認して、他
514
532
 
515
533
  ## 試す
516
534
 
517
- 1 コマンド、clone もビルドも不要:
535
+ 公開packageを導入して、検出したAIへ登録する。cloneやビルドは不要:
518
536
 
519
537
  ```bash
520
- claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
538
+ npm install -g aiterm-mcp@latest
539
+ aiterm-setup --json
521
540
  ```
522
541
 
523
542
  aiterm が、あなたの AI に別のエージェントへ仕事を渡させたなら——あるいはトークンの往復を 1 回でも省けたなら——**[リポジトリに star](https://github.com/kitepon/aiterm-mcp)** を。他の人に見つけてもらう一番安い方法です。
package/README.md CHANGED
@@ -28,6 +28,24 @@ Built and maintained by [Quo at kitepon.dev](https://kitepon.dev/en).
28
28
 
29
29
  ## Install in your MCP client
30
30
 
31
+ 検出したClaude Code・Codex・Grok・Cursorのユーザー設定へ登録する標準入口:
32
+
33
+ ```bash
34
+ npm install -g aiterm-mcp@latest
35
+ aiterm-setup --json
36
+ ```
37
+
38
+ `aiterm-setup`は端末の依存準備、MCP経由の端末実行、登録と読戻しまでを一回で行う。
39
+ WindowsはwingetでPowerShell 7・Git for Windows・psmux、macOSはHomebrewでtmux、
40
+ Ubuntu/Debianはsudoとaptでtmuxを準備する。必要な公式package managerと実行権限は事前に必要。
41
+ 他のLinuxでも既存tmuxを利用できるが、自動導入は`unsupported`で停止する。
42
+ 既存設定の他サーバーを保持し、JSON設定は変更前の`.aiterm-backup`を残す。
43
+ 結果の`status`は`ready`/`unsupported`/`failed`。未検出のAIは`not_detected`とし、全AI未検出は成功にしない。
44
+ 登録先はglobal packageのNodeとMCP入口の絶対パスで、npm一時cacheやsource checkoutは登録しない。
45
+ 更新後も同じ入口を実行し、MCP clientを再起動する。npm install自体はユーザー設定を変更しない。
46
+ 公開JSONは`schema: "aiterm.setup-result.v1"`、全体の`status`、端末の`backend`、
47
+ AI別の`integrations`を持つ。失敗時は`reason_code`を付け、終了コードはreadyなら0、それ以外は2となる。
48
+
31
49
  No clone or build is required. Each client launches the published package with:
32
50
 
33
51
  ```bash
@@ -169,13 +187,13 @@ collection is off by default and performs no network I/O. It ships via
169
187
  tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
170
188
  Release re-registers the Official MCP Registry entry.
171
189
 
172
- **Status:** actively maintained · current public release **v0.31.2** · runs on Linux · WSL2 · macOS · native Windows (tmux on POSIX, the tmux-CLI-compatible [psmux](https://github.com/psmux/psmux) on native Windows — no WSL required) · MIT · see the [CHANGELOG](CHANGELOG.md).
190
+ **Status:** actively maintained · current public release **v0.32.0** · runs on Linux · WSL2 · macOS · native Windows (tmux on POSIX, the tmux-CLI-compatible [psmux](https://github.com/psmux/psmux) on native Windows — no WSL required) · MIT · see the [CHANGELOG](CHANGELOG.md).
173
191
 
174
192
  ### Update and rollback
175
193
 
176
194
  The npm package is the standalone distribution; dotagents is not involved. For a global install,
177
- update with `npm install -g aiterm-mcp@latest`. To roll back, install a known-good immutable version,
178
- for example `npm install -g "aiterm-mcp@<known-good-version>"`, then restart the MCP client. For an `npx` configuration,
195
+ update with `npm install -g aiterm-mcp@latest` and `aiterm-setup --json`. To roll back, install a known-good immutable version,
196
+ for example `npm install -g "aiterm-mcp@<known-good-version>"`, then restart the MCP client. setupを持つ版では再起動前に`aiterm-setup --json`を再実行する。For an `npx` configuration,
179
197
  use `aiterm-mcp@latest` to update or replace it with `aiterm-mcp@<version>` to pin or roll back.
180
198
  Check the [CHANGELOG](CHANGELOG.md) for state/schema compatibility before downgrading. Maintainer
181
199
  release and artifact rollback are specified in the product-owned [release procedure](docs/RELEASE.md).
@@ -323,7 +341,7 @@ The only edits to the captures above are the two `⋮` lines (a long head/tail r
323
341
 
324
342
  ## First run (≈60 seconds)
325
343
 
326
- Restart Claude Code, then verify the connection:
344
+ `aiterm-setup --json`が`ready`になったら、利用するMCP clientを再起動して接続を確認する。Claude Codeの場合:
327
345
 
328
346
  ```bash
329
347
  /mcp # aiterm should show as connected, exposing 16 tools
@@ -550,10 +568,10 @@ npm test # build, then the node:test regression suite (requires tmux o
550
568
  npm link # put `aiterm-mcp` on PATH locally
551
569
  ```
552
570
 
553
- Development uses focused local tests first. GitHub Actions runs the suite on the self-hosted
554
- `linux-workstation` runner for every push, adds `windows-native` only when Windows-specific files change,
555
- and runs all three runners (`macos-native`, `linux-workstation`, `windows-native`) once a week as a health
556
- check. `npm run release -- <version>` syncs the version, commits, tags, and publishes the GitHub Release in
571
+ 開発中は変更に直結する試験を先に実行する。GitHub Actionsは共通実装・CI自身・未分類の変更をMac・Linux・Windowsで検証し、
572
+ Windows固有だけの変更はLinuxとWindowsを選ぶ。版番号だけの変更はLinuxの配布情報・pack確認、文書だけなら文書検査を行う。
573
+ 試験内容とOSの選択、週次・手動実行の範囲は[公開手順](docs/RELEASE.md)に従う。
574
+ `npm run release -- <version>` syncs the version, commits, tags, and publishes the GitHub Release in
557
575
  one command; tag-triggered npm publishing checks only that the tagged commit is on `origin/main` and does not
558
576
  wait for another CI run. The native
559
577
  Windows runner needs psmux ≥ 3.3.8 and Git for Windows on its PATH, and must run as an
@@ -564,10 +582,11 @@ Logic lives in `src/core.ts` (tmux control, reduction, completion detection, saf
564
582
 
565
583
  ## Try it
566
584
 
567
- One command, no clone, no build:
585
+ 公開packageを導入して、検出したAIへ登録する。cloneやビルドは不要:
568
586
 
569
587
  ```bash
570
- claude mcp add --scope user --transport stdio aiterm -- npx -y aiterm-mcp
588
+ npm install -g aiterm-mcp@latest
589
+ aiterm-setup --json
571
590
  ```
572
591
 
573
592
  If aiterm let your AI hand a task to another agent — or saved you a round-trip of tokens — **[star the repo](https://github.com/kitepon/aiterm-mcp)**. It's the cheapest way to help others find it.
package/dist/core.js CHANGED
@@ -2169,6 +2169,8 @@ export async function readAgentTranscriptResult(name, o = {}) {
2169
2169
  text = codexTranscriptText(meta, turnId, readTranscriptLines, transcriptUnavailable);
2170
2170
  }
2171
2171
  else {
2172
+ if (!done)
2173
+ transcriptUnavailable();
2172
2174
  text = grokTranscriptText(meta, readTranscriptLines, transcriptUnavailable);
2173
2175
  }
2174
2176
  if (!text.trim())
@@ -71,7 +71,8 @@ export function assertGrokModelAvailable(bin, cwd, model) {
71
71
  export function grokSessionDirectory(meta) {
72
72
  if ((meta.kind !== "grok" && meta.kind !== "composer") || !meta.grok_home || !meta.vendor_session_id)
73
73
  return null;
74
- const cwd = meta.cwd ?? process.cwd();
74
+ // Grok CLIは起動cwdをOSの絶対パスへ正規化して保存する。
75
+ const cwd = path.resolve(meta.cwd ?? process.cwd());
75
76
  return path.join(meta.grok_home, "sessions", encodeURIComponent(cwd), meta.vendor_session_id);
76
77
  }
77
78
  export function grokEventsTranscript(meta) {
@@ -109,7 +110,9 @@ export function latestGrokCompletion(meta, readTranscriptLines) {
109
110
  if (!line.trim())
110
111
  continue;
111
112
  try {
112
- latest = grokCompletionEvent(meta, JSON.parse(line)) ?? latest;
113
+ const record = JSON.parse(line);
114
+ // 次のturn開始後は前の完了eventを現在の回答に結び付けない。
115
+ latest = record?.type === "turn_started" ? null : grokCompletionEvent(meta, record) ?? latest;
113
116
  }
114
117
  catch {
115
118
  // 末尾書込み中のlineは次の観測で完結してから読む。
@@ -321,12 +324,10 @@ export function grokFooterHasConfiguration(screen, model, effort) {
321
324
  }
322
325
  // 最後のuser発話以降に確定した最後のassistantメッセージをchat_history.jsonlから抽出する。
323
326
  export function grokTranscriptText(meta, readTranscriptLines, transcriptUnavailable) {
324
- if (!meta.grok_home || !meta.vendor_session_id)
327
+ const directory = grokSessionDirectory(meta);
328
+ if (!directory)
325
329
  transcriptUnavailable();
326
- // cwd 未指定で起動した TUI はサーバープロセスの cwd を継承する。metadata に null が残る既存
327
- // launch との互換のため、その実際の起動 cwd を path 導出に使う(launch 側は変更しない)。
328
- const cwd = meta.cwd ?? process.cwd();
329
- const transcript = path.join(meta.grok_home, "sessions", encodeURIComponent(cwd), meta.vendor_session_id, "chat_history.jsonl");
330
+ const transcript = path.join(directory, "chat_history.jsonl");
330
331
  const lines = readTranscriptLines(transcript);
331
332
  let lastUser = -1;
332
333
  const records = [];
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { runSetup } from "./setup.js";
3
+ const args = process.argv.slice(2);
4
+ if (args.length === 1 && ["--help", "-h"].includes(args[0])) {
5
+ process.stdout.write("使い方: aiterm-setup [--json]\n製品の依存準備、検出したAIへの登録、MCPと端末の実動作確認を行います。結果はJSONで返します。\n");
6
+ }
7
+ else if (args.length > 1 || (args.length === 1 && args[0] !== "--json")) {
8
+ process.stderr.write("使い方: aiterm-setup [--json]\n");
9
+ process.exitCode = 2;
10
+ }
11
+ else {
12
+ const result = await runSetup();
13
+ process.stdout.write(`${JSON.stringify(result)}\n`);
14
+ process.exitCode = result.status === "ready" ? 0 : 2;
15
+ }
@@ -0,0 +1,107 @@
1
+ // AI clientごとの登録形式をここに閉じ込め、setup共通処理へ漏らさない。
2
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ import { isDeepStrictEqual } from "node:util";
6
+ import { resolveAgentBin } from "./agent-resolver.js";
7
+ import { SetupError, runSetupCommand } from "./setup-platform.js";
8
+ export { powershellInvocation } from "./setup-platform.js";
9
+ const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10
+ export function mergeJsonMcp(file, registration) {
11
+ let current = {};
12
+ let target = file;
13
+ if (existsSync(file)) {
14
+ target = realpathSync(file);
15
+ try {
16
+ current = JSON.parse(readFileSync(target, "utf8"));
17
+ }
18
+ catch {
19
+ throw new SetupError("config_invalid", "既存設定のJSONを読めません");
20
+ }
21
+ if (!record(current) || (current.mcpServers !== undefined && !record(current.mcpServers))) {
22
+ throw new SetupError("config_invalid", "既存設定とmcpServersはobjectである必要があります");
23
+ }
24
+ }
25
+ else {
26
+ try {
27
+ if (lstatSync(file).isSymbolicLink())
28
+ throw new SetupError("config_invalid", "設定symlinkの参照先がありません");
29
+ }
30
+ catch (error) {
31
+ if (error.code !== "ENOENT")
32
+ throw error;
33
+ }
34
+ }
35
+ const servers = (current.mcpServers ?? {});
36
+ const previous = record(servers.aiterm) ? servers.aiterm : {};
37
+ const updated = { ...previous, ...registration };
38
+ if (isDeepStrictEqual(servers.aiterm, updated))
39
+ return "unchanged";
40
+ const next = { ...current, mcpServers: { ...servers, aiterm: updated } };
41
+ mkdirSync(dirname(target), { recursive: true });
42
+ const temporary = `${target}.aiterm-${randomUUID()}`;
43
+ try {
44
+ writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600, flag: "wx" });
45
+ if (existsSync(target))
46
+ copyFileSync(target, `${target}.aiterm-backup`);
47
+ renameSync(temporary, target);
48
+ }
49
+ finally {
50
+ if (existsSync(temporary))
51
+ unlinkSync(temporary);
52
+ }
53
+ const observed = JSON.parse(readFileSync(target, "utf8"));
54
+ if (!isDeepStrictEqual(observed.mcpServers?.aiterm, updated)) {
55
+ throw new SetupError("config_readback_failed", "aiterm登録の読戻しが一致しません");
56
+ }
57
+ return "configured";
58
+ }
59
+ export function configureIntegrations(home, registration, run = runSetupCommand, resolveClient = resolveAgentBin) {
60
+ const results = {};
61
+ for (const client of ["claude", "codex", "grok", "cursor"]) {
62
+ try {
63
+ const executable = resolveClient(client);
64
+ if (!executable && !(client === "cursor" && existsSync(join(home, ".cursor")))) {
65
+ results[client] = { status: "not_detected" };
66
+ continue;
67
+ }
68
+ if (client === "claude" || client === "cursor") {
69
+ const file = client === "cursor" ? join(home, ".cursor", "mcp.json")
70
+ : process.env.CLAUDE_CONFIG_DIR ? join(process.env.CLAUDE_CONFIG_DIR, ".claude.json") : join(home, ".claude.json");
71
+ mergeJsonMcp(file, client === "claude" ? { type: "stdio", ...registration } : registration);
72
+ }
73
+ else if (client === "codex") {
74
+ const servers = JSON.parse(run(executable, ["mcp", "list", "--json"]));
75
+ if (!Array.isArray(servers))
76
+ throw new SetupError("config_readback_failed", "CodexのMCP一覧形式を確認できません");
77
+ const existing = servers.find((entry) => entry.name === "aiterm");
78
+ const envArgs = Object.entries(existing?.transport?.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
79
+ run(executable, ["mcp", "add", "aiterm", ...envArgs, "--", registration.command, ...registration.args]);
80
+ const value = JSON.parse(run(executable, ["mcp", "get", "aiterm", "--json"]));
81
+ if (value.transport?.command !== registration.command || !isDeepStrictEqual(value.transport?.args, registration.args)) {
82
+ throw new SetupError("config_readback_failed", "Codexのaiterm登録が一致しません");
83
+ }
84
+ }
85
+ else {
86
+ const servers = JSON.parse(run(executable, ["mcp", "list", "--json"]));
87
+ if (!Array.isArray(servers))
88
+ throw new SetupError("config_readback_failed", "GrokのMCP一覧形式を確認できません");
89
+ const existing = servers.find((entry) => entry.name === "aiterm" && entry.scope === "user");
90
+ const envArgs = Object.entries(existing?.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
91
+ run(executable, ["mcp", "add", "--scope", "user", "aiterm", ...envArgs, "--", registration.command, ...registration.args]);
92
+ const value = JSON.parse(run(executable, ["mcp", "list", "--json"]));
93
+ // 公開CLIのJSON応答を照合する。未知schemaを成功へ丸めない。
94
+ const item = Array.isArray(value) ? value.find((entry) => entry.name === "aiterm") : null;
95
+ if (!item || item.command !== registration.command || !isDeepStrictEqual(item.args, registration.args)) {
96
+ throw new SetupError("config_readback_failed", "Grokのaiterm登録が一致しません");
97
+ }
98
+ }
99
+ results[client] = { status: "ready" };
100
+ }
101
+ catch (error) {
102
+ process.stderr.write(`aiterm-setup: ${client}: ${error instanceof Error ? error.message : String(error)}\n`);
103
+ results[client] = { status: "failed", reason_code: error instanceof SetupError ? error.code : "integration_failed" };
104
+ }
105
+ }
106
+ return results;
107
+ }
@@ -0,0 +1,77 @@
1
+ // setupのOS差と公式package managerの呼出しを所有する。
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { resolveWindowsPowerShell7 } from "./windows-powershell.js";
5
+ import { resolveWinPaneShell } from "./agent-resolver.js";
6
+ import { ensureWinPsmux, resolveTmux } from "./tmux-runtime.js";
7
+ export class SetupError extends Error {
8
+ code;
9
+ constructor(code, message) {
10
+ super(message);
11
+ this.code = code;
12
+ }
13
+ }
14
+ export function powershellInvocation(command, args) {
15
+ const literal = (value) => `'${value.replaceAll("'", "''")}'`;
16
+ const script = `& ${[command, ...args].map(literal).join(" ")}; if ($null -eq $LASTEXITCODE) { exit 1 }; exit $LASTEXITCODE`;
17
+ return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")];
18
+ }
19
+ export const runSetupCommand = (command, args) => {
20
+ const batch = process.platform === "win32" && /\.(?:cmd|bat)$/iu.test(command);
21
+ const result = spawnSync(batch ? resolveWindowsPowerShell7() : command, batch ? powershellInvocation(command, args) : args, { encoding: "utf8", windowsHide: true, timeout: 300_000, maxBuffer: 4 * 1024 * 1024 });
22
+ if (result.error || result.status !== 0) {
23
+ throw new SetupError("command_failed", `${command}: ${result.error?.message ?? result.stderr?.trim() ?? `exit ${result.status}`}`);
24
+ }
25
+ return result.stdout;
26
+ };
27
+ export function dependencyInstallCommand(platform, dependency, linuxId = "") {
28
+ if (platform === "win32") {
29
+ const ids = { psmux: "marlocarlo.psmux", pwsh: "Microsoft.PowerShell", git: "Git.Git" };
30
+ if (ids[dependency])
31
+ return ["winget.exe", ["install", "--id", ids[dependency], "--exact", "--source", "winget", "--accept-source-agreements", "--accept-package-agreements", "--disable-interactivity"]];
32
+ }
33
+ if (dependency === "tmux" && platform === "darwin") {
34
+ const brew = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"].find(existsSync) ?? "brew";
35
+ return [brew, ["install", "tmux"]];
36
+ }
37
+ if (dependency === "tmux" && platform === "linux" && ["ubuntu", "debian"].includes(linuxId)) {
38
+ return ["sudo", ["-n", "apt-get", "install", "--no-remove", "-y", "tmux"]];
39
+ }
40
+ throw new SetupError("platform_unsupported", `${platform}/${linuxId}の${dependency}自動導入には対応していません`);
41
+ }
42
+ export function prepareBackend(run = runSetupCommand) {
43
+ const platform = process.platform;
44
+ const linuxId = platform === "linux" ? /^ID=["']?([^"'\r\n]+)/mu.exec(readFileSync("/etc/os-release", "utf8"))?.[1] ?? "" : "";
45
+ const ensure = (name, probe) => {
46
+ try {
47
+ probe();
48
+ return;
49
+ }
50
+ catch (error) {
51
+ // 失敗を隠さず、製品の正規導入で修復してから同じprobeを再実行する。
52
+ process.stderr.write(`aiterm-setup: ${name}を準備します(${error instanceof Error ? error.message : String(error)})\n`);
53
+ }
54
+ const [command, args] = dependencyInstallCommand(platform, name, linuxId);
55
+ if (platform === "linux")
56
+ run("sudo", ["-n", "apt-get", "update"]);
57
+ run(command, args);
58
+ probe();
59
+ };
60
+ if (platform === "win32") {
61
+ ensure("pwsh", () => resolveWindowsPowerShell7());
62
+ ensure("git", () => resolveWinPaneShell("bash"));
63
+ if (process.env.AITERM_PSMUX)
64
+ ensureWinPsmux(false);
65
+ else
66
+ ensure("psmux", () => ensureWinPsmux(false));
67
+ }
68
+ else if (platform === "darwin" || platform === "linux") {
69
+ if (process.env.AITERM_TMUX)
70
+ resolveTmux(false);
71
+ else
72
+ ensure("tmux", () => resolveTmux(false));
73
+ }
74
+ else {
75
+ throw new SetupError("platform_unsupported", `${platform}には対応していません`);
76
+ }
77
+ }
package/dist/setup.js ADDED
@@ -0,0 +1,88 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, isAbsolute } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { randomUUID } from "node:crypto";
6
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8
+ import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
9
+ import { prepareBackend, runSetupCommand, SetupError } from "./setup-platform.js";
10
+ import { configureIntegrations } from "./setup-integrations.js";
11
+ export function globalRegistration(run = runSetupCommand) {
12
+ const root = run(process.platform === "win32" ? "npm.cmd" : "npm", ["root", "-g"]).trim();
13
+ if (!isAbsolute(root) || /[\r\n]/u.test(root))
14
+ throw new SetupError("global_package_required", "npm global rootを確認できません");
15
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
16
+ const installedRoot = join(root, "aiterm-mcp");
17
+ if (realpathSync(installedRoot) !== realpathSync(packageRoot)) {
18
+ throw new SetupError("global_package_required", "npm install -g aiterm-mcp後にaiterm-setupを実行してください。一時npm cacheやsource checkoutは登録しません");
19
+ }
20
+ return { command: process.execPath, args: [join(installedRoot, "dist", "index.js")] };
21
+ }
22
+ export async function verifySetupRuntime(registration) {
23
+ const client = new Client({ name: "aiterm-setup", version: "1.0.0" });
24
+ const transport = new StdioClientTransport({ ...registration, stderr: "pipe" });
25
+ transport.stderr?.on("data", (chunk) => process.stderr.write(chunk));
26
+ const name = `setup-${randomUUID().slice(0, 12)}`;
27
+ const call = async (tool, args) => {
28
+ const result = await client.request({ method: "tools/call", params: { name: tool, arguments: args } }, CallToolResultSchema, { timeout: 25_000 });
29
+ if (result.isError)
30
+ throw new SetupError("runtime_probe_failed", `${tool}が失敗しました`);
31
+ return result;
32
+ };
33
+ await client.connect(transport);
34
+ try {
35
+ await call("pty_open", { name, shell: process.platform === "win32" ? "pwsh" : "bash" });
36
+ try {
37
+ const text = process.platform === "win32" ? "Write-Output ('aiterm-' + 'ready')" : "printf 'aiterm-%s\\n' ready";
38
+ await call("pty_send", { session_id: name, text, mark: true });
39
+ const result = await call("pty_read", { session_id: name, wait: true, until: "aiterm-ready", timeout: 15, raw: true });
40
+ const output = result.content.filter((item) => item.type === "text").map((item) => item.text).join("\n");
41
+ if (!output.includes("aiterm-ready"))
42
+ throw new SetupError("runtime_probe_failed", "端末の実行結果を確認できません");
43
+ }
44
+ finally {
45
+ await call("pty_close", { session_id: name });
46
+ }
47
+ }
48
+ finally {
49
+ await client.close();
50
+ }
51
+ }
52
+ export async function runSetup(options = {}) {
53
+ const result = { schema: "aiterm.setup-result.v1", status: "failed", backend: { kind: process.platform === "win32" ? "psmux" : "tmux", status: "failed" }, integrations: {} };
54
+ const progress = options.progress ?? ((message) => process.stderr.write(`aiterm-setup: ${message}\n`));
55
+ let stage = "backend";
56
+ try {
57
+ progress("端末の依存製品を確認します");
58
+ (options.prepare ?? prepareBackend)();
59
+ // Windowsのnpm.cmd実行に必要なPowerShell 7も、global root照会より先に導入する。
60
+ stage = "global_package";
61
+ const registration = (options.registration ?? globalRegistration)();
62
+ stage = "backend";
63
+ progress("MCP経由で端末を開き、実行結果と終了を確認します");
64
+ await (options.verify ?? verifySetupRuntime)(registration);
65
+ result.backend.status = "ready";
66
+ stage = "integrations";
67
+ progress("検出したAI clientのaiterm登録を更新して確認します");
68
+ result.integrations = (options.configure ?? configureIntegrations)(process.env.HOME ?? homedir(), registration);
69
+ if (Object.values(result.integrations).some((item) => item.status === "failed")) {
70
+ result.reason_code = "integration_failed";
71
+ }
72
+ else if (!Object.values(result.integrations).some((item) => item.status === "ready")) {
73
+ result.status = "unsupported";
74
+ result.reason_code = "clients_not_detected";
75
+ }
76
+ else
77
+ result.status = "ready";
78
+ }
79
+ catch (error) {
80
+ const code = error instanceof SetupError ? error.code : `${stage}_failed`;
81
+ result.status = code === "platform_unsupported" ? "unsupported" : "failed";
82
+ result.reason_code = code;
83
+ if (stage === "backend")
84
+ result.backend = { ...result.backend, status: result.status, reason_code: code };
85
+ progress(error instanceof Error ? error.message : String(error));
86
+ }
87
+ return result;
88
+ }
@@ -24,10 +24,20 @@ export const WIN_NS = `aiterm-${createHash("sha1").update(SOCKDIR).digest("hex")
24
24
  // psmux は tmux CLI 互換の Windows ネイティブ実装(ConPTY・WSL 不要)。AITERM_PSMUX で
25
25
  // バイナリを明示上書きできる(POSIX の AITERM_TMUX に対応)。
26
26
  function psmuxBin() {
27
- return process.env.AITERM_PSMUX || "psmux";
27
+ if (process.env.AITERM_PSMUX)
28
+ return process.env.AITERM_PSMUX;
29
+ const wingetLink = process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, "Microsoft", "WinGet", "Links", "psmux.exe");
30
+ return wingetLink && fs.existsSync(wingetLink) ? wingetLink : "psmux";
31
+ }
32
+ export function psmuxVersionSupported(output) {
33
+ const match = /^psmux (\d+)\.(\d+)\.(\d+)(?:\s|$)/mu.exec(output);
34
+ if (!match)
35
+ return false;
36
+ const [major, minor, patch] = match.slice(1).map(Number);
37
+ return major > 3 || (major === 3 && (minor > 3 || (minor === 3 && patch >= 8)));
28
38
  }
29
39
  let winPsmuxOk = false;
30
- function ensureWinPsmux(observe = true) {
40
+ export function ensureWinPsmux(observe = true) {
31
41
  if (winPsmuxOk)
32
42
  return;
33
43
  const r = spawnSync(psmuxBin(), ["-V"], { encoding: "utf8", timeout: 10000 });
@@ -39,6 +49,8 @@ function ensureWinPsmux(observe = true) {
39
49
  }
40
50
  if (r.status !== 0)
41
51
  ptyDependencyError("psmux -V が失敗しました。`psmux -V` が通るか確認してください。", observe);
52
+ if (!psmuxVersionSupported(r.stdout ?? ""))
53
+ ptyDependencyError("psmux 3.3.8以上が必要です。winget upgrade --id marlocarlo.psmux --source winget で更新してください。", observe);
42
54
  winPsmuxOk = true;
43
55
  }
44
56
  // tmux が見つからないときの説明。macOS は tmux を同梱せず、Homebrew の bin は GUI 起動時の PATH に
@@ -16,12 +16,11 @@ export function resolveWindowsPowerShell7(probe = defaultProbe) {
16
16
  const located = probe("where.exe", [WINDOWS_POWERSHELL_7_COMMAND]);
17
17
  const resolved = located.stdout?.split(/\r?\n/u)
18
18
  .find(candidate => path.win32.isAbsolute(candidate)
19
- && path.win32.basename(candidate).toLowerCase() === WINDOWS_POWERSHELL_7_COMMAND);
19
+ && path.win32.basename(candidate).toLowerCase() === WINDOWS_POWERSHELL_7_COMMAND)
20
+ ?? path.win32.join(process.env.ProgramFiles ?? "C:\\Program Files", "PowerShell", "7", WINDOWS_POWERSHELL_7_COMMAND);
20
21
  const fail = () => {
21
22
  throw new AitermError(`PowerShell 7が必要です。Microsoft公式経路で導入してください: ${WINDOWS_POWERSHELL_7_INSTALL}`, 2);
22
23
  };
23
- if (located.status !== 0 || typeof resolved !== "string")
24
- fail();
25
24
  const executable = resolved;
26
25
  const version = probe(executable, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command",
27
26
  '[ordered]@{ edition = $PSVersionTable.PSEdition; major = $PSVersionTable.PSVersion.Major } | ConvertTo-Json -Compress']);
@@ -5,7 +5,7 @@ dotagentsは任意の工場統合を担うが、Aitermの製品正典や実行
5
5
 
6
6
  ## 現行正典
7
7
 
8
- - [README](../README.md)/[日本語README](../README.ja.md): 公開API、install、利用、復旧。
8
+ - [README](../README.md)/[日本語README](../README.ja.md): 公開API、`aiterm-setup`による依存準備とAI登録、利用、復旧。
9
9
  - [AGENTS](https://github.com/kitepon/aiterm-mcp/blob/main/AGENTS.md): AI作業者向けの製品境界と変更規律。
10
10
  - [DESIGN](DESIGN.md): 現行アーキテクチャと不変条件。
11
11
  - [RELEASE](RELEASE.md): version同期、検証、公開、公開後smoke、巻き戻し。
package/docs/DESIGN.md CHANGED
@@ -6,6 +6,16 @@ Aitermは、AIがローカルshell、SSH、container、REPL、別agentの対話T
6
6
  操作するstdio MCP serverである。install、session、state、schema、diagnostics、recovery、releaseは
7
7
  このrepositoryが所有し、外部の工場管理製品がなくても単独で動く。
8
8
 
9
+ ## 導入と登録
10
+
11
+ `aiterm-setup`はglobal packageからだけ実行し、依存準備、公開MCP経由の端末実行、
12
+ 検出したAIのユーザー設定への登録と読戻しを連続実行する。npm lifecycleでユーザー設定を変更しない。
13
+ 共通の順序と結果は`src/setup.ts`、公式package managerとOS差は`src/setup-platform.ts`、
14
+ 各AIの登録形式は`src/setup-integrations.ts`が所有する。既存の他サーバーは保持し、
15
+ Claude/CursorのJSONは参照先を原子的に更新して変更前backupを残す。Codex/Grokは公式CLIで登録・確認する。
16
+ 各AIの読戻しは登録内容の確認であり、端末の実動作はその前の公開MCP試験で確認する。
17
+ 失敗は理由付きJSONと非ゼロ終了で返す。対応外の自動導入と全AI未検出を成功扱いしない。
18
+
9
19
  ## Terminal model
10
20
 
11
21
  プリミティブはlocal PTYを1つ開き、text/keyを送り、画面を読み、閉じることだけである。
@@ -26,6 +36,7 @@ Throughlineの補足記憶はpathを透過搬送するだけで、内容、proje
26
36
  agent turnは常に非ブロックdispatchである。receiptの`event_cursor`がturn境界、`wait_process`が
27
37
  platform nativeな別process起動情報を返す。waiterは純readerで、親のforeground turnを塞がない。
28
38
  回答はharness所有transcriptから同じturnへ相関して回収し、欠落・曖昧・timeout時にpromptを再送しない。
39
+ Grok/Composerの記録先はCLIと同じOS絶対パスへcwdを正規化して導出し、完了通知と回答で同じ関数を使う。
29
40
  `agent_steer`は実行中のCodex/Grok turnへ追加textを差し込み、idleなら送信せず状態を返す。
30
41
  Cursorのsubmitはadapterがextended keyboard protocolのEnterへ変換し、呼び出し側は通常のdispatchだけを使う。
31
42
  起動直後のClaude sessionへの初回dispatchは、他harnessと同じくTUIの入力受付を確認してから貼付とEnterを送る。
package/docs/RELEASE.md CHANGED
@@ -5,16 +5,15 @@ Aitermのreleaseはこのrepositoryが所有する。`.github/workflows/product-
5
5
 
6
6
  ## CIの範囲
7
7
 
8
- - push/pull request: `linux-workstation` 1環境。変更した実装の依存graphから試験を選び、依存を確定できない変更は
9
- Linuxの全テストへ広げる。Windows固有ファイル(`src/windows-powershell.ts`、`src/psmux-send-worker.ts`、
10
- `test/windows-*.test.mjs`)を触った変更だけ`windows-native`を加える。
11
- - 週1回の定期実行(月曜 03:00 JST)と手動実行だけが、`macos-native`、`linux-workstation`、`windows-native`の
12
- 3環境で全テストを回す。
8
+ - push/pull request: 共通実装・CI自身・未分類の変更は`macos-native`、`linux-workstation`、`windows-native`を選ぶ。
9
+ Windows固有ファイル(`src/windows-powershell.ts`、`src/psmux-send-worker.ts`、`test/windows-*.test.mjs`)だけの変更は
10
+ LinuxとWindowsを選ぶ。共通変更が混ざっても対象OSを落とさない。試験は依存graphから選び、依存を確定できない変更だけ全テストへ広げる。
11
+ - 版番号だけの変更はJSONの実差分で識別し、Linuxでbuildと配布metadata・pack・文書確認を行う。依存や実行設定の変更は省略しない。
12
+ 文書だけなら文書検査、実装と文書の混在なら関連試験と文書検査を行う。
13
+ - 週1回の定期実行(月曜 03:00 JST)と手動実行は、指定された環境で全テストを回す。定期実行の対象は3環境である。
13
14
  - tag push: 所有確認と、tagged commitが`origin/main`の祖先であることの確認だけを行い、npmへprovenance付きで
14
15
  publishする。同じcommitのmain CIの結果は待たない。
15
16
 
16
- 実測(2026-09-02): Linux 2分、macOS 2分、Windows 6分。全環境展開ではWindowsが常にcritical pathになる。
17
-
18
17
  ## Release手順
19
18
 
20
19
  1. 変更に直結するfocused testを手元で通す。full regressionは手元で回さず、CIに任せる。
@@ -36,6 +35,10 @@ Aitermのreleaseはこのrepositoryが所有する。`.github/workflows/product-
36
35
 
37
36
  ## 公開後smoke
38
37
 
38
+ setupを変更した場合は、公開packageのglobal install後に`aiterm-setup --json`を実行し、
39
+ 端末実行と検出した各AIの登録結果を確認する。初回と再実行は一時設定領域でも試験し、所有外の設定保持を確かめる。
40
+ WindowsのGrokパス変更ではスラッシュ区切りcwdで起動し、同じturnの完了通知と回答回収を確認する。
41
+
39
42
  公式npm packageを隔離またはglobal installし、変更に触れたharnessの起動、non-blocking dispatch、wait outcome、
40
43
  transcript回収、`pty_close`後の残骸ゼロを確認する。
41
44
 
@@ -46,13 +49,21 @@ timeoutや再送案内へ変わらないことを確認する。拒否を検証
46
49
 
47
50
  ## 利用者の更新と巻き戻し
48
51
 
49
- global installはnpmの公開packageだけで完結する。
52
+ 更新はnpmの公開packageから行う。
50
53
 
51
54
  ```bash
52
55
  npm install -g aiterm-mcp@latest
56
+ aiterm-setup --json
57
+ ```
58
+
59
+ 巻き戻しは既知の正常版を指定する。
60
+
61
+ ```bash
53
62
  npm install -g "aiterm-mcp@<known-good-version>"
54
63
  ```
55
64
 
65
+ setupを持つ版では`aiterm-setup --json`を再実行する。どちらもMCP clientを再起動する。
66
+
56
67
  `npx`をMCP設定から使う場合は、package引数を`aiterm-mcp@latest`へ変えると更新でき、
57
68
  `aiterm-mcp@<known-good-version>`へ変えると固定・巻き戻しできる。変更後はMCP clientを再起動する。
58
69
  dotagentsの導入・更新は不要である。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.31.2",
3
+ "version": "0.32.0",
4
4
  "mcpName": "io.github.kitepon/aiterm-mcp",
5
5
  "description": "Persistent terminal MCP with one harness-based launcher for Claude Code, Codex CLI, Grok CLI, and Cursor Agent CLI, plus durable PTYs for SSH, containers, and REPLs.",
6
6
  "keywords": [
@@ -40,6 +40,7 @@
40
40
  "type": "module",
41
41
  "bin": {
42
42
  "aiterm-mcp": "dist/index.js",
43
+ "aiterm-setup": "dist/setup-cli.js",
43
44
  "aiterm-runtime-errors": "dist/runtime-errors-cli.js",
44
45
  "aiterm-wait": "dist/aiterm-wait-cli.js"
45
46
  },