aiterm-mcp 0.46.1 → 0.47.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,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.47.0] - 2026-10-03
11
+
12
+ ### 追加
13
+
14
+ - `aiterm-setup --hooks-only`。Claude Code・Cursorの親配送hookだけを登録し、依存準備、端末の実動作確認、MCP登録、Codex Steerには触れない。MCP登録を利用者や他の製品が管理する環境向け。結果は`aiterm.parent-hooks-result.v1`で返し、登録済みなら設定を書き換えない。
15
+ - `diagnostics`が2つ目のtextで親配送hookの状態(`aiterm-mcp.parent-delivery-diagnostics.v1`)を返す。Claude Code・Cursorそれぞれの`status`と`reason_code`、呼出元のclientに当たる`caller_status`を持つ。hookが無いと`CLAUDE_PARENT_HOOK_UNAVAILABLE`で送信が拒否されるのに、診断は`ready`だけを返していた。1つ目のfactory向けJSON(`aiterm-mcp.factory-diagnostics.v1`)は項目も`overall`の意味も変えていない。
16
+
17
+ ### 修正
18
+
19
+ - npmのprefixを利用者ごとの場所へ向けた環境(`npm_config_prefix`など)で、共通の場所へ導入した`aiterm-setup`が`global_package_failed`で失敗していた。npmの現在のglobal rootに加え、実行中のNodeの既定のglobal rootにある当packageも導入先と認める。npm一時cacheとsource checkoutは引き続き登録しない。
20
+ - `aiterm-setup`(`aiterm-update`からの実行を含む)が、CodexとGrokの登録を毎回公式CLIで作り直し、利用者が足した項目(`tool_timeout_sec`など)を落としていた。登録が同じなら書き直さない。
21
+
10
22
  ## [0.46.1] - 2026-10-02
11
23
 
12
24
  ### 修正
