@minhspark/codex-mcp-bridge 1.26.4 → 1.26.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-mcp-bridge",
3
- "version": "1.26.4",
3
+ "version": "1.26.5",
4
4
  "description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
5
5
  "author": {
6
6
  "name": "Bui Dang Minh"
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](ht
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.26.5] - 2026-11-02
8
+
9
+ ### Fixed
10
+
11
+ - Add `codex-bridge-update --user` to pin installation to the per-user npm prefix before inspecting or replacing packages. The recommended Windows command now avoids `EPERM` when Node resolves its global prefix to `Program Files`, including machines with several Node installations. Existing owned relay runtimes, project grants and disabled-service choices are preserved when moving between prefixes.
12
+ - Install the global package with npm lifecycle scripts disabled, then apply managed setup directly using Node and verify settings and live relay readiness. This removes the lifecycle shell and npm script-approval dependency, and preserves the installed package if setup needs repair.
13
+ - Report installation permission failures, numeric child exit codes, signals and timeouts explicitly while keeping raw child output and credentials out of diagnostics.
14
+
15
+ ### Upgrade
16
+
17
+ Windows PowerShell, without administrator rights:
18
+
19
+ ```powershell
20
+ npx.cmd --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update --user
21
+ ```
22
+
23
+ This single command updates the package, applies all managed client settings and verifies the independent background relay. On macOS/Linux, keep using the installation command in the README; optional `--user` selects `~/.local`.
24
+
7
25
  ## [1.26.4] - 2026-11-01
8
26
 
9
27
  ### Fixed
package/README.md CHANGED
@@ -36,10 +36,10 @@ npm exec --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest --
36
36
  **Windows PowerShell**
37
37
 
38
38
  ```powershell
39
- npm.cmd exec --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update
39
+ npx.cmd --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update --user
40
40
  ```
41
41
 
42
- Requires **Node.js 22.13+**. Use `.cmd` for this package's commands in Windows PowerShell. **Use this same single command for a fresh install and every update**, including upgrades from 1.26.0 or 1.26.1. npm fetches the latest updater into its cache without running setup there. The updater installs that exact resolved version globally, runs the managed setup hook, and verifies the global version, saved settings and service activation separately. A second installation command is not required.
42
+ Requires **Node.js 22.13+**. Use `.cmd` for this package's commands in Windows PowerShell. **Use this same single command for a fresh install and every update**, including upgrades from 1.26.0 or 1.26.1. On Windows, `--user` selects `%APPDATA%\npm`, avoiding protected `Program Files` prefixes and administrator rights even when switching Node installations. npm fetches the latest updater into its cache without running setup there. The updater installs that exact resolved version globally with lifecycle scripts disabled, applies managed setup directly using Node, and verifies the global version, saved settings and service activation separately. A second installation command is not required.
43
43
 
44
44
  For an enabled legacy Windows relay from 1.26.0 or 1.26.1, the updater moves only this package's owned service to newer code using private relay storage as its working directory before replacing the global package. The temporary service runtime points to the downloaded updater package; changing the working directory releases the old package lock. It waits for active work to drain and the replacement transport to become ready without killing processes or opening apps or chats. If migration is still pending after its bounded wait, it reports `UPDATE_RELAY_PENDING`; keep the existing jobs and receipts and rerun the same command after they finish. Intentionally disabled services stay disabled and are not reported as ready.
45
45
 
@@ -65,7 +65,7 @@ Keep the same request UUID for retries and read its durable receipt. Queued work
65
65
 
66
66
  Worker delegation is bounded. `WORKER_DELEGATION_CAPACITY` means the destination has no free execution slot and that delegation was not sent or queued behind waiting workers. Inspect or finish existing delegated work before retrying the same unsent request. Queue and delegation limits are reported explicitly.
67
67
 
68
- The recommended command's `--ignore-scripts` applies only to downloading the updater into npm's cache. The updater explicitly enables this package's setup hook for the global installation and displays its progress. This avoids client MCP registrations pointing into a temporary npm cache directory. A direct global npm install remains an advanced alternative, but it skips the updater's migration of an old service that can lock the package on Windows.
68
+ The updater disables npm lifecycle scripts for both the cached download and the global installation, then runs the installed package's managed setup directly using Node. This avoids npm script-policy differences, lifecycle shell failures and client MCP registrations pointing into a temporary npm cache directory. A direct global npm install remains an advanced alternative, but it skips the updater's migration of an old service that can lock the package on Windows.
69
69
 
