codex-account-orchestrator 1.1.1 → 1.2.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/CHANGELOG.md +14 -0
- package/README.md +45 -0
- package/dist/cli_main.js +244 -3
- package/dist/process_runner.d.ts +1 -1
- package/dist/process_runner.js +6 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] - 2026-01-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `cao switch` interactive account picker and `cao current` convenience command
|
|
13
|
+
- `cao status --compact` for one-line per-account summaries
|
|
14
|
+
- `cao import codex-auth` to migrate snapshots from codex-auth
|
|
15
|
+
- `cao run --gateway` for running through the local gateway without CLI fallback
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Gateway status now includes token expiry and last refresh hints
|
|
20
|
+
- README expanded with migration, switching, and gateway run guidance
|
|
21
|
+
|
|
8
22
|
## [1.1.1] - 2026-01-27
|
|
9
23
|
|
|
10
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Codex OAuth account fallback orchestrator. CAO keeps **separate `CODEX_HOME` dir
|
|
|
17
17
|
- Automatic fallback on quota exhaustion (keyword-based detector)
|
|
18
18
|
- Gateway mode for seamless account switching without session drops
|
|
19
19
|
- Lightweight observability via `cao status` and `cao list --details`
|
|
20
|
+
- Interactive switching and codex-auth snapshot import
|
|
20
21
|
- Strict TypeScript build with a small, dependency-light CLI
|
|
21
22
|
|
|
22
23
|
## Requirements
|
|
@@ -67,6 +68,18 @@ cao add accountA --device-auth
|
|
|
67
68
|
cao use accountA
|
|
68
69
|
```
|
|
69
70
|
|
|
71
|
+
Or pick interactively:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cao switch
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Check the current default account:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cao current
|
|
81
|
+
```
|
|
82
|
+
|
|
70
83
|
### 3. Run with fallback
|
|
71
84
|
|
|
72
85
|
```bash
|
|
@@ -99,6 +112,12 @@ cao list
|
|
|
99
112
|
cao status
|
|
100
113
|
```
|
|
101
114
|
|
|
115
|
+
### Compact summary
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cao status --compact
|
|
119
|
+
```
|
|
120
|
+
|
|
102
121
|
You can also use:
|
|
103
122
|
|
|
104
123
|
```bash
|
|
@@ -128,6 +147,12 @@ Gateway mode keeps the Codex session open while switching accounts on quota erro
|
|
|
128
147
|
cao gateway start
|
|
129
148
|
```
|
|
130
149
|
|
|
150
|
+
Run Codex through the gateway (no CLI fallback, gateway handles switching):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
cao run --gateway
|
|
154
|
+
```
|
|
155
|
+
|
|
131
156
|
Tune upstream retry/backoff (for transient 5xx/network errors):
|
|
132
157
|
|
|
133
158
|
```bash
|
|
@@ -192,6 +217,26 @@ Key files:
|
|
|
192
217
|
- `<account>/auth.json`: account-scoped tokens managed by Codex
|
|
193
218
|
- `<account>/config.toml`: account-scoped Codex configuration
|
|
194
219
|
|
|
220
|
+
## Migration from codex-auth
|
|
221
|
+
|
|
222
|
+
Import snapshots created by `codex-auth`:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
cao import codex-auth
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Custom source directory:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
cao import codex-auth --source ~/.codex/accounts
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Overwrite existing auth files if needed:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
cao import codex-auth --overwrite
|
|
238
|
+
```
|
|
239
|
+
|
|
195
240
|
## Development
|
|
196
241
|
|
|
197
242
|
Build:
|
package/dist/cli_main.js
CHANGED
|
@@ -6,7 +6,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const promises_1 = require("readline/promises");
|
|
10
12
|
const account_manager_1 = require("./account_manager");
|
|
11
13
|
const account_inspector_1 = require("./account_inspector");
|
|
12
14
|
const account_status_store_1 = require("./account_status_store");
|
|
@@ -78,10 +80,44 @@ program
|
|
|
78
80
|
}
|
|
79
81
|
renderAccountDetails(inspections);
|
|
80
82
|
});
|
|
83
|
+
program
|
|
84
|
+
.command("switch")
|
|
85
|
+
.argument("[name]", "Account name")
|
|
86
|
+
.description("Switch the default account (interactive if omitted)")
|
|
87
|
+
.action(async (name) => {
|
|
88
|
+
const baseDir = (0, paths_1.getBaseDir)(program.opts().dataDir);
|
|
89
|
+
(0, account_manager_1.ensureBaseDir)(baseDir);
|
|
90
|
+
const inspections = (0, account_inspector_1.inspectAccounts)(baseDir);
|
|
91
|
+
if (inspections.length === 0) {
|
|
92
|
+
process.stdout.write("No accounts registered. Use `cao add <name>` first.\n");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const resolved = name ?? (await promptForAccountSelection(inspections));
|
|
96
|
+
if (!resolved) {
|
|
97
|
+
process.stdout.write("No account selected.\n");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const registry = (0, account_manager_1.setDefaultAccount)(baseDir, resolved);
|
|
101
|
+
process.stdout.write(`Default account set to: ${registry.default_account}\n`);
|
|
102
|
+
});
|
|
103
|
+
program
|
|
104
|
+
.command("current")
|
|
105
|
+
.description("Show the current default account")
|
|
106
|
+
.action(() => {
|
|
107
|
+
const baseDir = (0, paths_1.getBaseDir)(program.opts().dataDir);
|
|
108
|
+
(0, account_manager_1.ensureBaseDir)(baseDir);
|
|
109
|
+
const registry = (0, registry_store_1.loadRegistry)(baseDir);
|
|
110
|
+
if (!registry.default_account) {
|
|
111
|
+
process.stdout.write("No default account set.\n");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
process.stdout.write(`${registry.default_account}\n`);
|
|
115
|
+
});
|
|
81
116
|
program
|
|
82
117
|
.command("status")
|
|
83
118
|
.description("Show detailed account status and cooldown/usage signals")
|
|
84
119
|
.option("--json", "Output account status as JSON")
|
|
120
|
+
.option("--compact", "Output a compact one-line summary per account")
|
|
85
121
|
.action((options) => {
|
|
86
122
|
const baseDir = (0, paths_1.getBaseDir)(program.opts().dataDir);
|
|
87
123
|
(0, account_manager_1.ensureBaseDir)(baseDir);
|
|
@@ -94,8 +130,97 @@ program
|
|
|
94
130
|
renderAccountDetailsJson(inspections);
|
|
95
131
|
return;
|
|
96
132
|
}
|
|
133
|
+
if (options.compact) {
|
|
134
|
+
renderAccountCompact(inspections);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
97
137
|
renderAccountDetails(inspections);
|
|
98
138
|
});
|
|
139
|
+
const importCommand = program.command("import").description("Import accounts from other tools");
|
|
140
|
+
importCommand
|
|
141
|
+
.command("codex-auth")
|
|
142
|
+
.description("Import account snapshots from codex-auth")
|
|
143
|
+
.option("--source <path>", "Source directory with codex-auth snapshots", path_1.default.join(os_1.default.homedir(), ".codex", "accounts"))
|
|
144
|
+
.option("--overwrite", "Overwrite existing auth.json files")
|
|
145
|
+
.option("--current <name>", "Treat this account as active during import")
|
|
146
|
+
.option("--default <name>", "Set this account as the default after import")
|
|
147
|
+
.action((options) => {
|
|
148
|
+
const baseDir = (0, paths_1.getBaseDir)(program.opts().dataDir);
|
|
149
|
+
(0, account_manager_1.ensureBaseDir)(baseDir);
|
|
150
|
+
const sourceDir = path_1.default.resolve(options.source);
|
|
151
|
+
if (!fs_1.default.existsSync(sourceDir)) {
|
|
152
|
+
process.stderr.write(`Source directory not found: ${sourceDir}\n`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const entries = fs_1.default.readdirSync(sourceDir);
|
|
157
|
+
const snapshotFiles = entries.filter((entry) => entry.endsWith(".json"));
|
|
158
|
+
if (snapshotFiles.length === 0) {
|
|
159
|
+
process.stdout.write(`No snapshot files found in ${sourceDir}.\n`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const importedNames = [];
|
|
163
|
+
let importedCount = 0;
|
|
164
|
+
let skippedCount = 0;
|
|
165
|
+
let errorCount = 0;
|
|
166
|
+
for (const fileName of snapshotFiles) {
|
|
167
|
+
const rawName = path_1.default.basename(fileName, ".json");
|
|
168
|
+
let normalizedName;
|
|
169
|
+
try {
|
|
170
|
+
normalizedName = (0, account_manager_1.validateAccountName)(rawName);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
process.stderr.write(`Skipping snapshot '${fileName}': ${error.message}\n`);
|
|
174
|
+
errorCount += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const sourcePath = path_1.default.join(sourceDir, fileName);
|
|
178
|
+
let parsed;
|
|
179
|
+
try {
|
|
180
|
+
parsed = JSON.parse(fs_1.default.readFileSync(sourcePath, "utf8"));
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
process.stderr.write(`Skipping snapshot '${fileName}': invalid JSON (${error.message}).\n`);
|
|
184
|
+
errorCount += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
(0, account_manager_1.addAccount)(baseDir, normalizedName);
|
|
188
|
+
const accountDir = (0, paths_1.getAccountDir)(baseDir, normalizedName);
|
|
189
|
+
(0, account_manager_1.ensureAccountConfig)(accountDir);
|
|
190
|
+
const authPath = getAuthFilePath(accountDir);
|
|
191
|
+
if (fs_1.default.existsSync(authPath) && !options.overwrite) {
|
|
192
|
+
skippedCount += 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
fs_1.default.writeFileSync(authPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
196
|
+
importedNames.push(normalizedName);
|
|
197
|
+
importedCount += 1;
|
|
198
|
+
}
|
|
199
|
+
const activeName = options.default ??
|
|
200
|
+
options.current ??
|
|
201
|
+
findCodexAuthActiveAccount(sourceDir) ??
|
|
202
|
+
undefined;
|
|
203
|
+
if (activeName) {
|
|
204
|
+
try {
|
|
205
|
+
const normalizedActive = (0, account_manager_1.validateAccountName)(activeName);
|
|
206
|
+
const registry = (0, registry_store_1.loadRegistry)(baseDir);
|
|
207
|
+
if (registry.accounts.includes(normalizedActive)) {
|
|
208
|
+
(0, account_manager_1.setDefaultAccount)(baseDir, normalizedActive);
|
|
209
|
+
process.stdout.write(`Default account set to: ${normalizedActive}\n`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
process.stderr.write(`Requested default '${normalizedActive}' not found in imported accounts.\n`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
process.stderr.write(`Unable to set default account '${activeName}': ${error.message}\n`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
process.stdout.write(`Import complete. Imported: ${importedCount}, skipped: ${skippedCount}, errors: ${errorCount}.\n`);
|
|
220
|
+
if (importedNames.length > 0) {
|
|
221
|
+
process.stdout.write(`Imported accounts: ${importedNames.join(", ")}\n`);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
99
224
|
program
|
|
100
225
|
.command("use")
|
|
101
226
|
.argument("<name>", "Account name")
|
|
@@ -221,6 +346,8 @@ program
|
|
|
221
346
|
.command("run")
|
|
222
347
|
.option("--account <name>", "Run with a specific account")
|
|
223
348
|
.option("--codex <path>", "Path to the codex binary", "codex")
|
|
349
|
+
.option("--gateway", "Route Codex traffic through the local gateway")
|
|
350
|
+
.option("--gateway-url <url>", "Gateway base URL", "http://127.0.0.1:4319")
|
|
224
351
|
.option("--no-fallback", "Disable automatic fallback")
|
|
225
352
|
.option("--max-passes <count>", "Retry passes when all accounts hit quota", "2")
|
|
226
353
|
.option("--retry-delay <seconds>", "Delay between retry passes in seconds", "0")
|
|
@@ -276,6 +403,74 @@ function normalizeCodexArgs(args, codexBin) {
|
|
|
276
403
|
function getAuthFilePath(accountDir) {
|
|
277
404
|
return path_1.default.join(accountDir, constants_1.AUTH_FILE_NAME);
|
|
278
405
|
}
|
|
406
|
+
async function promptForAccountSelection(inspections) {
|
|
407
|
+
if (!process.stdin.isTTY) {
|
|
408
|
+
process.stderr.write("No TTY available. Please provide an account name.\n");
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
process.stdout.write("Select an account:\n");
|
|
412
|
+
for (let index = 0; index < inspections.length; index += 1) {
|
|
413
|
+
const inspection = inspections[index];
|
|
414
|
+
const marker = inspection.isDefault ? "*" : " ";
|
|
415
|
+
process.stdout.write(`${index + 1}. ${marker} ${inspection.name}\n`);
|
|
416
|
+
}
|
|
417
|
+
const rl = (0, promises_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
418
|
+
const answer = (await rl.question("Enter a number or name (blank to cancel): ")).trim();
|
|
419
|
+
rl.close();
|
|
420
|
+
if (!answer) {
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
const index = Number.parseInt(answer, 10);
|
|
424
|
+
if (!Number.isNaN(index)) {
|
|
425
|
+
const selected = inspections[index - 1];
|
|
426
|
+
return selected?.name;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
const normalized = (0, account_manager_1.validateAccountName)(answer);
|
|
430
|
+
const exists = inspections.some((inspection) => inspection.name === normalized);
|
|
431
|
+
if (!exists) {
|
|
432
|
+
process.stderr.write(`Account not found: ${normalized}\n`);
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
return normalized;
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
process.stderr.write(`Invalid account name: ${error.message}\n`);
|
|
439
|
+
return undefined;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function findCodexAuthActiveAccount(sourceDir) {
|
|
443
|
+
const parentDir = path_1.default.dirname(sourceDir);
|
|
444
|
+
const candidates = [
|
|
445
|
+
path_1.default.join(sourceDir, ".active"),
|
|
446
|
+
path_1.default.join(sourceDir, "active"),
|
|
447
|
+
path_1.default.join(sourceDir, ".current"),
|
|
448
|
+
path_1.default.join(sourceDir, "current"),
|
|
449
|
+
path_1.default.join(sourceDir, ".selected"),
|
|
450
|
+
path_1.default.join(sourceDir, "selected"),
|
|
451
|
+
path_1.default.join(parentDir, ".active"),
|
|
452
|
+
path_1.default.join(parentDir, "active"),
|
|
453
|
+
path_1.default.join(parentDir, ".current"),
|
|
454
|
+
path_1.default.join(parentDir, "current"),
|
|
455
|
+
path_1.default.join(parentDir, ".selected"),
|
|
456
|
+
path_1.default.join(parentDir, "selected")
|
|
457
|
+
];
|
|
458
|
+
for (const candidate of candidates) {
|
|
459
|
+
if (!fs_1.default.existsSync(candidate)) {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const value = fs_1.default.readFileSync(candidate, "utf8").trim();
|
|
464
|
+
if (value.length > 0) {
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
279
474
|
function renderAccountSummary(inspections) {
|
|
280
475
|
for (const inspection of inspections) {
|
|
281
476
|
const marker = inspection.isDefault ? "*" : " ";
|
|
@@ -351,6 +546,18 @@ function renderAccountDetailsJson(inspections) {
|
|
|
351
546
|
const payload = inspections.map((inspection) => toAccountDetailRecord(inspection, referenceMs));
|
|
352
547
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
353
548
|
}
|
|
549
|
+
function renderAccountCompact(inspections) {
|
|
550
|
+
const referenceMs = Date.now();
|
|
551
|
+
for (const inspection of inspections) {
|
|
552
|
+
const marker = inspection.isDefault ? "*" : " ";
|
|
553
|
+
const status = inspection.status ?? {};
|
|
554
|
+
const loginStatus = inspection.loggedIn ? "logged-in" : "not-logged-in";
|
|
555
|
+
const expires = formatExpiryShort(inspection.tokenDetails?.expiresAtMs, referenceMs);
|
|
556
|
+
const lastQuota = formatRelativeShort(status.lastQuotaAtMs, referenceMs);
|
|
557
|
+
const cooldown = formatCooldownShort(status.cooldownUntilMs, referenceMs);
|
|
558
|
+
process.stdout.write(`${marker} ${inspection.name} (${loginStatus}) | expires: ${expires} | cooldown: ${cooldown} | last_quota: ${lastQuota} | failures: ${status.consecutiveFailures ?? 0}\n`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
354
561
|
function formatDuration(durationMs) {
|
|
355
562
|
const totalSeconds = Math.max(0, Math.floor(durationMs / 1000));
|
|
356
563
|
if (totalSeconds < 60) {
|
|
@@ -382,6 +589,32 @@ function formatTimestampWithRelative(timestampMs, referenceMs) {
|
|
|
382
589
|
const relative = diffMs > 0 ? `in ${formatDuration(diffMs)}` : `${formatDuration(-diffMs)} ago`;
|
|
383
590
|
return `${iso} (${relative})`;
|
|
384
591
|
}
|
|
592
|
+
function formatRelativeShort(timestampMs, referenceMs) {
|
|
593
|
+
if (!timestampMs) {
|
|
594
|
+
return "none";
|
|
595
|
+
}
|
|
596
|
+
const diffMs = timestampMs - referenceMs;
|
|
597
|
+
if (diffMs <= 0) {
|
|
598
|
+
return `${formatDuration(-diffMs)} ago`;
|
|
599
|
+
}
|
|
600
|
+
return `in ${formatDuration(diffMs)}`;
|
|
601
|
+
}
|
|
602
|
+
function formatExpiryShort(timestampMs, referenceMs) {
|
|
603
|
+
if (!timestampMs) {
|
|
604
|
+
return "unknown";
|
|
605
|
+
}
|
|
606
|
+
const diffMs = timestampMs - referenceMs;
|
|
607
|
+
if (diffMs <= 0) {
|
|
608
|
+
return "expired";
|
|
609
|
+
}
|
|
610
|
+
return `in ${formatDuration(diffMs)}`;
|
|
611
|
+
}
|
|
612
|
+
function formatCooldownShort(timestampMs, referenceMs) {
|
|
613
|
+
if (!timestampMs || timestampMs <= referenceMs) {
|
|
614
|
+
return "none";
|
|
615
|
+
}
|
|
616
|
+
return `in ${formatDuration(timestampMs - referenceMs)}`;
|
|
617
|
+
}
|
|
385
618
|
function formatCooldown(cooldownUntilMs, referenceMs) {
|
|
386
619
|
if (!cooldownUntilMs) {
|
|
387
620
|
return "none";
|
|
@@ -433,8 +666,16 @@ function buildGatewayOverrides(options) {
|
|
|
433
666
|
}
|
|
434
667
|
async function runWithFallback(options, baseDir, accounts, codexArgs) {
|
|
435
668
|
const codexBin = options.codex;
|
|
669
|
+
const gatewayUrl = options.gateway ? options.gatewayUrl : undefined;
|
|
670
|
+
const fallbackEnabled = options.fallback && !options.gateway;
|
|
436
671
|
const maxPasses = normalizeMaxPasses(options.maxPasses);
|
|
437
672
|
const retryDelayMs = normalizeDelay(options.retryDelay);
|
|
673
|
+
if (options.gateway && gatewayUrl) {
|
|
674
|
+
process.stderr.write(`Gateway routing enabled: ${gatewayUrl}\n`);
|
|
675
|
+
if (options.fallback) {
|
|
676
|
+
process.stderr.write("Gateway mode disables CLI fallback (handled by gateway).\n");
|
|
677
|
+
}
|
|
678
|
+
}
|
|
438
679
|
for (let passIndex = 0; passIndex < maxPasses; passIndex += 1) {
|
|
439
680
|
let quotaFailures = 0;
|
|
440
681
|
let lastExitCode = 1;
|
|
@@ -448,7 +689,7 @@ async function runWithFallback(options, baseDir, accounts, codexArgs) {
|
|
|
448
689
|
lastAttemptAtMs: attemptAtMs
|
|
449
690
|
}));
|
|
450
691
|
process.stderr.write(`Using account: ${name}\n`);
|
|
451
|
-
const result = await (0, process_runner_1.runCodexOnce)(codexBin, codexArgs, accountDir,
|
|
692
|
+
const result = await (0, process_runner_1.runCodexOnce)(codexBin, codexArgs, accountDir, fallbackEnabled, gatewayUrl ? { OPENAI_BASE_URL: gatewayUrl } : {});
|
|
452
693
|
lastExitCode = result.exitCode;
|
|
453
694
|
if (result.exitCode === 0) {
|
|
454
695
|
(0, account_status_store_1.updateAccountStatus)(baseDir, name, (previous) => ({
|
|
@@ -462,7 +703,7 @@ async function runWithFallback(options, baseDir, accounts, codexArgs) {
|
|
|
462
703
|
process.exit(0);
|
|
463
704
|
return;
|
|
464
705
|
}
|
|
465
|
-
if (!
|
|
706
|
+
if (!fallbackEnabled) {
|
|
466
707
|
(0, account_status_store_1.updateAccountStatus)(baseDir, name, (previous) => ({
|
|
467
708
|
...previous,
|
|
468
709
|
lastAttemptAtMs: attemptAtMs,
|
|
@@ -500,7 +741,7 @@ async function runWithFallback(options, baseDir, accounts, codexArgs) {
|
|
|
500
741
|
process.stderr.write(`Quota exhausted. Falling back to: ${nextName}\n`);
|
|
501
742
|
}
|
|
502
743
|
}
|
|
503
|
-
if (!
|
|
744
|
+
if (!fallbackEnabled) {
|
|
504
745
|
process.exit(lastExitCode);
|
|
505
746
|
return;
|
|
506
747
|
}
|
package/dist/process_runner.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export interface RunResult {
|
|
|
2
2
|
exitCode: number;
|
|
3
3
|
quotaError: boolean;
|
|
4
4
|
}
|
|
5
|
-
export declare function runCodexOnce(codexBin: string, codexArgs: string[], accountDir: string, captureOutput: boolean): Promise<RunResult>;
|
|
5
|
+
export declare function runCodexOnce(codexBin: string, codexArgs: string[], accountDir: string, captureOutput: boolean, envOverrides?: Record<string, string | undefined>): Promise<RunResult>;
|
package/dist/process_runner.js
CHANGED
|
@@ -4,9 +4,13 @@ exports.runCodexOnce = runCodexOnce;
|
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const output_capture_1 = require("./output_capture");
|
|
6
6
|
const quota_detector_1 = require("./quota_detector");
|
|
7
|
-
async function runCodexOnce(codexBin, codexArgs, accountDir, captureOutput) {
|
|
7
|
+
async function runCodexOnce(codexBin, codexArgs, accountDir, captureOutput, envOverrides = {}) {
|
|
8
8
|
const capture = new output_capture_1.OutputCapture();
|
|
9
|
-
const env = {
|
|
9
|
+
const env = {
|
|
10
|
+
...process.env,
|
|
11
|
+
CODEX_HOME: accountDir,
|
|
12
|
+
...envOverrides
|
|
13
|
+
};
|
|
10
14
|
const child = (0, child_process_1.spawn)(codexBin, codexArgs, {
|
|
11
15
|
env,
|
|
12
16
|
stdio: captureOutput ? ["inherit", "pipe", "pipe"] : ["inherit", "inherit", "inherit"]
|