codex-multi-auth 0.1.5 → 0.1.8

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 (75) hide show
  1. package/LICENSE +21 -1
  2. package/README.md +19 -6
  3. package/assets/readme-hero.svg +31 -31
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/codex-cli/state.js +4 -4
  9. package/dist/lib/codex-cli/state.js.map +1 -1
  10. package/dist/lib/codex-cli/sync.d.ts +9 -16
  11. package/dist/lib/codex-cli/sync.d.ts.map +1 -1
  12. package/dist/lib/codex-cli/sync.js +51 -284
  13. package/dist/lib/codex-cli/sync.js.map +1 -1
  14. package/dist/lib/codex-cli/writer.d.ts.map +1 -1
  15. package/dist/lib/codex-cli/writer.js +62 -5
  16. package/dist/lib/codex-cli/writer.js.map +1 -1
  17. package/dist/lib/codex-manager/settings-hub.d.ts +44 -2
  18. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
  19. package/dist/lib/codex-manager/settings-hub.js +746 -117
  20. package/dist/lib/codex-manager/settings-hub.js.map +1 -1
  21. package/dist/lib/codex-manager.d.ts.map +1 -1
  22. package/dist/lib/codex-manager.js +3 -8
  23. package/dist/lib/codex-manager.js.map +1 -1
  24. package/dist/lib/context-overflow.js +10 -10
  25. package/dist/lib/named-backup-export.d.ts +69 -0
  26. package/dist/lib/named-backup-export.d.ts.map +1 -0
  27. package/dist/lib/named-backup-export.js +207 -0
  28. package/dist/lib/named-backup-export.js.map +1 -0
  29. package/dist/lib/oauth-success.html +66 -210
  30. package/dist/lib/oc-chatgpt-import-adapter.d.ts +64 -0
  31. package/dist/lib/oc-chatgpt-import-adapter.d.ts.map +1 -0
  32. package/dist/lib/oc-chatgpt-import-adapter.js +506 -0
  33. package/dist/lib/oc-chatgpt-import-adapter.js.map +1 -0
  34. package/dist/lib/oc-chatgpt-orchestrator.d.ts +80 -0
  35. package/dist/lib/oc-chatgpt-orchestrator.d.ts.map +1 -0
  36. package/dist/lib/oc-chatgpt-orchestrator.js +130 -0
  37. package/dist/lib/oc-chatgpt-orchestrator.js.map +1 -0
  38. package/dist/lib/oc-chatgpt-target-detection.d.ts +59 -0
  39. package/dist/lib/oc-chatgpt-target-detection.d.ts.map +1 -0
  40. package/dist/lib/oc-chatgpt-target-detection.js +327 -0
  41. package/dist/lib/oc-chatgpt-target-detection.js.map +1 -0
  42. package/dist/lib/prompts/codex.js +2 -2
  43. package/dist/lib/runtime-paths.d.ts.map +1 -1
  44. package/dist/lib/runtime-paths.js +44 -2
  45. package/dist/lib/runtime-paths.js.map +1 -1
  46. package/dist/lib/storage.d.ts +42 -3
  47. package/dist/lib/storage.d.ts.map +1 -1
  48. package/dist/lib/storage.js +634 -49
  49. package/dist/lib/storage.js.map +1 -1
  50. package/dist/lib/ui/auth-menu.d.ts.map +1 -1
  51. package/dist/lib/ui/auth-menu.js +13 -1
  52. package/dist/lib/ui/auth-menu.js.map +1 -1
  53. package/dist/lib/ui/copy.d.ts +15 -1
  54. package/dist/lib/ui/copy.d.ts.map +1 -1
  55. package/dist/lib/ui/copy.js +15 -1
  56. package/dist/lib/ui/copy.js.map +1 -1
  57. package/node_modules/@codex-ai/plugin/dist/index.d.ts +1 -1
  58. package/node_modules/@codex-ai/plugin/dist/tool.d.ts +41 -41
  59. package/node_modules/@codex-ai/plugin/dist/tool.js +28 -28
  60. package/package.json +132 -133
  61. package/scripts/bench-format/hashline-v2.mjs +642 -642
  62. package/scripts/bench-format/render.mjs +496 -496
  63. package/scripts/bench-format/stats.mjs +54 -54
  64. package/scripts/bench-format/tasks.mjs +151 -151
  65. package/scripts/benchmark-render-dashboard.mjs +49 -49
  66. package/scripts/codex-multi-auth.js +12 -0
  67. package/scripts/codex.js +315 -2
  68. package/scripts/install-codex-auth.js +9 -9
  69. package/scripts/test-all-models.sh +7 -7
  70. package/scripts/test-model-matrix.js +247 -148
  71. package/scripts/validate-model-map.sh +7 -7
  72. package/vendor/codex-ai-plugin/dist/index.d.ts +1 -1
  73. package/vendor/codex-ai-plugin/dist/tool.d.ts +41 -41
  74. package/vendor/codex-ai-plugin/dist/tool.js +28 -28
  75. package/vendor/codex-ai-sdk/dist/index.d.ts +3 -3
@@ -1,12 +1,17 @@
1
+ import { promises as fs } from "node:fs";
1
2
  import { stdin as input, stdout as output } from "node:process";
2
- import { loadDashboardDisplaySettings, saveDashboardDisplaySettings, getDashboardSettingsPath, DEFAULT_DASHBOARD_DISPLAY_SETTINGS, } from "../dashboard-settings.js";
3
- import { getDefaultPluginConfig, loadPluginConfig, savePluginConfig } from "../config.js";
4
- import { getUnifiedSettingsPath } from "../unified-settings.js";
5
- import { sleep } from "../utils.js";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { getDefaultPluginConfig, loadPluginConfig, savePluginConfig, } from "../config.js";
5
+ import { DEFAULT_DASHBOARD_DISPLAY_SETTINGS, getDashboardSettingsPath, loadDashboardDisplaySettings, saveDashboardDisplaySettings, } from "../dashboard-settings.js";
6
+ import { applyOcChatgptSync, planOcChatgptSync, runNamedBackupExport, } from "../oc-chatgpt-orchestrator.js";
7
+ import { detectOcChatgptMultiAuthTarget } from "../oc-chatgpt-target-detection.js";
8
+ import { loadAccounts, normalizeAccountStorage } from "../storage.js";
6
9
  import { ANSI } from "../ui/ansi.js";
7
10
  import { UI_COPY } from "../ui/copy.js";
8
11
  import { getUiRuntimeOptions, setUiRuntimeOptions } from "../ui/runtime.js";
9
12
  import { select } from "../ui/select.js";
