codex-multi-auth 2.0.0 → 2.0.1

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 (86) hide show
  1. package/README.md +7 -6
  2. package/config/README.md +1 -1
  3. package/config/codex-legacy.json +571 -646
  4. package/config/codex-modern.json +258 -274
  5. package/config/minimal-codex.json +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +85 -14
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/auto-update-checker.d.ts +23 -1
  11. package/dist/lib/auto-update-checker.d.ts.map +1 -1
  12. package/dist/lib/auto-update-checker.js +481 -8
  13. package/dist/lib/auto-update-checker.js.map +1 -1
  14. package/dist/lib/cli.d.ts +5 -0
  15. package/dist/lib/cli.d.ts.map +1 -1
  16. package/dist/lib/cli.js.map +1 -1
  17. package/dist/lib/codex-manager/commands/best.js +1 -1
  18. package/dist/lib/codex-manager/commands/best.js.map +1 -1
  19. package/dist/lib/codex-manager/commands/forecast.js +3 -3
  20. package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
  21. package/dist/lib/codex-manager/commands/report.js +3 -3
  22. package/dist/lib/codex-manager/commands/report.js.map +1 -1
  23. package/dist/lib/codex-manager/commands/rotation.d.ts +4 -0
  24. package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
  25. package/dist/lib/codex-manager/commands/rotation.js +37 -11
  26. package/dist/lib/codex-manager/commands/rotation.js.map +1 -1
  27. package/dist/lib/codex-manager/commands/status.d.ts +6 -0
  28. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
  29. package/dist/lib/codex-manager/commands/status.js +26 -3
  30. package/dist/lib/codex-manager/commands/status.js.map +1 -1
  31. package/dist/lib/codex-manager/forecast-report-commands.js +4 -4
  32. package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -1
  33. package/dist/lib/codex-manager/help.js +2 -2
  34. package/dist/lib/codex-manager/help.js.map +1 -1
  35. package/dist/lib/codex-manager/rate-limit-markers.d.ts +2 -0
  36. package/dist/lib/codex-manager/rate-limit-markers.d.ts.map +1 -0
  37. package/dist/lib/codex-manager/rate-limit-markers.js +4 -0
  38. package/dist/lib/codex-manager/rate-limit-markers.js.map +1 -0
  39. package/dist/lib/codex-manager/repair-commands.js +3 -3
  40. package/dist/lib/codex-manager/repair-commands.js.map +1 -1
  41. package/dist/lib/codex-manager.d.ts.map +1 -1
  42. package/dist/lib/codex-manager.js +56 -86
  43. package/dist/lib/codex-manager.js.map +1 -1
  44. package/dist/lib/config.js +2 -2
  45. package/dist/lib/config.js.map +1 -1
  46. package/dist/lib/prompts/codex.d.ts +1 -1
  47. package/dist/lib/prompts/codex.d.ts.map +1 -1
  48. package/dist/lib/prompts/codex.js +3 -3
  49. package/dist/lib/prompts/codex.js.map +1 -1
  50. package/dist/lib/quota-probe.js +1 -1
  51. package/dist/lib/quota-probe.js.map +1 -1
  52. package/dist/lib/quota-readiness.d.ts +16 -0
  53. package/dist/lib/quota-readiness.d.ts.map +1 -0
  54. package/dist/lib/quota-readiness.js +82 -0
  55. package/dist/lib/quota-readiness.js.map +1 -0
  56. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  57. package/dist/lib/request/fetch-helpers.js +19 -8
  58. package/dist/lib/request/fetch-helpers.js.map +1 -1
  59. package/dist/lib/request/helpers/model-map.d.ts +2 -1
  60. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  61. package/dist/lib/request/helpers/model-map.js +52 -60
  62. package/dist/lib/request/helpers/model-map.js.map +1 -1
  63. package/dist/lib/request/request-transformer.d.ts +2 -2
  64. package/dist/lib/request/request-transformer.js +4 -3
  65. package/dist/lib/request/request-transformer.js.map +1 -1
  66. package/dist/lib/runtime/login-menu-accounts.d.ts +5 -0
  67. package/dist/lib/runtime/login-menu-accounts.d.ts.map +1 -1
  68. package/dist/lib/runtime/login-menu-accounts.js +7 -2
  69. package/dist/lib/runtime/login-menu-accounts.js.map +1 -1
  70. package/dist/lib/runtime/quota-probe.js +1 -1
  71. package/dist/lib/runtime/quota-probe.js.map +1 -1
  72. package/dist/lib/runtime/runtime-current-account.d.ts +52 -0
  73. package/dist/lib/runtime/runtime-current-account.d.ts.map +1 -0
  74. package/dist/lib/runtime/runtime-current-account.js +252 -0
  75. package/dist/lib/runtime/runtime-current-account.js.map +1 -0
  76. package/dist/lib/runtime-rotation-proxy.js +1 -1
  77. package/dist/lib/runtime-rotation-proxy.js.map +1 -1
  78. package/dist/lib/ui/auth-menu.d.ts +6 -1
  79. package/dist/lib/ui/auth-menu.d.ts.map +1 -1
  80. package/dist/lib/ui/auth-menu.js +22 -2
  81. package/dist/lib/ui/auth-menu.js.map +1 -1
  82. package/package.json +1 -1
  83. package/scripts/bench-format/models.mjs +6 -8
  84. package/scripts/benchmark-edit-formats.mjs +1 -1
  85. package/scripts/codex.js +3538 -3375
  86. package/scripts/postinstall.js +120 -14
