codex-multi-auth 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -10
  3. package/assets/readme-hero.svg +31 -31
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +51 -13
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/auth/auth.d.ts +6 -2
  8. package/dist/lib/auth/auth.d.ts.map +1 -1
  9. package/dist/lib/auth/auth.js +34 -11
  10. package/dist/lib/auth/auth.js.map +1 -1
  11. package/dist/lib/auth/server.d.ts +1 -1
  12. package/dist/lib/auth/server.js +3 -3
  13. package/dist/lib/codex-cli/state.d.ts +9 -0
  14. package/dist/lib/codex-cli/state.d.ts.map +1 -1
  15. package/dist/lib/codex-cli/state.js +14 -0
  16. package/dist/lib/codex-cli/state.js.map +1 -1
  17. package/dist/lib/codex-cli/writer.d.ts.map +1 -1
  18. package/dist/lib/codex-cli/writer.js +106 -14
  19. package/dist/lib/codex-cli/writer.js.map +1 -1
  20. package/dist/lib/codex-manager/settings-hub.d.ts +16 -1
  21. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
  22. package/dist/lib/codex-manager/settings-hub.js +28 -2
  23. package/dist/lib/codex-manager/settings-hub.js.map +1 -1
  24. package/dist/lib/codex-manager.d.ts +1 -0
  25. package/dist/lib/codex-manager.d.ts.map +1 -1
  26. package/dist/lib/codex-manager.js +319 -16
  27. package/dist/lib/codex-manager.js.map +1 -1
  28. package/dist/lib/context-overflow.js +10 -10
  29. package/dist/lib/oauth-success.html +192 -260
  30. package/dist/lib/parallel-probe.d.ts +7 -0
  31. package/dist/lib/parallel-probe.d.ts.map +1 -1
  32. package/dist/lib/parallel-probe.js +37 -9
  33. package/dist/lib/parallel-probe.js.map +1 -1
  34. package/dist/lib/prompts/codex.js +2 -2
  35. package/dist/lib/refresh-queue.d.ts +4 -5
  36. package/dist/lib/refresh-queue.d.ts.map +1 -1
  37. package/dist/lib/refresh-queue.js +99 -21
  38. package/dist/lib/refresh-queue.js.map +1 -1
  39. package/dist/lib/request/fetch-helpers.d.ts +12 -4
  40. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  41. package/dist/lib/request/fetch-helpers.js +53 -25
  42. package/dist/lib/request/fetch-helpers.js.map +1 -1
  43. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -1
  44. package/dist/lib/request/helpers/tool-utils.js +36 -6
  45. package/dist/lib/request/helpers/tool-utils.js.map +1 -1
  46. package/dist/lib/request/rate-limit-backoff.d.ts +7 -0
  47. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -1
  48. package/dist/lib/request/rate-limit-backoff.js +24 -4
  49. package/dist/lib/request/rate-limit-backoff.js.map +1 -1
  50. package/dist/lib/request/request-transformer.d.ts +10 -0
  51. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  52. package/dist/lib/request/request-transformer.js +61 -37
  53. package/dist/lib/request/request-transformer.js.map +1 -1
  54. package/dist/lib/rotation.d.ts +19 -0
  55. package/dist/lib/rotation.d.ts.map +1 -1
  56. package/dist/lib/rotation.js +60 -39
  57. package/dist/lib/rotation.js.map +1 -1
  58. package/dist/lib/ui/copy.d.ts +1 -1
  59. package/dist/lib/ui/copy.js +1 -1
  60. package/dist/lib/utils.d.ts +6 -0
  61. package/dist/lib/utils.d.ts.map +1 -1
  62. package/dist/lib/utils.js +11 -0
  63. package/dist/lib/utils.js.map +1 -1
  64. package/node_modules/@codex-ai/plugin/dist/index.d.ts +1 -1
  65. package/node_modules/@codex-ai/plugin/dist/tool.d.ts +41 -41
  66. package/node_modules/@codex-ai/plugin/dist/tool.js +28 -28
  67. package/package.json +9 -5
  68. package/scripts/audit-dev-allowlist.js +186 -94
  69. package/scripts/bench-format/hashline-v2.mjs +642 -642
  70. package/scripts/bench-format/render.mjs +496 -496
  71. package/scripts/bench-format/stats.mjs +54 -54
  72. package/scripts/bench-format/tasks.mjs +151 -151
  73. package/scripts/benchmark-render-dashboard.mjs +49 -49
  74. package/scripts/benchmark-runtime-path.mjs +166 -0
  75. package/scripts/codex-multi-auth.js +1 -1
  76. package/scripts/codex.js +86 -26
  77. package/scripts/copy-oauth-success.js +42 -3
  78. package/scripts/install-codex-auth-utils.js +69 -0
  79. package/scripts/install-codex-auth.js +23 -14
  80. package/scripts/test-all-models.sh +7 -7
  81. package/scripts/test-model-matrix.js +108 -80
  82. package/scripts/validate-model-map.sh +7 -7
  83. package/vendor/codex-ai-plugin/dist/index.d.ts +1 -1
  84. package/vendor/codex-ai-plugin/dist/tool.d.ts +41 -41
  85. package/vendor/codex-ai-plugin/dist/tool.js +28 -28
  86. package/vendor/codex-ai-sdk/dist/index.d.ts +3 -3