13
+ import { getUnifiedSettingsPath } from "../unified-settings.js";
14
+ import { sleep } from "../utils.js";
10
15
  const DASHBOARD_DISPLAY_OPTIONS = [
11
16
  {
12
17
  key: "menuShowStatusBadge",
@@ -59,7 +64,11 @@ const DASHBOARD_DISPLAY_OPTIONS = [
59
64
  description: "Number keys (1-9) follow what you see in the list.",
60
65
  },
61
66
  ];
62
- const DEFAULT_STATUSLINE_FIELDS = ["last-used", "limits", "status"];
67
+ const DEFAULT_STATUSLINE_FIELDS = [
68
+ "last-used",
69
+ "limits",
70
+ "status",
71
+ ];
63
72
  const STATUSLINE_FIELD_OPTIONS = [
64
73
  {
65
74
  key: "last-used",
@@ -80,7 +89,12 @@ const STATUSLINE_FIELD_OPTIONS = [
80
89
  const AUTO_RETURN_OPTIONS_MS = [1_000, 2_000, 4_000];
81
90
  const MENU_QUOTA_TTL_OPTIONS_MS = [60_000, 5 * 60_000, 10 * 60_000];
82
91
  const THEME_PRESET_OPTIONS = ["green", "blue"];
83
- const ACCENT_COLOR_OPTIONS = ["green", "cyan", "blue", "yellow"];
92
+ const ACCENT_COLOR_OPTIONS = [
93
+ "green",
94
+ "cyan",
95
+ "blue",
96
+ "yellow",
97
+ ];
84
98
  const PREVIEW_ACCOUNT_EMAIL = "demo@example.com";
85
99
  const PREVIEW_LAST_USED = "today";
86
100
  const PREVIEW_STATUS = "active";
@@ -326,12 +340,8 @@ const BACKEND_CATEGORY_OPTIONS = [
326
340
  key: "refresh-recovery",
327
341
  label: "Refresh & Recovery",
328
342
  description: "Token refresh and recovery safety.",
329
- toggleKeys: ["proactiveRefreshGuardian", "storageBackupEnabled"],
330
- numberKeys: [
331
- "proactiveRefreshIntervalMs",
332
- "proactiveRefreshBufferMs",
333
- "tokenRefreshSkewMs",
334
- ],
343
+ toggleKeys: ["storageBackupEnabled"],
344
+ numberKeys: ["proactiveRefreshBufferMs", "tokenRefreshSkewMs"],
335
345
  },
336
346
  {
337
347
  key: "performance-timeouts",
@@ -348,7 +358,13 @@ const BACKEND_CATEGORY_OPTIONS = [
348
358
  ],
349
359
  },
350
360
  ];
351
- const RETRYABLE_SETTINGS_WRITE_CODES = new Set(["EBUSY", "EPERM", "EAGAIN", "ENOTEMPTY", "EACCES"]);
361
+ const RETRYABLE_SETTINGS_WRITE_CODES = new Set([
362
+ "EBUSY",
363
+ "EPERM",
364
+ "EAGAIN",
365
+ "ENOTEMPTY",
366
+ "EACCES",
367
+ ]);
352
368
  const SETTINGS_WRITE_MAX_ATTEMPTS = 4;
353
369
  const SETTINGS_WRITE_BASE_DELAY_MS = 20;
354
370
  const SETTINGS_WRITE_MAX_DELAY_MS = 30_000;
@@ -368,7 +384,9 @@ const ACCOUNT_LIST_PANEL_KEYS = [
368
384
  "menuSortQuickSwitchVisibleRow",
369
385
  "menuLayoutMode",
370
386
  ];
371
- const STATUSLINE_PANEL_KEYS = ["menuStatuslineFields"];
387
+ const STATUSLINE_PANEL_KEYS = [
388
+ "menuStatuslineFields",
389
+ ];
372
390
  const BEHAVIOR_PANEL_KEYS = [
373
391
  "actionAutoReturnMs",
374
392
  "actionPauseOnKey",
@@ -376,7 +394,10 @@ const BEHAVIOR_PANEL_KEYS = [
376
394
  "menuShowFetchStatus",
377
395
  "menuQuotaTtlMs",
378
396
  ];
379
- const THEME_PANEL_KEYS = ["uiThemePreset", "uiAccentColor"];
397
+ const THEME_PANEL_KEYS = [
398
+ "uiThemePreset",
399
+ "uiAccentColor",
400
+ ];
380
401
  function readErrorNumber(value) {
381
402
  if (typeof value === "number" && Number.isFinite(value))
382
403
  return value;
@@ -411,7 +432,9 @@ function isRetryableSettingsWriteError(error) {
411
432
  }
412
433
  function resolveRetryDelayMs(error, attempt) {
413
434
  const retryAfterMs = getRetryAfterMs(error);
414
- if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
435
+ if (typeof retryAfterMs === "number" &&
436
+ Number.isFinite(retryAfterMs) &&
437
+ retryAfterMs > 0) {
415
438
  return Math.max(10, Math.min(SETTINGS_WRITE_MAX_DELAY_MS, Math.round(retryAfterMs)));
416
439
  }
417
440
  return Math.min(SETTINGS_WRITE_MAX_DELAY_MS, SETTINGS_WRITE_BASE_DELAY_MS * 2 ** attempt);
@@ -439,18 +462,23 @@ async function withQueuedRetry(pathKey, task) {
439
462
  }
440
463
  catch (error) {
441
464
  lastError = error;
442
- if (!isRetryableSettingsWriteError(error) || attempt + 1 >= SETTINGS_WRITE_MAX_ATTEMPTS) {
465
+ if (!isRetryableSettingsWriteError(error) ||
466
+ attempt + 1 >= SETTINGS_WRITE_MAX_ATTEMPTS) {
443
467
  throw error;
444
468
  }
445
469
  await sleep(resolveRetryDelayMs(error, attempt));
446
470
  }
447
471
  }
448
- throw lastError instanceof Error ? lastError : new Error("settings save retry exhausted");
472
+ throw lastError instanceof Error
473
+ ? lastError
474
+ : new Error("settings save retry exhausted");
449
475
  });
450
476
  }
451
477
  function copyDashboardSettingValue(target, source, key) {
452
478
  const value = source[key];
453
- target[key] = Array.isArray(value) ? [...value] : value;
479
+ target[key] = Array.isArray(value)
480
+ ? [...value]
481
+ : value;
454
482
  }
455
483
  function applyDashboardDefaultsForKeys(draft, keys) {
456
484
  const next = cloneDashboardSettings(draft);
@@ -494,6 +522,25 @@ async function persistDashboardSettingsSelection(selected, keys, scope) {
494
522
  return fallback;
495
523
  }
496
524
  }
525
+ async function readFileWithRetry(path) {
526
+ for (let attempt = 0;; attempt += 1) {
527
+ try {
528
+ return await fs.readFile(path, "utf-8");
529
+ }
530
+ catch (error) {
531
+ const code = error.code;
532
+ if (code === "ENOENT") {
533
+ throw error;
534
+ }
535
+ if (!code ||
536
+ !RETRYABLE_SETTINGS_WRITE_CODES.has(code) ||
537
+ attempt >= SETTINGS_WRITE_MAX_ATTEMPTS - 1) {
538
+ throw error;
539
+ }
540
+ await sleep(25 * 2 ** attempt);
541
+ }
542
+ }
543
+ }
497
544
  async function persistBackendConfigSelection(selected, scope) {
498
545
  const fallback = cloneBackendPluginConfig(selected);
499
546
  try {
@@ -549,7 +596,7 @@ function isCurrentRowPreviewFocus(focus) {
549
596
  return focus === "menuHighlightCurrentRow";
550
597
  }
551
598
  function isExpandedRowsPreviewFocus(focus) {
552
- return focus === "menuShowDetailsForUnselectedRows" || focus === "menuLayoutMode";
599
+ return (focus === "menuShowDetailsForUnselectedRows" || focus === "menuLayoutMode");
553
600
  }
554
601
  function buildSummaryPreviewText(settings, ui, focus = null) {
555
602
  const partsByField = new Map();
@@ -587,11 +634,15 @@ function buildAccountListPreview(settings, ui, focus = null) {
587
634
  const badges = [];
588
635
  if (settings.menuShowCurrentBadge !== false) {
589
636
  const currentBadge = "[current]";
590
- badges.push(isCurrentBadgePreviewFocus(focus) ? highlightPreviewToken(currentBadge, ui) : currentBadge);
637
+ badges.push(isCurrentBadgePreviewFocus(focus)
638
+ ? highlightPreviewToken(currentBadge, ui)
639
+ : currentBadge);
591
640
  }
592
641
  if (settings.menuShowStatusBadge !== false) {
593
642
  const statusBadge = "[active]";
594
- badges.push(isStatusPreviewFocus(focus) ? highlightPreviewToken(statusBadge, ui) : statusBadge);
643
+ badges.push(isStatusPreviewFocus(focus)
644
+ ? highlightPreviewToken(statusBadge, ui)
645
+ : statusBadge);
595
646
  }
596
647
  const badgeSuffix = badges.length > 0 ? ` ${badges.join(" ")}` : "";
597
648
  const accountEmail = isCurrentRowPreviewFocus(focus)
@@ -619,10 +670,15 @@ function cloneDashboardSettings(settings) {
619
670
  actionAutoReturnMs: settings.actionAutoReturnMs ?? 2_000,
620
671
  actionPauseOnKey: settings.actionPauseOnKey ?? true,
621
672
  menuAutoFetchLimits: settings.menuAutoFetchLimits ?? true,
622
- menuSortEnabled: settings.menuSortEnabled ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ?? true),
623
- menuSortMode: settings.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first"),
673
+ menuSortEnabled: settings.menuSortEnabled ??
674
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ??
675
+ true,
676
+ menuSortMode: settings.menuSortMode ??
677
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ??
678
+ "ready-first",
624
679
  menuSortPinCurrent: settings.menuSortPinCurrent ??
625
- (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ?? false),
680
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ??
681
+ false,
626
682
  menuSortQuickSwitchVisibleRow: settings.menuSortQuickSwitchVisibleRow ?? true,
627
683
  uiThemePreset: settings.uiThemePreset ?? "green",
628
684
  uiAccentColor: settings.uiAccentColor ?? "green",
@@ -637,7 +693,9 @@ function cloneDashboardSettings(settings) {
637
693
  menuQuotaTtlMs: settings.menuQuotaTtlMs ?? 5 * 60_000,
638
694
  menuFocusStyle: settings.menuFocusStyle ?? "row-invert",
639
695
  menuHighlightCurrentRow: settings.menuHighlightCurrentRow ?? true,
640
- menuStatuslineFields: [...normalizeStatuslineFields(settings.menuStatuslineFields)],
696
+ menuStatuslineFields: [
697
+ ...normalizeStatuslineFields(settings.menuStatuslineFields),
698
+ ],
641
699
  };
642
700
  }
643
701
  function dashboardSettingsEqual(left, right) {
@@ -646,29 +704,51 @@ function dashboardSettingsEqual(left, right) {
646
704
  left.showForecastReasons === right.showForecastReasons &&
647
705
  left.showRecommendations === right.showRecommendations &&
648
706
  left.showLiveProbeNotes === right.showLiveProbeNotes &&
649
- (left.actionAutoReturnMs ?? 2_000) === (right.actionAutoReturnMs ?? 2_000) &&
707
+ (left.actionAutoReturnMs ?? 2_000) ===
708
+ (right.actionAutoReturnMs ?? 2_000) &&
650
709
  (left.actionPauseOnKey ?? true) === (right.actionPauseOnKey ?? true) &&
651
- (left.menuAutoFetchLimits ?? true) === (right.menuAutoFetchLimits ?? true) &&
652
- (left.menuSortEnabled ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ?? true)) ===
653
- (right.menuSortEnabled ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ?? true)) &&
654
- (left.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first")) ===
655
- (right.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first")) &&
656
- (left.menuSortPinCurrent ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ?? false)) ===
657
- (right.menuSortPinCurrent ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ?? false)) &&
710
+ (left.menuAutoFetchLimits ?? true) ===
711
+ (right.menuAutoFetchLimits ?? true) &&
712
+ (left.menuSortEnabled ??
713
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ??
714
+ true) ===
715
+ (right.menuSortEnabled ??
716
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ??
717
+ true) &&
718
+ (left.menuSortMode ??
719
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ??
720
+ "ready-first") ===
721
+ (right.menuSortMode ??
722
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ??
723
+ "ready-first") &&
724
+ (left.menuSortPinCurrent ??
725
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ??
726
+ false) ===
727
+ (right.menuSortPinCurrent ??
728
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ??
729
+ false) &&
658
730
  (left.menuSortQuickSwitchVisibleRow ?? true) ===
659
731
  (right.menuSortQuickSwitchVisibleRow ?? true) &&
660
732
  (left.uiThemePreset ?? "green") === (right.uiThemePreset ?? "green") &&
661
733
  (left.uiAccentColor ?? "green") === (right.uiAccentColor ?? "green") &&
662
- (left.menuShowStatusBadge ?? true) === (right.menuShowStatusBadge ?? true) &&
663
- (left.menuShowCurrentBadge ?? true) === (right.menuShowCurrentBadge ?? true) &&
734
+ (left.menuShowStatusBadge ?? true) ===
735
+ (right.menuShowStatusBadge ?? true) &&
736
+ (left.menuShowCurrentBadge ?? true) ===
737
+ (right.menuShowCurrentBadge ?? true) &&
664
738
  (left.menuShowLastUsed ?? true) === (right.menuShowLastUsed ?? true) &&
665
- (left.menuShowQuotaSummary ?? true) === (right.menuShowQuotaSummary ?? true) &&
666
- (left.menuShowQuotaCooldown ?? true) === (right.menuShowQuotaCooldown ?? true) &&
667
- (left.menuShowFetchStatus ?? true) === (right.menuShowFetchStatus ?? true) &&
739
+ (left.menuShowQuotaSummary ?? true) ===
740
+ (right.menuShowQuotaSummary ?? true) &&
741
+ (left.menuShowQuotaCooldown ?? true) ===
742
+ (right.menuShowQuotaCooldown ?? true) &&
743
+ (left.menuShowFetchStatus ?? true) ===
744
+ (right.menuShowFetchStatus ?? true) &&
668
745
  resolveMenuLayoutMode(left) === resolveMenuLayoutMode(right) &&
669
- (left.menuQuotaTtlMs ?? 5 * 60_000) === (right.menuQuotaTtlMs ?? 5 * 60_000) &&
670
- (left.menuFocusStyle ?? "row-invert") === (right.menuFocusStyle ?? "row-invert") &&
671
- (left.menuHighlightCurrentRow ?? true) === (right.menuHighlightCurrentRow ?? true) &&
746
+ (left.menuQuotaTtlMs ?? 5 * 60_000) ===
747
+ (right.menuQuotaTtlMs ?? 5 * 60_000) &&
748
+ (left.menuFocusStyle ?? "row-invert") ===
749
+ (right.menuFocusStyle ?? "row-invert") &&
750
+ (left.menuHighlightCurrentRow ?? true) ===
751
+ (right.menuHighlightCurrentRow ?? true) &&
672
752
  JSON.stringify(normalizeStatuslineFields(left.menuStatuslineFields)) ===
673
753
  JSON.stringify(normalizeStatuslineFields(right.menuStatuslineFields)));
674
754
  }
@@ -685,15 +765,18 @@ function cloneBackendPluginConfig(config) {
685
765
  function backendSettingsSnapshot(config) {
686
766
  const snapshot = {};
687
767
  for (const option of BACKEND_TOGGLE_OPTIONS) {
688
- snapshot[option.key] = config[option.key] ?? BACKEND_DEFAULTS[option.key] ?? false;
768
+ snapshot[option.key] =
769
+ config[option.key] ?? BACKEND_DEFAULTS[option.key] ?? false;
689
770
  }
690
771
  for (const option of BACKEND_NUMBER_OPTIONS) {
691
- snapshot[option.key] = config[option.key] ?? BACKEND_DEFAULTS[option.key] ?? option.min;
772
+ snapshot[option.key] =
773
+ config[option.key] ?? BACKEND_DEFAULTS[option.key] ?? option.min;
692
774
  }
693
775
  return snapshot;
694
776
  }
695
777
  function backendSettingsEqual(left, right) {
696
- return JSON.stringify(backendSettingsSnapshot(left)) === JSON.stringify(backendSettingsSnapshot(right));
778
+ return (JSON.stringify(backendSettingsSnapshot(left)) ===
779
+ JSON.stringify(backendSettingsSnapshot(right)));
697
780
  }
698
781
  function formatBackendNumberValue(option, value) {
699
782
  if (option.unit === "percent")
@@ -714,7 +797,9 @@ function clampBackendNumber(option, value) {
714
797
  function buildBackendSettingsPreview(config, ui, focus = null) {
715
798
  const liveSync = config.liveAccountSync ?? BACKEND_DEFAULTS.liveAccountSync ?? true;
716
799
  const affinity = config.sessionAffinity ?? BACKEND_DEFAULTS.sessionAffinity ?? true;
717
- const preemptive = config.preemptiveQuotaEnabled ?? BACKEND_DEFAULTS.preemptiveQuotaEnabled ?? true;
800
+ const preemptive = config.preemptiveQuotaEnabled ??
801
+ BACKEND_DEFAULTS.preemptiveQuotaEnabled ??
802
+ true;
718
803
  const threshold5h = config.preemptiveQuotaRemainingPercent5h ??
719
804
  BACKEND_DEFAULTS.preemptiveQuotaRemainingPercent5h ??
720
805
  5;
@@ -722,7 +807,9 @@ function buildBackendSettingsPreview(config, ui, focus = null) {
722
807
  BACKEND_DEFAULTS.preemptiveQuotaRemainingPercent7d ??
723
808
  5;
724
809
  const fetchTimeout = config.fetchTimeoutMs ?? BACKEND_DEFAULTS.fetchTimeoutMs ?? 60_000;
725
- const stallTimeout = config.streamStallTimeoutMs ?? BACKEND_DEFAULTS.streamStallTimeoutMs ?? 45_000;
810
+ const stallTimeout = config.streamStallTimeoutMs ??
811
+ BACKEND_DEFAULTS.streamStallTimeoutMs ??
812
+ 45_000;
726
813
  const fetchTimeoutOption = BACKEND_NUMBER_OPTION_BY_KEY.get("fetchTimeoutMs");
727
814
  const stallTimeoutOption = BACKEND_NUMBER_OPTION_BY_KEY.get("streamStallTimeoutMs");
728
815
  const highlightIfFocused = (key, text) => {
@@ -781,7 +868,9 @@ function resolveMenuLayoutMode(settings) {
781
868
  if (settings.menuLayoutMode === "compact-details") {
782
869
  return "compact-details";
783
870
  }
784
- return settings.menuShowDetailsForUnselectedRows === true ? "expanded-rows" : "compact-details";
871
+ return settings.menuShowDetailsForUnselectedRows === true
872
+ ? "expanded-rows"
873
+ : "compact-details";
785
874
  }
786
875
  function formatMenuLayoutMode(mode) {
787
876
  return mode === "expanded-rows" ? "Expanded Rows" : "Compact + Details Pane";
@@ -816,8 +905,19 @@ const __testOnly = {
816
905
  formatMenuLayoutMode,
817
906
  cloneDashboardSettings,
818
907
  withQueuedRetry: withQueuedRetryForTests,
908
+ loadExperimentalSyncTarget,
909
+ promptExperimentalSettings,
819
910
  persistDashboardSettingsSelection: persistDashboardSettingsSelectionForTests,
820
911
  persistBackendConfigSelection: persistBackendConfigSelectionForTests,
912
+ buildAccountListPreview,
913
+ buildSummaryPreviewText,
914
+ normalizeStatuslineFields,
915
+ reorderField,
916
+ promptDashboardDisplaySettings,
917
+ promptStatuslineSettings,
918
+ promptBehaviorSettings,
919
+ promptThemeSettings,
920
+ promptBackendSettings,
821
921
  };
822
922
  /* c8 ignore start - interactive prompt flows are covered by integration tests */
823
923
  async function promptDashboardDisplaySettings(initial) {
@@ -832,7 +932,9 @@ async function promptDashboardDisplaySettings(initial) {
832
932
  const optionItems = DASHBOARD_DISPLAY_OPTIONS.map((option, index) => {
833
933
  const enabled = draft[option.key] ?? true;
834
934
  const label = `${formatDashboardSettingState(enabled)} ${index + 1}. ${option.label}`;
835
- const color = enabled ? "green" : "yellow";
935
+ const color = enabled
936
+ ? "green"
937
+ : "yellow";
836
938
  return {
837
939
  label,
838
940
  hint: option.description,
@@ -840,7 +942,9 @@ async function promptDashboardDisplaySettings(initial) {
840
942
  color,
841
943
  };
842
944
  });
843
- const sortMode = draft.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first");
945
+ const sortMode = draft.menuSortMode ??
946
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ??
947
+ "ready-first";
844
948
  const sortModeItem = {
845
949
  label: `Sort mode: ${formatMenuSortMode(sortMode)}`,
846
950
  hint: "Applies when smart sort is enabled.",
@@ -855,7 +959,11 @@ async function promptDashboardDisplaySettings(initial) {
855
959
  color: layoutMode === "compact-details" ? "green" : "yellow",
856
960
  };
857
961
  const items = [
858
- { label: UI_COPY.settings.previewHeading, value: { type: "cancel" }, kind: "heading" },
962
+ {
963
+ label: UI_COPY.settings.previewHeading,
964
+ value: { type: "cancel" },
965
+ kind: "heading",
966
+ },
859
967
  {
860
968
  label: preview.label,
861
969
  hint: preview.hint,
@@ -865,18 +973,36 @@ async function promptDashboardDisplaySettings(initial) {
865
973
  hideUnavailableSuffix: true,
866
974
  },
867
975
  { label: "", value: { type: "cancel" }, separator: true },
868
- { label: UI_COPY.settings.displayHeading, value: { type: "cancel" }, kind: "heading" },
976
+ {
977
+ label: UI_COPY.settings.displayHeading,
978
+ value: { type: "cancel" },
979
+ kind: "heading",
980
+ },
869
981
  ...optionItems,
870
982
  sortModeItem,
871
983
  layoutModeItem,
872
984
  { label: "", value: { type: "cancel" }, separator: true },
873
- { label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
874
- { label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
875
- { label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
985
+ {
986
+ label: UI_COPY.settings.resetDefault,
987
+ value: { type: "reset" },
988
+ color: "yellow",
989
+ },
990
+ {
991
+ label: UI_COPY.settings.saveAndBack,
992
+ value: { type: "save" },
993
+ color: "green",
994
+ },
995
+ {
996
+ label: UI_COPY.settings.backNoSave,
997
+ value: { type: "cancel" },
998
+ color: "red",
999
+ },
876
1000
  ];
877
1001
  const initialCursor = items.findIndex((item) => (item.value.type === "toggle" && item.value.key === focusKey) ||
878
- (item.value.type === "cycle-sort-mode" && focusKey === "menuSortMode") ||
879
- (item.value.type === "cycle-layout-mode" && focusKey === "menuLayoutMode"));
1002
+ (item.value.type === "cycle-sort-mode" &&
1003
+ focusKey === "menuSortMode") ||
1004
+ (item.value.type === "cycle-layout-mode" &&
1005
+ focusKey === "menuLayoutMode"));
880
1006
  const updateFocusedPreview = (cursor) => {
881
1007
  const focusedItem = items[cursor];
882
1008
  const focusedKey = focusedItem?.value.type === "toggle"
@@ -927,7 +1053,9 @@ async function promptDashboardDisplaySettings(initial) {
927
1053
  if (lower === "l")
928
1054
  return { type: "cycle-layout-mode" };
929
1055
  const parsed = Number.parseInt(raw, 10);
930
- if (Number.isFinite(parsed) && parsed >= 1 && parsed <= DASHBOARD_DISPLAY_OPTIONS.length) {
1056
+ if (Number.isFinite(parsed) &&
1057
+ parsed >= 1 &&
1058
+ parsed <= DASHBOARD_DISPLAY_OPTIONS.length) {
931
1059
  const target = DASHBOARD_DISPLAY_OPTIONS[parsed - 1];
932
1060
  if (target) {
933
1061
  return { type: "toggle", key: target.key };
@@ -954,23 +1082,27 @@ async function promptDashboardDisplaySettings(initial) {
954
1082
  continue;
955
1083
  }
956
1084
  if (result.type === "cycle-sort-mode") {
957
- const currentMode = draft.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first");
958
- const nextMode = currentMode === "ready-first"
959
- ? "manual"
960
- : "ready-first";
1085
+ const currentMode = draft.menuSortMode ??
1086
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ??
1087
+ "ready-first";
1088
+ const nextMode = currentMode === "ready-first" ? "manual" : "ready-first";
961
1089
  draft = {
962
1090
  ...draft,
963
1091
  menuSortMode: nextMode,
964
1092
  menuSortEnabled: nextMode === "ready-first"
965
1093
  ? true
966
- : (draft.menuSortEnabled ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ?? true)),
1094
+ : (draft.menuSortEnabled ??
1095
+ DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ??
1096
+ true),
967
1097
  };
968
1098
  focusKey = "menuSortMode";
969
1099
  continue;
970
1100
  }
971
1101
  if (result.type === "cycle-layout-mode") {
972
1102
  const currentLayout = resolveMenuLayoutMode(draft);
973
- const nextLayout = currentLayout === "compact-details" ? "expanded-rows" : "compact-details";
1103
+ const nextLayout = currentLayout === "compact-details"
1104
+ ? "expanded-rows"
1105
+ : "compact-details";
974
1106
  draft = {
975
1107
  ...draft,
976
1108
  menuLayoutMode: nextLayout,
@@ -987,7 +1119,7 @@ async function promptDashboardDisplaySettings(initial) {
987
1119
  }
988
1120
  }
989
1121
  async function configureDashboardDisplaySettings(currentSettings) {
990
- const current = currentSettings ?? await loadDashboardDisplaySettings();
1122
+ const current = currentSettings ?? (await loadDashboardDisplaySettings());
991
1123
  if (!input.isTTY || !output.isTTY) {
992
1124
  console.log("Settings require interactive mode.");
993
1125
  console.log(`Settings file: ${getDashboardSettingsPath()}`);
@@ -1047,7 +1179,11 @@ async function promptStatuslineSettings(initial) {
1047
1179
  };
1048
1180
  });
1049
1181
  const items = [
1050
- { label: UI_COPY.settings.previewHeading, value: { type: "cancel" }, kind: "heading" },
1182
+ {
1183
+ label: UI_COPY.settings.previewHeading,
1184
+ value: { type: "cancel" },
1185
+ kind: "heading",
1186
+ },
1051
1187
  {
1052
1188
  label: preview.label,
1053
1189
  hint: preview.hint,
@@ -1057,15 +1193,39 @@ async function promptStatuslineSettings(initial) {
1057
1193
  hideUnavailableSuffix: true,
1058
1194
  },
1059
1195
  { label: "", value: { type: "cancel" }, separator: true },
1060
- { label: UI_COPY.settings.displayHeading, value: { type: "cancel" }, kind: "heading" },
1196
+ {
1197
+ label: UI_COPY.settings.displayHeading,
1198
+ value: { type: "cancel" },
1199
+ kind: "heading",
1200
+ },
1061
1201
  ...optionItems,
1062
1202
  { label: "", value: { type: "cancel" }, separator: true },
1063
- { label: UI_COPY.settings.moveUp, value: { type: "move-up", key: focusKey }, color: "green" },
1064
- { label: UI_COPY.settings.moveDown, value: { type: "move-down", key: focusKey }, color: "green" },
1203
+ {
1204
+ label: UI_COPY.settings.moveUp,
1205
+ value: { type: "move-up", key: focusKey },
1206
+ color: "green",
1207
+ },
1208
+ {
1209
+ label: UI_COPY.settings.moveDown,
1210
+ value: { type: "move-down", key: focusKey },
1211
+ color: "green",
1212
+ },
1065
1213
  { label: "", value: { type: "cancel" }, separator: true },
1066
- { label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
1067
- { label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
1068
- { label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
1214
+ {
1215
+ label: UI_COPY.settings.resetDefault,
1216
+ value: { type: "reset" },
1217
+ color: "yellow",
1218
+ },
1219
+ {
1220
+ label: UI_COPY.settings.saveAndBack,
1221
+ value: { type: "save" },
1222
+ color: "green",
1223
+ },
1224
+ {
1225
+ label: UI_COPY.settings.backNoSave,
1226
+ value: { type: "cancel" },
1227
+ color: "red",
1228
+ },
1069
1229
  ];
1070
1230
  const initialCursor = items.findIndex((item) => item.value.type === "toggle" && item.value.key === focusKey);
1071
1231
  const updateFocusedPreview = (cursor) => {
@@ -1106,7 +1266,9 @@ async function promptStatuslineSettings(initial) {
1106
1266
  if (lower === "]")
1107
1267
  return { type: "move-down", key: focusKey };
1108
1268
  const parsed = Number.parseInt(raw, 10);
1109
- if (Number.isFinite(parsed) && parsed >= 1 && parsed <= STATUSLINE_FIELD_OPTIONS.length) {
1269
+ if (Number.isFinite(parsed) &&
1270
+ parsed >= 1 &&
1271
+ parsed <= STATUSLINE_FIELD_OPTIONS.length) {
1110
1272
  const target = STATUSLINE_FIELD_OPTIONS[parsed - 1];
1111
1273
  if (target) {
1112
1274
  return { type: "toggle", key: target.key };
@@ -1161,7 +1323,7 @@ async function promptStatuslineSettings(initial) {
1161
1323
  }
1162
1324
  }
1163
1325
  async function configureStatuslineSettings(currentSettings) {
1164
- const current = currentSettings ?? await loadDashboardDisplaySettings();
1326
+ const current = currentSettings ?? (await loadDashboardDisplaySettings());
1165
1327
  if (!input.isTTY || !output.isTTY) {
1166
1328
  console.log("Settings require interactive mode.");
1167
1329
  console.log(`Settings file: ${getDashboardSettingsPath()}`);
@@ -1177,7 +1339,9 @@ async function configureStatuslineSettings(currentSettings) {
1177
1339
  return merged;
1178
1340
  }
1179
1341
  function formatDelayLabel(delayMs) {
1180
- return delayMs <= 0 ? "Instant return" : `${Math.round(delayMs / 1000)}s auto-return`;
1342
+ return delayMs <= 0
1343
+ ? "Instant return"
1344
+ : `${Math.round(delayMs / 1000)}s auto-return`;
1181
1345
  }
1182
1346
  async function promptBehaviorSettings(initial) {
1183
1347
  if (!input.isTTY || !output.isTTY)
@@ -1207,9 +1371,15 @@ async function promptBehaviorSettings(initial) {
1207
1371
  color,
1208
1372
  };
1209
1373
  });
1210
- const pauseColor = pauseOnKey ? "green" : "yellow";
1374
+ const pauseColor = pauseOnKey
1375
+ ? "green"
1376
+ : "yellow";
1211
1377
  const items = [
1212
- { label: UI_COPY.settings.actionTiming, value: { type: "cancel" }, kind: "heading" },
1378
+ {
1379
+ label: UI_COPY.settings.actionTiming,
1380
+ value: { type: "cancel" },
1381
+ kind: "heading",
1382
+ },
1213
1383
  ...delayItems,
1214
1384
  { label: "", value: { type: "cancel" }, separator: true },
1215
1385
  {
@@ -1237,9 +1407,21 @@ async function promptBehaviorSettings(initial) {
1237
1407
  color: "yellow",
1238
1408
  },
1239
1409
  { label: "", value: { type: "cancel" }, separator: true },
1240
- { label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
1241
- { label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
1242
- { label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
1410
+ {
1411
+ label: UI_COPY.settings.resetDefault,
1412
+ value: { type: "reset" },
1413
+ color: "yellow",
1414
+ },
1415
+ {
1416
+ label: UI_COPY.settings.saveAndBack,
1417
+ value: { type: "save" },
1418
+ color: "green",
1419
+ },
1420
+ {
1421
+ label: UI_COPY.settings.backNoSave,
1422
+ value: { type: "cancel" },
1423
+ color: "red",
1424
+ },
1243
1425
  ];
1244
1426
  const initialCursor = items.findIndex((item) => {
1245
1427
  const value = item.value;
@@ -1281,7 +1463,9 @@ async function promptBehaviorSettings(initial) {
1281
1463
  if (lower === "t")
1282
1464
  return { type: "set-menu-quota-ttl", ttlMs: menuQuotaTtlMs };
1283
1465
  const parsed = Number.parseInt(raw, 10);
1284
- if (Number.isFinite(parsed) && parsed >= 1 && parsed <= AUTO_RETURN_OPTIONS_MS.length) {
1466
+ if (Number.isFinite(parsed) &&
1467
+ parsed >= 1 &&
1468
+ parsed <= AUTO_RETURN_OPTIONS_MS.length) {
1285
1469
  const delayMs = AUTO_RETURN_OPTIONS_MS[parsed - 1];
1286
1470
  if (typeof delayMs === "number")
1287
1471
  return { type: "set-delay", delayMs };
@@ -1327,7 +1511,9 @@ async function promptBehaviorSettings(initial) {
1327
1511
  const nextIndex = currentIndex < 0
1328
1512
  ? 0
1329
1513
  : (currentIndex + 1) % MENU_QUOTA_TTL_OPTIONS_MS.length;
1330
- const nextTtl = MENU_QUOTA_TTL_OPTIONS_MS[nextIndex] ?? MENU_QUOTA_TTL_OPTIONS_MS[0] ?? menuQuotaTtlMs;
1514
+ const nextTtl = MENU_QUOTA_TTL_OPTIONS_MS[nextIndex] ??
1515
+ MENU_QUOTA_TTL_OPTIONS_MS[0] ??
1516
+ menuQuotaTtlMs;
1331
1517
  draft = {
1332
1518
  ...draft,
1333
1519
  menuQuotaTtlMs: nextTtl,
@@ -1359,7 +1545,9 @@ async function promptThemeSettings(initial) {
1359
1545
  const color = palette === candidate ? "green" : "yellow";
1360
1546
  return {
1361
1547
  label: `${palette === candidate ? "[x]" : "[ ]"} ${index + 1}. ${candidate === "green" ? "Green base" : "Blue base"}`,
1362
- hint: candidate === "green" ? "High-contrast default." : "Codex-style blue look.",
1548
+ hint: candidate === "green"
1549
+ ? "High-contrast default."
1550
+ : "Codex-style blue look.",
1363
1551
  value: { type: "set-palette", palette: candidate },
1364
1552
  color,
1365
1553
  };
@@ -1373,15 +1561,35 @@ async function promptThemeSettings(initial) {
1373
1561
  };
1374
1562
  });
1375
1563
  const items = [
1376
- { label: UI_COPY.settings.baseTheme, value: { type: "cancel" }, kind: "heading" },
1564
+ {
1565
+ label: UI_COPY.settings.baseTheme,
1566
+ value: { type: "cancel" },
1567
+ kind: "heading",
1568
+ },
1377
1569
  ...paletteItems,
1378
1570
  { label: "", value: { type: "cancel" }, separator: true },
1379
- { label: UI_COPY.settings.accentColor, value: { type: "cancel" }, kind: "heading" },
1571
+ {
1572
+ label: UI_COPY.settings.accentColor,
1573
+ value: { type: "cancel" },
1574
+ kind: "heading",
1575
+ },
1380
1576
  ...accentItems,
1381
1577
  { label: "", value: { type: "cancel" }, separator: true },
1382
- { label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
1383
- { label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
1384
- { label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
1578
+ {
1579
+ label: UI_COPY.settings.resetDefault,
1580
+ value: { type: "reset" },
1581
+ color: "yellow",
1582
+ },
1583
+ {
1584
+ label: UI_COPY.settings.saveAndBack,
1585
+ value: { type: "save" },
1586
+ color: "green",
1587
+ },
1588
+ {
1589
+ label: UI_COPY.settings.backNoSave,
1590
+ value: { type: "cancel" },
1591
+ color: "red",
1592
+ },
1385
1593
  ];
1386
1594
  const initialCursor = items.findIndex((item) => {
1387
1595
  const value = item.value;
@@ -1455,7 +1663,7 @@ function resolveFocusedBackendNumberKey(focus, numberOptions = BACKEND_NUMBER_OP
1455
1663
  return numberOptions[0]?.key ?? "fetchTimeoutMs";
1456
1664
  }
1457
1665
  function getBackendCategory(key) {
1458
- return BACKEND_CATEGORY_OPTIONS.find((category) => category.key === key) ?? null;
1666
+ return (BACKEND_CATEGORY_OPTIONS.find((category) => category.key === key) ?? null);
1459
1667
  }
1460
1668
  function getBackendCategoryInitialFocus(category) {
1461
1669
  const firstToggle = category.toggleKeys[0];
@@ -1517,7 +1725,11 @@ async function promptBackendCategorySettings(initial, category, initialFocus) {
1517
1725
  });
1518
1726
  const focusedNumberKey = resolveFocusedBackendNumberKey(focusKey, numberOptions);
1519
1727
  const items = [
1520
- { label: UI_COPY.settings.previewHeading, value: { type: "back" }, kind: "heading" },
1728
+ {
1729
+ label: UI_COPY.settings.previewHeading,
1730
+ value: { type: "back" },
1731
+ kind: "heading",
1732
+ },
1521
1733
  {
1522
1734
  label: preview.label,
1523
1735
  hint: preview.hint,
@@ -1527,10 +1739,18 @@ async function promptBackendCategorySettings(initial, category, initialFocus) {
1527
1739
  hideUnavailableSuffix: true,
1528
1740
  },
1529
1741
  { label: "", value: { type: "back" }, separator: true },
1530
- { label: UI_COPY.settings.backendToggleHeading, value: { type: "back" }, kind: "heading" },
1742
+ {
1743
+ label: UI_COPY.settings.backendToggleHeading,
1744
+ value: { type: "back" },
1745
+ kind: "heading",
1746
+ },
1531
1747
  ...toggleItems,
1532
1748
  { label: "", value: { type: "back" }, separator: true },
1533
- { label: UI_COPY.settings.backendNumberHeading, value: { type: "back" }, kind: "heading" },
1749
+ {
1750
+ label: UI_COPY.settings.backendNumberHeading,
1751
+ value: { type: "back" },
1752
+ kind: "heading",
1753
+ },
1534
1754
  ...numberItems,
1535
1755
  ];
1536
1756
  if (numberOptions.length > 0) {
@@ -1547,8 +1767,16 @@ async function promptBackendCategorySettings(initial, category, initialFocus) {
1547
1767
  });
1548
1768
  }
1549
1769
  items.push({ label: "", value: { type: "back" }, separator: true });
1550
- items.push({ label: UI_COPY.settings.backendResetCategory, value: { type: "reset-category" }, color: "yellow" });
1551
- items.push({ label: UI_COPY.settings.backendBackToCategories, value: { type: "back" }, color: "red" });
1770
+ items.push({
1771
+ label: UI_COPY.settings.backendResetCategory,
1772
+ value: { type: "reset-category" },
1773
+ color: "yellow",
1774
+ });
1775
+ items.push({
1776
+ label: UI_COPY.settings.backendBackToCategories,
1777
+ value: { type: "back" },
1778
+ color: "red",
1779
+ });
1552
1780
  const initialCursor = items.findIndex((item) => {
1553
1781
  if (item.separator || item.disabled || item.kind === "heading")
1554
1782
  return false;
@@ -1568,7 +1796,8 @@ async function promptBackendCategorySettings(initial, category, initialFocus) {
1568
1796
  initialCursor: initialCursor >= 0 ? initialCursor : undefined,
1569
1797
  onCursorChange: ({ cursor }) => {
1570
1798
  const focusedItem = items[cursor];
1571
- if (focusedItem?.value.type === "toggle" || focusedItem?.value.type === "bump") {
1799
+ if (focusedItem?.value.type === "toggle" ||
1800
+ focusedItem?.value.type === "bump") {
1572
1801
  focusKey = focusedItem.value.key;
1573
1802
  }
1574
1803
  },
@@ -1578,14 +1807,26 @@ async function promptBackendCategorySettings(initial, category, initialFocus) {
1578
1807
  return { type: "back" };
1579
1808
  if (lower === "r")
1580
1809
  return { type: "reset-category" };
1581
- if (numberOptions.length > 0 && (lower === "+" || lower === "=" || lower === "]" || lower === "d")) {
1582
- return { type: "bump", key: resolveFocusedBackendNumberKey(focusKey, numberOptions), direction: 1 };
1810
+ if (numberOptions.length > 0 &&
1811
+ (lower === "+" || lower === "=" || lower === "]" || lower === "d")) {
1812
+ return {
1813
+ type: "bump",
1814
+ key: resolveFocusedBackendNumberKey(focusKey, numberOptions),
1815
+ direction: 1,
1816
+ };
1583
1817
  }
1584
- if (numberOptions.length > 0 && (lower === "-" || lower === "[" || lower === "a")) {
1585
- return { type: "bump", key: resolveFocusedBackendNumberKey(focusKey, numberOptions), direction: -1 };
1818
+ if (numberOptions.length > 0 &&
1819
+ (lower === "-" || lower === "[" || lower === "a")) {
1820
+ return {
1821
+ type: "bump",
1822
+ key: resolveFocusedBackendNumberKey(focusKey, numberOptions),
1823
+ direction: -1,
1824
+ };
1586
1825
  }
1587
1826
  const parsed = Number.parseInt(raw, 10);
1588
- if (Number.isFinite(parsed) && parsed >= 1 && parsed <= toggleOptions.length) {
1827
+ if (Number.isFinite(parsed) &&
1828
+ parsed >= 1 &&
1829
+ parsed <= toggleOptions.length) {
1589
1830
  const target = toggleOptions[parsed - 1];
1590
1831
  if (target)
1591
1832
  return { type: "toggle", key: target.key };
@@ -1643,7 +1884,11 @@ async function promptBackendSettings(initial) {
1643
1884
  };
1644
1885
  });
1645
1886
  const items = [
1646
- { label: UI_COPY.settings.previewHeading, value: { type: "cancel" }, kind: "heading" },
1887
+ {
1888
+ label: UI_COPY.settings.previewHeading,
1889
+ value: { type: "cancel" },
1890
+ kind: "heading",
1891
+ },
1647
1892
  {
1648
1893
  label: preview.label,
1649
1894
  hint: preview.hint,
@@ -1653,17 +1898,33 @@ async function promptBackendSettings(initial) {
1653
1898
  hideUnavailableSuffix: true,
1654
1899
  },
1655
1900
  { label: "", value: { type: "cancel" }, separator: true },
1656
- { label: UI_COPY.settings.backendCategoriesHeading, value: { type: "cancel" }, kind: "heading" },
1901
+ {
1902
+ label: UI_COPY.settings.backendCategoriesHeading,
1903
+ value: { type: "cancel" },
1904
+ kind: "heading",
1905
+ },
1657
1906
  ...categoryItems,
1658
1907
  { label: "", value: { type: "cancel" }, separator: true },
1659
- { label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
1660
- { label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
1661
- { label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
1908
+ {
1909
+ label: UI_COPY.settings.resetDefault,
1910
+ value: { type: "reset" },
1911
+ color: "yellow",
1912
+ },
1913
+ {
1914
+ label: UI_COPY.settings.saveAndBack,
1915
+ value: { type: "save" },
1916
+ color: "green",
1917
+ },
1918
+ {
1919
+ label: UI_COPY.settings.backNoSave,
1920
+ value: { type: "cancel" },
1921
+ color: "red",
1922
+ },
1662
1923
  ];
1663
1924
  const initialCursor = items.findIndex((item) => {
1664
1925
  if (item.separator || item.disabled || item.kind === "heading")
1665
1926
  return false;
1666
- return item.value.type === "open-category" && item.value.key === activeCategory;
1927
+ return (item.value.type === "open-category" && item.value.key === activeCategory);
1667
1928
  });
1668
1929
  const result = await select(items, {
1669
1930
  message: UI_COPY.settings.backendTitle,
@@ -1688,7 +1949,9 @@ async function promptBackendSettings(initial) {
1688
1949
  if (lower === "r")
1689
1950
  return { type: "reset" };
1690
1951
  const parsed = Number.parseInt(raw, 10);
1691
- if (Number.isFinite(parsed) && parsed >= 1 && parsed <= BACKEND_CATEGORY_OPTIONS.length) {
1952
+ if (Number.isFinite(parsed) &&
1953
+ parsed >= 1 &&
1954
+ parsed <= BACKEND_CATEGORY_OPTIONS.length) {
1692
1955
  const target = BACKEND_CATEGORY_OPTIONS[parsed - 1];
1693
1956
  if (target)
1694
1957
  return { type: "open-category", key: target.key };
@@ -1703,7 +1966,8 @@ async function promptBackendSettings(initial) {
1703
1966
  if (result.type === "reset") {
1704
1967
  draft = cloneBackendPluginConfig(BACKEND_DEFAULTS);
1705
1968
  for (const category of BACKEND_CATEGORY_OPTIONS) {
1706
- focusByCategory[category.key] = getBackendCategoryInitialFocus(category);
1969
+ focusByCategory[category.key] =
1970
+ getBackendCategoryInitialFocus(category);
1707
1971
  }
1708
1972
  activeCategory = BACKEND_CATEGORY_OPTIONS[0]?.key ?? activeCategory;
1709
1973
  continue;
@@ -1717,6 +1981,328 @@ async function promptBackendSettings(initial) {
1717
1981
  focusByCategory[category.key] = categoryResult.focusKey;
1718
1982
  }
1719
1983
  }
1984
+ async function loadExperimentalSyncTarget() {
1985
+ const detection = detectOcChatgptMultiAuthTarget();
1986
+ if (detection.kind === "ambiguous") {
1987
+ return { kind: "blocked-ambiguous", detection };
1988
+ }
1989
+ if (detection.kind === "none") {
1990
+ return { kind: "blocked-none", detection };
1991
+ }
1992
+ try {
1993
+ const raw = JSON.parse(await readFileWithRetry(detection.descriptor.accountPath));
1994
+ const normalized = normalizeAccountStorage(raw);
1995
+ if (!normalized) {
1996
+ return {
1997
+ kind: "error",
1998
+ message: "Invalid target account storage format",
1999
+ };
2000
+ }
2001
+ return { kind: "target", detection, destination: normalized };
2002
+ }
2003
+ catch (error) {
2004
+ const code = error.code;
2005
+ if (code === "ENOENT") {
2006
+ return { kind: "target", detection, destination: null };
2007
+ }
2008
+ return {
2009
+ kind: "error",
2010
+ message: error instanceof Error ? error.message : String(error),
2011
+ };
2012
+ }
2013
+ }
2014
+ async function promptExperimentalSettings(initialConfig) {
2015
+ if (!input.isTTY || !output.isTTY)
2016
+ return null;
2017
+ const ui = getUiRuntimeOptions();
2018
+ let draft = cloneBackendPluginConfig(initialConfig);
2019
+ while (true) {
2020
+ const action = await select([
2021
+ {
2022
+ label: UI_COPY.settings.experimentalSync,
2023
+ value: { type: "sync" },
2024
+ color: "yellow",
2025
+ },
2026
+ {
2027
+ label: UI_COPY.settings.experimentalBackup,
2028
+ value: { type: "backup" },
2029
+ color: "green",
2030
+ },
2031
+ {
2032
+ label: `${formatDashboardSettingState(draft.proactiveRefreshGuardian ?? false)} ${UI_COPY.settings.experimentalRefreshGuard}`,
2033
+ value: { type: "toggle-refresh-guardian" },
2034
+ color: "yellow",
2035
+ },
2036
+ {
2037
+ label: `${UI_COPY.settings.experimentalRefreshInterval}: ${Math.round((draft.proactiveRefreshIntervalMs ?? 60000) / 60000)} min`,
2038
+ value: { type: "back" },
2039
+ disabled: true,
2040
+ hideUnavailableSuffix: true,
2041
+ color: "green",
2042
+ },
2043
+ {
2044
+ label: UI_COPY.settings.experimentalDecreaseInterval,
2045
+ value: { type: "decrease-refresh-interval" },
2046
+ color: "yellow",
2047
+ },
2048
+ {
2049
+ label: UI_COPY.settings.experimentalIncreaseInterval,
2050
+ value: { type: "increase-refresh-interval" },
2051
+ color: "green",
2052
+ },
2053
+ {
2054
+ label: UI_COPY.settings.saveAndBack,
2055
+ value: { type: "save" },
2056
+ color: "green",
2057
+ },
2058
+ {
2059
+ label: UI_COPY.settings.backNoSave,
2060
+ value: { type: "back" },
2061
+ color: "red",
2062
+ },
2063
+ ], {
2064
+ message: UI_COPY.settings.experimentalTitle,
2065
+ subtitle: UI_COPY.settings.experimentalSubtitle,
2066
+ help: UI_COPY.settings.experimentalHelpMenu,
2067
+ clearScreen: true,
2068
+ theme: ui.theme,
2069
+ selectedEmphasis: "minimal",
2070
+ onInput: (raw) => raw.toLowerCase() === "q" ? { type: "back" } : undefined,
2071
+ });
2072
+ if (!action || action.type === "back")
2073
+ return null;
2074
+ if (action.type === "save")
2075
+ return draft;
2076
+ if (action.type === "toggle-refresh-guardian") {
2077
+ draft = {
2078
+ ...draft,
2079
+ proactiveRefreshGuardian: !(draft.proactiveRefreshGuardian ?? false),
2080
+ };
2081
+ continue;
2082
+ }
2083
+ if (action.type === "decrease-refresh-interval") {
2084
+ draft = {
2085
+ ...draft,
2086
+ proactiveRefreshIntervalMs: Math.max(60_000, (draft.proactiveRefreshIntervalMs ?? 60000) - 60000),
2087
+ };
2088
+ continue;
2089
+ }
2090
+ if (action.type === "increase-refresh-interval") {
2091
+ draft = {
2092
+ ...draft,
2093
+ proactiveRefreshIntervalMs: Math.min(600000, (draft.proactiveRefreshIntervalMs ?? 60000) + 60000),
2094
+ };
2095
+ continue;
2096
+ }
2097
+ if (action.type === "backup") {
2098
+ const prompt = createInterface({ input, output });
2099
+ try {
2100
+ const backupName = (await prompt.question(UI_COPY.settings.experimentalBackupPrompt)).trim();
2101
+ if (!backupName || backupName.toLowerCase() === "q") {
2102
+ continue;
2103
+ }
2104
+ try {
2105
+ const backupResult = await runNamedBackupExport({ name: backupName });
2106
+ const backupLabel = backupResult.kind === "exported"
2107
+ ? `Saved backup to ${backupResult.path}`
2108
+ : backupResult.kind === "collision"
2109
+ ? `Backup already exists: ${backupResult.path}`
2110
+ : backupResult.error instanceof Error
2111
+ ? backupResult.error.message
2112
+ : String(backupResult.error);
2113
+ await select([
2114
+ {
2115
+ label: backupLabel,
2116
+ value: { type: "back" },
2117
+ disabled: true,
2118
+ hideUnavailableSuffix: true,
2119
+ color: backupResult.kind === "exported" ? "green" : "yellow",
2120
+ },
2121
+ {
2122
+ label: UI_COPY.settings.back,
2123
+ value: { type: "back" },
2124
+ color: "red",
2125
+ },
2126
+ ], {
2127
+ message: UI_COPY.settings.experimentalTitle,
2128
+ subtitle: UI_COPY.settings.experimentalSubtitle,
2129
+ help: UI_COPY.settings.experimentalHelpStatus,
2130
+ clearScreen: true,
2131
+ theme: ui.theme,
2132
+ selectedEmphasis: "minimal",
2133
+ });
2134
+ }
2135
+ catch (error) {
2136
+ const message = error instanceof Error ? error.message : String(error);
2137
+ await select([
2138
+ {
2139
+ label: message,
2140
+ value: { type: "back" },
2141
+ disabled: true,
2142
+ hideUnavailableSuffix: true,
2143
+ color: "yellow",
2144
+ },
2145
+ {
2146
+ label: UI_COPY.settings.back,
2147
+ value: { type: "back" },
2148
+ color: "red",
2149
+ },
2150
+ ], {
2151
+ message: UI_COPY.settings.experimentalTitle,
2152
+ subtitle: UI_COPY.settings.experimentalSubtitle,
2153
+ help: UI_COPY.settings.experimentalHelpStatus,
2154
+ clearScreen: true,
2155
+ theme: ui.theme,
2156
+ selectedEmphasis: "minimal",
2157
+ });
2158
+ }
2159
+ }
2160
+ finally {
2161
+ prompt.close();
2162
+ }
2163
+ continue;
2164
+ }
2165
+ const source = await loadAccounts();
2166
+ const targetState = await loadExperimentalSyncTarget();
2167
+ if (targetState.kind === "error") {
2168
+ await select([
2169
+ {
2170
+ label: targetState.message,
2171
+ value: { type: "back" },
2172
+ disabled: true,
2173
+ hideUnavailableSuffix: true,
2174
+ color: "yellow",
2175
+ },
2176
+ {
2177
+ label: UI_COPY.settings.back,
2178
+ value: { type: "back" },
2179
+ color: "red",
2180
+ },
2181
+ ], {
2182
+ message: UI_COPY.settings.experimentalTitle,
2183
+ subtitle: UI_COPY.settings.experimentalSubtitle,
2184
+ help: UI_COPY.settings.experimentalHelpStatus,
2185
+ clearScreen: true,
2186
+ theme: ui.theme,
2187
+ selectedEmphasis: "minimal",
2188
+ });
2189
+ continue;
2190
+ }
2191
+ const plan = await planOcChatgptSync({
2192
+ source,
2193
+ destination: targetState.kind === "target" ? targetState.destination : null,
2194
+ dependencies: targetState.kind === "target"
2195
+ ? { detectTarget: () => targetState.detection }
2196
+ : undefined,
2197
+ });
2198
+ if (plan.kind !== "ready") {
2199
+ await select([
2200
+ {
2201
+ label: plan.kind === "blocked-ambiguous"
2202
+ ? `Sync blocked: ${plan.detection.reason}`
2203
+ : `Sync unavailable: ${plan.detection.reason}`,
2204
+ value: { type: "back" },
2205
+ disabled: true,
2206
+ hideUnavailableSuffix: true,
2207
+ color: "yellow",
2208
+ },
2209
+ {
2210
+ label: UI_COPY.settings.back,
2211
+ value: { type: "back" },
2212
+ color: "red",
2213
+ },
2214
+ ], {
2215
+ message: UI_COPY.settings.experimentalTitle,
2216
+ subtitle: UI_COPY.settings.experimentalSubtitle,
2217
+ help: UI_COPY.settings.experimentalHelpStatus,
2218
+ clearScreen: true,
2219
+ theme: ui.theme,
2220
+ selectedEmphasis: "minimal",
2221
+ });
2222
+ continue;
2223
+ }
2224
+ const review = await select([
2225
+ {
2226
+ label: `Preview: add ${plan.preview.toAdd.length} | update ${plan.preview.toUpdate.length} | skip ${plan.preview.toSkip.length}`,
2227
+ value: { type: "back" },
2228
+ disabled: true,
2229
+ hideUnavailableSuffix: true,
2230
+ color: "green",
2231
+ },
2232
+ {
2233
+ label: `Preserve destination-only: ${plan.preview.unchangedDestinationOnly.length}`,
2234
+ value: { type: "back" },
2235
+ disabled: true,
2236
+ hideUnavailableSuffix: true,
2237
+ color: "green",
2238
+ },
2239
+ {
2240
+ label: `Active selection: ${plan.preview.activeSelectionBehavior}`,
2241
+ value: { type: "back" },
2242
+ disabled: true,
2243
+ hideUnavailableSuffix: true,
2244
+ color: "green",
2245
+ },
2246
+ {
2247
+ label: UI_COPY.settings.experimentalApplySync,
2248
+ value: { type: "apply" },
2249
+ color: "green",
2250
+ },
2251
+ {
2252
+ label: UI_COPY.settings.backNoSave,
2253
+ value: { type: "back" },
2254
+ color: "red",
2255
+ },
2256
+ ], {
2257
+ message: UI_COPY.settings.experimentalTitle,
2258
+ subtitle: UI_COPY.settings.experimentalSubtitle,
2259
+ help: UI_COPY.settings.experimentalHelpStatus,
2260
+ clearScreen: true,
2261
+ theme: ui.theme,
2262
+ selectedEmphasis: "minimal",
2263
+ onInput: (raw) => {
2264
+ const lower = raw.toLowerCase();
2265
+ if (lower === "q")
2266
+ return { type: "back" };
2267
+ if (lower === "a")
2268
+ return { type: "apply" };
2269
+ return undefined;
2270
+ },
2271
+ });
2272
+ if (!review || review.type === "back")
2273
+ continue;
2274
+ const applied = await applyOcChatgptSync({
2275
+ source,
2276
+ destination: targetState.kind === "target" ? targetState.destination : undefined,
2277
+ dependencies: targetState.kind === "target"
2278
+ ? { detectTarget: () => targetState.detection }
2279
+ : undefined,
2280
+ });
2281
+ await select([
2282
+ {
2283
+ label: applied.kind === "applied"
2284
+ ? `Applied sync to ${applied.target.accountPath}`
2285
+ : applied.kind === "error"
2286
+ ? applied.error instanceof Error
2287
+ ? applied.error.message
2288
+ : String(applied.error)
2289
+ : "Sync did not apply",
2290
+ value: { type: "back" },
2291
+ disabled: true,
2292
+ hideUnavailableSuffix: true,
2293
+ color: applied.kind === "applied" ? "green" : "yellow",
2294
+ },
2295
+ { label: UI_COPY.settings.back, value: { type: "back" }, color: "red" },
2296
+ ], {
2297
+ message: UI_COPY.settings.experimentalTitle,
2298
+ subtitle: UI_COPY.settings.experimentalSubtitle,
2299
+ help: UI_COPY.settings.experimentalHelpStatus,
2300
+ clearScreen: true,
2301
+ theme: ui.theme,
2302
+ selectedEmphasis: "minimal",
2303
+ });
2304
+ }
2305
+ }
1720
2306
  async function configureBackendSettings(currentConfig) {
1721
2307
  const current = cloneBackendPluginConfig(currentConfig ?? loadPluginConfig());
1722
2308
  if (!input.isTTY || !output.isTTY) {
@@ -1735,16 +2321,49 @@ async function promptSettingsHub(initialFocus = "account-list") {
1735
2321
  return null;
1736
2322
  const ui = getUiRuntimeOptions();
1737
2323
  const items = [
1738
- { label: UI_COPY.settings.sectionTitle, value: { type: "back" }, kind: "heading" },
1739
- { label: UI_COPY.settings.accountList, value: { type: "account-list" }, color: "green" },
1740
- { label: UI_COPY.settings.summaryFields, value: { type: "summary-fields" }, color: "green" },
1741
- { label: UI_COPY.settings.behavior, value: { type: "behavior" }, color: "green" },
2324
+ {
2325
+ label: UI_COPY.settings.sectionTitle,
2326
+ value: { type: "back" },
2327
+ kind: "heading",
2328
+ },
2329
+ {
2330
+ label: UI_COPY.settings.accountList,
2331
+ value: { type: "account-list" },
2332
+ color: "green",
2333
+ },
2334
+ {
2335
+ label: UI_COPY.settings.summaryFields,
2336
+ value: { type: "summary-fields" },
2337
+ color: "green",
2338
+ },
2339
+ {
2340
+ label: UI_COPY.settings.behavior,
2341
+ value: { type: "behavior" },
2342
+ color: "green",
2343
+ },
1742
2344
  { label: UI_COPY.settings.theme, value: { type: "theme" }, color: "green" },
1743
2345
  { label: "", value: { type: "back" }, separator: true },
1744
- { label: UI_COPY.settings.advancedTitle, value: { type: "back" }, kind: "heading" },
1745
- { label: UI_COPY.settings.backend, value: { type: "backend" }, color: "green" },
2346
+ {
2347
+ label: UI_COPY.settings.advancedTitle,
2348
+ value: { type: "back" },
2349
+ kind: "heading",
2350
+ },
2351
+ {
2352
+ label: UI_COPY.settings.experimental,
2353
+ value: { type: "experimental" },
2354
+ color: "yellow",
2355
+ },
2356
+ {
2357
+ label: UI_COPY.settings.backend,
2358
+ value: { type: "backend" },
2359
+ color: "green",
2360
+ },
1746
2361
  { label: "", value: { type: "back" }, separator: true },
1747
- { label: UI_COPY.settings.exitTitle, value: { type: "back" }, kind: "heading" },
2362
+ {
2363
+ label: UI_COPY.settings.exitTitle,
2364
+ value: { type: "back" },
2365
+ kind: "heading",
2366
+ },
1748
2367
  { label: UI_COPY.settings.back, value: { type: "back" }, color: "red" },
1749
2368
  ];
1750
2369
  const initialCursor = items.findIndex((item) => {
@@ -1770,7 +2389,7 @@ async function promptSettingsHub(initialFocus = "account-list") {
1770
2389
  }
1771
2390
  /* c8 ignore stop */
1772
2391
  async function configureUnifiedSettings(initialSettings) {
1773
- let current = cloneDashboardSettings(initialSettings ?? await loadDashboardDisplaySettings());
2392
+ let current = cloneDashboardSettings(initialSettings ?? (await loadDashboardDisplaySettings()));
1774
2393
  let backendConfig = cloneBackendPluginConfig(loadPluginConfig());
1775
2394
  applyUiThemeFromDashboardSettings(current);
1776
2395
  let hubFocus = "account-list";
@@ -1803,10 +2422,20 @@ async function configureUnifiedSettings(initialSettings) {
1803
2422
  }
1804
2423
  continue;
1805
2424
  }
2425
+ if (action.type === "experimental") {
2426
+ const selected = await promptExperimentalSettings(backendConfig);
2427
+ if (selected && !backendSettingsEqual(backendConfig, selected)) {
2428
+ backendConfig = await persistBackendConfigSelection(selected, "experimental");
2429
+ }
2430
+ else if (selected) {
2431
+ backendConfig = selected;
2432
+ }
2433
+ continue;
2434
+ }
1806
2435
  if (action.type === "backend") {
1807
2436
  backendConfig = await configureBackendSettings(backendConfig);
1808
2437
  }
1809
2438
  }
1810
2439
  }
1811
- export { configureUnifiedSettings, applyUiThemeFromDashboardSettings, resolveMenuLayoutMode, __testOnly };
2440
+ export { configureUnifiedSettings, applyUiThemeFromDashboardSettings, resolveMenuLayoutMode, __testOnly, };
1812
2441
  //# sourceMappingURL=settings-hub.js.map