@@ -34,7 +34,9 @@ import { createLogger } from "./logger.js";
34
34
  import { MODEL_FAMILIES } from "./prompts/codex.js";
35
35
  import { getModelCapabilities, getModelProfile, resolveNormalizedModel, } from "./request/helpers/model-map.js";
36
36
  import { formatRateLimitEntry as formatAccountRateLimitEntry, getRateLimitResetTimeForFamily, resolveActiveIndex, } from "./runtime/account-status.js";
37
+ import { buildQuotaEmailFallbackState, hasSafeQuotaEmailFallback, hasUniqueQuotaAccountId, isQuotaCacheEntryExhausted, normalizeQuotaAccountId, normalizeQuotaEmail, quotaLeftPercentFromUsed, } from "./quota-readiness.js";
37
38
  import { loadQuotaCache, saveQuotaCache, } from "./quota-cache.js";
39
+ import { isDisplayCurrentAccount, readAppRuntimeHelperAccountSignal, resolveAccountCurrentMarkers, resolveRuntimeCurrentAccount, } from "./runtime/runtime-current-account.js";
38
40
  import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe.js";
39
41
  import { queuedRefresh } from "./refresh-queue.js";
40
42
  import { clearAccounts, findMatchingAccountIndex, formatStorageErrorHint, inspectStorageHealth, getLastAccountsSaveTimestamp, getNamedBackups, getStoragePath, loadAccounts, loadFlaggedAccounts, restoreAccountsFromBackup, StorageError, saveAccounts, setStoragePath, withAccountStorageTransaction, } from "./storage.js";
