oc-codex-multi-auth 6.19.0 → 6.21.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 +15 -12
- package/config/README.md +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +473 -145
- package/dist/index.js.map +1 -1
- package/dist/lib/account-display.d.ts.map +1 -1
- package/dist/lib/account-display.js +3 -1
- package/dist/lib/account-display.js.map +1 -1
- package/dist/lib/accounts/persistence.d.ts +25 -1
- package/dist/lib/accounts/persistence.d.ts.map +1 -1
- package/dist/lib/accounts/persistence.js +105 -2
- package/dist/lib/accounts/persistence.js.map +1 -1
- package/dist/lib/accounts/rate-limits.d.ts +22 -0
- package/dist/lib/accounts/rate-limits.d.ts.map +1 -1
- package/dist/lib/accounts/rate-limits.js +25 -0
- package/dist/lib/accounts/rate-limits.js.map +1 -1
- package/dist/lib/accounts/rotation.d.ts +22 -23
- package/dist/lib/accounts/rotation.d.ts.map +1 -1
- package/dist/lib/accounts/rotation.js +62 -37
- package/dist/lib/accounts/rotation.js.map +1 -1
- package/dist/lib/accounts/stale-state.d.ts +32 -0
- package/dist/lib/accounts/stale-state.d.ts.map +1 -1
- package/dist/lib/accounts/stale-state.js +86 -1
- package/dist/lib/accounts/stale-state.js.map +1 -1
- package/dist/lib/accounts/state.d.ts +6 -0
- package/dist/lib/accounts/state.d.ts.map +1 -1
- package/dist/lib/accounts/state.js +15 -1
- package/dist/lib/accounts/state.js.map +1 -1
- package/dist/lib/accounts/warm-recovery.d.ts +8 -0
- package/dist/lib/accounts/warm-recovery.d.ts.map +1 -0
- package/dist/lib/accounts/warm-recovery.js +70 -0
- package/dist/lib/accounts/warm-recovery.js.map +1 -0
- package/dist/lib/accounts/warm-request.d.ts +2 -0
- package/dist/lib/accounts/warm-request.d.ts.map +1 -1
- package/dist/lib/accounts/warm-request.js +4 -2
- package/dist/lib/accounts/warm-request.js.map +1 -1
- package/dist/lib/accounts.d.ts +1 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +2 -2
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auth/login-runner.d.ts +3 -0
- package/dist/lib/auth/login-runner.d.ts.map +1 -1
- package/dist/lib/auth/login-runner.js +35 -0
- package/dist/lib/auth/login-runner.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +5 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -1
- package/dist/lib/circuit-breaker.js +23 -6
- package/dist/lib/circuit-breaker.js.map +1 -1
- package/dist/lib/codex-reset.d.ts +7 -1
- package/dist/lib/codex-reset.d.ts.map +1 -1
- package/dist/lib/codex-reset.js +9 -2
- package/dist/lib/codex-reset.js.map +1 -1
- package/dist/lib/codex-usage.d.ts +16 -6
- package/dist/lib/codex-usage.d.ts.map +1 -1
- package/dist/lib/codex-usage.js +68 -23
- package/dist/lib/codex-usage.js.map +1 -1
- package/dist/lib/config.d.ts +2 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +38 -14
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/parallel-probe.d.ts.map +1 -1
- package/dist/lib/parallel-probe.js +5 -2
- package/dist/lib/parallel-probe.js.map +1 -1
- package/dist/lib/quota-notifications.d.ts.map +1 -1
- package/dist/lib/quota-notifications.js +10 -1
- package/dist/lib/quota-notifications.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +24 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +88 -38
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/response-handler.d.ts.map +1 -1
- package/dist/lib/request/response-handler.js +85 -43
- package/dist/lib/request/response-handler.js.map +1 -1
- package/dist/lib/rotation.d.ts +1 -1
- package/dist/lib/schemas.d.ts +9 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +3 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/storage/flagged.d.ts.map +1 -1
- package/dist/lib/storage/flagged.js +5 -0
- package/dist/lib/storage/flagged.js.map +1 -1
- package/dist/lib/storage/load-save.d.ts +1 -0
- package/dist/lib/storage/load-save.d.ts.map +1 -1
- package/dist/lib/storage/load-save.js +11 -0
- package/dist/lib/storage/load-save.js.map +1 -1
- package/dist/lib/storage/migrations.d.ts +21 -0
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage/migrations.js.map +1 -1
- package/dist/lib/storage/normalize.d.ts.map +1 -1
- package/dist/lib/storage/normalize.js +93 -1
- package/dist/lib/storage/normalize.js.map +1 -1
- package/dist/lib/storage/state.d.ts +1 -0
- package/dist/lib/storage/state.d.ts.map +1 -1
- package/dist/lib/storage/state.js +12 -0
- package/dist/lib/storage/state.js.map +1 -1
- package/dist/lib/tools/codex-doctor.d.ts.map +1 -1
- package/dist/lib/tools/codex-doctor.js +8 -66
- package/dist/lib/tools/codex-doctor.js.map +1 -1
- package/dist/lib/tools/codex-health.d.ts.map +1 -1
- package/dist/lib/tools/codex-health.js +7 -1
- package/dist/lib/tools/codex-health.js.map +1 -1
- package/dist/lib/tools/codex-limits.d.ts.map +1 -1
- package/dist/lib/tools/codex-limits.js +13 -1
- package/dist/lib/tools/codex-limits.js.map +1 -1
- package/dist/lib/tools/codex-list.d.ts.map +1 -1
- package/dist/lib/tools/codex-list.js +14 -1
- package/dist/lib/tools/codex-list.js.map +1 -1
- package/dist/lib/tools/codex-reset.d.ts.map +1 -1
- package/dist/lib/tools/codex-reset.js +40 -1
- package/dist/lib/tools/codex-reset.js.map +1 -1
- package/dist/lib/tools/codex-status.d.ts.map +1 -1
- package/dist/lib/tools/codex-status.js +6 -1
- package/dist/lib/tools/codex-status.js.map +1 -1
- package/dist/lib/tools/codex-warm.d.ts +2 -1
- package/dist/lib/tools/codex-warm.d.ts.map +1 -1
- package/dist/lib/tools/codex-warm.js +48 -5
- package/dist/lib/tools/codex-warm.js.map +1 -1
- package/dist/lib/tools/doctor-repair.d.ts +10 -0
- package/dist/lib/tools/doctor-repair.d.ts.map +1 -0
- package/dist/lib/tools/doctor-repair.js +86 -0
- package/dist/lib/tools/doctor-repair.js.map +1 -0
- package/dist/lib/tools/index.d.ts +4 -1
- package/dist/lib/tools/index.d.ts.map +1 -1
- package/dist/lib/tools/index.js.map +1 -1
- package/dist/lib/tui-quota-cache.d.ts.map +1 -1
- package/dist/lib/tui-quota-cache.js +3 -1
- package/dist/lib/tui-quota-cache.js.map +1 -1
- package/dist/tui.d.ts +4 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +24 -14
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/scripts/install-oc-codex-multi-auth-core.js +168 -11
|
@@ -257,8 +257,28 @@ async function readStandaloneStorage(path) {
|
|
|
257
257
|
try {
|
|
258
258
|
const raw = await readFile(path, "utf-8");
|
|
259
259
|
const parsed = JSON.parse(raw);
|
|
260
|
+
// Shape validation, not just parse validation: a JSON array, scalar, or
|
|
261
|
+
// object without an `accounts` array is unreadable by the plugin runtime
|
|
262
|
+
// too (normalizeAccountStorage rejects it), so reporting it as a healthy
|
|
263
|
+
// empty pool (exit 0, "No accounts configured") hides the corruption
|
|
264
|
+
// from scripted callers that key on exit codes.
|
|
265
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
266
|
+
return { storage: null, error: "Storage file must be a JSON object with an accounts array." };
|
|
267
|
+
}
|
|
268
|
+
// Forward-compat mirror of the runtime guard: a newer schema version
|
|
269
|
+
// must not be shown as readable accounts by this build.
|
|
270
|
+
const version = parsed.version;
|
|
271
|
+
if (typeof version === "number" && Number.isFinite(version) && version > 3) {
|
|
272
|
+
return {
|
|
273
|
+
storage: null,
|
|
274
|
+
error: `Unsupported account storage schema version ${version}; this build supports up to version 3.`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (!Array.isArray(parsed.accounts)) {
|
|
278
|
+
return { storage: null, error: "Storage file must be a JSON object with an accounts array." };
|
|
279
|
+
}
|
|
260
280
|
return {
|
|
261
|
-
storage:
|
|
281
|
+
storage: normalizeStandaloneStorage(parsed),
|
|
262
282
|
error: null,
|
|
263
283
|
};
|
|
264
284
|
} catch (error) {
|
|
@@ -417,6 +437,7 @@ function summarizeStandaloneAccounts(storage, includeSensitive, tag) {
|
|
|
417
437
|
tags: Array.isArray(account?.accountTags) ? account.accountTags : [],
|
|
418
438
|
note: account?.accountNote,
|
|
419
439
|
rateLimitResetTimes: account?.rateLimitResetTimes ?? {},
|
|
440
|
+
quotaExhaustedUntil: account?.quotaExhaustedUntil,
|
|
420
441
|
};
|
|
421
442
|
});
|
|
422
443
|
}
|
|
@@ -440,6 +461,8 @@ function printStandaloneResult(command, payload, json) {
|
|
|
440
461
|
}
|
|
441
462
|
}
|
|
442
463
|
if (payload.error) console.log(`Error: ${payload.error}`);
|
|
464
|
+
for (const fix of payload.appliedFixes ?? []) console.log(`Fixed: ${fix}`);
|
|
465
|
+
for (const error of payload.fixErrors ?? []) console.log(`Repair failed: ${error}`);
|
|
443
466
|
if (payload.nextAction) console.log(`Next: ${payload.nextAction}`);
|
|
444
467
|
}
|
|
445
468
|
|
|
@@ -462,13 +485,14 @@ async function loadDistModules(relativePaths, label) {
|
|
|
462
485
|
}
|
|
463
486
|
|
|
464
487
|
async function loadWarmRuntime(env) {
|
|
465
|
-
const [storageMod, usageMod, warmReqMod, warmMod, shutdownMod] = await loadDistModules(
|
|
488
|
+
const [storageMod, usageMod, warmReqMod, warmMod, shutdownMod, recoveryMod] = await loadDistModules(
|
|
466
489
|
[
|
|
467
490
|
"storage.js",
|
|
468
491
|
"codex-usage.js",
|
|
469
492
|
"accounts/warm-request.js",
|
|
470
493
|
"accounts/warm.js",
|
|
471
494
|
"shutdown.js",
|
|
495
|
+
"accounts/warm-recovery.js",
|
|
472
496
|
],
|
|
473
497
|
"warm",
|
|
474
498
|
);
|
|
@@ -477,7 +501,7 @@ async function loadWarmRuntime(env) {
|
|
|
477
501
|
// Refreshing a token here persists credentials, which registers the
|
|
478
502
|
// shutdown handler via the storage lock.
|
|
479
503
|
shutdownMod.setShutdownOwnsProcess(true);
|
|
480
|
-
return { storageMod, usageMod, warmReqMod, warmMod, shutdownMod };
|
|
504
|
+
return { storageMod, usageMod, warmReqMod, warmMod, shutdownMod, recoveryMod };
|
|
481
505
|
}
|
|
482
506
|
|
|
483
507
|
async function loadLimitsRuntime(env) {
|
|
@@ -497,26 +521,47 @@ export async function runWarmCommand(parsed, options = {}) {
|
|
|
497
521
|
|
|
498
522
|
let runtime;
|
|
499
523
|
try {
|
|
500
|
-
runtime = await loadWarmRuntime(env);
|
|
524
|
+
runtime = await (options.loadWarmRuntime ?? loadWarmRuntime)(env);
|
|
501
525
|
} catch (error) {
|
|
502
526
|
const payload = { command: "warm", storagePath, error: formatErrorForLog(error) };
|
|
503
527
|
printWarmResult(payload, parsed.json);
|
|
504
528
|
return { exitCode: 1, action: "warm", storagePath };
|
|
505
529
|
}
|
|
506
530
|
|
|
507
|
-
const { storageMod, usageMod, warmReqMod, warmMod } = runtime;
|
|
531
|
+
const { storageMod, usageMod, warmReqMod, warmMod, recoveryMod } = runtime;
|
|
508
532
|
// Point dist storage at the resolved accounts file so a refreshed token is
|
|
509
533
|
// persisted to the SAME file the rest of the toolchain reads.
|
|
510
534
|
storageMod.setStoragePathDirect(storagePath);
|
|
511
535
|
|
|
512
|
-
|
|
536
|
+
let storage = null;
|
|
537
|
+
try {
|
|
538
|
+
storage = await storageMod.loadAccounts();
|
|
539
|
+
} catch (error) {
|
|
540
|
+
// Typed storage errors (e.g. UNSUPPORTED_SCHEMA_VERSION) carry the
|
|
541
|
+
// upgrade hint; surface them rather than crashing the CLI.
|
|
542
|
+
const hint = error && typeof error.hint === "string" ? ` ${error.hint}` : "";
|
|
543
|
+
const payload = { command: "warm", storagePath, error: `${formatErrorForLog(error)}${hint}` };
|
|
544
|
+
printWarmResult(payload, parsed.json);
|
|
545
|
+
return { exitCode: 1, action: "warm", storagePath };
|
|
546
|
+
}
|
|
513
547
|
const accounts = Array.isArray(storage?.accounts) ? storage.accounts : [];
|
|
514
548
|
if (accounts.length === 0) {
|
|
549
|
+
// `loadAccounts` swallows parse/IO errors and returns null. Probe the
|
|
550
|
+
// file so a corrupt storage fails like `status`/`doctor` do (exit 1)
|
|
551
|
+
// instead of reporting a healthy empty pool. ENOENT stays a silent
|
|
552
|
+
// empty pool: a missing file legitimately means no accounts yet.
|
|
553
|
+
const probe = await readStandaloneStorage(storagePath);
|
|
554
|
+
if (probe.error) {
|
|
555
|
+
const payload = { command: "warm", storagePath, error: probe.error };
|
|
556
|
+
printWarmResult(payload, parsed.json);
|
|
557
|
+
return { exitCode: 1, action: "warm", storagePath };
|
|
558
|
+
}
|
|
515
559
|
const payload = {
|
|
516
560
|
command: "warm",
|
|
517
561
|
storagePath,
|
|
518
562
|
totalAccounts: 0,
|
|
519
563
|
warmed: 0,
|
|
564
|
+
blocksCleared: 0,
|
|
520
565
|
failed: 0,
|
|
521
566
|
skipped: 0,
|
|
522
567
|
results: [],
|
|
@@ -530,7 +575,9 @@ export async function runWarmCommand(parsed, options = {}) {
|
|
|
530
575
|
// Same adapter as lib/tools/codex-warm.ts createWarmOne: refresh → resolve
|
|
531
576
|
// account id → open the usage window; map an exhausted (quota-429) account
|
|
532
577
|
// to a failure so it is not reported as warmed.
|
|
578
|
+
const succeeded = [];
|
|
533
579
|
const warmOne = async (account) => {
|
|
580
|
+
const snapshot = { ...account, rateLimitResetTimes: { ...account.rateLimitResetTimes } };
|
|
534
581
|
const { accessToken } = await usageMod.ensureCodexUsageAccessToken({ storage, account });
|
|
535
582
|
const accountId = usageMod.resolveCodexUsageAccountId({ account, accessToken });
|
|
536
583
|
if (!accountId) {
|
|
@@ -544,12 +591,29 @@ export async function runWarmCommand(parsed, options = {}) {
|
|
|
544
591
|
if (result.status === "exhausted") {
|
|
545
592
|
return { status: "failed", detail: result.detail ?? "quota/usage limit reached" };
|
|
546
593
|
}
|
|
594
|
+
if (!result.rateLimited && result.model) succeeded.push({
|
|
595
|
+
account: { ...snapshot, refreshToken: account.refreshToken }, model: result.model, accessToken,
|
|
596
|
+
});
|
|
547
597
|
return { status: "warmed" };
|
|
548
598
|
};
|
|
549
599
|
|
|
550
600
|
const summary = await warmMod.warmAccounts(accounts, warmOne);
|
|
601
|
+
let blocksCleared = 0;
|
|
602
|
+
let blockClearError;
|
|
603
|
+
for (const observation of succeeded) {
|
|
604
|
+
let changed = false;
|
|
605
|
+
try {
|
|
606
|
+
const completed = await recoveryMod.recoverWarmedAccount(observation, () => { changed = true; });
|
|
607
|
+
changed = completed || changed;
|
|
608
|
+
} catch {
|
|
609
|
+
blockClearError = "Failed to clear local blocks; warm results are unchanged.";
|
|
610
|
+
}
|
|
611
|
+
if (changed) blocksCleared++;
|
|
612
|
+
}
|
|
551
613
|
const payload = {
|
|
552
614
|
command: "warm",
|
|
615
|
+
blocksCleared,
|
|
616
|
+
blockClearError,
|
|
553
617
|
storagePath,
|
|
554
618
|
totalAccounts: summary.total,
|
|
555
619
|
warmed: summary.warmedCount,
|
|
@@ -585,6 +649,8 @@ function printWarmResult(payload, json) {
|
|
|
585
649
|
console.log(`- ${label}: ${r.status}${detail}`);
|
|
586
650
|
}
|
|
587
651
|
console.log(`Summary: ${payload.warmed} warmed, ${payload.failed} failed, ${payload.skipped} skipped`);
|
|
652
|
+
console.log(`Blocks cleared: ${payload.blocksCleared ?? 0}`);
|
|
653
|
+
if (payload.blockClearError) console.log(payload.blockClearError);
|
|
588
654
|
if (payload.nextAction) console.log(`Next: ${payload.nextAction}`);
|
|
589
655
|
}
|
|
590
656
|
|
|
@@ -603,7 +669,7 @@ export async function runLimitsCommand(parsed, options = {}) {
|
|
|
603
669
|
|
|
604
670
|
let runtime;
|
|
605
671
|
try {
|
|
606
|
-
runtime = await loadLimitsRuntime(env);
|
|
672
|
+
runtime = await (options.loadLimitsRuntime ?? loadLimitsRuntime)(env);
|
|
607
673
|
} catch (error) {
|
|
608
674
|
const payload = { command: "limits", storagePath, error: formatErrorForLog(error) };
|
|
609
675
|
printLimitsResult(payload, parsed.json);
|
|
@@ -615,9 +681,27 @@ export async function runLimitsCommand(parsed, options = {}) {
|
|
|
615
681
|
// persisted to the SAME file the rest of the toolchain reads.
|
|
616
682
|
storageMod.setStoragePathDirect(storagePath);
|
|
617
683
|
|
|
618
|
-
|
|
684
|
+
let storage = null;
|
|
685
|
+
try {
|
|
686
|
+
storage = await storageMod.loadAccounts();
|
|
687
|
+
} catch (error) {
|
|
688
|
+
// Typed storage errors (e.g. UNSUPPORTED_SCHEMA_VERSION) carry the
|
|
689
|
+
// upgrade hint; surface them rather than crashing the CLI.
|
|
690
|
+
const hint = error && typeof error.hint === "string" ? ` ${error.hint}` : "";
|
|
691
|
+
const payload = { command: "limits", storagePath, error: `${formatErrorForLog(error)}${hint}` };
|
|
692
|
+
printLimitsResult(payload, parsed.json);
|
|
693
|
+
return { exitCode: 1, action: "limits", storagePath };
|
|
694
|
+
}
|
|
619
695
|
const accounts = Array.isArray(storage?.accounts) ? storage.accounts : [];
|
|
620
696
|
if (accounts.length === 0) {
|
|
697
|
+
// Same probe contract as `warm`: a corrupt file exits 1 like
|
|
698
|
+
// `status`/`doctor`; a missing file stays a silent empty pool.
|
|
699
|
+
const probe = await readStandaloneStorage(storagePath);
|
|
700
|
+
if (probe.error) {
|
|
701
|
+
const payload = { command: "limits", storagePath, error: probe.error };
|
|
702
|
+
printLimitsResult(payload, parsed.json);
|
|
703
|
+
return { exitCode: 1, action: "limits", storagePath };
|
|
704
|
+
}
|
|
621
705
|
const payload = {
|
|
622
706
|
command: "limits",
|
|
623
707
|
storagePath,
|
|
@@ -658,6 +742,7 @@ export async function runLimitsCommand(parsed, options = {}) {
|
|
|
658
742
|
label: account.accountLabel ?? `Account ${index + 1}`,
|
|
659
743
|
email: maskValue(account.email, parsed.includeSensitive),
|
|
660
744
|
rateLimitResetTimes: account.rateLimitResetTimes ?? {},
|
|
745
|
+
quotaExhaustedUntil: account.quotaExhaustedUntil,
|
|
661
746
|
};
|
|
662
747
|
try {
|
|
663
748
|
const { accessToken } = await usageMod.ensureCodexUsageAccessToken({ storage, account });
|
|
@@ -685,6 +770,13 @@ export async function runLimitsCommand(parsed, options = {}) {
|
|
|
685
770
|
);
|
|
686
771
|
}
|
|
687
772
|
}
|
|
773
|
+
if (usageMod.isUsageQuotaRecovered([usage.primary, usage.secondary])) {
|
|
774
|
+
try {
|
|
775
|
+
await usageMod.persistUsageQuotaRecovery(account);
|
|
776
|
+
} catch {
|
|
777
|
+
loggerMod.logWarn("Failed to persist recovered usage quota");
|
|
778
|
+
}
|
|
779
|
+
}
|
|
688
780
|
entry.planType = usage.planType;
|
|
689
781
|
entry.credits = usage.credits;
|
|
690
782
|
// Raw counts stay in `resetCredits` and the rendered line lives in
|
|
@@ -771,7 +863,68 @@ export async function runStandaloneCommand(command, argv = [], options = {}) {
|
|
|
771
863
|
}
|
|
772
864
|
const { env = process.env } = options;
|
|
773
865
|
const storagePath = getStandaloneStoragePath(parsed, env);
|
|
774
|
-
const
|
|
866
|
+
const repairRequested = command === "doctor" && parsed.fix;
|
|
867
|
+
let storage = null;
|
|
868
|
+
let error = null;
|
|
869
|
+
if (parsed.configPath || !repairRequested) {
|
|
870
|
+
({ storage, error } = await readStandaloneStorage(storagePath));
|
|
871
|
+
}
|
|
872
|
+
const appliedFixes = [];
|
|
873
|
+
const fixErrors = [];
|
|
874
|
+
if (repairRequested && !error) {
|
|
875
|
+
const previousKeychain = process.env.CODEX_KEYCHAIN;
|
|
876
|
+
try {
|
|
877
|
+
const loadDoctorRuntime = options.loadDoctorRuntime ?? (() => loadDistModules(
|
|
878
|
+
["storage.js", "tools/doctor-repair.js", "shutdown.js"], "doctor",
|
|
879
|
+
));
|
|
880
|
+
const [storageMod, repairMod, shutdownMod] = await loadDoctorRuntime();
|
|
881
|
+
// A CLI file selection must not read or replace the global keychain pool.
|
|
882
|
+
if (parsed.configPath) process.env.CODEX_KEYCHAIN = "0";
|
|
883
|
+
storageMod.setStoragePathDirect(storagePath);
|
|
884
|
+
shutdownMod.setShutdownOwnsProcess(true);
|
|
885
|
+
try {
|
|
886
|
+
storage = await storageMod.loadAccounts();
|
|
887
|
+
} catch (loadError) {
|
|
888
|
+
// Typed storage errors (UNSUPPORTED_SCHEMA_VERSION, unknown V2)
|
|
889
|
+
// carry exact in-tree copy plus an upgrade/recovery hint, and the
|
|
890
|
+
// load never got far enough to attempt a repair. Surface them on
|
|
891
|
+
// the error channel (exit 1) instead of the generic catch below,
|
|
892
|
+
// which is reserved for unknown throws so upstream failure text
|
|
893
|
+
// never reaches output unredacted.
|
|
894
|
+
if (loadError && typeof loadError.code === "string") {
|
|
895
|
+
const hint = typeof loadError.hint === "string" ? ` ${loadError.hint}` : "";
|
|
896
|
+
error = `${formatErrorForLog(loadError)}${hint}`;
|
|
897
|
+
} else {
|
|
898
|
+
throw loadError;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (!storage && !error) {
|
|
902
|
+
// `loadAccounts` swallows JSON parse/IO errors and returns null. In
|
|
903
|
+
// default-path mode the pre-read above was skipped (keychain routing
|
|
904
|
+
// may own the pool), so probe the JSON file here: a corrupt file must
|
|
905
|
+
// surface as a parse error (exit 1) instead of "No accounts
|
|
906
|
+
// configured" (exit 0). ENOENT stays silent - a missing file with an
|
|
907
|
+
// empty keychain legitimately means no accounts yet. Skipped when a
|
|
908
|
+
// typed load error already set `error`; the probe would only
|
|
909
|
+
// overwrite the precise schema message with its own paraphrase.
|
|
910
|
+
const probe = await readStandaloneStorage(storagePath);
|
|
911
|
+
if (probe.error) error = probe.error;
|
|
912
|
+
}
|
|
913
|
+
if (!error) {
|
|
914
|
+
const repair = await repairMod.repairDoctorAccounts(storage?.accounts ?? []);
|
|
915
|
+
appliedFixes.push(...repair.appliedFixes);
|
|
916
|
+
fixErrors.push(...repair.fixErrors);
|
|
917
|
+
storage = (await storageMod.loadAccounts()) ?? storage;
|
|
918
|
+
}
|
|
919
|
+
} catch {
|
|
920
|
+
fixErrors.push("Doctor repair could not complete. Check the selected storage file and installed runtime.");
|
|
921
|
+
} finally {
|
|
922
|
+
if (parsed.configPath) {
|
|
923
|
+
if (previousKeychain === undefined) delete process.env.CODEX_KEYCHAIN;
|
|
924
|
+
else process.env.CODEX_KEYCHAIN = previousKeychain;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
775
928
|
const accounts = summarizeStandaloneAccounts(storage, parsed.includeSensitive, parsed.tag);
|
|
776
929
|
const totalAccounts = Array.isArray(storage?.accounts) ? storage.accounts.length : 0;
|
|
777
930
|
const payload = {
|
|
@@ -790,7 +943,11 @@ export async function runStandaloneCommand(command, argv = [], options = {}) {
|
|
|
790
943
|
} else if (command === "doctor") {
|
|
791
944
|
payload.message = error ? "Storage could not be parsed." : totalAccounts > 0 ? "Local diagnostics completed." : "No accounts configured.";
|
|
792
945
|
payload.deep = parsed.deep;
|
|
793
|
-
payload.fixApplied = parsed.fix ?
|
|
946
|
+
payload.fixApplied = parsed.fix ? appliedFixes.length > 0 : undefined;
|
|
947
|
+
if (parsed.fix) {
|
|
948
|
+
payload.appliedFixes = appliedFixes;
|
|
949
|
+
payload.fixErrors = fixErrors;
|
|
950
|
+
}
|
|
794
951
|
payload.nextAction = totalAccounts > 0 ? "Run oc-codex-multi-auth health --json for scriptable checks." : "Run opencode auth login.";
|
|
795
952
|
} else if (command === "health") {
|
|
796
953
|
payload.healthyCount = accounts.filter((account) => account.enabled && account.hasRefreshToken).length;
|
|
@@ -799,7 +956,7 @@ export async function runStandaloneCommand(command, argv = [], options = {}) {
|
|
|
799
956
|
payload.message = totalAccounts > 0 ? "Account storage loaded." : "No accounts configured.";
|
|
800
957
|
}
|
|
801
958
|
printStandaloneResult(command, payload, parsed.json);
|
|
802
|
-
return { exitCode: error ? 1 : 0, action: command, storagePath };
|
|
959
|
+
return { exitCode: error || fixErrors.length > 0 ? 1 : 0, action: command, storagePath };
|
|
803
960
|
}
|
|
804
961
|
|
|
805
962
|
// Top-level keys inside `provider.openai` that the installer owns absolutely.
|