@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 +7 -0
- package/LICENSE +21 -21
- package/README.md +5 -1
- package/package.json +2 -2
- package/scripts/check-claude-bridge.mjs +37 -37
- package/scripts/install-launch-agent.mjs +93 -93
- package/scripts/smoke.mjs +50 -50
- package/scripts/sync-version.mjs +17 -10
- package/src/app-server-client.mjs +414 -414
- package/src/claude-bridge.mjs +322 -322
- package/src/index.mjs +1 -1
- package/src/security-policy.mjs +149 -149
- package/src/turn.mjs +140 -140
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
|
+
[](https://www.npmjs.com/package/@minhspark/codex-mcp-bridge)
|
|
4
|
+
[](https://github.com/buidangminh23/codex-mcp-bridge/actions/workflows/ci.yml)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](LICENSE)
|
|
3
7
|
[](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
|
|
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.
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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);
|
package/scripts/sync-version.mjs
CHANGED
|
@@ -4,22 +4,29 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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
|
|
18
|
-
const
|
|
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
|
-
|
|
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(
|
|
25
|
-
console.log(
|
|
30
|
+
if (before !== after) fs.writeFileSync(file, after);
|
|
31
|
+
console.log(`${entry.split(path.sep).join("/")} VERSION -> ${version}`);
|
|
32
|
+
}
|