codex-multi-auth 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -98
- package/assets/readme-hero.svg +2 -2
- package/config/README.md +30 -87
- package/config/{opencode-legacy.json → codex-legacy.json} +571 -571
- package/config/{opencode-modern.json → codex-modern.json} +241 -239
- package/config/{minimal-opencode.json → minimal-codex.json} +15 -13
- package/config/schema/config.schema.json +21 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts +1 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/auth/auth.d.ts +1 -1
- package/dist/lib/auth/auth.js +1 -1
- package/dist/lib/cli.d.ts +1 -1
- package/dist/lib/cli.js +4 -4
- package/dist/lib/cli.js.map +1 -1
- package/dist/lib/codex-manager/settings-hub.d.ts +6 -0
- package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-hub.js +1786 -0
- package/dist/lib/codex-manager/settings-hub.js.map +1 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +5 -1637
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +20 -11
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/context-overflow.d.ts +1 -1
- package/dist/lib/context-overflow.js +1 -1
- package/dist/lib/oauth-success.html +1 -1
- package/dist/lib/prompts/codex-host-bridge.d.ts +19 -0
- package/dist/lib/prompts/codex-host-bridge.d.ts.map +1 -0
- package/dist/lib/prompts/{codex-opencode-bridge.js → codex-host-bridge.js} +143 -143
- package/dist/lib/prompts/codex-host-bridge.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts +2 -2
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +3 -3
- package/dist/lib/prompts/host-codex-prompt.d.ts +25 -0
- package/dist/lib/prompts/host-codex-prompt.d.ts.map +1 -0
- package/dist/lib/prompts/{opencode-codex.js → host-codex-prompt.js} +136 -47
- package/dist/lib/prompts/host-codex-prompt.js.map +1 -0
- package/dist/lib/recovery/storage.d.ts +2 -2
- package/dist/lib/recovery/storage.js +2 -2
- package/dist/lib/recovery/types.d.ts +1 -1
- package/dist/lib/recovery/types.js +1 -1
- package/dist/lib/recovery.d.ts +1 -1
- package/dist/lib/recovery.d.ts.map +1 -1
- package/dist/lib/recovery.js +1 -4
- package/dist/lib/recovery.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +3 -3
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +6 -2
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/input-utils.d.ts +2 -2
- package/dist/lib/request/helpers/input-utils.d.ts.map +1 -1
- package/dist/lib/request/helpers/input-utils.js +14 -14
- package/dist/lib/request/helpers/input-utils.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts +2 -2
- package/dist/lib/request/helpers/model-map.js +2 -2
- package/dist/lib/request/request-transformer.d.ts +12 -12
- package/dist/lib/request/request-transformer.d.ts.map +1 -1
- package/dist/lib/request/request-transformer.js +23 -24
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/runtime-paths.d.ts +4 -4
- package/dist/lib/runtime-paths.d.ts.map +1 -1
- package/dist/lib/runtime-paths.js +27 -9
- package/dist/lib/runtime-paths.js.map +1 -1
- package/dist/lib/storage/paths.d.ts +11 -0
- package/dist/lib/storage/paths.d.ts.map +1 -1
- package/dist/lib/storage/paths.js +146 -2
- package/dist/lib/storage/paths.js.map +1 -1
- package/dist/lib/storage.d.ts +1 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +106 -32
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/tools/hashline-tools.d.ts +1 -1
- package/dist/lib/tools/hashline-tools.d.ts.map +1 -1
- package/dist/lib/tools/hashline-tools.js +1 -1
- package/dist/lib/tools/hashline-tools.js.map +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/ui/copy.d.ts +6 -6
- package/dist/lib/ui/copy.js +6 -6
- package/dist/lib/ui/copy.js.map +1 -1
- package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -0
- package/node_modules/@codex-ai/plugin/dist/index.js +2 -0
- package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -0
- package/node_modules/@codex-ai/plugin/dist/tool.js +29 -0
- package/node_modules/@codex-ai/plugin/package.json +9 -0
- package/package.json +30 -16
- package/scripts/bench-format/{opencode.mjs → codex-host.mjs} +206 -205
- package/scripts/bench-format/models.mjs +111 -105
- package/scripts/benchmark-edit-formats.mjs +1162 -1161
- package/scripts/codex.js +40 -2
- package/scripts/install-codex-auth-utils.js +49 -0
- package/scripts/{install-opencode-codex-auth.js → install-codex-auth.js} +220 -193
- package/scripts/repo-hygiene.js +320 -0
- package/scripts/test-model-matrix.js +475 -423
- package/vendor/codex-ai-plugin/dist/index.d.ts +2 -0
- package/vendor/codex-ai-plugin/dist/index.js +2 -0
- package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -0
- package/vendor/codex-ai-plugin/dist/tool.js +29 -0
- package/vendor/codex-ai-plugin/package.json +9 -0
- package/vendor/codex-ai-sdk/dist/index.d.ts +4 -0
- package/vendor/codex-ai-sdk/dist/index.js +2 -0
- package/vendor/codex-ai-sdk/package.json +8 -0
- package/assets/opencode-logo-ornate-dark.svg +0 -18
- package/dist/lib/prompts/codex-opencode-bridge.d.ts +0 -19
- package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +0 -1
- package/dist/lib/prompts/codex-opencode-bridge.js.map +0 -1
- package/dist/lib/prompts/opencode-codex.d.ts +0 -25
- package/dist/lib/prompts/opencode-codex.d.ts.map +0 -1
- package/dist/lib/prompts/opencode-codex.js.map +0 -1
|
@@ -8,8 +8,7 @@ import { copyTextToClipboard, openBrowserUrl } from "./auth/browser.js";
|
|
|
8
8
|
import { promptAddAnotherAccount, promptLoginMode } from "./cli.js";
|
|
9
9
|
import { extractAccountEmail, extractAccountId, formatAccountLabel, formatCooldown, formatWaitTime, getAccountIdCandidates, resolveRequestAccountId, sanitizeEmail, selectBestAccountCandidate, } from "./accounts.js";
|
|
10
10
|
import { ACCOUNT_LIMITS } from "./constants.js";
|
|
11
|
-
import { loadDashboardDisplaySettings,
|
|
12
|
-
import { getDefaultPluginConfig, loadPluginConfig, savePluginConfig, } from "./config.js";
|
|
11
|
+
import { loadDashboardDisplaySettings, DEFAULT_DASHBOARD_DISPLAY_SETTINGS, } from "./dashboard-settings.js";
|
|
13
12
|
import { evaluateForecastAccounts, isHardRefreshFailure, recommendForecastAccount, summarizeForecast, } from "./forecast.js";
|
|
14
13
|
import { MODEL_FAMILIES } from "./prompts/codex.js";
|
|
15
14
|
import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe.js";
|
|
@@ -20,8 +19,9 @@ import { setCodexCliActiveSelection } from "./codex-cli/writer.js";
|
|
|
20
19
|
import { ANSI } from "./ui/ansi.js";
|
|
21
20
|
import { UI_COPY } from "./ui/copy.js";
|
|
22
21
|
import { paintUiText, quotaToneFromLeftPercent } from "./ui/format.js";
|
|
23
|
-
import { getUiRuntimeOptions
|
|
22
|
+
import { getUiRuntimeOptions } from "./ui/runtime.js";
|
|
24
23
|
import { select } from "./ui/select.js";
|
|
24
|
+
import { applyUiThemeFromDashboardSettings, configureUnifiedSettings, resolveMenuLayoutMode } from "./codex-manager/settings-hub.js";
|
|
25
25
|
function stylePromptText(text, tone) {
|
|
26
26
|
if (!output.isTTY)
|
|
27
27
|
return text;
|
|
@@ -192,594 +192,6 @@ function availabilityTone(availability) {
|
|
|
192
192
|
return "warning";
|
|
193
193
|
return "danger";
|
|
194
194
|
}
|
|
195
|
-
const DASHBOARD_DISPLAY_OPTIONS = [
|
|
196
|
-
{
|
|
197
|
-
key: "menuShowStatusBadge",
|
|
198
|
-
label: "Show Status Badges",
|
|
199
|
-
description: "Show [ok], [active], and similar badges.",
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
key: "menuShowCurrentBadge",
|
|
203
|
-
label: "Show [current]",
|
|
204
|
-
description: "Mark the account active in Codex.",
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
key: "menuShowLastUsed",
|
|
208
|
-
label: "Show Last Used",
|
|
209
|
-
description: "Show relative usage like 'today'.",
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
key: "menuShowQuotaSummary",
|
|
213
|
-
label: "Show Limits (5h / 7d)",
|
|
214
|
-
description: "Show limit bars in each row.",
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
key: "menuShowQuotaCooldown",
|
|
218
|
-
label: "Show Limit Cooldowns",
|
|
219
|
-
description: "Show reset timers next to 5h/7d bars.",
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
key: "menuShowFetchStatus",
|
|
223
|
-
label: "Show Fetch Status",
|
|
224
|
-
description: "Show background limit refresh status in the menu subtitle.",
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
key: "menuHighlightCurrentRow",
|
|
228
|
-
label: "Highlight Current Row",
|
|
229
|
-
description: "Use stronger color on the current row.",
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
key: "menuSortEnabled",
|
|
233
|
-
label: "Enable Smart Sort",
|
|
234
|
-
description: "Sort accounts by readiness (view only).",
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
key: "menuSortPinCurrent",
|
|
238
|
-
label: "Pin [current] when tied",
|
|
239
|
-
description: "Keep current at top only when it is equally ready.",
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
key: "menuSortQuickSwitchVisibleRow",
|
|
243
|
-
label: "Quick Switch Uses Visible Rows",
|
|
244
|
-
description: "Number keys (1-9) follow what you see in the list.",
|
|
245
|
-
},
|
|
246
|
-
];
|
|
247
|
-
const DEFAULT_STATUSLINE_FIELDS = ["last-used", "limits", "status"];
|
|
248
|
-
const AUTO_RETURN_OPTIONS_MS = [1_000, 2_000, 4_000];
|
|
249
|
-
const MENU_QUOTA_TTL_OPTIONS_MS = [60_000, 5 * 60_000, 10 * 60_000];
|
|
250
|
-
const THEME_PRESET_OPTIONS = ["green", "blue"];
|
|
251
|
-
const ACCENT_COLOR_OPTIONS = ["green", "cyan", "blue", "yellow"];
|
|
252
|
-
const PREVIEW_ACCOUNT_EMAIL = "demo@example.com";
|
|
253
|
-
const PREVIEW_LAST_USED = "today";
|
|
254
|
-
const PREVIEW_STATUS = "active";
|
|
255
|
-
const PREVIEW_LIMITS = "5h ██████▒▒▒▒ 62% | 7d █████▒▒▒▒▒ 49%";
|
|
256
|
-
const PREVIEW_LIMIT_COOLDOWNS = "5h reset 1h 20m | 7d reset 2d 04h";
|
|
257
|
-
const BACKEND_TOGGLE_OPTIONS = [
|
|
258
|
-
{
|
|
259
|
-
key: "liveAccountSync",
|
|
260
|
-
label: "Enable Live Sync",
|
|
261
|
-
description: "Keep accounts synced when files change in another window.",
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
key: "sessionAffinity",
|
|
265
|
-
label: "Enable Session Affinity",
|
|
266
|
-
description: "Try to keep each conversation on the same account.",
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
key: "proactiveRefreshGuardian",
|
|
270
|
-
label: "Enable Token Refresh Guard",
|
|
271
|
-
description: "Refresh tokens early in the background.",
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
key: "retryAllAccountsRateLimited",
|
|
275
|
-
label: "Retry When All Rate-Limited",
|
|
276
|
-
description: "If all accounts are limited, wait and try again.",
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
key: "parallelProbing",
|
|
280
|
-
label: "Enable Parallel Probing",
|
|
281
|
-
description: "Check multiple accounts at the same time.",
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
key: "storageBackupEnabled",
|
|
285
|
-
label: "Enable Storage Backups",
|
|
286
|
-
description: "Create a backup before account data changes.",
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
key: "preemptiveQuotaEnabled",
|
|
290
|
-
label: "Enable Quota Deferral",
|
|
291
|
-
description: "Delay requests before limits are fully exhausted.",
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
key: "fastSession",
|
|
295
|
-
label: "Enable Fast Session Mode",
|
|
296
|
-
description: "Use lighter request handling for faster responses.",
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
key: "sessionRecovery",
|
|
300
|
-
label: "Enable Session Recovery",
|
|
301
|
-
description: "Restore recoverable sessions after restart.",
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
key: "autoResume",
|
|
305
|
-
label: "Enable Auto Resume",
|
|
306
|
-
description: "Automatically continue sessions when possible.",
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
key: "perProjectAccounts",
|
|
310
|
-
label: "Enable Per-Project Accounts",
|
|
311
|
-
description: "Keep separate account lists for each project.",
|
|
312
|
-
},
|
|
313
|
-
];
|
|
314
|
-
const BACKEND_NUMBER_OPTIONS = [
|
|
315
|
-
{
|
|
316
|
-
key: "liveAccountSyncDebounceMs",
|
|
317
|
-
label: "Live Sync Debounce",
|
|
318
|
-
description: "Wait this long before applying sync file changes.",
|
|
319
|
-
min: 50,
|
|
320
|
-
max: 10_000,
|
|
321
|
-
step: 50,
|
|
322
|
-
unit: "ms",
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
key: "liveAccountSyncPollMs",
|
|
326
|
-
label: "Live Sync Poll",
|
|
327
|
-
description: "How often to check files for account updates.",
|
|
328
|
-
min: 500,
|
|
329
|
-
max: 60_000,
|
|
330
|
-
step: 500,
|
|
331
|
-
unit: "ms",
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
key: "sessionAffinityTtlMs",
|
|
335
|
-
label: "Session Affinity TTL",
|
|
336
|
-
description: "How long conversation-to-account mapping is kept.",
|
|
337
|
-
min: 1_000,
|
|
338
|
-
max: 24 * 60 * 60_000,
|
|
339
|
-
step: 60_000,
|
|
340
|
-
unit: "ms",
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
key: "sessionAffinityMaxEntries",
|
|
344
|
-
label: "Session Affinity Max Entries",
|
|
345
|
-
description: "Maximum stored conversation mappings.",
|
|
346
|
-
min: 8,
|
|
347
|
-
max: 4_096,
|
|
348
|
-
step: 32,
|
|
349
|
-
unit: "count",
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
key: "proactiveRefreshIntervalMs",
|
|
353
|
-
label: "Refresh Guard Interval",
|
|
354
|
-
description: "How often to scan for tokens near expiry.",
|
|
355
|
-
min: 5_000,
|
|
356
|
-
max: 10 * 60_000,
|
|
357
|
-
step: 5_000,
|
|
358
|
-
unit: "ms",
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
key: "proactiveRefreshBufferMs",
|
|
362
|
-
label: "Refresh Guard Buffer",
|
|
363
|
-
description: "How early to refresh before expiry.",
|
|
364
|
-
min: 30_000,
|
|
365
|
-
max: 10 * 60_000,
|
|
366
|
-
step: 30_000,
|
|
367
|
-
unit: "ms",
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
key: "parallelProbingMaxConcurrency",
|
|
371
|
-
label: "Parallel Probe Concurrency",
|
|
372
|
-
description: "Maximum checks running at once.",
|
|
373
|
-
min: 1,
|
|
374
|
-
max: 5,
|
|
375
|
-
step: 1,
|
|
376
|
-
unit: "count",
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
key: "fastSessionMaxInputItems",
|
|
380
|
-
label: "Fast Session Max Inputs",
|
|
381
|
-
description: "Max number of input items kept in fast mode.",
|
|
382
|
-
min: 8,
|
|
383
|
-
max: 200,
|
|
384
|
-
step: 2,
|
|
385
|
-
unit: "count",
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
key: "networkErrorCooldownMs",
|
|
389
|
-
label: "Network Error Cooldown",
|
|
390
|
-
description: "Wait time after network errors before retry.",
|
|
391
|
-
min: 0,
|
|
392
|
-
max: 120_000,
|
|
393
|
-
step: 500,
|
|
394
|
-
unit: "ms",
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
key: "serverErrorCooldownMs",
|
|
398
|
-
label: "Server Error Cooldown",
|
|
399
|
-
description: "Wait time after server errors before retry.",
|
|
400
|
-
min: 0,
|
|
401
|
-
max: 120_000,
|
|
402
|
-
step: 500,
|
|
403
|
-
unit: "ms",
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
key: "fetchTimeoutMs",
|
|
407
|
-
label: "Request Timeout",
|
|
408
|
-
description: "Max time to wait for a request.",
|
|
409
|
-
min: 1_000,
|
|
410
|
-
max: 10 * 60_000,
|
|
411
|
-
step: 5_000,
|
|
412
|
-
unit: "ms",
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
key: "streamStallTimeoutMs",
|
|
416
|
-
label: "Stream Stall Timeout",
|
|
417
|
-
description: "Max wait before a stuck stream is retried.",
|
|
418
|
-
min: 1_000,
|
|
419
|
-
max: 10 * 60_000,
|
|
420
|
-
step: 5_000,
|
|
421
|
-
unit: "ms",
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
key: "tokenRefreshSkewMs",
|
|
425
|
-
label: "Token Refresh Buffer",
|
|
426
|
-
description: "Refresh this long before token expiry.",
|
|
427
|
-
min: 0,
|
|
428
|
-
max: 10 * 60_000,
|
|
429
|
-
step: 10_000,
|
|
430
|
-
unit: "ms",
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
key: "preemptiveQuotaRemainingPercent5h",
|
|
434
|
-
label: "5h Remaining Threshold",
|
|
435
|
-
description: "Start delaying when 5h remaining reaches this percent.",
|
|
436
|
-
min: 0,
|
|
437
|
-
max: 100,
|
|
438
|
-
step: 1,
|
|
439
|
-
unit: "percent",
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
key: "preemptiveQuotaRemainingPercent7d",
|
|
443
|
-
label: "7d Remaining Threshold",
|
|
444
|
-
description: "Start delaying when weekly remaining reaches this percent.",
|
|
445
|
-
min: 0,
|
|
446
|
-
max: 100,
|
|
447
|
-
step: 1,
|
|
448
|
-
unit: "percent",
|
|
449
|
-
},
|
|
450
|
-
{
|
|
451
|
-
key: "preemptiveQuotaMaxDeferralMs",
|
|
452
|
-
label: "Max Preemptive Deferral",
|
|
453
|
-
description: "Maximum time allowed for quota-based delay.",
|
|
454
|
-
min: 1_000,
|
|
455
|
-
max: 24 * 60 * 60_000,
|
|
456
|
-
step: 60_000,
|
|
457
|
-
unit: "ms",
|
|
458
|
-
},
|
|
459
|
-
];
|
|
460
|
-
const BACKEND_DEFAULTS = getDefaultPluginConfig();
|
|
461
|
-
const BACKEND_TOGGLE_OPTION_BY_KEY = new Map(BACKEND_TOGGLE_OPTIONS.map((option) => [option.key, option]));
|
|
462
|
-
const BACKEND_NUMBER_OPTION_BY_KEY = new Map(BACKEND_NUMBER_OPTIONS.map((option) => [option.key, option]));
|
|
463
|
-
const BACKEND_CATEGORY_OPTIONS = [
|
|
464
|
-
{
|
|
465
|
-
key: "session-sync",
|
|
466
|
-
label: "Session & Sync",
|
|
467
|
-
description: "Sync and session behavior.",
|
|
468
|
-
toggleKeys: [
|
|
469
|
-
"liveAccountSync",
|
|
470
|
-
"sessionAffinity",
|
|
471
|
-
"perProjectAccounts",
|
|
472
|
-
"sessionRecovery",
|
|
473
|
-
"autoResume",
|
|
474
|
-
],
|
|
475
|
-
numberKeys: [
|
|
476
|
-
"liveAccountSyncDebounceMs",
|
|
477
|
-
"liveAccountSyncPollMs",
|
|
478
|
-
"sessionAffinityTtlMs",
|
|
479
|
-
"sessionAffinityMaxEntries",
|
|
480
|
-
],
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
key: "rotation-quota",
|
|
484
|
-
label: "Rotation & Quota",
|
|
485
|
-
description: "Quota and retry behavior.",
|
|
486
|
-
toggleKeys: ["preemptiveQuotaEnabled", "retryAllAccountsRateLimited"],
|
|
487
|
-
numberKeys: [
|
|
488
|
-
"preemptiveQuotaRemainingPercent5h",
|
|
489
|
-
"preemptiveQuotaRemainingPercent7d",
|
|
490
|
-
"preemptiveQuotaMaxDeferralMs",
|
|
491
|
-
],
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
key: "refresh-recovery",
|
|
495
|
-
label: "Refresh & Recovery",
|
|
496
|
-
description: "Token refresh and recovery safety.",
|
|
497
|
-
toggleKeys: ["proactiveRefreshGuardian", "storageBackupEnabled"],
|
|
498
|
-
numberKeys: [
|
|
499
|
-
"proactiveRefreshIntervalMs",
|
|
500
|
-
"proactiveRefreshBufferMs",
|
|
501
|
-
"tokenRefreshSkewMs",
|
|
502
|
-
],
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
key: "performance-timeouts",
|
|
506
|
-
label: "Performance & Timeouts",
|
|
507
|
-
description: "Speed, probing, and timeout controls.",
|
|
508
|
-
toggleKeys: ["fastSession", "parallelProbing"],
|
|
509
|
-
numberKeys: [
|
|
510
|
-
"fastSessionMaxInputItems",
|
|
511
|
-
"parallelProbingMaxConcurrency",
|
|
512
|
-
"fetchTimeoutMs",
|
|
513
|
-
"streamStallTimeoutMs",
|
|
514
|
-
"networkErrorCooldownMs",
|
|
515
|
-
"serverErrorCooldownMs",
|
|
516
|
-
],
|
|
517
|
-
},
|
|
518
|
-
];
|
|
519
|
-
function normalizeStatuslineFields(fields) {
|
|
520
|
-
const source = fields ?? DEFAULT_STATUSLINE_FIELDS;
|
|
521
|
-
const seen = new Set();
|
|
522
|
-
const normalized = [];
|
|
523
|
-
for (const field of source) {
|
|
524
|
-
if (seen.has(field))
|
|
525
|
-
continue;
|
|
526
|
-
seen.add(field);
|
|
527
|
-
normalized.push(field);
|
|
528
|
-
}
|
|
529
|
-
if (normalized.length === 0) {
|
|
530
|
-
return [...DEFAULT_STATUSLINE_FIELDS];
|
|
531
|
-
}
|
|
532
|
-
return normalized;
|
|
533
|
-
}
|
|
534
|
-
function highlightPreviewToken(text, ui) {
|
|
535
|
-
if (!output.isTTY)
|
|
536
|
-
return text;
|
|
537
|
-
if (ui.v2Enabled) {
|
|
538
|
-
return `${ui.theme.colors.accent}${ANSI.bold}${text}${ui.theme.colors.reset}`;
|
|
539
|
-
}
|
|
540
|
-
return `${ANSI.cyan}${ANSI.bold}${text}${ANSI.reset}`;
|
|
541
|
-
}
|
|
542
|
-
function isLastUsedPreviewFocus(focus) {
|
|
543
|
-
return focus === "menuShowLastUsed" || focus === "last-used";
|
|
544
|
-
}
|
|
545
|
-
function isLimitsPreviewFocus(focus) {
|
|
546
|
-
return focus === "menuShowQuotaSummary" || focus === "limits";
|
|
547
|
-
}
|
|
548
|
-
function isLimitsCooldownPreviewFocus(focus) {
|
|
549
|
-
return focus === "menuShowQuotaCooldown";
|
|
550
|
-
}
|
|
551
|
-
function isStatusPreviewFocus(focus) {
|
|
552
|
-
return focus === "menuShowStatusBadge" || focus === "status";
|
|
553
|
-
}
|
|
554
|
-
function isCurrentBadgePreviewFocus(focus) {
|
|
555
|
-
return focus === "menuShowCurrentBadge";
|
|
556
|
-
}
|
|
557
|
-
function isCurrentRowPreviewFocus(focus) {
|
|
558
|
-
return focus === "menuHighlightCurrentRow";
|
|
559
|
-
}
|
|
560
|
-
function isExpandedRowsPreviewFocus(focus) {
|
|
561
|
-
return focus === "menuShowDetailsForUnselectedRows" || focus === "menuLayoutMode";
|
|
562
|
-
}
|
|
563
|
-
function buildSummaryPreviewText(settings, ui, focus = null) {
|
|
564
|
-
const partsByField = new Map();
|
|
565
|
-
if (settings.menuShowLastUsed !== false) {
|
|
566
|
-
const part = `last used: ${PREVIEW_LAST_USED}`;
|
|
567
|
-
partsByField.set("last-used", isLastUsedPreviewFocus(focus) ? highlightPreviewToken(part, ui) : part);
|
|
568
|
-
}
|
|
569
|
-
if (settings.menuShowQuotaSummary !== false) {
|
|
570
|
-
const limitsText = settings.menuShowQuotaCooldown === false
|
|
571
|
-
? PREVIEW_LIMITS
|
|
572
|
-
: `${PREVIEW_LIMITS} | ${PREVIEW_LIMIT_COOLDOWNS}`;
|
|
573
|
-
const part = `limits: ${limitsText}`;
|
|
574
|
-
partsByField.set("limits", isLimitsPreviewFocus(focus) || isLimitsCooldownPreviewFocus(focus)
|
|
575
|
-
? highlightPreviewToken(part, ui)
|
|
576
|
-
: part);
|
|
577
|
-
}
|
|
578
|
-
if (settings.menuShowStatusBadge === false) {
|
|
579
|
-
const part = `status: ${PREVIEW_STATUS}`;
|
|
580
|
-
partsByField.set("status", isStatusPreviewFocus(focus) ? highlightPreviewToken(part, ui) : part);
|
|
581
|
-
}
|
|
582
|
-
const orderedParts = normalizeStatuslineFields(settings.menuStatuslineFields)
|
|
583
|
-
.map((field) => partsByField.get(field))
|
|
584
|
-
.filter((part) => typeof part === "string" && part.length > 0);
|
|
585
|
-
if (orderedParts.length > 0) {
|
|
586
|
-
return orderedParts.join(" | ");
|
|
587
|
-
}
|
|
588
|
-
const showsStatusField = normalizeStatuslineFields(settings.menuStatuslineFields).includes("status");
|
|
589
|
-
if (showsStatusField && settings.menuShowStatusBadge !== false) {
|
|
590
|
-
const note = "status text appears only when status badges are hidden";
|
|
591
|
-
return isStatusPreviewFocus(focus) ? highlightPreviewToken(note, ui) : note;
|
|
592
|
-
}
|
|
593
|
-
return "no summary text is visible with current account-list settings";
|
|
594
|
-
}
|
|
595
|
-
function buildAccountListPreview(settings, ui, focus = null) {
|
|
596
|
-
const badges = [];
|
|
597
|
-
if (settings.menuShowCurrentBadge !== false) {
|
|
598
|
-
const currentBadge = "[current]";
|
|
599
|
-
badges.push(isCurrentBadgePreviewFocus(focus) ? highlightPreviewToken(currentBadge, ui) : currentBadge);
|
|
600
|
-
}
|
|
601
|
-
if (settings.menuShowStatusBadge !== false) {
|
|
602
|
-
const statusBadge = "[active]";
|
|
603
|
-
badges.push(isStatusPreviewFocus(focus) ? highlightPreviewToken(statusBadge, ui) : statusBadge);
|
|
604
|
-
}
|
|
605
|
-
const badgeSuffix = badges.length > 0 ? ` ${badges.join(" ")}` : "";
|
|
606
|
-
const accountEmail = isCurrentRowPreviewFocus(focus)
|
|
607
|
-
? highlightPreviewToken(PREVIEW_ACCOUNT_EMAIL, ui)
|
|
608
|
-
: PREVIEW_ACCOUNT_EMAIL;
|
|
609
|
-
const rowDetailMode = resolveMenuLayoutMode(settings) === "expanded-rows"
|
|
610
|
-
? "details shown on all rows"
|
|
611
|
-
: "details shown on selected row only";
|
|
612
|
-
const detailModeText = isExpandedRowsPreviewFocus(focus)
|
|
613
|
-
? highlightPreviewToken(rowDetailMode, ui)
|
|
614
|
-
: rowDetailMode;
|
|
615
|
-
return {
|
|
616
|
-
label: `1. ${accountEmail}${badgeSuffix}`,
|
|
617
|
-
hint: `${buildSummaryPreviewText(settings, ui, focus)}\n${detailModeText}`,
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
function cloneDashboardSettings(settings) {
|
|
621
|
-
const layoutMode = resolveMenuLayoutMode(settings);
|
|
622
|
-
return {
|
|
623
|
-
showPerAccountRows: settings.showPerAccountRows,
|
|
624
|
-
showQuotaDetails: settings.showQuotaDetails,
|
|
625
|
-
showForecastReasons: settings.showForecastReasons,
|
|
626
|
-
showRecommendations: settings.showRecommendations,
|
|
627
|
-
showLiveProbeNotes: settings.showLiveProbeNotes,
|
|
628
|
-
actionAutoReturnMs: settings.actionAutoReturnMs ?? 2_000,
|
|
629
|
-
actionPauseOnKey: settings.actionPauseOnKey ?? true,
|
|
630
|
-
menuAutoFetchLimits: settings.menuAutoFetchLimits ?? true,
|
|
631
|
-
menuSortEnabled: settings.menuSortEnabled ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ?? true),
|
|
632
|
-
menuSortMode: settings.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first"),
|
|
633
|
-
menuSortPinCurrent: settings.menuSortPinCurrent ??
|
|
634
|
-
(DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ?? false),
|
|
635
|
-
menuSortQuickSwitchVisibleRow: settings.menuSortQuickSwitchVisibleRow ?? true,
|
|
636
|
-
uiThemePreset: settings.uiThemePreset ?? "green",
|
|
637
|
-
uiAccentColor: settings.uiAccentColor ?? "green",
|
|
638
|
-
menuShowStatusBadge: settings.menuShowStatusBadge ?? true,
|
|
639
|
-
menuShowCurrentBadge: settings.menuShowCurrentBadge ?? true,
|
|
640
|
-
menuShowLastUsed: settings.menuShowLastUsed ?? true,
|
|
641
|
-
menuShowQuotaSummary: settings.menuShowQuotaSummary ?? true,
|
|
642
|
-
menuShowQuotaCooldown: settings.menuShowQuotaCooldown ?? true,
|
|
643
|
-
menuShowFetchStatus: settings.menuShowFetchStatus ?? true,
|
|
644
|
-
menuShowDetailsForUnselectedRows: layoutMode === "expanded-rows",
|
|
645
|
-
menuLayoutMode: layoutMode,
|
|
646
|
-
menuQuotaTtlMs: settings.menuQuotaTtlMs ?? 5 * 60_000,
|
|
647
|
-
menuFocusStyle: settings.menuFocusStyle ?? "row-invert",
|
|
648
|
-
menuHighlightCurrentRow: settings.menuHighlightCurrentRow ?? true,
|
|
649
|
-
menuStatuslineFields: [...normalizeStatuslineFields(settings.menuStatuslineFields)],
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
function dashboardSettingsEqual(left, right) {
|
|
653
|
-
return (left.showPerAccountRows === right.showPerAccountRows &&
|
|
654
|
-
left.showQuotaDetails === right.showQuotaDetails &&
|
|
655
|
-
left.showForecastReasons === right.showForecastReasons &&
|
|
656
|
-
left.showRecommendations === right.showRecommendations &&
|
|
657
|
-
left.showLiveProbeNotes === right.showLiveProbeNotes &&
|
|
658
|
-
(left.actionAutoReturnMs ?? 2_000) === (right.actionAutoReturnMs ?? 2_000) &&
|
|
659
|
-
(left.actionPauseOnKey ?? true) === (right.actionPauseOnKey ?? true) &&
|
|
660
|
-
(left.menuAutoFetchLimits ?? true) === (right.menuAutoFetchLimits ?? true) &&
|
|
661
|
-
(left.menuSortEnabled ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ?? true)) ===
|
|
662
|
-
(right.menuSortEnabled ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ?? true)) &&
|
|
663
|
-
(left.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first")) ===
|
|
664
|
-
(right.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first")) &&
|
|
665
|
-
(left.menuSortPinCurrent ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ?? false)) ===
|
|
666
|
-
(right.menuSortPinCurrent ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ?? false)) &&
|
|
667
|
-
(left.menuSortQuickSwitchVisibleRow ?? true) ===
|
|
668
|
-
(right.menuSortQuickSwitchVisibleRow ?? true) &&
|
|
669
|
-
(left.uiThemePreset ?? "green") === (right.uiThemePreset ?? "green") &&
|
|
670
|
-
(left.uiAccentColor ?? "green") === (right.uiAccentColor ?? "green") &&
|
|
671
|
-
(left.menuShowStatusBadge ?? true) === (right.menuShowStatusBadge ?? true) &&
|
|
672
|
-
(left.menuShowCurrentBadge ?? true) === (right.menuShowCurrentBadge ?? true) &&
|
|
673
|
-
(left.menuShowLastUsed ?? true) === (right.menuShowLastUsed ?? true) &&
|
|
674
|
-
(left.menuShowQuotaSummary ?? true) === (right.menuShowQuotaSummary ?? true) &&
|
|
675
|
-
(left.menuShowQuotaCooldown ?? true) === (right.menuShowQuotaCooldown ?? true) &&
|
|
676
|
-
(left.menuShowFetchStatus ?? true) === (right.menuShowFetchStatus ?? true) &&
|
|
677
|
-
resolveMenuLayoutMode(left) === resolveMenuLayoutMode(right) &&
|
|
678
|
-
(left.menuQuotaTtlMs ?? 5 * 60_000) === (right.menuQuotaTtlMs ?? 5 * 60_000) &&
|
|
679
|
-
(left.menuFocusStyle ?? "row-invert") === (right.menuFocusStyle ?? "row-invert") &&
|
|
680
|
-
(left.menuHighlightCurrentRow ?? true) === (right.menuHighlightCurrentRow ?? true) &&
|
|
681
|
-
JSON.stringify(normalizeStatuslineFields(left.menuStatuslineFields)) ===
|
|
682
|
-
JSON.stringify(normalizeStatuslineFields(right.menuStatuslineFields)));
|
|
683
|
-
}
|
|
684
|
-
function cloneBackendPluginConfig(config) {
|
|
685
|
-
const fallbackChain = config.unsupportedCodexFallbackChain;
|
|
686
|
-
return {
|
|
687
|
-
...BACKEND_DEFAULTS,
|
|
688
|
-
...config,
|
|
689
|
-
unsupportedCodexFallbackChain: fallbackChain && typeof fallbackChain === "object"
|
|
690
|
-
? { ...fallbackChain }
|
|
691
|
-
: {},
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
function backendSettingsSnapshot(config) {
|
|
695
|
-
const snapshot = {};
|
|
696
|
-
for (const option of BACKEND_TOGGLE_OPTIONS) {
|
|
697
|
-
snapshot[option.key] = config[option.key] ?? BACKEND_DEFAULTS[option.key] ?? false;
|
|
698
|
-
}
|
|
699
|
-
for (const option of BACKEND_NUMBER_OPTIONS) {
|
|
700
|
-
snapshot[option.key] = config[option.key] ?? BACKEND_DEFAULTS[option.key] ?? option.min;
|
|
701
|
-
}
|
|
702
|
-
return snapshot;
|
|
703
|
-
}
|
|
704
|
-
function backendSettingsEqual(left, right) {
|
|
705
|
-
return JSON.stringify(backendSettingsSnapshot(left)) === JSON.stringify(backendSettingsSnapshot(right));
|
|
706
|
-
}
|
|
707
|
-
function formatBackendNumberValue(option, value) {
|
|
708
|
-
if (option.unit === "percent")
|
|
709
|
-
return `${Math.round(value)}%`;
|
|
710
|
-
if (option.unit === "count")
|
|
711
|
-
return `${Math.round(value)}`;
|
|
712
|
-
if (value >= 60_000 && value % 60_000 === 0) {
|
|
713
|
-
return `${Math.round(value / 60_000)}m`;
|
|
714
|
-
}
|
|
715
|
-
if (value >= 1_000 && value % 1_000 === 0) {
|
|
716
|
-
return `${Math.round(value / 1_000)}s`;
|
|
717
|
-
}
|
|
718
|
-
return `${Math.round(value)}ms`;
|
|
719
|
-
}
|
|
720
|
-
function clampBackendNumber(option, value) {
|
|
721
|
-
return Math.max(option.min, Math.min(option.max, Math.round(value)));
|
|
722
|
-
}
|
|
723
|
-
function buildBackendSettingsPreview(config, ui, focus = null) {
|
|
724
|
-
const liveSync = config.liveAccountSync ?? BACKEND_DEFAULTS.liveAccountSync ?? true;
|
|
725
|
-
const affinity = config.sessionAffinity ?? BACKEND_DEFAULTS.sessionAffinity ?? true;
|
|
726
|
-
const preemptive = config.preemptiveQuotaEnabled ?? BACKEND_DEFAULTS.preemptiveQuotaEnabled ?? true;
|
|
727
|
-
const threshold5h = config.preemptiveQuotaRemainingPercent5h ??
|
|
728
|
-
BACKEND_DEFAULTS.preemptiveQuotaRemainingPercent5h ??
|
|
729
|
-
5;
|
|
730
|
-
const threshold7d = config.preemptiveQuotaRemainingPercent7d ??
|
|
731
|
-
BACKEND_DEFAULTS.preemptiveQuotaRemainingPercent7d ??
|
|
732
|
-
5;
|
|
733
|
-
const fetchTimeout = config.fetchTimeoutMs ?? BACKEND_DEFAULTS.fetchTimeoutMs ?? 60_000;
|
|
734
|
-
const stallTimeout = config.streamStallTimeoutMs ?? BACKEND_DEFAULTS.streamStallTimeoutMs ?? 45_000;
|
|
735
|
-
const fetchTimeoutOption = BACKEND_NUMBER_OPTION_BY_KEY.get("fetchTimeoutMs");
|
|
736
|
-
const stallTimeoutOption = BACKEND_NUMBER_OPTION_BY_KEY.get("streamStallTimeoutMs");
|
|
737
|
-
const highlightIfFocused = (key, text) => {
|
|
738
|
-
if (focus !== key)
|
|
739
|
-
return text;
|
|
740
|
-
return highlightPreviewToken(text, ui);
|
|
741
|
-
};
|
|
742
|
-
const label = [
|
|
743
|
-
`live sync ${highlightIfFocused("liveAccountSync", liveSync ? "on" : "off")}`,
|
|
744
|
-
`affinity ${highlightIfFocused("sessionAffinity", affinity ? "on" : "off")}`,
|
|
745
|
-
`preemptive ${highlightIfFocused("preemptiveQuotaEnabled", preemptive ? "on" : "off")}`,
|
|
746
|
-
].join(" | ");
|
|
747
|
-
const hint = [
|
|
748
|
-
`thresholds 5h<=${highlightIfFocused("preemptiveQuotaRemainingPercent5h", `${threshold5h}%`)}`,
|
|
749
|
-
`7d<=${highlightIfFocused("preemptiveQuotaRemainingPercent7d", `${threshold7d}%`)}`,
|
|
750
|
-
`timeouts ${highlightIfFocused("fetchTimeoutMs", fetchTimeoutOption ? formatBackendNumberValue(fetchTimeoutOption, fetchTimeout) : `${fetchTimeout}ms`)}/${highlightIfFocused("streamStallTimeoutMs", stallTimeoutOption ? formatBackendNumberValue(stallTimeoutOption, stallTimeout) : `${stallTimeout}ms`)}`,
|
|
751
|
-
].join(" | ");
|
|
752
|
-
return { label, hint };
|
|
753
|
-
}
|
|
754
|
-
function buildBackendConfigPatch(config) {
|
|
755
|
-
const patch = {};
|
|
756
|
-
for (const option of BACKEND_TOGGLE_OPTIONS) {
|
|
757
|
-
const value = config[option.key];
|
|
758
|
-
if (typeof value === "boolean") {
|
|
759
|
-
patch[option.key] = value;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
for (const option of BACKEND_NUMBER_OPTIONS) {
|
|
763
|
-
const value = config[option.key];
|
|
764
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
765
|
-
patch[option.key] = clampBackendNumber(option, value);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return patch;
|
|
769
|
-
}
|
|
770
|
-
function applyUiThemeFromDashboardSettings(settings) {
|
|
771
|
-
const current = getUiRuntimeOptions();
|
|
772
|
-
setUiRuntimeOptions({
|
|
773
|
-
v2Enabled: current.v2Enabled,
|
|
774
|
-
colorProfile: current.colorProfile,
|
|
775
|
-
glyphMode: current.glyphMode,
|
|
776
|
-
palette: settings.uiThemePreset ?? "green",
|
|
777
|
-
accent: settings.uiAccentColor ?? "green",
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
function formatDashboardSettingState(value) {
|
|
781
|
-
return value ? "[x]" : "[ ]";
|
|
782
|
-
}
|
|
783
195
|
function formatQuotaSnapshotForDashboard(snapshot, settings) {
|
|
784
196
|
if (!settings.showQuotaDetails)
|
|
785
197
|
return "live session OK";
|
|
@@ -811,7 +223,7 @@ function printUsage() {
|
|
|
811
223
|
" codex-multi-auth auth verify-flagged [--dry-run] [--json] [--no-restore]",
|
|
812
224
|
" codex-multi-auth auth forecast [--live] [--json] [--model <model>]",
|
|
813
225
|
" codex-multi-auth auth report [--live] [--json] [--model <model>] [--out <path>]",
|
|
814
|
-
" codex-multi-auth auth fix [--dry-run] [--json]",
|
|
226
|
+
" codex-multi-auth auth fix [--dry-run] [--json] [--live] [--model <model>]",
|
|
815
227
|
" codex-multi-auth auth doctor [--json] [--fix] [--dry-run]",
|
|
816
228
|
"",
|
|
817
229
|
"Notes:",
|
|
@@ -850,7 +262,7 @@ const IMPLEMENTED_FEATURES = [
|
|
|
850
262
|
{ id: 28, name: "Failure policy evaluation module" },
|
|
851
263
|
{ id: 29, name: "Streaming failover pipeline" },
|
|
852
264
|
{ id: 30, name: "Rate-limit backoff and cooldown handling" },
|
|
853
|
-
{ id: 31, name: "
|
|
265
|
+
{ id: 31, name: "Host request transformer bridge" },
|
|
854
266
|
{ id: 32, name: "Prompt template sync with cache" },
|
|
855
267
|
{ id: 33, name: "Codex CLI active-account state sync" },
|
|
856
268
|
{ id: 34, name: "TUI quick-switch hotkeys (1-9)" },
|
|
@@ -1590,1050 +1002,6 @@ async function runActionPanel(title, stage, action, settings) {
|
|
|
1590
1002
|
throw failed;
|
|
1591
1003
|
}
|
|
1592
1004
|
}
|
|
1593
|
-
const STATUSLINE_FIELD_OPTIONS = [
|
|
1594
|
-
{
|
|
1595
|
-
key: "last-used",
|
|
1596
|
-
label: "Show Last Used",
|
|
1597
|
-
description: "Example: 'today' or '2d ago'.",
|
|
1598
|
-
},
|
|
1599
|
-
{
|
|
1600
|
-
key: "limits",
|
|
1601
|
-
label: "Show Limits (5h / 7d)",
|
|
1602
|
-
description: "Uses cached limit data from checks.",
|
|
1603
|
-
},
|
|
1604
|
-
{
|
|
1605
|
-
key: "status",
|
|
1606
|
-
label: "Show Status Text",
|
|
1607
|
-
description: "Visible when badges are hidden.",
|
|
1608
|
-
},
|
|
1609
|
-
];
|
|
1610
|
-
function formatMenuSortMode(mode) {
|
|
1611
|
-
return mode === "ready-first" ? "Ready-First" : "Manual";
|
|
1612
|
-
}
|
|
1613
|
-
function resolveMenuLayoutMode(settings) {
|
|
1614
|
-
if (settings.menuLayoutMode === "expanded-rows") {
|
|
1615
|
-
return "expanded-rows";
|
|
1616
|
-
}
|
|
1617
|
-
if (settings.menuLayoutMode === "compact-details") {
|
|
1618
|
-
return "compact-details";
|
|
1619
|
-
}
|
|
1620
|
-
return settings.menuShowDetailsForUnselectedRows === true ? "expanded-rows" : "compact-details";
|
|
1621
|
-
}
|
|
1622
|
-
function formatMenuLayoutMode(mode) {
|
|
1623
|
-
return mode === "expanded-rows" ? "Expanded Rows" : "Compact + Details Pane";
|
|
1624
|
-
}
|
|
1625
|
-
function formatMenuQuotaTtl(ttlMs) {
|
|
1626
|
-
if (ttlMs >= 60_000 && ttlMs % 60_000 === 0) {
|
|
1627
|
-
return `${Math.round(ttlMs / 60_000)}m`;
|
|
1628
|
-
}
|
|
1629
|
-
if (ttlMs >= 1_000 && ttlMs % 1_000 === 0) {
|
|
1630
|
-
return `${Math.round(ttlMs / 1_000)}s`;
|
|
1631
|
-
}
|
|
1632
|
-
return `${ttlMs}ms`;
|
|
1633
|
-
}
|
|
1634
|
-
async function promptDashboardDisplaySettings(initial) {
|
|
1635
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1636
|
-
return null;
|
|
1637
|
-
}
|
|
1638
|
-
const ui = getUiRuntimeOptions();
|
|
1639
|
-
let draft = cloneDashboardSettings(initial);
|
|
1640
|
-
let focusKey = DASHBOARD_DISPLAY_OPTIONS[0]?.key ?? "menuShowStatusBadge";
|
|
1641
|
-
while (true) {
|
|
1642
|
-
const preview = buildAccountListPreview(draft, ui, focusKey);
|
|
1643
|
-
const optionItems = DASHBOARD_DISPLAY_OPTIONS.map((option, index) => {
|
|
1644
|
-
const enabled = draft[option.key] ?? true;
|
|
1645
|
-
const label = `${formatDashboardSettingState(enabled)} ${index + 1}. ${option.label}`;
|
|
1646
|
-
const color = enabled ? "green" : "yellow";
|
|
1647
|
-
return {
|
|
1648
|
-
label,
|
|
1649
|
-
hint: option.description,
|
|
1650
|
-
value: { type: "toggle", key: option.key },
|
|
1651
|
-
color,
|
|
1652
|
-
};
|
|
1653
|
-
});
|
|
1654
|
-
const sortMode = draft.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first");
|
|
1655
|
-
const sortModeItem = {
|
|
1656
|
-
label: `Sort mode: ${formatMenuSortMode(sortMode)}`,
|
|
1657
|
-
hint: "Applies when smart sort is enabled.",
|
|
1658
|
-
value: { type: "cycle-sort-mode" },
|
|
1659
|
-
color: sortMode === "ready-first" ? "green" : "yellow",
|
|
1660
|
-
};
|
|
1661
|
-
const layoutMode = resolveMenuLayoutMode(draft);
|
|
1662
|
-
const layoutModeItem = {
|
|
1663
|
-
label: `Layout: ${formatMenuLayoutMode(layoutMode)}`,
|
|
1664
|
-
hint: "Compact shows one-line rows with a selected details pane.",
|
|
1665
|
-
value: { type: "cycle-layout-mode" },
|
|
1666
|
-
color: layoutMode === "compact-details" ? "green" : "yellow",
|
|
1667
|
-
};
|
|
1668
|
-
const items = [
|
|
1669
|
-
{ label: UI_COPY.settings.previewHeading, value: { type: "cancel" }, kind: "heading" },
|
|
1670
|
-
{
|
|
1671
|
-
label: preview.label,
|
|
1672
|
-
hint: preview.hint,
|
|
1673
|
-
value: { type: "cancel" },
|
|
1674
|
-
color: "green",
|
|
1675
|
-
disabled: true,
|
|
1676
|
-
hideUnavailableSuffix: true,
|
|
1677
|
-
},
|
|
1678
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
1679
|
-
{ label: UI_COPY.settings.displayHeading, value: { type: "cancel" }, kind: "heading" },
|
|
1680
|
-
...optionItems,
|
|
1681
|
-
sortModeItem,
|
|
1682
|
-
layoutModeItem,
|
|
1683
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
1684
|
-
{ label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
|
|
1685
|
-
{ label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
|
|
1686
|
-
{ label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
|
|
1687
|
-
];
|
|
1688
|
-
const initialCursor = items.findIndex((item) => (item.value.type === "toggle" && item.value.key === focusKey) ||
|
|
1689
|
-
(item.value.type === "cycle-sort-mode" && focusKey === "menuSortMode") ||
|
|
1690
|
-
(item.value.type === "cycle-layout-mode" && focusKey === "menuLayoutMode"));
|
|
1691
|
-
const updateFocusedPreview = (cursor) => {
|
|
1692
|
-
const focusedItem = items[cursor];
|
|
1693
|
-
const focusedKey = focusedItem?.value.type === "toggle"
|
|
1694
|
-
? focusedItem.value.key
|
|
1695
|
-
: focusedItem?.value.type === "cycle-sort-mode"
|
|
1696
|
-
? "menuSortMode"
|
|
1697
|
-
: focusedItem?.value.type === "cycle-layout-mode"
|
|
1698
|
-
? "menuLayoutMode"
|
|
1699
|
-
: focusKey;
|
|
1700
|
-
const nextPreview = buildAccountListPreview(draft, ui, focusedKey);
|
|
1701
|
-
const previewItem = items[1];
|
|
1702
|
-
if (!previewItem)
|
|
1703
|
-
return;
|
|
1704
|
-
previewItem.label = nextPreview.label;
|
|
1705
|
-
previewItem.hint = nextPreview.hint;
|
|
1706
|
-
};
|
|
1707
|
-
const result = await select(items, {
|
|
1708
|
-
message: UI_COPY.settings.accountListTitle,
|
|
1709
|
-
subtitle: UI_COPY.settings.accountListSubtitle,
|
|
1710
|
-
help: UI_COPY.settings.accountListHelp,
|
|
1711
|
-
clearScreen: true,
|
|
1712
|
-
theme: ui.theme,
|
|
1713
|
-
selectedEmphasis: "minimal",
|
|
1714
|
-
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
|
|
1715
|
-
onCursorChange: ({ cursor }) => {
|
|
1716
|
-
const focusedItem = items[cursor];
|
|
1717
|
-
if (focusedItem?.value.type === "toggle") {
|
|
1718
|
-
focusKey = focusedItem.value.key;
|
|
1719
|
-
}
|
|
1720
|
-
else if (focusedItem?.value.type === "cycle-sort-mode") {
|
|
1721
|
-
focusKey = "menuSortMode";
|
|
1722
|
-
}
|
|
1723
|
-
else if (focusedItem?.value.type === "cycle-layout-mode") {
|
|
1724
|
-
focusKey = "menuLayoutMode";
|
|
1725
|
-
}
|
|
1726
|
-
updateFocusedPreview(cursor);
|
|
1727
|
-
},
|
|
1728
|
-
onInput: (raw) => {
|
|
1729
|
-
const lower = raw.toLowerCase();
|
|
1730
|
-
if (lower === "q")
|
|
1731
|
-
return { type: "save" };
|
|
1732
|
-
if (lower === "s")
|
|
1733
|
-
return { type: "save" };
|
|
1734
|
-
if (lower === "r")
|
|
1735
|
-
return { type: "reset" };
|
|
1736
|
-
if (lower === "m")
|
|
1737
|
-
return { type: "cycle-sort-mode" };
|
|
1738
|
-
if (lower === "l")
|
|
1739
|
-
return { type: "cycle-layout-mode" };
|
|
1740
|
-
const parsed = Number.parseInt(raw, 10);
|
|
1741
|
-
if (Number.isFinite(parsed) && parsed >= 1 && parsed <= DASHBOARD_DISPLAY_OPTIONS.length) {
|
|
1742
|
-
const target = DASHBOARD_DISPLAY_OPTIONS[parsed - 1];
|
|
1743
|
-
if (target) {
|
|
1744
|
-
return { type: "toggle", key: target.key };
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
if (parsed === DASHBOARD_DISPLAY_OPTIONS.length + 1) {
|
|
1748
|
-
return { type: "cycle-sort-mode" };
|
|
1749
|
-
}
|
|
1750
|
-
if (parsed === DASHBOARD_DISPLAY_OPTIONS.length + 2) {
|
|
1751
|
-
return { type: "cycle-layout-mode" };
|
|
1752
|
-
}
|
|
1753
|
-
return undefined;
|
|
1754
|
-
},
|
|
1755
|
-
});
|
|
1756
|
-
if (!result || result.type === "cancel") {
|
|
1757
|
-
return null;
|
|
1758
|
-
}
|
|
1759
|
-
if (result.type === "save") {
|
|
1760
|
-
return draft;
|
|
1761
|
-
}
|
|
1762
|
-
if (result.type === "reset") {
|
|
1763
|
-
draft = cloneDashboardSettings(DEFAULT_DASHBOARD_DISPLAY_SETTINGS);
|
|
1764
|
-
focusKey = DASHBOARD_DISPLAY_OPTIONS[0]?.key ?? focusKey;
|
|
1765
|
-
await saveDashboardDisplaySettings(draft);
|
|
1766
|
-
continue;
|
|
1767
|
-
}
|
|
1768
|
-
if (result.type === "cycle-sort-mode") {
|
|
1769
|
-
const currentMode = draft.menuSortMode ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ?? "ready-first");
|
|
1770
|
-
const nextMode = currentMode === "ready-first"
|
|
1771
|
-
? "manual"
|
|
1772
|
-
: "ready-first";
|
|
1773
|
-
draft = {
|
|
1774
|
-
...draft,
|
|
1775
|
-
menuSortMode: nextMode,
|
|
1776
|
-
menuSortEnabled: nextMode === "ready-first"
|
|
1777
|
-
? true
|
|
1778
|
-
: (draft.menuSortEnabled ?? (DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ?? true)),
|
|
1779
|
-
};
|
|
1780
|
-
focusKey = "menuSortMode";
|
|
1781
|
-
await saveDashboardDisplaySettings(draft);
|
|
1782
|
-
continue;
|
|
1783
|
-
}
|
|
1784
|
-
if (result.type === "cycle-layout-mode") {
|
|
1785
|
-
const currentLayout = resolveMenuLayoutMode(draft);
|
|
1786
|
-
const nextLayout = currentLayout === "compact-details" ? "expanded-rows" : "compact-details";
|
|
1787
|
-
draft = {
|
|
1788
|
-
...draft,
|
|
1789
|
-
menuLayoutMode: nextLayout,
|
|
1790
|
-
menuShowDetailsForUnselectedRows: nextLayout === "expanded-rows",
|
|
1791
|
-
};
|
|
1792
|
-
focusKey = "menuLayoutMode";
|
|
1793
|
-
await saveDashboardDisplaySettings(draft);
|
|
1794
|
-
continue;
|
|
1795
|
-
}
|
|
1796
|
-
focusKey = result.key;
|
|
1797
|
-
draft = {
|
|
1798
|
-
...draft,
|
|
1799
|
-
[result.key]: !draft[result.key],
|
|
1800
|
-
};
|
|
1801
|
-
await saveDashboardDisplaySettings(draft);
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
async function configureDashboardDisplaySettings(currentSettings) {
|
|
1805
|
-
const current = currentSettings ?? await loadDashboardDisplaySettings();
|
|
1806
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1807
|
-
console.log("Settings require interactive mode.");
|
|
1808
|
-
console.log(`Settings file: ${getDashboardSettingsPath()}`);
|
|
1809
|
-
return current;
|
|
1810
|
-
}
|
|
1811
|
-
const selected = await promptDashboardDisplaySettings(current);
|
|
1812
|
-
if (!selected)
|
|
1813
|
-
return current;
|
|
1814
|
-
if (dashboardSettingsEqual(current, selected))
|
|
1815
|
-
return current;
|
|
1816
|
-
await saveDashboardDisplaySettings(selected);
|
|
1817
|
-
applyUiThemeFromDashboardSettings(selected);
|
|
1818
|
-
return selected;
|
|
1819
|
-
}
|
|
1820
|
-
function reorderField(fields, key, direction) {
|
|
1821
|
-
const index = fields.indexOf(key);
|
|
1822
|
-
if (index < 0)
|
|
1823
|
-
return fields;
|
|
1824
|
-
const target = index + direction;
|
|
1825
|
-
if (target < 0 || target >= fields.length)
|
|
1826
|
-
return fields;
|
|
1827
|
-
const next = [...fields];
|
|
1828
|
-
const current = next[index];
|
|
1829
|
-
const swap = next[target];
|
|
1830
|
-
if (!current || !swap)
|
|
1831
|
-
return fields;
|
|
1832
|
-
next[index] = swap;
|
|
1833
|
-
next[target] = current;
|
|
1834
|
-
return next;
|
|
1835
|
-
}
|
|
1836
|
-
async function promptStatuslineSettings(initial) {
|
|
1837
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1838
|
-
return null;
|
|
1839
|
-
}
|
|
1840
|
-
const ui = getUiRuntimeOptions();
|
|
1841
|
-
let draft = cloneDashboardSettings(initial);
|
|
1842
|
-
let focusKey = draft.menuStatuslineFields?.[0] ?? "last-used";
|
|
1843
|
-
while (true) {
|
|
1844
|
-
const preview = buildAccountListPreview(draft, ui, focusKey);
|
|
1845
|
-
const selectedSet = new Set(normalizeStatuslineFields(draft.menuStatuslineFields));
|
|
1846
|
-
const ordered = normalizeStatuslineFields(draft.menuStatuslineFields);
|
|
1847
|
-
const orderMap = new Map();
|
|
1848
|
-
for (let index = 0; index < ordered.length; index += 1) {
|
|
1849
|
-
const key = ordered[index];
|
|
1850
|
-
if (key)
|
|
1851
|
-
orderMap.set(key, index + 1);
|
|
1852
|
-
}
|
|
1853
|
-
const optionItems = STATUSLINE_FIELD_OPTIONS.map((option, index) => {
|
|
1854
|
-
const enabled = selectedSet.has(option.key);
|
|
1855
|
-
const rank = orderMap.get(option.key);
|
|
1856
|
-
const label = `${formatDashboardSettingState(enabled)} ${index + 1}. ${option.label}${rank ? ` (order ${rank})` : ""}`;
|
|
1857
|
-
return {
|
|
1858
|
-
label,
|
|
1859
|
-
hint: option.description,
|
|
1860
|
-
value: { type: "toggle", key: option.key },
|
|
1861
|
-
color: enabled ? "green" : "yellow",
|
|
1862
|
-
};
|
|
1863
|
-
});
|
|
1864
|
-
const items = [
|
|
1865
|
-
{ label: UI_COPY.settings.previewHeading, value: { type: "cancel" }, kind: "heading" },
|
|
1866
|
-
{
|
|
1867
|
-
label: preview.label,
|
|
1868
|
-
hint: preview.hint,
|
|
1869
|
-
value: { type: "cancel" },
|
|
1870
|
-
color: "green",
|
|
1871
|
-
disabled: true,
|
|
1872
|
-
hideUnavailableSuffix: true,
|
|
1873
|
-
},
|
|
1874
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
1875
|
-
{ label: UI_COPY.settings.displayHeading, value: { type: "cancel" }, kind: "heading" },
|
|
1876
|
-
...optionItems,
|
|
1877
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
1878
|
-
{ label: UI_COPY.settings.moveUp, value: { type: "move-up", key: focusKey }, color: "green" },
|
|
1879
|
-
{ label: UI_COPY.settings.moveDown, value: { type: "move-down", key: focusKey }, color: "green" },
|
|
1880
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
1881
|
-
{ label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
|
|
1882
|
-
{ label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
|
|
1883
|
-
{ label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
|
|
1884
|
-
];
|
|
1885
|
-
const initialCursor = items.findIndex((item) => item.value.type === "toggle" && item.value.key === focusKey);
|
|
1886
|
-
const updateFocusedPreview = (cursor) => {
|
|
1887
|
-
const focusedItem = items[cursor];
|
|
1888
|
-
const focusedKey = focusedItem?.value.type === "toggle" ? focusedItem.value.key : focusKey;
|
|
1889
|
-
const nextPreview = buildAccountListPreview(draft, ui, focusedKey);
|
|
1890
|
-
const previewItem = items[1];
|
|
1891
|
-
if (!previewItem)
|
|
1892
|
-
return;
|
|
1893
|
-
previewItem.label = nextPreview.label;
|
|
1894
|
-
previewItem.hint = nextPreview.hint;
|
|
1895
|
-
};
|
|
1896
|
-
const result = await select(items, {
|
|
1897
|
-
message: UI_COPY.settings.summaryTitle,
|
|
1898
|
-
subtitle: UI_COPY.settings.summarySubtitle,
|
|
1899
|
-
help: UI_COPY.settings.summaryHelp,
|
|
1900
|
-
clearScreen: true,
|
|
1901
|
-
theme: ui.theme,
|
|
1902
|
-
selectedEmphasis: "minimal",
|
|
1903
|
-
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
|
|
1904
|
-
onCursorChange: ({ cursor }) => {
|
|
1905
|
-
const focusedItem = items[cursor];
|
|
1906
|
-
if (focusedItem?.value.type === "toggle") {
|
|
1907
|
-
focusKey = focusedItem.value.key;
|
|
1908
|
-
}
|
|
1909
|
-
updateFocusedPreview(cursor);
|
|
1910
|
-
},
|
|
1911
|
-
onInput: (raw) => {
|
|
1912
|
-
const lower = raw.toLowerCase();
|
|
1913
|
-
if (lower === "q")
|
|
1914
|
-
return { type: "save" };
|
|
1915
|
-
if (lower === "s")
|
|
1916
|
-
return { type: "save" };
|
|
1917
|
-
if (lower === "r")
|
|
1918
|
-
return { type: "reset" };
|
|
1919
|
-
if (lower === "[")
|
|
1920
|
-
return { type: "move-up", key: focusKey };
|
|
1921
|
-
if (lower === "]")
|
|
1922
|
-
return { type: "move-down", key: focusKey };
|
|
1923
|
-
const parsed = Number.parseInt(raw, 10);
|
|
1924
|
-
if (Number.isFinite(parsed) && parsed >= 1 && parsed <= STATUSLINE_FIELD_OPTIONS.length) {
|
|
1925
|
-
const target = STATUSLINE_FIELD_OPTIONS[parsed - 1];
|
|
1926
|
-
if (target) {
|
|
1927
|
-
return { type: "toggle", key: target.key };
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
return undefined;
|
|
1931
|
-
},
|
|
1932
|
-
});
|
|
1933
|
-
if (!result || result.type === "cancel") {
|
|
1934
|
-
return null;
|
|
1935
|
-
}
|
|
1936
|
-
if (result.type === "save") {
|
|
1937
|
-
return draft;
|
|
1938
|
-
}
|
|
1939
|
-
if (result.type === "reset") {
|
|
1940
|
-
draft = cloneDashboardSettings(DEFAULT_DASHBOARD_DISPLAY_SETTINGS);
|
|
1941
|
-
focusKey = draft.menuStatuslineFields?.[0] ?? "last-used";
|
|
1942
|
-
await saveDashboardDisplaySettings(draft);
|
|
1943
|
-
continue;
|
|
1944
|
-
}
|
|
1945
|
-
if (result.type === "move-up") {
|
|
1946
|
-
draft = {
|
|
1947
|
-
...draft,
|
|
1948
|
-
menuStatuslineFields: reorderField(normalizeStatuslineFields(draft.menuStatuslineFields), result.key, -1),
|
|
1949
|
-
};
|
|
1950
|
-
focusKey = result.key;
|
|
1951
|
-
await saveDashboardDisplaySettings(draft);
|
|
1952
|
-
continue;
|
|
1953
|
-
}
|
|
1954
|
-
if (result.type === "move-down") {
|
|
1955
|
-
draft = {
|
|
1956
|
-
...draft,
|
|
1957
|
-
menuStatuslineFields: reorderField(normalizeStatuslineFields(draft.menuStatuslineFields), result.key, 1),
|
|
1958
|
-
};
|
|
1959
|
-
focusKey = result.key;
|
|
1960
|
-
await saveDashboardDisplaySettings(draft);
|
|
1961
|
-
continue;
|
|
1962
|
-
}
|
|
1963
|
-
focusKey = result.key;
|
|
1964
|
-
const fields = normalizeStatuslineFields(draft.menuStatuslineFields);
|
|
1965
|
-
const isEnabled = fields.includes(result.key);
|
|
1966
|
-
if (isEnabled) {
|
|
1967
|
-
const next = fields.filter((field) => field !== result.key);
|
|
1968
|
-
draft = {
|
|
1969
|
-
...draft,
|
|
1970
|
-
menuStatuslineFields: next.length > 0 ? next : [result.key],
|
|
1971
|
-
};
|
|
1972
|
-
}
|
|
1973
|
-
else {
|
|
1974
|
-
draft = {
|
|
1975
|
-
...draft,
|
|
1976
|
-
menuStatuslineFields: [...fields, result.key],
|
|
1977
|
-
};
|
|
1978
|
-
}
|
|
1979
|
-
await saveDashboardDisplaySettings(draft);
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
async function configureStatuslineSettings(currentSettings) {
|
|
1983
|
-
const current = currentSettings ?? await loadDashboardDisplaySettings();
|
|
1984
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1985
|
-
console.log("Settings require interactive mode.");
|
|
1986
|
-
console.log(`Settings file: ${getDashboardSettingsPath()}`);
|
|
1987
|
-
return current;
|
|
1988
|
-
}
|
|
1989
|
-
const selected = await promptStatuslineSettings(current);
|
|
1990
|
-
if (!selected)
|
|
1991
|
-
return current;
|
|
1992
|
-
if (dashboardSettingsEqual(current, selected))
|
|
1993
|
-
return current;
|
|
1994
|
-
await saveDashboardDisplaySettings(selected);
|
|
1995
|
-
applyUiThemeFromDashboardSettings(selected);
|
|
1996
|
-
return selected;
|
|
1997
|
-
}
|
|
1998
|
-
function formatDelayLabel(delayMs) {
|
|
1999
|
-
return delayMs <= 0 ? "Instant return" : `${Math.round(delayMs / 1000)}s auto-return`;
|
|
2000
|
-
}
|
|
2001
|
-
async function promptBehaviorSettings(initial) {
|
|
2002
|
-
if (!input.isTTY || !output.isTTY)
|
|
2003
|
-
return null;
|
|
2004
|
-
const ui = getUiRuntimeOptions();
|
|
2005
|
-
let draft = cloneDashboardSettings(initial);
|
|
2006
|
-
let focus = {
|
|
2007
|
-
type: "set-delay",
|
|
2008
|
-
delayMs: draft.actionAutoReturnMs ?? 2_000,
|
|
2009
|
-
};
|
|
2010
|
-
while (true) {
|
|
2011
|
-
const currentDelay = draft.actionAutoReturnMs ?? 2_000;
|
|
2012
|
-
const pauseOnKey = draft.actionPauseOnKey ?? true;
|
|
2013
|
-
const autoFetchLimits = draft.menuAutoFetchLimits ?? true;
|
|
2014
|
-
const fetchStatusVisible = draft.menuShowFetchStatus ?? true;
|
|
2015
|
-
const menuQuotaTtlMs = draft.menuQuotaTtlMs ?? 5 * 60_000;
|
|
2016
|
-
const delayItems = AUTO_RETURN_OPTIONS_MS.map((delayMs) => {
|
|
2017
|
-
const color = currentDelay === delayMs ? "green" : "yellow";
|
|
2018
|
-
return {
|
|
2019
|
-
label: `${currentDelay === delayMs ? "[x]" : "[ ]"} ${formatDelayLabel(delayMs)}`,
|
|
2020
|
-
hint: delayMs === 1_000
|
|
2021
|
-
? "Fastest loop for frequent actions."
|
|
2022
|
-
: delayMs === 2_000
|
|
2023
|
-
? "Balanced default for most users."
|
|
2024
|
-
: "More time to read action output.",
|
|
2025
|
-
value: { type: "set-delay", delayMs },
|
|
2026
|
-
color,
|
|
2027
|
-
};
|
|
2028
|
-
});
|
|
2029
|
-
const pauseColor = pauseOnKey ? "green" : "yellow";
|
|
2030
|
-
const items = [
|
|
2031
|
-
{ label: UI_COPY.settings.actionTiming, value: { type: "cancel" }, kind: "heading" },
|
|
2032
|
-
...delayItems,
|
|
2033
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
2034
|
-
{
|
|
2035
|
-
label: `${pauseOnKey ? "[x]" : "[ ]"} Pause on key press`,
|
|
2036
|
-
hint: "Press any key to stop auto-return.",
|
|
2037
|
-
value: { type: "toggle-pause" },
|
|
2038
|
-
color: pauseColor,
|
|
2039
|
-
},
|
|
2040
|
-
{
|
|
2041
|
-
label: `${autoFetchLimits ? "[x]" : "[ ]"} Auto-fetch limits on menu open (5m cache)`,
|
|
2042
|
-
hint: "Refreshes account limits automatically when opening the menu.",
|
|
2043
|
-
value: { type: "toggle-menu-limit-fetch" },
|
|
2044
|
-
color: autoFetchLimits ? "green" : "yellow",
|
|
2045
|
-
},
|
|
2046
|
-
{
|
|
2047
|
-
label: `${fetchStatusVisible ? "[x]" : "[ ]"} Show limit refresh status`,
|
|
2048
|
-
hint: "Shows background fetch progress like [2/7] in menu subtitle.",
|
|
2049
|
-
value: { type: "toggle-menu-fetch-status" },
|
|
2050
|
-
color: fetchStatusVisible ? "green" : "yellow",
|
|
2051
|
-
},
|
|
2052
|
-
{
|
|
2053
|
-
label: `Limit cache TTL: ${formatMenuQuotaTtl(menuQuotaTtlMs)}`,
|
|
2054
|
-
hint: "How fresh cached quota data must be before refresh runs.",
|
|
2055
|
-
value: { type: "set-menu-quota-ttl", ttlMs: menuQuotaTtlMs },
|
|
2056
|
-
color: "yellow",
|
|
2057
|
-
},
|
|
2058
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
2059
|
-
{ label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
|
|
2060
|
-
{ label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
|
|
2061
|
-
{ label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
|
|
2062
|
-
];
|
|
2063
|
-
const initialCursor = items.findIndex((item) => {
|
|
2064
|
-
const value = item.value;
|
|
2065
|
-
if (value.type !== focus.type)
|
|
2066
|
-
return false;
|
|
2067
|
-
if (value.type === "set-delay" && focus.type === "set-delay") {
|
|
2068
|
-
return value.delayMs === focus.delayMs;
|
|
2069
|
-
}
|
|
2070
|
-
return true;
|
|
2071
|
-
});
|
|
2072
|
-
const result = await select(items, {
|
|
2073
|
-
message: UI_COPY.settings.behaviorTitle,
|
|
2074
|
-
subtitle: UI_COPY.settings.behaviorSubtitle,
|
|
2075
|
-
help: UI_COPY.settings.behaviorHelp,
|
|
2076
|
-
clearScreen: true,
|
|
2077
|
-
theme: ui.theme,
|
|
2078
|
-
selectedEmphasis: "minimal",
|
|
2079
|
-
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
|
|
2080
|
-
onCursorChange: ({ cursor }) => {
|
|
2081
|
-
const item = items[cursor];
|
|
2082
|
-
if (item && !item.separator && item.kind !== "heading") {
|
|
2083
|
-
focus = item.value;
|
|
2084
|
-
}
|
|
2085
|
-
},
|
|
2086
|
-
onInput: (raw) => {
|
|
2087
|
-
const lower = raw.toLowerCase();
|
|
2088
|
-
if (lower === "q")
|
|
2089
|
-
return { type: "save" };
|
|
2090
|
-
if (lower === "s")
|
|
2091
|
-
return { type: "save" };
|
|
2092
|
-
if (lower === "r")
|
|
2093
|
-
return { type: "reset" };
|
|
2094
|
-
if (lower === "p")
|
|
2095
|
-
return { type: "toggle-pause" };
|
|
2096
|
-
if (lower === "l")
|
|
2097
|
-
return { type: "toggle-menu-limit-fetch" };
|
|
2098
|
-
if (lower === "f")
|
|
2099
|
-
return { type: "toggle-menu-fetch-status" };
|
|
2100
|
-
if (lower === "t")
|
|
2101
|
-
return { type: "set-menu-quota-ttl", ttlMs: menuQuotaTtlMs };
|
|
2102
|
-
const parsed = Number.parseInt(raw, 10);
|
|
2103
|
-
if (Number.isFinite(parsed) && parsed >= 1 && parsed <= AUTO_RETURN_OPTIONS_MS.length) {
|
|
2104
|
-
const delayMs = AUTO_RETURN_OPTIONS_MS[parsed - 1];
|
|
2105
|
-
if (typeof delayMs === "number")
|
|
2106
|
-
return { type: "set-delay", delayMs };
|
|
2107
|
-
}
|
|
2108
|
-
return undefined;
|
|
2109
|
-
},
|
|
2110
|
-
});
|
|
2111
|
-
if (!result || result.type === "cancel")
|
|
2112
|
-
return null;
|
|
2113
|
-
if (result.type === "save")
|
|
2114
|
-
return draft;
|
|
2115
|
-
if (result.type === "reset") {
|
|
2116
|
-
draft = cloneDashboardSettings(DEFAULT_DASHBOARD_DISPLAY_SETTINGS);
|
|
2117
|
-
focus = { type: "set-delay", delayMs: draft.actionAutoReturnMs ?? 2_000 };
|
|
2118
|
-
await saveDashboardDisplaySettings(draft);
|
|
2119
|
-
continue;
|
|
2120
|
-
}
|
|
2121
|
-
if (result.type === "toggle-pause") {
|
|
2122
|
-
draft = {
|
|
2123
|
-
...draft,
|
|
2124
|
-
actionPauseOnKey: !(draft.actionPauseOnKey ?? true),
|
|
2125
|
-
};
|
|
2126
|
-
focus = result;
|
|
2127
|
-
await saveDashboardDisplaySettings(draft);
|
|
2128
|
-
continue;
|
|
2129
|
-
}
|
|
2130
|
-
if (result.type === "toggle-menu-limit-fetch") {
|
|
2131
|
-
draft = {
|
|
2132
|
-
...draft,
|
|
2133
|
-
menuAutoFetchLimits: !(draft.menuAutoFetchLimits ?? true),
|
|
2134
|
-
};
|
|
2135
|
-
focus = result;
|
|
2136
|
-
await saveDashboardDisplaySettings(draft);
|
|
2137
|
-
continue;
|
|
2138
|
-
}
|
|
2139
|
-
if (result.type === "toggle-menu-fetch-status") {
|
|
2140
|
-
draft = {
|
|
2141
|
-
...draft,
|
|
2142
|
-
menuShowFetchStatus: !(draft.menuShowFetchStatus ?? true),
|
|
2143
|
-
};
|
|
2144
|
-
focus = result;
|
|
2145
|
-
await saveDashboardDisplaySettings(draft);
|
|
2146
|
-
continue;
|
|
2147
|
-
}
|
|
2148
|
-
if (result.type === "set-menu-quota-ttl") {
|
|
2149
|
-
const currentIndex = MENU_QUOTA_TTL_OPTIONS_MS.findIndex((value) => value === menuQuotaTtlMs);
|
|
2150
|
-
const nextIndex = currentIndex < 0
|
|
2151
|
-
? 0
|
|
2152
|
-
: (currentIndex + 1) % MENU_QUOTA_TTL_OPTIONS_MS.length;
|
|
2153
|
-
const nextTtl = MENU_QUOTA_TTL_OPTIONS_MS[nextIndex] ?? MENU_QUOTA_TTL_OPTIONS_MS[0] ?? menuQuotaTtlMs;
|
|
2154
|
-
draft = {
|
|
2155
|
-
...draft,
|
|
2156
|
-
menuQuotaTtlMs: nextTtl,
|
|
2157
|
-
};
|
|
2158
|
-
focus = { type: "set-menu-quota-ttl", ttlMs: nextTtl };
|
|
2159
|
-
await saveDashboardDisplaySettings(draft);
|
|
2160
|
-
continue;
|
|
2161
|
-
}
|
|
2162
|
-
draft = {
|
|
2163
|
-
...draft,
|
|
2164
|
-
actionAutoReturnMs: result.delayMs,
|
|
2165
|
-
};
|
|
2166
|
-
focus = result;
|
|
2167
|
-
await saveDashboardDisplaySettings(draft);
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
async function promptThemeSettings(initial) {
|
|
2171
|
-
if (!input.isTTY || !output.isTTY)
|
|
2172
|
-
return null;
|
|
2173
|
-
const ui = getUiRuntimeOptions();
|
|
2174
|
-
let draft = cloneDashboardSettings(initial);
|
|
2175
|
-
let focus = {
|
|
2176
|
-
type: "set-palette",
|
|
2177
|
-
palette: draft.uiThemePreset ?? "green",
|
|
2178
|
-
};
|
|
2179
|
-
while (true) {
|
|
2180
|
-
const palette = draft.uiThemePreset ?? "green";
|
|
2181
|
-
const accent = draft.uiAccentColor ?? "green";
|
|
2182
|
-
const paletteItems = THEME_PRESET_OPTIONS.map((candidate, index) => {
|
|
2183
|
-
const color = palette === candidate ? "green" : "yellow";
|
|
2184
|
-
return {
|
|
2185
|
-
label: `${palette === candidate ? "[x]" : "[ ]"} ${index + 1}. ${candidate === "green" ? "Green base" : "Blue base"}`,
|
|
2186
|
-
hint: candidate === "green" ? "High-contrast default." : "Codex-style blue look.",
|
|
2187
|
-
value: { type: "set-palette", palette: candidate },
|
|
2188
|
-
color,
|
|
2189
|
-
};
|
|
2190
|
-
});
|
|
2191
|
-
const accentItems = ACCENT_COLOR_OPTIONS.map((candidate) => {
|
|
2192
|
-
const color = accent === candidate ? "green" : "yellow";
|
|
2193
|
-
return {
|
|
2194
|
-
label: `${accent === candidate ? "[x]" : "[ ]"} ${candidate}`,
|
|
2195
|
-
value: { type: "set-accent", accent: candidate },
|
|
2196
|
-
color,
|
|
2197
|
-
};
|
|
2198
|
-
});
|
|
2199
|
-
const items = [
|
|
2200
|
-
{ label: UI_COPY.settings.baseTheme, value: { type: "cancel" }, kind: "heading" },
|
|
2201
|
-
...paletteItems,
|
|
2202
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
2203
|
-
{ label: UI_COPY.settings.accentColor, value: { type: "cancel" }, kind: "heading" },
|
|
2204
|
-
...accentItems,
|
|
2205
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
2206
|
-
{ label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
|
|
2207
|
-
{ label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
|
|
2208
|
-
{ label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
|
|
2209
|
-
];
|
|
2210
|
-
const initialCursor = items.findIndex((item) => {
|
|
2211
|
-
const value = item.value;
|
|
2212
|
-
if (value.type !== focus.type)
|
|
2213
|
-
return false;
|
|
2214
|
-
if (value.type === "set-palette" && focus.type === "set-palette") {
|
|
2215
|
-
return value.palette === focus.palette;
|
|
2216
|
-
}
|
|
2217
|
-
if (value.type === "set-accent" && focus.type === "set-accent") {
|
|
2218
|
-
return value.accent === focus.accent;
|
|
2219
|
-
}
|
|
2220
|
-
return true;
|
|
2221
|
-
});
|
|
2222
|
-
const result = await select(items, {
|
|
2223
|
-
message: UI_COPY.settings.themeTitle,
|
|
2224
|
-
subtitle: UI_COPY.settings.themeSubtitle,
|
|
2225
|
-
help: UI_COPY.settings.themeHelp,
|
|
2226
|
-
clearScreen: true,
|
|
2227
|
-
theme: ui.theme,
|
|
2228
|
-
selectedEmphasis: "minimal",
|
|
2229
|
-
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
|
|
2230
|
-
onCursorChange: ({ cursor }) => {
|
|
2231
|
-
const item = items[cursor];
|
|
2232
|
-
if (item && !item.separator && item.kind !== "heading") {
|
|
2233
|
-
focus = item.value;
|
|
2234
|
-
}
|
|
2235
|
-
},
|
|
2236
|
-
onInput: (raw) => {
|
|
2237
|
-
const lower = raw.toLowerCase();
|
|
2238
|
-
if (lower === "q")
|
|
2239
|
-
return { type: "save" };
|
|
2240
|
-
if (lower === "s")
|
|
2241
|
-
return { type: "save" };
|
|
2242
|
-
if (lower === "r")
|
|
2243
|
-
return { type: "reset" };
|
|
2244
|
-
if (raw === "1")
|
|
2245
|
-
return { type: "set-palette", palette: "green" };
|
|
2246
|
-
if (raw === "2")
|
|
2247
|
-
return { type: "set-palette", palette: "blue" };
|
|
2248
|
-
return undefined;
|
|
2249
|
-
},
|
|
2250
|
-
});
|
|
2251
|
-
if (!result || result.type === "cancel")
|
|
2252
|
-
return null;
|
|
2253
|
-
if (result.type === "save")
|
|
2254
|
-
return draft;
|
|
2255
|
-
if (result.type === "reset") {
|
|
2256
|
-
draft = cloneDashboardSettings(DEFAULT_DASHBOARD_DISPLAY_SETTINGS);
|
|
2257
|
-
focus = { type: "set-palette", palette: draft.uiThemePreset ?? "green" };
|
|
2258
|
-
await saveDashboardDisplaySettings(draft);
|
|
2259
|
-
continue;
|
|
2260
|
-
}
|
|
2261
|
-
if (result.type === "set-palette") {
|
|
2262
|
-
draft = { ...draft, uiThemePreset: result.palette };
|
|
2263
|
-
focus = result;
|
|
2264
|
-
applyUiThemeFromDashboardSettings(draft);
|
|
2265
|
-
await saveDashboardDisplaySettings(draft);
|
|
2266
|
-
continue;
|
|
2267
|
-
}
|
|
2268
|
-
draft = { ...draft, uiAccentColor: result.accent };
|
|
2269
|
-
focus = result;
|
|
2270
|
-
applyUiThemeFromDashboardSettings(draft);
|
|
2271
|
-
await saveDashboardDisplaySettings(draft);
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
function resolveFocusedBackendNumberKey(focus, numberOptions = BACKEND_NUMBER_OPTIONS) {
|
|
2275
|
-
const numberKeys = new Set(numberOptions.map((option) => option.key));
|
|
2276
|
-
if (focus && numberKeys.has(focus)) {
|
|
2277
|
-
return focus;
|
|
2278
|
-
}
|
|
2279
|
-
return numberOptions[0]?.key ?? "fetchTimeoutMs";
|
|
2280
|
-
}
|
|
2281
|
-
function getBackendCategory(key) {
|
|
2282
|
-
return BACKEND_CATEGORY_OPTIONS.find((category) => category.key === key) ?? null;
|
|
2283
|
-
}
|
|
2284
|
-
function getBackendCategoryInitialFocus(category) {
|
|
2285
|
-
const firstToggle = category.toggleKeys[0];
|
|
2286
|
-
if (firstToggle)
|
|
2287
|
-
return firstToggle;
|
|
2288
|
-
return category.numberKeys[0] ?? null;
|
|
2289
|
-
}
|
|
2290
|
-
function applyBackendCategoryDefaults(draft, category) {
|
|
2291
|
-
const next = { ...draft };
|
|
2292
|
-
for (const key of category.toggleKeys) {
|
|
2293
|
-
next[key] = BACKEND_DEFAULTS[key] ?? false;
|
|
2294
|
-
}
|
|
2295
|
-
for (const key of category.numberKeys) {
|
|
2296
|
-
const option = BACKEND_NUMBER_OPTION_BY_KEY.get(key);
|
|
2297
|
-
const fallback = option?.min ?? 0;
|
|
2298
|
-
next[key] = BACKEND_DEFAULTS[key] ?? fallback;
|
|
2299
|
-
}
|
|
2300
|
-
return next;
|
|
2301
|
-
}
|
|
2302
|
-
async function promptBackendCategorySettings(initial, category, initialFocus) {
|
|
2303
|
-
const ui = getUiRuntimeOptions();
|
|
2304
|
-
let draft = cloneBackendPluginConfig(initial);
|
|
2305
|
-
let focusKey = initialFocus;
|
|
2306
|
-
if (!focusKey ||
|
|
2307
|
-
(!category.toggleKeys.includes(focusKey) &&
|
|
2308
|
-
!category.numberKeys.includes(focusKey))) {
|
|
2309
|
-
focusKey = getBackendCategoryInitialFocus(category);
|
|
2310
|
-
}
|
|
2311
|
-
const toggleOptions = category.toggleKeys
|
|
2312
|
-
.map((key) => BACKEND_TOGGLE_OPTION_BY_KEY.get(key))
|
|
2313
|
-
.filter((option) => !!option);
|
|
2314
|
-
const numberOptions = category.numberKeys
|
|
2315
|
-
.map((key) => BACKEND_NUMBER_OPTION_BY_KEY.get(key))
|
|
2316
|
-
.filter((option) => !!option);
|
|
2317
|
-
while (true) {
|
|
2318
|
-
const preview = buildBackendSettingsPreview(draft, ui, focusKey);
|
|
2319
|
-
const toggleItems = toggleOptions.map((option, index) => {
|
|
2320
|
-
const enabled = draft[option.key] ?? BACKEND_DEFAULTS[option.key] ?? false;
|
|
2321
|
-
return {
|
|
2322
|
-
label: `${formatDashboardSettingState(enabled)} ${index + 1}. ${option.label}`,
|
|
2323
|
-
hint: option.description,
|
|
2324
|
-
value: { type: "toggle", key: option.key },
|
|
2325
|
-
color: enabled ? "green" : "yellow",
|
|
2326
|
-
};
|
|
2327
|
-
});
|
|
2328
|
-
const numberItems = numberOptions.map((option) => {
|
|
2329
|
-
const rawValue = draft[option.key] ?? BACKEND_DEFAULTS[option.key] ?? option.min;
|
|
2330
|
-
const numericValue = typeof rawValue === "number" && Number.isFinite(rawValue)
|
|
2331
|
-
? rawValue
|
|
2332
|
-
: option.min;
|
|
2333
|
-
const clampedValue = clampBackendNumber(option, numericValue);
|
|
2334
|
-
const valueLabel = formatBackendNumberValue(option, clampedValue);
|
|
2335
|
-
return {
|
|
2336
|
-
label: `${option.label}: ${valueLabel}`,
|
|
2337
|
-
hint: `${option.description} Step ${formatBackendNumberValue(option, option.step)}.`,
|
|
2338
|
-
value: { type: "bump", key: option.key, direction: 1 },
|
|
2339
|
-
color: "yellow",
|
|
2340
|
-
};
|
|
2341
|
-
});
|
|
2342
|
-
const focusedNumberKey = resolveFocusedBackendNumberKey(focusKey, numberOptions);
|
|
2343
|
-
const items = [
|
|
2344
|
-
{ label: UI_COPY.settings.previewHeading, value: { type: "back" }, kind: "heading" },
|
|
2345
|
-
{
|
|
2346
|
-
label: preview.label,
|
|
2347
|
-
hint: preview.hint,
|
|
2348
|
-
value: { type: "back" },
|
|
2349
|
-
disabled: true,
|
|
2350
|
-
color: "green",
|
|
2351
|
-
hideUnavailableSuffix: true,
|
|
2352
|
-
},
|
|
2353
|
-
{ label: "", value: { type: "back" }, separator: true },
|
|
2354
|
-
{ label: UI_COPY.settings.backendToggleHeading, value: { type: "back" }, kind: "heading" },
|
|
2355
|
-
...toggleItems,
|
|
2356
|
-
{ label: "", value: { type: "back" }, separator: true },
|
|
2357
|
-
{ label: UI_COPY.settings.backendNumberHeading, value: { type: "back" }, kind: "heading" },
|
|
2358
|
-
...numberItems,
|
|
2359
|
-
];
|
|
2360
|
-
if (numberOptions.length > 0) {
|
|
2361
|
-
items.push({ label: "", value: { type: "back" }, separator: true });
|
|
2362
|
-
items.push({
|
|
2363
|
-
label: UI_COPY.settings.backendDecrease,
|
|
2364
|
-
value: { type: "bump", key: focusedNumberKey, direction: -1 },
|
|
2365
|
-
color: "yellow",
|
|
2366
|
-
});
|
|
2367
|
-
items.push({
|
|
2368
|
-
label: UI_COPY.settings.backendIncrease,
|
|
2369
|
-
value: { type: "bump", key: focusedNumberKey, direction: 1 },
|
|
2370
|
-
color: "green",
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
items.push({ label: "", value: { type: "back" }, separator: true });
|
|
2374
|
-
items.push({ label: UI_COPY.settings.backendResetCategory, value: { type: "reset-category" }, color: "yellow" });
|
|
2375
|
-
items.push({ label: UI_COPY.settings.backendBackToCategories, value: { type: "back" }, color: "red" });
|
|
2376
|
-
const initialCursor = items.findIndex((item) => {
|
|
2377
|
-
if (item.separator || item.disabled || item.kind === "heading")
|
|
2378
|
-
return false;
|
|
2379
|
-
if (item.value.type === "toggle" && focusKey === item.value.key)
|
|
2380
|
-
return true;
|
|
2381
|
-
if (item.value.type === "bump" && focusKey === item.value.key)
|
|
2382
|
-
return true;
|
|
2383
|
-
return false;
|
|
2384
|
-
});
|
|
2385
|
-
const result = await select(items, {
|
|
2386
|
-
message: `${UI_COPY.settings.backendCategoryTitle}: ${category.label}`,
|
|
2387
|
-
subtitle: category.description,
|
|
2388
|
-
help: UI_COPY.settings.backendCategoryHelp,
|
|
2389
|
-
clearScreen: true,
|
|
2390
|
-
theme: ui.theme,
|
|
2391
|
-
selectedEmphasis: "minimal",
|
|
2392
|
-
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
|
|
2393
|
-
onCursorChange: ({ cursor }) => {
|
|
2394
|
-
const focusedItem = items[cursor];
|
|
2395
|
-
if (focusedItem?.value.type === "toggle" || focusedItem?.value.type === "bump") {
|
|
2396
|
-
focusKey = focusedItem.value.key;
|
|
2397
|
-
}
|
|
2398
|
-
},
|
|
2399
|
-
onInput: (raw) => {
|
|
2400
|
-
const lower = raw.toLowerCase();
|
|
2401
|
-
if (lower === "q")
|
|
2402
|
-
return { type: "back" };
|
|
2403
|
-
if (lower === "r")
|
|
2404
|
-
return { type: "reset-category" };
|
|
2405
|
-
if (numberOptions.length > 0 && (lower === "+" || lower === "=" || lower === "]" || lower === "d")) {
|
|
2406
|
-
return { type: "bump", key: resolveFocusedBackendNumberKey(focusKey, numberOptions), direction: 1 };
|
|
2407
|
-
}
|
|
2408
|
-
if (numberOptions.length > 0 && (lower === "-" || lower === "[" || lower === "a")) {
|
|
2409
|
-
return { type: "bump", key: resolveFocusedBackendNumberKey(focusKey, numberOptions), direction: -1 };
|
|
2410
|
-
}
|
|
2411
|
-
const parsed = Number.parseInt(raw, 10);
|
|
2412
|
-
if (Number.isFinite(parsed) && parsed >= 1 && parsed <= toggleOptions.length) {
|
|
2413
|
-
const target = toggleOptions[parsed - 1];
|
|
2414
|
-
if (target)
|
|
2415
|
-
return { type: "toggle", key: target.key };
|
|
2416
|
-
}
|
|
2417
|
-
return undefined;
|
|
2418
|
-
},
|
|
2419
|
-
});
|
|
2420
|
-
if (!result || result.type === "back") {
|
|
2421
|
-
return { draft, focusKey };
|
|
2422
|
-
}
|
|
2423
|
-
if (result.type === "reset-category") {
|
|
2424
|
-
draft = applyBackendCategoryDefaults(draft, category);
|
|
2425
|
-
focusKey = getBackendCategoryInitialFocus(category);
|
|
2426
|
-
continue;
|
|
2427
|
-
}
|
|
2428
|
-
if (result.type === "toggle") {
|
|
2429
|
-
const currentValue = draft[result.key] ?? BACKEND_DEFAULTS[result.key] ?? false;
|
|
2430
|
-
draft = { ...draft, [result.key]: !currentValue };
|
|
2431
|
-
focusKey = result.key;
|
|
2432
|
-
continue;
|
|
2433
|
-
}
|
|
2434
|
-
const option = BACKEND_NUMBER_OPTION_BY_KEY.get(result.key);
|
|
2435
|
-
if (!option)
|
|
2436
|
-
continue;
|
|
2437
|
-
const currentValue = draft[result.key] ?? BACKEND_DEFAULTS[result.key] ?? option.min;
|
|
2438
|
-
const numericCurrent = typeof currentValue === "number" && Number.isFinite(currentValue)
|
|
2439
|
-
? currentValue
|
|
2440
|
-
: option.min;
|
|
2441
|
-
draft = {
|
|
2442
|
-
...draft,
|
|
2443
|
-
[result.key]: clampBackendNumber(option, numericCurrent + option.step * result.direction),
|
|
2444
|
-
};
|
|
2445
|
-
focusKey = result.key;
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
async function promptBackendSettings(initial) {
|
|
2449
|
-
if (!input.isTTY || !output.isTTY)
|
|
2450
|
-
return null;
|
|
2451
|
-
const ui = getUiRuntimeOptions();
|
|
2452
|
-
let draft = cloneBackendPluginConfig(initial);
|
|
2453
|
-
let activeCategory = BACKEND_CATEGORY_OPTIONS[0]?.key ?? "session-sync";
|
|
2454
|
-
const focusByCategory = {};
|
|
2455
|
-
for (const category of BACKEND_CATEGORY_OPTIONS) {
|
|
2456
|
-
focusByCategory[category.key] = getBackendCategoryInitialFocus(category);
|
|
2457
|
-
}
|
|
2458
|
-
while (true) {
|
|
2459
|
-
const previewFocus = focusByCategory[activeCategory] ?? null;
|
|
2460
|
-
const preview = buildBackendSettingsPreview(draft, ui, previewFocus);
|
|
2461
|
-
const categoryItems = BACKEND_CATEGORY_OPTIONS.map((category, index) => {
|
|
2462
|
-
return {
|
|
2463
|
-
label: `${index + 1}. ${category.label}`,
|
|
2464
|
-
hint: category.description,
|
|
2465
|
-
value: { type: "open-category", key: category.key },
|
|
2466
|
-
color: "green",
|
|
2467
|
-
};
|
|
2468
|
-
});
|
|
2469
|
-
const items = [
|
|
2470
|
-
{ label: UI_COPY.settings.previewHeading, value: { type: "cancel" }, kind: "heading" },
|
|
2471
|
-
{
|
|
2472
|
-
label: preview.label,
|
|
2473
|
-
hint: preview.hint,
|
|
2474
|
-
value: { type: "cancel" },
|
|
2475
|
-
disabled: true,
|
|
2476
|
-
color: "green",
|
|
2477
|
-
hideUnavailableSuffix: true,
|
|
2478
|
-
},
|
|
2479
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
2480
|
-
{ label: UI_COPY.settings.backendCategoriesHeading, value: { type: "cancel" }, kind: "heading" },
|
|
2481
|
-
...categoryItems,
|
|
2482
|
-
{ label: "", value: { type: "cancel" }, separator: true },
|
|
2483
|
-
{ label: UI_COPY.settings.resetDefault, value: { type: "reset" }, color: "yellow" },
|
|
2484
|
-
{ label: UI_COPY.settings.saveAndBack, value: { type: "save" }, color: "green" },
|
|
2485
|
-
{ label: UI_COPY.settings.backNoSave, value: { type: "cancel" }, color: "red" },
|
|
2486
|
-
];
|
|
2487
|
-
const initialCursor = items.findIndex((item) => {
|
|
2488
|
-
if (item.separator || item.disabled || item.kind === "heading")
|
|
2489
|
-
return false;
|
|
2490
|
-
return item.value.type === "open-category" && item.value.key === activeCategory;
|
|
2491
|
-
});
|
|
2492
|
-
const result = await select(items, {
|
|
2493
|
-
message: UI_COPY.settings.backendTitle,
|
|
2494
|
-
subtitle: UI_COPY.settings.backendSubtitle,
|
|
2495
|
-
help: UI_COPY.settings.backendHelp,
|
|
2496
|
-
clearScreen: true,
|
|
2497
|
-
theme: ui.theme,
|
|
2498
|
-
selectedEmphasis: "minimal",
|
|
2499
|
-
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
|
|
2500
|
-
onCursorChange: ({ cursor }) => {
|
|
2501
|
-
const focusedItem = items[cursor];
|
|
2502
|
-
if (focusedItem?.value.type === "open-category") {
|
|
2503
|
-
activeCategory = focusedItem.value.key;
|
|
2504
|
-
}
|
|
2505
|
-
},
|
|
2506
|
-
onInput: (raw) => {
|
|
2507
|
-
const lower = raw.toLowerCase();
|
|
2508
|
-
if (lower === "q")
|
|
2509
|
-
return { type: "save" };
|
|
2510
|
-
if (lower === "s")
|
|
2511
|
-
return { type: "save" };
|
|
2512
|
-
if (lower === "r")
|
|
2513
|
-
return { type: "reset" };
|
|
2514
|
-
const parsed = Number.parseInt(raw, 10);
|
|
2515
|
-
if (Number.isFinite(parsed) && parsed >= 1 && parsed <= BACKEND_CATEGORY_OPTIONS.length) {
|
|
2516
|
-
const target = BACKEND_CATEGORY_OPTIONS[parsed - 1];
|
|
2517
|
-
if (target)
|
|
2518
|
-
return { type: "open-category", key: target.key };
|
|
2519
|
-
}
|
|
2520
|
-
return undefined;
|
|
2521
|
-
},
|
|
2522
|
-
});
|
|
2523
|
-
if (!result || result.type === "cancel")
|
|
2524
|
-
return null;
|
|
2525
|
-
if (result.type === "save")
|
|
2526
|
-
return draft;
|
|
2527
|
-
if (result.type === "reset") {
|
|
2528
|
-
draft = cloneBackendPluginConfig(BACKEND_DEFAULTS);
|
|
2529
|
-
for (const category of BACKEND_CATEGORY_OPTIONS) {
|
|
2530
|
-
focusByCategory[category.key] = getBackendCategoryInitialFocus(category);
|
|
2531
|
-
}
|
|
2532
|
-
activeCategory = BACKEND_CATEGORY_OPTIONS[0]?.key ?? activeCategory;
|
|
2533
|
-
continue;
|
|
2534
|
-
}
|
|
2535
|
-
const category = getBackendCategory(result.key);
|
|
2536
|
-
if (!category)
|
|
2537
|
-
continue;
|
|
2538
|
-
activeCategory = category.key;
|
|
2539
|
-
const categoryResult = await promptBackendCategorySettings(draft, category, focusByCategory[category.key] ?? getBackendCategoryInitialFocus(category));
|
|
2540
|
-
draft = categoryResult.draft;
|
|
2541
|
-
focusByCategory[category.key] = categoryResult.focusKey;
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2544
|
-
async function configureBackendSettings(currentConfig) {
|
|
2545
|
-
const current = cloneBackendPluginConfig(currentConfig ?? loadPluginConfig());
|
|
2546
|
-
if (!input.isTTY || !output.isTTY) {
|
|
2547
|
-
console.log("Settings require interactive mode.");
|
|
2548
|
-
return current;
|
|
2549
|
-
}
|
|
2550
|
-
const selected = await promptBackendSettings(current);
|
|
2551
|
-
if (!selected)
|
|
2552
|
-
return current;
|
|
2553
|
-
if (backendSettingsEqual(current, selected))
|
|
2554
|
-
return current;
|
|
2555
|
-
await savePluginConfig(buildBackendConfigPatch(selected));
|
|
2556
|
-
return selected;
|
|
2557
|
-
}
|
|
2558
|
-
async function promptSettingsHub(initialFocus = "account-list") {
|
|
2559
|
-
if (!input.isTTY || !output.isTTY)
|
|
2560
|
-
return null;
|
|
2561
|
-
const ui = getUiRuntimeOptions();
|
|
2562
|
-
const items = [
|
|
2563
|
-
{ label: UI_COPY.settings.sectionTitle, value: { type: "back" }, kind: "heading" },
|
|
2564
|
-
{ label: UI_COPY.settings.accountList, value: { type: "account-list" }, color: "green" },
|
|
2565
|
-
{ label: UI_COPY.settings.summaryFields, value: { type: "summary-fields" }, color: "green" },
|
|
2566
|
-
{ label: UI_COPY.settings.behavior, value: { type: "behavior" }, color: "green" },
|
|
2567
|
-
{ label: UI_COPY.settings.theme, value: { type: "theme" }, color: "green" },
|
|
2568
|
-
{ label: "", value: { type: "back" }, separator: true },
|
|
2569
|
-
{ label: UI_COPY.settings.advancedTitle, value: { type: "back" }, kind: "heading" },
|
|
2570
|
-
{ label: UI_COPY.settings.backend, value: { type: "backend" }, color: "green" },
|
|
2571
|
-
{ label: "", value: { type: "back" }, separator: true },
|
|
2572
|
-
{ label: UI_COPY.settings.exitTitle, value: { type: "back" }, kind: "heading" },
|
|
2573
|
-
{ label: UI_COPY.settings.back, value: { type: "back" }, color: "red" },
|
|
2574
|
-
];
|
|
2575
|
-
const initialCursor = items.findIndex((item) => {
|
|
2576
|
-
if (item.separator || item.disabled || item.kind === "heading")
|
|
2577
|
-
return false;
|
|
2578
|
-
return item.value.type === initialFocus;
|
|
2579
|
-
});
|
|
2580
|
-
return select(items, {
|
|
2581
|
-
message: UI_COPY.settings.title,
|
|
2582
|
-
subtitle: UI_COPY.settings.subtitle,
|
|
2583
|
-
help: UI_COPY.settings.help,
|
|
2584
|
-
clearScreen: true,
|
|
2585
|
-
theme: ui.theme,
|
|
2586
|
-
selectedEmphasis: "minimal",
|
|
2587
|
-
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
|
|
2588
|
-
onInput: (raw) => {
|
|
2589
|
-
const lower = raw.toLowerCase();
|
|
2590
|
-
if (lower === "q")
|
|
2591
|
-
return { type: "back" };
|
|
2592
|
-
return undefined;
|
|
2593
|
-
},
|
|
2594
|
-
});
|
|
2595
|
-
}
|
|
2596
|
-
async function configureUnifiedSettings(initialSettings) {
|
|
2597
|
-
let current = cloneDashboardSettings(initialSettings ?? await loadDashboardDisplaySettings());
|
|
2598
|
-
let backendConfig = cloneBackendPluginConfig(loadPluginConfig());
|
|
2599
|
-
applyUiThemeFromDashboardSettings(current);
|
|
2600
|
-
let hubFocus = "account-list";
|
|
2601
|
-
while (true) {
|
|
2602
|
-
const action = await promptSettingsHub(hubFocus);
|
|
2603
|
-
if (!action || action.type === "back") {
|
|
2604
|
-
return current;
|
|
2605
|
-
}
|
|
2606
|
-
hubFocus = action.type;
|
|
2607
|
-
if (action.type === "account-list") {
|
|
2608
|
-
current = await configureDashboardDisplaySettings(current);
|
|
2609
|
-
continue;
|
|
2610
|
-
}
|
|
2611
|
-
if (action.type === "summary-fields") {
|
|
2612
|
-
current = await configureStatuslineSettings(current);
|
|
2613
|
-
continue;
|
|
2614
|
-
}
|
|
2615
|
-
if (action.type === "behavior") {
|
|
2616
|
-
const selected = await promptBehaviorSettings(current);
|
|
2617
|
-
if (selected && !dashboardSettingsEqual(current, selected)) {
|
|
2618
|
-
current = selected;
|
|
2619
|
-
await saveDashboardDisplaySettings(current);
|
|
2620
|
-
}
|
|
2621
|
-
continue;
|
|
2622
|
-
}
|
|
2623
|
-
if (action.type === "theme") {
|
|
2624
|
-
const selected = await promptThemeSettings(current);
|
|
2625
|
-
if (selected && !dashboardSettingsEqual(current, selected)) {
|
|
2626
|
-
current = selected;
|
|
2627
|
-
await saveDashboardDisplaySettings(current);
|
|
2628
|
-
applyUiThemeFromDashboardSettings(current);
|
|
2629
|
-
}
|
|
2630
|
-
continue;
|
|
2631
|
-
}
|
|
2632
|
-
if (action.type === "backend") {
|
|
2633
|
-
backendConfig = await configureBackendSettings(backendConfig);
|
|
2634
|
-
}
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
1005
|
async function runOAuthFlow(forceNewLogin) {
|
|
2638
1006
|
const { pkce, state, url } = await createAuthorizationFlow({ forceNewLogin });
|
|
2639
1007
|
const oauthServer = await startLocalOAuthServer({ state });
|