@@ -15,6 +15,7 @@ import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe
15
15
  import { queuedRefresh } from "./refresh-queue.js";
16
16
  import { loadQuotaCache, saveQuotaCache, } from "./quota-cache.js";
17
17
  import { getStoragePath, loadFlaggedAccounts, loadAccounts, saveFlaggedAccounts, saveAccounts, setStoragePath, } from "./storage.js";
18
+ import { getCodexCliAuthPath, getCodexCliConfigPath, loadCodexCliState, } from "./codex-cli/state.js";
18
19
  import { setCodexCliActiveSelection } from "./codex-cli/writer.js";
19
20
  import { ANSI } from "./ui/ansi.js";
20
21
  import { UI_COPY } from "./ui/copy.js";
@@ -214,17 +215,17 @@ function printUsage() {
214
215
  "Codex Multi-Auth CLI",
215
216
  "",
216
217
  "Usage:",
217
- " codex-multi-auth auth login",
218
- " codex-multi-auth auth list",
219
- " codex-multi-auth auth status",
220
- " codex-multi-auth auth switch <index>",
221
- " codex-multi-auth auth check",
222
- " codex-multi-auth auth features",
223
- " codex-multi-auth auth verify-flagged [--dry-run] [--json] [--no-restore]",
224
- " codex-multi-auth auth forecast [--live] [--json] [--model <model>]",
225
- " codex-multi-auth auth report [--live] [--json] [--model <model>] [--out <path>]",
226
- " codex-multi-auth auth fix [--dry-run] [--json] [--live] [--model <model>]",
227
- " codex-multi-auth auth doctor [--json] [--fix] [--dry-run]",
218
+ " codex auth login",
219
+ " codex auth list",
220
+ " codex auth status",
221
+ " codex auth switch <index>",
222
+ " codex auth check",
223
+ " codex auth features",
224
+ " codex auth verify-flagged [--dry-run] [--json] [--no-restore]",
225
+ " codex auth forecast [--live] [--json] [--model <model>]",
226
+ " codex auth report [--live] [--json] [--model <model>] [--out <path>]",
227
+ " codex auth fix [--dry-run] [--json] [--live] [--model <model>]",
228
+ " codex auth doctor [--json] [--fix] [--dry-run]",
228
229
  "",
229
230
  "Notes:",
230
231
  " - Uses ~/.codex/multi-auth/openai-codex-accounts.json",
@@ -2728,6 +2729,103 @@ async function runDoctor(args) {
2728
2729
  });
2729
2730
  }
2730
2731
  }