70
70
  | Client | Installed entry | Purpose |
71
71
  | --- | --- | --- |
@@ -79,11 +79,11 @@ The recommended command's `--ignore-scripts` applies only to downloading the upd
79
79
 
80
80
  For native Claude-to-Codex handoffs, use an existing native Claude **Code** session and the same local project saved in Codex Desktop. The shared `codex-bridge-desktop` connector has its own legacy route; it does not establish a native Code sender identity. Setup applies saved configuration without opening or restarting apps. Saved configuration is not proof of a live connection; available supervisors reload compatible workers when idle. Complete [verification](#verify-the-installation) with the tools already available in the actual clients. If a native host is unloaded, report the diagnostic instead of opening chats to load a relay.
81
81
 
82
- Automatic setup runs during the updater's **global npm installation**. Local `npm install` and `npm ci` do not change app settings. For a deliberate package-only installation, use a direct global npm install with `CODEX_BRIDGE_SKIP_SETUP=1` or `--ignore-scripts`; that opt-out is separate from the cache-only `--ignore-scripts` in the recommended updater command. The optional [workflow skill](#workflow-skill--npx-skills), VS Code preview, and hosted ChatGPT pairing retain their separate setup steps.
82
+ Managed setup runs **after the updater's global npm installation succeeds**, outside npm's lifecycle hook. Local `npm install` and `npm ci` do not change app settings. For a deliberate package-only installation, use a direct global npm install with `CODEX_BRIDGE_SKIP_SETUP=1` or `--ignore-scripts`; the complete updater always applies and verifies setup, and refuses an explicit `CODEX_BRIDGE_SKIP_SETUP=1` opt-out. The optional [workflow skill](#workflow-skill--npx-skills), VS Code preview, and hosted ChatGPT pairing retain their separate setup steps.
83
83
 
84
84
  ### Setup recovery and checks
85
85
 
86
- If service migration, installation or managed setup fails, the updater returns a nonzero exit and reports the failed step. Inspect that component and the installed version, correct the reported cause, and rerun the same update command above. npm can roll back package files while settings already written remain changed. For configuration-only repair of an already installed package, the following is an optional alternative:
86
+ If service migration, installation or managed setup fails, the updater returns a nonzero exit and reports the failed step. Inspect that component and the installed version, correct the reported cause, and rerun the same update command above. A completed package installation is preserved if managed setup fails. For configuration-only repair of an already installed package, the following is an optional alternative:
87
87
 
88
88
  ```bash
89
89
  codex-bridge-setup
@@ -549,10 +549,10 @@ Node.js 22.13+ is required. If a command is missing, open a new terminal and che
549
549
  If the package or registrations are missing, use the complete installation command:
550
550
 
551
551
  ```powershell
552
- npm.cmd exec --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update
552
+ npx.cmd --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update --user
553
553
  ```
554
554
 
555
- This runs the latest updater outside the global package directory, handles an owned legacy service that can lock that directory, and installs the exact resolved package with its setup hook enabled. `--ignore-scripts` suppresses setup only for the temporary cached updater. If a previous direct install printed `1 package had install scripts blocked`, use this same updater command to install and apply the managed settings together. Intentional setup opt-outs and disabled-service choices are not silently removed.
555
+ This runs the latest updater outside the global package directory, handles an owned legacy service that can lock that directory, installs the exact resolved package into the per-user npm prefix, then directly applies and verifies managed setup. Lifecycle scripts remain disabled during npm installation. If a previous update reported `UPDATE_NPM_FAILED` or `UPDATE_INSTALL_PERMISSION` because npm selected a protected directory, `--user` selects the user-writable destination. If a previous direct install printed `1 package had install scripts blocked`, this same updater command installs and applies the managed settings together. Intentional setup opt-outs and disabled-service choices are not silently removed. On macOS/Linux, use the installation command above; optional `--user` selects `~/.local`, whose `bin` directory must be on `PATH` for terminal commands.
556
556
 
557
557
  For an already-installed package, these optional commands preview or repair configuration without updating package files:
558
558
 
@@ -19,9 +19,9 @@ npm exec --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest --
19
19
  ```
20
20
 
21
21
  npm downloads the latest updater into its cache. `--ignore-scripts` suppresses
22
- setup only in that temporary location. The updater installs its exact resolved
23
- version globally, enables this package's managed setup hook, displays progress
24
- and verifies the global version and configuration. No second installation or
22
+ setup in that temporary location. The updater installs its exact resolved
23
+ version globally with lifecycle scripts disabled, runs managed setup directly
24
+ using Node and verifies the global version and configuration. No second installation or
25
25
  setup command is required. Existing disabled choices, authentication and custom
26
26
  project grants are preserved; the updater does not open apps or chats or kill
27
27
  processes. A pending service migration is reported instead of interrupting work.
@@ -32,8 +32,8 @@ official package under `~/.config/GptClaudeBridge/tools` (or
32
32
  `XDG_CONFIG_HOME/GptClaudeBridge/tools`). No separate Codex CLI install command
33
33
  is required. Run the install under the account that uses the apps, without `sudo`.
34
34
  If setup or update exits nonzero, inspect the reported component and installed
35
- version, correct the cause, and rerun the same updater command. npm can roll back
36
- package files without restoring settings already written or command shims.
35
+ version, correct the cause, and rerun the same updater command. A completed
36
+ package installation is preserved when managed setup fails.
37
37
 
38
38
  The command also installs an independent per-user LaunchAgent for separate
39
39
  Claude, Codex and Antigravity CLI workers and updates its managed settings on
@@ -101,8 +101,8 @@ a nonzero exit status while configuration is incomplete.
101
101
 
102
102
  Local `npm install` and `npm ci` do not change app settings. For a deliberate
103
103
  package-only installation, a direct global npm install can use
104
- `CODEX_BRIDGE_SKIP_SETUP=1` or `--ignore-scripts`; this differs from the cache-only
105
- script suppression in the recommended updater command. For Homebrew, run
104
+ `CODEX_BRIDGE_SKIP_SETUP=1` or `--ignore-scripts`; the complete updater runs
105
+ managed setup directly after package installation. For Homebrew, run
106
106
  `codex-bridge-setup` after installing the formula. A pinned source checkout can
107
107
  use `node scripts/setup.mjs`.
108
108
 
@@ -7,13 +7,15 @@ does not require the Desktop apps to be open. Use this one PowerShell command
7
7
  for a fresh installation or an update, including from 1.26.0 or 1.26.1:
8
8
 
9
9
  ```powershell
10
- npm.cmd exec --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update
10
+ npx.cmd --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update --user
11
11
  ```
12
12
 
13
- npm downloads the latest updater into its cache. `--ignore-scripts` prevents
14
- setup from running in that temporary location; the updater then installs its
15
- exact resolved version globally with this package's managed setup hook enabled.
16
- It displays progress and verifies the global version and saved settings.
13
+ npm downloads the latest updater into its cache. `--user` selects `%APPDATA%\npm`
14
+ instead of a protected Node installation directory such as `Program Files`;
15
+ administrator rights are not required. The updater installs its exact resolved
16
+ version globally with lifecycle scripts disabled, then runs managed setup
17
+ directly using Node. It displays progress and verifies the global version,
18
+ saved settings and relay activation.
17
19
  No second installation or setup command is required.
18
20
 
19
21
  An older Windows background service can keep the global npm package directory
@@ -33,8 +35,8 @@ official package in the current user's `.config\GptClaudeBridge\tools`
33
35
  directory. It also configures native pipe forwarding and bootstraps the relay
34
36
  executor. A failed component is reported as incomplete rather than ready. If
35
37
  setup or update exits nonzero, inspect the reported component and installed
36
- version, correct the cause, and rerun the same updater command. npm can roll back
37
- package files without restoring settings already written or command shims.
38
+ version, correct the cause, and rerun the same updater command. A completed
39
+ package installation is preserved when managed setup fails.
38
40
 
39
41
  The same command installs the independent worker relay as a hidden per-user
40
42
  scheduled task and applies its managed settings on every update. It registers
@@ -104,8 +106,8 @@ attempting delivery.
104
106
  Local `npm install` and `npm ci` do not configure apps. For a deliberate
105
107
  package-only installation, a direct global npm install can use
106
108
  `CODEX_BRIDGE_SKIP_SETUP=1` or `--ignore-scripts`. This differs from the
107
- recommended updater command, which suppresses setup only in its temporary cache
108
- and enables it for the final global installation. A direct install skips the
109
+ recommended updater command, which disables lifecycle scripts and runs managed
110
+ setup directly after the final global installation. A direct install skips the
109
111
  updater's migration of legacy Windows services that can lock the package.
110
112
  The optional card extension, hosted ChatGPT pairing, and VS Code preview have
111
113
  separate installation steps.
@@ -7,12 +7,12 @@ configures the independent worker relay automatically:
7
7
  npm exec --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update
8
8
  ```
9
9
 
10
- In Windows PowerShell use `npm.cmd`. npm fetches the latest updater into its
10
+ In Windows PowerShell use `npx.cmd --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update --user` to select the per-user npm prefix without administrator rights. npm fetches the latest updater into its
11
11
  cache with setup disabled there. The updater moves an owned legacy service to
12
12
  newer code using private relay storage as its working directory when needed,
13
13
  temporarily using the downloaded updater package. It waits for active work to
14
- drain, then installs its exact resolved version globally with the setup hook
15
- enabled. It verifies the global installation and managed settings. No second
14
+ drain, then installs its exact resolved version globally with lifecycle scripts
15
+ disabled and runs managed setup directly using Node. It verifies the global installation and managed settings. No second
16
16
  installation command is required. It does not open apps or chats or kill
17
17
  processes. The setup creates
18
18
  `codex-bridge` for Claude Code and the separate `codex-bridge-desktop` shared
@@ -37,10 +37,10 @@ setup after correcting an incomplete component. Add `.cmd` on Windows and
37
37
  `--json` for structured output. Local package installs do not run setup.
38
38
  For a deliberate package-only direct global installation,
39
39
  `CODEX_BRIDGE_SKIP_SETUP=1` or npm `--ignore-scripts` opts out of setup; this is
40
- separate from the cache-only script suppression in the recommended updater.
40
+ separate from the complete updater, which runs managed setup directly after installation.
41
41
  Failed setup or update returns a nonzero exit. Inspect the reported component
42
42
  and installed version, correct the cause, then rerun the same updater command.
43
- npm can roll back package files without restoring settings or command shims.
43
+ A completed package installation is preserved when managed setup fails.
44
44
  Explicit setup and `--check` also return a nonzero status until the saved
45
45
  configuration passes verification.
46
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhspark/codex-mcp-bridge",
3
- "version": "1.26.4",
3
+ "version": "1.26.5",
4
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",
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.26.4",
4
+ "version": "1.26.5",
5
5
  "description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
6
6
  "author": {
7
7
  "name": "Bui Dang Minh",
@@ -25,7 +25,7 @@ import { removeRelayFile } from "../src/native-relay-files.mjs";
25
25
 
26
26
  exitForVersionRequest(import.meta.url);
27
27
 
28
- const VERSION = "1.26.4";
28
+ const VERSION = "1.26.5";
29
29
  const root = stableInstallPath(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
30
30
  const entry = path.join(root, "src", "mcp-supervisor.mjs");
31
31
  const entryArgs = ["native-relay-companion.mjs"];
@@ -87,11 +87,19 @@ function neutralDirectory(roots, env) {
87
87
  }
88
88
 
89
89
  function childFailure(result, phase) {
90
- const match = /npm\s+(?:ERR!|error)\s+code\s+([A-Z][A-Z0-9_]{0,79})\b/.exec(String(result?.stderr ?? ""));
91
- const code = match?.[1] ?? (/^[A-Z][A-Z0-9_]{0,79}$/.test(result?.error?.code ?? "") ? result.error.code : null);
90
+ const match = /npm\s+(?:ERR!|error)\s+code\s+([A-Z][A-Z0-9_]{0,79}|\d{1,10})\b/.exec(String(result?.stderr ?? ""));
91
+ const signal = result?.error?.signal ?? result?.signal;
92
+ const value = match?.[1] ?? result?.error?.code ?? (signal ? null : result?.status ?? result?.exitCode);
93
+ const timedOut = value === "ETIMEDOUT" || result?.error?.killed === true && signal === "SIGTERM";
94
+ const code = timedOut ? "ETIMEDOUT" : /^\d{1,10}$/.test(String(value)) ? `EXIT_${value}`
95
+ : /^[A-Z][A-Z0-9_]{0,79}$/.test(value ?? "") ? value
96
+ : /^SIG[A-Z0-9]{1,20}$/.test(signal ?? "") ? `SIGNAL_${signal}` : null;
92
97
  const cause = code ? Object.assign(new Error("The child command reported a failure."), { code }) : undefined;
93
- return failure(code === "EBUSY" ? "UPDATE_PACKAGE_BUSY" : phase === "verification" ? "UPDATE_SETUP_INCOMPLETE" : "UPDATE_NPM_FAILED",
94
- code === "EBUSY" ? `The installed package is still held open by another process. No process was stopped. ${RETRY}`
98
+ const permissionDenied = phase === "installation" && ["EPERM", "EACCES"].includes(code);
99
+ return failure(permissionDenied ? "UPDATE_INSTALL_PERMISSION" : code === "EBUSY" ? "UPDATE_PACKAGE_BUSY" : timedOut ? "UPDATE_COMMAND_TIMEOUT" : phase === "verification" ? "UPDATE_SETUP_INCOMPLETE" : "UPDATE_NPM_FAILED",
100
+ permissionDenied ? "npm could not write to the installation directory. Run codex-bridge-update --user to install and configure the bridge in your user account without administrator rights. Existing work and receipts were preserved."
101
+ : timedOut ? `The update command exceeded its deadline. ${RETRY}`
102
+ : code === "EBUSY" ? `The installed package is still held open by another process. No process was stopped. ${RETRY}`
95
103
  : `The ${phase === "verification" ? "installed setup verification" : "npm command"} did not complete. ${RETRY}`, phase, cause);
96
104
  }
97
105
 
@@ -114,28 +122,35 @@ function serviceQuiescent(config) {
114
122
  export async function runUpdate({ args = [], env = process.env, platform = process.platform, root = ROOT, run = execute,
115
123
  installService = installBackgroundRelay, readConfig = readRelayConfiguration, readRuntime = readRelayServiceRuntime,
116
124
  quiescent = serviceQuiescent, now = Date.now, sleep = pause, waitMs = 120000, onProgress = () => {} } = {}) {
117
- if (args.some(arg => !["--help", "--json"].includes(arg))) throw failure("UPDATE_ARGUMENT_INVALID", "Usage: codex-bridge-update [--json] [--help]", "preflight");
118
- if (args.includes("--help")) return { help: "Usage: codex-bridge-update [--json]\nSafely update the global bridge and all managed settings. Legacy Windows relays migrate when existing work finishes; pending migrations can be retried with the same command." };
125
+ if (args.some(arg => !["--help", "--json", "--user"].includes(arg))) throw failure("UPDATE_ARGUMENT_INVALID", "Usage: codex-bridge-update [--user] [--json] [--help]", "preflight");
126
+ if (args.includes("--help")) return { help: "Usage: codex-bridge-update [--user] [--json]\nSafely update the global bridge and all managed settings. --user selects the per-user npm prefix (Windows: AppData/Roaming/npm; macOS/Linux: ~/.local) without administrator rights. Legacy Windows relays migrate when existing work finishes; pending migrations can be retried with the same command." };
119
127
  if (env.CODEX_BRIDGE_SKIP_SETUP === "1") throw failure("UPDATE_SETUP_DISABLED", "CODEX_BRIDGE_SKIP_SETUP=1 disables managed setup. Remove that explicit opt-out before requesting this complete update.", "preflight");
120
128
  if (!Number.isSafeInteger(waitMs) || waitMs < 1 || waitMs > 600000) throw failure("UPDATE_ARGUMENT_INVALID", "The update readiness deadline is invalid.", "preflight");
121
129
  root = fs.realpathSync.native(root);
122
130
  const version = packageVersion(root);
123
131
  const npm = resolveUpdateNpm(env);
132
+ const userPrefix = args.includes("--user") ? platform === "win32"
133
+ ? path.join(env.APPDATA || path.join(env.USERPROFILE || os.homedir(), "AppData", "Roaming"), "npm")
134
+ : path.join(env.HOME || os.homedir(), ".local") : null;
135
+ if (userPrefix && (!path.isAbsolute(userPrefix) || /[\u0000-\u001f\u007f]/.test(userPrefix))) {
136
+ throw failure("UPDATE_GLOBAL_ROOT_INVALID", "The per-user installation directory is invalid.", "preflight");
137
+ }
138
+ const prefixArgs = userPrefix ? ["--prefix", userPrefix] : [];
124
139
  let cwd = neutralDirectory([root], env);
125
140
  let childEnv = Object.fromEntries(Object.entries(env).filter(([name]) => !/^AGENT_RELAY_(?!DIRECTORY$)/i.test(name)));
126
- const command = async (arguments_, phase, timeout = 600000) => {
141
+ const command = async (arguments_, phase, timeout = 600000, environment = childEnv) => {
127
142
  let result;
128
143
  try {
129
- result = await run(process.execPath, arguments_, { env: childEnv, cwd, encoding: "utf8", windowsHide: true,
144
+ result = await run(process.execPath, arguments_, { env: environment, cwd, encoding: "utf8", windowsHide: true,
130
145
  shell: false, timeout, maxBuffer: 8 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
131
146
  } catch (error) { throw childFailure({ error, stderr: error.stderr }, phase); }
132
147
  if (result?.error || (result?.status ?? result?.exitCode ?? 0) !== 0) throw childFailure(result, phase);
133
148
  return String(result?.stdout ?? "");
134
149
  };
135
- const modules = (await command([npm, "root", "--global"], "preflight", 30000)).trim();
150
+ const modules = (await command([npm, "root", "--global", ...prefixArgs], "preflight", 30000)).trim();
136
151
  if (!path.isAbsolute(modules) || /[\u0000-\u001f\u007f]/.test(modules)) throw failure("UPDATE_GLOBAL_ROOT_INVALID", "npm returned an invalid global installation directory.", "preflight");
137
- const prefix = (await command([npm, "prefix", "--global"], "preflight", 30000)).trim();
138
- if (!path.isAbsolute(prefix) || /[\u0000-\u001f\u007f]/.test(prefix)
152
+ const prefix = (await command([npm, "prefix", "--global", ...prefixArgs], "preflight", 30000)).trim();
153
+ if (!path.isAbsolute(prefix) || /[\u0000-\u001f\u007f]/.test(prefix) || userPrefix && path.relative(userPrefix, prefix) !== ""
139
154
  || path.relative(modules, path.join(prefix, ...(platform === "win32" ? [] : ["lib"]), "node_modules")) !== "") {
140
155
  throw failure("UPDATE_GLOBAL_ROOT_INVALID", "npm returned inconsistent global prefix and installation directories.", "preflight");
141
156
  }
@@ -146,7 +161,7 @@ export async function runUpdate({ args = [], env = process.env, platform = proce
146
161
  const config = readConfig({ env: childEnv, platform, missing: true });
147
162
  let runtime;
148
163
  if (config) {
149
- runtime = readRuntime(config.paths.root, { root: globalRoot, nodeExecutable: process.execPath });
164
+ runtime = readRuntime(config.paths.root, { root: globalRoot, nodeExecutable: process.execPath, allowMissingFiles: true });
150
165
  if (runtime.packageVersion && newerVersion(runtime.packageVersion, version)) throw failure("UPDATE_DOWNGRADE_REFUSED", "The owned relay runtime is newer than this updater. Run the update command with the latest published package.", "preflight");
151
166
  if (runtime.environment !== undefined) childEnv = { ...Object.fromEntries(Object.entries(childEnv).filter(([name]) => !MANAGED_ENV.has(name.toUpperCase()))), ...runtime.environment };
152
167
  childEnv.AGENT_RELAY_DIRECTORY = config.paths.root;
@@ -176,19 +191,22 @@ export async function runUpdate({ args = [], env = process.env, platform = proce
176
191
  if (!quiescent(config)) throw Object.assign(failure("UPDATE_RELAY_PENDING", `The disabled relay still has an owned process whose release could not be verified. ${RETRY}`, "migration"), { pending: true });
177
192
  } else {
178
193
  onProgress("Waiting for the legacy relay to finish existing work and activate the update runtime.");
179
- const staged = runtime.packageVersion && !legacy(runtime.packageVersion) && !newerVersion("1.26.2", runtime.packageVersion);
194
+ const staged = runtime.filesAvailable !== false && runtime.packageVersion && !legacy(runtime.packageVersion) && !newerVersion("1.26.2", runtime.packageVersion);
180
195
  const service = await awaitService(staged ? runtime.packageRoot : root, !staged, "migration", staged ? runtime.packageVersion : version);
181
196
  migrated = !staged && service.enabled;
182
197
  }
183
198
  }
184
- onProgress(`Installing bridge ${version} and applying managed settings.`);
185
- await command([npm, "install", "--global", "--prefix", prefix, `${PACKAGE}@${version}`, "--ignore-scripts=false", `--allow-scripts=${PACKAGE}`, "--foreground-scripts"], "installation");
199
+ onProgress(`Installing bridge ${version}.`);
200
+ await command([npm, "install", "--global", "--prefix", prefix, `${PACKAGE}@${version}`, "--ignore-scripts"],
201
+ "installation", 600000, { ...childEnv, CODEX_BRIDGE_SKIP_SETUP: "1" });
186
202
  if (packageVersion(globalRoot) !== version) throw failure("UPDATE_VERSION_MISMATCH", "The global package version does not match the updater version. Rerun the same update command.", "verification");
187
- onProgress("Verifying the global bridge settings and owned relay runtime.");
188
- let setup;
189
- try { setup = JSON.parse(await command([path.join(globalRoot, "scripts", "setup.mjs"), "--check", "--json"], "verification")); }
190
- catch (error) { if (error?.code) throw error; throw failure("UPDATE_SETUP_INCOMPLETE", `Global setup did not return a verifiable result. ${RETRY}`, "verification"); }
191
- if (setup?.configured !== true) throw failure("UPDATE_SETUP_INCOMPLETE", `The global bridge settings are incomplete. ${RETRY}`, "verification");
203
+ for (const check of [false, true]) {
204
+ onProgress(check ? "Verifying the global bridge settings and owned relay runtime." : "Applying managed settings from the installed bridge.");
205
+ let setup;
206
+ try { setup = JSON.parse(await command([path.join(globalRoot, "scripts", "setup.mjs"), ...(check ? ["--check"] : []), "--json"], "verification")); }
207
+ catch (error) { if (error?.code) throw error; throw failure("UPDATE_SETUP_INCOMPLETE", `Global setup did not return a verifiable result. ${RETRY}`, "verification"); }
208
+ if (setup?.configured !== true) throw failure("UPDATE_SETUP_INCOMPLETE", `The global bridge settings are incomplete. ${RETRY}`, "verification");
209
+ }
192
210
  const service = await awaitService(globalRoot, false, "verification");
193
211
  return { updated: true, configured: true, version, previousVersion, migrated, service, opensApps: false, opensChats: false };
194
212
  }
@@ -66,7 +66,7 @@ export function relayServiceRuntimeId(runtime) {
66
66
  return createHash("sha256").update(JSON.stringify([runtime.packageRoot, runtime.nodeExecutable, runtime.packageVersion ?? null, runtime.environment ?? {}])).digest("hex");
67
67
  }
68
68
 
69
- export function readRelayServiceRuntime(directory, { root = ROOT, nodeExecutable = stableNodeBin() } = {}) {
69
+ export function readRelayServiceRuntime(directory, { root = ROOT, nodeExecutable = stableNodeBin(), allowMissingFiles = false } = {}) {
70
70
  let text;
71
71
  try { text = readRegularConfigFile(path.join(directory, "service-runtime.json"), { missing: true, maxBytes: 64 * 1024 }); }
72
72
  catch { throw serviceFailure("SERVICE_RUNTIME_INVALID"); }
@@ -78,11 +78,17 @@ export function readRelayServiceRuntime(directory, { root = ROOT, nodeExecutable
78
78
  || ![value.packageRoot, value.nodeExecutable].every((entry) => typeof entry === "string" && path.isAbsolute(entry) && !/[\u0000-\u001f\u007f]/.test(entry))
79
79
  || value.environment !== undefined && (!value.environment || typeof value.environment !== "object" || Array.isArray(value.environment)
80
80
  || Object.entries(value.environment).some(([name, entry]) => !["HOME", "PATH", "CODEX_HOME", "CLAUDE_CONFIG_DIR", "XDG_CONFIG_HOME"].includes(name)
81
- || typeof entry !== "string" || !entry || /[\u0000-\u001f\u007f]/.test(entry)))
82
- || ![path.join(value.packageRoot, "src", "background-relay-service.mjs"), path.join(value.packageRoot, "src", "background-relay-daemon.mjs"), value.nodeExecutable].every((entry) => {
83
- try { return fs.statSync(entry).isFile(); } catch { return false; }
84
- })) throw serviceFailure("SERVICE_RUNTIME_INVALID");
85
- return value;
81
+ || typeof entry !== "string" || !entry || /[\u0000-\u001f\u007f]/.test(entry)))) throw serviceFailure("SERVICE_RUNTIME_INVALID");
82
+ let filesAvailable = true;
83
+ for (const entry of [path.join(value.packageRoot, "src", "background-relay-service.mjs"), path.join(value.packageRoot, "src", "background-relay-daemon.mjs"), value.nodeExecutable]) {
84
+ try {
85
+ if (!fs.statSync(entry).isFile()) throw serviceFailure("SERVICE_RUNTIME_INVALID");
86
+ } catch (error) {
87
+ if (!allowMissingFiles || error.code !== "ENOENT") throw serviceFailure("SERVICE_RUNTIME_INVALID");
88
+ filesAvailable = false;
89
+ }
90
+ }
91
+ return allowMissingFiles ? { ...value, filesAvailable } : value;
86
92
  }
87
93
 
88
94
  export function acquireRelayServiceLock(root, { pid = process.pid, isAlive = alive, identity = processIdentity } = {}) {
@@ -31,7 +31,7 @@ import { BACKGROUND_WORKER_GUIDANCE, registerBackgroundTools } from "./backgroun
31
31
 
32
32
  exitForVersionRequest(import.meta.url);
33
33
 
34
- const VERSION = "1.26.4";
34
+ const VERSION = "1.26.5";
35
35
  void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
36
36
  const FORWARD_MIN_INTERVAL_MS = 5000;
37
37
  const FORWARD_MAX_PER_SESSION = 50;
package/src/index.mjs CHANGED
@@ -44,7 +44,7 @@ import { BACKGROUND_WORKER_GUIDANCE, registerBackgroundTools } from "./backgroun
44
44
 
45
45
  exitForVersionRequest(import.meta.url);
46
46
 
47
- const VERSION = "1.26.4";
47
+ const VERSION = "1.26.5";
48
48
  void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
49
49
  const log = (msg) => process.stderr.write(`[codex-mcp-bridge] ${msg}\n`);
50
50
 
@@ -43,7 +43,7 @@ import { remoteCodexSocketPath, remoteNativeAdapter, remoteRequestResponse } fro
43
43
 
44
44
  exitForVersionRequest(import.meta.url);
45
45
 
46
- const VERSION = "1.26.4";
46
+ const VERSION = "1.26.5";
47
47
  const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
48
48
 
49
49
  function errorResponse(code, message, sent, details) {
package/src/setup.mjs CHANGED
@@ -179,7 +179,7 @@ export function checkSetup({ env = process.env, platform = process.platform, roo
179
179
 
180
180
  export function runSetup({ args = [], env = process.env, platform = process.platform, root = ROOT, run = execute, resolve = resolveCodexBin } = {}) {
181
181
  const options = setupArguments(args);
182
- if (options.help) return { help: "Usage: codex-bridge-setup [--check|--dry-run] [--json] [--no-download]\nUpdate the package and apply managed settings with: npm install -g @minhspark/codex-mcp-bridge@latest --allow-scripts=@minhspark/codex-mcp-bridge --foreground-scripts\nGlobal npm installation configures Claude Desktop, Claude Code, Codex, Antigravity and the independent worker relay automatically. CODEX_BRIDGE_SKIP_SETUP=1 opts out. Saved configuration, background relay health and loaded native app connections are verified separately." };
182
+ if (options.help) return { help: `Usage: codex-bridge-setup [--check|--dry-run] [--json] [--no-download]\nUpdate the package and apply managed settings with: ${platform === "win32" ? "npx.cmd" : "npx"} --yes --ignore-scripts --package=@minhspark/codex-mcp-bridge@latest -- codex-bridge-update${platform === "win32" ? " --user" : ""}\nThe updater installs the package with lifecycle scripts disabled, then directly configures Claude Desktop, Claude Code, Codex, Antigravity and the independent worker relay. CODEX_BRIDGE_SKIP_SETUP=1 opts out. Saved configuration, background relay health and loaded native app connections are verified separately.` };
183
183
  if (options.postinstall && !automaticSetupEnabled(env)) return { skipped: true, reason: "Automatic configuration runs only for a global npm install; use codex-bridge-setup for manual setup." };
184
184
  if (options.check) return checkSetup({ env, platform, root, run, resolve });
185
185
  const native = platform === "win32" || platform === "darwin";