@juspay/neurolink 12.7.3 → 12.7.4

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.
@@ -63,8 +63,10 @@ const ANTHROPIC_CONSOLE_OAUTH_CONFIG = {
63
63
  // API key creation endpoint
64
64
  createApiKeyUrl: "https://api.anthropic.com/api/oauth/claude_cli/create_api_key",
65
65
  };
66
- // Supported providers
67
- const SUPPORTED_PROVIDERS = ["anthropic", "codex"];
66
+ // Providers with first-class credential flows in `neurolink auth`. This is
67
+ // intentionally narrower than the CLI's full AI-provider catalog: `auth list`
68
+ // renders every provider-qualified account stored by the CLI.
69
+ const AUTH_LOGIN_PROVIDERS = ["anthropic", "codex"];
68
70
  // =============================================================================
69
71
  // SUBCOMMAND HANDLERS
70
72
  // =============================================================================
@@ -79,8 +81,8 @@ export async function handleLogin(argv) {
79
81
  try {
80
82
  const provider = argv.provider?.toLowerCase();
81
83
  // Validate provider
82
- if (!SUPPORTED_PROVIDERS.includes(provider)) {
83
- logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${SUPPORTED_PROVIDERS.join(", ")}`));
84
+ if (!AUTH_LOGIN_PROVIDERS.includes(provider)) {
85
+ logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${AUTH_LOGIN_PROVIDERS.join(", ")}`));
84
86
  process.exit(1);
85
87
  }
86
88
  // Codex has a dedicated import-based login path (imports the current
