codex-multi-auth 2.0.0 → 2.0.2

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 (102) hide show
  1. package/README.md +13 -10
  2. package/config/README.md +4 -4
  3. package/config/codex-legacy.json +45 -120
  4. package/config/codex-modern.json +36 -52
  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/auth/device-auth.d.ts +46 -0
  11. package/dist/lib/auth/device-auth.d.ts.map +1 -0
  12. package/dist/lib/auth/device-auth.js +325 -0
  13. package/dist/lib/auth/device-auth.js.map +1 -0
  14. package/dist/lib/auth/index.d.ts +2 -0
  15. package/dist/lib/auth/index.d.ts.map +1 -1
  16. package/dist/lib/auth/index.js +1 -0
  17. package/dist/lib/auth/index.js.map +1 -1
  18. package/dist/lib/auto-update-checker.d.ts +23 -1
  19. package/dist/lib/auto-update-checker.d.ts.map +1 -1
  20. package/dist/lib/auto-update-checker.js +481 -8
  21. package/dist/lib/auto-update-checker.js.map +1 -1
  22. package/dist/lib/cli.d.ts +5 -0
  23. package/dist/lib/cli.d.ts.map +1 -1
  24. package/dist/lib/cli.js.map +1 -1
  25. package/dist/lib/codex-manager/commands/best.js +1 -1
  26. package/dist/lib/codex-manager/commands/best.js.map +1 -1
  27. package/dist/lib/codex-manager/commands/forecast.js +3 -3
  28. package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
  29. package/dist/lib/codex-manager/commands/report.js +3 -3
  30. package/dist/lib/codex-manager/commands/report.js.map +1 -1
  31. package/dist/lib/codex-manager/commands/rotation.d.ts +4 -0
  32. package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
  33. package/dist/lib/codex-manager/commands/rotation.js +37 -11
  34. package/dist/lib/codex-manager/commands/rotation.js.map +1 -1
  35. package/dist/lib/codex-manager/commands/status.d.ts +6 -0
  36. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
  37. package/dist/lib/codex-manager/commands/status.js +26 -3
  38. package/dist/lib/codex-manager/commands/status.js.map +1 -1
  39. package/dist/lib/codex-manager/forecast-report-commands.js +4 -4
  40. package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -1
  41. package/dist/lib/codex-manager/help.d.ts +1 -0
  42. package/dist/lib/codex-manager/help.d.ts.map +1 -1
  43. package/dist/lib/codex-manager/help.js +20 -3
  44. package/dist/lib/codex-manager/help.js.map +1 -1
  45. package/dist/lib/codex-manager/rate-limit-markers.d.ts +2 -0
  46. package/dist/lib/codex-manager/rate-limit-markers.d.ts.map +1 -0
  47. package/dist/lib/codex-manager/rate-limit-markers.js +4 -0
  48. package/dist/lib/codex-manager/rate-limit-markers.js.map +1 -0
  49. package/dist/lib/codex-manager/repair-commands.js +3 -3
  50. package/dist/lib/codex-manager/repair-commands.js.map +1 -1
  51. package/dist/lib/codex-manager.d.ts.map +1 -1
  52. package/dist/lib/codex-manager.js +78 -91
  53. package/dist/lib/codex-manager.js.map +1 -1
  54. package/dist/lib/config.js +2 -2
  55. package/dist/lib/config.js.map +1 -1
  56. package/dist/lib/prompts/codex.d.ts +1 -1
  57. package/dist/lib/prompts/codex.d.ts.map +1 -1
  58. package/dist/lib/prompts/codex.js +3 -3
  59. package/dist/lib/prompts/codex.js.map +1 -1
  60. package/dist/lib/quota-probe.js +1 -1
  61. package/dist/lib/quota-probe.js.map +1 -1
  62. package/dist/lib/quota-readiness.d.ts +16 -0
  63. package/dist/lib/quota-readiness.d.ts.map +1 -0
  64. package/dist/lib/quota-readiness.js +82 -0
  65. package/dist/lib/quota-readiness.js.map +1 -0
  66. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  67. package/dist/lib/request/fetch-helpers.js +19 -8
  68. package/dist/lib/request/fetch-helpers.js.map +1 -1
  69. package/dist/lib/request/helpers/model-map.d.ts +2 -1
  70. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  71. package/dist/lib/request/helpers/model-map.js +52 -60
  72. package/dist/lib/request/helpers/model-map.js.map +1 -1
  73. package/dist/lib/request/request-transformer.d.ts +2 -2
  74. package/dist/lib/request/request-transformer.js +4 -3
  75. package/dist/lib/request/request-transformer.js.map +1 -1
  76. package/dist/lib/runtime/login-menu-accounts.d.ts +5 -0
  77. package/dist/lib/runtime/login-menu-accounts.d.ts.map +1 -1
  78. package/dist/lib/runtime/login-menu-accounts.js +7 -2
  79. package/dist/lib/runtime/login-menu-accounts.js.map +1 -1
  80. package/dist/lib/runtime/quota-probe.js +1 -1
  81. package/dist/lib/runtime/quota-probe.js.map +1 -1
  82. package/dist/lib/runtime/runtime-current-account.d.ts +52 -0
  83. package/dist/lib/runtime/runtime-current-account.d.ts.map +1 -0
  84. package/dist/lib/runtime/runtime-current-account.js +252 -0
  85. package/dist/lib/runtime/runtime-current-account.js.map +1 -0
  86. package/dist/lib/runtime-rotation-proxy.js +1 -1
  87. package/dist/lib/runtime-rotation-proxy.js.map +1 -1
  88. package/dist/lib/schemas.d.ts +3 -0
  89. package/dist/lib/schemas.d.ts.map +1 -1
  90. package/dist/lib/schemas.js +1 -0
  91. package/dist/lib/schemas.js.map +1 -1
  92. package/dist/lib/ui/auth-menu.d.ts +6 -1
  93. package/dist/lib/ui/auth-menu.d.ts.map +1 -1
  94. package/dist/lib/ui/auth-menu.js +22 -2
  95. package/dist/lib/ui/auth-menu.js.map +1 -1
  96. package/package.json +1 -1
  97. package/scripts/bench-format/codex-host.mjs +142 -142
  98. package/scripts/bench-format/models.mjs +20 -22
  99. package/scripts/benchmark-edit-formats.mjs +9 -9
  100. package/scripts/codex-app-router.js +121 -121
  101. package/scripts/codex.js +3636 -3473
  102. package/scripts/postinstall.js +120 -14