2732
+ const codexAuthPath = getCodexCliAuthPath();
2733
+ const codexConfigPath = getCodexCliConfigPath();
2734
+ let codexAuthEmail;
2735
+ let codexAuthAccountId;
2736
+ addCheck({
2737
+ key: "codex-auth-file",
2738
+ severity: existsSync(codexAuthPath) ? "ok" : "warn",
2739
+ message: existsSync(codexAuthPath)
2740
+ ? "Codex auth file found"
2741
+ : "Codex auth file does not exist",
2742
+ details: codexAuthPath,
2743
+ });
2744
+ if (existsSync(codexAuthPath)) {
2745
+ try {
2746
+ const raw = await fs.readFile(codexAuthPath, "utf-8");
2747
+ const parsed = JSON.parse(raw);
2748
+ if (parsed && typeof parsed === "object") {
2749
+ const payload = parsed;
2750
+ const tokens = payload.tokens && typeof payload.tokens === "object"
2751
+ ? payload.tokens
2752
+ : null;
2753
+ const accessToken = tokens && typeof tokens.access_token === "string"
2754
+ ? tokens.access_token
2755
+ : undefined;
2756
+ const idToken = tokens && typeof tokens.id_token === "string"
2757
+ ? tokens.id_token
2758
+ : undefined;
2759
+ const accountIdFromFile = tokens && typeof tokens.account_id === "string"
2760
+ ? tokens.account_id
2761
+ : undefined;
2762
+ const emailFromFile = typeof payload.email === "string" ? payload.email : undefined;
2763
+ codexAuthEmail = sanitizeEmail(emailFromFile ?? extractAccountEmail(accessToken, idToken));
2764
+ codexAuthAccountId = accountIdFromFile ?? extractAccountId(accessToken);
2765
+ }
2766
+ addCheck({
2767
+ key: "codex-auth-readable",
2768
+ severity: "ok",
2769
+ message: "Codex auth file is readable",
2770
+ details: codexAuthEmail || codexAuthAccountId
2771
+ ? `email=${codexAuthEmail ?? "unknown"}, accountId=${codexAuthAccountId ?? "unknown"}`
2772
+ : undefined,
2773
+ });
2774
+ }
2775
+ catch (error) {
2776
+ addCheck({
2777
+ key: "codex-auth-readable",
2778
+ severity: "error",
2779
+ message: "Unable to read Codex auth file",
2780
+ details: error instanceof Error ? error.message : String(error),
2781
+ });
2782
+ }
2783
+ }
2784
+ addCheck({
2785
+ key: "codex-config-file",
2786
+ severity: existsSync(codexConfigPath) ? "ok" : "warn",
2787
+ message: existsSync(codexConfigPath)
2788
+ ? "Codex config file found"
2789
+ : "Codex config file does not exist",
2790
+ details: codexConfigPath,
2791
+ });
2792
+ let codexAuthStoreMode;
2793
+ if (existsSync(codexConfigPath)) {
2794
+ try {
2795
+ const configRaw = await fs.readFile(codexConfigPath, "utf-8");
2796
+ const match = configRaw.match(/^\s*cli_auth_credentials_store\s*=\s*"([^"]+)"\s*$/m);
2797
+ if (match?.[1]) {
2798
+ codexAuthStoreMode = match[1].trim();
2799
+ }
2800
+ }
2801
+ catch (error) {
2802
+ addCheck({
2803
+ key: "codex-auth-store",
2804
+ severity: "warn",
2805
+ message: "Unable to read Codex auth-store config",
2806
+ details: error instanceof Error ? error.message : String(error),
2807
+ });
2808
+ }
2809
+ }
2810
+ if (!checks.some((check) => check.key === "codex-auth-store")) {
2811
+ addCheck({
2812
+ key: "codex-auth-store",
2813
+ severity: codexAuthStoreMode === "file" ? "ok" : "warn",
2814
+ message: codexAuthStoreMode === "file"
2815
+ ? "Codex auth storage is set to file"
2816
+ : "Codex auth storage is not explicitly set to file",
2817
+ details: codexAuthStoreMode ? `mode=${codexAuthStoreMode}` : "mode=unset",
2818
+ });
2819
+ }
2820
+ const codexCliState = await loadCodexCliState({ forceRefresh: true });
2821
+ addCheck({
2822
+ key: "codex-cli-state",
2823
+ severity: codexCliState ? "ok" : "warn",
2824
+ message: codexCliState
2825
+ ? "Codex CLI state loaded"
2826
+ : "Codex CLI state unavailable",
2827
+ details: codexCliState?.path,
2828
+ });
2731
2829
  const storage = await loadAccounts();
