codex-multi-auth 1.2.5 → 1.2.7
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/README.md +28 -17
- package/package.json +1 -1
- package/scripts/codex-bin-resolver.js +162 -7
- package/scripts/codex.js +218 -26
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/codex-multi-auth)
|
|
4
4
|
[](https://www.npmjs.com/package/codex-multi-auth)
|
|
5
5
|
|
|
6
|
-
Codex CLI-first multi-account OAuth manager for the official `@openai/codex`
|
|
6
|
+
Codex CLI-first multi-account OAuth manager for the official Codex CLI, including the `@openai/codex` npm launcher and native `codex` installs.
|
|
7
7
|
|
|
8
8
|
<img width="1270" height="729" alt="2026-02-28 12_54_58-prompt txt - Notepads" src="https://github.com/user-attachments/assets/0cecb77e-a6d3-432a-ba48-3577db0c7093" />
|
|
9
9
|
|
|
@@ -11,6 +11,7 @@ Codex CLI-first multi-account OAuth manager for the official `@openai/codex` CLI
|
|
|
11
11
|
> [!NOTE]
|
|
12
12
|
> Legacy scoped prerelease package `@ndycode/codex-multi-auth` is migration-only.
|
|
13
13
|
> Use `codex-multi-auth` for all new installs.
|
|
14
|
+
|
|
14
15
|
## What You Get
|
|
15
16
|
|
|
16
17
|
- Canonical `codex auth ...` workflow for account login, switching, checks, and diagnostics
|
|
@@ -19,7 +20,7 @@ Codex CLI-first multi-account OAuth manager for the official `@openai/codex` CLI
|
|
|
19
20
|
- Interactive dashboard for account actions and settings
|
|
20
21
|
- Experimental settings tab for staged sync, backup, and refresh-guard controls
|
|
21
22
|
- Forecast, report, fix, and doctor commands for operational safety
|
|
22
|
-
- Runtime
|
|
23
|
+
- Runtime counters, budget/cooldown state, and multi-auth probe visibility in `codex auth status` / `codex auth report`
|
|
23
24
|
- Flagged account verification and restore flow
|
|
24
25
|
- Session affinity and live account sync controls
|
|
25
26
|
- Proactive refresh and preemptive quota deferral controls
|
|
@@ -71,6 +72,8 @@ codex-multi-auth --version
|
|
|
71
72
|
codex auth status
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
Any official install path is fine as long as `codex` is on `PATH`: `npm i -g @openai/codex`, `brew install --cask codex`, or an official release binary.
|
|
76
|
+
|
|
74
77
|
</details>
|
|
75
78
|
|
|
76
79
|
<details>
|
|
@@ -105,6 +108,13 @@ npm i -g codex-multi-auth
|
|
|
105
108
|
codex auth login
|
|
106
109
|
```
|
|
107
110
|
|
|
111
|
+
If you already installed the official native CLI via Homebrew or a release binary, you only need:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm i -g codex-multi-auth
|
|
115
|
+
codex auth login
|
|
116
|
+
```
|
|
117
|
+
|
|
108
118
|
Verify the wrapper and the new account:
|
|
109
119
|
|
|
110
120
|
```bash
|
|
@@ -163,7 +173,7 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
|
|
|
163
173
|
- active requests use a bounded outbound request budget so one prompt cannot walk the full pool indefinitely
|
|
164
174
|
- repeated cross-account 5xx bursts trigger a short cooldown instead of continuing aggressive rotation
|
|
165
175
|
- proactive refresh is staggered to reduce background refresh bursts
|
|
166
|
-
- `codex auth status` surfaces recent runtime request metrics in text output, and `codex auth report --json` exposes the machine-readable cooldown/runtime snapshot
|
|
176
|
+
- `codex auth status` surfaces recent runtime request metrics in text output, and `codex auth report --json` exposes the machine-readable cooldown/runtime snapshot
|
|
167
177
|
|
|
168
178
|
---
|
|
169
179
|
|
|
@@ -196,12 +206,12 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
|
|
|
196
206
|
| File | Default path |
|
|
197
207
|
| --- | --- |
|
|
198
208
|
| Settings | `~/.codex/multi-auth/settings.json` |
|
|
199
|
-
| Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` |
|
|
200
|
-
| Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` |
|
|
201
|
-
| Quota cache | `~/.codex/multi-auth/quota-cache.json` |
|
|
202
|
-
| Runtime observability | `~/.codex/multi-auth/runtime-observability.json` |
|
|
203
|
-
| Logs | `~/.codex/multi-auth/logs/codex-plugin/` |
|
|
204
|
-
| Per-project accounts | `~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.json` |
|
|
209
|
+
| Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` |
|
|
210
|
+
| Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` |
|
|
211
|
+
| Quota cache | `~/.codex/multi-auth/quota-cache.json` |
|
|
212
|
+
| Runtime observability | `~/.codex/multi-auth/runtime-observability.json` |
|
|
213
|
+
| Logs | `~/.codex/multi-auth/logs/codex-plugin/` |
|
|
214
|
+
| Per-project accounts | `~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.json` |
|
|
205
215
|
|
|
206
216
|
Override root with `CODEX_MULTI_AUTH_DIR=<path>`.
|
|
207
217
|
|
|
@@ -273,12 +283,12 @@ codex auth login
|
|
|
273
283
|
<details>
|
|
274
284
|
<summary><b>Common symptoms</b></summary>
|
|
275
285
|
|
|
276
|
-
- `codex auth` unrecognized: run `where codex`, then follow `docs/troubleshooting.md` for routing fallback commands
|
|
286
|
+
- `codex auth` unrecognized: run `where codex` or `which codex`, then follow `docs/troubleshooting.md` for routing fallback commands
|
|
277
287
|
- Switch succeeds but wrong account appears active: run `codex auth switch <index>`, then restart session
|
|
278
|
-
- Requests fail fast with a pool cooldown message: wait for the cooldown window or inspect `codex auth status`
|
|
279
|
-
- Requests fail fast after repeated upstream 5xx errors: inspect `codex auth report --json` for runtime traffic and cooldown details
|
|
280
|
-
- Storage cleanup fails with `EBUSY` / `EPERM` (Windows): run `codex auth doctor --fix` to retry, or manually remove `~/.codex/multi-auth/<project-key>/` and re-login
|
|
281
|
-
- OAuth callback on port `1455` fails: free the port and re-run `codex auth login`
|
|
288
|
+
- Requests fail fast with a pool cooldown message: wait for the cooldown window or inspect `codex auth status`
|
|
289
|
+
- Requests fail fast after repeated upstream 5xx errors: inspect `codex auth report --json` for runtime traffic and cooldown details
|
|
290
|
+
- Storage cleanup fails with `EBUSY` / `EPERM` (Windows): run `codex auth doctor --fix` to retry, or manually remove `~/.codex/multi-auth/<project-key>/` and re-login
|
|
291
|
+
- OAuth callback on port `1455` fails: free the port and re-run `codex auth login`
|
|
282
292
|
- Browser launch is blocked or you are in a headless shell: re-run `codex auth login --manual` or set `CODEX_AUTH_NO_BROWSER=1`
|
|
283
293
|
- `missing field id_token` / `token_expired` / `refresh_token_reused`: re-login affected account
|
|
284
294
|
|
|
@@ -321,9 +331,10 @@ codex auth doctor --json
|
|
|
321
331
|
|
|
322
332
|
## Release Notes
|
|
323
333
|
|
|
324
|
-
- Current stable: [docs/releases/v1.2.
|
|
325
|
-
- Previous stable: [docs/releases/v1.2.
|
|
326
|
-
- Earlier stable: [docs/releases/v1.2.
|
|
334
|
+
- Current stable: [docs/releases/v1.2.7.md](docs/releases/v1.2.7.md)
|
|
335
|
+
- Previous stable: [docs/releases/v1.2.6.md](docs/releases/v1.2.6.md)
|
|
336
|
+
- Earlier stable: [docs/releases/v1.2.5.md](docs/releases/v1.2.5.md)
|
|
337
|
+
- Full release archive: [docs/README.md#release-history](docs/README.md#release-history)
|
|
327
338
|
- Archived prerelease: [docs/releases/v0.1.0-beta.0.md](docs/releases/v0.1.0-beta.0.md)
|
|
328
339
|
|
|
329
340
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-multi-auth",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Multi-account OAuth manager and codex auth wrapper for the official @openai/codex CLI, with switching, health checks, and recovery tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { basename, delimiter, dirname, extname, join } from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
|
+
function isJavaScriptEntryPath(candidate) {
|
|
9
|
+
const extension = extname(candidate).toLowerCase();
|
|
10
|
+
return extension === ".js" || extension === ".mjs" || extension === ".cjs";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createResolvedCodexBin(path) {
|
|
14
|
+
return {
|
|
15
|
+
path,
|
|
16
|
+
launchWithNode: isJavaScriptEntryPath(path),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeResolvedPath(candidatePath, realpathSyncImpl) {
|
|
21
|
+
try {
|
|
22
|
+
return realpathSyncImpl(candidatePath);
|
|
23
|
+
} catch {
|
|
24
|
+
return candidatePath;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveWrapperScriptPath(moduleUrl, realpathSyncImpl) {
|
|
29
|
+
return normalizeResolvedPath(fileURLToPath(moduleUrl), realpathSyncImpl);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DEFAULT_WRAPPER_MODULE_URL = new URL("./codex.js", import.meta.url).href;
|
|
33
|
+
|
|
8
34
|
function defaultResolvePackageBin(moduleUrl) {
|
|
9
35
|
try {
|
|
10
36
|
const require = createRequire(moduleUrl);
|
|
@@ -26,26 +52,141 @@ function resolveWindowsCmdPath(env) {
|
|
|
26
52
|
return "cmd.exe";
|
|
27
53
|
}
|
|
28
54
|
|
|
55
|
+
export function splitPathEntries(pathValue) {
|
|
56
|
+
if (typeof pathValue !== "string" || pathValue.trim().length === 0) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
return pathValue
|
|
60
|
+
.split(delimiter)
|
|
61
|
+
.map((entry) => entry.trim())
|
|
62
|
+
.filter((entry) => entry.length > 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolvePathExecutableName(platform) {
|
|
66
|
+
return platform === "win32" ? "codex.exe" : "codex";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveCandidateExecutableNames(platform) {
|
|
70
|
+
if (platform !== "win32") {
|
|
71
|
+
return [resolvePathExecutableName(platform)];
|
|
72
|
+
}
|
|
73
|
+
return ["codex.exe", "codex"];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveCodexExecutableFromPath(
|
|
77
|
+
pathEntries,
|
|
78
|
+
platform,
|
|
79
|
+
existsSyncImpl,
|
|
80
|
+
selfScriptPath,
|
|
81
|
+
realpathSyncImpl,
|
|
82
|
+
) {
|
|
83
|
+
for (const entry of pathEntries) {
|
|
84
|
+
for (const executableName of resolveCandidateExecutableNames(platform)) {
|
|
85
|
+
const candidate = join(entry, executableName);
|
|
86
|
+
if (!existsSyncImpl(candidate)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (
|
|
90
|
+
selfScriptPath &&
|
|
91
|
+
normalizeResolvedPath(candidate, realpathSyncImpl) === selfScriptPath
|
|
92
|
+
) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
return candidate;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeWhereOutput(stdout) {
|
|
102
|
+
return stdout
|
|
103
|
+
.split(/\r?\n/)
|
|
104
|
+
.map((line) => line.trim())
|
|
105
|
+
.filter((line) => line.length > 0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveCodexExecutableFromSystemPath(
|
|
109
|
+
env,
|
|
110
|
+
platform,
|
|
111
|
+
spawnSyncImpl,
|
|
112
|
+
existsSyncImpl,
|
|
113
|
+
selfScriptPath,
|
|
114
|
+
realpathSyncImpl,
|
|
115
|
+
) {
|
|
116
|
+
const pathEntries = splitPathEntries(env.PATH ?? env.Path ?? "");
|
|
117
|
+
const fromEnvPath = resolveCodexExecutableFromPath(
|
|
118
|
+
pathEntries,
|
|
119
|
+
platform,
|
|
120
|
+
existsSyncImpl,
|
|
121
|
+
selfScriptPath,
|
|
122
|
+
realpathSyncImpl,
|
|
123
|
+
);
|
|
124
|
+
if (fromEnvPath) {
|
|
125
|
+
return fromEnvPath;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const lookupResult =
|
|
130
|
+
platform === "win32"
|
|
131
|
+
? spawnSyncImpl(resolveWindowsCmdPath(env), ["/d", "/s", "/c", "where codex"], {
|
|
132
|
+
encoding: "utf8",
|
|
133
|
+
env,
|
|
134
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
135
|
+
timeout: 5000,
|
|
136
|
+
windowsHide: true,
|
|
137
|
+
})
|
|
138
|
+
: spawnSyncImpl("which", ["codex"], {
|
|
139
|
+
encoding: "utf8",
|
|
140
|
+
env,
|
|
141
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
142
|
+
timeout: 5000,
|
|
143
|
+
});
|
|
144
|
+
if (lookupResult.status !== 0) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
for (const candidate of normalizeWhereOutput(lookupResult.stdout)) {
|
|
148
|
+
if (!existsSyncImpl(candidate)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (
|
|
152
|
+
selfScriptPath &&
|
|
153
|
+
normalizeResolvedPath(candidate, realpathSyncImpl) === selfScriptPath
|
|
154
|
+
) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const fileName = basename(candidate).toLowerCase();
|
|
158
|
+
if (fileName === "codex" || fileName === "codex.exe") {
|
|
159
|
+
return candidate;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Ignore and fall through.
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
29
169
|
export function resolveRealCodexBin(options = {}) {
|
|
30
170
|
const {
|
|
31
171
|
env = process.env,
|
|
32
172
|
argv = process.argv,
|
|
33
173
|
platform = process.platform,
|
|
34
|
-
moduleUrl =
|
|
174
|
+
moduleUrl = DEFAULT_WRAPPER_MODULE_URL,
|
|
35
175
|
existsSyncImpl = existsSync,
|
|
176
|
+
realpathSyncImpl = realpathSync,
|
|
36
177
|
spawnSyncImpl = spawnSync,
|
|
37
178
|
resolvePackageBin = defaultResolvePackageBin,
|
|
38
179
|
} = options;
|
|
39
180
|
|
|
40
181
|
const override = (env.CODEX_MULTI_AUTH_REAL_CODEX_BIN ?? "").trim();
|
|
41
182
|
if (override.length > 0) {
|
|
42
|
-
if (existsSyncImpl(override)) return override;
|
|
183
|
+
if (existsSyncImpl(override)) return createResolvedCodexBin(override);
|
|
43
184
|
return null;
|
|
44
185
|
}
|
|
45
186
|
|
|
46
187
|
const resolved = resolvePackageBin(moduleUrl);
|
|
47
188
|
if (typeof resolved === "string" && resolved.length > 0 && existsSyncImpl(resolved)) {
|
|
48
|
-
return resolved;
|
|
189
|
+
return createResolvedCodexBin(resolved);
|
|
49
190
|
}
|
|
50
191
|
|
|
51
192
|
const searchRoots = [];
|
|
@@ -65,7 +206,7 @@ export function resolveRealCodexBin(options = {}) {
|
|
|
65
206
|
|
|
66
207
|
for (const root of searchRoots) {
|
|
67
208
|
const candidate = join(root, "@openai", "codex", "bin", "codex.js");
|
|
68
|
-
if (existsSyncImpl(candidate)) return candidate;
|
|
209
|
+
if (existsSyncImpl(candidate)) return createResolvedCodexBin(candidate);
|
|
69
210
|
}
|
|
70
211
|
|
|
71
212
|
try {
|
|
@@ -88,12 +229,26 @@ export function resolveRealCodexBin(options = {}) {
|
|
|
88
229
|
const globalRoot = rootResult.stdout.trim();
|
|
89
230
|
if (globalRoot.length > 0) {
|
|
90
231
|
const globalBin = join(globalRoot, "@openai", "codex", "bin", "codex.js");
|
|
91
|
-
if (existsSyncImpl(globalBin)) return globalBin;
|
|
232
|
+
if (existsSyncImpl(globalBin)) return createResolvedCodexBin(globalBin);
|
|
92
233
|
}
|
|
93
234
|
}
|
|
94
235
|
} catch {
|
|
95
236
|
// Ignore and fall through to null.
|
|
96
237
|
}
|
|
97
238
|
|
|
239
|
+
const selfScriptPath = resolveWrapperScriptPath(moduleUrl, realpathSyncImpl);
|
|
240
|
+
|
|
241
|
+
const nativeCodexBin = resolveCodexExecutableFromSystemPath(
|
|
242
|
+
env,
|
|
243
|
+
platform,
|
|
244
|
+
spawnSyncImpl,
|
|
245
|
+
existsSyncImpl,
|
|
246
|
+
selfScriptPath,
|
|
247
|
+
realpathSyncImpl,
|
|
248
|
+
);
|
|
249
|
+
if (nativeCodexBin) {
|
|
250
|
+
return createResolvedCodexBin(nativeCodexBin);
|
|
251
|
+
}
|
|
252
|
+
|
|
98
253
|
return null;
|
|
99
254
|
}
|
package/scripts/codex.js
CHANGED
|
@@ -18,7 +18,10 @@ import { homedir, tmpdir } from "node:os";
|
|
|
18
18
|
import { basename, delimiter, dirname, join, resolve as resolvePath } from "node:path";
|
|
19
19
|
import process from "node:process";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
resolveRealCodexBin as resolveRealCodexBinFromEnvironment,
|
|
23
|
+
splitPathEntries,
|
|
24
|
+
} from "./codex-bin-resolver.js";
|
|
22
25
|
import { normalizeAuthAlias, shouldHandleMultiAuthAuth } from "./codex-routing.js";
|
|
23
26
|
|
|
24
27
|
const RETRYABLE_SHADOW_HOME_CLEANUP_CODES = new Set(["EBUSY", "EPERM", "ENOTEMPTY"]);
|
|
@@ -124,11 +127,17 @@ async function autoSyncManagerActiveSelectionIfEnabled() {
|
|
|
124
127
|
function resolveRealCodexBin() {
|
|
125
128
|
const override = (process.env.CODEX_MULTI_AUTH_REAL_CODEX_BIN ?? "").trim();
|
|
126
129
|
if (override.length > 0) {
|
|
127
|
-
if (existsSync(override))
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
if (!existsSync(override)) {
|
|
131
|
+
console.error(
|
|
132
|
+
`CODEX_MULTI_AUTH_REAL_CODEX_BIN is set but missing: ${override}`,
|
|
133
|
+
);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return resolveRealCodexBinFromEnvironment({
|
|
137
|
+
moduleUrl: import.meta.url,
|
|
138
|
+
env: process.env,
|
|
139
|
+
existsSyncImpl: existsSync,
|
|
140
|
+
});
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
return resolveRealCodexBinFromEnvironment({ moduleUrl: import.meta.url });
|
|
@@ -145,7 +154,9 @@ function forwardToRealCodex(codexBin, args, env = process.env, cleanup) {
|
|
|
145
154
|
resolve(exitCode);
|
|
146
155
|
};
|
|
147
156
|
|
|
148
|
-
const
|
|
157
|
+
const command = codexBin.launchWithNode ? process.execPath : codexBin.path;
|
|
158
|
+
const commandArgs = codexBin.launchWithNode ? [codexBin.path, ...args] : args;
|
|
159
|
+
const child = spawn(command, commandArgs, {
|
|
149
160
|
stdio: "inherit",
|
|
150
161
|
env,
|
|
151
162
|
});
|
|
@@ -661,6 +672,189 @@ function resolveOriginalMultiAuthDir(env) {
|
|
|
661
672
|
return undefined;
|
|
662
673
|
}
|
|
663
674
|
|
|
675
|
+
async function loadRuntimeObservabilityModule() {
|
|
676
|
+
try {
|
|
677
|
+
const mod = await import("../dist/lib/runtime/runtime-observability.js");
|
|
678
|
+
if (
|
|
679
|
+
typeof mod.loadPersistedRuntimeObservabilitySnapshot !== "function" ||
|
|
680
|
+
typeof mod.mutateRuntimeObservabilitySnapshot !== "function"
|
|
681
|
+
) {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
return mod;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
if (
|
|
687
|
+
error &&
|
|
688
|
+
typeof error === "object" &&
|
|
689
|
+
"code" in error &&
|
|
690
|
+
error.code === "ERR_MODULE_NOT_FOUND"
|
|
691
|
+
) {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
throw error;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function isPureHelpOrVersionArgs(rawArgs) {
|
|
699
|
+
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
return rawArgs.every((arg) =>
|
|
703
|
+
typeof arg === "string" && ["--help", "-h", "--version", "-V"].includes(arg),
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function consumesNextArg(arg) {
|
|
708
|
+
return new Set([
|
|
709
|
+
"-c",
|
|
710
|
+
"--config",
|
|
711
|
+
"--enable",
|
|
712
|
+
"--disable",
|
|
713
|
+
"--remote",
|
|
714
|
+
"--remote-auth-token-env",
|
|
715
|
+
"-i",
|
|
716
|
+
"--image",
|
|
717
|
+
"-m",
|
|
718
|
+
"--model",
|
|
719
|
+
"--local-provider",
|
|
720
|
+
"-p",
|
|
721
|
+
"--profile",
|
|
722
|
+
"-s",
|
|
723
|
+
"--sandbox",
|
|
724
|
+
"-a",
|
|
725
|
+
"--ask-for-approval",
|
|
726
|
+
"-C",
|
|
727
|
+
"--cd",
|
|
728
|
+
"--add-dir",
|
|
729
|
+
"--output-schema",
|
|
730
|
+
"--color",
|
|
731
|
+
"-o",
|
|
732
|
+
"--output-last-message",
|
|
733
|
+
]).has(arg);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function shouldTrackForwardedRuntimeObservability(rawArgs) {
|
|
737
|
+
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
if (isPureHelpOrVersionArgs(rawArgs)) {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const requestCommands = new Set(["exec", "review", "resume", "fork"]);
|
|
745
|
+
const nonRequestCommands = new Set([
|
|
746
|
+
"help",
|
|
747
|
+
"completion",
|
|
748
|
+
"login",
|
|
749
|
+
"logout",
|
|
750
|
+
"mcp",
|
|
751
|
+
"mcp-server",
|
|
752
|
+
"app-server",
|
|
753
|
+
"sandbox",
|
|
754
|
+
"debug",
|
|
755
|
+
"apply",
|
|
756
|
+
"cloud",
|
|
757
|
+
"features",
|
|
758
|
+
"auth",
|
|
759
|
+
]);
|
|
760
|
+
|
|
761
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
762
|
+
const arg = rawArgs[i];
|
|
763
|
+
if (typeof arg !== "string" || arg.length === 0) continue;
|
|
764
|
+
if (arg === "--") {
|
|
765
|
+
return i + 1 < rawArgs.length;
|
|
766
|
+
}
|
|
767
|
+
if (arg.startsWith("--config=")) {
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (arg.startsWith("--") || (arg.startsWith("-") && arg !== "-")) {
|
|
771
|
+
if (consumesNextArg(arg)) {
|
|
772
|
+
i += 1;
|
|
773
|
+
}
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (requestCommands.has(arg)) {
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
if (nonRequestCommands.has(arg)) {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function createRuntimeSnapshotChangeToken(snapshot) {
|
|
789
|
+
return JSON.stringify({
|
|
790
|
+
updatedAt: snapshot?.updatedAt ?? null,
|
|
791
|
+
responsesRequests: snapshot?.responsesRequests ?? null,
|
|
792
|
+
authRefreshRequests: snapshot?.authRefreshRequests ?? null,
|
|
793
|
+
diagnosticProbeRequests: snapshot?.diagnosticProbeRequests ?? null,
|
|
794
|
+
totalRequests: snapshot?.runtimeMetrics?.totalRequests ?? null,
|
|
795
|
+
successfulRequests: snapshot?.runtimeMetrics?.successfulRequests ?? null,
|
|
796
|
+
failedRequests: snapshot?.runtimeMetrics?.failedRequests ?? null,
|
|
797
|
+
lastRequestAt: snapshot?.runtimeMetrics?.lastRequestAt ?? null,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
async function loadRuntimeSnapshotWithRetry(runtimeObservabilityModule) {
|
|
802
|
+
let snapshot = null;
|
|
803
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
804
|
+
snapshot =
|
|
805
|
+
(await runtimeObservabilityModule.loadPersistedRuntimeObservabilitySnapshot()) ??
|
|
806
|
+
null;
|
|
807
|
+
if (snapshot) {
|
|
808
|
+
return snapshot;
|
|
809
|
+
}
|
|
810
|
+
if (attempt < 4) {
|
|
811
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return snapshot;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
async function withForwardedRuntimeObservability(rawArgs, runForwardedCommand) {
|
|
818
|
+
if (!shouldTrackForwardedRuntimeObservability(rawArgs)) {
|
|
819
|
+
return runForwardedCommand();
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const runtimeObservabilityModule = await loadRuntimeObservabilityModule();
|
|
823
|
+
if (!runtimeObservabilityModule) {
|
|
824
|
+
return runForwardedCommand();
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const beforeSnapshot = await loadRuntimeSnapshotWithRetry(runtimeObservabilityModule);
|
|
828
|
+
const beforeToken = createRuntimeSnapshotChangeToken(beforeSnapshot);
|
|
829
|
+
const startedAt = Date.now();
|
|
830
|
+
const exitCode = await runForwardedCommand();
|
|
831
|
+
const afterSnapshot = await loadRuntimeSnapshotWithRetry(runtimeObservabilityModule);
|
|
832
|
+
const afterToken = createRuntimeSnapshotChangeToken(afterSnapshot);
|
|
833
|
+
if (afterToken !== beforeToken) {
|
|
834
|
+
return exitCode;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
runtimeObservabilityModule.mutateRuntimeObservabilitySnapshot((snapshot) => {
|
|
838
|
+
snapshot.currentRequestId = null;
|
|
839
|
+
snapshot.responsesRequests += 1;
|
|
840
|
+
snapshot.runtimeMetrics.totalRequests += 1;
|
|
841
|
+
snapshot.runtimeMetrics.responsesRequests += 1;
|
|
842
|
+
snapshot.runtimeMetrics.cumulativeLatencyMs += Math.max(0, Date.now() - startedAt);
|
|
843
|
+
snapshot.runtimeMetrics.lastRequestAt = Date.now();
|
|
844
|
+
if (!snapshot.runtimeMetrics.startedAt) {
|
|
845
|
+
snapshot.runtimeMetrics.startedAt = startedAt;
|
|
846
|
+
}
|
|
847
|
+
if (exitCode === 0) {
|
|
848
|
+
snapshot.runtimeMetrics.successfulRequests += 1;
|
|
849
|
+
snapshot.runtimeMetrics.lastError = null;
|
|
850
|
+
} else {
|
|
851
|
+
snapshot.runtimeMetrics.failedRequests += 1;
|
|
852
|
+
snapshot.runtimeMetrics.lastError = `forwarded-codex-exit:${exitCode}`;
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
return exitCode;
|
|
856
|
+
}
|
|
857
|
+
|
|
664
858
|
function createCompatibilityCodexHome(
|
|
665
859
|
processedArgs,
|
|
666
860
|
requestedModel,
|
|
@@ -810,16 +1004,6 @@ function shouldInstallWindowsBatchShimGuard() {
|
|
|
810
1004
|
return override !== "0";
|
|
811
1005
|
}
|
|
812
1006
|
|
|
813
|
-
function splitPathEntries(pathValue) {
|
|
814
|
-
if (typeof pathValue !== "string" || pathValue.trim().length === 0) {
|
|
815
|
-
return [];
|
|
816
|
-
}
|
|
817
|
-
return pathValue
|
|
818
|
-
.split(delimiter)
|
|
819
|
-
.map((entry) => entry.trim())
|
|
820
|
-
.filter((entry) => entry.length > 0);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
1007
|
function resolveWindowsShimDirectoryFromInvocation() {
|
|
824
1008
|
const invokedScript = (process.argv[1] ?? "").trim();
|
|
825
1009
|
if (invokedScript.length === 0) return null;
|
|
@@ -955,7 +1139,13 @@ function ensureWindowsShellShim(filePath, desiredContent, options = {}) {
|
|
|
955
1139
|
}
|
|
956
1140
|
const looksLikeStockOpenAiShim =
|
|
957
1141
|
currentContent.includes("node_modules\\@openai\\codex\\bin\\codex.js") ||
|
|
958
|
-
currentContent.includes("node_modules/@openai/codex/bin/codex.js")
|
|
1142
|
+
currentContent.includes("node_modules/@openai/codex/bin/codex.js") ||
|
|
1143
|
+
currentContent.includes("@openai\\codex-win32-") ||
|
|
1144
|
+
currentContent.includes("@openai/codex-win32-") ||
|
|
1145
|
+
currentContent.includes("vendor\\x86_64-pc-windows-msvc\\codex\\codex.exe") ||
|
|
1146
|
+
currentContent.includes("vendor\\aarch64-pc-windows-msvc\\codex\\codex.exe") ||
|
|
1147
|
+
currentContent.includes("vendor/x86_64-pc-windows-msvc/codex/codex.exe") ||
|
|
1148
|
+
currentContent.includes("vendor/aarch64-pc-windows-msvc/codex/codex.exe");
|
|
959
1149
|
if (looksLikeStockOpenAiShim) {
|
|
960
1150
|
try {
|
|
961
1151
|
writeFileSync(filePath, desiredContent, { encoding: "utf8", mode: 0o755 });
|
|
@@ -1135,9 +1325,9 @@ async function main() {
|
|
|
1135
1325
|
if (!realCodexBin) {
|
|
1136
1326
|
console.error(
|
|
1137
1327
|
[
|
|
1138
|
-
"Could not locate the official Codex CLI
|
|
1139
|
-
"Install it
|
|
1140
|
-
"Or set CODEX_MULTI_AUTH_REAL_CODEX_BIN to
|
|
1328
|
+
"Could not locate the official Codex CLI.",
|
|
1329
|
+
"Install it with npm, Homebrew, or an official native release so `codex` is on PATH.",
|
|
1330
|
+
"Or set CODEX_MULTI_AUTH_REAL_CODEX_BIN to the full path of either codex or @openai/codex/bin/codex.js.",
|
|
1141
1331
|
].join("\n"),
|
|
1142
1332
|
);
|
|
1143
1333
|
return 1;
|
|
@@ -1149,11 +1339,13 @@ async function main() {
|
|
|
1149
1339
|
forwardArgs,
|
|
1150
1340
|
requestedModel,
|
|
1151
1341
|
);
|
|
1152
|
-
return
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1342
|
+
return withForwardedRuntimeObservability(rawArgs, () =>
|
|
1343
|
+
forwardToRealCodex(
|
|
1344
|
+
realCodexBin,
|
|
1345
|
+
compatibility.args,
|
|
1346
|
+
compatibility.env,
|
|
1347
|
+
compatibility.cleanup,
|
|
1348
|
+
),
|
|
1157
1349
|
);
|
|
1158
1350
|
}
|
|
1159
1351
|
|