@@ -318,70 +320,6 @@ const IMPLEMENTED_FEATURES = [
318
320
  function formatRateLimitEntry(account, now, family = "codex") {
319
321
  return formatAccountRateLimitEntry(account, now, formatWaitTime, family);
320
322
  }
321
- function normalizeQuotaEmail(email) {
322
- const normalized = sanitizeEmail(email);
323
- return normalized && normalized.length > 0 ? normalized : null;
324
- }
325
- function normalizeQuotaAccountId(accountId) {
326
- if (typeof accountId !== "string")
327
- return null;
328
- const trimmed = accountId.trim();
329
- return trimmed.length > 0 ? trimmed : null;
330
- }
331
- function hasUniqueQuotaAccountId(accounts, account) {
332
- const accountId = normalizeQuotaAccountId(account.accountId);
333
- if (!accountId)
334
- return false;
335
- let matchCount = 0;
336
- for (const candidate of accounts) {
337
- if (normalizeQuotaAccountId(candidate.accountId) !== accountId)
338
- continue;
339
- matchCount += 1;
340
- if (matchCount > 1)
341
- return false;
342
- }
343
- return matchCount === 1;
344
- }
345
- function buildQuotaEmailFallbackState(accounts) {
346
- const stateByEmail = new Map();
347
- for (const account of accounts) {
348
- const email = normalizeQuotaEmail(account.email);
349
- if (!email)
350
- continue;
351
- const existing = stateByEmail.get(email);
352
- if (existing) {
353
- existing.matchingCount += 1;
354
- const accountId = normalizeQuotaAccountId(account.accountId);
355
- if (accountId) {
356
- existing.distinctAccountIds.add(accountId);
357
- }
358
- continue;
359
- }
360
- const distinctAccountIds = new Set();
361
- const accountId = normalizeQuotaAccountId(account.accountId);
362
- if (accountId) {
363
- distinctAccountIds.add(accountId);
364
- }
365
- stateByEmail.set(email, {
366
- matchingCount: 1,
367
- distinctAccountIds,
368
- });
369
- }
370
- return stateByEmail;
371
- }
372
- function hasSafeQuotaEmailFallback(emailFallbackState, account) {
373
- const email = normalizeQuotaEmail(account.email);
374
- if (!email)
375
- return false;
376
- const state = emailFallbackState.get(email);
377
- if (!state)
378
- return false;
379
- // size > 1 only matters when multiple accounts share the same email but
380
- // disagree on accountId; a single matching account already implies size <= 1.
381
- if (state.distinctAccountIds.size > 1)
382
- return false;
383
- return state.matchingCount === 1;
384
- }
385
323
  function quotaCacheEntryToSnapshot(entry) {
386
324
  return {
387
325
  status: entry.status,
@@ -417,13 +355,7 @@ function formatCompactQuotaPart(windowMinutes, usedPercent) {
417
355
  const left = quotaLeftPercentFromUsed(usedPercent);
418
356
  return `${label} ${left}%`;
419
357
  }
420
- function quotaLeftPercentFromUsed(usedPercent) {
421
- if (typeof usedPercent !== "number" || !Number.isFinite(usedPercent)) {
422
- return undefined;
423
- }
424
- return Math.max(0, Math.min(100, Math.round(100 - usedPercent)));
425
- }
426
- function formatCompactQuotaSnapshot(snapshot) {
358
+ function formatCompactQuotaSnapshot(snapshot, now = Date.now()) {
427
359
  const parts = [
428
360
  formatCompactQuotaPart(snapshot.primary.windowMinutes, snapshot.primary.usedPercent),
429
361
  formatCompactQuotaPart(snapshot.secondary.windowMinutes, snapshot.secondary.usedPercent),
@@ -431,12 +363,15 @@ function formatCompactQuotaSnapshot(snapshot) {
431
363
  if (snapshot.status === 429) {
432
364
  parts.push("rate-limited");
433
365
  }
366
+ if (isQuotaCacheEntryExhausted(snapshot, now)) {
367
+ parts.push("quota-exhausted");
368
+ }
434
369
  if (parts.length > 0) {
435
370
  return parts.join(" | ");
436
371
  }
437
372
  return formatQuotaSnapshotLine(snapshot);
438
373
  }
439
- function formatAccountQuotaSummary(entry) {
374
+ function formatAccountQuotaSummary(entry, now = Date.now()) {
440
375
  const parts = [
441
376
  formatCompactQuotaPart(entry.primary.windowMinutes, entry.primary.usedPercent),
442
377
  formatCompactQuotaPart(entry.secondary.windowMinutes, entry.secondary.usedPercent),
@@ -444,6 +379,9 @@ function formatAccountQuotaSummary(entry) {
444
379
  if (entry.status === 429) {
445
380
  parts.push("rate-limited");
446
381
  }
382
+ if (isQuotaCacheEntryExhausted(entry, now)) {
383
+ parts.push("quota-exhausted");
384
+ }
447
385
  if (parts.length > 0) {
448
386
  return parts.join(" | ");
449
387
  }
@@ -481,7 +419,7 @@ function getPersistedQuotaViewForAccount(cache, account, accounts, now, emailFal
481
419
  return {
482
420
  updatedAt: cachedEntry?.updatedAt ?? now,
483
421
  status: 429,
484
- model: cachedEntry?.model ?? "gpt-5-codex",
422
+ model: cachedEntry?.model ?? "gpt-5.3-codex",
485
423
  planType: cachedEntry?.planType,
486
424
  primary: {
487
425
  ...cachedEntry?.primary,
@@ -549,7 +487,7 @@ function pruneUnsafeQuotaEmailCacheEntry(cache, email, accounts, emailFallbackSt
549
487
  return true;
550
488
  }
551
489
  const DEFAULT_MENU_QUOTA_REFRESH_TTL_MS = 5 * 60_000;
552
- const MENU_QUOTA_REFRESH_MODEL = "gpt-5-codex";
490
+ const MENU_QUOTA_REFRESH_MODEL = "gpt-5.3-codex";
553
491
  function resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, accounts, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
554
492
  if (account.enabled === false)
555
493
  return null;
@@ -651,19 +589,22 @@ function hasLikelyInvalidRefreshToken(refreshToken) {
651
589
  return true;
652
590
  return trimmed.startsWith("token-");
653
591
  }
654
- function mapAccountStatus(account, index, activeIndex, now, persistedQuotaStatus) {
592
+ function mapAccountStatus(account, isCurrentAccount, now, persistedQuotaEntry) {
655
593
  if (account.enabled === false)
656
594
  return "disabled";
657
595
  if (typeof account.coolingDownUntil === "number" &&
658
596
  account.coolingDownUntil > now) {
659
597
  return "cooldown";
660
598
  }
661
- if (persistedQuotaStatus === 429)
599
+ if (persistedQuotaEntry && isQuotaCacheEntryExhausted(persistedQuotaEntry, now)) {
600
+ return "quota-exhausted";
601
+ }
602
+ if (persistedQuotaEntry?.status === 429)
662
603
  return "rate-limited";
663
604
  const rateLimit = formatRateLimitEntry(account, now, "codex");
664
605
  if (rateLimit)
665
606
  return "rate-limited";
666
- if (index === activeIndex)
607
+ if (isCurrentAccount)
667
608
  return "active";
668
609
  return "ok";
669
610
  }
@@ -695,6 +636,7 @@ function accountStatusSortBucket(status) {
695
636
  return 0;
696
637
  case "unknown":
697
638
  return 1;
639
+ case "quota-exhausted":
698
640
  case "cooldown":
699
641
  case "rate-limited":
700
642
  return 2;
@@ -710,7 +652,7 @@ function accountReadinessSortBucket(account) {
710
652
  const statusBucket = accountStatusSortBucket(account.status);
711
653
  if (statusBucket >= 2)
712
654
  return statusBucket;
713
- return account.quotaRateLimited ? 2 : statusBucket;
655
+ return account.quotaRateLimited || account.quotaExhausted ? 2 : statusBucket;
714
656
  }
715
657
  function compareReadyFirstAccounts(left, right) {
716
658
  const bucketDelta = accountReadinessSortBucket(left) - accountReadinessSortBucket(right);
@@ -765,13 +707,16 @@ function applyAccountMenuOrdering(accounts, displaySettings) {
765
707
  }
766
708
  return sorted;
767
709
  }
768
- function toExistingAccountInfo(storage, quotaCache, displaySettings) {
710
+ function toExistingAccountInfo(storage, quotaCache, displaySettings, runtimeCurrent = null) {
769
711
  const now = Date.now();
770
712
  const activeIndex = resolveActiveIndex(storage, "codex");
771
713
  const layoutMode = resolveMenuLayoutMode(displaySettings);
772
714
  const emailFallbackState = buildQuotaEmailFallbackState(storage.accounts);
773
715
  const baseAccounts = storage.accounts.map((account, index) => {
774
716
  const entry = getPersistedQuotaViewForAccount(quotaCache, account, storage.accounts, now, emailFallbackState);
717
+ const currentMarkers = resolveAccountCurrentMarkers(index, activeIndex, runtimeCurrent);
718
+ const isCurrentAccount = isDisplayCurrentAccount(index, activeIndex, runtimeCurrent);
719
+ const quotaExhausted = entry ? isQuotaCacheEntryExhausted(entry, now) : false;
775
720
  return {
776
721
  index,
777
722
  sourceIndex: index,
@@ -780,16 +725,20 @@ function toExistingAccountInfo(storage, quotaCache, displaySettings) {
780
725
  email: account.email,
781
726
  addedAt: account.addedAt,
782
727
  lastUsed: account.lastUsed,
783
- status: mapAccountStatus(account, index, activeIndex, now, entry?.status),
728
+ status: mapAccountStatus(account, isCurrentAccount, now, entry),
784
729
  quotaSummary: (displaySettings.menuShowQuotaSummary ?? true) && entry
785
- ? formatAccountQuotaSummary(entry)
730
+ ? formatAccountQuotaSummary(entry, now)
786
731
  : undefined,
787
732
  quota5hLeftPercent: quotaLeftPercentFromUsed(entry?.primary.usedPercent),
788
733
  quota5hResetAtMs: entry?.primary.resetAtMs,
789
734
  quota7dLeftPercent: quotaLeftPercentFromUsed(entry?.secondary.usedPercent),
790
735
  quota7dResetAtMs: entry?.secondary.resetAtMs,
791
736
  quotaRateLimited: entry?.status === 429,
792
- isCurrentAccount: index === activeIndex,
737
+ quotaExhausted,
738
+ isCurrentAccount,
739
+ isDefaultAccount: index === activeIndex,
740
+ isRuntimeCurrentAccount: runtimeCurrent?.index === index,
741
+ currentMarkers,
793
742
  enabled: account.enabled !== false,
794
743
  showStatusBadge: displaySettings.menuShowStatusBadge ?? true,
795
744
  showCurrentBadge: displaySettings.menuShowCurrentBadge ?? true,
@@ -815,6 +764,19 @@ function toExistingAccountInfo(storage, quotaCache, displaySettings) {
815
764
  : (account.sourceIndex ?? displayIndex) + 1,
816
765
  }));
817
766
  }
767
+ async function loadRuntimeCurrentSelectionForStorage(storage, now = Date.now()) {
768
+ const [runtimeSnapshot, appBindStatus] = await Promise.all([
769
+ loadPersistedRuntimeObservabilitySnapshot().catch(() => null),
770
+ getAppBindStatus()
771
+ .then((status) => (status.running ? status.router : null))
772
+ .catch(() => null),
773
+ ]);
774
+ return resolveRuntimeCurrentAccount(storage, {
775
+ runtimeSnapshot,
776
+ appBindStatus,
777
+ appHelperStatus: readAppRuntimeHelperAccountSignal(),
778
+ }, { now });
779
+ }
818
780
  function activeAccountMatchesCodexCliState(account, state) {
819
781
  if (!state)
820
782
  return true;
@@ -1572,7 +1534,7 @@ async function syncSelectionToCodex(tokens) {
1572
1534
  async function runHealthCheck(options = {}) {
1573
1535
  const forceRefresh = options.forceRefresh === true;
1574
1536
  const liveProbe = options.liveProbe === true;
1575
- const probeModel = options.model?.trim() || "gpt-5-codex";
1537
+ const probeModel = options.model?.trim() || "gpt-5.3-codex";
1576
1538
  const modelInspection = inspectRequestedModel(probeModel);
1577
1539
  const display = options.display ?? DEFAULT_DASHBOARD_DISPLAY_SETTINGS;
1578
1540
  const quotaCache = liveProbe ? await loadQuotaCache() : null;
@@ -1788,7 +1750,7 @@ function printBestUsage() {
1788
1750
  "Options:",
1789
1751
  " --live, -l Probe live quota headers via Codex backend before switching",
1790
1752
  " --json, -j Print machine-readable JSON output",
1791
- " --model, -m Probe model for live mode (default: gpt-5-codex)",
1753
+ " --model, -m Probe model for live mode (default: gpt-5.3-codex)",
1792
1754
  "",
1793
1755
  "Behavior:",
1794
1756
  " - Chooses the healthiest account using forecast scoring",
@@ -1799,7 +1761,7 @@ function parseBestArgs(args) {
1799
1761
  const options = {
1800
1762
  live: false,
1801
1763
  json: false,
1802
- model: "gpt-5-codex",
1764
+ model: "gpt-5.3-codex",
1803
1765
  modelProvided: false,
1804
1766
  };
1805
1767
  for (let i = 0; i < args.length; i += 1) {
@@ -2101,7 +2063,8 @@ async function runAuthLogin(args) {
2101
2063
  }
2102
2064
  const flaggedStorage = await loadFlaggedAccounts();
2103
2065
  await syncCodexCliActiveSelectionIfDrifted(currentStorage);
2104
- const menuResult = await promptLoginMode(toExistingAccountInfo(currentStorage, quotaCache, displaySettings), {
2066
+ const runtimeCurrent = await loadRuntimeCurrentSelectionForStorage(currentStorage);
2067
+ const menuResult = await promptLoginMode(toExistingAccountInfo(currentStorage, quotaCache, displaySettings, runtimeCurrent), {
2105
2068
  flaggedCount: flaggedStorage.accounts.length,
2106
2069
  statusMessage: showFetchStatus
2107
2070
  ? () => menuQuotaRefreshStatus
@@ -2576,6 +2539,11 @@ export async function runCodexMultiAuthCli(rawArgs) {
2576
2539
  resolveActiveIndex,
2577
2540
  formatRateLimitEntry,
2578
2541
  loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
2542
+ loadAppBindStatus: async () => getAppBindStatus()
2543
+ .then((status) => (status.running ? status.router : null))
2544
+ .catch(() => null),
2545
+ loadAppHelperStatus: readAppRuntimeHelperAccountSignal,
2546
+ loadQuotaCache,
2579
2547
  });
2580
2548
  }
2581
2549
  if (command === "switch") {
@@ -2628,6 +2596,8 @@ export async function runCodexMultiAuthCli(rawArgs) {
2628
2596
  bindCodexApp: bindCodexAppRuntimeRotation,
2629
2597
  unbindCodexApp: unbindCodexAppRuntimeRotation,
2630
2598
  getCodexAppBindStatus: getAppBindStatus,
2599
+ loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
2600
+ loadQuotaCache,
2631
2601
  });
2632
2602
  }
2633
2603
  if (command === "why-selected") {