2732
2830
  let fixChanged = false;
2733
2831
  let fixActions = [];
@@ -2857,6 +2955,111 @@ async function runDoctor(args) {
2857
2955
  message: "Current account aligns with forecast recommendation",
2858
2956
  });
2859
2957
  }
2958
+ if (activeExists) {
2959
+ const activeAccount = storage.accounts[activeIndex];
2960
+ const managerActiveEmail = sanitizeEmail(activeAccount?.email);
2961
+ const managerActiveAccountId = activeAccount?.accountId;
2962
+ const codexActiveEmail = sanitizeEmail(codexCliState?.activeEmail) ?? codexAuthEmail;
2963
+ const codexActiveAccountId = codexCliState?.activeAccountId ?? codexAuthAccountId;
2964
+ const isEmailMismatch = !!managerActiveEmail &&
2965
+ !!codexActiveEmail &&
2966
+ managerActiveEmail !== codexActiveEmail;
2967
+ const isAccountIdMismatch = !!managerActiveAccountId &&
2968
+ !!codexActiveAccountId &&
2969
+ managerActiveAccountId !== codexActiveAccountId;
2970
+ addCheck({
2971
+ key: "active-selection-sync",
2972
+ severity: isEmailMismatch || isAccountIdMismatch ? "warn" : "ok",
2973
+ message: isEmailMismatch || isAccountIdMismatch
2974
+ ? "Manager active account and Codex active account are not aligned"
2975
+ : "Manager active account and Codex active account are aligned",
2976
+ details: `manager=${managerActiveEmail ?? managerActiveAccountId ?? "unknown"} | codex=${codexActiveEmail ?? codexActiveAccountId ?? "unknown"}`,
2977
+ });
2978
+ if (options.fix && activeAccount) {
2979
+ let syncAccessToken = activeAccount.accessToken;
2980
+ let syncRefreshToken = activeAccount.refreshToken;
2981
+ let syncExpiresAt = activeAccount.expiresAt;
2982
+ let syncIdToken;
2983
+ let storageChangedFromDoctorSync = false;
2984
+ if (!hasUsableAccessToken(activeAccount, now)) {
2985
+ if (options.dryRun) {
2986
+ fixActions.push({
2987
+ key: "doctor-refresh",
2988
+ message: `Prepared active-account token refresh for account ${activeIndex + 1} (dry-run)`,
2989
+ });
2990
+ }
2991
+ else {
2992
+ const refreshResult = await queuedRefresh(activeAccount.refreshToken);
2993
+ if (refreshResult.type === "success") {
2994
+ const refreshedEmail = sanitizeEmail(extractAccountEmail(refreshResult.access, refreshResult.idToken));
2995
+ const refreshedAccountId = extractAccountId(refreshResult.access);
2996
+ activeAccount.accessToken = refreshResult.access;
2997
+ activeAccount.refreshToken = refreshResult.refresh;
2998
+ activeAccount.expiresAt = refreshResult.expires;
2999
+ if (refreshedEmail)
3000
+ activeAccount.email = refreshedEmail;
3001
+ if (refreshedAccountId) {
3002
+ activeAccount.accountId = refreshedAccountId;
3003
+ activeAccount.accountIdSource = "token";
3004
+ }
3005
+ syncAccessToken = refreshResult.access;
3006
+ syncRefreshToken = refreshResult.refresh;
3007
+ syncExpiresAt = refreshResult.expires;
3008
+ syncIdToken = refreshResult.idToken;
3009
+ storageChangedFromDoctorSync = true;
3010
+ fixActions.push({
3011
+ key: "doctor-refresh",
3012
+ message: `Refreshed active account tokens for account ${activeIndex + 1}`,
3013
+ });
3014
+ }
3015
+ else {
3016
+ addCheck({
3017
+ key: "doctor-refresh",
3018
+ severity: "warn",
3019
+ message: "Unable to refresh active account before Codex sync",
3020
+ details: normalizeFailureDetail(refreshResult.message, refreshResult.reason),
3021
+ });
3022
+ }
3023
+ }
3024
+ }
3025
+ if (storageChangedFromDoctorSync) {
3026
+ fixChanged = true;
3027
+ if (!options.dryRun) {
3028
+ await saveAccounts(storage);
3029
+ }
3030
+ }
3031
+ if (!options.dryRun) {
3032
+ const synced = await setCodexCliActiveSelection({
3033
+ accountId: activeAccount.accountId,
3034
+ email: activeAccount.email,
3035
+ accessToken: syncAccessToken,
3036
+ refreshToken: syncRefreshToken,
3037
+ expiresAt: syncExpiresAt,
3038
+ ...(syncIdToken ? { idToken: syncIdToken } : {}),
3039
+ });
3040
+ if (synced) {
3041
+ fixChanged = true;
3042
+ fixActions.push({
3043
+ key: "codex-active-sync",
3044
+ message: "Synced manager active account into Codex auth state",
3045
+ });
3046
+ }
3047
+ else {
3048
+ addCheck({
3049
+ key: "codex-active-sync",
3050
+ severity: "warn",
3051
+ message: "Failed to sync manager active account into Codex auth state",
3052
+ });
3053
+ }
3054
+ }
3055
+ else {
3056
+ fixActions.push({
3057
+ key: "codex-active-sync",
3058
+ message: "Prepared Codex active-account sync (dry-run)",
3059
+ });
3060
+ }
3061
+ }
3062
+ }
2860
3063
  }
