oc-codex-multi-auth 6.4.1 → 6.6.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/README.md CHANGED
@@ -180,6 +180,8 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
180
180
  | `codex-limits` | What quota or rate-limit state is visible now? |
181
181
  | `codex-dashboard` | Can I manage accounts from one interactive surface? |
182
182
 
183
+ Most of these also run as a **direct CLI** with no agent/model involvement (no token cost) — e.g. `oc-codex-multi-auth warm`, `oc-codex-multi-auth status`, or `npx -y oc-codex-multi-auth@latest warm`. Use `oc-codex-multi-auth warm` to open every enabled account's usage window at the start of a session and stagger the rolling quota cooldowns. Add `--json` for scriptable output.
184
+
183
185
  ### Account management
184
186
 
185
187
  | Tool | What it answers |
@@ -26,6 +26,12 @@ export interface WarmRequestParams {
26
26
  /**
27
27
  * Build the minimal warm-ping request body. Exported for tests so the exact
28
28
  * shape (stream/store/reasoning) stays pinned.
29
+ *
30
+ * Instruction resolution is best-effort: a warm ping only needs a valid request
31
+ * that opens the usage window, not the full Codex system prompt. If
32
+ * `getCodexInstructions` cannot resolve the prompt (offline, cache miss, or the
33
+ * bundled file is unavailable in a standalone CLI run), we fall back to a
34
+ * minimal instruction so warming never fails on prompt-file resolution.
29
35
  */
30
36
  export declare function buildWarmRequestBody(model?: string): Promise<RequestBody>;
31
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"warm-request.d.ts","sourceRoot":"","sources":["../../../lib/accounts/warm-request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAW/C,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,SAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAkBnF;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACtC,MAAM,EAAE,iBAAiB,GACvB,OAAO,CAAC,iBAAiB,CAAC,CA4D5B"}
1
+ {"version":3,"file":"warm-request.d.ts","sourceRoot":"","sources":["../../../lib/accounts/warm-request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAW/C,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,SAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CA0BnF;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACtC,MAAM,EAAE,iBAAiB,GACvB,OAAO,CAAC,iBAAiB,CAAC,CA4D5B"}
@@ -26,9 +26,24 @@ const WARM_TIMEOUT_MS = 15_000;
26
26
  /**
27
27
  * Build the minimal warm-ping request body. Exported for tests so the exact
28
28
  * shape (stream/store/reasoning) stays pinned.
29
+ *
30
+ * Instruction resolution is best-effort: a warm ping only needs a valid request
31
+ * that opens the usage window, not the full Codex system prompt. If
32
+ * `getCodexInstructions` cannot resolve the prompt (offline, cache miss, or the
33
+ * bundled file is unavailable in a standalone CLI run), we fall back to a
34
+ * minimal instruction so warming never fails on prompt-file resolution.
29
35
  */
30
36
  export async function buildWarmRequestBody(model = WARM_MODEL) {
31
- const instructions = await getCodexInstructions(model);
37
+ let instructions;
38
+ try {
39
+ instructions = await getCodexInstructions(model);
40
+ }
41
+ catch (error) {
42
+ log.debug("getCodexInstructions failed for warm ping; using minimal fallback", {
43
+ error: error instanceof Error ? error.message : String(error),
44
+ });
45
+ instructions = "You are a helpful assistant.";
46
+ }
32
47
  return {
33
48
  model,
34
49
  stream: true,
@@ -1 +1 @@
1
- {"version":3,"file":"warm-request.js","sourceRoot":"","sources":["../../../lib/accounts/warm-request.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"warm-request.js","sourceRoot":"","sources":["../../../lib/accounts/warm-request.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  type CleanupFn = () => void | Promise<void>;
2
+ export declare function setShutdownOwnsProcess(owns: boolean): void;
2
3
  export declare function registerCleanup(fn: CleanupFn): void;
3
4
  export declare function unregisterCleanup(fn: CleanupFn): void;
4
5
  export declare function runCleanup(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../lib/shutdown.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAK5C,wBAAgB,eAAe,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAGnD;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAKrD;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAWhD;AAmBD,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
1
+ {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../lib/shutdown.ts"],"names":[],"mappings":"AAAA,KAAK,SAAS,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAyB5C,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE1D;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAGnD;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAKrD;AAED,wBAAgB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAmB1C;AAuBD,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
@@ -1,5 +1,26 @@
1
1
  const cleanupFunctions = [];
2
2
  let shutdownRegistered = false;
3
+ /**
4
+ * Whether this process is ours to terminate.
5
+ *
6
+ * Defaults to false: the package normally runs as a plugin *inside* the
7
+ * opencode host process, where calling `process.exit` from a signal handler
8
+ * preempts opencode's own shutdown (#187). Only an entrypoint that IS the
9
+ * process — the standalone `warm` CLI — opts in. Read lazily at signal time,
10
+ * so the opt-in may happen after handlers are installed.
11
+ */
12
+ let ownsProcess = false;
13
+ /**
14
+ * The in-flight drain, if one is running. Concurrent callers (a signal handler
15
+ * and `beforeExit` firing in the same shutdown) share it rather than racing on
16
+ * an already-emptied queue. Cleared once settled, so *sequential* calls each
17
+ * drain freshly — `AccountManager` re-registers its flush handler after an
18
+ * external `runCleanup()`, and the test suites depend on that.
19
+ */
20
+ let inFlight = null;
21
+ export function setShutdownOwnsProcess(owns) {
22
+ ownsProcess = owns;
23
+ }
3
24
  export function registerCleanup(fn) {
4
25
  cleanupFunctions.push(fn);
5
26
  ensureShutdownHandler();
@@ -10,29 +31,41 @@ export function unregisterCleanup(fn) {
10
31
  cleanupFunctions.splice(index, 1);
11
32
  }
12
33
  }
13
- export async function runCleanup() {
34
+ export function runCleanup() {
35
+ if (inFlight)
36
+ return inFlight;
14
37
  const fns = [...cleanupFunctions];
15
38
  cleanupFunctions.length = 0;
16
- for (const fn of fns) {
17
- try {
18
- await fn();
19
- }
20
- catch {
21
- // Ignore cleanup errors during shutdown
39
+ inFlight = (async () => {
40
+ for (const fn of fns) {
41
+ try {
42
+ await fn();
43
+ }
44
+ catch {
45
+ // Ignore cleanup errors during shutdown
46
+ }
22
47
  }
23
- }
48
+ })().finally(() => {
49
+ inFlight = null;
50
+ });
51
+ return inFlight;
24
52
  }
25
53
  function ensureShutdownHandler() {
26
54
  if (shutdownRegistered)
27
55
  return;
28
56
  shutdownRegistered = true;
29
- const handleSignal = () => {
30
- void runCleanup().finally(() => {
31
- process.exit(0);
57
+ const handleSignal = (signal) => {
58
+ const drained = runCleanup();
59
+ // A guest in someone else's process: run cleanup, then let the host
60
+ // finish its own shutdown. Exiting here is what broke #187.
61
+ if (!ownsProcess)
62
+ return;
63
+ void drained.finally(() => {
64
+ process.exit(signal === "SIGTERM" ? 143 : 130);
32
65
  });
33
66
  };
34
- process.once("SIGINT", handleSignal);
35
- process.once("SIGTERM", handleSignal);
67
+ process.once("SIGINT", () => handleSignal("SIGINT"));
68
+ process.once("SIGTERM", () => handleSignal("SIGTERM"));
36
69
  process.once("beforeExit", () => {
37
70
  void runCleanup();
38
71
  });
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../../lib/shutdown.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAgB,EAAE,CAAC;AACzC,IAAI,kBAAkB,GAAG,KAAK,CAAC;AAE/B,MAAM,UAAU,eAAe,CAAC,EAAa;IAC5C,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,qBAAqB,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAa;IAC9C,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QAClB,gBAAgB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC/B,MAAM,GAAG,GAAG,CAAC,GAAG,gBAAgB,CAAC,CAAC;IAClC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC;IAE5B,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC;YACJ,MAAM,EAAE,EAAE,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACR,wCAAwC;QACzC,CAAC;IACF,CAAC;AACF,CAAC;AAED,SAAS,qBAAqB;IAC7B,IAAI,kBAAkB;QAAE,OAAO;IAC/B,kBAAkB,GAAG,IAAI,CAAC;IAE1B,MAAM,YAAY,GAAG,GAAG,EAAE;QACzB,KAAK,UAAU,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE;QAC/B,KAAK,UAAU,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe;IAC9B,OAAO,gBAAgB,CAAC,MAAM,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../../lib/shutdown.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAgB,EAAE,CAAC;AACzC,IAAI,kBAAkB,GAAG,KAAK,CAAC;AAE/B;;;;;;;;GAQG;AACH,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,IAAI,QAAQ,GAAyB,IAAI,CAAC;AAE1C,MAAM,UAAU,sBAAsB,CAAC,IAAa;IACnD,WAAW,GAAG,IAAI,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAAa;IAC5C,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,qBAAqB,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAa;IAC9C,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3C,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QAClB,gBAAgB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;AACF,CAAC;AAED,MAAM,UAAU,UAAU;IACzB,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,GAAG,GAAG,CAAC,GAAG,gBAAgB,CAAC,CAAC;IAClC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC;IAE5B,QAAQ,GAAG,CAAC,KAAK,IAAI,EAAE;QACtB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC;gBACJ,MAAM,EAAE,EAAE,CAAC;YACZ,CAAC;YAAC,MAAM,CAAC;gBACR,wCAAwC;YACzC,CAAC;QACF,CAAC;IACF,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;QACjB,QAAQ,GAAG,IAAI,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,SAAS,qBAAqB;IAC7B,IAAI,kBAAkB;QAAE,OAAO;IAC/B,kBAAkB,GAAG,IAAI,CAAC;IAE1B,MAAM,YAAY,GAAG,CAAC,MAA4B,EAAE,EAAE;QACrD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;QAC7B,oEAAoE;QACpE,4DAA4D;QAC5D,IAAI,CAAC,WAAW;YAAE,OAAO;QACzB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE;YACzB,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE;QAC/B,KAAK,UAAU,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe;IAC9B,OAAO,gBAAgB,CAAC,MAAM,CAAC;AAChC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-codex-multi-auth",
3
- "version": "6.4.1",
3
+ "version": "6.6.0",
4
4
  "description": "OpenCode plugin for ChatGPT Plus/Pro OAuth with Codex/GPT-5 routing, multi-account rotation, account switching, health checks, diagnostics, and recovery tools",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -2,7 +2,7 @@ import { existsSync, realpathSync } from "node:fs";
2
2
  import { copyFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
 
7
7
  const PACKAGE_NAME = "oc-codex-multi-auth";
8
8
  const LEGACY_PACKAGE_NAMES = ["oc-chatgpt-multi-auth"];
@@ -13,7 +13,7 @@ const STALE_MANAGED_MODEL_KEYS = new Set([
13
13
  "gpt-5.3-codex",
14
14
  "gpt-5.4",
15
15
  ]);
16
- const STANDALONE_COMMANDS = new Set(["doctor", "status", "list", "limits", "dashboard", "health", "diag"]);
16
+ const STANDALONE_COMMANDS = new Set(["doctor", "status", "list", "limits", "dashboard", "health", "diag", "warm"]);
17
17
  const INSTALLER_COMMANDS = new Set(["install"]);
18
18
 
19
19
  function splitCommandArgv(argv) {
@@ -83,7 +83,8 @@ function printHelp() {
83
83
  " limits Show stored rate-limit state\n" +
84
84
  " dashboard Print dashboard guidance\n" +
85
85
  " health Check local token/account health\n" +
86
- " diag Alias for doctor --deep\n\n" +
86
+ " diag Alias for doctor --deep\n" +
87
+ " warm Open every enabled account's usage window now (one request each)\n\n" +
87
88
  `Installer usage: ${PACKAGE_NAME} [--modern|--full|--legacy] [--dry-run] [--no-cache-clear]\n\n` +
88
89
  "Default behavior:\n" +
89
90
  " - Installs/updates global config at ~/.config/opencode/opencode.json\n" +
@@ -352,6 +353,131 @@ function printStandaloneResult(command, payload, json) {
352
353
  if (payload.nextAction) console.log(`Next: ${payload.nextAction}`);
353
354
  }
354
355
 
356
+ async function loadWarmRuntime(env) {
357
+ // Reuse the COMPILED warm logic from dist/ so the standalone CLI behaves
358
+ // identically to the in-conversation codex-warm tool. dist/ ships in the
359
+ // npm package (files allowlist) and none of these modules import the
360
+ // OpenCode plugin runtime, so they load cleanly in plain Node.
361
+ const distRoot = join(repoRoot, "dist", "lib");
362
+ const toUrl = (rel) => pathToFileURL(join(distRoot, rel)).href;
363
+ try {
364
+ const [storageMod, usageMod, warmReqMod, warmMod, shutdownMod] = await Promise.all([
365
+ import(toUrl("storage.js")),
366
+ import(toUrl("codex-usage.js")),
367
+ import(toUrl("accounts/warm-request.js")),
368
+ import(toUrl("accounts/warm.js")),
369
+ import(toUrl("shutdown.js")),
370
+ ]);
371
+ // Unlike the plugin, this CLI *is* the process, so it owns termination:
372
+ // Ctrl+C must abort the warm run rather than wait for it to drain.
373
+ // Refreshing a token here persists credentials, which registers the
374
+ // shutdown handler via the storage lock.
375
+ shutdownMod.setShutdownOwnsProcess(true);
376
+ return { storageMod, usageMod, warmReqMod, warmMod, shutdownMod };
377
+ } catch (error) {
378
+ throw new Error(
379
+ `Could not load warm runtime from dist/. Build the package first (npm run build). Cause: ${formatErrorForLog(error)}`,
380
+ );
381
+ }
382
+ }
383
+
384
+ export async function runWarmCommand(parsed, options = {}) {
385
+ const { env = process.env } = options;
386
+ const storagePath = getStandaloneStoragePath(parsed, env);
387
+
388
+ let runtime;
389
+ try {
390
+ runtime = await loadWarmRuntime(env);
391
+ } catch (error) {
392
+ const payload = { command: "warm", storagePath, error: formatErrorForLog(error) };
393
+ printWarmResult(payload, parsed.json);
394
+ return { exitCode: 1, action: "warm", storagePath };
395
+ }
396
+
397
+ const { storageMod, usageMod, warmReqMod, warmMod } = runtime;
398
+ // Point dist storage at the resolved accounts file so a refreshed token is
399
+ // persisted to the SAME file the rest of the toolchain reads.
400
+ storageMod.setStoragePathDirect(storagePath);
401
+
402
+ const storage = await storageMod.loadAccounts();
403
+ const accounts = Array.isArray(storage?.accounts) ? storage.accounts : [];
404
+ if (accounts.length === 0) {
405
+ const payload = {
406
+ command: "warm",
407
+ storagePath,
408
+ totalAccounts: 0,
409
+ warmed: 0,
410
+ failed: 0,
411
+ skipped: 0,
412
+ results: [],
413
+ message: "No accounts configured.",
414
+ nextAction: "Run opencode auth login.",
415
+ };
416
+ printWarmResult(payload, parsed.json);
417
+ return { exitCode: 0, action: "warm", storagePath };
418
+ }
419
+
420
+ // Same adapter as lib/tools/codex-warm.ts createWarmOne: refresh → resolve
421
+ // account id → open the usage window; map an exhausted (quota-429) account
422
+ // to a failure so it is not reported as warmed.
423
+ const warmOne = async (account) => {
424
+ const { accessToken } = await usageMod.ensureCodexUsageAccessToken({ storage, account });
425
+ const accountId = usageMod.resolveCodexUsageAccountId({ account, accessToken });
426
+ if (!accountId) {
427
+ return { status: "failed", detail: "could not resolve account id (re-login may be required)" };
428
+ }
429
+ const result = await warmReqMod.warmAccountWindow({
430
+ accountId,
431
+ accessToken,
432
+ organizationId: account.organizationId,
433
+ });
434
+ if (result.status === "exhausted") {
435
+ return { status: "failed", detail: result.detail ?? "quota/usage limit reached" };
436
+ }
437
+ return { status: "warmed" };
438
+ };
439
+
440
+ const summary = await warmMod.warmAccounts(accounts, warmOne);
441
+ const payload = {
442
+ command: "warm",
443
+ storagePath,
444
+ totalAccounts: summary.total,
445
+ warmed: summary.warmedCount,
446
+ failed: summary.failedCount,
447
+ skipped: summary.skippedCount,
448
+ results: summary.results.map((r) => ({
449
+ index: r.index,
450
+ email: maskValue(accounts[r.index]?.email, parsed.includeSensitive),
451
+ status: r.status,
452
+ detail: r.detail,
453
+ })),
454
+ };
455
+ printWarmResult(payload, parsed.json);
456
+ return { exitCode: summary.failedCount > 0 ? 1 : 0, action: "warm", storagePath };
457
+ }
458
+
459
+ function printWarmResult(payload, json) {
460
+ if (json) {
461
+ console.log(JSON.stringify(payload, null, 2));
462
+ return;
463
+ }
464
+ console.log(`oc-codex-multi-auth warm`);
465
+ if (payload.message) console.log(payload.message);
466
+ console.log(`Storage: ${payload.storagePath}`);
467
+ if (payload.error) {
468
+ console.log(`Error: ${payload.error}`);
469
+ return;
470
+ }
471
+ console.log(`Accounts: ${payload.totalAccounts}`);
472
+ for (const r of payload.results ?? []) {
473
+ const label = r.email ? `[${r.index}] ${r.email}` : `[${r.index}]`;
474
+ const detail = r.detail ? ` — ${r.detail}` : "";
475
+ console.log(`- ${label}: ${r.status}${detail}`);
476
+ }
477
+ console.log(`Summary: ${payload.warmed} warmed, ${payload.failed} failed, ${payload.skipped} skipped`);
478
+ if (payload.nextAction) console.log(`Next: ${payload.nextAction}`);
479
+ }
480
+
355
481
  export async function runStandaloneCommand(command, argv = [], options = {}) {
356
482
  const parsed = parseStandaloneArgs(argv);
357
483
  if (command === "diag") {
@@ -362,6 +488,9 @@ export async function runStandaloneCommand(command, argv = [], options = {}) {
362
488
  printHelp();
363
489
  return { exitCode: 0, action: "help" };
364
490
  }
491
+ if (command === "warm") {
492
+ return runWarmCommand(parsed, options);
493
+ }
365
494
  const { env = process.env } = options;
366
495
  const storagePath = getStandaloneStoragePath(parsed, env);
367
496
  const { storage, error } = await readStandaloneStorage(storagePath);
@@ -19,9 +19,19 @@ export function isDirectRunPath(
19
19
  }
20
20
 
21
21
  if (isDirectRunPath()) {
22
- runInstaller().catch((error) => {
23
- const message = error instanceof Error ? error.message : String(error);
24
- console.error(`Installer failed: ${message}`);
25
- process.exit(1);
26
- });
22
+ runInstaller()
23
+ .then((result) => {
24
+ // Propagate a non-zero exit from standalone commands (e.g. `warm`
25
+ // reporting per-account failures) without throwing. Read-only
26
+ // commands return exitCode 0; only set a failure code so a clean run
27
+ // still exits 0.
28
+ if (result && typeof result.exitCode === "number" && result.exitCode !== 0) {
29
+ process.exitCode = result.exitCode;
30
+ }
31
+ })
32
+ .catch((error) => {
33
+ const message = error instanceof Error ? error.message : String(error);
34
+ console.error(`Installer failed: ${message}`);
35
+ process.exit(1);
36
+ });
27
37
  }