codex-multi-auth 1.3.2 → 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 (132) hide show
  1. package/README.md +40 -26
  2. package/config/README.md +4 -4
  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 +26 -0
  24. package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -0
  25. package/dist/lib/codex-manager/commands/rotation.js +313 -0
  26. package/dist/lib/codex-manager/commands/rotation.js.map +1 -0
  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 +64 -10
  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.d.ts.map +1 -1
  34. package/dist/lib/codex-manager/help.js +3 -2
  35. package/dist/lib/codex-manager/help.js.map +1 -1
  36. package/dist/lib/codex-manager/rate-limit-markers.d.ts +2 -0
  37. package/dist/lib/codex-manager/rate-limit-markers.d.ts.map +1 -0
  38. package/dist/lib/codex-manager/rate-limit-markers.js +4 -0
  39. package/dist/lib/codex-manager/rate-limit-markers.js.map +1 -0
  40. package/dist/lib/codex-manager/repair-commands.js +3 -3
  41. package/dist/lib/codex-manager/repair-commands.js.map +1 -1
  42. package/dist/lib/codex-manager.d.ts.map +1 -1
  43. package/dist/lib/codex-manager.js +115 -87
  44. package/dist/lib/codex-manager.js.map +1 -1
  45. package/dist/lib/config.d.ts +1 -0
  46. package/dist/lib/config.d.ts.map +1 -1
  47. package/dist/lib/config.js +9 -0
  48. package/dist/lib/config.js.map +1 -1
  49. package/dist/lib/constants.d.ts +2 -0
  50. package/dist/lib/constants.d.ts.map +1 -1
  51. package/dist/lib/constants.js +2 -0
  52. package/dist/lib/constants.js.map +1 -1
  53. package/dist/lib/fs-retry.d.ts +7 -0
  54. package/dist/lib/fs-retry.d.ts.map +1 -0
  55. package/dist/lib/fs-retry.js +35 -0
  56. package/dist/lib/fs-retry.js.map +1 -0
  57. package/dist/lib/index.d.ts +1 -0
  58. package/dist/lib/index.d.ts.map +1 -1
  59. package/dist/lib/index.js +1 -0
  60. package/dist/lib/index.js.map +1 -1
  61. package/dist/lib/logger.d.ts.map +1 -1
  62. package/dist/lib/logger.js +1 -0
  63. package/dist/lib/logger.js.map +1 -1
  64. package/dist/lib/prompts/codex.d.ts +1 -1
  65. package/dist/lib/prompts/codex.d.ts.map +1 -1
  66. package/dist/lib/prompts/codex.js +3 -3
  67. package/dist/lib/prompts/codex.js.map +1 -1
  68. package/dist/lib/quota-probe.js +1 -1
  69. package/dist/lib/quota-probe.js.map +1 -1
  70. package/dist/lib/quota-readiness.d.ts +16 -0
  71. package/dist/lib/quota-readiness.d.ts.map +1 -0
  72. package/dist/lib/quota-readiness.js +82 -0
  73. package/dist/lib/quota-readiness.js.map +1 -0
  74. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  75. package/dist/lib/request/fetch-helpers.js +19 -8
  76. package/dist/lib/request/fetch-helpers.js.map +1 -1
  77. package/dist/lib/request/helpers/model-map.d.ts +2 -1
  78. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  79. package/dist/lib/request/helpers/model-map.js +52 -60
  80. package/dist/lib/request/helpers/model-map.js.map +1 -1
  81. package/dist/lib/request/request-transformer.d.ts +2 -2
  82. package/dist/lib/request/request-transformer.js +4 -3
  83. package/dist/lib/request/request-transformer.js.map +1 -1
  84. package/dist/lib/runtime/app-bind.d.ts +74 -0
  85. package/dist/lib/runtime/app-bind.d.ts.map +1 -0
  86. package/dist/lib/runtime/app-bind.js +620 -0
  87. package/dist/lib/runtime/app-bind.js.map +1 -0
  88. package/dist/lib/runtime/config-toml.d.ts +10 -0
  89. package/dist/lib/runtime/config-toml.d.ts.map +1 -0
  90. package/dist/lib/runtime/config-toml.js +128 -0
  91. package/dist/lib/runtime/config-toml.js.map +1 -0
  92. package/dist/lib/runtime/login-menu-accounts.d.ts +5 -0
  93. package/dist/lib/runtime/login-menu-accounts.d.ts.map +1 -1
  94. package/dist/lib/runtime/login-menu-accounts.js +7 -2
  95. package/dist/lib/runtime/login-menu-accounts.js.map +1 -1
  96. package/dist/lib/runtime/quota-probe.js +1 -1
  97. package/dist/lib/runtime/quota-probe.js.map +1 -1
  98. package/dist/lib/runtime/runtime-current-account.d.ts +52 -0
  99. package/dist/lib/runtime/runtime-current-account.d.ts.map +1 -0
  100. package/dist/lib/runtime/runtime-current-account.js +252 -0
  101. package/dist/lib/runtime/runtime-current-account.js.map +1 -0
  102. package/dist/lib/runtime/runtime-observability.d.ts +5 -0
  103. package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
  104. package/dist/lib/runtime/runtime-observability.js +5 -0
  105. package/dist/lib/runtime/runtime-observability.js.map +1 -1
  106. package/dist/lib/runtime-constants.d.ts +3 -0
  107. package/dist/lib/runtime-constants.d.ts.map +1 -0
  108. package/dist/lib/runtime-constants.js +3 -0
  109. package/dist/lib/runtime-constants.js.map +1 -0
  110. package/dist/lib/runtime-rotation-proxy.d.ts +36 -0
  111. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -0
  112. package/dist/lib/runtime-rotation-proxy.js +828 -0
  113. package/dist/lib/runtime-rotation-proxy.js.map +1 -0
  114. package/dist/lib/schemas.d.ts +1 -0
  115. package/dist/lib/schemas.d.ts.map +1 -1
  116. package/dist/lib/schemas.js +1 -0
  117. package/dist/lib/schemas.js.map +1 -1
  118. package/dist/lib/ui/auth-menu.d.ts +6 -1
  119. package/dist/lib/ui/auth-menu.d.ts.map +1 -1
  120. package/dist/lib/ui/auth-menu.js +22 -2
  121. package/dist/lib/ui/auth-menu.js.map +1 -1
  122. package/package.json +13 -11
  123. package/scripts/bench-format/codex-host.mjs +142 -73
  124. package/scripts/bench-format/models.mjs +23 -24
  125. package/scripts/benchmark-edit-formats.mjs +9 -7
  126. package/scripts/check-pack-budget-lib.js +49 -6
  127. package/scripts/codex-app-launcher.js +653 -0
  128. package/scripts/codex-app-router.js +322 -0
  129. package/scripts/codex-multi-auth.js +3 -1
  130. package/scripts/codex-routing.js +6 -0
  131. package/scripts/codex.js +2428 -445
  132. package/scripts/postinstall.js +343 -0