@@ -2,6 +2,7 @@ import { stdin as input, stdout as output } from "node:process";
2
2
  import { createInterface } from "node:readline/promises";
3
3
  import { extractAccountEmail, extractAccountId, formatAccountLabel, formatWaitTime, getAccountIdCandidates, resolveRequestAccountId, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, } from "./accounts.js";
4
4
  import { createAuthorizationFlow, exchangeAuthorizationCode, parseAuthorizationInput, redactOAuthUrlForLog, REDIRECT_URI, } from "./auth/auth.js";
5
+ import { runDeviceAuthFlow } from "./auth/device-auth.js";
5
6
  import { copyTextToClipboard, isBrowserLaunchSuppressed, openBrowserUrl, } from "./auth/browser.js";
6
7
  import { startLocalOAuthServer } from "./auth/server.js";
7
8
  import { promptAddAnotherAccount, promptLoginMode, } from "./cli.js";
@@ -34,7 +35,9 @@ import { createLogger } from "./logger.js";
34
35
  import { MODEL_FAMILIES } from "./prompts/codex.js";
35
36
  import { getModelCapabilities, getModelProfile, resolveNormalizedModel, } from "./request/helpers/model-map.js";
36
37
  import { formatRateLimitEntry as formatAccountRateLimitEntry, getRateLimitResetTimeForFamily, resolveActiveIndex, } from "./runtime/account-status.js";
38
+ import { buildQuotaEmailFallbackState, hasSafeQuotaEmailFallback, hasUniqueQuotaAccountId, isQuotaCacheEntryExhausted, normalizeQuotaAccountId, normalizeQuotaEmail, quotaLeftPercentFromUsed, } from "./quota-readiness.js";
37
39
  import { loadQuotaCache, saveQuotaCache, } from "./quota-cache.js";