@@ -294,13 +296,148 @@ export function formatQuotaWindowRows(quota) {
294
296
  /**
295
297
  * Fetch fresh limits for `auth list --refresh`.
296
298
  *
297
- * Prefers the running proxy's GET /limits endpoint so the proxy's in-memory
298
- * routing state is refreshed as a side effect; falls back to fetching the
299
- * usage endpoint directly from this process (persisting through the same
300
- * quota store) when no proxy is running or the call fails.
299
+ * Every provider-qualified token-store account receives an explicit result.
300
+ * The running proxy is authoritative for provider adapters that declare proxy
301
+ * support, or for namespaces it returns. Registered direct adapters fill in
302
+ * the remaining namespaces. Providers with no quota adapter are shown as
303
+ * `not_supported`, never silently omitted.
301
304
  */
302
- async function refreshAccountLimitsForList() {
305
+ function providerFromAccountKey(key) {
306
+ const separator = key.indexOf(":");
307
+ const provider = (separator === -1 ? key : key.slice(0, separator))
308
+ .trim()
309
+ .toLowerCase();
310
+ return provider || "unknown";
311
+ }
312
+ const DIRECT_QUOTA_REFRESH_CONCURRENCY = 3;
313
+ // Provider-specific protocol knowledge is kept behind this capability map.
314
+ // The CLI result itself remains provider-generic: adding a new adapter only
315
+ // requires registering it here, while every other configured provider still
316
+ // receives an explicit `not_supported` result.
317
+ const PROVIDER_QUOTA_ADAPTERS = {
318
+ anthropic: {
319
+ supportsProxyRefresh: true,
320
+ listAccounts: () => listAnthropicAccountsForUsage(),
321
+ priorQuotaKeys: (account) => [account.key, account.label],
322
+ async refreshAccount(account, { prior }) {
323
+ if (account.type !== "oauth") {
324
+ return { status: "not_supported" };
325
+ }
326
+ const result = await fetchAccountUsage(account);
327
+ if (result.ok === false) {
328
+ return { status: "unavailable", error: result.error };
329
+ }
330
+ const quota = usageToQuota(result.usage, { now: Date.now(), prior });
331
+ if (!quota) {
332
+ return {
333
+ status: "unavailable",
334
+ error: "usage payload had no recognizable limit windows",
335
+ };
336
+ }
337
+ return { status: "refreshed", quota };
338
+ },
339
+ },
340
+ codex: {
341
+ listAccounts: () => listCodexAccountsForUsage(),
342
+ priorQuotaKeys: (account) => [account.key],
343
+ async refreshAccount(account) {
344
+ if (account.type !== "oauth") {
345
+ return { status: "not_supported" };
346
+ }
347
+ const result = await fetchCodexAccountUsage(account);
348
+ if (result.ok === false) {
349
+ return result.reason === "not_oauth"
350
+ ? { status: "not_supported" }
351
+ : { status: "unavailable", error: `usage ${result.reason}` };
352
+ }
353
+ return { status: "refreshed", quota: result.quota };
354
+ },
355
+ },
356
+ };
357
+ async function refreshDirectProviderLimits(options) {
358
+ const { provider, adapter, accountKeys, prior, quotas, errors, accountResults, setAccountResult, } = options;
359
+ let accounts;
360
+ try {
361
+ accounts = await adapter.listAccounts();
362
+ }
363
+ catch (error) {
364
+ const message = error instanceof Error ? error.message : String(error);
365
+ for (const key of accountKeys) {
366
+ if (providerFromAccountKey(key) === provider) {
367
+ setAccountResult(key, "unavailable", message);
368
+ }
369
+ }
370
+ errors.push(`${provider} accounts: ${message}`);
371
+ return false;
372
+ }
373
+ let attemptedDirectRefresh = false;
374
+ let nextIndex = 0;
375
+ const worker = async () => {
376
+ for (;;) {
377
+ const index = nextIndex++;
378
+ if (index >= accounts.length) {
379
+ return;
380
+ }
381
+ const account = accounts[index];
382
+ if (accountResults[account.key]?.status === "refreshed" ||
383
+ accountResults[account.key]?.status === "snapshot") {
384
+ continue;
385
+ }
386
+ if (account.type === "oauth") {
387
+ attemptedDirectRefresh = true;
388
+ }
389
+ try {
390
+ const priorQuota = adapter
391
+ .priorQuotaKeys(account)
392
+ .map((key) => prior[key])
393
+ .find((quota) => quota !== undefined) ?? null;
394
+ const result = await adapter.refreshAccount(account, {
395
+ prior: priorQuota,
396
+ });
397
+ if (result.status === "refreshed") {
398
+ await saveAccountQuota(account.key, result.quota);
399
+ quotas[account.key] = result.quota;
400
+ setAccountResult(account.key, "refreshed");
401
+ continue;
402
+ }
403
+ setAccountResult(account.key, result.status, result.error);
404
+ if (result.status === "unavailable" && result.error) {
405
+ errors.push(`${provider}:${account.label}: ${result.error}`);
406
+ }
407
+ }
408
+ catch (error) {
409
+ const message = error instanceof Error ? error.message : String(error);
410
+ setAccountResult(account.key, "unavailable", message);
411
+ errors.push(`${provider}:${account.label}: ${message}`);
412
+ }
413
+ }
414
+ };
415
+ await Promise.all(Array.from({
416
+ length: Math.min(DIRECT_QUOTA_REFRESH_CONCURRENCY, accounts.length || 1),
417
+ }, () => worker()));
418
+ return attemptedDirectRefresh;
419
+ }
420
+ async function refreshAccountLimitsForList(accountKeys) {
303
421
  const errors = [];
422
+ const quotas = {};
423
+ const accountResults = {};
424
+ const proxyRefreshedProviders = new Set();
425
+ let refreshedViaProxy = false;
426
+ let refreshedDirectly = false;
427
+ const setAccountResult = (key, status, error) => {
428
+ accountResults[key] = {
429
+ provider: providerFromAccountKey(key),
430
+ status,
431
+ ...(error ? { error } : {}),
432
+ };
433
+ };
434
+ // `auth list` supports arbitrary provider-prefixed token-store entries.
435
+ // Providers with no quota adapter stay visible as explicitly unsupported
436
+ // rather than being rendered as an unexplained unavailable account.
437
+ for (const accountKey of accountKeys) {
438
+ setAccountResult(accountKey, "not_supported");
439
+ }
440
+ const configuredProviders = new Set(accountKeys.map((accountKey) => providerFromAccountKey(accountKey)));
304
441
  const proxyState = detectRunningProxyState();
305
442
  if (proxyState?.port) {
306
443
  const host = proxyState.host && proxyState.host !== "0.0.0.0"
@@ -312,113 +449,81 @@ async function refreshAccountLimitsForList() {
312
449
  });
313
450
  if (response.ok) {
314
451
  const payload = (await response.json());
315
- const quotas = {};
452
+ for (const provider of configuredProviders) {
453
+ if (PROVIDER_QUOTA_ADAPTERS[provider]?.supportsProxyRefresh) {
454
+ proxyRefreshedProviders.add(provider);
455
+ refreshedViaProxy = true;
456
+ }
457
+ }
316
458
  for (const result of payload.results) {
459
+ // The proxy already returns the canonical key. Keep the fallback for
460
+ // a pre-provider-key proxy during a rolling package transition.
461
+ const key = result.key || `anthropic:${result.account}`;
462
+ proxyRefreshedProviders.add(providerFromAccountKey(key));
463
+ refreshedViaProxy = true;
317
464
  if (result.quota) {
318
- quotas[result.account] = result.quota;
465
+ quotas[key] = result.quota;
319
466
  }
467
+ setAccountResult(key, result.status === "refreshed"
468
+ ? "refreshed"
469
+ : result.quota
470
+ ? "snapshot"
471
+ : result.status === "skipped_api_key"
472
+ ? "not_supported"
473
+ : "unavailable", result.error);
320
474
  if (result.status === "error" && result.error) {
321
- errors.push(`${result.account}: ${result.error}`);
475
+ errors.push(`${providerFromAccountKey(key)}:${result.account}: ${result.error}`);
322
476
  }
323
477
  }
324
- return { via: "proxy", quotas, errors };
325
478
  }
326
- errors.push(`running proxy /limits returned HTTP ${response.status}; fetching directly`);
479
+ if (!response.ok) {
480
+ errors.push(`running proxy /limits returned HTTP ${response.status}; trying direct quota adapters`);
481
+ }
327
482
  }
328
483
  catch {
329
484
  // Keep the message generic: a raw fetch error can echo the requested
330
485
  // URL, and this string reaches the text and JSON CLI output.
331
- errors.push("running proxy /limits unreachable; fetching directly");
486
+ errors.push("running proxy /limits unreachable; trying direct quota adapters");
332
487
  }
333
488
  }
489
+ const prior = await loadAccountQuotas().catch(() => ({}));
334
490
  try {
335
- const accounts = await listAnthropicAccountsForUsage();
336
- const prior = await loadAccountQuotas().catch(() => ({}));
337
- const quotas = {};
338
- const CONCURRENCY = 3;
339
- let nextIndex = 0;
340
- const worker = async () => {
341
- for (;;) {
342
- const index = nextIndex++;
343
- if (index >= accounts.length) {
344
- return;
345
- }
346
- const account = accounts[index];
347
- if (account.type !== "oauth") {
348
- continue; // api_key accounts have no subscription windows
349
- }
350
- // Isolate failures per account: one rejection must not abort the
351
- // Promise.all sweep or discard the other accounts' refreshed quotas.
352
- try {
353
- const result = await fetchAccountUsage(account);
354
- if (!result.ok) {
355
- errors.push(`${account.label}: ${result.error}`);
356
- continue;
357
- }
358
- const quota = usageToQuota(result.usage, {
359
- now: Date.now(),
360
- prior: prior[account.label] ?? null,
361
- });
362
- if (!quota) {
363
- errors.push(`${account.label}: usage payload had no recognizable limit windows`);
364
- continue;
365
- }
366
- await saveAccountQuota(account.label, quota);
367
- quotas[account.label] = quota;
368
- }
369
- catch (err) {
370
- errors.push(`${account.label}: ${err instanceof Error ? err.message : String(err)}`);
371
- }
372
- }
373
- };
374
- try {
375
- await Promise.all(Array.from({ length: Math.min(CONCURRENCY, accounts.length || 1) }, () => worker()));
376
- }
377
- finally {
378
- // The quota store's debounced flush timer is unref()'d and this process
379
- // is short-lived — flush now (even on a partial sweep) or the completed
380
- // saves never reach disk.
381
- await flushAccountQuotas().catch(() => undefined);
382
- }
383
- // Codex accounts: fetch the ChatGPT usage windows. Keyed by the full
384
- // `codex:` account key so quota never collides with an anthropic account
385
- // that shares a bare label.
386
- try {
387
- const codexAccounts = await listCodexAccountsForUsage();
388
- for (const account of codexAccounts) {
389
- if (account.type !== "oauth") {
390
- continue;
391
- }
392
- try {
393
- const result = await fetchCodexAccountUsage(account);
394
- if (!result.ok) {
395
- errors.push(`${account.label}: codex usage ${result.reason}`);
396
- continue;
397
- }
398
- await saveAccountQuota(account.key, result.quota);
399
- quotas[account.key] = result.quota;
400
- }
401
- catch (err) {
402
- errors.push(`${account.label}: ${err instanceof Error ? err.message : String(err)}`);
403
- }
491
+ for (const [provider, adapter] of Object.entries(PROVIDER_QUOTA_ADAPTERS)) {
492
+ if (!configuredProviders.has(provider) ||
493
+ proxyRefreshedProviders.has(provider)) {
494
+ continue;
404
495
  }
405
- }
406
- catch (err) {
407
- // Enumeration can throw before the per-account guard is reached. Without
408
- // this the failure escapes to the outer handler and the Anthropic quotas
409
- // fetched just above are dropped, reporting a total failure for a
410
- // Codex-only problem.
411
- errors.push(`codex accounts: ${err instanceof Error ? err.message : String(err)}`);
412
- }
413
- finally {
414
- await flushAccountQuotas().catch(() => undefined);
415
- }
416
- return { via: "direct", quotas, errors };
417
- }
418
- catch (err) {
419
- errors.push(`direct limit fetch failed (${err instanceof Error ? err.message : String(err)})`);
420
- return { via: "none", quotas: null, errors };
421
- }
496
+ refreshedDirectly =
497
+ (await refreshDirectProviderLimits({
498
+ provider,
499
+ adapter,
500
+ accountKeys,
501
+ prior,
502
+ quotas,
503
+ errors,
504
+ accountResults,
505
+ setAccountResult,
506
+ })) || refreshedDirectly;
507
+ }
508
+ }
509
+ finally {
510
+ // The quota store's debounced flush timer is unref()'d and this process is
511
+ // short-lived flush every completed provider snapshot before returning.
512
+ await flushAccountQuotas().catch(() => undefined);
513
+ }
514
+ const via = refreshedViaProxy && refreshedDirectly
515
+ ? "mixed"
516
+ : refreshedViaProxy
517
+ ? "proxy"
518
+ : refreshedDirectly
519
+ ? "direct"
520
+ : "none";
521
+ return {
522
+ via,
523
+ quotas: Object.keys(quotas).length > 0 ? quotas : null,
524
+ accounts: accountResults,
525
+ errors,
526
+ };
422
527
  }
