n2n-nexus 0.9.4 → 0.9.5

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
@@ -4,6 +4,12 @@
4
4
 
5
5
  All notable changes to this project will be documented in this file.
6
6
 
7
+ ## [0.9.5] - 2026-10-26
8
+
9
+ ### Changed
10
+
11
+ - `n2n-nexus install codex` trusts the n2n-nexus hooks in Codex itself: it asks Codex (`codex app-server`, the copy the Codex app runs on Windows, else `codex` on `PATH`) for each hook's trust hash and stores it, so no `/hooks` step is needed. If that fails, it says how to trust them by hand (the Codex app's settings, or `/hooks` in the CLI). `n2n-nexus doctor codex` checks with Codex that the hooks are trusted, which a Codex update can undo.
12
+
7
13
  ## [0.9.4] - 2026-10-26
8
14
 
9
15
  ### Changed
package/README.md CHANGED
@@ -92,7 +92,7 @@ n2n-nexus gives AI assistants a shared coordination workspace. A long-running lo
92
92
 
93
93
  1. Install and start the host, `n2n-nexus-host`, with Scoop, Homebrew or an archive from [Releases](https://github.com/n2ns/n2n-nexus/releases): [Installation guide, steps 1–2](docs/INSTALL.md#1-install-the-host).
94
94
  2. Install the client and connect Claude Code or Codex (`npm i -g n2n-nexus@latest`, then `n2n-nexus install claude` or `n2n-nexus install codex`): [step 3](docs/INSTALL.md#3-install-the-client-for-claude-code-and-codex).
95
- 3. Codex only: trust the n2n-nexus hooks once with `/hooks`: [step 4](docs/INSTALL.md#4-turn-on-the-codex-hooks-one-time-required).
95
+ 3. Codex only: `install codex` trusts the n2n-nexus hooks in Codex; if it says it could not, trust them by hand: [step 4](docs/INSTALL.md#4-turn-on-the-codex-hooks-one-time-required).
96
96
 
97
97
  Other MCP clients and several machines: see the [Installation guide](docs/INSTALL.md). Using it, troubleshooting and configuration: see the [User guide](docs/USER_GUIDE.md).
98
98
 
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Codex runs a hook from hooks.json only once it is trusted: config.toml keeps
3
+ * a `hooks.state.<key>.trusted_hash` per hook, a hash Codex computes from the
4
+ * hook's whole configuration as its own version serializes it, so another
5
+ * Codex version may compute another hash and the value cannot be computed
6
+ * here. Codex's app server reports it instead: `hooks/list` gives each hook's
7
+ * key, current hash and trust status, and `config/value/write` stores the
8
+ * hash, as the CLI's /hooks screen and the app's settings do. The Codex the
9
+ * user runs decides, so the app's own copy is preferred to the one on PATH.
10
+ */
11
+ import { spawn } from "node:child_process";
12
+ import fs from "node:fs";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import { pkg } from "../config/index.js";
16
+ import { clientCommand } from "./client-command.js";
17
+ const TIMEOUT_MS = 30_000;
18
+ /**
19
+ * The Codex to ask: NEXUS_CODEX_COMMAND when set; on Windows the newest copy
20
+ * the Codex app keeps in %LOCALAPPDATA%\OpenAI\Codex\bin\<build>\codex.exe;
21
+ * else `codex` on PATH. Null when none can be run.
22
+ */
23
+ export function codexProgram() {
24
+ const override = process.env.NEXUS_CODEX_COMMAND;
25
+ if (override) {
26
+ const resolved = clientCommand(override);
27
+ return resolved && { ...resolved, label: override };
28
+ }
29
+ if (process.platform === "win32" && process.env.LOCALAPPDATA) {
30
+ const bin = path.join(process.env.LOCALAPPDATA, "OpenAI", "Codex", "bin");
31
+ let newest;
32
+ try {
33
+ for (const build of fs.readdirSync(bin)) {
34
+ const file = path.join(bin, build, "codex.exe");
35
+ const time = fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs;
36
+ if (time !== undefined && (!newest || time > newest.time))
37
+ newest = { file, time };
38
+ }
39
+ }
40
+ catch { /* no Codex app */ }
41
+ if (newest)
42
+ return { command: newest.file, args: [], label: newest.file };
43
+ }
44
+ const resolved = clientCommand("codex");
45
+ return resolved && { ...resolved, label: "codex" };
46
+ }
47
+ /**
48
+ * Ask `codex app-server` about our hooks in `hooksFile`
49
+ * and, with `trust`, store the trust of those it does not trust yet. Returns
50
+ * the state afterwards; throws when the app server cannot be used.
51
+ */
52
+ export async function codexHookTrust(program, hooksFile, trust) {
53
+ const child = spawn(program.command, [...program.args, "app-server"], { stdio: ["pipe", "pipe", "ignore"], windowsHide: true });
54
+ const waiting = new Map();
55
+ let fail = () => { };
56
+ const failure = new Promise((_, reject) => { fail = reject; });
57
+ failure.catch(() => { });
58
+ child.on("error", (error) => fail(error));
59
+ // A write after the app server closed its input (it crashed) fails here, not as an uncaught error.
60
+ child.stdin.on("error", (error) => fail(error));
61
+ child.on("exit", (code) => fail(new Error(`codex app-server exited (${code})`)));
62
+ let buffer = "";
63
+ child.stdout.setEncoding("utf8");
64
+ child.stdout.on("data", (chunk) => {
65
+ buffer += chunk;
66
+ let end;
67
+ while ((end = buffer.indexOf("\n")) >= 0) {
68
+ const line = buffer.slice(0, end);
69
+ buffer = buffer.slice(end + 1);
70
+ let message;
71
+ try {
72
+ message = JSON.parse(line);
73
+ }
74
+ catch {
75
+ continue;
76
+ }
77
+ if (typeof message.id === "number")
78
+ waiting.get(message.id)?.(message);
79
+ }
80
+ });
81
+ let next = 1;
82
+ const call = async (method, params) => {
83
+ const id = next++;
84
+ const answer = new Promise((resolve) => waiting.set(id, resolve));
85
+ child.stdin.write(JSON.stringify({ id, method, params }) + "\n");
86
+ const message = await Promise.race([answer, failure]);
87
+ waiting.delete(id);
88
+ if (message.error)
89
+ throw new Error(`${method}: ${message.error.message ?? "failed"}`);
90
+ return message.result;
91
+ };
92
+ const file = path.resolve(hooksFile);
93
+ const list = async () => {
94
+ const result = await call("hooks/list", { cwds: [os.homedir()] });
95
+ return (result.data ?? []).flatMap((entry) => entry.hooks ?? [])
96
+ .filter((hook) => typeof hook.sourcePath === "string" && path.resolve(hook.sourcePath) === file && isOurCodexHook(hook.command ?? ""));
97
+ };
98
+ const timer = setTimeout(() => fail(new Error("codex app-server did not answer in time")), TIMEOUT_MS);
99
+ try {
100
+ await call("initialize", { clientInfo: { name: "n2n-nexus", version: pkg.version } });
101
+ child.stdin.write(JSON.stringify({ method: "initialized" }) + "\n");
102
+ let hooks = await list();
103
+ const pending = hooks.filter((hook) => hook.trustStatus === "untrusted" || hook.trustStatus === "modified");
104
+ if (trust && pending.length > 0) {
105
+ const state = Object.fromEntries(pending.map((hook) => [hook.key, { trusted_hash: hook.currentHash }]));
106
+ await call("config/value/write", { keyPath: "hooks.state", mergeStrategy: "upsert", value: state });
107
+ hooks = await list();
108
+ }
109
+ return { codex: program.label, ours: hooks.length, untrusted: hooks.filter((hook) => hook.trustStatus !== "trusted").map((hook) => hook.key) };
110
+ }
111
+ finally {
112
+ clearTimeout(timer);
113
+ child.removeAllListeners("exit");
114
+ child.stdin.end();
115
+ child.kill();
116
+ }
117
+ }
118
+ /**
119
+ * Whether a command Codex lists is one of our hooks. Codex on Windows lists
120
+ * the PowerShell form install writes as `commandWindows` (`... 'hook' 'codex'`).
121
+ */
122
+ export function isOurCodexHook(command) {
123
+ return command.includes("n2n-nexus") && (command.includes(" hook codex") || command.includes("'hook' 'codex'"));
124
+ }
125
+ /** Why Codex may list none of our hooks, for messages. */
126
+ export const NOT_LISTED = "Codex does not list the n2n-nexus hooks: check that hooks are on in Codex's config.toml ([features] hooks)";
127
+ /** How to trust the hooks by hand, for messages. */
128
+ export const MANUAL_TRUST = "trust the n2n-nexus hooks by hand: in the Codex app's settings (hooks), or with /hooks in the Codex CLI";
@@ -3,7 +3,8 @@
3
3
  * script in the hook commands, and the Node path and npm's npx script in the
4
4
  * MCP server command (`"<node>" "<npx>" -y n2n-nexus@latest mcp`), that
5
5
  * `install` wrote still exist and run, that the host those commands talk to answers
6
- * GET /health as n2n-nexus-host with this client's contract version, and say
6
+ * GET /health as n2n-nexus-host with this client's contract version, that
7
+ * Codex trusts the Codex hooks (asked through codex-trust.ts), and say
7
8
  * how to fix what does not. Without a client both are checked; a client with
8
9
  * nothing installed is only reported when it was named. Exit code 1 when a
9
10
  * check failed.
@@ -147,6 +148,37 @@ function doctorClient(client, endpoints, defaultEndpoint) {
147
148
  process.stdout.write(lines.join("\n") + "\n");
148
149
  return !failed;
149
150
  }
151
+ /**
152
+ * Whether Codex trusts our hooks (it runs untrusted or modified ones not at
153
+ * all); prints the result. A Codex that cannot be asked is only noted.
154
+ */
155
+ async function doctorCodexTrust() {
156
+ const { MANUAL_TRUST, NOT_LISTED, codexHookTrust, codexProgram } = await import("./codex-trust.js");
157
+ const program = codexProgram();
158
+ if (!program) {
159
+ process.stdout.write(` note: hook trust not checked: the "codex" command could not be run.\n`);
160
+ return true;
161
+ }
162
+ let state;
163
+ try {
164
+ state = await codexHookTrust(program, configFile("codex"), false);
165
+ }
166
+ catch (error) {
167
+ process.stdout.write(` note: hook trust not checked with ${program.label}: ${error.message}\n`);
168
+ return true;
169
+ }
170
+ if (state.ours === 0) {
171
+ process.stdout.write(` FAIL hook trust (${program.label}): ${NOT_LISTED}, then run "n2n-nexus install codex" again.\n`);
172
+ return false;
173
+ }
174
+ if (state.untrusted.length === 0) {
175
+ process.stdout.write(` ok hook trust (${program.label}): ${state.ours} hooks trusted\n`);
176
+ return true;
177
+ }
178
+ process.stdout.write(` FAIL hook trust (${program.label}): not trusted: ${state.untrusted.join(", ")}\n`
179
+ + ` fix: run "n2n-nexus install codex" again, or ${MANUAL_TRUST}.\n`);
180
+ return false;
181
+ }
150
182
  /** Parse `[claude|codex]`, run the checks and return the exit code. */
151
183
  export async function runDoctor(argv) {
152
184
  const client = argv[0];
@@ -172,6 +204,8 @@ export async function runDoctor(argv) {
172
204
  else if (!result) {
173
205
  failed = true;
174
206
  }
207
+ if (c === "codex" && result !== undefined && !await doctorCodexTrust())
208
+ failed = true;
175
209
  }
176
210
  if (endpoints.size === 0)
177
211
  endpoints.add(defaultEndpoint);
@@ -373,9 +373,6 @@ export function runInstall(options) {
373
373
  if (VERSIONED_NODE_DIR.test(node) || VERSIONED_NODE_DIR.test(npm.npx)) {
374
374
  process.stdout.write(`Note: this Node or npx path contains a version directory that an upgrade may remove. After upgrading Node, run "n2n-nexus install ${client}" again, or install with --node <path> pointing at a stable entry such as /usr/local/bin/node.\n`);
375
375
  }
376
- if (client === "codex") {
377
- process.stdout.write("Next: open Codex and run /hooks to trust the n2n-nexus hooks; until then they do not run.\n");
378
- }
379
376
  }
380
377
  return 0;
381
378
  }
@@ -385,7 +382,7 @@ export function runInstall(options) {
385
382
  }
386
383
  }
387
384
  /** Parse `<claude|codex> [--endpoint <url>] [--node <path>]` and run. */
388
- export function runInstallCommand(action, argv) {
385
+ export async function runInstallCommand(action, argv) {
389
386
  const client = argv[0];
390
387
  if (!isHookClient(client)) {
391
388
  process.stderr.write(`Usage: n2n-nexus ${action} <claude|codex>${action === "install" ? " [--endpoint <url>] [--node <path>]" : ""}\n`);
@@ -408,5 +405,29 @@ export function runInstallCommand(action, argv) {
408
405
  const node = option("--node", "a path");
409
406
  if (node === null)
410
407
  return 1;
411
- return runInstall({ action, client, endpoint, node });
408
+ const code = runInstall({ action, client, endpoint, node });
409
+ if (code === 0 && action === "install" && client === "codex")
410
+ await trustCodex();
411
+ return code;
412
+ }
413
+ /**
414
+ * Codex runs the hooks just written only once they are trusted: trust them
415
+ * through Codex itself, or say how to do it by hand. Never fails the install.
416
+ */
417
+ async function trustCodex() {
418
+ const { MANUAL_TRUST, NOT_LISTED, codexHookTrust, codexProgram } = await import("./codex-trust.js");
419
+ const program = codexProgram();
420
+ try {
421
+ if (!program)
422
+ throw new Error(`the "codex" command could not be run`);
423
+ const state = await codexHookTrust(program, configFile("codex"), true);
424
+ if (state.ours === 0)
425
+ throw new Error(`${program.label}: ${NOT_LISTED}`);
426
+ if (state.untrusted.length > 0)
427
+ throw new Error(`Codex (${program.label}) still does not trust ${state.untrusted.join(", ")}`);
428
+ process.stdout.write(`Trusted the ${state.ours} n2n-nexus hooks in Codex (${program.label}); start a new Codex conversation to use them.\n`);
429
+ }
430
+ catch (error) {
431
+ process.stdout.write(`Could not trust the hooks automatically (${error.message}). Codex does not run them until you ${MANUAL_TRUST}.\n`);
432
+ }
412
433
  }
package/build/index.js CHANGED
@@ -50,7 +50,7 @@ async function main() {
50
50
  }
51
51
  if (command === "install" || command === "uninstall") {
52
52
  const { runInstallCommand } = await import("./cli/install.js");
53
- process.exit(runInstallCommand(command, process.argv.slice(3)));
53
+ process.exit(await runInstallCommand(command, process.argv.slice(3)));
54
54
  }
55
55
  if (command === "doctor") {
56
56
  const { runDoctor } = await import("./cli/doctor.js");
@@ -4,6 +4,12 @@
4
4
 
5
5
  本项目的所有重大变更都将记录在此文件中。
6
6
 
7
+ ## [0.9.5] - 2026-10-26
8
+
9
+ ### 变更
10
+
11
+ - `n2n-nexus install codex` 直接在 Codex 里信任 n2n-nexus 的 hooks:它向 Codex 自己(`codex app-server`;Windows 上用 Codex 桌面应用运行的那份,否则用 `PATH` 上的 `codex`)取得每条 hook 的信任哈希并存下,不再需要 `/hooks` 这一步。失败时会说明怎么手动信任(Codex 桌面应用的设置,或命令行里的 `/hooks`)。`n2n-nexus doctor codex` 会向 Codex 核对 hooks 是否被信任,Codex 升级可能会让信任失效。
12
+
7
13
  ## [0.9.4] - 2026-10-26
8
14
 
9
15
  ### 变更
package/docs/INSTALL.md CHANGED
@@ -66,19 +66,22 @@ The MCP server updates itself: it runs `n2n-nexus@latest` through npm's npx, so
66
66
 
67
67
  ## 4. Turn on the Codex hooks (one-time, required)
68
68
 
69
- Codex does not run hooks that someone else wrote until you have reviewed and trusted them. Without this step Codex conversations still get the shared tools, but they cannot send or receive direct messages, and the host never sees them come online.
69
+ Codex does not run hooks that someone else wrote until they are trusted. Without trust, Codex conversations still get the shared tools, but they cannot send or receive direct messages, and the host never sees them come online.
70
70
 
71
- 1. Open the Codex CLI in a terminal: `codex`.
72
- 2. Type `/hooks`.
73
- 3. Review the n2n-nexus entries (SessionStart, SessionEnd, UserPromptSubmit, PostToolUse and two Stop hooks; their commands run `n2n-nexus … hook codex`) and trust them.
74
- 4. Start a new Codex conversation.
71
+ `n2n-nexus install codex` trusts the n2n-nexus hooks for you: it asks Codex itself for each hook's trust hash (`codex app-server`) and stores it, as Codex's own settings do. On Windows it uses the copy of Codex that the Codex app runs (`%LOCALAPPDATA%\OpenAI\Codex\bin\<build>\codex.exe`), elsewhere `codex` on `PATH` (`NEXUS_CODEX_COMMAND` overrides both). It prints `Trusted the 6 n2n-nexus hooks in Codex (...)`; then start a new Codex conversation.
72
+
73
+ If it prints `Could not trust the hooks automatically`, trust them by hand:
74
+
75
+ - Codex app: open its settings, find the hooks, and trust the n2n-nexus entries.
76
+ - Codex CLI: run `codex`, type `/hooks`, and trust the n2n-nexus entries.
77
+
78
+ The entries are SessionStart, SessionEnd, UserPromptSubmit, PostToolUse and two Stop hooks; their commands run `n2n-nexus … hook codex`.
75
79
 
76
80
  Good to know:
77
81
 
78
- - Codex stores the trust per hook, with a hash of its command, in `~/.codex/config.toml` (or `$CODEX_HOME/config.toml`). The CLI and the Codex app share it, so trusting once in the CLI is enough for the app too. The Codex app has no `/hooks` screen of its own: use the CLI as above.
79
- - A changed command needs trusting again. That happens when you run `n2n-nexus install codex` again with a different `--endpoint` or `--node`, or after your Node.js moved to another path. Upgrading the npm package alone does not change the commands.
82
+ - Codex stores the trust per hook in `~/.codex/config.toml` (or `$CODEX_HOME/config.toml`), as a hash Codex computes from the hook's whole configuration. The CLI and the Codex app share the file, but the hash depends on the Codex version: when the CLI on `PATH` is older or newer than the app's own copy, one of them may see the hooks as modified. The app's copy decides, which is why `install` prefers it.
83
+ - Trust has to be given again when a hook changes (another `--endpoint` or `--node`, a Node.js that moved) or when a Codex update computes the hash differently. `n2n-nexus doctor codex` checks the trust with Codex and reports `FAIL hook trust` then; run `n2n-nexus install codex` again. Upgrading the npm package alone does not change the hooks.
80
84
  - Hooks are on by default in current Codex versions. If a policy or your own `config.toml` sets `[features] hooks = false`, they do not run at all.
81
- - When hooks need review, Codex warns at startup and points you to `/hooks`.
82
85
 
83
86
  Official documentation: [Codex hooks](https://learn.chatgpt.com/docs/hooks).
84
87
 
@@ -127,7 +130,7 @@ On macOS the first connection may trigger a **Local Network** permission prompt
127
130
 
128
131
  ## 7. Check the installation
129
132
 
130
- `n2n-nexus doctor [claude|codex]` checks that the Node path and the entry script in the installed hook commands, and the Node path and npx in the MCP command, still exist and run, and that the host they talk to answers with the client's contract version; it exits with code 1 when a check fails.
133
+ `n2n-nexus doctor [claude|codex]` checks that the Node path and the entry script in the installed hook commands, and the Node path and npx in the MCP command, still exist and run, that Codex trusts the Codex hooks (asking Codex itself), and that the host they talk to answers with the client's contract version; it exits with code 1 when a check fails.
131
134
 
132
135
  ## 8. Upgrading
133
136
 
@@ -66,19 +66,22 @@ MCP 服务器会自己更新:它通过 npm 的 npx 运行 `n2n-nexus@latest`
66
66
 
67
67
  ## 4. 启用 Codex 的 hooks(只需一次,必须做)
68
68
 
69
- Codex 不会运行别人写的 hooks,除非你审阅并信任了它们。不做这一步,Codex 对话仍能用共享工具,但收发不了直接消息,host 也看不到它们上线。
69
+ Codex 不会运行别人写的 hooks,除非它们被信任。没有信任,Codex 对话仍能用共享工具,但收发不了直接消息,host 也看不到它们上线。
70
70
 
71
- 1. 在终端里打开 Codex 命令行:`codex`。
72
- 2. 输入 `/hooks`。
73
- 3. 审阅 n2n-nexus 的几条 hook(SessionStart、SessionEnd、UserPromptSubmit、PostToolUse 和两条 Stop,命令都是 `n2n-nexus … hook codex`),然后信任它们。
74
- 4. 新开一个 Codex 对话。
71
+ `n2n-nexus install codex` 会替你信任 n2n-nexus 的 hooks:它通过 Codex 自己(`codex app-server`)取得每条 hook 的信任哈希并存下,和 Codex 自己的设置做的一样。Windows 上用 Codex 桌面应用实际运行的那份 Codex(`%LOCALAPPDATA%\OpenAI\Codex\bin\<build>\codex.exe`),其他系统用 `PATH` 上的 `codex`(`NEXUS_CODEX_COMMAND` 可替换两者)。成功时输出 `Trusted the 6 n2n-nexus hooks in Codex (...)`,之后新开一个 Codex 对话即可。
72
+
73
+ 如果输出 `Could not trust the hooks automatically`,就手动信任:
74
+
75
+ - Codex 桌面应用:打开设置,找到 hooks,信任 n2n-nexus 的几条。
76
+ - Codex 命令行:运行 `codex`,输入 `/hooks`,信任 n2n-nexus 的几条。
77
+
78
+ 这几条是 SessionStart、SessionEnd、UserPromptSubmit、PostToolUse 和两条 Stop,命令都是 `n2n-nexus … hook codex`。
75
79
 
76
80
  需要知道的:
77
81
 
78
- - Codex 按每条 hook 记录信任,连同它命令的哈希,存在 `~/.codex/config.toml`(或 `$CODEX_HOME/config.toml`)里。命令行和 Codex 桌面应用共用这份记录,所以在命令行里信任一次,桌面应用也生效。Codex 桌面应用没有自己的 `/hooks` 界面,请按上面的步骤用命令行。
79
- - 命令变了就要重新信任。比如用不同的 `--endpoint` 或 `--node` 重新运行 `n2n-nexus install codex`,或者 Node.js 换了路径之后。只升级 npm 包不会改变命令。
82
+ - Codex 按每条 hook 记录信任,存在 `~/.codex/config.toml`(或 `$CODEX_HOME/config.toml`)里,值是 Codex 根据这条 hook 的完整配置算出的哈希。命令行和桌面应用共用这个文件,但哈希的算法随 Codex 版本变化:`PATH` 上的命令行版本和桌面应用自带的那份不一致时,其中一方可能认为 hooks 已被修改。以桌面应用自带的那份为准,所以 `install` 优先用它。
83
+ - hook 变了(换了 `--endpoint` 或 `--node`、Node.js 换了路径),或者 Codex 升级后哈希算法变了,都要重新信任。这时 `n2n-nexus doctor codex` 会向 Codex 核对并报告 `FAIL hook trust`,重新运行 `n2n-nexus install codex` 即可。只升级 npm 包不会改变 hooks。
80
84
  - 当前版本的 Codex 默认开启 hooks。如果管理策略或你自己的 `config.toml` 里设置了 `[features] hooks = false`,hooks 就完全不会运行。
81
- - 有 hooks 需要审阅时,Codex 启动时会给出提示,指向 `/hooks`。
82
85
 
83
86
  官方文档:[Codex hooks](https://learn.chatgpt.com/docs/hooks)。
84
87
 
@@ -127,7 +130,7 @@ macOS 上第一次连接时,系统可能会为运行客户端的应用(Claud
127
130
 
128
131
  ## 7. 检查安装
129
132
 
130
- `n2n-nexus doctor [claude|codex]` 检查已安装的 hook 命令里的 Node 路径与入口脚本、MCP 命令里的 Node 路径与 npx 是否仍然存在、能否运行,以及它们连接的 host 是否应答、协议版本是否与客户端一致,有检查失败时以退出码 1 退出。
133
+ `n2n-nexus doctor [claude|codex]` 检查已安装的 hook 命令里的 Node 路径与入口脚本、MCP 命令里的 Node 路径与 npx 是否仍然存在、能否运行,Codex 是否信任 Codex 的 hooks(直接问 Codex),以及它们连接的 host 是否应答、协议版本是否与客户端一致,有检查失败时以退出码 1 退出。
131
134
 
132
135
  ## 8. 升级
133
136
 
package/docs/README_zh.md CHANGED
@@ -92,7 +92,7 @@ n2n-nexus 为 AI 助手提供共享协作空间。一个长期运行的本地 ho
92
92
 
93
93
  1. 安装并启动 host `n2n-nexus-host`:用 Scoop、Homebrew 或 [Releases](https://github.com/n2ns/n2n-nexus/releases) 里的压缩包,见[安装手册第 1–2 步](INSTALL_zh.md#1-安装-host)。
94
94
  2. 安装客户端,接入 Claude Code 或 Codex(`npm i -g n2n-nexus@latest`,再执行 `n2n-nexus install claude` 或 `n2n-nexus install codex`),见[第 3 步](INSTALL_zh.md#3-为-claude-code-和-codex-安装客户端)。
95
- 3. 仅 Codex:用 `/hooks` 信任一次 n2n-nexus 的 hooks,见[第 4 步](INSTALL_zh.md#4-启用-codex-的-hooks只需一次必须做)。
95
+ 3. 仅 Codex:`install codex` 会在 Codex 里信任 n2n-nexus 的 hooks;提示没能自动信任时再手动信任,见[第 4 步](INSTALL_zh.md#4-启用-codex-的-hooks只需一次必须做)。
96
96
 
97
97
  其他 MCP 客户端和多台机器,见[安装手册](INSTALL_zh.md);使用方法、常见问题和配置,见[使用手册](USER_GUIDE_zh.md)。
98
98
 
@@ -37,7 +37,7 @@ Each conversation appears in `peers`. A new direct message is announced to the r
37
37
  | Symptom | Cause and fix |
38
38
  |---|---|
39
39
  | Tool calls say n2n-nexus-host is not available | The host is not running or not reachable: start it ([Installation guide, step 2](INSTALL.md#2-start-the-host)), or for another machine turn LAN access on ([step 6](INSTALL.md#6-several-machines-and-environments)). |
40
- | Codex sessions never appear in `peers` | The Codex hooks are not trusted yet ([Installation guide, step 4](INSTALL.md#4-turn-on-the-codex-hooks-one-time-required)). |
40
+ | Codex sessions never appear in `peers` | The Codex hooks are not trusted (yet, or again after a Codex update): `n2n-nexus doctor codex` says so; run `n2n-nexus install codex` again ([Installation guide, step 4](INSTALL.md#4-turn-on-the-codex-hooks-one-time-required)). |
41
41
  | A conversation gets message notices but has no `read` / `reply` tools | The hooks are installed but the MCP server is not registered for this configuration. Common with several Claude config directories (`CLAUDE_CONFIG_DIR`, account switchers): run `CLAUDE_CONFIG_DIR=<that dir> n2n-nexus install claude`, then start a new conversation. |
42
42
  | "This call carries no agent session" | The conversation started before the hooks were installed (start a new one), the MCP server is not named `n2n-nexus`, or an identity expired (call the tool again). In a Claude desktop chat this is expected, and in a Code tab it means the desktop app bridges n2n-nexus from `claude_desktop_config.json`: remove it there (section 2). |
43
43
  | `claude -p` scripts | They are not held open by the background wait; nothing to do. |
@@ -97,7 +97,7 @@ Client (`n2n-nexus`):
97
97
  | `NEXUS_ENDPOINT` | Host URL for the MCP adapter, the hooks and `doctor`, or `discover` to find one on the LAN | `http://127.0.0.1:5688` |
98
98
  | `NEXUS_DISCOVERY_PORT` | UDP port the client queries with `discover` | `5688` |
99
99
  | `NEXUS_INSTANCE_ID` | Override the MCP instance ID (identifies chat and meeting calls from clients without hooks) | `mcp-<hostname>-<hash of working directory>-<IDE name>` |
100
- | `NEXUS_CODEX_COMMAND` | Command the Codex background wait runs as `<command> queue ...` | `codex` |
100
+ | `NEXUS_CODEX_COMMAND` | Codex command that the background wait runs as `<command> queue ...`, and that `install codex` and `doctor` ask about hook trust | `codex` (for trust on Windows: the Codex app's own copy) |
101
101
 
102
102
  Host (`n2n-nexus-host`): only `NEXUS_ROOT`, the data directory (`--root` overrides it). Every other host setting is in `settings.json`, which the tray, the service and the command line share.
103
103
 
@@ -37,7 +37,7 @@ Claude 桌面版里有两种对话:
37
37
  | 现象 | 原因和解决办法 |
38
38
  |---|---|
39
39
  | 工具调用提示 n2n-nexus-host 不可用 | host 没运行或连不上:启动它([安装手册第 2 步](INSTALL_zh.md#2-启动-host));如果在另一台机器上,打开局域网访问([第 6 步](INSTALL_zh.md#6-多台机器和多个环境))。 |
40
- | `peers` 里一直看不到 Codex 会话 | Codex 的 hooks 还没被信任([安装手册第 4 步](INSTALL_zh.md#4-启用-codex-的-hooks只需一次必须做))。 |
40
+ | `peers` 里一直看不到 Codex 会话 | Codex 的 hooks 没有被信任(还没信任,或者 Codex 升级后需要重新信任):`n2n-nexus doctor codex` 会报告;重新运行 `n2n-nexus install codex`([安装手册第 4 步](INSTALL_zh.md#4-启用-codex-的-hooks只需一次必须做))。 |
41
41
  | 对话能收到消息通知,却没有 `read` / `reply` 工具 | hooks 装了,但这个配置下没注册 MCP 服务器。多个 Claude 配置目录(`CLAUDE_CONFIG_DIR`、账号切换工具)时常见:运行 `CLAUDE_CONFIG_DIR=<那个目录> n2n-nexus install claude`,再新开对话。 |
42
42
  | 提示"This call carries no agent session" | 对话是在安装 hooks 之前开的(新开一个)、MCP 服务器名不是 `n2n-nexus`,或者身份过期(再调用一次)。在 Claude 桌面版的聊天里这是正常的;在 Code 标签页里出现,说明桌面版把 `claude_desktop_config.json` 里的 n2n-nexus 桥接了过来:把它从那里删掉(第 2 节)。 |
43
43
  | `claude -p` 脚本 | 不会被后台等待拖住,无需处理。 |
@@ -97,7 +97,7 @@ n2n-nexus hook <claude|codex> [--endpoint <url>] [--wait]
97
97
  | `NEXUS_ENDPOINT` | MCP 适配器、hooks 和 `doctor` 连接的 host 地址,设为 `discover` 时在局域网里查找 | `http://127.0.0.1:5688` |
98
98
  | `NEXUS_DISCOVERY_PORT` | 客户端用 `discover` 查询时使用的 UDP 端口 | `5688` |
99
99
  | `NEXUS_INSTANCE_ID` | 覆盖 MCP 实例 ID(没装 hooks 的客户端调用聊天和会议工具时的身份) | `mcp-<主机名>-<工作目录哈希>-<IDE 名>` |
100
- | `NEXUS_CODEX_COMMAND` | Codex 后台等待以 `<命令> queue ...` 方式运行的命令 | `codex` |
100
+ | `NEXUS_CODEX_COMMAND` | Codex 后台等待以 `<命令> queue ...` 方式运行的命令,`install codex` 和 `doctor` 也用它查询 hook 信任 | `codex`(Windows 上查询信任时默认用 Codex 桌面应用自带的那份) |
101
101
 
102
102
  host(`n2n-nexus-host`):只读取 `NEXUS_ROOT`,即数据目录(`--root` 优先)。其余 host 设置都在 `settings.json` 里,托盘、服务和命令行共用。
103
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n2n-nexus",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "Local-first MCP coordination hub from N2NS Lab for multi-AI assistant collaboration.",
5
5
  "main": "build/index.js",
6
6
  "type": "module",