40
+ import { isDisplayCurrentAccount, readAppRuntimeHelperAccountSignal, resolveAccountCurrentMarkers, resolveRuntimeCurrentAccount, } from "./runtime/runtime-current-account.js";
38
41
  import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe.js";
39
42
  import { queuedRefresh } from "./refresh-queue.js";
40
43
  import { clearAccounts, findMatchingAccountIndex, formatStorageErrorHint, inspectStorageHealth, getLastAccountsSaveTimestamp, getNamedBackups, getStoragePath, loadAccounts, loadFlaggedAccounts, restoreAccountsFromBackup, StorageError, saveAccounts, setStoragePath, withAccountStorageTransaction, } from "./storage.js";
@@ -318,70 +321,6 @@ const IMPLEMENTED_FEATURES = [
318
321
  function formatRateLimitEntry(account, now, family = "codex") {
319
322
  return formatAccountRateLimitEntry(account, now, formatWaitTime, family);
320
323
  }
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
324
  function quotaCacheEntryToSnapshot(entry) {
386
325
  return {
387
326
  status: entry.status,
@@ -417,13 +356,7 @@ function formatCompactQuotaPart(windowMinutes, usedPercent) {
417
356
  const left = quotaLeftPercentFromUsed(usedPercent);
418
357
  return `${label} ${left}%`;
419
358
  }
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) {
359
+ function formatCompactQuotaSnapshot(snapshot, now = Date.now()) {
427
360
  const parts = [
428
361
  formatCompactQuotaPart(snapshot.primary.windowMinutes, snapshot.primary.usedPercent),
429
362
  formatCompactQuotaPart(snapshot.secondary.windowMinutes, snapshot.secondary.usedPercent),
@@ -431,12 +364,15 @@ function formatCompactQuotaSnapshot(snapshot) {
431
364
  if (snapshot.status === 429) {
432
365
  parts.push("rate-limited");
433
366
  }
367
+ if (isQuotaCacheEntryExhausted(snapshot, now)) {
368
+ parts.push("quota-exhausted");
369
+ }
434
370
  if (parts.length > 0) {
435
371
  return parts.join(" | ");
436
372
  }
437
373
  return formatQuotaSnapshotLine(snapshot);
438
374
  }
439
- function formatAccountQuotaSummary(entry) {
375
+ function formatAccountQuotaSummary(entry, now = Date.now()) {
440
376
  const parts = [
441
377
  formatCompactQuotaPart(entry.primary.windowMinutes, entry.primary.usedPercent),
442
378
  formatCompactQuotaPart(entry.secondary.windowMinutes, entry.secondary.usedPercent),
@@ -444,6 +380,9 @@ function formatAccountQuotaSummary(entry) {
444
380
  if (entry.status === 429) {
445
381
  parts.push("rate-limited");
446
382
  }
383
+ if (isQuotaCacheEntryExhausted(entry, now)) {
384
+ parts.push("quota-exhausted");
385
+ }
447
386
  if (parts.length > 0) {
448
387
  return parts.join(" | ");
449
388
  }
@@ -481,7 +420,7 @@ function getPersistedQuotaViewForAccount(cache, account, accounts, now, emailFal
481
420
  return {
482
421
  updatedAt: cachedEntry?.updatedAt ?? now,
483
422
  status: 429,
484
- model: cachedEntry?.model ?? "gpt-5-codex",
423
+ model: cachedEntry?.model ?? "gpt-5.3-codex",
485
424
  planType: cachedEntry?.planType,
486
425
  primary: {
487
426
  ...cachedEntry?.primary,
@@ -549,7 +488,7 @@ function pruneUnsafeQuotaEmailCacheEntry(cache, email, accounts, emailFallbackSt
549
488
  return true;
550
489
  }
551
490
  const DEFAULT_MENU_QUOTA_REFRESH_TTL_MS = 5 * 60_000;
552
- const MENU_QUOTA_REFRESH_MODEL = "gpt-5-codex";
491
+ const MENU_QUOTA_REFRESH_MODEL = "gpt-5.3-codex";
553
492
  function resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, accounts, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
554
493
  if (account.enabled === false)
555
494
  return null;
@@ -651,19 +590,22 @@ function hasLikelyInvalidRefreshToken(refreshToken) {
651
590
  return true;
652
591
  return trimmed.startsWith("token-");
653
592
  }
654
- function mapAccountStatus(account, index, activeIndex, now, persistedQuotaStatus) {
593
+ function mapAccountStatus(account, isCurrentAccount, now, persistedQuotaEntry) {
655
594
  if (account.enabled === false)
656
595
  return "disabled";
657
596
  if (typeof account.coolingDownUntil === "number" &&
658
597
  account.coolingDownUntil > now) {
659
598
  return "cooldown";
660
599
  }
661
- if (persistedQuotaStatus === 429)
600
+ if (persistedQuotaEntry && isQuotaCacheEntryExhausted(persistedQuotaEntry, now)) {
601
+ return "quota-exhausted";
602
+ }
603
+ if (persistedQuotaEntry?.status === 429)
662
604
  return "rate-limited";
663
605
  const rateLimit = formatRateLimitEntry(account, now, "codex");
664
606
  if (rateLimit)
665
607
  return "rate-limited";
666
- if (index === activeIndex)
608
+ if (isCurrentAccount)
667
609
  return "active";
668
610
  return "ok";
669
611
  }
@@ -695,6 +637,7 @@ function accountStatusSortBucket(status) {
695
637
  return 0;
696
638
  case "unknown":
697
639
  return 1;
640
+ case "quota-exhausted":
698
641
  case "cooldown":
699
642
  case "rate-limited":
700
643
  return 2;
@@ -710,7 +653,7 @@ function accountReadinessSortBucket(account) {
710
653
  const statusBucket = accountStatusSortBucket(account.status);
711
654
  if (statusBucket >= 2)
712
655
  return statusBucket;
713
- return account.quotaRateLimited ? 2 : statusBucket;
656
+ return account.quotaRateLimited || account.quotaExhausted ? 2 : statusBucket;
714
657
  }
715
658
  function compareReadyFirstAccounts(left, right) {
716
659
  const bucketDelta = accountReadinessSortBucket(left) - accountReadinessSortBucket(right);
@@ -765,13 +708,16 @@ function applyAccountMenuOrdering(accounts, displaySettings) {
765
708
  }
766
709
  return sorted;
767
710
  }
768
- function toExistingAccountInfo(storage, quotaCache, displaySettings) {
711
+ function toExistingAccountInfo(storage, quotaCache, displaySettings, runtimeCurrent = null) {
769
712
  const now = Date.now();
770
713
  const activeIndex = resolveActiveIndex(storage, "codex");
771
714
  const layoutMode = resolveMenuLayoutMode(displaySettings);
772
715
  const emailFallbackState = buildQuotaEmailFallbackState(storage.accounts);
773
716
  const baseAccounts = storage.accounts.map((account, index) => {
774
717
  const entry = getPersistedQuotaViewForAccount(quotaCache, account, storage.accounts, now, emailFallbackState);
718
+ const currentMarkers = resolveAccountCurrentMarkers(index, activeIndex, runtimeCurrent);
719
+ const isCurrentAccount = isDisplayCurrentAccount(index, activeIndex, runtimeCurrent);
720
+ const quotaExhausted = entry ? isQuotaCacheEntryExhausted(entry, now) : false;
775
721
  return {
776
722
  index,
777
723
  sourceIndex: index,
@@ -780,16 +726,20 @@ function toExistingAccountInfo(storage, quotaCache, displaySettings) {
780
726
  email: account.email,
781
727
  addedAt: account.addedAt,
782
728
  lastUsed: account.lastUsed,
783
- status: mapAccountStatus(account, index, activeIndex, now, entry?.status),
729
+ status: mapAccountStatus(account, isCurrentAccount, now, entry),
784
730
  quotaSummary: (displaySettings.menuShowQuotaSummary ?? true) && entry
785
- ? formatAccountQuotaSummary(entry)
731
+ ? formatAccountQuotaSummary(entry, now)
786
732
  : undefined,
787
733
  quota5hLeftPercent: quotaLeftPercentFromUsed(entry?.primary.usedPercent),
788
734
  quota5hResetAtMs: entry?.primary.resetAtMs,
789
735
  quota7dLeftPercent: quotaLeftPercentFromUsed(entry?.secondary.usedPercent),
790
736
  quota7dResetAtMs: entry?.secondary.resetAtMs,
791
737
  quotaRateLimited: entry?.status === 429,
792
- isCurrentAccount: index === activeIndex,
738
+ quotaExhausted,
739
+ isCurrentAccount,
740
+ isDefaultAccount: index === activeIndex,
741
+ isRuntimeCurrentAccount: runtimeCurrent?.index === index,
742
+ currentMarkers,
793
743
  enabled: account.enabled !== false,
794
744
  showStatusBadge: displaySettings.menuShowStatusBadge ?? true,
795
745
  showCurrentBadge: displaySettings.menuShowCurrentBadge ?? true,
@@ -815,6 +765,19 @@ function toExistingAccountInfo(storage, quotaCache, displaySettings) {
815
765
  : (account.sourceIndex ?? displayIndex) + 1,
816
766
  }));
817
767
  }
768
+ async function loadRuntimeCurrentSelectionForStorage(storage, now = Date.now()) {
769
+ const [runtimeSnapshot, appBindStatus] = await Promise.all([
770
+ loadPersistedRuntimeObservabilitySnapshot().catch(() => null),
771
+ getAppBindStatus()
772
+ .then((status) => (status.running ? status.router : null))
773
+ .catch(() => null),
774
+ ]);
775
+ return resolveRuntimeCurrentAccount(storage, {
776
+ runtimeSnapshot,
777
+ appBindStatus,
778
+ appHelperStatus: readAppRuntimeHelperAccountSignal(),
779
+ }, { now });
780
+ }
818
781
  function activeAccountMatchesCodexCliState(account, state) {
819
782
  if (!state)
820
783
  return true;
@@ -1473,6 +1436,18 @@ async function runOAuthFlow(forceNewLogin, signInMode) {
1473
1436
  }
1474
1437
  return exchangeAuthorizationCode(code, pkce.verifier, REDIRECT_URI);
1475
1438
  }
1439
+ async function runSignInFlow(forceNewLogin, signInMode, options = {}) {
1440
+ if (signInMode === "device") {
1441
+ // OpenAI owns the device-code account picker; there is no force-new-login
1442
+ // equivalent to pass through for this mode.
1443
+ // TODO: Thread a manager-level AbortSignal when login cancellation exists.
1444
+ return runDeviceAuthFlow({
1445
+ log: console.log,
1446
+ timeoutMs: options.timeoutMs,
1447
+ });
1448
+ }
1449
+ return runOAuthFlow(forceNewLogin, signInMode);
1450
+ }
1476
1451
  async function persistAccountPool(results, replaceAll) {
1477
1452
  if (results.length === 0)
1478
1453
  return;
@@ -1572,7 +1547,7 @@ async function syncSelectionToCodex(tokens) {
1572
1547
  async function runHealthCheck(options = {}) {
1573
1548
  const forceRefresh = options.forceRefresh === true;
1574
1549
  const liveProbe = options.liveProbe === true;
1575
- const probeModel = options.model?.trim() || "gpt-5-codex";
1550
+ const probeModel = options.model?.trim() || "gpt-5.3-codex";
1576
1551
  const modelInspection = inspectRequestedModel(probeModel);
1577
1552
  const display = options.display ?? DEFAULT_DASHBOARD_DISPLAY_SETTINGS;
1578
1553
  const quotaCache = liveProbe ? await loadQuotaCache() : null;
@@ -1788,7 +1763,7 @@ function printBestUsage() {
1788
1763
  "Options:",
1789
1764
  " --live, -l Probe live quota headers via Codex backend before switching",
1790
1765
  " --json, -j Print machine-readable JSON output",
1791
- " --model, -m Probe model for live mode (default: gpt-5-codex)",
1766
+ " --model, -m Probe model for live mode (default: gpt-5.3-codex)",
1792
1767
  "",
1793
1768
  "Behavior:",
1794
1769
  " - Chooses the healthiest account using forecast scoring",
@@ -1799,7 +1774,7 @@ function parseBestArgs(args) {
1799
1774
  const options = {
1800
1775
  live: false,
1801
1776
  json: false,
1802
- model: "gpt-5-codex",
1777
+ model: "gpt-5.3-codex",
1803
1778
  modelProvided: false,
1804
1779
  };
1805
1780
  for (let i = 0; i < args.length; i += 1) {
@@ -2101,7 +2076,8 @@ async function runAuthLogin(args) {
2101
2076
  }
2102
2077
  const flaggedStorage = await loadFlaggedAccounts();
2103
2078
  await syncCodexCliActiveSelectionIfDrifted(currentStorage);
2104
- const menuResult = await promptLoginMode(toExistingAccountInfo(currentStorage, quotaCache, displaySettings), {
2079
+ const runtimeCurrent = await loadRuntimeCurrentSelectionForStorage(currentStorage);
2080
+ const menuResult = await promptLoginMode(toExistingAccountInfo(currentStorage, quotaCache, displaySettings, runtimeCurrent), {
2105
2081
  flaggedCount: flaggedStorage.accounts.length,
2106
2082
  statusMessage: showFetchStatus
2107
2083
  ? () => menuQuotaRefreshStatus
@@ -2210,9 +2186,11 @@ async function runAuthLogin(args) {
2210
2186
  while (true) {
2211
2187
  const latestNamedBackup = namedBackups[0] ?? null;
2212
2188
  const preferManualMode = loginOptions.manual || isBrowserLaunchSuppressed();
2213
- const signInMode = preferManualMode
2214
- ? "manual"
2215
- : await promptOAuthSignInMode(latestNamedBackup, onboardingBackupDiscoveryWarning);
2189
+ const signInMode = loginOptions.deviceAuth
2190
+ ? "device"
2191
+ : preferManualMode
2192
+ ? "manual"
2193
+ : await promptOAuthSignInMode(latestNamedBackup, onboardingBackupDiscoveryWarning);
2216
2194
  if (signInMode === "cancel") {
2217
2195
  if (existingCount > 0) {
2218
2196
  console.log(stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
@@ -2280,10 +2258,12 @@ async function runAuthLogin(args) {
2280
2258
  }
2281
2259
  continue loginFlow;
2282
2260
  }
2283
- if (signInMode !== "browser" && signInMode !== "manual") {
2261
+ if (signInMode !== "browser" &&
2262
+ signInMode !== "manual" &&
2263
+ signInMode !== "device") {
2284
2264
  continue;
2285
2265
  }
2286
- const tokenResult = await runOAuthFlow(forceNewLogin, signInMode);
2266
+ const tokenResult = await runSignInFlow(forceNewLogin, signInMode);
2287
2267
  if (tokenResult.type !== "success") {
2288
2268
  if (isOAuthCancellation(tokenResult)) {
2289
2269
  if (existingCount > 0) {
@@ -2576,6 +2556,11 @@ export async function runCodexMultiAuthCli(rawArgs) {
2576
2556
  resolveActiveIndex,
2577
2557
  formatRateLimitEntry,
2578
2558
  loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
2559
+ loadAppBindStatus: async () => getAppBindStatus()
2560
+ .then((status) => (status.running ? status.router : null))
2561
+ .catch(() => null),
2562
+ loadAppHelperStatus: readAppRuntimeHelperAccountSignal,
2563
+ loadQuotaCache,
2579
2564
  });
2580
2565
  }
2581
2566
  if (command === "switch") {
@@ -2628,6 +2613,8 @@ export async function runCodexMultiAuthCli(rawArgs) {
2628
2613
  bindCodexApp: bindCodexAppRuntimeRotation,
2629
2614
  unbindCodexApp: unbindCodexAppRuntimeRotation,
2630
2615
  getCodexAppBindStatus: getAppBindStatus,
2616
+ loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
2617
+ loadQuotaCache,
2631
2618
  });
2632
2619
  }
2633
2620
  if (command === "why-selected") {