@minhspark/codex-mcp-bridge 1.24.0 → 1.25.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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +36 -0
- package/README.md +550 -28
- package/assets/README.md +2 -1
- package/assets/agent-pets.svg +47 -1
- package/docs/new-macos-setup.md +85 -37
- package/docs/new-windows-setup.md +101 -42
- package/docs/project-onboarding.md +64 -9
- package/package.json +5 -2
- package/plugin.json +1 -1
- package/scripts/install-antigravity-bridge.mjs +31 -17
- package/scripts/install-claude-code.mjs +58 -43
- package/scripts/install-claude-desktop.mjs +60 -32
- package/scripts/install-codex-mcp.mjs +1 -1
- package/scripts/install-native-relay.mjs +3 -2
- package/scripts/setup.mjs +14 -0
- package/src/app-server-client.mjs +2 -1
- package/src/claude-bridge.mjs +18 -11
- package/src/claude-code-registration.mjs +94 -0
- package/src/claude-sender-context.mjs +22 -2
- package/src/codex-bridge-launcher.mjs +8 -1
- package/src/codex-mcp-registration.mjs +1 -0
- package/src/codex-native-response.mjs +6 -2
- package/src/desktop-account-context.mjs +18 -0
- package/src/desktop-read-tools.mjs +3 -2
- package/src/index.mjs +5 -3
- package/src/native-relay-companion.mjs +1 -1
- package/src/peer-protocol.mjs +23 -5
- package/src/platform.mjs +57 -5
- package/src/setup.mjs +204 -0
- package/src/thread-delivery.mjs +35 -1
|
@@ -1,70 +1,129 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Windows setup and initial permissions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
extension is distributed separately. Keep the source in a stable directory.
|
|
3
|
+
Install Node.js 22.13+ and sign in to the apps you intend to use. For native
|
|
4
|
+
handoffs, save the local project in Codex Desktop and open the same directory
|
|
5
|
+
in Claude Desktop's **Code tab**. Run one PowerShell command:
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
```powershell
|
|
8
|
+
npm.cmd install -g @minhspark/codex-mcp-bridge@latest --allow-scripts=@minhspark/codex-mcp-bridge
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The `--allow-scripts` value permits only this package's setup hook. npm versions
|
|
12
|
+
that block install scripts by default need it; a plain install runs setup only
|
|
13
|
+
when scripts are allowed. Add `--foreground-scripts` to see setup progress. If
|
|
14
|
+
npm reports `1 package had install scripts blocked`, run the recommended command
|
|
15
|
+
again or run `codex-bridge-setup.cmd` explicitly.
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
The global installation configures the bridges and verifies their saved
|
|
18
|
+
registrations. It reuses an available native Codex CLI, or installs a pinned
|
|
19
|
+
official package in the current user's `.config\GptClaudeBridge\tools`
|
|
20
|
+
directory. It also configures native pipe forwarding and bootstraps the relay
|
|
21
|
+
executor. A failed component is reported as incomplete rather than ready. The
|
|
22
|
+
package stays installed when an app configuration step fails, so the setup
|
|
23
|
+
command remains available for repair. npm success alone does not verify setup.
|
|
24
|
+
|
|
25
|
+
## Installed entries
|
|
14
26
|
|
|
15
27
|
| Client | Entry | Purpose |
|
|
16
28
|
| --- | --- | --- |
|
|
17
29
|
| Codex | `claude-bridge` | Send to Claude and observe replies |
|
|
18
30
|
| Codex | `codex-native-relay` | Deliver Claude requests through the Desktop host |
|
|
19
31
|
| Claude Code, user scope | `codex-bridge` | Send to Codex and continue reply observation |
|
|
32
|
+
| Claude Desktop shared connectors | `codex-bridge-desktop` | Connect ordinary chat to local Codex app-server sessions |
|
|
33
|
+
| Codex and Claude Code | `antigravity-bridge` | Send to Antigravity App or bridge-owned CLI sessions |
|
|
34
|
+
| Antigravity | `agent-bridge` and `codex-bridge` sidecar | Outbound agent messages and native App connectivity |
|
|
35
|
+
|
|
36
|
+
The shared `codex-bridge-desktop` connector does not establish a native Code
|
|
37
|
+
sender identity. Use the actual Code session for native Codex Desktop work.
|
|
38
|
+
Existing project restrictions, permission modes, and custom settings are
|
|
39
|
+
preserved. Setup does not sign in, grant workspace trust, or close applications.
|
|
20
40
|
|
|
21
|
-
|
|
41
|
+
After active work finishes, open or reconnect the affected apps so they load
|
|
42
|
+
the entries. A conversation refresh may leave the previous MCP process loaded.
|
|
43
|
+
Check `codex_bridge_status` inside the sending Claude Code task,
|
|
44
|
+
`claude_bridge_status` and `native_relay_status` in Codex, and
|
|
45
|
+
`agent_bridge_status` in Antigravity. Configuration verification is separate
|
|
46
|
+
from a completed live exchange.
|
|
47
|
+
|
|
48
|
+
## Checks and recovery
|
|
22
49
|
|
|
23
50
|
```powershell
|
|
24
|
-
|
|
51
|
+
codex-bridge-setup.cmd --check
|
|
52
|
+
codex-bridge-setup.cmd --dry-run
|
|
53
|
+
codex-bridge-setup.cmd
|
|
25
54
|
```
|
|
26
55
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
56
|
+
The first command inspects configuration; the second previews components and
|
|
57
|
+
paths without writes or downloads; the third applies setup again. Add `--json`
|
|
58
|
+
for structured output or `--no-download` to require an existing Codex CLI.
|
|
59
|
+
Explicit setup and `--check` return a nonzero exit status if configuration is
|
|
60
|
+
incomplete.
|
|
61
|
+
`codex-mcp-bridge.cmd setup` is an equivalent setup entry point. If Claude Code
|
|
62
|
+
has no bridge tools, inspect and reconnect `codex-bridge` using `/mcp` before
|
|
63
|
+
attempting delivery.
|
|
64
|
+
|
|
65
|
+
Local `npm install` and `npm ci` do not configure apps. To opt out during a
|
|
66
|
+
global install, set `CODEX_BRIDGE_SKIP_SETUP=1` or use `--ignore-scripts`.
|
|
67
|
+
The optional card extension, hosted ChatGPT pairing, and VS Code preview have
|
|
68
|
+
separate installation steps.
|
|
69
|
+
|
|
70
|
+
## If startup still fails
|
|
71
|
+
|
|
72
|
+
Check the version reported by `codex_bridge_status` in the actual Claude Code
|
|
73
|
+
session against the installed package:
|
|
74
|
+
|
|
75
|
+
```powershell
|
|
76
|
+
npm.cmd list -g @minhspark/codex-mcp-bridge --depth=0
|
|
49
77
|
```
|
|
50
78
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
Also inspect the session's MCP command and script path. A source checkout can
|
|
80
|
+
keep an older version running even after npm updates the global package. If
|
|
81
|
+
the tool is unavailable, the startup log and configured path identify which
|
|
82
|
+
installation Claude tried to load. Rerun setup and reconnect the affected
|
|
83
|
+
client after active work finishes before comparing another result.
|
|
84
|
+
|
|
85
|
+
Setup prepares runtime snapshots in advance. However, a startup message such
|
|
86
|
+
as `prepared ... (reused)` means only that the runtime directory already
|
|
87
|
+
existed; missing or invalid integrity metadata can still require verification
|
|
88
|
+
work. If a 30-second connection timeout persists after the intended global
|
|
89
|
+
installation is loaded, collect the runtime version, startup log and exact
|
|
90
|
+
timeout. Configuration repair and a reused directory do not prove that the
|
|
91
|
+
reported startup delay has been resolved.
|
|
54
92
|
|
|
55
|
-
|
|
93
|
+
Claude may label ordinary startup diagnostics as `Server stderr`, including
|
|
94
|
+
`preparing`, `prepared`, and `ready` lines. Check the real connection status and
|
|
95
|
+
error rather than treating that label alone as failure. Keep diagnostics off
|
|
96
|
+
stdout: MCP uses stdout for protocol messages.
|
|
97
|
+
|
|
98
|
+
## Optional source installation and project policy
|
|
99
|
+
|
|
100
|
+
For a pinned source checkout in a stable directory, `node scripts/setup.mjs`
|
|
101
|
+
runs the same setup. The individual installers remain available for deliberate
|
|
102
|
+
manual configuration. For example, with an available Codex CLI and an explicitly
|
|
103
|
+
selected test project:
|
|
56
104
|
|
|
57
105
|
```powershell
|
|
106
|
+
node scripts/bridge-projects.mjs allow-project 'C:\Projects\BridgeTest'
|
|
107
|
+
$env:CODEX_BIN = (Get-Command codex.exe).Source
|
|
108
|
+
$env:CODEX_EXE = $env:CODEX_BIN
|
|
58
109
|
$env:CODEX_BRIDGE_PROJECT_POLICY = Join-Path $env:USERPROFILE '.config\GptClaudeBridge\projects.json'
|
|
59
110
|
$env:CODEX_BRIDGE_DESKTOP_TASKS = '1'
|
|
111
|
+
$env:CODEX_BRIDGE_AUTOSTART = '0'
|
|
112
|
+
node scripts/install-claude-code.mjs --install
|
|
60
113
|
node scripts/install-native-relay.mjs
|
|
61
114
|
node scripts/install-codex-mcp.mjs
|
|
62
115
|
```
|
|
63
116
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
117
|
+
`--install` creates a missing Code entry or updates a recognized one;
|
|
118
|
+
`--install --dry-run` previews it. For an existing entry with a different scope,
|
|
119
|
+
use the explicit policy migration in [project onboarding](project-onboarding.md).
|
|
120
|
+
`--check` on that helper checks the optional shared policy arrangement; it is
|
|
121
|
+
not the general setup check. Keep CC Switch or another configuration manager
|
|
122
|
+
consistent with the saved entries so it does not restore an obsolete copy.
|
|
123
|
+
|
|
124
|
+
Do not copy login caches, session identities, unfinished receipts or
|
|
125
|
+
directory-identity grants from another machine. This guide is not evidence
|
|
126
|
+
that a particular second machine or client build has passed live verification.
|
|
68
127
|
|
|
69
128
|
## Permissions are separate
|
|
70
129
|
|
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Project onboarding and shared authorization
|
|
2
2
|
|
|
3
|
+
The recommended global installation registers the bridges automatically:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install -g @minhspark/codex-mcp-bridge@latest --allow-scripts=@minhspark/codex-mcp-bridge
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
In Windows PowerShell use `npm.cmd`. The script allowance authorizes only this
|
|
10
|
+
package's setup hook; npm versions that block install scripts by default need
|
|
11
|
+
it. A plain install runs setup only when the hook is allowed. Append
|
|
12
|
+
`--foreground-scripts` to see progress. If npm prints
|
|
13
|
+
`1 package had install scripts blocked`, rerun the recommended command or run
|
|
14
|
+
`codex-bridge-setup` explicitly. The setup creates
|
|
15
|
+
`codex-bridge` for Claude Code and the separate `codex-bridge-desktop` shared
|
|
16
|
+
Claude connector, configures Codex and Antigravity, and initializes the native
|
|
17
|
+
relay on Windows/macOS. See [installation](../README.md#installation).
|
|
18
|
+
|
|
19
|
+
Setup preserves existing scope and permission choices. Registration does not
|
|
20
|
+
grant workspace trust, authorize cross-project work, or establish a live
|
|
21
|
+
connection. The shared Desktop connector uses local app-server sessions;
|
|
22
|
+
native Codex Desktop handoffs require a verified Claude Code sender in the
|
|
23
|
+
same saved project.
|
|
24
|
+
|
|
25
|
+
`codex-bridge-setup --check` inspects all saved registrations. Use
|
|
26
|
+
`codex-bridge-setup --dry-run` to preview or rerun `codex-bridge-setup` to apply
|
|
27
|
+
setup after correcting an incomplete component. Add `.cmd` on Windows and
|
|
28
|
+
`--json` for structured output. Local package installs do not run setup;
|
|
29
|
+
`CODEX_BRIDGE_SKIP_SETUP=1` and npm `--ignore-scripts` opt out of global setup.
|
|
30
|
+
If app configuration is incomplete, npm retains the package for repair; its
|
|
31
|
+
success exit alone does not verify setup. Explicit setup and `--check` return
|
|
32
|
+
a nonzero status until the saved configuration passes verification.
|
|
33
|
+
|
|
34
|
+
## Optional shared project policy
|
|
35
|
+
|
|
3
36
|
Both bridge directions can read the same optional policy file via
|
|
4
37
|
`CODEX_BRIDGE_PROJECT_POLICY`. `bridge-projects` defaults to
|
|
5
38
|
`~/.config/GptClaudeBridge/projects.json`. A shared home-directory path avoids
|
|
@@ -48,20 +81,42 @@ node scripts/bridge-projects.mjs prepare /absolute/projects/example
|
|
|
48
81
|
|
|
49
82
|
Changes are backed up and verified. Concurrent changes abort; failed rollback
|
|
50
83
|
returns `partial_needs_review` and remaining files. `configured` does not mean
|
|
51
|
-
live communication passed.
|
|
52
|
-
create conversations or restart clients.
|
|
84
|
+
live communication passed. Project preparation does not approve tools, change
|
|
85
|
+
session modes, create conversations or restart clients. The package installer
|
|
86
|
+
separately initializes an internal relay executor when needed. Workspace trust
|
|
87
|
+
follows Claude's
|
|
53
88
|
[documented project settings](https://code.claude.com/docs/en/permissions#project-allow-rules-and-workspace-trust).
|
|
54
89
|
|
|
55
90
|
## Correct Code registration
|
|
56
91
|
|
|
57
92
|
Claude's user-level `codex-bridge` must run under the actual Code session. A
|
|
58
|
-
generic shared Desktop process cannot establish that caller's ancestry.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
generic shared Desktop process cannot establish that caller's ancestry.
|
|
94
|
+
Automatic setup creates or updates the supervised Code entry and safely
|
|
95
|
+
renames a recognized shared entry to `codex-bridge-desktop`. Conflicting or
|
|
96
|
+
unrecognized entries are reported without overwriting them; custom project
|
|
97
|
+
overrides are preserved and reported.
|
|
98
|
+
|
|
99
|
+
For selective installation, `codex-bridge-code-install --install` creates a
|
|
100
|
+
missing Code entry or updates a recognized one. `--install --dry-run` previews
|
|
101
|
+
the change. The complete `codex-bridge-setup` command also handles the other
|
|
102
|
+
registrations and prerequisites. Neither path copies permissions or login
|
|
103
|
+
credentials from another client.
|
|
104
|
+
|
|
105
|
+
The helper's existing policy migration remains an explicit operation and
|
|
106
|
+
previews changes by default. After initializing the desired policy and stopping
|
|
107
|
+
Claude and any configuration manager, run:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
codex-bridge-code-install --policy /absolute/path/projects.json
|
|
111
|
+
codex-bridge-code-install --policy /absolute/path/projects.json --apply --clients-stopped
|
|
112
|
+
codex-bridge-code-install --check
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Migration backs up and updates an existing supervised entry. This helper's
|
|
116
|
+
`--check` diagnoses the optional shared-policy arrangement, including shared or
|
|
117
|
+
duplicate entries; use `codex-bridge-setup --check` for the general installation
|
|
118
|
+
check. Keep CC Switch or another external configuration manager consistent
|
|
119
|
+
with the selected entry so it does not restore an obsolete copy later.
|
|
65
120
|
|
|
66
121
|
Reload the affected client after changing MCP registration. Merely refreshing
|
|
67
122
|
the conversation did not reload the Code process in the measured Windows
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minhspark/codex-mcp-bridge",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.25.0",
|
|
4
|
+
"description": "Connect Claude, Codex, and Antigravity through MCP, with native Desktop handoffs and bridge-owned Antigravity CLI sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
7
7
|
"codex",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"antigravity-mcp-bridge-install": "scripts/install-antigravity-bridge.mjs",
|
|
30
30
|
"codex-mcp-bridge-install": "scripts/install-claude-desktop.mjs",
|
|
31
31
|
"codex-bridge-code-install": "scripts/install-claude-code.mjs",
|
|
32
|
+
"codex-bridge-setup": "scripts/setup.mjs",
|
|
32
33
|
"bridge-projects": "scripts/bridge-projects.mjs",
|
|
33
34
|
"claude-mcp-bridge-install": "scripts/install-codex-mcp.mjs",
|
|
34
35
|
"codex-native-relay": "src/native-relay-launcher.mjs",
|
|
@@ -61,6 +62,8 @@
|
|
|
61
62
|
"access": "public"
|
|
62
63
|
},
|
|
63
64
|
"scripts": {
|
|
65
|
+
"postinstall": "node scripts/setup.mjs --postinstall",
|
|
66
|
+
"setup": "node scripts/setup.mjs",
|
|
64
67
|
"analytics": "node scripts/collect-repo-analytics.mjs",
|
|
65
68
|
"analytics:publish": "node scripts/publish-repo-analytics.mjs",
|
|
66
69
|
"start": "node src/codex-bridge-launcher.mjs",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "codex-mcp-bridge",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.25.0",
|
|
5
5
|
"description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bui Dang Minh",
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import { fileURLToPath } from "node:url";
|
|
7
6
|
import { applyStdioMcpRegistration, stdioMcpRegistration } from "../src/codex-mcp-registration.mjs";
|
|
8
7
|
import { isMainModule, stableInstallPath, stableNodeBin } from "../src/install-paths.mjs";
|
|
9
|
-
import { resolveCodexBin, spawnEnv } from "../src/platform.mjs";
|
|
8
|
+
import { homeDir, resolveCodexBin, spawnEnv } from "../src/platform.mjs";
|
|
10
9
|
import { exitForVersionRequest } from "../src/cli-version.mjs";
|
|
11
10
|
|
|
12
11
|
const NAME = "antigravity-bridge";
|
|
@@ -54,12 +53,24 @@ export function validateCodexExecutable(executable, { platform = process.platfor
|
|
|
54
53
|
return resolved;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
function ownEntry(entry, expected, label) {
|
|
56
|
+
function ownEntry(entry, expected, label, retarget = false) {
|
|
58
57
|
if (entry === undefined || entry === null) return;
|
|
59
58
|
object(entry, label);
|
|
60
|
-
|
|
59
|
+
const script = entry.args?.[0];
|
|
60
|
+
if (!Array.isArray(entry.args) || entry.args.length !== 1 || typeof script !== "string") {
|
|
61
61
|
throw new Error(`${label} is already configured with a different entrypoint; it was not changed`);
|
|
62
62
|
}
|
|
63
|
+
if (path.resolve(script) === path.resolve(expected)) return;
|
|
64
|
+
const source = path.dirname(script);
|
|
65
|
+
if (!retarget || !path.isAbsolute(script) || script.includes("\0") || path.basename(script) !== path.basename(expected)
|
|
66
|
+
|| path.basename(source) !== "src" || path.basename(path.dirname(source)).toLowerCase() !== "codex-mcp-bridge") {
|
|
67
|
+
throw new Error(`${label} is already configured with a different entrypoint; it was not changed`);
|
|
68
|
+
}
|
|
69
|
+
const command = entry.command;
|
|
70
|
+
if (typeof command !== "string" || command.includes("\0") || !/^(?:node|node\.exe)$/i.test(path.basename(command))
|
|
71
|
+
|| !path.isAbsolute(command) && command !== path.basename(command)) {
|
|
72
|
+
throw new Error(`${label} is already configured with a different launcher; it was not changed`);
|
|
73
|
+
}
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
function changed(original, value) {
|
|
@@ -68,28 +79,29 @@ function changed(original, value) {
|
|
|
68
79
|
return { ...original, next: text };
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
export function planAntigravityInstallation({ home =
|
|
72
|
-
if (!path.isAbsolute(home) || !path.isAbsolute(root) || !path.isAbsolute(node)) throw new Error("Installer paths must be absolute");
|
|
82
|
+
export function planAntigravityInstallation({ home = homeDir(), codeConfigDir = process.env.CLAUDE_CONFIG_DIR || home, root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."), node = stableNodeBin(), targets = ["codex", "claude", "desktop"], remove = false, setup = false, codexExisting = null } = {}) {
|
|
83
|
+
if (!path.isAbsolute(home) || !path.isAbsolute(codeConfigDir) || !path.isAbsolute(root) || !path.isAbsolute(node)) throw new Error("Installer paths must be absolute");
|
|
73
84
|
if (!targets.length || targets.some((target) => !["codex", "claude", "desktop"].includes(target))) throw new Error("Unknown installation target");
|
|
74
85
|
const entry = path.join(root, "src", "antigravity-bridge.mjs");
|
|
75
86
|
const sidecarEntry = path.join(root, "src", "antigravity-sidecar.mjs");
|
|
76
87
|
const outboundEntry = path.join(root, "src", "antigravity-outbound-bridge.mjs");
|
|
77
88
|
const edits = [];
|
|
78
89
|
const notices = [];
|
|
90
|
+
const retarget = setup && !remove;
|
|
79
91
|
let codex = null;
|
|
80
92
|
if (targets.includes("codex")) {
|
|
81
93
|
if (codexExisting && codexExisting.transport?.type !== "stdio") throw new Error("The existing Antigravity MCP transport is not stdio");
|
|
82
|
-
ownEntry(codexExisting?.transport, entry, "Codex Antigravity bridge");
|
|
94
|
+
ownEntry(codexExisting?.transport, entry, "Codex Antigravity bridge", retarget);
|
|
83
95
|
const existingCaller = codexExisting?.transport?.env?.ANTIGRAVITY_BRIDGE_CALLER;
|
|
84
96
|
if (existingCaller !== undefined && existingCaller !== "codex") throw new Error("The existing Codex bridge has a conflicting caller identity");
|
|
85
97
|
codex = remove ? { remove: Boolean(codexExisting) } : stdioMcpRegistration({ name: NAME, existing: codexExisting, node, entry, envOverrides: { ANTIGRAVITY_BRIDGE_CALLER: "codex" } });
|
|
86
98
|
}
|
|
87
99
|
if (targets.includes("claude")) {
|
|
88
|
-
const original = snapshot(path.join(
|
|
100
|
+
const original = snapshot(path.join(codeConfigDir, ".claude.json"));
|
|
89
101
|
const value = structuredClone(original.value);
|
|
90
102
|
if (value.mcpServers !== undefined) object(value.mcpServers, "Claude mcpServers");
|
|
91
103
|
const existing = value.mcpServers?.[NAME];
|
|
92
|
-
ownEntry(existing, entry, "Claude Antigravity bridge");
|
|
104
|
+
ownEntry(existing, entry, "Claude Antigravity bridge", retarget);
|
|
93
105
|
if (existing?.type !== undefined && existing.type !== "stdio") throw new Error("The existing Claude bridge transport is not stdio");
|
|
94
106
|
if (existing?.env !== undefined) object(existing.env, "Claude bridge environment");
|
|
95
107
|
if (existing?.env?.ANTIGRAVITY_BRIDGE_CALLER !== undefined && existing.env.ANTIGRAVITY_BRIDGE_CALLER !== "claude") throw new Error("The existing Claude bridge has a conflicting caller identity");
|
|
@@ -107,7 +119,7 @@ export function planAntigravityInstallation({ home = os.homedir(), root = path.r
|
|
|
107
119
|
const mcp = structuredClone(originalMcp.value);
|
|
108
120
|
if (mcp.mcpServers !== undefined) object(mcp.mcpServers, "Antigravity mcpServers");
|
|
109
121
|
const outbound = mcp.mcpServers?.[OUTBOUND];
|
|
110
|
-
ownEntry(outbound, outboundEntry, "Antigravity outbound bridge");
|
|
122
|
+
ownEntry(outbound, outboundEntry, "Antigravity outbound bridge", retarget);
|
|
111
123
|
if (outbound?.type !== undefined && outbound.type !== "stdio" || outbound?.url !== undefined || outbound?.serverUrl !== undefined || outbound?.httpUrl !== undefined) throw new Error("The existing Antigravity outbound bridge transport is not stdio");
|
|
112
124
|
if (outbound?.env !== undefined) object(outbound.env, "Antigravity outbound bridge environment");
|
|
113
125
|
if (Object.keys(outbound?.env ?? {}).some((name) => /^(?:CLAUDE|CODEX_|ANTIGRAVITY_BRIDGE_CALLER$)/i.test(name))) throw new Error("The existing Antigravity outbound bridge contains a foreign caller environment; review it before installation");
|
|
@@ -122,7 +134,7 @@ export function planAntigravityInstallation({ home = os.homedir(), root = path.r
|
|
|
122
134
|
const mcpEdit = changed(originalMcp, mcp);
|
|
123
135
|
if (mcpEdit) edits.push(mcpEdit);
|
|
124
136
|
const originalSidecar = snapshot(path.join(configRoot, "sidecars", SIDECAR, "sidecar.json"));
|
|
125
|
-
if (originalSidecar.text !== null) ownEntry(originalSidecar.value, sidecarEntry, "Antigravity sidecar");
|
|
137
|
+
if (originalSidecar.text !== null) ownEntry(originalSidecar.value, sidecarEntry, "Antigravity sidecar", retarget);
|
|
126
138
|
const originalConfig = snapshot(path.join(configRoot, "config.json"));
|
|
127
139
|
const config = structuredClone(originalConfig.value);
|
|
128
140
|
if (config.sidecars !== undefined) object(config.sidecars, "Antigravity sidecars");
|
|
@@ -131,10 +143,12 @@ export function planAntigravityInstallation({ home = os.homedir(), root = path.r
|
|
|
131
143
|
let definition = null;
|
|
132
144
|
if (remove) { if (config.sidecars) delete config.sidecars[SIDECAR]; }
|
|
133
145
|
else {
|
|
134
|
-
definition = { ...originalSidecar.value, command: node, args: [sidecarEntry], restart_policy: originalSidecar.value.restart_policy ?? "on-failure", display_name: "Codex / Claude Antigravity Bridge" };
|
|
146
|
+
definition = { ...originalSidecar.value, command: node, args: [sidecarEntry], restart_policy: originalSidecar.value.restart_policy ?? "on-failure", display_name: setup ? originalSidecar.value.display_name ?? "Codex / Claude Antigravity Bridge" : "Codex / Claude Antigravity Bridge" };
|
|
135
147
|
if (definition.builtin !== undefined) throw new Error("A builtin Antigravity sidecar cannot be replaced");
|
|
136
148
|
config.sidecars ??= {};
|
|
137
|
-
|
|
149
|
+
const enabled = setup ? config.sidecars[SIDECAR]?.enabled ?? true : true;
|
|
150
|
+
config.sidecars[SIDECAR] = { ...config.sidecars[SIDECAR], enabled };
|
|
151
|
+
if (enabled === false) notices.push("The existing Antigravity bridge sidecar is disabled; automatic setup preserved that choice.");
|
|
138
152
|
}
|
|
139
153
|
const sidecarEdit = changed(originalSidecar, definition);
|
|
140
154
|
const configEdit = changed(originalConfig, config);
|
|
@@ -179,19 +193,19 @@ export function applyJsonEdits(edits) {
|
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
export function installerArguments(args) {
|
|
182
|
-
const allowed = new Set(["--codex", "--claude", "--desktop", "--all", "--remove", "--dry-run", "--help"]);
|
|
196
|
+
const allowed = new Set(["--codex", "--claude", "--desktop", "--all", "--remove", "--dry-run", "--setup", "--help"]);
|
|
183
197
|
if (args.some((arg) => !allowed.has(arg))) throw new Error("Unknown option; use --help");
|
|
184
198
|
const selected = ["codex", "claude", "desktop"].filter((target) => args.includes(`--${target}`));
|
|
185
|
-
return { targets: args.includes("--all") || !selected.length ? ["codex", "claude", "desktop"] : selected, remove: args.includes("--remove"), dryRun: args.includes("--dry-run"), help: args.includes("--help") };
|
|
199
|
+
return { targets: args.includes("--all") || !selected.length ? ["codex", "claude", "desktop"] : selected, remove: args.includes("--remove"), dryRun: args.includes("--dry-run"), setup: args.includes("--setup"), help: args.includes("--help") };
|
|
186
200
|
}
|
|
187
201
|
|
|
188
202
|
export function runAntigravityInstaller(args = process.argv.slice(2)) {
|
|
189
203
|
const options = installerArguments(args);
|
|
190
204
|
if (options.help) {
|
|
191
|
-
console.log("Usage: antigravity-mcp-bridge-install [--all|--codex|--claude|--desktop] [--remove] [--dry-run]");
|
|
205
|
+
console.log("Usage: antigravity-mcp-bridge-install [--all|--codex|--claude|--desktop] [--remove] [--dry-run] [--setup]");
|
|
192
206
|
return;
|
|
193
207
|
}
|
|
194
|
-
const home =
|
|
208
|
+
const home = homeDir();
|
|
195
209
|
const root = stableInstallPath(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
|
|
196
210
|
const node = stableNodeBin();
|
|
197
211
|
let run;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { claudeDesktopConfigPath, homeDir } from "../src/platform.mjs";
|
|
7
|
+
import { checkCodeRegistration, planCodeRegistration, planInitialCodeRegistration } from "../src/claude-code-registration.mjs";
|
|
8
8
|
import { readProjectPolicy, readRegularConfigFile, backupRegularConfigFile } from "../src/project-policy.mjs";
|
|
9
9
|
import { exitForVersionRequest } from "../src/cli-version.mjs";
|
|
10
|
+
import { stableInstallPath, stableNodeBin } from "../src/install-paths.mjs";
|
|
10
11
|
|
|
11
12
|
exitForVersionRequest(import.meta.url);
|
|
12
13
|
|
|
13
14
|
const args = process.argv.slice(2);
|
|
14
15
|
const option = (key, fallback) => { const index = args.indexOf(key); return index < 0 ? fallback : args[index + 1]; };
|
|
15
|
-
const codeFile = option("--code-config", path.join(
|
|
16
|
+
const codeFile = option("--code-config", path.join(process.env.CLAUDE_CONFIG_DIR || homeDir(), ".claude.json"));
|
|
16
17
|
const desktopFile = option("--desktop-config", claudeDesktopConfigPath());
|
|
17
18
|
const policyFile = option("--policy", process.env.CODEX_BRIDGE_PROJECT_POLICY);
|
|
18
19
|
const maxConfigBytes = 8 * 1024 * 1024;
|
|
@@ -21,7 +22,58 @@ try {
|
|
|
21
22
|
const files = [codeFile, desktopFile];
|
|
22
23
|
const originals = files.map(read);
|
|
23
24
|
const [code, desktop] = originals.map((raw) => raw === null ? {} : JSON.parse(raw.replace(/^\uFEFF/, "")));
|
|
24
|
-
|
|
25
|
+
const apply = (plan) => {
|
|
26
|
+
const contents = [plan.code, plan.desktop].map((value) => JSON.stringify(value, null, 2) + "\n");
|
|
27
|
+
const suffix = randomUUID();
|
|
28
|
+
const written = [];
|
|
29
|
+
try {
|
|
30
|
+
for (let index = 0; index < files.length; index++) {
|
|
31
|
+
const file = files[index];
|
|
32
|
+
const now = read(file);
|
|
33
|
+
if (now !== originals[index]) throw new Error(`Configuration changed concurrently: ${file}`);
|
|
34
|
+
if (now === null && index === 1 && !Object.keys(plan.desktop.mcpServers ?? {}).length) continue;
|
|
35
|
+
if (now !== null && JSON.stringify(JSON.parse(now.replace(/^\uFEFF/, ""))) === JSON.stringify([plan.code, plan.desktop][index])) continue;
|
|
36
|
+
if (now !== null) backupRegularConfigFile(file, `${file}.backup-${suffix}`, now, maxConfigBytes);
|
|
37
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
38
|
+
const temporary = `${file}.pending-${suffix}`;
|
|
39
|
+
try {
|
|
40
|
+
fs.writeFileSync(temporary, contents[index], { flag: "wx", mode: 0o600 });
|
|
41
|
+
if (read(file) !== now) throw new Error(`Configuration changed concurrently: ${file}`);
|
|
42
|
+
fs.renameSync(temporary, file);
|
|
43
|
+
} finally { fs.rmSync(temporary, { force: true }); }
|
|
44
|
+
written.push(index);
|
|
45
|
+
if (read(file) !== contents[index]) throw new Error(`Configuration readback failed: ${file}`);
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const remaining = [];
|
|
49
|
+
for (const index of written.reverse()) {
|
|
50
|
+
const file = files[index];
|
|
51
|
+
const temporary = `${file}.rollback-${suffix}`;
|
|
52
|
+
try {
|
|
53
|
+
if (read(file) !== contents[index]) { remaining.push(file); continue; }
|
|
54
|
+
if (originals[index] === null) fs.unlinkSync(file);
|
|
55
|
+
else {
|
|
56
|
+
fs.writeFileSync(temporary, originals[index], { flag: "wx", mode: 0o600 });
|
|
57
|
+
if (read(file) !== contents[index]) { remaining.push(file); continue; }
|
|
58
|
+
fs.renameSync(temporary, file);
|
|
59
|
+
}
|
|
60
|
+
} catch { remaining.push(file); }
|
|
61
|
+
finally { fs.rmSync(temporary, { force: true }); }
|
|
62
|
+
}
|
|
63
|
+
if (remaining.length) error.message += `; rollback requires review: ${remaining.join(", ")}`;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
return written.length;
|
|
67
|
+
};
|
|
68
|
+
if (args.includes("--install")) {
|
|
69
|
+
if (args.includes("--check") || args.includes("--apply")) throw new Error("Use --install separately from --check or --apply");
|
|
70
|
+
const root = stableInstallPath(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
|
|
71
|
+
const plan = planInitialCodeRegistration({ code, desktop, root, node: stableNodeBin(), environment: process.env });
|
|
72
|
+
const dryRun = args.includes("--dry-run");
|
|
73
|
+
const writtenFiles = dryRun ? 0 : apply(plan);
|
|
74
|
+
console.log(JSON.stringify({ action: dryRun ? "plan" : "install", configured: !dryRun, liveVerified: false,
|
|
75
|
+
changes: plan.changes, notices: plan.notices, writtenFiles, restartRequired: writtenFiles > 0 }, null, 2));
|
|
76
|
+
} else if (args.includes("--check")) {
|
|
25
77
|
const result = checkCodeRegistration({ code, desktop });
|
|
26
78
|
if (result.policyFile) { try { readProjectPolicy(result.policyFile); } catch (error) { result.issues.push(error.message); result.ready = false; } }
|
|
27
79
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -33,44 +85,7 @@ try {
|
|
|
33
85
|
console.log(JSON.stringify({ action: args.includes("--apply") ? "apply" : "plan", changes: plan.changes, policyFile }, null, 2));
|
|
34
86
|
if (args.includes("--apply")) {
|
|
35
87
|
if (!args.includes("--clients-stopped")) throw new Error("Exit Claude and the configuration manager first, then pass --clients-stopped to avoid their overwriting the migration");
|
|
36
|
-
|
|
37
|
-
const suffix = randomUUID();
|
|
38
|
-
const written = [];
|
|
39
|
-
try {
|
|
40
|
-
for (let index = 0; index < files.length; index++) {
|
|
41
|
-
const file = files[index];
|
|
42
|
-
const now = read(file);
|
|
43
|
-
if (now !== originals[index]) throw new Error(`Configuration changed concurrently: ${file}`);
|
|
44
|
-
if (now === null && index === 1 && !Object.keys(plan.desktop.mcpServers ?? {}).length) continue;
|
|
45
|
-
if (now !== null) backupRegularConfigFile(file, `${file}.backup-${suffix}`, now, maxConfigBytes);
|
|
46
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
47
|
-
const temporary = `${file}.pending-${suffix}`;
|
|
48
|
-
try {
|
|
49
|
-
fs.writeFileSync(temporary, contents[index], { flag: "wx", mode: 0o600 });
|
|
50
|
-
if (read(file) !== now) throw new Error(`Configuration changed concurrently: ${file}`);
|
|
51
|
-
fs.renameSync(temporary, file);
|
|
52
|
-
} finally { fs.rmSync(temporary, { force: true }); }
|
|
53
|
-
written.push(index);
|
|
54
|
-
}
|
|
55
|
-
} catch (error) {
|
|
56
|
-
const remaining = [];
|
|
57
|
-
for (const index of written.reverse()) {
|
|
58
|
-
const file = files[index];
|
|
59
|
-
const temporary = `${file}.rollback-${suffix}`;
|
|
60
|
-
try {
|
|
61
|
-
if (read(file) !== contents[index]) { remaining.push(file); continue; }
|
|
62
|
-
if (originals[index] === null) fs.unlinkSync(file);
|
|
63
|
-
else {
|
|
64
|
-
fs.writeFileSync(temporary, originals[index], { flag: "wx", mode: 0o600 });
|
|
65
|
-
if (read(file) !== contents[index]) { remaining.push(file); continue; }
|
|
66
|
-
fs.renameSync(temporary, file);
|
|
67
|
-
}
|
|
68
|
-
} catch { remaining.push(file); }
|
|
69
|
-
finally { fs.rmSync(temporary, { force: true }); }
|
|
70
|
-
}
|
|
71
|
-
if (remaining.length) error.message += `; rollback requires review: ${remaining.join(", ")}`;
|
|
72
|
-
throw error;
|
|
73
|
-
}
|
|
88
|
+
apply(plan);
|
|
74
89
|
console.log("Reopen the original Code task and call codex_bridge_status with the intended project cwd. Update the configuration manager's matching entry to the same policy file. No new per-project MCP entry is needed.");
|
|
75
90
|
}
|
|
76
91
|
}
|