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 +2 -0
- package/dist/lib/accounts/warm-request.d.ts +6 -0
- package/dist/lib/accounts/warm-request.d.ts.map +1 -1
- package/dist/lib/accounts/warm-request.js +16 -1
- package/dist/lib/accounts/warm-request.js.map +1 -1
- package/dist/lib/shutdown.d.ts +1 -0
- package/dist/lib/shutdown.d.ts.map +1 -1
- package/dist/lib/shutdown.js +46 -13
- package/dist/lib/shutdown.js.map +1 -1
- package/package.json +1 -1
- package/scripts/install-oc-codex-multi-auth-core.js +132 -3
- package/scripts/install-oc-codex-multi-auth.js +15 -5
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
|
|
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
|
-
|
|
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":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,GAAG,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;AAEzC,0EAA0E;AAC1E,MAAM,UAAU,GAAG,SAAS,CAAC;AAE7B,gFAAgF;AAChF,MAAM,eAAe,GAAG,MAAM,CAAC;AAY/B
|
|
1
|
+
{"version":3,"file":"warm-request.js","sourceRoot":"","sources":["../../../lib/accounts/warm-request.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/shutdown.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/lib/shutdown.js
CHANGED
|
@@ -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
|
|
34
|
+
export function runCleanup() {
|
|
35
|
+
if (inFlight)
|
|
36
|
+
return inFlight;
|
|
14
37
|
const fns = [...cleanupFunctions];
|
|
15
38
|
cleanupFunctions.length = 0;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
});
|
package/dist/lib/shutdown.js.map
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
|
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()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
}
|