423
528
  /**
424
529
  * Handle the list subcommand
@@ -514,10 +619,10 @@ export async function handleList(argv) {
514
619
  tokenType,
515
620
  };
516
621
  }));
517
- // Optionally fetch FRESH limits from Anthropic before rendering.
622
+ // Optionally fetch fresh provider limits before rendering.
518
623
  let refreshOutcome;
519
624
  if (argv.refresh) {
520
- refreshOutcome = await refreshAccountLimitsForList();
625
+ refreshOutcome = await refreshAccountLimitsForList(allKeys);
521
626
  }
522
627
  // Load persisted quota data (captured from proxy responses), then overlay
523
628
  // anything just refreshed — freshly fetched values win over the snapshot.
@@ -534,9 +639,19 @@ export async function handleList(argv) {
534
639
  if (argv.format === "json") {
535
640
  // Merge quota data into each account object for JSON output
536
641
  const withQuota = enrichedAccounts.map((acct) => {
537
- const quotaKey = acct.provider === "codex" ? acct.key : (acct.label ?? acct.key);
538
- const quota = quotas[quotaKey] ?? null;
539
- return { ...acct, quota };
642
+ const quota = quotas[acct.key] ??
643
+ // Old Anthropic snapshots used bare labels. Read them once so an
644
+ // upgrade does not hide a useful historic reading, but all new
645
+ // writes use the provider-qualified key above.
646
+ (acct.provider === "anthropic" && acct.label
647
+ ? quotas[acct.label]
648
+ : undefined) ??
649
+ null;
650
+ return {
651
+ ...acct,
652
+ quota,
653
+ refresh: refreshOutcome?.accounts[acct.key] ?? null,
654
+ };
540
655
  });
541
656
  if (refreshOutcome) {
542
657
  // --refresh envelopes the array so the fetch outcome travels with it.
@@ -544,6 +659,7 @@ export async function handleList(argv) {
544
659
  refresh: {
545
660
  via: refreshOutcome.via,
546
661
  errors: refreshOutcome.errors,
662
+ accounts: refreshOutcome.accounts,
547
663
  },
548
664
  accounts: withQuota,
549
665
  }, null, 2));
@@ -555,18 +671,21 @@ export async function handleList(argv) {
555
671
  else {
556
672
  if (refreshOutcome) {
557
673
  if (refreshOutcome.via !== "none") {
558
- logger.always(chalk.gray(`\nFetched fresh limits from Anthropic (${refreshOutcome.via === "proxy" ? "via running proxy" : "direct"}).`));
674
+ logger.always(chalk.gray(`\nFetched fresh limits (${refreshOutcome.via === "proxy" ? "via running proxy" : refreshOutcome.via === "direct" ? "direct provider APIs" : "via the running proxy and direct provider APIs"}).`));
559
675
  }
560
676
  for (const refreshError of refreshOutcome.errors) {
561
677
  logger.always(chalk.yellow(`⚠ ${refreshError}`));
562
678
  }
563
679
  }
564
680
  logger.always(chalk.bold("\nAuthenticated Accounts:\n"));
565
- // Check if any account has quota data to decide column layout
566
- const hasQuota = enrichedAccounts.some((acct) => {
567
- const quotaKey = acct.provider === "codex" ? acct.key : (acct.label ?? acct.key);
568
- return quotas[quotaKey] !== undefined;
569
- });
681
+ // `--refresh` always shows the limit columns, including an explicit
682
+ // unavailable/not-supported state. A blank table silently looked like a
683
+ // healthy Codex account had no quota information.
684
+ const hasQuota = !!refreshOutcome ||
685
+ enrichedAccounts.some((acct) => quotas[acct.key] !== undefined ||
686
+ (acct.provider === "anthropic" && acct.label
687
+ ? quotas[acct.label] !== undefined
688
+ : false));
570
689
  // Table header
571
690
  // Why an account is (or isn't) in the proxy pool. Without this a
572
691
  // disabled or parked account simply vanishes from routing with no clue.
@@ -575,10 +694,10 @@ export async function handleList(argv) {
575
694
  const colEmail = "EMAIL".padEnd(28);
576
695
  const colStatus = "TOKEN STATUS".padEnd(14);
577
696
  const colProvider = "PROVIDER".padEnd(12);
578
- const colSession = hasQuota ? "SESSION".padEnd(10) : "";
579
- const colWeekly = hasQuota ? "WEEKLY".padEnd(10) : "";
697
+ const colSession = hasQuota ? "SESSION".padEnd(14) : "";
698
+ const colWeekly = hasQuota ? "WEEKLY".padEnd(14) : "";
580
699
  logger.always(` ${chalk.gray(colKey)} ${chalk.gray(colProvider)} ${chalk.gray(colEmail)} ${chalk.gray(colStatus)}${hasQuota ? ` ${chalk.gray(colSession)} ${chalk.gray(colWeekly)}` : ""}`);
581
- logger.always(` ${chalk.gray("-".repeat(hasQuota ? 100 : 78))}`);
700
+ logger.always(` ${chalk.gray("-".repeat(hasQuota ? 108 : 78))}`);
582
701
  for (const acct of enrichedAccounts) {
583
702
  const displayLabel = (acct.label ?? acct.key).padEnd(20);
584
703
  const displayEmail = (acct.email ?? "-").padEnd(28);
@@ -593,16 +712,18 @@ export async function handleList(argv) {
593
712
  else {
594
713
  statusText = chalk.yellow("unknown".padEnd(14));
595
714
  }
596
- const quotaKey = acct.provider === "codex" ? acct.key : (acct.label ?? acct.key);
597
- const quota = quotas[quotaKey];
715
+ const quota = quotas[acct.key] ??
716
+ (acct.provider === "anthropic" && acct.label
717
+ ? quotas[acct.label]
718
+ : undefined);
598
719
  const poolNote = poolState[acct.key];
599
720
  if (hasQuota && quota) {
600
721
  const qc = formatQuotaColumns(quota);
601
- logger.always(` ${chalk.cyan(displayLabel)} ${displayProvider} ${displayEmail} ${statusText} ${qc.sessionText.padEnd(10)} ${qc.weeklyText.padEnd(10)}`);
722
+ logger.always(` ${chalk.cyan(displayLabel)} ${displayProvider} ${displayEmail} ${statusText} ${qc.sessionText.padEnd(14)} ${qc.weeklyText.padEnd(14)}`);
602
723
  const indent = " ".repeat(2 + 20 + 1 + 12 + 1 + 28 + 1 + 14 + 1);
603
724
  // Second line: reset times (indented under session/weekly columns)
604
725
  if (qc.sessionReset || qc.weeklyReset) {
605
- logger.always(`${indent}${(qc.sessionReset || "").padEnd(10)} ${qc.weeklyReset || ""}`);
726
+ logger.always(`${indent}${(qc.sessionReset || "").padEnd(14)} ${qc.weeklyReset || ""}`);
606
727
  }
607
728
  // Dynamic per-plan windows (e.g. the Fable-only weekly limit)
608
729
  for (const windowRow of formatQuotaWindowRows(quota)) {
@@ -613,10 +734,16 @@ export async function handleList(argv) {
613
734
  }
614
735
  }
615
736
  else {
737
+ const refreshState = refreshOutcome?.accounts[acct.key];
738
+ const unavailableText = refreshState?.status === "not_supported"
739
+ ? "not supported"
740
+ : refreshOutcome
741
+ ? "unavailable"
742
+ : "-";
616
743
  const apiKeyNote = refreshOutcome && acct.tokenType && acct.tokenType !== "Bearer"
617
744
  ? chalk.gray(" (api key — not refreshed)")
618
745
  : "";
619
- logger.always(` ${chalk.cyan(displayLabel)} ${displayProvider} ${displayEmail} ${statusText}${hasQuota ? " - -" : ""}${apiKeyNote}`);
746
+ logger.always(` ${chalk.cyan(displayLabel)} ${displayProvider} ${displayEmail} ${statusText}${hasQuota ? ` ${chalk.yellow(unavailableText.padEnd(14))} ${chalk.gray("n/a".padEnd(14))}` : ""}${apiKeyNote}`);
620
747
  if (poolNote) {
621
748
  logger.always(` ${poolNote}`);
622
749
  }
@@ -693,8 +820,8 @@ export async function handleLogout(argv) {
693
820
  try {
694
821
  const provider = argv.provider?.toLowerCase();
695
822
  // Validate provider
696
- if (!SUPPORTED_PROVIDERS.includes(provider)) {
697
- logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${SUPPORTED_PROVIDERS.join(", ")}`));
823
+ if (!AUTH_LOGIN_PROVIDERS.includes(provider)) {
824
+ logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${AUTH_LOGIN_PROVIDERS.join(", ")}`));
698
825
  process.exit(1);
699
826
  }
700
827
  logger.always(chalk.blue(`\nClearing ${provider} credentials...\n`));
@@ -784,7 +911,7 @@ export async function handleStatus(argv) {
784
911
  // If provider specified, show just that provider
785
912
  const providersToCheck = provider
786
913
  ? [provider]
787
- : [...SUPPORTED_PROVIDERS];
914
+ : [...AUTH_LOGIN_PROVIDERS];
788
915
  const results = [];
789
916
  for (const p of providersToCheck) {
790
917
  const status = await getAuthStatus(p);
@@ -892,8 +1019,8 @@ export async function handleRefresh(argv) {
892
1019
  try {
893
1020
  const provider = argv.provider?.toLowerCase();
894
1021
  // Validate provider
895
- if (!SUPPORTED_PROVIDERS.includes(provider)) {
896
- logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${SUPPORTED_PROVIDERS.join(", ")}`));
1022
+ if (!AUTH_LOGIN_PROVIDERS.includes(provider)) {
1023
+ logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${AUTH_LOGIN_PROVIDERS.join(", ")}`));
897
1024
  process.exit(1);
898
1025
  }
899
1026
  // Codex credentials live only in the pooled token store — there is no