codex-multi-auth 2.3.3 → 2.5.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.
Files changed (41) hide show
  1. package/.codex-plugin/plugin.json +9 -10
  2. package/README.md +423 -421
  3. package/config/README.md +1 -1
  4. package/config/codex-modern.json +139 -0
  5. package/dist/lib/constants.d.ts +19 -0
  6. package/dist/lib/constants.d.ts.map +1 -1
  7. package/dist/lib/constants.js +19 -0
  8. package/dist/lib/request/helpers/model-map.d.ts +2 -1
  9. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  10. package/dist/lib/request/helpers/model-map.js +96 -0
  11. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  12. package/dist/lib/request/request-transformer.js +26 -13
  13. package/dist/lib/runtime/rotation-proxy-state.d.ts +7 -0
  14. package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
  15. package/dist/lib/runtime/rotation-server-types.d.ts +9 -0
  16. package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
  17. package/dist/lib/runtime-rotation-proxy.d.ts +10 -0
  18. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  19. package/dist/lib/runtime-rotation-proxy.js +38 -1
  20. package/dist/lib/types.d.ts +8 -2
  21. package/dist/lib/types.d.ts.map +1 -1
  22. package/dist/lib/usage/pricing.d.ts.map +1 -1
  23. package/dist/lib/usage/pricing.js +18 -0
  24. package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -2
  25. package/node_modules/@codex-ai/plugin/dist/index.js +2 -2
  26. package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -42
  27. package/node_modules/@codex-ai/plugin/dist/tool.js +29 -29
  28. package/node_modules/@codex-ai/plugin/package.json +9 -9
  29. package/node_modules/@codex-ai/sdk/dist/index.d.ts +4 -4
  30. package/node_modules/@codex-ai/sdk/dist/index.js +2 -2
  31. package/node_modules/@codex-ai/sdk/package.json +8 -8
  32. package/package.json +196 -196
  33. package/scripts/codex.js +189 -8
  34. package/vendor/codex-ai-plugin/dist/index.d.ts +2 -2
  35. package/vendor/codex-ai-plugin/dist/index.js +2 -2
  36. package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -42
  37. package/vendor/codex-ai-plugin/dist/tool.js +29 -29
  38. package/vendor/codex-ai-plugin/package.json +9 -9
  39. package/vendor/codex-ai-sdk/dist/index.d.ts +4 -4
  40. package/vendor/codex-ai-sdk/dist/index.js +2 -2
  41. package/vendor/codex-ai-sdk/package.json +8 -8
