codex-multi-auth 2.19.0 → 2.20.0
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/.codex-plugin/plugin.json +9 -9
- package/LICENSE +21 -21
- package/README.md +149 -148
- package/assets/codex-multi-auth-icon.svg +39 -39
- package/assets/readme-hero.svg +31 -31
- package/config/README.md +31 -31
- package/config/codex-legacy.json +1021 -1021
- package/config/codex-modern.json +347 -347
- package/config/minimal-codex.json +15 -15
- package/dist/index.js +1 -1
- package/dist/lib/accounts/rate-limits.js +5 -0
- package/dist/lib/accounts.js +5 -4
- package/dist/lib/auth/account-access.js +2 -2
- package/dist/lib/codex-cli/version.d.ts +22 -0
- package/dist/lib/codex-cli/version.js +78 -0
- package/dist/lib/codex-manager/commands/limits.d.ts +2 -0
- package/dist/lib/codex-manager/commands/limits.js +31 -0
- package/dist/lib/codex-manager/commands/status.js +1 -1
- package/dist/lib/codex-manager/dashboard-settings-data.js +2 -0
- package/dist/lib/codex-manager/formatters/subscription-status.d.ts +2 -2
- package/dist/lib/codex-manager/formatters/text-style.js +4 -1
- package/dist/lib/codex-manager/settings-hub/dashboard.d.ts +1 -1
- package/dist/lib/codex-manager/settings-hub/dashboard.js +4 -0
- package/dist/lib/codex-manager/settings-panels.d.ts +1 -0
- package/dist/lib/codex-manager/settings-panels.js +1 -0
- package/dist/lib/codex-manager/theme-settings-panel.d.ts +2 -1
- package/dist/lib/codex-manager/theme-settings-panel.js +39 -0
- package/dist/lib/codex-manager.js +2 -0
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.js +2 -0
- package/dist/lib/context-budget/model-context-windows.d.ts +1 -1
- package/dist/lib/context-budget/model-context-windows.js +4 -0
- package/dist/lib/context-budget-response.js +8 -8
- package/dist/lib/context-overflow.js +10 -10
- package/dist/lib/dashboard-settings.d.ts +2 -0
- package/dist/lib/dashboard-settings.js +18 -0
- package/dist/lib/luna-reserve.d.ts +26 -0
- package/dist/lib/luna-reserve.js +68 -0
- package/dist/lib/model-route-policy.d.ts +5 -5
- package/dist/lib/oauth-success.html +125 -125
- package/dist/lib/policy/runtime-policy.js +14 -0
- package/dist/lib/prompts/codex-host-bridge.js +138 -138
- package/dist/lib/prompts/codex.js +79 -79
- package/dist/lib/quota-readiness.d.ts +2 -2
- package/dist/lib/request/helpers/model-map.d.ts +1 -1
- package/dist/lib/request/helpers/model-map.js +12 -0
- package/dist/lib/request/response-outcome.d.ts +1 -1
- package/dist/lib/request/response-outcome.js +4 -1
- package/dist/lib/request/url-rewriting.js +3 -1
- package/dist/lib/runtime/account-model-catalog.d.ts +5 -5
- package/dist/lib/runtime/account-reset-credits.d.ts +2 -2
- package/dist/lib/runtime/account-status.js +2 -1
- package/dist/lib/runtime/luna-reserve-usage.d.ts +9 -0
- package/dist/lib/runtime/luna-reserve-usage.js +68 -0
- package/dist/lib/runtime/model-discovery-status.d.ts +11 -7
- package/dist/lib/runtime/model-discovery-status.js +13 -5
- package/dist/lib/runtime/reset-credit-routing.js +4 -1
- package/dist/lib/runtime/reset-credits.d.ts +7 -7
- package/dist/lib/runtime/resume-picker.js +1 -1
- package/dist/lib/runtime/rotation-server-types.d.ts +2 -0
- package/dist/lib/runtime/runtime-observability.d.ts +2 -0
- package/dist/lib/runtime/runtime-observability.js +1 -0
- package/dist/lib/runtime/workspace-model-scopes.d.ts +3 -3
- package/dist/lib/runtime-rotation-proxy.js +200 -32
- package/dist/lib/schemas.d.ts +65 -65
- package/dist/lib/ui/index.d.ts +1 -1
- package/dist/lib/ui/index.js +1 -1
- package/dist/lib/ui/runtime.d.ts +3 -1
- package/dist/lib/ui/runtime.js +11 -1
- package/dist/lib/ui/theme.d.ts +20 -1
- package/dist/lib/ui/theme.js +154 -30
- package/dist/lib/ui/ui-copy.d.ts +3 -2
- package/dist/lib/ui/ui-copy.js +3 -2
- package/dist/lib/usage/pricing.d.ts +1 -1
- package/dist/lib/usage/pricing.js +4 -0
- package/dist/lib/usage/usage-extraction.d.ts +2 -0
- package/dist/lib/usage/usage-extraction.js +25 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.js +1 -1
- package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -2
- package/node_modules/@codex-ai/plugin/dist/index.js +2 -2
- package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -42
- package/node_modules/@codex-ai/plugin/dist/tool.js +29 -29
- package/node_modules/@codex-ai/plugin/package.json +9 -9
- package/node_modules/@codex-ai/sdk/dist/index.d.ts +4 -4
- package/node_modules/@codex-ai/sdk/dist/index.js +2 -2
- package/node_modules/@codex-ai/sdk/package.json +8 -8
- package/package.json +207 -206
- package/scripts/codex-app-launcher.js +653 -653
- package/scripts/codex-app-router.js +374 -373
- package/scripts/codex-bin-resolver.js +332 -332
- package/scripts/codex-multi-auth.js +65 -65
- package/scripts/codex-routing.js +62 -62
- package/scripts/codex.js +7006 -7005
- package/scripts/install-codex-auth-utils.js +213 -213
- package/scripts/mcodex.js +367 -367
- package/scripts/postinstall.js +114 -114
- package/scripts/preuninstall.js +265 -265
- package/skills/codex-auth-setup/SKILL.md +73 -73
- package/vendor/codex-ai-plugin/dist/index.d.ts +2 -2
- package/vendor/codex-ai-plugin/dist/index.js +2 -2
- package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -42
- package/vendor/codex-ai-plugin/dist/tool.js +29 -29
- package/vendor/codex-ai-plugin/package.json +9 -9
- package/vendor/codex-ai-sdk/dist/index.d.ts +4 -4
- package/vendor/codex-ai-sdk/dist/index.js +2 -2
- package/vendor/codex-ai-sdk/package.json +8 -8
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "./schema/config.schema.json",
|
|
3
|
-
"plugin": ["codex-multi-auth"],
|
|
4
|
-
|
|
5
|
-
"provider": {
|
|
6
|
-
"openai": {
|
|
7
|
-
"options": {
|
|
8
|
-
"store": false
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"model": "openai/gpt-5.6-sol"
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./schema/config.schema.json",
|
|
3
|
+
"plugin": ["codex-multi-auth"],
|
|
4
|
+
|
|
5
|
+
"provider": {
|
|
6
|
+
"openai": {
|
|
7
|
+
"options": {
|
|
8
|
+
"store": false
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"model": "openai/gpt-5.6-sol"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
package/dist/index.js
CHANGED
|
@@ -1043,7 +1043,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1043
1043
|
}
|
|
1044
1044
|
let response;
|
|
1045
1045
|
const fetchStart = performance.now();
|
|
1046
|
-
// Merge user AbortSignal with timeout
|
|
1046
|
+
// Merge user AbortSignal with timeout without AbortSignal.any (keeps listeners bounded)
|
|
1047
1047
|
const fetchController = new AbortController();
|
|
1048
1048
|
const requestTimeoutMs = fetchTimeoutMs;
|
|
1049
1049
|
let requestTimedOut = false;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Rate limiting utilities for account management.
|
|
3
3
|
* Extracted from accounts.ts to reduce module size and improve cohesion.
|
|
4
4
|
*/
|
|
5
|
+
import { LUNA_RESERVE_MODEL } from "../constants.js";
|
|
5
6
|
import { nowMs } from "../utils.js";
|
|
6
7
|
export function parseRateLimitReason(code) {
|
|
7
8
|
if (!code)
|
|
@@ -61,6 +62,10 @@ export function isRateLimitedForFamily(entity, family, model) {
|
|
|
61
62
|
if (isRateLimitedForQuotaKey(entity, modelKey)) {
|
|
62
63
|
return true;
|
|
63
64
|
}
|
|
65
|
+
// Luna Reserve is independently metered. Ordinary Luna/Codex family
|
|
66
|
+
// exhaustion must not suppress a separately available reserve allowance.
|
|
67
|
+
if (model === LUNA_RESERVE_MODEL)
|
|
68
|
+
return false;
|
|
64
69
|
}
|
|
65
70
|
const baseKey = getQuotaKey(family);
|
|
66
71
|
return isRateLimitedForQuotaKey(entity, baseKey);
|
package/dist/lib/accounts.js
CHANGED
|
@@ -9,7 +9,7 @@ import { MODEL_FAMILIES } from "./request/helpers/model-map.js";
|
|
|
9
9
|
import { getHealthTracker, getTokenTracker, resetTrackers, selectHybridAccount, } from "./rotation.js";
|
|
10
10
|
import { withRoutingMutex, } from "./routing-mutex.js";
|
|
11
11
|
import { nowMs } from "./utils.js";
|
|
12
|
-
import { ERROR_MESSAGES, HTTP_STATUS, MAX_RATE_LIMIT_DELAY_MS } from "./constants.js";
|
|
12
|
+
import { ERROR_MESSAGES, HTTP_STATUS, LUNA_RESERVE_MODEL, MAX_RATE_LIMIT_DELAY_MS } from "./constants.js";
|
|
13
13
|
import { CodexAuthError } from "./errors.js";
|
|
14
14
|
import { loadCodexCliState, } from "./codex-cli/state.js";
|
|
15
15
|
import { syncAccountStorageFromCodexCli } from "./codex-cli/sync.js";
|
|
@@ -1062,13 +1062,14 @@ export class AccountManager {
|
|
|
1062
1062
|
: undefined;
|
|
1063
1063
|
const newLimits = { ...carried?.limits };
|
|
1064
1064
|
const baseKey = getQuotaKey(family);
|
|
1065
|
-
|
|
1065
|
+
const independentReserve = model === LUNA_RESERVE_MODEL;
|
|
1066
|
+
if (!independentReserve && (!model || reason === "quota" || reason === "unknown")) {
|
|
1066
1067
|
const currentResetAt = account.rateLimitResetTimes[baseKey] ?? 0;
|
|
1067
1068
|
account.rateLimitResetTimes[baseKey] = Math.max(currentResetAt, resetAt);
|
|
1068
1069
|
newLimits[baseKey] = Math.max(newLimits[baseKey] ?? 0, resetAt);
|
|
1069
1070
|
}
|
|
1070
1071
|
if (model &&
|
|
1071
|
-
(reason === "tokens" || reason === "concurrent" || reason === "unknown")) {
|
|
1072
|
+
(independentReserve || reason === "tokens" || reason === "concurrent" || reason === "unknown")) {
|
|
1072
1073
|
const modelKey = getQuotaKey(family, model);
|
|
1073
1074
|
const currentResetAt = account.rateLimitResetTimes[modelKey] ?? 0;
|
|
1074
1075
|
account.rateLimitResetTimes[modelKey] = Math.max(currentResetAt, resetAt);
|
|
@@ -1550,7 +1551,7 @@ export class AccountManager {
|
|
|
1550
1551
|
for (const account of enabledAccounts) {
|
|
1551
1552
|
const perAccountWaitTimes = [];
|
|
1552
1553
|
const baseResetAt = account.rateLimitResetTimes[baseKey];
|
|
1553
|
-
if (typeof baseResetAt === "number") {
|
|
1554
|
+
if (model !== LUNA_RESERVE_MODEL && typeof baseResetAt === "number") {
|
|
1554
1555
|
perAccountWaitTimes.push(Math.max(0, baseResetAt - now));
|
|
1555
1556
|
}
|
|
1556
1557
|
if (modelKey) {
|
|
@@ -51,8 +51,8 @@ export async function fetchAuthorizedAccounts(accessToken, options = {}) {
|
|
|
51
51
|
return null;
|
|
52
52
|
const doFetch = options.fetch ?? globalThis.fetch;
|
|
53
53
|
const timeout = AbortSignal.timeout(ACCOUNTS_CHECK_TIMEOUT_MS);
|
|
54
|
-
// combineSignals wraps AbortSignal.any (available
|
|
55
|
-
// engines floor) with null-tolerance; the native composite also detaches
|
|
54
|
+
// combineSignals wraps AbortSignal.any (available on every Node the
|
|
55
|
+
// engines floor, `>=22.19`, permits) with null-tolerance; the native composite also detaches
|
|
56
56
|
// from a long-lived caller signal once this request completes.
|
|
57
57
|
const signal = options.signal
|
|
58
58
|
? combineSignals(options.signal, timeout)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface ResolvedCodexBin {
|
|
2
|
+
path: string;
|
|
3
|
+
launchWithNode: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface CodexCliVersionDeps {
|
|
6
|
+
resolveBin?: () => Promise<ResolvedCodexBin | null>;
|
|
7
|
+
execFile?: (file: string, args: readonly string[], options: {
|
|
8
|
+
timeout: number;
|
|
9
|
+
}) => Promise<{
|
|
10
|
+
stdout: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export declare const CLIENT_VERSION_PATTERN: RegExp;
|
|
14
|
+
/** Extracts `0.160.1` from `codex --version` output such as `codex-cli 0.160.1`. */
|
|
15
|
+
export declare function parseCodexCliVersion(output: string): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the installed Codex CLI version, or null when no binary resolves or
|
|
18
|
+
* the probe fails. Detection failure must never break a check: the caller just
|
|
19
|
+
* sends no `client_version`, exactly as before.
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectInstalledCodexCliVersion(deps?: CodexCliVersionDeps): Promise<string | null>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects the installed Codex CLI version for the `client_version` parameter
|
|
3
|
+
* the upstream models catalog requires (issue #733). The catalog filters by
|
|
4
|
+
* `minimal_client_version`, so the honest value is whatever `codex --version`
|
|
5
|
+
* reports on this host — never a hardcoded string.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
// scripts/codex-bin-resolver.js is plain shipped JS, so it loads by URL:
|
|
11
|
+
// dist/lib/codex-cli/ resolves ../../../scripts, the source tree ../../scripts.
|
|
12
|
+
const RESOLVER_CANDIDATES = [
|
|
13
|
+
fileURLToPath(new URL("../../../scripts/codex-bin-resolver.js", import.meta.url)),
|
|
14
|
+
fileURLToPath(new URL("../../scripts/codex-bin-resolver.js", import.meta.url)),
|
|
15
|
+
];
|
|
16
|
+
async function loadBinResolver() {
|
|
17
|
+
for (const candidate of RESOLVER_CANDIDATES) {
|
|
18
|
+
if (!existsSync(candidate))
|
|
19
|
+
continue;
|
|
20
|
+
const resolverModule = (await import(pathToFileURL(candidate).href).catch(() => null));
|
|
21
|
+
if (typeof resolverModule?.resolveRealCodexBin === "function") {
|
|
22
|
+
return resolverModule;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
async function defaultResolveBin() {
|
|
28
|
+
const resolverModule = await loadBinResolver();
|
|
29
|
+
const resolve = resolverModule?.resolveRealCodexBin;
|
|
30
|
+
if (!resolve)
|
|
31
|
+
return null;
|
|
32
|
+
const bin = resolve();
|
|
33
|
+
// PATH discovery can hand back npm's extensionless `codex` shim on Windows,
|
|
34
|
+
// which no spawn API can exec. The resolver knows the sibling package entry
|
|
35
|
+
// the shim points at; launch that through node instead.
|
|
36
|
+
if (bin &&
|
|
37
|
+
process.platform === "win32" &&
|
|
38
|
+
!bin.launchWithNode &&
|
|
39
|
+
resolverModule.isWindowsShimPath?.(bin.path)) {
|
|
40
|
+
const entry = resolverModule.resolveWindowsShimPackageEntry?.(bin.path);
|
|
41
|
+
if (entry)
|
|
42
|
+
return { path: entry, launchWithNode: true };
|
|
43
|
+
}
|
|
44
|
+
return bin;
|
|
45
|
+
}
|
|
46
|
+
const defaultExecFile = (file, args, options) => new Promise((resolve, reject) => {
|
|
47
|
+
execFile(file, [...args], { ...options, encoding: "utf8", windowsHide: true, maxBuffer: 1024 * 1024 }, (error, stdout) => {
|
|
48
|
+
if (error)
|
|
49
|
+
reject(error);
|
|
50
|
+
else
|
|
51
|
+
resolve({ stdout: String(stdout) });
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
export const CLIENT_VERSION_PATTERN = /^[0-9A-Za-z.+_-]{1,80}$/;
|
|
55
|
+
/** Extracts `0.160.1` from `codex --version` output such as `codex-cli 0.160.1`. */
|
|
56
|
+
export function parseCodexCliVersion(output) {
|
|
57
|
+
const match = /(?:^|\s)v?(\d+\.\d+\.\d+[-0-9A-Za-z.+_]*)/.exec(output);
|
|
58
|
+
const version = match?.[1];
|
|
59
|
+
return version && CLIENT_VERSION_PATTERN.test(version) ? version : null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns the installed Codex CLI version, or null when no binary resolves or
|
|
63
|
+
* the probe fails. Detection failure must never break a check: the caller just
|
|
64
|
+
* sends no `client_version`, exactly as before.
|
|
65
|
+
*/
|
|
66
|
+
export async function detectInstalledCodexCliVersion(deps = {}) {
|
|
67
|
+
try {
|
|
68
|
+
const bin = await (deps.resolveBin ?? defaultResolveBin)();
|
|
69
|
+
if (!bin || typeof bin.path !== "string" || bin.path.length === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const exec = deps.execFile ?? defaultExecFile;
|
|
72
|
+
const { stdout } = await exec(bin.launchWithNode ? process.execPath : bin.path, bin.launchWithNode ? [bin.path, "--version"] : ["--version"], { timeout: 10_000 });
|
|
73
|
+
return parseCodexCliVersion(stdout);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type ModelFamily } from "../../prompts/codex.js";
|
|
2
2
|
import type { QuotaCacheData } from "../../quota-cache.js";
|
|
3
3
|
import type { AccountStorageV3 } from "../../storage.js";
|
|
4
|
+
import type { LunaReserveSnapshot } from "../../luna-reserve.js";
|
|
4
5
|
export interface LimitsCommandDeps {
|
|
5
6
|
setStoragePath: (path: string | null) => void;
|
|
6
7
|
loadAccounts: () => Promise<AccountStorageV3 | null>;
|
|
7
8
|
loadQuotaCache: () => Promise<QuotaCacheData>;
|
|
8
9
|
refreshQuotaCache: (storage: AccountStorageV3, cache: QuotaCacheData, maxAgeMs: number) => Promise<QuotaCacheData>;
|
|
10
|
+
refreshLunaReserveUsage?: (storage: AccountStorageV3) => Promise<Record<number, LunaReserveSnapshot>>;
|
|
9
11
|
resolveActiveIndex: (storage: AccountStorageV3, family?: ModelFamily) => number;
|
|
10
12
|
getNow?: () => number;
|
|
11
13
|
logInfo?: (message: string) => void;
|
|
@@ -46,6 +46,32 @@ function publicQuotaEntry(entry) {
|
|
|
46
46
|
secondary: publicWindow(entry.secondary),
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
/** Convert one Reserve window to the stable public JSON shape. */
|
|
50
|
+
function publicReserveWindow(window) {
|
|
51
|
+
return window
|
|
52
|
+
? {
|
|
53
|
+
usedPercent: window.usedPercent,
|
|
54
|
+
remainingPercent: window.remainingPercent,
|
|
55
|
+
windowMinutes: window.windowMinutes,
|
|
56
|
+
resetAtMs: window.resetAtMs,
|
|
57
|
+
}
|
|
58
|
+
: null;
|
|
59
|
+
}
|
|
60
|
+
/** Convert a Reserve snapshot to public JSON while preserving explicit unknowns. */
|
|
61
|
+
function publicLunaReserve(snapshot) {
|
|
62
|
+
if (!snapshot)
|
|
63
|
+
return null;
|
|
64
|
+
return {
|
|
65
|
+
observedAt: snapshot.observedAt,
|
|
66
|
+
offered: snapshot.offered,
|
|
67
|
+
available: snapshot.available,
|
|
68
|
+
limitId: snapshot.limitId,
|
|
69
|
+
limitName: snapshot.limitName,
|
|
70
|
+
normalModelSlug: snapshot.normalModelSlug,
|
|
71
|
+
primary: publicReserveWindow(snapshot.primary),
|
|
72
|
+
secondary: publicReserveWindow(snapshot.secondary),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
49
75
|
/**
|
|
50
76
|
* Emit configured accounts joined to safe cached quota records.
|
|
51
77
|
*
|
|
@@ -86,8 +112,12 @@ export async function runLimitsCommand(args, deps) {
|
|
|
86
112
|
return 0;
|
|
87
113
|
}
|
|
88
114
|
let cache = await deps.loadQuotaCache();
|
|
115
|
+
let lunaReserveByAccount = {};
|
|
89
116
|
if (parsed.options.refresh) {
|
|
90
117
|
cache = await deps.refreshQuotaCache(storage, cache, LIMITS_REFRESH_MAX_AGE_MS);
|
|
118
|
+
if (deps.refreshLunaReserveUsage) {
|
|
119
|
+
lunaReserveByAccount = await deps.refreshLunaReserveUsage(storage);
|
|
120
|
+
}
|
|
91
121
|
}
|
|
92
122
|
const generatedAt = deps.getNow?.() ?? Date.now();
|
|
93
123
|
const selection = resolveSelection(storage, deps.resolveActiveIndex);
|
|
@@ -102,6 +132,7 @@ export async function runLimitsCommand(args, deps) {
|
|
|
102
132
|
enabled: account.enabled !== false,
|
|
103
133
|
current: index === selection.routedIndex,
|
|
104
134
|
quota: quota ? publicQuotaEntry(quota) : null,
|
|
135
|
+
lunaReserve: publicLunaReserve(lunaReserveByAccount[index]),
|
|
105
136
|
};
|
|
106
137
|
});
|
|
107
138
|
logInfo(JSON.stringify({
|
|
@@ -279,7 +279,7 @@ export async function runStatusCommand(deps) {
|
|
|
279
279
|
runtimeSnapshot.serverBurstCooldownUntil > now
|
|
280
280
|
? formatWaitTime(runtimeSnapshot.serverBurstCooldownUntil - now)
|
|
281
281
|
: null;
|
|
282
|
-
logInfo(`Runtime: responses=${runtimeSnapshot.responsesRequests}, refresh=${runtimeSnapshot.authRefreshRequests}, probes=${runtimeSnapshot.diagnosticProbeRequests}, budgetExhaustions=${runtimeMetrics.requestAttemptBudgetExhaustions}`);
|
|
282
|
+
logInfo(`Runtime: responses=${runtimeSnapshot.responsesRequests}, refresh=${runtimeSnapshot.authRefreshRequests}, probes=${runtimeSnapshot.diagnosticProbeRequests}, reserveFallbacks=${runtimeSnapshot.lunaReserveFallbacks ?? 0}, budgetExhaustions=${runtimeMetrics.requestAttemptBudgetExhaustions}`);
|
|
283
283
|
const lastRuntimeAccount = formatRuntimeLastAccount(runtimeSnapshot);
|
|
284
284
|
if (lastRuntimeAccount) {
|
|
285
285
|
logInfo(paint(`Last runtime account: ${lastRuntimeAccount}`, "accent"));
|
|
@@ -22,6 +22,7 @@ export function cloneDashboardSettingsData(settings, deps) {
|
|
|
22
22
|
menuSortQuickSwitchVisibleRow: settings.menuSortQuickSwitchVisibleRow ?? true,
|
|
23
23
|
uiThemePreset: settings.uiThemePreset ?? "green",
|
|
24
24
|
uiAccentColor: settings.uiAccentColor ?? "green",
|
|
25
|
+
uiColorMode: settings.uiColorMode ?? "auto",
|
|
25
26
|
menuShowStatusBadge: settings.menuShowStatusBadge ?? true,
|
|
26
27
|
menuShowCurrentBadge: settings.menuShowCurrentBadge ?? true,
|
|
27
28
|
menuShowLastUsed: settings.menuShowLastUsed ?? true,
|
|
@@ -71,6 +72,7 @@ export function dashboardSettingsDataEqual(left, right, deps) {
|
|
|
71
72
|
(right.menuSortQuickSwitchVisibleRow ?? true) &&
|
|
72
73
|
(left.uiThemePreset ?? "green") === (right.uiThemePreset ?? "green") &&
|
|
73
74
|
(left.uiAccentColor ?? "green") === (right.uiAccentColor ?? "green") &&
|
|
75
|
+
(left.uiColorMode ?? "auto") === (right.uiColorMode ?? "auto") &&
|
|
74
76
|
(left.menuShowStatusBadge ?? true) ===
|
|
75
77
|
(right.menuShowStatusBadge ?? true) &&
|
|
76
78
|
(left.menuShowCurrentBadge ?? true) ===
|
|
@@ -7,8 +7,8 @@ export declare function subscriptionStatus(account: AccountMetadataV3, entry: Qu
|
|
|
7
7
|
planLabel: string;
|
|
8
8
|
selectedWorkspace: string;
|
|
9
9
|
autoPrime: boolean | null;
|
|
10
|
-
primingState: "
|
|
11
|
-
primingFailure: "
|
|
10
|
+
primingState: "awaiting-first-use" | "completed" | "failed" | "not-applicable" | "timer-running" | "unknown" | "used";
|
|
11
|
+
primingFailure: "network error" | "response too large" | "stream ended early" | "timed out" | "upstream failed" | null;
|
|
12
12
|
automaticBlock: string | null;
|
|
13
13
|
freshness: string;
|
|
14
14
|
observedAt: number | null;
|
|
@@ -8,7 +8,10 @@ export function stylePromptText(text, tone) {
|
|
|
8
8
|
const ui = getUiRuntimeOptions();
|
|
9
9
|
if (ui.v2Enabled) {
|
|
10
10
|
if (tone === "muted") {
|
|
11
|
-
|
|
11
|
+
// In light mode the muted foreground is already dark; stacking the
|
|
12
|
+
// dim attribute on top makes the text faint again on light themes.
|
|
13
|
+
const dim = ui.theme.colorMode === "light" ? "" : ui.theme.colors.dim;
|
|
14
|
+
return `${dim}${paintUiText(ui, text, "muted")}${ui.theme.colors.reset}`;
|
|
12
15
|
}
|
|
13
16
|
const mapped = tone === "accent" ? "primary" : tone;
|
|
14
17
|
return paintUiText(ui, text, mapped);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DashboardDisplaySettings } from "../../dashboard-settings.js";
|
|
2
2
|
export declare const BEHAVIOR_PANEL_KEYS: readonly ["actionAutoReturnMs", "actionPauseOnKey", "menuAutoFetchLimits", "menuShowFetchStatus", "menuQuotaTtlMs"];
|
|
3
|
-
export declare const THEME_PANEL_KEYS: readonly ["uiThemePreset", "uiAccentColor"];
|
|
3
|
+
export declare const THEME_PANEL_KEYS: readonly ["uiThemePreset", "uiAccentColor", "uiColorMode"];
|
|
4
4
|
export declare function applyUiThemeFromDashboardSettings(settings: DashboardDisplaySettings): void;
|
|
5
5
|
export declare function promptDashboardDisplaySettings(initial: DashboardDisplaySettings): Promise<DashboardDisplaySettings | null>;
|
|
6
6
|
export declare function configureDashboardDisplaySettings(currentSettings?: DashboardDisplaySettings): Promise<DashboardDisplaySettings>;
|
|
@@ -93,6 +93,7 @@ const ACCENT_COLOR_OPTIONS = [
|
|
|
93
93
|
"blue",
|
|
94
94
|
"yellow",
|
|
95
95
|
];
|
|
96
|
+
const COLOR_MODE_OPTIONS = ["auto", "dark", "light"];
|
|
96
97
|
const ACCOUNT_LIST_PANEL_KEYS = [
|
|
97
98
|
"menuShowStatusBadge",
|
|
98
99
|
"menuShowCurrentBadge",
|
|
@@ -121,6 +122,7 @@ export const BEHAVIOR_PANEL_KEYS = [
|
|
|
121
122
|
export const THEME_PANEL_KEYS = [
|
|
122
123
|
"uiThemePreset",
|
|
123
124
|
"uiAccentColor",
|
|
125
|
+
"uiColorMode",
|
|
124
126
|
];
|
|
125
127
|
export function applyUiThemeFromDashboardSettings(settings) {
|
|
126
128
|
const current = getUiRuntimeOptions();
|
|
@@ -130,6 +132,7 @@ export function applyUiThemeFromDashboardSettings(settings) {
|
|
|
130
132
|
glyphMode: current.glyphMode,
|
|
131
133
|
palette: settings.uiThemePreset ?? "green",
|
|
132
134
|
accent: settings.uiAccentColor ?? "green",
|
|
135
|
+
colorMode: settings.uiColorMode ?? "auto",
|
|
133
136
|
});
|
|
134
137
|
}
|
|
135
138
|
/* c8 ignore start - interactive prompt flows are covered by integration tests */
|
|
@@ -215,6 +218,7 @@ export async function promptThemeSettings(initial) {
|
|
|
215
218
|
applyUiThemeFromDashboardSettings,
|
|
216
219
|
THEME_PRESET_OPTIONS,
|
|
217
220
|
ACCENT_COLOR_OPTIONS,
|
|
221
|
+
COLOR_MODE_OPTIONS,
|
|
218
222
|
THEME_PANEL_KEYS,
|
|
219
223
|
UI_COPY,
|
|
220
224
|
});
|
|
@@ -50,6 +50,7 @@ export declare function promptThemeSettingsPanelEntry(params: {
|
|
|
50
50
|
applyUiThemeFromDashboardSettings: ThemeSettingsPanelDeps["applyUiThemeFromDashboardSettings"];
|
|
51
51
|
THEME_PRESET_OPTIONS: ThemeSettingsPanelDeps["THEME_PRESET_OPTIONS"];
|
|
52
52
|
ACCENT_COLOR_OPTIONS: ThemeSettingsPanelDeps["ACCENT_COLOR_OPTIONS"];
|
|
53
|
+
COLOR_MODE_OPTIONS: ThemeSettingsPanelDeps["COLOR_MODE_OPTIONS"];
|
|
53
54
|
THEME_PANEL_KEYS: ThemeSettingsPanelDeps["THEME_PANEL_KEYS"];
|
|
54
55
|
UI_COPY: ThemeSettingsPanelDeps["UI_COPY"];
|
|
55
56
|
}): Promise<DashboardDisplaySettings | null>;
|
|
@@ -66,6 +66,7 @@ export async function promptThemeSettingsPanelEntry(params) {
|
|
|
66
66
|
applyUiThemeFromDashboardSettings: params.applyUiThemeFromDashboardSettings,
|
|
67
67
|
THEME_PRESET_OPTIONS: params.THEME_PRESET_OPTIONS,
|
|
68
68
|
ACCENT_COLOR_OPTIONS: params.ACCENT_COLOR_OPTIONS,
|
|
69
|
+
COLOR_MODE_OPTIONS: params.COLOR_MODE_OPTIONS,
|
|
69
70
|
THEME_PANEL_KEYS: params.THEME_PANEL_KEYS,
|
|
70
71
|
UI_COPY: params.UI_COPY,
|
|
71
72
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashboardAccentColor, DashboardDisplaySettings, DashboardThemePreset } from "../dashboard-settings.js";
|
|
1
|
+
import type { DashboardAccentColor, DashboardColorMode, DashboardDisplaySettings, DashboardThemePreset } from "../dashboard-settings.js";
|
|
2
2
|
import type { UI_COPY } from "../ui/ui-copy.js";
|
|
3
3
|
export interface ThemeSettingsPanelDeps {
|
|
4
4
|
cloneDashboardSettings: (settings: DashboardDisplaySettings) => DashboardDisplaySettings;
|
|
@@ -6,6 +6,7 @@ export interface ThemeSettingsPanelDeps {
|
|
|
6
6
|
applyUiThemeFromDashboardSettings: (settings: DashboardDisplaySettings) => void;
|
|
7
7
|
THEME_PRESET_OPTIONS: readonly DashboardThemePreset[];
|
|
8
8
|
ACCENT_COLOR_OPTIONS: readonly DashboardAccentColor[];
|
|
9
|
+
COLOR_MODE_OPTIONS: readonly DashboardColorMode[];
|
|
9
10
|
THEME_PANEL_KEYS: readonly (keyof DashboardDisplaySettings)[];
|
|
10
11
|
UI_COPY: typeof UI_COPY;
|
|
11
12
|
}
|
|
@@ -14,6 +14,7 @@ export async function promptThemeSettingsPanel(initial, deps) {
|
|
|
14
14
|
const ui = getUiRuntimeOptions();
|
|
15
15
|
const palette = draft.uiThemePreset ?? "green";
|
|
16
16
|
const accent = draft.uiAccentColor ?? "green";
|
|
17
|
+
const colorMode = draft.uiColorMode ?? "auto";
|
|
17
18
|
const paletteItems = deps.THEME_PRESET_OPTIONS.map((candidate, index) => {
|
|
18
19
|
const color = palette === candidate ? "green" : "yellow";
|
|
19
20
|
return {
|
|
@@ -33,6 +34,21 @@ export async function promptThemeSettingsPanel(initial, deps) {
|
|
|
33
34
|
color,
|
|
34
35
|
};
|
|
35
36
|
});
|
|
37
|
+
const colorModeItems = deps.COLOR_MODE_OPTIONS.map((candidate) => {
|
|
38
|
+
const color = colorMode === candidate ? "green" : "yellow";
|
|
39
|
+
const key = candidate === "auto" ? "A" : candidate === "dark" ? "D" : "L";
|
|
40
|
+
const hint = candidate === "auto"
|
|
41
|
+
? "Detect from COLORFGBG; dark when unknown."
|
|
42
|
+
: candidate === "dark"
|
|
43
|
+
? "Pale text on a dark terminal background."
|
|
44
|
+
: "Dark text on a light terminal background.";
|
|
45
|
+
return {
|
|
46
|
+
label: `${colorMode === candidate ? "[x]" : "[ ]"} ${key}. ${candidate}`,
|
|
47
|
+
hint,
|
|
48
|
+
value: { type: "set-color-mode", colorMode: candidate },
|
|
49
|
+
color,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
36
52
|
const items = [
|
|
37
53
|
{
|
|
38
54
|
label: deps.UI_COPY.settings.baseTheme,
|
|
@@ -48,6 +64,13 @@ export async function promptThemeSettingsPanel(initial, deps) {
|
|
|
48
64
|
},
|
|
49
65
|
...accentItems,
|
|
50
66
|
{ label: "", value: { type: "cancel" }, separator: true },
|
|
67
|
+
{
|
|
68
|
+
label: deps.UI_COPY.settings.terminalBackground,
|
|
69
|
+
value: { type: "cancel" },
|
|
70
|
+
kind: "heading",
|
|
71
|
+
},
|
|
72
|
+
...colorModeItems,
|
|
73
|
+
{ label: "", value: { type: "cancel" }, separator: true },
|
|
51
74
|
{
|
|
52
75
|
label: deps.UI_COPY.settings.resetDefault,
|
|
53
76
|
value: { type: "reset" },
|
|
@@ -74,6 +97,10 @@ export async function promptThemeSettingsPanel(initial, deps) {
|
|
|
74
97
|
if (value.type === "set-accent" && focus.type === "set-accent") {
|
|
75
98
|
return value.accent === focus.accent;
|
|
76
99
|
}
|
|
100
|
+
if (value.type === "set-color-mode" &&
|
|
101
|
+
focus.type === "set-color-mode") {
|
|
102
|
+
return value.colorMode === focus.colorMode;
|
|
103
|
+
}
|
|
77
104
|
return true;
|
|
78
105
|
});
|
|
79
106
|
const result = await select(items, {
|
|
@@ -98,6 +125,12 @@ export async function promptThemeSettingsPanel(initial, deps) {
|
|
|
98
125
|
return { type: "save" };
|
|
99
126
|
if (lower === "r")
|
|
100
127
|
return { type: "reset" };
|
|
128
|
+
if (lower === "a")
|
|
129
|
+
return { type: "set-color-mode", colorMode: "auto" };
|
|
130
|
+
if (lower === "d")
|
|
131
|
+
return { type: "set-color-mode", colorMode: "dark" };
|
|
132
|
+
if (lower === "l")
|
|
133
|
+
return { type: "set-color-mode", colorMode: "light" };
|
|
101
134
|
if (raw === "1")
|
|
102
135
|
return { type: "set-palette", palette: "green" };
|
|
103
136
|
if (raw === "2")
|
|
@@ -123,6 +156,12 @@ export async function promptThemeSettingsPanel(initial, deps) {
|
|
|
123
156
|
deps.applyUiThemeFromDashboardSettings(draft);
|
|
124
157
|
continue;
|
|
125
158
|
}
|
|
159
|
+
if (result.type === "set-color-mode") {
|
|
160
|
+
draft = { ...draft, uiColorMode: result.colorMode };
|
|
161
|
+
focus = result;
|
|
162
|
+
deps.applyUiThemeFromDashboardSettings(draft);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
126
165
|
draft = { ...draft, uiAccentColor: result.accent };
|
|
127
166
|
focus = result;
|
|
128
167
|
deps.applyUiThemeFromDashboardSettings(draft);
|
|
@@ -46,6 +46,7 @@ import { runUnpinCommand } from "./codex-manager/commands/unpin.js";
|
|
|
46
46
|
import { runWorkspaceCommand } from "./codex-manager/commands/workspace.js";
|
|
47
47
|
import { runUsageCommand } from "./codex-manager/commands/usage.js";
|
|
48
48
|
import { refreshQuotaCacheForMenu } from "./codex-manager/login-menu-data.js";
|
|
49
|
+
import { refreshLunaReserveUsage } from "./runtime/luna-reserve-usage.js";
|
|
49
50
|
import { printUsage } from "./codex-manager/help.js";
|
|
50
51
|
import { availabilityTone, formatBackupSavedAt, formatCompactQuotaSnapshot, formatRateLimitEntry, formatResultSummary, normalizeFailureDetail, riskTone, styleAccountDetailText, stylePromptText, styleQuotaSummary, } from "./codex-manager/formatters/index.js";
|
|
51
52
|
import { applyUiThemeFromDashboardSettings } from "./codex-manager/settings-hub.js";
|
|
@@ -307,6 +308,7 @@ const CLI_COMMAND_HANDLERS = new Map([
|
|
|
307
308
|
loadAccounts,
|
|
308
309
|
loadQuotaCache,
|
|
309
310
|
refreshQuotaCache: refreshQuotaCacheForMenu,
|
|
311
|
+
refreshLunaReserveUsage,
|
|
310
312
|
resolveActiveIndex,
|
|
311
313
|
}),
|
|
312
314
|
],
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export declare const PLUGIN_NAME = "codex-multi-auth";
|
|
7
7
|
/** Base URL for ChatGPT backend API */
|
|
8
8
|
export declare const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
9
|
+
/** Backend model used for the separately metered Luna Reserve allowance. */
|
|
10
|
+
export declare const LUNA_RESERVE_MODEL: "gpt-reserve";
|
|
9
11
|
/** Dummy API key used for OpenAI SDK (actual auth via OAuth) */
|
|
10
12
|
export declare const DUMMY_API_KEY = "chatgpt-oauth";
|
|
11
13
|
/** Provider ID for UI display - shows under "OpenAI" in auth dropdown */
|
package/dist/lib/constants.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export const PLUGIN_NAME = "codex-multi-auth";
|
|
7
7
|
/** Base URL for ChatGPT backend API */
|
|
8
8
|
export const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
9
|
+
/** Backend model used for the separately metered Luna Reserve allowance. */
|
|
10
|
+
export const LUNA_RESERVE_MODEL = "gpt-reserve";
|
|
9
11
|
/** Dummy API key used for OpenAI SDK (actual auth via OAuth) */
|
|
10
12
|
export const DUMMY_API_KEY = "chatgpt-oauth";
|
|
11
13
|
/** Provider ID for UI display - shows under "OpenAI" in auth dropdown */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* behavior (see the v2.5.0 release notes) closely enough to launch that no
|
|
7
7
|
* estimate here has been reviewed for it yet.
|
|
8
8
|
*/
|
|
9
|
-
export declare const UNESTIMATED_ROUTABLE_MODELS: readonly ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-6.1-sol", "gpt-daybreak-blue-latest", "gpt-daybreak-red-latest"];
|
|
9
|
+
export declare const UNESTIMATED_ROUTABLE_MODELS: readonly ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-reserve", "gpt-6.1-sol", "gpt-daybreak-blue-latest", "gpt-daybreak-red-latest"];
|
|
10
10
|
export interface EffectiveContextWindow {
|
|
11
11
|
tokens: number;
|
|
12
12
|
source: "override" | "estimate";
|
|
@@ -53,6 +53,10 @@ export const UNESTIMATED_ROUTABLE_MODELS = [
|
|
|
53
53
|
// upstream catalog as Astra, so the same reasoning keeps them unestimated.
|
|
54
54
|
"gpt-6-sol",
|
|
55
55
|
"gpt-6-luna",
|
|
56
|
+
// Luna Reserve is a quota alias of Luna, but the backend does not publish
|
|
57
|
+
// a distinct transport context ceiling for the alias. Keep it fail-closed
|
|
58
|
+
// alongside Luna rather than copying an unverified API/catalog number.
|
|
59
|
+
"gpt-reserve",
|
|
56
60
|
"gpt-6.1-sol",
|
|
57
61
|
"gpt-daybreak-blue-latest",
|
|
58
62
|
"gpt-daybreak-red-latest",
|
|
@@ -17,14 +17,14 @@ function buildPauseMessage(advisory) {
|
|
|
17
17
|
const windowNote = advisory.windowSource === "override"
|
|
18
18
|
? "your configured window size"
|
|
19
19
|
: "an estimated window size (unverified — set `contextBudgetGuardModelWindowOverrides` if you know the real limit)";
|
|
20
|
-
return `[Plugin Notice] Context budget guard paused this session at ${formatPercent(advisory.percent)}% of ${windowNote} for \`${advisory.model}\`, before hitting the model's actual limit.
|
|
21
|
-
|
|
22
|
-
Please use one of these commands to reduce context size:
|
|
23
|
-
|
|
24
|
-
• **/compact** - Compress conversation history (recommended)
|
|
25
|
-
• **/clear** - Start fresh with empty context
|
|
26
|
-
• **/undo** - Remove recent messages
|
|
27
|
-
|
|
20
|
+
return `[Plugin Notice] Context budget guard paused this session at ${formatPercent(advisory.percent)}% of ${windowNote} for \`${advisory.model}\`, before hitting the model's actual limit.
|
|
21
|
+
|
|
22
|
+
Please use one of these commands to reduce context size:
|
|
23
|
+
|
|
24
|
+
• **/compact** - Compress conversation history (recommended)
|
|
25
|
+
• **/clear** - Start fresh with empty context
|
|
26
|
+
• **/undo** - Remove recent messages
|
|
27
|
+
|
|
28
28
|
Then retry your request. This pause clears itself automatically once your context usage drops back below the threshold.`;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
@@ -33,16 +33,16 @@ export function isContextOverflowError(status, bodyText) {
|
|
|
33
33
|
/**
|
|
34
34
|
* The message shown to users when context overflow occurs
|
|
35
35
|
*/
|
|
36
|
-
const CONTEXT_OVERFLOW_MESSAGE = `[Plugin Notice] Context is too long for this model.
|
|
37
|
-
|
|
38
|
-
Please use one of these commands to reduce context size:
|
|
39
|
-
|
|
40
|
-
• **/compact** - Compress conversation history (recommended)
|
|
41
|
-
• **/clear** - Start fresh with empty context
|
|
42
|
-
• **/undo** - Remove recent messages
|
|
43
|
-
|
|
44
|
-
Then retry your request.
|
|
45
|
-
|
|
36
|
+
const CONTEXT_OVERFLOW_MESSAGE = `[Plugin Notice] Context is too long for this model.
|
|
37
|
+
|
|
38
|
+
Please use one of these commands to reduce context size:
|
|
39
|
+
|
|
40
|
+
• **/compact** - Compress conversation history (recommended)
|
|
41
|
+
• **/clear** - Start fresh with empty context
|
|
42
|
+
• **/undo** - Remove recent messages
|
|
43
|
+
|
|
44
|
+
Then retry your request.
|
|
45
|
+
|
|
46
46
|
Alternatively, you can switch to a model with a larger context window.`;
|
|
47
47
|
/**
|
|
48
48
|
* Creates a synthetic SSE response for context overflow errors.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type DashboardThemePreset = "green" | "blue";
|
|
2
2
|
export type DashboardAccentColor = "green" | "cyan" | "blue" | "yellow";
|
|
3
|
+
export type DashboardColorMode = "auto" | "dark" | "light";
|
|
3
4
|
export type DashboardAccountSortMode = "manual" | "ready-first";
|
|
4
5
|
type DashboardLayoutMode = "compact-details" | "expanded-rows";
|
|
5
6
|
type DashboardFocusStyle = "row-invert";
|
|
@@ -18,6 +19,7 @@ export interface DashboardDisplaySettings {
|
|
|
18
19
|
menuSortQuickSwitchVisibleRow?: boolean;
|
|
19
20
|
uiThemePreset?: DashboardThemePreset;
|
|
20
21
|
uiAccentColor?: DashboardAccentColor;
|
|
22
|
+
uiColorMode?: DashboardColorMode;
|
|
21
23
|
menuShowStatusBadge?: boolean;
|
|
22
24
|
menuShowCurrentBadge?: boolean;
|
|
23
25
|
menuShowLastUsed?: boolean;
|