2861
3064
  const summary = checks.reduce((acc, check) => {
2862
3065
  acc[check.severity] += 1;
@@ -3099,7 +3302,7 @@ async function runSwitch(args) {
3099
3302
  setStoragePath(null);
3100
3303
  const indexArg = args[0];
3101
3304
  if (!indexArg) {
3102
- console.error("Missing index. Usage: codex-multi-auth auth switch <index>");
3305
+ console.error("Missing index. Usage: codex auth switch <index>");
3103
3306
  return 1;
3104
3307
  }
3105
3308
  const parsed = Number.parseInt(indexArg, 10);
@@ -3131,15 +3334,51 @@ async function runSwitch(args) {
3131
3334
  if (wasDisabled) {
3132
3335
  account.enabled = true;
3133
3336
  }
3134
- account.lastUsed = Date.now();
3337
+ const switchNow = Date.now();
3338
+ let syncAccessToken = account.accessToken;
3339
+ let syncRefreshToken = account.refreshToken;
3340
+ let syncExpiresAt = account.expiresAt;
3341
+ let syncIdToken;
3342
+ if (!hasUsableAccessToken(account, switchNow)) {
3343
+ const refreshResult = await queuedRefresh(account.refreshToken);
3344
+ if (refreshResult.type === "success") {
3345
+ const tokenAccountId = extractAccountId(refreshResult.access);
3346
+ const nextEmail = sanitizeEmail(extractAccountEmail(refreshResult.access, refreshResult.idToken));
3347
+ if (account.refreshToken !== refreshResult.refresh) {
3348
+ account.refreshToken = refreshResult.refresh;
3349
+ }
3350
+ if (account.accessToken !== refreshResult.access) {
3351
+ account.accessToken = refreshResult.access;
3352
+ }
3353
+ if (account.expiresAt !== refreshResult.expires) {
3354
+ account.expiresAt = refreshResult.expires;
3355
+ }
3356
+ if (nextEmail && nextEmail !== account.email) {
3357
+ account.email = nextEmail;
3358
+ }
3359
+ if (tokenAccountId && tokenAccountId !== account.accountId) {
3360
+ account.accountId = tokenAccountId;
3361
+ account.accountIdSource = "token";
3362
+ }
3363
+ syncAccessToken = refreshResult.access;
3364
+ syncRefreshToken = refreshResult.refresh;
3365
+ syncExpiresAt = refreshResult.expires;
3366
+ syncIdToken = refreshResult.idToken;
3367
+ }
3368
+ else {
3369
+ console.warn(`Switch validation refresh failed for account ${parsed}: ${normalizeFailureDetail(refreshResult.message, refreshResult.reason)}.`);
3370
+ }
3371
+ }
3372
+ account.lastUsed = switchNow;
3135
3373
  account.lastSwitchReason = "rotation";
3136
3374
  await saveAccounts(storage);
3137
3375
  const synced = await setCodexCliActiveSelection({
3138
3376
  accountId: account.accountId,
3139
3377
  email: account.email,
3140
- accessToken: account.accessToken,
3141
- refreshToken: account.refreshToken,
3142
- expiresAt: account.expiresAt,
3378
+ accessToken: syncAccessToken,
3379
+ refreshToken: syncRefreshToken,
3380
+ expiresAt: syncExpiresAt,
3381
+ ...(syncIdToken ? { idToken: syncIdToken } : {}),
3143
3382
  });
3144
3383
  if (!synced) {
3145
3384
  console.warn(`Switched account ${parsed} locally, but Codex auth sync did not complete. Multi-auth routing will still use this account.`);
@@ -3147,6 +3386,70 @@ async function runSwitch(args) {
3147
3386
  console.log(`Switched to account ${parsed}: ${formatAccountLabel(account, targetIndex)}${wasDisabled ? " (re-enabled)" : ""}`);
3148
3387
  return 0;
3149
3388
  }
3389
+ export async function autoSyncActiveAccountToCodex() {
3390
+ setStoragePath(null);
3391
+ const storage = await loadAccounts();
3392
+ if (!storage || storage.accounts.length === 0) {
3393
+ return false;
3394
+ }
3395
+ const activeIndex = resolveActiveIndex(storage, "codex");
3396
+ if (activeIndex < 0 || activeIndex >= storage.accounts.length) {
3397
+ return false;
3398
+ }
3399
+ const account = storage.accounts[activeIndex];
3400
+ if (!account) {
3401
+ return false;
3402
+ }
3403
+ const now = Date.now();
3404
+ let syncAccessToken = account.accessToken;
3405
+ let syncRefreshToken = account.refreshToken;
3406
+ let syncExpiresAt = account.expiresAt;
3407
+ let syncIdToken;
3408
+ let changed = false;
3409
+ if (!hasUsableAccessToken(account, now)) {
3410
+ const refreshResult = await queuedRefresh(account.refreshToken);
3411
+ if (refreshResult.type === "success") {
3412
+ const tokenAccountId = extractAccountId(refreshResult.access);
3413
+ const nextEmail = sanitizeEmail(extractAccountEmail(refreshResult.access, refreshResult.idToken));
3414
+ if (account.refreshToken !== refreshResult.refresh) {
3415
+ account.refreshToken = refreshResult.refresh;
3416
+ changed = true;
3417
+ }
3418
+ if (account.accessToken !== refreshResult.access) {
3419
+ account.accessToken = refreshResult.access;
3420
+ changed = true;
3421
+ }
3422
+ if (account.expiresAt !== refreshResult.expires) {
3423
+ account.expiresAt = refreshResult.expires;
3424
+ changed = true;
3425
+ }
3426
+ if (nextEmail && nextEmail !== account.email) {
3427
+ account.email = nextEmail;
3428
+ changed = true;
3429
+ }
3430
+ if (tokenAccountId && tokenAccountId !== account.accountId) {
3431
+ account.accountId = tokenAccountId;
3432
+ account.accountIdSource = "token";
3433
+ changed = true;
3434
+ }
3435
+ syncAccessToken = refreshResult.access;
3436
+ syncRefreshToken = refreshResult.refresh;
3437
+ syncExpiresAt = refreshResult.expires;
3438
+ syncIdToken = refreshResult.idToken;
3439
+ }
3440
+ }
3441
+ if (changed) {
3442
+ await saveAccounts(storage);
3443
+ }
3444
+ return setCodexCliActiveSelection({
3445
+ accountId: account.accountId,
3446
+ email: account.email,
3447
+ accessToken: syncAccessToken,
3448
+ refreshToken: syncRefreshToken,
3449
+ expiresAt: syncExpiresAt,
3450
+ ...(syncIdToken ? { idToken: syncIdToken } : {}),
3451
+ });
3452
+ }
3150
3453
  export async function runCodexMultiAuthCli(rawArgs) {
3151
3454
  const startupDisplaySettings = await loadDashboardDisplaySettings();
3152
3455
  applyUiThemeFromDashboardSettings(startupDisplaySettings);