package/package.json CHANGED
@@ -1,196 +1,196 @@
1
- {
2
- "name": "codex-multi-auth",
3
- "version": "2.3.3",
4
- "description": "Codex CLI multi-account OAuth manager with account switching, health checks, runtime rotation, diagnostics, and recovery tools for @openai/codex",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "types": "./dist/index.d.ts",
10
- "import": "./dist/index.js",
11
- "default": "./dist/index.js"
12
- },
13
- "./auth": {
14
- "types": "./dist/lib/auth/index.d.ts",
15
- "import": "./dist/lib/auth/index.js",
16
- "default": "./dist/lib/auth/index.js"
17
- },
18
- "./storage": {
19
- "types": "./dist/lib/storage.d.ts",
20
- "import": "./dist/lib/storage.js",
21
- "default": "./dist/lib/storage.js"
22
- },
23
- "./config": {
24
- "types": "./dist/lib/config.d.ts",
25
- "import": "./dist/lib/config.js",
26
- "default": "./dist/lib/config.js"
27
- },
28
- "./request": {
29
- "types": "./dist/lib/request/index.d.ts",
30
- "import": "./dist/lib/request/index.js",
31
- "default": "./dist/lib/request/index.js"
32
- },
33
- "./cli": {
34
- "types": "./dist/lib/codex-cli/index.d.ts",
35
- "import": "./dist/lib/codex-cli/index.js",
36
- "default": "./dist/lib/codex-cli/index.js"
37
- },
38
- "./package.json": "./package.json"
39
- },
40
- "type": "module",
41
- "license": "MIT",
42
- "author": "ndycode",
43
- "repository": {
44
- "type": "git",
45
- "url": "git+https://github.com/ndycode/codex-multi-auth.git"
46
- },
47
- "keywords": [
48
- "openai",
49
- "codex",
50
- "codex-cli",
51
- "codex-auth",
52
- "codex-multi-account",
53
- "chatgpt",
54
- "oauth",
55
- "oauth2",
56
- "pkce",
57
- "multi-account",
58
- "account-switching",
59
- "oauth-manager",
60
- "runtime-rotation",
61
- "responses-api",
62
- "local-proxy",
63
- "quota-management",
64
- "diagnostics",
65
- "fault-tolerance",
66
- "terminal-ui",
67
- "cli-tool",
68
- "typescript",
69
- "nodejs",
70
- "developer-tools",
71
- "authentication",
72
- "account-health",
73
- "recovery-tools",
74
- "project-scoped-storage"
75
- ],
76
- "homepage": "https://github.com/ndycode/codex-multi-auth#readme",
77
- "bugs": {
78
- "url": "https://github.com/ndycode/codex-multi-auth/issues"
79
- },
80
- "scripts": {
81
- "build": "tsc && node scripts/copy-oauth-success.js",
82
- "generate:schema": "npm run build && node scripts/generate-config-schema.mjs",
83
- "typecheck": "tsc --noEmit",
84
- "typecheck:scripts": "tsc -p tsconfig.scripts.json",
85
- "lint": "npm run lint:ts && npm run lint:scripts",
86
- "lint:ts": "eslint . --ext .ts",
87
- "lint:scripts": "eslint scripts --ext .js,.mjs",
88
- "lint:fix": "npm run lint:ts:fix && npm run lint:scripts:fix",
89
- "lint:ts:fix": "eslint . --ext .ts --fix",
90
- "lint:scripts:fix": "eslint scripts --ext .js,.mjs --fix",
91
- "test": "vitest run --maxWorkers=1",
92
- "test:watch": "vitest",
93
- "test:ui": "vitest --ui",
94
- "test:model-matrix": "node scripts/test-model-matrix.js",
95
- "test:model-matrix:smoke": "node scripts/test-model-matrix.js --smoke",
96
- "test:model-matrix:report": "node scripts/test-model-matrix.js --smoke --report-json=.tmp/model-matrix-report.json",
97
- "clean:repo": "node scripts/repo-hygiene.js clean --mode aggressive",
98
- "clean:repo:check": "node scripts/repo-hygiene.js check",
99
- "pack:check": "npm run build && node scripts/check-pack-budget.mjs",
100
- "bench:edit-formats": "node scripts/benchmark-edit-formats.mjs --preset=codex-core",
101
- "bench:edit-formats:smoke": "node scripts/benchmark-edit-formats.mjs --smoke --preset=codex-core",
102
- "bench:edit-formats:render": "node scripts/benchmark-render-dashboard.mjs",
103
- "bench:runtime-path": "npm run build && node scripts/benchmark-runtime-path.mjs",
104
- "bench:runtime-path:quick": "node scripts/benchmark-runtime-path.mjs",
105
- "test:coverage": "vitest run --coverage --maxWorkers=1",
106
- "coverage": "npm run build && vitest run --coverage --maxWorkers=1",
107
- "audit:prod": "npm audit --omit=dev --audit-level=high",
108
- "audit:all": "npm audit --audit-level=high",
109
- "audit:dev:allowlist": "node scripts/audit-dev-allowlist.js",
110
- "audit:ci": "npm run audit:prod && npm run audit:dev:allowlist",
111
- "vendor:verify": "node scripts/verify-vendor-provenance.mjs",
112
- "vendor:update-manifest": "node scripts/update-vendor-provenance.mjs",
113
- "postinstall": "node scripts/postinstall.js",
114
- "prepublishOnly": "npm run build",
115
- "prepare": "husky"
116
- },
117
- "bin": {
118
- "mcodex": "scripts/mcodex.js",
119
- "codex-multi-auth-codex": "scripts/codex.js",
120
- "codex-multi-auth-app-launcher": "scripts/codex-app-launcher.js",
121
- "codex-multi-auth": "scripts/codex-multi-auth.js"
122
- },
123
- "files": [
124
- ".codex-plugin/plugin.json",
125
- "dist/",
126
- "assets/",
127
- "config/",
128
- "scripts/codex.js",
129
- "scripts/codex-app-launcher.js",
130
- "scripts/codex-app-router.js",
131
- "scripts/codex-bin-resolver.js",
132
- "scripts/codex-multi-auth.js",
133
- "scripts/codex-routing.js",
134
- "scripts/mcodex.js",
135
- "scripts/install-codex-auth-utils.js",
136
- "scripts/postinstall.js",
137
- "scripts/preuninstall.js",
138
- "vendor/codex-ai-plugin/",
139
- "vendor/codex-ai-sdk/",
140
- "README.md",
141
- "LICENSE"
142
- ],
143
- "bundleDependencies": [
144
- "@codex-ai/plugin",
145
- "@codex-ai/sdk"
146
- ],
147
- "lint-staged": {
148
- "*.ts": [
149
- "eslint --max-warnings=0 --fix --no-warn-ignored"
150
- ],
151
- "scripts/**/*.{js,mjs}": [
152
- "eslint --max-warnings=0 --fix --no-warn-ignored"
153
- ]
154
- },
155
- "engines": {
156
- "node": ">=18.17.0"
157
- },
158
- "devDependencies": {
159
- "@types/node": "^20.19.42",
160
- "@typescript-eslint/eslint-plugin": "^8.56.0",
161
- "@typescript-eslint/parser": "^8.56.0",
162
- "@vitest/coverage-v8": "^4.1.8",
163
- "@vitest/ui": "^4.1.8",
164
- "eslint": "^10.0.0",
165
- "eslint-import-resolver-typescript": "^4.4.5",
166
- "eslint-plugin-import-x": "^4.16.2",
167
- "fast-check": "^4.5.3",
168
- "husky": "^9.1.7",
169
- "lint-staged": "^16.2.7",
170
- "typescript": "^5.9.3",
171
- "typescript-language-server": "^5.1.3",
172
- "vitest": "^4.1.8"
173
- },
174
- "dependencies": {
175
- "@codex-ai/plugin": "file:vendor/codex-ai-plugin",
176
- "@codex-ai/sdk": "file:vendor/codex-ai-sdk",
177
- "@openauthjs/openauth": "^0.4.3",
178
- "hono": "4.12.21",
179
- "undici": "6.25.0",
180
- "zod": "4.4.3"
181
- },
182
- "overrides": {
183
- "hono": "4.12.21",
184
- "flatted": "3.4.2",
185
- "minimatch": "10.2.4",
186
- "picomatch": "4.0.4",
187
- "rollup": "4.59.0",
188
- "vite": "^7.3.2",
189
- "micromatch": {
190
- "picomatch": "2.3.2"
191
- },
192
- "@typescript-eslint/typescript-estree": {
193
- "minimatch": "9.0.9"
194
- }
195
- }
196
- }
1
+ {
2
+ "name": "codex-multi-auth",
3
+ "version": "2.5.0",
4
+ "description": "Codex CLI multi-account OAuth manager with account switching, health checks, runtime rotation, diagnostics, and recovery tools for @openai/codex",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./auth": {
14
+ "types": "./dist/lib/auth/index.d.ts",
15
+ "import": "./dist/lib/auth/index.js",
16
+ "default": "./dist/lib/auth/index.js"
17
+ },
18
+ "./storage": {
19
+ "types": "./dist/lib/storage.d.ts",
20
+ "import": "./dist/lib/storage.js",
21
+ "default": "./dist/lib/storage.js"
22
+ },
23
+ "./config": {
24
+ "types": "./dist/lib/config.d.ts",
25
+ "import": "./dist/lib/config.js",
26
+ "default": "./dist/lib/config.js"
27
+ },
28
+ "./request": {
29
+ "types": "./dist/lib/request/index.d.ts",
30
+ "import": "./dist/lib/request/index.js",
31
+ "default": "./dist/lib/request/index.js"
32
+ },
33
+ "./cli": {
34
+ "types": "./dist/lib/codex-cli/index.d.ts",
35
+ "import": "./dist/lib/codex-cli/index.js",
36
+ "default": "./dist/lib/codex-cli/index.js"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "type": "module",
41
+ "license": "MIT",
42
+ "author": "ndycode",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/ndycode/codex-multi-auth.git"
46
+ },
47
+ "keywords": [
48
+ "openai",
49
+ "codex",
50
+ "codex-cli",
51
+ "codex-auth",
52
+ "codex-multi-account",
53
+ "chatgpt",
54
+ "oauth",
55
+ "oauth2",
56
+ "pkce",
57
+ "multi-account",
58
+ "account-switching",
59
+ "oauth-manager",
60
+ "runtime-rotation",
61
+ "responses-api",
62
+ "local-proxy",
63
+ "quota-management",
64
+ "diagnostics",
65
+ "fault-tolerance",
66
+ "terminal-ui",
67
+ "cli-tool",
68
+ "typescript",
69
+ "nodejs",
70
+ "developer-tools",
71
+ "authentication",
72
+ "account-health",
73
+ "recovery-tools",
74
+ "project-scoped-storage"
75
+ ],
76
+ "homepage": "https://github.com/ndycode/codex-multi-auth#readme",
77
+ "bugs": {
78
+ "url": "https://github.com/ndycode/codex-multi-auth/issues"
79
+ },
80
+ "scripts": {
81
+ "build": "tsc && node scripts/copy-oauth-success.js",
82
+ "generate:schema": "npm run build && node scripts/generate-config-schema.mjs",
83
+ "typecheck": "tsc --noEmit",
84
+ "typecheck:scripts": "tsc -p tsconfig.scripts.json",
85
+ "lint": "npm run lint:ts && npm run lint:scripts",
86
+ "lint:ts": "eslint . --ext .ts",
87
+ "lint:scripts": "eslint scripts --ext .js,.mjs",
88
+ "lint:fix": "npm run lint:ts:fix && npm run lint:scripts:fix",
89
+ "lint:ts:fix": "eslint . --ext .ts --fix",
90
+ "lint:scripts:fix": "eslint scripts --ext .js,.mjs --fix",
91
+ "test": "vitest run --maxWorkers=1",
92
+ "test:watch": "vitest",
93
+ "test:ui": "vitest --ui",
94
+ "test:model-matrix": "node scripts/test-model-matrix.js",
95
+ "test:model-matrix:smoke": "node scripts/test-model-matrix.js --smoke",
96
+ "test:model-matrix:report": "node scripts/test-model-matrix.js --smoke --report-json=.tmp/model-matrix-report.json",
97
+ "clean:repo": "node scripts/repo-hygiene.js clean --mode aggressive",
98
+ "clean:repo:check": "node scripts/repo-hygiene.js check",
99
+ "pack:check": "npm run build && node scripts/check-pack-budget.mjs",
100
+ "bench:edit-formats": "node scripts/benchmark-edit-formats.mjs --preset=codex-core",
101
+ "bench:edit-formats:smoke": "node scripts/benchmark-edit-formats.mjs --smoke --preset=codex-core",
102
+ "bench:edit-formats:render": "node scripts/benchmark-render-dashboard.mjs",
103
+ "bench:runtime-path": "npm run build && node scripts/benchmark-runtime-path.mjs",
104
+ "bench:runtime-path:quick": "node scripts/benchmark-runtime-path.mjs",
105
+ "test:coverage": "vitest run --coverage --maxWorkers=1",
106
+ "coverage": "npm run build && vitest run --coverage --maxWorkers=1",
107
+ "audit:prod": "npm audit --omit=dev --audit-level=high",
108
+ "audit:all": "npm audit --audit-level=high",
109
+ "audit:dev:allowlist": "node scripts/audit-dev-allowlist.js",
110
+ "audit:ci": "npm run audit:prod && npm run audit:dev:allowlist",
111
+ "vendor:verify": "node scripts/verify-vendor-provenance.mjs",
112
+ "vendor:update-manifest": "node scripts/update-vendor-provenance.mjs",
113
+ "postinstall": "node scripts/postinstall.js",
114
+ "prepublishOnly": "npm run build",
115
+ "prepare": "husky"
116
+ },
117
+ "bin": {
118
+ "mcodex": "scripts/mcodex.js",
119
+ "codex-multi-auth-codex": "scripts/codex.js",
120
+ "codex-multi-auth-app-launcher": "scripts/codex-app-launcher.js",
121
+ "codex-multi-auth": "scripts/codex-multi-auth.js"
122
+ },
123
+ "files": [
124
+ ".codex-plugin/plugin.json",
125
+ "dist/",
126
+ "assets/",
127
+ "config/",
128
+ "scripts/codex.js",
129
+ "scripts/codex-app-launcher.js",
130
+ "scripts/codex-app-router.js",
131
+ "scripts/codex-bin-resolver.js",
132
+ "scripts/codex-multi-auth.js",
133
+ "scripts/codex-routing.js",
134
+ "scripts/mcodex.js",
135
+ "scripts/install-codex-auth-utils.js",
136
+ "scripts/postinstall.js",
137
+ "scripts/preuninstall.js",
138
+ "vendor/codex-ai-plugin/",
139
+ "vendor/codex-ai-sdk/",
140
+ "README.md",
141
+ "LICENSE"
142
+ ],
143
+ "bundleDependencies": [
144
+ "@codex-ai/plugin",
145
+ "@codex-ai/sdk"
146
+ ],
147
+ "lint-staged": {
148
+ "*.ts": [
149
+ "eslint --max-warnings=0 --fix --no-warn-ignored"
150
+ ],
151
+ "scripts/**/*.{js,mjs}": [
152
+ "eslint --max-warnings=0 --fix --no-warn-ignored"
153
+ ]
154
+ },
155
+ "engines": {
156
+ "node": ">=18.17.0"
157
+ },
158
+ "devDependencies": {
159
+ "@types/node": "^20.19.42",
160
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
161
+ "@typescript-eslint/parser": "^8.56.0",
162
+ "@vitest/coverage-v8": "^4.1.8",
163
+ "@vitest/ui": "^4.1.8",
164
+ "eslint": "^10.0.0",
165
+ "eslint-import-resolver-typescript": "^4.4.5",
166
+ "eslint-plugin-import-x": "^4.16.2",
167
+ "fast-check": "^4.5.3",
168
+ "husky": "^9.1.7",
169
+ "lint-staged": "^16.2.7",
170
+ "typescript": "^5.9.3",
171
+ "typescript-language-server": "^5.1.3",
172
+ "vitest": "^4.1.8"
173
+ },
174
+ "dependencies": {
175
+ "@codex-ai/plugin": "file:vendor/codex-ai-plugin",
176
+ "@codex-ai/sdk": "file:vendor/codex-ai-sdk",
177
+ "@openauthjs/openauth": "^0.4.3",
178
+ "hono": "4.12.21",
179
+ "undici": "6.25.0",
180
+ "zod": "4.4.3"
181
+ },
182
+ "overrides": {
183
+ "hono": "4.12.21",
184
+ "flatted": "3.4.2",
185
+ "minimatch": "10.2.4",
186
+ "picomatch": "4.0.4",
187
+ "rollup": "4.59.0",
188
+ "vite": "^7.3.2",
189
+ "micromatch": {
190
+ "picomatch": "2.3.2"
191
+ },
192
+ "@typescript-eslint/typescript-estree": {
193
+ "minimatch": "9.0.9"
194
+ }
195
+ }
196
+ }
package/scripts/codex.js CHANGED
@@ -307,6 +307,172 @@ function findAccountIndexByIdOrEmail(accounts, id, email) {
307
307
  return -1;
308
308
  }
