@minhspark/codex-mcp-bridge 1.10.0 → 1.10.1

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
@@ -2,6 +2,13 @@
2
2
 
3
3
  Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](https://semver.org/).
4
4
 
5
+ ## [1.10.1] - 2026-08-20
6
+
7
+ ### Fixed
8
+
9
+ - **`claude_bridge_status` advertised `claude-bridge 1.3.0` inside a 1.10.0 package.** Seven releases of drift, and the only thing it could tell a bug reporter was a wrong answer about which build was running. The stale constant was the symptom: `scripts/sync-version.mjs` named `src/index.mjs` and the repo-hygiene test checked that same single file, so nothing in the release path ever looked at the second entry point. The sync script now walks both, the `version` lifecycle stages both, and the test **discovers** every `src/*.mjs` declaring `const VERSION` rather than naming one - a third bridge added later is covered without editing any of this again. Confirmed red on the previous tree: `src/claude-bridge.mjs declares 1.3.0, package.json says 1.10.0`.
10
+ - README said Windows was supported but not covered by CI, while `windows-latest` has been in the matrix since 1.10.0. A README that undersells its own coverage reads as a warning, and it is the first thing anyone evaluating the bridge sees. What stays true is narrower and is now what it says: the Codex to Claude tests skip on Windows because they need unix sockets, and the rest of the suite runs there.
11
+
5
12
  ## [1.10.0] - 2026-08-20
6
13
 
7
14
  ### Added
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Bui Dang Minh
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bui Dang Minh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # codex-mcp-bridge
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@minhspark/codex-mcp-bridge?logo=npm&color=CB3837)](https://www.npmjs.com/package/@minhspark/codex-mcp-bridge)
4
+ [![CI](https://github.com/buidangminh23/codex-mcp-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/buidangminh23/codex-mcp-bridge/actions/workflows/ci.yml)
5
+ [![node](https://img.shields.io/node/v/@minhspark/codex-mcp-bridge?logo=node.js&color=5FA04E)](https://nodejs.org)
6
+ [![license](https://img.shields.io/npm/l/@minhspark/codex-mcp-bridge)](LICENSE)
3
7
  [![M8ven Verified](https://m8ven.ai/badge/mcp/buidangminh23-codex-mcp-bridge-1ke8t1?variant=verified)](https://m8ven.ai/mcp/buidangminh23-codex-mcp-bridge-1ke8t1)
4
8
 
5
9
  A **two-way** bridge between Claude and Codex: Claude pushes prompts into a **live Codex thread**, and Codex messages back into a **running Claude Code session**. Each side sees the other's sessions and follows the conversation inside its own app. Runs on **macOS, Windows and Linux** (the Codex → Claude direction needs unix sockets, so macOS/Linux only).
@@ -415,7 +419,7 @@ Runs the whole suite with `node --test`. It needs no Codex install, no login and
415
419
  | `test/platform.test.mjs` | binary resolution, the PATH handed to child processes, per-OS config paths and cwd remapping |
416
420
  | `test/repo-hygiene.test.mjs` | no environment file or build output is ever tracked, versions do not drift, documentation stays in English |
417
421
 
418
- GitHub Actions runs the same command on every push and pull request, across Node 22 and 24 on Linux and macOS (`.github/workflows/ci.yml`). Windows is supported by the bridge but not covered by CI: the Codex → Claude direction needs unix sockets, so those tests skip there anyway.
422
+ GitHub Actions runs the same command on every push and pull request, across Node 22 and 24 on Linux, macOS and Windows (`.github/workflows/ci.yml`). The Codex → Claude direction needs unix sockets, so those tests skip on Windows; the rest of the suite runs there like anywhere else.
419
423
 
420
424
  Two checks need a real Codex and are not part of `npm test`:
421
425
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhspark/codex-mcp-bridge",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "Two-way MCP bridge between Claude and Codex: prompts into a live Codex thread, messages into a running Claude Code session.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -50,7 +50,7 @@
50
50
  "uninstall:codex": "node scripts/install-codex-mcp.mjs --remove",
51
51
  "install:agent": "node scripts/install-launch-agent.mjs",
52
52
  "uninstall:agent": "node scripts/install-launch-agent.mjs --uninstall",
53
- "version": "node scripts/sync-version.mjs && git add src/index.mjs"
53
+ "version": "node scripts/sync-version.mjs && git add src/index.mjs src/claude-bridge.mjs"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=22"
@@ -1,37 +1,37 @@
1
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
-
6
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
- const transport = new StdioClientTransport({
8
- command: process.execPath,
9
- args: [path.join(root, "src", "claude-bridge.mjs")],
10
- stderr: "inherit",
11
- });
12
- const client = new Client({ name: "claude-bridge-check", version: "1.0.0" });
13
- await client.connect(transport);
14
-
15
- const tools = await client.listTools();
16
- console.log("tools:", tools.tools.map((t) => t.name).join(", "));
17
-
18
- const status = await client.callTool({ name: "claude_bridge_status", arguments: {} });
19
- console.log("\n--- claude_bridge_status ---\n" + status.content[0].text);
20
-
21
- const listed = await client.callTool({ name: "list_claude_sessions", arguments: {} });
22
- console.log("\n--- list_claude_sessions ---\n" + listed.content[0].text);
23
-
24
- const target = process.env.CLAUDE_TARGET;
25
- if (target) {
26
- const message = process.env.CLAUDE_MESSAGE ?? "Ping from the Codex side of the bridge.";
27
- const waitSec = Number(process.env.CLAUDE_WAIT ?? 180);
28
- console.log(`\nsending to "${target}" (waiting ${waitSec}s)...`);
29
- const sent = await client.callTool({
30
- name: "send_to_claude_session",
31
- arguments: { target, message, waitSec },
32
- });
33
- console.log("\n--- send_to_claude_session ---\n" + sent.content[0].text);
34
- }
35
-
36
- await client.close();
37
- process.exit(0);
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
+ const transport = new StdioClientTransport({
8
+ command: process.execPath,
9
+ args: [path.join(root, "src", "claude-bridge.mjs")],
10
+ stderr: "inherit",
11
+ });
12
+ const client = new Client({ name: "claude-bridge-check", version: "1.0.0" });
13
+ await client.connect(transport);
14
+
15
+ const tools = await client.listTools();
16
+ console.log("tools:", tools.tools.map((t) => t.name).join(", "));
17
+
18
+ const status = await client.callTool({ name: "claude_bridge_status", arguments: {} });
19
+ console.log("\n--- claude_bridge_status ---\n" + status.content[0].text);
20
+
21
+ const listed = await client.callTool({ name: "list_claude_sessions", arguments: {} });
22
+ console.log("\n--- list_claude_sessions ---\n" + listed.content[0].text);
23
+
24
+ const target = process.env.CLAUDE_TARGET;
25
+ if (target) {
26
+ const message = process.env.CLAUDE_MESSAGE ?? "Ping from the Codex side of the bridge.";
27
+ const waitSec = Number(process.env.CLAUDE_WAIT ?? 180);
28
+ console.log(`\nsending to "${target}" (waiting ${waitSec}s)...`);
29
+ const sent = await client.callTool({
30
+ name: "send_to_claude_session",
31
+ arguments: { target, message, waitSec },
32
+ });
33
+ console.log("\n--- send_to_claude_session ---\n" + sent.content[0].text);
34
+ }
35
+
36
+ await client.close();
37
+ process.exit(0);
@@ -1,93 +1,93 @@
1
- import { execFileSync } from "node:child_process";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
-
6
- import { IS_MACOS, LAUNCH_AGENT_LABEL, launchAgentPath, resolveCodexBin, spawnEnv } from "../src/platform.mjs";
7
-
8
- if (!IS_MACOS) {
9
- throw new Error("install-launch-agent.mjs is macOS-only. On Windows use a Startup shortcut or a scheduled task.");
10
- }
11
-
12
- const plistPath = launchAgentPath();
13
- const uninstall = process.argv.includes("--uninstall");
14
- const domain = `gui/${process.getuid()}`;
15
-
16
- function bootout() {
17
- try {
18
- execFileSync("launchctl", ["bootout", `${domain}/${LAUNCH_AGENT_LABEL}`], { stdio: "pipe" });
19
- return true;
20
- } catch {
21
- return false;
22
- }
23
- }
24
-
25
- if (uninstall) {
26
- const wasLoaded = bootout();
27
- if (fs.existsSync(plistPath)) fs.rmSync(plistPath);
28
- console.log(`removed ${plistPath}${wasLoaded ? " (agent unloaded)" : ""}`);
29
- process.exit(0);
30
- }
31
-
32
- const codexBin = resolveCodexBin(process.env.CODEX_EXE);
33
- if (!path.isAbsolute(codexBin) || !fs.existsSync(codexBin)) {
34
- throw new Error(`codex binary not found (resolved to "${codexBin}"). Set CODEX_EXE to its absolute path.`);
35
- }
36
-
37
- const url = process.env.CODEX_APP_SERVER_URL ?? "ws://127.0.0.1:8791";
38
- const logDir = path.join(os.homedir(), "Library", "Logs", "codex-mcp-bridge");
39
- fs.mkdirSync(logDir, { recursive: true });
40
- fs.mkdirSync(path.dirname(plistPath), { recursive: true });
41
-
42
- const escapeXml = (value) =>
43
- value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
44
-
45
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
46
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
47
- <plist version="1.0">
48
- <dict>
49
- <key>Label</key>
50
- <string>${LAUNCH_AGENT_LABEL}</string>
51
- <key>ProgramArguments</key>
52
- <array>
53
- <string>${escapeXml(codexBin)}</string>
54
- <string>app-server</string>
55
- <string>--listen</string>
56
- <string>${escapeXml(url)}</string>
57
- </array>
58
- <key>EnvironmentVariables</key>
59
- <dict>
60
- <key>PATH</key>
61
- <string>${escapeXml(spawnEnv().PATH)}</string>
62
- <key>HOME</key>
63
- <string>${escapeXml(os.homedir())}</string>
64
- </dict>
65
- <key>RunAtLoad</key>
66
- <true/>
67
- <key>KeepAlive</key>
68
- <dict>
69
- <key>SuccessfulExit</key>
70
- <false/>
71
- </dict>
72
- <key>ThrottleInterval</key>
73
- <integer>10</integer>
74
- <key>ProcessType</key>
75
- <string>Background</string>
76
- <key>StandardOutPath</key>
77
- <string>${escapeXml(path.join(logDir, "app-server.out.log"))}</string>
78
- <key>StandardErrorPath</key>
79
- <string>${escapeXml(path.join(logDir, "app-server.err.log"))}</string>
80
- </dict>
81
- </plist>
82
- `;
83
-
84
- fs.writeFileSync(plistPath, plist, "utf8");
85
- bootout();
86
- execFileSync("launchctl", ["bootstrap", domain, plistPath], { stdio: "inherit" });
87
- execFileSync("launchctl", ["enable", `${domain}/${LAUNCH_AGENT_LABEL}`], { stdio: "pipe" });
88
-
89
- console.log(`installed ${plistPath}`);
90
- console.log(`endpoint: ${url}`);
91
- console.log(`logs: ${logDir}`);
92
- console.log(`status: launchctl print ${domain}/${LAUNCH_AGENT_LABEL} | head -20`);
93
- console.log(`remove: node scripts/install-launch-agent.mjs --uninstall`);
1
+ import { execFileSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+
6
+ import { IS_MACOS, LAUNCH_AGENT_LABEL, launchAgentPath, resolveCodexBin, spawnEnv } from "../src/platform.mjs";
7
+
8
+ if (!IS_MACOS) {
9
+ throw new Error("install-launch-agent.mjs is macOS-only. On Windows use a Startup shortcut or a scheduled task.");
10
+ }
11
+
12
+ const plistPath = launchAgentPath();
13
+ const uninstall = process.argv.includes("--uninstall");
14
+ const domain = `gui/${process.getuid()}`;
15
+
16
+ function bootout() {
17
+ try {
18
+ execFileSync("launchctl", ["bootout", `${domain}/${LAUNCH_AGENT_LABEL}`], { stdio: "pipe" });
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ if (uninstall) {
26
+ const wasLoaded = bootout();
27
+ if (fs.existsSync(plistPath)) fs.rmSync(plistPath);
28
+ console.log(`removed ${plistPath}${wasLoaded ? " (agent unloaded)" : ""}`);
29
+ process.exit(0);
30
+ }
31
+
32
+ const codexBin = resolveCodexBin(process.env.CODEX_EXE);
33
+ if (!path.isAbsolute(codexBin) || !fs.existsSync(codexBin)) {
34
+ throw new Error(`codex binary not found (resolved to "${codexBin}"). Set CODEX_EXE to its absolute path.`);
35
+ }
36
+
37
+ const url = process.env.CODEX_APP_SERVER_URL ?? "ws://127.0.0.1:8791";
38
+ const logDir = path.join(os.homedir(), "Library", "Logs", "codex-mcp-bridge");
39
+ fs.mkdirSync(logDir, { recursive: true });
40
+ fs.mkdirSync(path.dirname(plistPath), { recursive: true });
41
+
42
+ const escapeXml = (value) =>
43
+ value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
44
+
45
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
46
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
47
+ <plist version="1.0">
48
+ <dict>
49
+ <key>Label</key>
50
+ <string>${LAUNCH_AGENT_LABEL}</string>
51
+ <key>ProgramArguments</key>
52
+ <array>
53
+ <string>${escapeXml(codexBin)}</string>
54
+ <string>app-server</string>
55
+ <string>--listen</string>
56
+ <string>${escapeXml(url)}</string>
57
+ </array>
58
+ <key>EnvironmentVariables</key>
59
+ <dict>
60
+ <key>PATH</key>
61
+ <string>${escapeXml(spawnEnv().PATH)}</string>
62
+ <key>HOME</key>
63
+ <string>${escapeXml(os.homedir())}</string>
64
+ </dict>
65
+ <key>RunAtLoad</key>
66
+ <true/>
67
+ <key>KeepAlive</key>
68
+ <dict>
69
+ <key>SuccessfulExit</key>
70
+ <false/>
71
+ </dict>
72
+ <key>ThrottleInterval</key>
73
+ <integer>10</integer>
74
+ <key>ProcessType</key>
75
+ <string>Background</string>
76
+ <key>StandardOutPath</key>
77
+ <string>${escapeXml(path.join(logDir, "app-server.out.log"))}</string>
78
+ <key>StandardErrorPath</key>
79
+ <string>${escapeXml(path.join(logDir, "app-server.err.log"))}</string>
80
+ </dict>
81
+ </plist>
82
+ `;
83
+
84
+ fs.writeFileSync(plistPath, plist, "utf8");
85
+ bootout();
86
+ execFileSync("launchctl", ["bootstrap", domain, plistPath], { stdio: "inherit" });
87
+ execFileSync("launchctl", ["enable", `${domain}/${LAUNCH_AGENT_LABEL}`], { stdio: "pipe" });
88
+
89
+ console.log(`installed ${plistPath}`);
90
+ console.log(`endpoint: ${url}`);
91
+ console.log(`logs: ${logDir}`);
92
+ console.log(`status: launchctl print ${domain}/${LAUNCH_AGENT_LABEL} | head -20`);
93
+ console.log(`remove: node scripts/install-launch-agent.mjs --uninstall`);
package/scripts/smoke.mjs CHANGED
@@ -1,50 +1,50 @@
1
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
- import { fileURLToPath } from "node:url";
4
- import path from "node:path";
5
-
6
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
- const cwdForTest = process.env.SMOKE_CWD ?? root;
8
-
9
- const transport = new StdioClientTransport({
10
- command: process.execPath,
11
- args: [path.join(root, "src", "index.mjs")],
12
- env: { ...process.env, CODEX_BRIDGE_ALLOWED_ROOTS: process.env.CODEX_BRIDGE_ALLOWED_ROOTS ?? cwdForTest },
13
- stderr: "inherit",
14
- });
15
- const client = new Client({ name: "smoke", version: "1.0.0" });
16
- await client.connect(transport);
17
-
18
- const tools = await client.listTools();
19
- console.log("TOOLS:", tools.tools.map((t) => t.name).join(", "));
20
-
21
- const listed = await client.callTool({ name: "list_codex_threads", arguments: { limit: 3 } });
22
- console.log("\n--- list_codex_threads ---\n" + listed.content[0].text.slice(0, 900));
23
-
24
- const created = await client.callTool({
25
- name: "start_codex_thread",
26
- arguments: { cwd: cwdForTest },
27
- });
28
- console.log("\n--- start_codex_thread ---\n" + created.content[0].text);
29
- const threadId = created.content[0].text.match(/threadId: ([0-9a-f-]+)/)?.[1];
30
- if (!threadId) throw new Error("could not parse threadId");
31
-
32
- const first = await client.callTool({
33
- name: "send_to_codex_thread",
34
- arguments: { threadId, prompt: "Remember the codeword PAPAYA. Reply with only: SAVED", timeoutSec: 180 },
35
- });
36
- console.log("\n--- send #1 ---\n" + first.content[0].text);
37
-
38
- const second = await client.callTool({
39
- name: "send_to_codex_thread",
40
- arguments: { threadId, prompt: "What codeword did I give you? Reply with only that word.", timeoutSec: 180 },
41
- });
42
- console.log("\n--- send #2 (same thread, must recall) ---\n" + second.content[0].text);
43
-
44
- const read = await client.callTool({ name: "read_codex_thread", arguments: { threadId, limit: 6 } });
45
- console.log("\n--- read_codex_thread ---\n" + read.content[0].text.slice(0, 900));
46
-
47
- const ok = /PAPAYA/i.test(second.content[0].text);
48
- console.log("\nRESULT:", ok ? "PASS - thread continuity works" : "FAIL - Codex did not recall the codeword");
49
- await client.close();
50
- process.exit(ok ? 0 : 1);
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import { fileURLToPath } from "node:url";
4
+ import path from "node:path";
5
+
6
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
+ const cwdForTest = process.env.SMOKE_CWD ?? root;
8
+
9
+ const transport = new StdioClientTransport({
10
+ command: process.execPath,
11
+ args: [path.join(root, "src", "index.mjs")],
12
+ env: { ...process.env, CODEX_BRIDGE_ALLOWED_ROOTS: process.env.CODEX_BRIDGE_ALLOWED_ROOTS ?? cwdForTest },
13
+ stderr: "inherit",
14
+ });
15
+ const client = new Client({ name: "smoke", version: "1.0.0" });
16
+ await client.connect(transport);
17
+
18
+ const tools = await client.listTools();
19
+ console.log("TOOLS:", tools.tools.map((t) => t.name).join(", "));
20
+
21
+ const listed = await client.callTool({ name: "list_codex_threads", arguments: { limit: 3 } });
22
+ console.log("\n--- list_codex_threads ---\n" + listed.content[0].text.slice(0, 900));
23
+
24
+ const created = await client.callTool({
25
+ name: "start_codex_thread",
26
+ arguments: { cwd: cwdForTest },
27
+ });
28
+ console.log("\n--- start_codex_thread ---\n" + created.content[0].text);
29
+ const threadId = created.content[0].text.match(/threadId: ([0-9a-f-]+)/)?.[1];
30
+ if (!threadId) throw new Error("could not parse threadId");
31
+
32
+ const first = await client.callTool({
33
+ name: "send_to_codex_thread",
34
+ arguments: { threadId, prompt: "Remember the codeword PAPAYA. Reply with only: SAVED", timeoutSec: 180 },
35
+ });
36
+ console.log("\n--- send #1 ---\n" + first.content[0].text);
37
+
38
+ const second = await client.callTool({
39
+ name: "send_to_codex_thread",
40
+ arguments: { threadId, prompt: "What codeword did I give you? Reply with only that word.", timeoutSec: 180 },
41
+ });
42
+ console.log("\n--- send #2 (same thread, must recall) ---\n" + second.content[0].text);
43
+
44
+ const read = await client.callTool({ name: "read_codex_thread", arguments: { threadId, limit: 6 } });
45
+ console.log("\n--- read_codex_thread ---\n" + read.content[0].text.slice(0, 900));
46
+
47
+ const ok = /PAPAYA/i.test(second.content[0].text);
48
+ console.log("\nRESULT:", ok ? "PASS - thread continuity works" : "FAIL - Codex did not recall the codeword");
49
+ await client.close();
50
+ process.exit(ok ? 0 : 1);
@@ -4,22 +4,29 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  /**
7
- * `codex_bridge_status` reports a version constant that lives in the source,
8
- * and `npm version` only ever rewrites package.json. Left to a human the two
7
+ * Both status tools report a version constant that lives in the source, and
8
+ * `npm version` only ever rewrites package.json. Left to a human the two
9
9
  * drift, and a drifting version turns every bug report into a guess about
10
10
  * which build is actually running - so this runs from the `version` lifecycle
11
11
  * script, between the bump and the commit npm makes for it.
12
+ *
13
+ * Every entry point carrying its own constant belongs in this list. One that
14
+ * is missing here is not bumped by any release, which is exactly how
15
+ * claude-bridge sat at 1.3.0 while the package shipped 1.10.0.
12
16
  */
13
17
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
- const entry = path.join(root, "src", "index.mjs");
18
+ const entries = [path.join("src", "index.mjs"), path.join("src", "claude-bridge.mjs")];
15
19
  const { version } = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
16
20
 
17
- const before = fs.readFileSync(entry, "utf8");
18
- const after = before.replace(/^const VERSION = "[^"]+";$/m, `const VERSION = "${version}";`);
21
+ for (const entry of entries) {
22
+ const file = path.join(root, entry);
23
+ const before = fs.readFileSync(file, "utf8");
24
+ const after = before.replace(/^const VERSION = "[^"]+";$/m, `const VERSION = "${version}";`);
19
25
 
20
- if (!after.includes(`const VERSION = "${version}";`)) {
21
- throw new Error(`could not find the VERSION constant in ${entry}`);
22
- }
26
+ if (!after.includes(`const VERSION = "${version}";`)) {
27
+ throw new Error(`could not find the VERSION constant in ${entry}`);
28
+ }
23
29
 
24
- if (before !== after) fs.writeFileSync(entry, after);
25
- console.log(`src/index.mjs VERSION -> ${version}`);
30
+ if (before !== after) fs.writeFileSync(file, after);
31
+ console.log(`${entry.split(path.sep).join("/")} VERSION -> ${version}`);
32
+ }