@@ -19,12 +19,14 @@ import { runDoctor as runRepairDoctor, runFix as runRepairFix, runVerifyFlagged
19
19
  import { runForecastCommand } from "./codex-manager/commands/forecast.js";
20
20
  import { runInitConfigCommand } from "./codex-manager/commands/init-config.js";
21
21
  import { runReportCommand } from "./codex-manager/commands/report.js";
22
+ import { runRotationCommand } from "./codex-manager/commands/rotation.js";
22
23
  import { runFeaturesCommand, runStatusCommand, } from "./codex-manager/commands/status.js";
23
24
  import { loadPersistedRuntimeObservabilitySnapshot } from "./runtime/runtime-observability.js";
24
25
  import { runSwitchCommand } from "./codex-manager/commands/switch.js";
25
26
  import { parseAuthLoginArgs, printUsage } from "./codex-manager/help.js";
26
27
  import { applyUiThemeFromDashboardSettings, configureUnifiedSettings, resolveMenuLayoutMode, } from "./codex-manager/settings-hub.js";
27
- import { getPluginConfigExplainReport } from "./config.js";
28
+ import { getCodexRuntimeRotationProxy, getPluginConfigExplainReport, loadPluginConfig, savePluginConfig, } from "./config.js";
29
+ import { bindCodexAppRuntimeRotation, getAppBindStatus, unbindCodexAppRuntimeRotation, } from "./runtime/app-bind.js";
28
30
  import { ACCOUNT_LIMITS } from "./constants.js";
29
31
  import { DEFAULT_DASHBOARD_DISPLAY_SETTINGS, loadDashboardDisplaySettings, } from "./dashboard-settings.js";
30
32
  import { evaluateForecastAccounts, recommendForecastAccount, summarizeForecast, } from "./forecast.js";
@@ -32,7 +34,9 @@ import { createLogger } from "./logger.js";
32
34
  import { MODEL_FAMILIES } from "./prompts/codex.js";
33
35
  import { getModelCapabilities, getModelProfile, resolveNormalizedModel, } from "./request/helpers/model-map.js";
34
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";
35
38
  import { loadQuotaCache, saveQuotaCache, } from "./quota-cache.js";
39
+ import { isDisplayCurrentAccount, readAppRuntimeHelperAccountSignal, resolveAccountCurrentMarkers, resolveRuntimeCurrentAccount, } from "./runtime/runtime-current-account.js";
36
40
  import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe.js";
37
41
  import { queuedRefresh } from "./refresh-queue.js";
38
42
  import { clearAccounts, findMatchingAccountIndex, formatStorageErrorHint, inspectStorageHealth, getLastAccountsSaveTimestamp, getNamedBackups, getStoragePath, loadAccounts, loadFlaggedAccounts, restoreAccountsFromBackup, StorageError, saveAccounts, setStoragePath, withAccountStorageTransaction, } from "./storage.js";
@@ -316,70 +320,6 @@ const IMPLEMENTED_FEATURES = [
316
320
  function formatRateLimitEntry(account, now, family = "codex") {
317
321
  return formatAccountRateLimitEntry(account, now, formatWaitTime, family);
318
322
  }
319
- function normalizeQuotaEmail(email) {
320
- const normalized = sanitizeEmail(email);
321
- return normalized && normalized.length > 0 ? normalized : null;
322
- }
323
- function normalizeQuotaAccountId(accountId) {
324
- if (typeof accountId !== "string")
325
- return null;
326
- const trimmed = accountId.trim();
327
- return trimmed.length > 0 ? trimmed : null;
328
- }
329
- function hasUniqueQuotaAccountId(accounts, account) {
330
- const accountId = normalizeQuotaAccountId(account.accountId);
331
- if (!accountId)
332
- return false;
333
- let matchCount = 0;
334
- for (const candidate of accounts) {
335
- if (normalizeQuotaAccountId(candidate.accountId) !== accountId)
336
- continue;
337
- matchCount += 1;
338
- if (matchCount > 1)
339
- return false;
340
- }
341
- return matchCount === 1;
342
- }
343
- function buildQuotaEmailFallbackState(accounts) {
344
- const stateByEmail = new Map();
345
- for (const account of accounts) {
346
- const email = normalizeQuotaEmail(account.email);
347
- if (!email)
348
- continue;
349
- const existing = stateByEmail.get(email);
350
- if (existing) {
351
- existing.matchingCount += 1;
352
- const accountId = normalizeQuotaAccountId(account.accountId);
353
- if (accountId) {
354
- existing.distinctAccountIds.add(accountId);
355
- }
356
- continue;
357
- }
358
- const distinctAccountIds = new Set();
359
- const accountId = normalizeQuotaAccountId(account.accountId);
360
- if (accountId) {
361
- distinctAccountIds.add(accountId);
362
- }
363
- stateByEmail.set(email, {
364
- matchingCount: 1,
365
- distinctAccountIds,
366
- });
367
- }
368
- return stateByEmail;
369
- }
370
- function hasSafeQuotaEmailFallback(emailFallbackState, account) {
371
- const email = normalizeQuotaEmail(account.email);
372
- if (!email)
373
- return false;
374
- const state = emailFallbackState.get(email);
375
- if (!state)
376
- return false;
377
- // size > 1 only matters when multiple accounts share the same email but
378
- // disagree on accountId; a single matching account already implies size <= 1.
379
- if (state.distinctAccountIds.size > 1)
380
- return false;
381
- return state.matchingCount === 1;
382
- }
383
323
  function quotaCacheEntryToSnapshot(entry) {
384
324
  return {
385
325
  status: entry.status,
@@ -415,13 +355,7 @@ function formatCompactQuotaPart(windowMinutes, usedPercent) {
415
355
  const left = quotaLeftPercentFromUsed(usedPercent);
416
356
  return `${label} ${left}%`;
417
357
  }
418
- function quotaLeftPercentFromUsed(usedPercent) {
419
- if (typeof usedPercent !== "number" || !Number.isFinite(usedPercent)) {
420
- return undefined;
421
- }
422
- return Math.max(0, Math.min(100, Math.round(100 - usedPercent)));
423
- }
424
- function formatCompactQuotaSnapshot(snapshot) {
358
+ function formatCompactQuotaSnapshot(snapshot, now = Date.now()) {
425
359
  const parts = [
426
360
  formatCompactQuotaPart(snapshot.primary.windowMinutes, snapshot.primary.usedPercent),
427
361
  formatCompactQuotaPart(snapshot.secondary.windowMinutes, snapshot.secondary.usedPercent),
@@ -429,12 +363,15 @@ function formatCompactQuotaSnapshot(snapshot) {
429
363
  if (snapshot.status === 429) {
430
364
  parts.push("rate-limited");
431
365
  }
366
+ if (isQuotaCacheEntryExhausted(snapshot, now)) {
367
+ parts.push("quota-exhausted");
368
+ }
432
369
  if (parts.length > 0) {
433
370
  return parts.join(" | ");
434
371
  }
435
372
  return formatQuotaSnapshotLine(snapshot);
436
373
  }
437
- function formatAccountQuotaSummary(entry) {
374
+ function formatAccountQuotaSummary(entry, now = Date.now()) {
438
375
  const parts = [
439
376
  formatCompactQuotaPart(entry.primary.windowMinutes, entry.primary.usedPercent),
440
377
  formatCompactQuotaPart(entry.secondary.windowMinutes, entry.secondary.usedPercent),
@@ -442,6 +379,9 @@ function formatAccountQuotaSummary(entry) {
442
379
  if (entry.status === 429) {
443
380
  parts.push("rate-limited");
444
381
  }
382
+ if (isQuotaCacheEntryExhausted(entry, now)) {
383
+ parts.push("quota-exhausted");
384
+ }
445
385
  if (parts.length > 0) {
446
386
  return parts.join(" | ");
447
387
  }
@@ -479,7 +419,7 @@ function getPersistedQuotaViewForAccount(cache, account, accounts, now, emailFal
479
419
  return {
480
420
  updatedAt: cachedEntry?.updatedAt ?? now,
481
421
  status: 429,
482
- model: cachedEntry?.model ?? "gpt-5-codex",
422
+ model: cachedEntry?.model ?? "gpt-5.3-codex",
483
423
  planType: cachedEntry?.planType,
484
424
  primary: {
485
425
  ...cachedEntry?.primary,
@@ -547,7 +487,7 @@ function pruneUnsafeQuotaEmailCacheEntry(cache, email, accounts, emailFallbackSt
547
487
  return true;
548
488
  }
549
489
  const DEFAULT_MENU_QUOTA_REFRESH_TTL_MS = 5 * 60_000;
550
- const MENU_QUOTA_REFRESH_MODEL = "gpt-5-codex";
490
+ const MENU_QUOTA_REFRESH_MODEL = "gpt-5.3-codex";
551
491
  function resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, accounts, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
552
492
  if (account.enabled === false)
553
493
  return null;
@@ -649,19 +589,22 @@ function hasLikelyInvalidRefreshToken(refreshToken) {
649
589
  return true;
650
590
  return trimmed.startsWith("token-");
651
591
  }
652
- function mapAccountStatus(account, index, activeIndex, now, persistedQuotaStatus) {
592
+ function mapAccountStatus(account, isCurrentAccount, now, persistedQuotaEntry) {
653
593
  if (account.enabled === false)
654
594
  return "disabled";
655
595
  if (typeof account.coolingDownUntil === "number" &&
656
596
  account.coolingDownUntil > now) {
657
597
  return "cooldown";
658
598
  }
659
- if (persistedQuotaStatus === 429)
599
+ if (persistedQuotaEntry && isQuotaCacheEntryExhausted(persistedQuotaEntry, now)) {
600
+ return "quota-exhausted";
601
+ }
602
+ if (persistedQuotaEntry?.status === 429)
660
603
  return "rate-limited";
661
604
  const rateLimit = formatRateLimitEntry(account, now, "codex");
662
605
  if (rateLimit)
663
606
  return "rate-limited";
664
- if (index === activeIndex)
607
+ if (isCurrentAccount)
665
608
  return "active";
666
609
  return "ok";
667
610
  }
@@ -693,6 +636,7 @@ function accountStatusSortBucket(status) {
693
636
  return 0;
694
637
  case "unknown":
695
638
  return 1;
639
+ case "quota-exhausted":
696
640
  case "cooldown":
697
641
  case "rate-limited":
698
642
  return 2;
@@ -708,7 +652,7 @@ function accountReadinessSortBucket(account) {
708
652
  const statusBucket = accountStatusSortBucket(account.status);
709
653
  if (statusBucket >= 2)
710
654
  return statusBucket;
711
- return account.quotaRateLimited ? 2 : statusBucket;
655
+ return account.quotaRateLimited || account.quotaExhausted ? 2 : statusBucket;
712
656
  }
713
657
  function compareReadyFirstAccounts(left, right) {
714
658
  const bucketDelta = accountReadinessSortBucket(left) - accountReadinessSortBucket(right);
@@ -763,13 +707,16 @@ function applyAccountMenuOrdering(accounts, displaySettings) {
763
707
  }
764
708
  return sorted;
765
709
  }
766
- function toExistingAccountInfo(storage, quotaCache, displaySettings) {
710
+ function toExistingAccountInfo(storage, quotaCache, displaySettings, runtimeCurrent = null) {
767
711
  const now = Date.now();
768
712
  const activeIndex = resolveActiveIndex(storage, "codex");
769
713
  const layoutMode = resolveMenuLayoutMode(displaySettings);
770
714
  const emailFallbackState = buildQuotaEmailFallbackState(storage.accounts);
771
715
  const baseAccounts = storage.accounts.map((account, index) => {
772
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;
773
720
  return {
774
721
  index,
775
722
  sourceIndex: index,
@@ -778,16 +725,20 @@ function toExistingAccountInfo(storage, quotaCache, displaySettings) {
778
725
  email: account.email,
779
726
  addedAt: account.addedAt,
780
727
  lastUsed: account.lastUsed,
781
- status: mapAccountStatus(account, index, activeIndex, now, entry?.status),
728
+ status: mapAccountStatus(account, isCurrentAccount, now, entry),
782
729
  quotaSummary: (displaySettings.menuShowQuotaSummary ?? true) && entry
783
- ? formatAccountQuotaSummary(entry)
730
+ ? formatAccountQuotaSummary(entry, now)
784
731
  : undefined,
785
732
  quota5hLeftPercent: quotaLeftPercentFromUsed(entry?.primary.usedPercent),
786
733
  quota5hResetAtMs: entry?.primary.resetAtMs,
787
734
  quota7dLeftPercent: quotaLeftPercentFromUsed(entry?.secondary.usedPercent),
788
735
  quota7dResetAtMs: entry?.secondary.resetAtMs,
789
736
  quotaRateLimited: entry?.status === 429,
790
- isCurrentAccount: index === activeIndex,
737
+ quotaExhausted,
738
+ isCurrentAccount,
739
+ isDefaultAccount: index === activeIndex,
740
+ isRuntimeCurrentAccount: runtimeCurrent?.index === index,
741
+ currentMarkers,
791
742
  enabled: account.enabled !== false,
792
743
  showStatusBadge: displaySettings.menuShowStatusBadge ?? true,
793
744
  showCurrentBadge: displaySettings.menuShowCurrentBadge ?? true,
@@ -813,6 +764,60 @@ function toExistingAccountInfo(storage, quotaCache, displaySettings) {
813
764
  : (account.sourceIndex ?? displayIndex) + 1,
814
765
  }));
815
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
+ }
780
+ function activeAccountMatchesCodexCliState(account, state) {
781
+ if (!state)
782
+ return true;
783
+ const accountId = account.accountId?.trim();
784
+ const activeAccountId = state.activeAccountId?.trim();
785
+ if (accountId && activeAccountId) {
786
+ return accountId === activeAccountId;
787
+ }
788
+ const email = sanitizeEmail(account.email);
789
+ const activeEmail = sanitizeEmail(state.activeEmail);
790
+ if (email && activeEmail) {
791
+ return email === activeEmail;
792
+ }
793
+ return false;
794
+ }
795
+ async function syncCodexCliActiveSelectionIfDrifted(storage) {
796
+ const activeIndex = resolveActiveIndex(storage, "codex");
797
+ if (activeIndex < 0 || activeIndex >= storage.accounts.length) {
798
+ return false;
799
+ }
800
+ const account = storage.accounts[activeIndex];
801
+ if (!account) {
802
+ return false;
803
+ }
804
+ try {
805
+ const cliState = await loadCodexCliState({ forceRefresh: true });
806
+ if (!cliState || activeAccountMatchesCodexCliState(account, cliState)) {
807
+ return false;
808
+ }
809
+ return setCodexCliActiveSelection({
810
+ accountId: account.accountId,
811
+ email: account.email,
812
+ accessToken: account.accessToken,
813
+ refreshToken: account.refreshToken,
814
+ expiresAt: account.expiresAt,
815
+ });
816
+ }
817
+ catch {
818
+ return false;
819
+ }
820
+ }
816
821
  function resolveAccountSelection(tokens) {
817
822
  const override = (process.env.CODEX_AUTH_ACCOUNT_ID ?? "").trim();
818
823
  if (override) {
@@ -1529,7 +1534,7 @@ async function syncSelectionToCodex(tokens) {
1529
1534
  async function runHealthCheck(options = {}) {
1530
1535
  const forceRefresh = options.forceRefresh === true;
1531
1536
  const liveProbe = options.liveProbe === true;
1532
- const probeModel = options.model?.trim() || "gpt-5-codex";
1537
+ const probeModel = options.model?.trim() || "gpt-5.3-codex";
1533
1538
  const modelInspection = inspectRequestedModel(probeModel);
1534
1539
  const display = options.display ?? DEFAULT_DASHBOARD_DISPLAY_SETTINGS;
1535
1540
  const quotaCache = liveProbe ? await loadQuotaCache() : null;
@@ -1745,7 +1750,7 @@ function printBestUsage() {
1745
1750
  "Options:",
1746
1751
  " --live, -l Probe live quota headers via Codex backend before switching",
1747
1752
  " --json, -j Print machine-readable JSON output",
1748
- " --model, -m Probe model for live mode (default: gpt-5-codex)",
1753
+ " --model, -m Probe model for live mode (default: gpt-5.3-codex)",
1749
1754
  "",
1750
1755
  "Behavior:",
1751
1756
  " - Chooses the healthiest account using forecast scoring",
@@ -1756,7 +1761,7 @@ function parseBestArgs(args) {
1756
1761
  const options = {
1757
1762
  live: false,
1758
1763
  json: false,
1759
- model: "gpt-5-codex",
1764
+ model: "gpt-5.3-codex",
1760
1765
  modelProvided: false,
1761
1766
  };
1762
1767
  for (let i = 0; i < args.length; i += 1) {
@@ -2057,7 +2062,9 @@ async function runAuthLogin(args) {
2057
2062
  }
2058
2063
  }
2059
2064
  const flaggedStorage = await loadFlaggedAccounts();
2060
- const menuResult = await promptLoginMode(toExistingAccountInfo(currentStorage, quotaCache, displaySettings), {
2065
+ await syncCodexCliActiveSelectionIfDrifted(currentStorage);
2066
+ const runtimeCurrent = await loadRuntimeCurrentSelectionForStorage(currentStorage);
2067
+ const menuResult = await promptLoginMode(toExistingAccountInfo(currentStorage, quotaCache, displaySettings, runtimeCurrent), {
2061
2068
  flaggedCount: flaggedStorage.accounts.length,
2062
2069
  statusMessage: showFetchStatus
2063
2070
  ? () => menuQuotaRefreshStatus
@@ -2532,6 +2539,11 @@ export async function runCodexMultiAuthCli(rawArgs) {
2532
2539
  resolveActiveIndex,
2533
2540
  formatRateLimitEntry,
2534
2541
  loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
2542
+ loadAppBindStatus: async () => getAppBindStatus()
2543
+ .then((status) => (status.running ? status.router : null))
2544
+ .catch(() => null),
2545
+ loadAppHelperStatus: readAppRuntimeHelperAccountSignal,
2546
+ loadQuotaCache,
2535
2547
  });
2536
2548
  }
2537
2549
  if (command === "switch") {
@@ -2572,6 +2584,22 @@ export async function runCodexMultiAuthCli(rawArgs) {
2572
2584
  loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
2573
2585
  });
2574
2586
  }
2587
+ if (command === "rotation") {
2588
+ return runRotationCommand(rest, {
2589
+ loadPluginConfig,
2590
+ savePluginConfig,
2591
+ getCodexRuntimeRotationProxy,
2592
+ setStoragePath,
2593
+ getStoragePath,
2594
+ loadAccounts,
2595
+ resolveActiveIndex,
2596
+ bindCodexApp: bindCodexAppRuntimeRotation,
2597
+ unbindCodexApp: unbindCodexAppRuntimeRotation,
2598
+ getCodexAppBindStatus: getAppBindStatus,
2599
+ loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
2600
+ loadQuotaCache,
2601
+ });
2602
+ }
2575
2603
  if (command === "why-selected") {
2576
2604
  return runWhySelectedCommand(rest, {
2577
2605
  parseWhySelectedArgs,