@@ -1964,7 +1976,8 @@ prototype (preserved under `prototype/python/` as the porting source and referen
1964
1976
  `ubuntu-latest` for Node 18/20/22, publishing to npm on `v*` tags with
1965
1977
  provenance.
1966
1978
 
1967
- [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.46.1...HEAD
1979
+ [Unreleased]: https://github.com/kitepon/aiterm-mcp/compare/v0.47.0...HEAD
1980
+ [0.47.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.46.1...v0.47.0
1968
1981
  [0.46.1]: https://github.com/kitepon/aiterm-mcp/compare/v0.46.0...v0.46.1
1969
1982
  [0.46.0]: https://github.com/kitepon/aiterm-mcp/compare/v0.45.2...v0.46.0
1970
1983
  [0.45.2]: https://github.com/kitepon/aiterm-mcp/compare/v0.45.1...v0.45.2
package/README.ja.md CHANGED
@@ -42,11 +42,17 @@ Ubuntu/Debianはsudoとaptでtmuxを準備する。必要な公式package mana
42
42
  既存設定の他サーバーを保持し、JSON設定は変更前の`.aiterm-backup`を残す。
43
43
  結果の`status`は`ready`/`unsupported`/`failed`/`restart_required`。未検出のAIは`not_detected`とし、全AI未検出は成功にしない。
44
44
  登録先はglobal packageのNodeとMCP入口の絶対パスで、npm一時cacheやsource checkoutは登録しない。
45
+ global packageは、npmの現在のglobal rootか、実行中のNodeの既定のglobal rootにあるものを指す。npmのprefixを利用者ごとの場所へ向けた環境でも、共通の場所へ導入したAitermを登録できる。
46
+ CodexとGrokは、登録が同じなら公式CLIで作り直さず、利用者が足した項目(待ち時間など)を保つ。
45
47
  HomebrewのNodeは更新後も有効な`opt`のパスをMCP登録とCodexのhookに使う。旧版の登録でNode更新後に起動できなくなった場合も、更新後の`aiterm-setup --json`で修復できる。
46
48
  更新後も同じ入口を実行し、MCP clientを再起動する。npm install自体はユーザー設定を変更しない。
47
49
  公開JSONは`schema: "aiterm.setup-result.v1"`、全体の`status`、端末の`backend`、
48
50
  AI別の`integrations`と選択機能の`codex_steer`を持つ。失敗時は`reason_code`を付け、終了コードはreadyなら0、再起動待ちは3、それ以外は2となる。
49
51
 
52
+ MCP登録を利用者や他の製品が管理する環境では、`aiterm-setup --hooks-only`でClaude Code・Cursorの親配送hookだけを登録できる。
53
+ 依存準備、端末の実動作確認、MCP登録、Codex Steerには触れない。登録済みなら設定を書き換えない。
54
+ 結果は`schema: "aiterm.parent-hooks-result.v1"`、全体の`status`(`ready`/`unsupported`/`failed`)、AI別の`hooks`(`configured`/`unchanged`/`not_detected`/`failed`)を持つ。終了コードはreadyなら0、それ以外は2となる。
55
+
50
56
 
51
57
  ### CodexへSteerを有効にする(macOS・Windows・Linux)
52
58
 
@@ -203,7 +209,7 @@ runtime-error store は canonical dotagents config の `collection.enabled: true
203
209
  場合だけ収集し、既定OFF、network送信は行いません。tag起点CIのnpm provenance(OIDC Trusted
204
210
  Publishing)で公開し、GitHub Release が Official MCP Registry を再登録します。
205
211
 
206
- **状態:** 開発継続中 · 現行公開版 **v0.46.1** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
212
+ **状態:** 開発継続中 · 現行公開版 **v0.47.0** · 動作対象は Linux · WSL2 · macOS · Windows ネイティブ · MIT · [変更履歴](CHANGELOG.md)。
207
213
 
208
214
  ### 更新と巻き戻し
209
215
 
@@ -538,6 +544,8 @@ Claudeの相関済み承認は既存の`claude_approval`を使う。
538
544
 
539
545
  `diagnostics` は PTY やエージェントを起動しない。パッケージ版、MCP 呼出 readiness、read-only な PTY 一覧要約、bounded runtime-error-store status、任意 vendor launcher の可用性だけを返す。path・環境値・認証情報・コマンド本文・PTY 出力・raw log は意図的に返さない。通常未設定の任意依存は `not_applicable`、安全に確定できない状態は `unverified` と表す。
540
546
 
547
+ 結果のtextは2つある。1つ目は上の内容で、項目を固定したfactory向けのJSON(`aiterm-mcp.factory-diagnostics.v1`)。2つ目は親配送hookの状態(`aiterm-mcp.parent-delivery-diagnostics.v1`)で、Claude Code・Cursorそれぞれの`status`(`ready`/`setup_required`/`not_applicable`/`unverified`)と`reason_code`、呼出元のclientに当たる`caller_status`を返す。`caller_status`が`setup_required`なら、そのclientからのagent送信は拒否されるので`aiterm-setup`を実行する。hookの状態は1つ目の`overall`には含めない。
548
+
541
549
  ### ローカル runtime error snapshot
542
550
 
543
551
  `aiterm-runtime-errors snapshot` は dotagents factory adapter 向けに、製品所有のローカル snapshot を機械可読 JSON で返す。canonical dotagents factory-reporter config が schema-exact、host profile が実行 OS と一致し、`collection.enabled` が JSON boolean `true` の時だけ収集する。reporting field は schema 検証するが endpoint/credential file へ接続・読取せず network I/O も行わない。観測 API は core owner layer の固定3 code(PTY dependency・persistence・任意 vendor launcher)だけを受け、保存するのも固定 template と aggregate metadata(SHA-256 fingerprint、count、first/last、status、monotonic sequence)だけ。exception、stderr/stdout、stack、prompt、PTY/transcript/event body、path、任意 context は受け付けない。保存済み JSON も top/record exact・固定定義一致・fingerprint 再計算を通し、明示 DTO だけを返す。
package/README.md CHANGED
@@ -42,11 +42,17 @@ Ubuntu/Debianはsudoとaptでtmuxを準備する。必要な公式package mana
42
42
  既存設定の他サーバーを保持し、JSON設定は変更前の`.aiterm-backup`を残す。
43
43
  結果の`status`は`ready`/`unsupported`/`failed`/`restart_required`。未検出のAIは`not_detected`とし、全AI未検出は成功にしない。
44
44
  登録先はglobal packageのNodeとMCP入口の絶対パスで、npm一時cacheやsource checkoutは登録しない。
45
+ global packageは、npmの現在のglobal rootか、実行中のNodeの既定のglobal rootにあるものを指す。npmのprefixを利用者ごとの場所へ向けた環境でも、共通の場所へ導入したAitermを登録できる。
46
+ CodexとGrokは、登録が同じなら公式CLIで作り直さず、利用者が足した項目(待ち時間など)を保つ。
45
47
  HomebrewのNodeは更新後も有効な`opt`のパスをMCP登録とCodexのhookに使う。旧版の登録でNode更新後に起動できなくなった場合も、更新後の`aiterm-setup --json`で修復できる。
46
48
  更新後も同じ入口を実行し、MCP clientを再起動する。npm install自体はユーザー設定を変更しない。
47
49
  公開JSONは`schema: "aiterm.setup-result.v1"`、全体の`status`、端末の`backend`、
48
50
  AI別の`integrations`と選択機能の`codex_steer`を持つ。失敗時は`reason_code`を付け、終了コードはreadyなら0、再起動待ちは3、それ以外は2となる。
49
51
 
52
+ MCP登録を利用者や他の製品が管理する環境では、`aiterm-setup --hooks-only`でClaude Code・Cursorの親配送hookだけを登録できる。
53
+ 依存準備、端末の実動作確認、MCP登録、Codex Steerには触れない。登録済みなら設定を書き換えない。
54
+ 結果は`schema: "aiterm.parent-hooks-result.v1"`、全体の`status`(`ready`/`unsupported`/`failed`)、AI別の`hooks`(`configured`/`unchanged`/`not_detected`/`failed`)を持つ。終了コードはreadyなら0、それ以外は2となる。
55
+
50
56
 
51
57
  ### CodexへSteerを有効にする(macOS・Windows・Linux)
52
58
 
@@ -217,7 +223,7 @@ collection is off by default and performs no network I/O. It ships via
217
223
  tag-triggered CI with npm provenance (OIDC Trusted Publishing); the GitHub
218
224
  Release re-registers the Official MCP Registry entry.
219
225
 
220
- **Status:** actively maintained · current public release **v0.46.1** · 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).
226
+ **Status:** actively maintained · current public release **v0.47.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).
221
227
 
222
228
  ### Update and rollback
223
229
 
@@ -571,6 +577,8 @@ continue to use `claude_approval`.
571
577
 
572
578
  `diagnostics` never starts a PTY or agent. It reports package version, MCP call readiness, a read-only PTY-list summary, bounded runtime-error-store status, and optional vendor-launcher availability. It deliberately excludes paths, environment values, credentials, command text, PTY output, and raw logs; normal unset optional dependencies are `not_applicable`, while an indeterminate probe is `unverified`.
573
579
 
580
+ The result carries two text items. The first is the factory JSON described above (`aiterm-mcp.factory-diagnostics.v1`), whose fields are fixed. The second reports the parent-delivery hooks (`aiterm-mcp.parent-delivery-diagnostics.v1`): a `status` (`ready` / `setup_required` / `not_applicable` / `unverified`) and `reason_code` for Claude Code and Cursor, plus `caller_status` for the calling client. When `caller_status` is `setup_required`, agent dispatch from that client is rejected; run `aiterm-setup`. Hook status is not folded into `overall` in the first item.
581
+
574
582
  ### Local runtime error snapshot
575
583
 
576
584
  snapshotの`product_version`は各recordの最終実発生時の版を表す。store v2は旧v1を読み取り、単発記録の版を保持し、複数回の旧集約の版は`unknown`にする。読取りでは状態JSONを書き戻さず、次のロック内更新でv2を保存する。consumerを先に更新し、旧writerの終了後に新writerを使う。v2保存後の旧版への切替は、製品のバックアップ復元を伴う。
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import { ParentDeliveryManager, deliveryKey } from "./parent-delivery.js";
20
20
  import { acceptRemote, callRemoteTool, observeRemoteAgentDone, remoteInputDescription, remoteInputSchema, remoteLabel, remoteWaitProcess } from "./remote.js";
21
21
  import { codexParentFromRequest } from "./codex-parent-receiver.js";
22
22
  import { claudeParentFromRequest } from "./claude-parent-receiver.js";
23
+ import { parentDeliveryDiagnostic } from "./parent-hook-diagnostic.js";
23
24
  import { cursorParentFromRequest, isCursorMcpClient } from "./cursor-parent-receiver.js";
24
25
  import { waitProcessCommandLine } from "./cursor-parent-receive.js";
25
26
  import { INTERIM_RESULT_META_KEY, interimRequestFromMeta } from "./interim-words.js";
@@ -234,9 +235,15 @@ async function remoteCompletionWait(delivery, target, sessionId, eventCursor) {
234
235
  return { wait_process: waitProcess, wait_command: `ssh ${remoteLabel(target)} aiterm-wait --session ${sessionId} --cursor ${eventCursor}` };
235
236
  }
236
237
  server.registerTool("diagnostics", {
237
- description: "Factory 向け read-only 診断。安全な状態語彙だけを機械可読 JSON で返す(PTY 内容・認証情報・path・環境値は返さない)。",
238
+ description: "Factory 向け read-only 診断。安全な状態語彙だけを機械可読 JSON で返す(PTY 内容・認証情報・path・環境値は返さない)。" +
239
+ "2つ目のtextは親配送hook(Claude Code・Cursor)の登録状態で、caller_status が setup_required なら呼出元からのagent送信は拒否される。aiterm-setup を実行する。",
238
240
  inputSchema: {},
239
- }, async () => ok(await factoryDiagnostics()));
241
+ },
242
+ // 1つ目は項目を固定したfactory向けの契約。親配送hookの状態は別のtextに分け、既存の読み手の形を変えない。
243
+ async () => ({ content: [
244
+ { type: "text", text: await factoryDiagnostics() },
245
+ { type: "text", text: JSON.stringify(parentDeliveryDiagnostic(deliveryParentKind(server.server.getClientVersion()?.name))) },
246
+ ] }));
240
247
  const DEFAULT_PTY_SHELL = process.platform === "win32" ? "pwsh" : "bash";
241
248
  registerRemoteAwareTool("pty_open", {
242
249
  description: "ローカル永続端末(POSIXはtmux、Windows nativeはpsmux 3.3.8以上)を1個開き、session_id を返す。backend server常駐ゆえ本サーバや " +
@@ -0,0 +1,83 @@
1
+ // 親配送hook(Claude Code・Cursor)の登録状態を、利用者設定の読取りだけで要約する。
2
+ // 設定の本文、path、環境値は返さない。書込みと修復はaiterm-setupが所有する。
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ import * as steer from "aiterm-steer-delivery";
7
+ import { resolveAgentBin } from "./agent-resolver.js";
8
+ import { AITERM_PROFILE } from "./steer-profile.js";
9
+ const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10
+ const ready = { status: "ready", reason_code: null };
11
+ const required = (reason_code) => ({ status: "setup_required", reason_code });
12
+ const unreadable = { status: "unverified", reason_code: "settings_unreadable" };
13
+ function readSettings(file) {
14
+ if (!existsSync(file))
15
+ return null;
16
+ try {
17
+ const value = JSON.parse(readFileSync(file, "utf8").replace(/^/u, ""));
18
+ return record(value) ? value : undefined;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ function detected(kind, home) {
25
+ try {
26
+ if (resolveAgentBin(kind))
27
+ return true;
28
+ }
29
+ catch { /* 解決できないCLIは未検出として扱う */ }
30
+ return kind === "cursor" && existsSync(join(home, ".cursor"));
31
+ }
32
+ /** aiterm-setupが登録するeventのすべてに、当製品のhook入口があり、その入口が実在するか。 */
33
+ export function claudeParentHookDiagnostic(file) {
34
+ const settings = readSettings(file);
35
+ if (settings === undefined)
36
+ return unreadable;
37
+ if (settings === null || settings.hooks === undefined)
38
+ return required("hooks_not_registered");
39
+ if (!record(settings.hooks))
40
+ return unreadable;
41
+ if (settings.disableAllHooks === true)
42
+ return required("hooks_disabled");
43
+ const entry = AITERM_PROFILE.hooks.claude;
44
+ const owned = (value) => value === entry || value.endsWith(`/${entry}`) || value.endsWith(`\\${entry}`);
45
+ const scripts = [];
46
+ for (const event of Object.keys(steer.claudeParentHookEntries(AITERM_PROFILE, { command: "", script: entry }))) {
47
+ const groups = settings.hooks[event];
48
+ if (groups !== undefined && !Array.isArray(groups))
49
+ return unreadable;
50
+ const found = (groups ?? []).flatMap(group => record(group) && Array.isArray(group.hooks) ? group.hooks : [])
51
+ .filter(hook => record(hook) && hook.type === "command" && Array.isArray(hook.args) && typeof hook.args[0] === "string" && owned(hook.args[0]))
52
+ .map(hook => hook.args[0]);
53
+ if (found.length === 0)
54
+ return required("hooks_not_registered");
55
+ scripts.push(...found);
56
+ }
57
+ return scripts.every(script => existsSync(script)) ? ready : required("hook_script_missing");
58
+ }
59
+ export function cursorParentHookDiagnostic(file) {
60
+ const document = readSettings(file);
61
+ if (document === undefined)
62
+ return unreadable;
63
+ return document !== null && steer.cursorParentHooksRegistered(AITERM_PROFILE, document) ? ready : required("hooks_not_registered");
64
+ }
65
+ /** 呼出元がそのclient自身なら、CLIを解決できなくても設定を読む。それ以外の未検出clientは対象外とする。 */
66
+ export function parentDeliveryDiagnostic(caller, options = {}) {
67
+ const home = options.home ?? process.env.HOME ?? homedir();
68
+ const detect = options.detect ?? ((kind) => detected(kind, home));
69
+ const absent = { status: "not_applicable", reason_code: null };
70
+ const hooks = {
71
+ claude: caller === "claude" || detect("claude")
72
+ ? claudeParentHookDiagnostic(options.claudeSettings ?? join(process.env.CLAUDE_CONFIG_DIR ?? join(home, ".claude"), "settings.json")) : absent,
73
+ cursor: caller === "cursor" || detect("cursor")
74
+ ? cursorParentHookDiagnostic(options.cursorHooks ?? steer.cursorHooksFile(home)) : absent,
75
+ };
76
+ return {
77
+ diagnostic_schema: "aiterm-mcp.parent-delivery-diagnostics.v1",
78
+ caller: caller ?? "other",
79
+ caller_status: caller ? hooks[caller].status : "not_applicable",
80
+ setup_command: AITERM_PROFILE.setup_command,
81
+ hooks,
82
+ };
83
+ }
package/dist/setup-cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runSetup } from "./setup.js";
2
+ import { runSetup, runParentHooksSetup } from "./setup.js";
3
3
  import { removeClaudeParentHooks, removeCursorParentHooks } from "./setup-integrations.js";
4
4
  import { homedir } from "node:os";
5
5
  import { join } from "node:path";
@@ -26,8 +26,20 @@ else if (args.length === 1 && args[0] === "--remove-cursor-parent-hooks") {
26
26
  process.exitCode = 2;
27
27
  }
28
28
  }
29
+ else if (args.includes("--hooks-only")) {
30
+ // 利用者や他製品がMCP登録を所有する環境向け。hookの登録だけを行い、stdoutへは結果のJSONだけを出す。
31
+ if (args.some(arg => !["--hooks-only", "--json"].includes(arg)) || new Set(args).size !== args.length) {
32
+ process.stderr.write("aiterm-setup: 使い方: aiterm-setup --hooks-only [--json]\n");
33
+ process.exitCode = 2;
34
+ }
35
+ else {
36
+ const result = runParentHooksSetup();
37
+ process.stdout.write(`${JSON.stringify(result)}\n`);
38
+ process.exitCode = result.status === "ready" ? 0 : 2;
39
+ }
40
+ }
29
41
  else if (args.length === 1 && ["--help", "-h"].includes(args[0])) {
30
- process.stdout.write("使い方: aiterm-setup [--json] [--codex-steer enable|disable|status]\n依存準備、AIへの登録、MCPと端末の実動作確認を行います。対話実行ではAiterm単品かCodex Desktop Steer付きかを選べます。SteerはmacOS・Windows対応で、初回はCodexの再起動が必要です。disableは専用hookを解除し、statusは公式hookの登録・承認と再起動の必要性を確認します。旧中継はhook導入後に解除します。--jsonは対話せず、Steerの選択を維持します。\n旧版へ戻す前のClaude専用hook解除: --remove-claude-parent-hooks\n旧版へ戻す前のCursor専用hook解除: --remove-cursor-parent-hooks\n");
42
+ process.stdout.write("使い方: aiterm-setup [--json] [--codex-steer enable|disable|status]\n依存準備、AIへの登録、MCPと端末の実動作確認を行います。対話実行ではAiterm単品かCodex Desktop Steer付きかを選べます。SteerはmacOS・Windows対応で、初回はCodexの再起動が必要です。disableは専用hookを解除し、statusは公式hookの登録・承認と再起動の必要性を確認します。旧中継はhook導入後に解除します。--jsonは対話せず、Steerの選択を維持します。\nClaude Code・Cursorの親配送hookだけの登録(MCP登録・依存準備・Codex Steerには触れない): --hooks-only\n旧版へ戻す前のClaude専用hook解除: --remove-claude-parent-hooks\n旧版へ戻す前のCursor専用hook解除: --remove-cursor-parent-hooks\n");
31
43
  }
32
44
  else {
33
45
  try {
@@ -81,6 +81,34 @@ export function mergeCursorParentHooks(file, registration) {
81
81
  export function removeCursorParentHooks(file) {
82
82
  return steer.removeCursorParentHooks(AITERM_PROFILE, file);
83
83
  }
84
+ function registerClaudeParentHooks(home, registration, run, executable) {
85
+ const version = /\b(\d+)\.(\d+)\.(\d+)\b/.exec(run(executable, ["--version"]));
86
+ if (!version || Number(version[1]) < 2 || (Number(version[1]) === 2 && (Number(version[2]) < 1 || (Number(version[2]) === 1 && Number(version[3]) < 259)))) {
87
+ throw new SetupError("claude_parent_delivery_unavailable", "自動配送にはClaude Code 2.1.259以上が必要です。公式CLIを更新してください");
88
+ }
89
+ return mergeClaudeParentHooks(join(process.env.CLAUDE_CONFIG_DIR ?? join(home, ".claude"), "settings.json"), registration);
90
+ }
91
+ /** 親配送hookだけの登録。clientの検出条件とhookの中身は通常のsetupと同じで、MCP登録は読みも書きもしない。 */
92
+ export function configureParentHooks(home, registration, run = runSetupCommand, resolveClient = resolveAgentBin) {
93
+ const results = {};
94
+ for (const client of ["claude", "cursor"]) {
95
+ try {
96
+ const executable = resolveClient(client);
97
+ if (!executable && !(client === "cursor" && existsSync(join(home, ".cursor")))) {
98
+ results[client] = { status: "not_detected" };
99
+ continue;
100
+ }
101
+ results[client] = { status: client === "claude"
102
+ ? registerClaudeParentHooks(home, registration, run, executable)
103
+ : mergeCursorParentHooks(join(home, ".cursor", "hooks.json"), registration) };
104
+ }
105
+ catch (error) {
106
+ process.stderr.write(`aiterm-setup: ${client}: ${error instanceof Error ? error.message : String(error)}\n`);
107
+ results[client] = { status: "failed", reason_code: error instanceof SetupError ? error.code : "hook_registration_failed" };
108
+ }
109
+ }
110
+ return results;
111
+ }
84
112
  export function configureIntegrations(home, registration, run = runSetupCommand, resolveClient = resolveAgentBin) {
85
113
  const results = {};
86
114
  for (const client of ["claude", "codex", "grok", "cursor"]) {
@@ -97,11 +125,7 @@ export function configureIntegrations(home, registration, run = runSetupCommand,
97
125
  if (client === "cursor")
98
126
  mergeCursorParentHooks(join(home, ".cursor", "hooks.json"), registration);
99
127
  if (client === "claude") {
100
- const version = /\b(\d+)\.(\d+)\.(\d+)\b/.exec(run(executable, ["--version"]));
101
- if (!version || Number(version[1]) < 2 || (Number(version[1]) === 2 && (Number(version[2]) < 1 || (Number(version[2]) === 1 && Number(version[3]) < 259)))) {
102
- throw new SetupError("claude_parent_delivery_unavailable", "自動配送にはClaude Code 2.1.259以上が必要です。公式CLIを更新してください");
103
- }
104
- mergeClaudeParentHooks(join(process.env.CLAUDE_CONFIG_DIR ?? join(home, ".claude"), "settings.json"), registration);
128
+ registerClaudeParentHooks(home, registration, run, executable);
105
129
  }
106
130
  }
107
131
  else if (client === "codex") {
@@ -120,11 +144,14 @@ export function configureIntegrations(home, registration, run = runSetupCommand,
120
144
  if (!Array.isArray(servers))
121
145
  throw new SetupError("config_readback_failed", "CodexのMCP一覧形式を確認できません");
122
146
  const existing = servers.find((entry) => entry.name === "aiterm");
123
- const envArgs = Object.entries(existing?.transport?.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
124
- run(executable, ["mcp", "add", "aiterm", ...envArgs, "--", registration.command, ...registration.args]);
125
- const value = JSON.parse(run(executable, ["mcp", "get", "aiterm", "--json"]));
126
- if (value.transport?.command !== registration.command || !isDeepStrictEqual(value.transport?.args, registration.args)) {
127
- throw new SetupError("config_readback_failed", "Codexのaiterm登録が一致しません");
147
+ // 公式CLIの追加は登録を作り直し、利用者が足した項目(待ち時間など)を落とす。同じ登録なら書き直さない。
148
+ if (existing?.transport?.command !== registration.command || !isDeepStrictEqual(existing?.transport?.args, registration.args)) {
149
+ const envArgs = Object.entries(existing?.transport?.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
150
+ run(executable, ["mcp", "add", "aiterm", ...envArgs, "--", registration.command, ...registration.args]);
151
+ const value = JSON.parse(run(executable, ["mcp", "get", "aiterm", "--json"]));
152
+ if (value.transport?.command !== registration.command || !isDeepStrictEqual(value.transport?.args, registration.args)) {
153
+ throw new SetupError("config_readback_failed", "Codexのaiterm登録が一致しません");
154
+ }
128
155
  }
129
156
  }
130
157
  else {
@@ -132,13 +159,16 @@ export function configureIntegrations(home, registration, run = runSetupCommand,
132
159
  if (!Array.isArray(servers))
133
160
  throw new SetupError("config_readback_failed", "GrokのMCP一覧形式を確認できません");
134
161
  const existing = servers.find((entry) => entry.name === "aiterm" && entry.scope === "user");
135
- const envArgs = Object.entries(existing?.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
136
- run(executable, ["mcp", "add", "--scope", "user", "aiterm", ...envArgs, "--", registration.command, ...registration.args]);
137
- const value = JSON.parse(run(executable, ["mcp", "list", "--json"]));
138
- // 公開CLIのJSON応答を照合する。未知schemaを成功へ丸めない。
139
- const item = Array.isArray(value) ? value.find((entry) => entry.name === "aiterm") : null;
140
- if (!item || item.command !== registration.command || !isDeepStrictEqual(item.args, registration.args)) {
141
- throw new SetupError("config_readback_failed", "Grokのaiterm登録が一致しません");
162
+ // Codexと同じく、同じ登録なら書き直さない。
163
+ if (existing?.command !== registration.command || !isDeepStrictEqual(existing?.args, registration.args)) {
164
+ const envArgs = Object.entries(existing?.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
165
+ run(executable, ["mcp", "add", "--scope", "user", "aiterm", ...envArgs, "--", registration.command, ...registration.args]);
166
+ const value = JSON.parse(run(executable, ["mcp", "list", "--json"]));
167
+ // 公開CLIのJSON応答を照合する。未知schemaを成功へ丸めない。
168
+ const item = Array.isArray(value) ? value.find((entry) => entry.name === "aiterm") : null;
169
+ if (!item || item.command !== registration.command || !isDeepStrictEqual(item.args, registration.args)) {
170
+ throw new SetupError("config_readback_failed", "Grokのaiterm登録が一致しません");
171
+ }
142
172
  }
143
173
  }
144
174
  results[client] = { status: "ready" };
package/dist/setup.js CHANGED
@@ -7,16 +7,33 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
7
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8
8
  import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { prepareBackend, runSetupCommand, SetupError } from "./setup-platform.js";
10
- import { configureIntegrations } from "./setup-integrations.js";
10
+ import { configureIntegrations, configureParentHooks } from "./setup-integrations.js";
11
11
  import { configureCodexSteer, codexSteerSelected } from "./setup-codex-hooks.js";
12
12
  import { setupNodeExecutable } from "./setup-node.js";
13
- export function globalRegistration(run = runSetupCommand) {
13
+ /** 実行中のNodeに付属するnpmの既定のglobal root。npmのprefixを環境変数や設定で別の場所へ向けていても変わらない。 */
14
+ export function nodeDefaultGlobalRoot(execPath = process.execPath, platform = process.platform) {
15
+ return platform === "win32" ? join(dirname(execPath), "node_modules") : join(dirname(dirname(execPath)), "lib", "node_modules");
16
+ }
17
+ /**
18
+ * 登録するのはglobal導入した当packageだけ。npmの現在のglobal rootに加え、実行中のNodeの既定のglobal rootも導入先と認める。
19
+ * npmのprefixを利用者ごとの場所へ向けた環境では、PATH上のaiterm-setupが後者に属する(ADR 0077)。
20
+ */
21
+ export function globalRegistration(run = runSetupCommand, options = {}) {
14
22
  const root = run(process.platform === "win32" ? "npm.cmd" : "npm", ["root", "-g"]).trim();
15
23
  if (!isAbsolute(root) || /[\r\n]/u.test(root))
16
24
  throw new SetupError("global_package_required", "npm global rootを確認できません");
17
- const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
18
- const installedRoot = join(root, "aiterm-mcp");
19
- if (realpathSync(installedRoot) !== realpathSync(packageRoot)) {
25
+ const packageRoot = options.packageRoot ?? dirname(dirname(fileURLToPath(import.meta.url)));
26
+ const current = join(root, "aiterm-mcp");
27
+ const same = (candidate) => { try {
28
+ return realpathSync(candidate) === realpathSync(packageRoot);
29
+ }
30
+ catch {
31
+ return false;
32
+ } };
33
+ const installedRoot = [current, join(options.defaultRoot ?? nodeDefaultGlobalRoot(), "aiterm-mcp")].find(same);
34
+ if (!installedRoot) {
35
+ // どちらにも当packageが無い時の失敗は従来のまま(導入先が無ければここで読めずに落ちる)。
36
+ realpathSync(current);
20
37
  throw new SetupError("global_package_required", "npm install -g aiterm-mcp後にaiterm-setupを実行してください。一時npm cacheやsource checkoutは登録しません");
21
38
  }
22
39
  return { command: setupNodeExecutable(), args: [join(installedRoot, "dist", "index.js")] };
@@ -100,3 +117,28 @@ export async function runSetup(options = {}) {
100
117
  }
101
118
  return result;
102
119
  }
120
+ /** Claude Code・Cursorの親配送hookだけを登録する。依存準備、端末の実動作確認、MCP登録、Codex Steerには触れない。 */
121
+ export function runParentHooksSetup(options = {}) {
122
+ const result = { schema: "aiterm.parent-hooks-result.v1", status: "failed", hooks: {} };
123
+ const progress = options.progress ?? ((message) => process.stderr.write(`aiterm-setup: ${message}\n`));
124
+ let stage = "global_package";
125
+ try {
126
+ const registration = (options.registration ?? globalRegistration)();
127
+ stage = "hooks";
128
+ progress("検出したAI clientの親配送hookを登録して確認します");
129
+ result.hooks = (options.configure ?? configureParentHooks)(process.env.HOME ?? homedir(), registration);
130
+ if (Object.values(result.hooks).some((item) => item.status === "failed"))
131
+ result.reason_code = "hook_registration_failed";
132
+ else if (Object.values(result.hooks).every((item) => item.status === "not_detected")) {
133
+ result.status = "unsupported";
134
+ result.reason_code = "clients_not_detected";
135
+ }
136
+ else
137
+ result.status = "ready";
138
+ }
139
+ catch (error) {
140
+ result.reason_code = error instanceof SetupError ? error.code : `${stage}_failed`;
141
+ progress(error instanceof Error ? error.message : String(error));
142
+ }
143
+ return result;
144
+ }
package/docs/DESIGN.md CHANGED
@@ -15,6 +15,9 @@ Aitermは、AIがローカルshell、SSH、container、REPL、別agentの対話T
15
15
  Claude/CursorのJSONは参照先を原子的に更新して変更前backupを残す。Codex/Grokは公式CLIで登録・確認する。
16
16
  各AIの読戻しは登録内容の確認であり、端末の実動作はその前の公開MCP試験で確認する。
17
17
  失敗は理由付きJSONと非ゼロ終了で返す。対応外の自動導入と全AI未検出を成功扱いしない。
18
+ global packageは、npmの現在のglobal rootか、実行中のNodeの既定のglobal rootにあるものを指す。
19
+ CodexとGrokは登録が同じなら公式CLIで作り直さない。`aiterm-setup --hooks-only`はClaude Code・Cursorの親配送hookだけを登録し、
20
+ 依存準備、端末の実動作確認、MCP登録、Codex Steerに触れない。判断はADR 0077。
18
21
 
19
22
  ## Terminal model
20
23
 
@@ -316,6 +319,8 @@ privacy案内は現在の枠付きcomposerとmodel footerが見える場合だ
316
319
  製品所有のlocal stateに固定codeと集約metadataだけを保存し、network I/Oを持たない。工場reporterとの
317
320
  連携は明示opt-inの任意adapterであり、未設定時もAiterm本体は単独動作する。raw error、prompt、出力、
318
321
  transcript、path、credentialを保存・公開しない。
322
+ 親配送hook(Claude Code・Cursor)の登録状態は`src/parent-hook-diagnostic.ts`が利用者設定の読取りだけで要約し、
323
+ 2つ目のtextとして返す。1つ目のfactory向けJSONは項目も`overall`の意味も変えない(ADR 0077)。
319
324
 
320
325
  ## 変更条件
321
326
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiterm-mcp",
3
- "version": "0.46.1",
3
+ "version": "0.47.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": [