309
309
 
310
+ // --- Forced-account selection (`--account`, issue #623) ---------------------
311
+ //
312
+ // `codex-multi-auth-codex --account <selector>` (or the CODEX_MULTI_AUTH_FORCE_ACCOUNT
313
+ // env var) pins ONE account for a single invocation. The selector is resolved to a
314
+ // 0-based index here in the launcher and published as CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX,
315
+ // which the runtime rotation proxy consumes as an ephemeral pin — never touching the
316
+ // persisted `switch` pin on disk. The flag wins over the env var.
317
+ const FORCE_ACCOUNT_ENV = "CODEX_MULTI_AUTH_FORCE_ACCOUNT";
318
+ const FORCE_ACCOUNT_INDEX_ENV = "CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX";
319
+
320
+ /**
321
+ * Extract and strip `--account <sel>` / `--account=<sel>` from a forwarded arg
322
+ * list. The flag is a launcher concept; real Codex has no `--account`, so it must
323
+ * never be forwarded. Returns { selector, strippedArgs, error }; selector is null
324
+ * when the flag is absent. The last occurrence wins if repeated.
325
+ */
326
+ function extractForcedAccountFlag(args) {
327
+ const strippedArgs = [];
328
+ let selector = null;
329
+ let sawFlag = false;
330
+ for (let index = 0; index < args.length; index += 1) {
331
+ const arg = args[index];
332
+ if (arg === "--account") {
333
+ sawFlag = true;
334
+ const next = args[index + 1];
335
+ if (typeof next !== "string") {
336
+ return {
337
+ selector: null,
338
+ strippedArgs: args,
339
+ error:
340
+ "codex-multi-auth: --account requires a value (account index, email, or account id).",
341
+ };
342
+ }
343
+ selector = next;
344
+ index += 1;
345
+ continue;
346
+ }
347
+ if (typeof arg === "string" && arg.startsWith("--account=")) {
348
+ sawFlag = true;
349
+ selector = arg.slice("--account=".length);
350
+ continue;
351
+ }
352
+ strippedArgs.push(arg);
353
+ }
354
+ return { selector, strippedArgs, error: null, sawFlag };
355
+ }
356
+
357
+ /**
358
+ * Resolve the effective forced-account selector from args (flag) or environment,
359
+ * with the flag taking precedence. Returns { selector, strippedArgs, error };
360
+ * selector is null when neither source requests a forced account.
361
+ */
362
+ function resolveForcedAccountSelector(rawArgs, env = process.env) {
363
+ const { selector: flagSelector, strippedArgs, error, sawFlag } =
364
+ extractForcedAccountFlag(rawArgs);
365
+ if (error) {
366
+ return { selector: null, strippedArgs, error };
367
+ }
368
+ if (sawFlag) {
369
+ const trimmed = flagSelector.trim();
370
+ if (trimmed.length === 0) {
371
+ return {
372
+ selector: null,
373
+ strippedArgs,
374
+ error:
375
+ "codex-multi-auth: --account requires a non-empty value (account index, email, or account id).",
376
+ };
377
+ }
378
+ return { selector: trimmed, strippedArgs, error: null };
379
+ }
380
+ const envSelector = (env[FORCE_ACCOUNT_ENV] ?? "").trim();
381
+ if (envSelector.length > 0) {
382
+ return { selector: envSelector, strippedArgs, error: null };
383
+ }
384
+ return { selector: null, strippedArgs, error: null };
385
+ }
386
+
387
+ function formatForcedAccountList(accounts) {
388
+ const lines = accounts.map((account, index) => {
389
+ const email =
390
+ typeof account?.email === "string" && account.email.trim().length > 0
391
+ ? account.email.trim()
392
+ : `account ${index + 1}`;
393
+ return ` ${index + 1}. ${email}`;
394
+ });
395
+ return `Available accounts:\n${lines.join("\n")}`;
396
+ }
397
+
398
+ /**
399
+ * Resolve a forced-account selector to a 0-based index against the same scoped
400
+ * accounts pool the runtime rotation proxy will load. Selector forms: a 1-based
401
+ * integer index, an email, or an account id. Returns { ok, index } or
402
+ * { ok: false, error }.
403
+ */
404
+ async function resolveForcedAccountIndex(selector, env = process.env) {
405
+ const accountsDir = await resolveStatusAccountsDir(env);
406
+ const storage = readJsonFileQuiet(resolveAccountsPath(env, accountsDir));
407
+ const accounts = Array.isArray(storage?.accounts) ? storage.accounts : [];
408
+ if (accounts.length === 0) {
409
+ return {
410
+ ok: false,
411
+ error:
412
+ "codex-multi-auth: --account was set but no Codex accounts are configured. Run `codex-multi-auth login` first.",
413
+ };
414
+ }
415
+ if (/^\d+$/.test(selector)) {
416
+ const oneBased = Number.parseInt(selector, 10);
417
+ if (oneBased < 1 || oneBased > accounts.length) {
418
+ return {
419
+ ok: false,
420
+ error: `codex-multi-auth: --account ${selector} is out of range (have ${accounts.length} account${
421
+ accounts.length === 1 ? "" : "s"
422
+ }).\n${formatForcedAccountList(accounts)}`,
423
+ };
424
+ }
425
+ return { ok: true, index: oneBased - 1 };
426
+ }
427
+ const index = findAccountIndexByIdOrEmail(accounts, selector, selector);
428
+ if (index < 0) {
429
+ return {
430
+ ok: false,
431
+ error: `codex-multi-auth: --account "${selector}" did not match any configured account.\n${formatForcedAccountList(accounts)}`,
432
+ };
433
+ }
434
+ return { ok: true, index };
435
+ }
436
+
437
+ /**
438
+ * Preflight the `--account` flag / env var before forwarding to Codex. On success
439
+ * returns { forwardArgs } with the flag stripped and (when a forced account was
440
+ * requested) CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX published on the environment for
441
+ * the proxy to consume. On failure returns { error } so the caller can exit
442
+ * non-zero WITHOUT launching Codex — a forced account must never silently fall
443
+ * back to a different one.
444
+ */
445
+ async function applyForcedAccountSelection(rawArgs, env = process.env) {
446
+ const forced = resolveForcedAccountSelector(rawArgs, env);
447
+ if (forced.error) {
448
+ return { error: forced.error };
449
+ }
450
+ if (forced.selector === null) {
451
+ // Defensive: the resolved-index var is internal and only ever set below.
452
+ // Clear any stray value from the ambient environment so a request without
453
+ // --account can never inherit an unintended pin.
454
+ delete env[FORCE_ACCOUNT_INDEX_ENV];
455
+ return { forwardArgs: rawArgs };
456
+ }
457
+ // The pin can only be honored when the runtime rotation proxy is active for
458
+ // this command; otherwise the run would silently use a non-forced account,
459
+ // so fail hard instead of ignoring the flag.
460
+ if (!(await isRuntimeRotationProxyEnabled(forced.strippedArgs, env))) {
461
+ return {
462
+ error: [
463
+ "codex-multi-auth: --account requires the runtime rotation proxy, which is not active for this command.",
464
+ "Enable it with `codex-multi-auth rotation enable`, and make sure CODEX_MULTI_AUTH_BYPASS is not set.",
465
+ ].join("\n"),
466
+ };
467
+ }
468
+ const resolved = await resolveForcedAccountIndex(forced.selector, env);
469
+ if (!resolved.ok) {
470
+ return { error: resolved.error };
471
+ }
472
+ env[FORCE_ACCOUNT_INDEX_ENV] = String(resolved.index);
473
+ return { forwardArgs: forced.strippedArgs };
474
+ }
475
+
310
476
  function resolveModelFamilyForStatus(model) {
311
477
  const normalized = typeof model === "string" ? model.trim().toLowerCase() : "";
312
478
  if (normalized.startsWith("gpt-5.2")) return "gpt-5.2";
@@ -657,15 +823,20 @@ function maybeRefreshQuotaCacheInBackground(env = process.env) {
657
823
  // process. The close/error handlers below remove the lock dir, but if the parent
658
824
  // exits before the child settles they will NOT fire — in that case the lock is
659
825
  // reclaimed by the 10-minute stale-lock recovery in acquireStatusRefreshLock.
826
+ const refreshChildEnv = {
827
+ ...env,
828
+ CODEX_MULTI_AUTH_STATUS_REFRESH_CHILD: "1",
829
+ CODEX_MULTI_AUTH_STATUSLINE: "0",
830
+ };
831
+ // A forced-account pin is scoped to a single forwarded Codex run; this
832
+ // management child (`forecast`) must never inherit it, so it can never be
833
+ // coupled to a specific account if a future change routes it through the proxy.
834
+ delete refreshChildEnv[FORCE_ACCOUNT_INDEX_ENV];
660
835
  const child = spawn(
661
836
  process.execPath,
662
837
  [scriptPath, "forecast", "--live", "--json"],
663
838
  {
664
- env: {
665
- ...env,
666
- CODEX_MULTI_AUTH_STATUS_REFRESH_CHILD: "1",
667
- CODEX_MULTI_AUTH_STATUSLINE: "0",
668
- },
839
+ env: refreshChildEnv,
669
840
  stdio: "ignore",
670
841
  detached: true,
671
842
  },
@@ -4766,12 +4937,22 @@ async function main() {
4766
4937
  return 1;
4767
4938
  }
4768
4939
 
4940
+ // Resolve `--account` / CODEX_MULTI_AUTH_FORCE_ACCOUNT before forwarding: strip
4941
+ // the launcher-only flag from the Codex args and publish the resolved pin, or
4942
+ // fail hard so a forced account can never silently fall back to another one.
4943
+ const forcedAccount = await applyForcedAccountSelection(rawArgs, process.env);
4944
+ if (forcedAccount.error) {
4945
+ console.error(forcedAccount.error);
4946
+ return 1;
4947
+ }
4948
+ const forwardArgs = forcedAccount.forwardArgs;
4949
+
4769
4950
  await autoSyncManagerActiveSelectionIfEnabled();
4770
- await maybePrintForwardStatusLine(rawArgs);
4951
+ await maybePrintForwardStatusLine(forwardArgs);
4771
4952
  maybeRefreshQuotaCacheInBackground();
4772
4953
  try {
4773
- return await withForwardedRuntimeObservability(rawArgs, () =>
4774
- forwardToRealCodex(realCodexBin, rawArgs),
4954
+ return await withForwardedRuntimeObservability(forwardArgs, () =>
4955
+ forwardToRealCodex(realCodexBin, forwardArgs),
4775
4956
  );
4776
4957
  } finally {
4777
4958
  await autoSyncManagerActiveSelectionIfEnabled();
@@ -1,2 +1,2 @@
1
- export type PluginInput = any;
2
- export type Plugin = any;
1
+ export type PluginInput = any;
2
+ export type Plugin = any;
@@ -1,2 +1,2 @@
1
- export {};
2
-
1
+ export {};
2
+