codex-multi-auth 2.22.0 → 2.24.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 +159 -150
- 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/config/schema/config.schema.json +8 -0
- package/dist/index.js +33 -10
- package/dist/lib/account-policy.d.ts +2 -0
- package/dist/lib/account-policy.js +3 -1
- package/dist/lib/accounts/rate-limits.d.ts +6 -0
- package/dist/lib/accounts/rate-limits.js +14 -1
- package/dist/lib/accounts.d.ts +6 -1
- package/dist/lib/accounts.js +33 -8
- package/dist/lib/api-route-store.d.ts +1 -0
- package/dist/lib/api-route-store.js +2 -1
- package/dist/lib/auth/account-access.js +2 -1
- package/dist/lib/auth/auth.js +3 -2
- package/dist/lib/auth/device-auth.js +2 -1
- package/dist/lib/auth/http.d.ts +1 -0
- package/dist/lib/auth/http.js +1 -0
- package/dist/lib/auth/token-utils.d.ts +5 -0
- package/dist/lib/auth/token-utils.js +10 -0
- package/dist/lib/codex-manager/account-manager-commands.js +1 -0
- package/dist/lib/codex-manager/commands/account.js +15 -0
- package/dist/lib/codex-manager/commands/locale.d.ts +12 -0
- package/dist/lib/codex-manager/commands/locale.js +25 -0
- package/dist/lib/codex-manager/commands/resets.js +18 -5
- package/dist/lib/codex-manager/commands/status.d.ts +4 -0
- package/dist/lib/codex-manager/commands/status.js +476 -5
- package/dist/lib/codex-manager/commands/workspace.d.ts +4 -2
- package/dist/lib/codex-manager/commands/workspace.js +33 -2
- package/dist/lib/codex-manager/help.js +5 -3
- package/dist/lib/codex-manager.js +112 -30
- package/dist/lib/config.js +3 -0
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.js +2 -0
- package/dist/lib/context-budget-response.js +8 -8
- package/dist/lib/context-overflow.js +10 -10
- package/dist/lib/locale.d.ts +7 -0
- package/dist/lib/locale.js +35 -0
- package/dist/lib/oauth-success.html +125 -125
- package/dist/lib/policy/runtime-policy-cache.js +1 -1
- package/dist/lib/policy/runtime-policy.d.ts +1 -0
- package/dist/lib/policy/runtime-policy.js +4 -0
- package/dist/lib/preemptive-quota-scheduler.js +12 -3
- package/dist/lib/prompts/codex-host-bridge.js +138 -138
- package/dist/lib/prompts/codex.js +79 -79
- package/dist/lib/quota-cache.d.ts +4 -0
- package/dist/lib/quota-probe.js +2 -1
- package/dist/lib/quota-readiness.d.ts +1 -1
- package/dist/lib/quota-readiness.js +9 -2
- package/dist/lib/request/proxy-fetch.d.ts +5 -0
- package/dist/lib/request/proxy-fetch.js +37 -0
- package/dist/lib/request/rate-limit-decision.js +1 -1
- package/dist/lib/request/url-rewriting.js +17 -0
- package/dist/lib/runtime/account-model-catalog.d.ts +2 -0
- package/dist/lib/runtime/account-model-catalog.js +7 -2
- package/dist/lib/runtime/account-reset-credits.d.ts +11 -2
- package/dist/lib/runtime/account-reset-credits.js +35 -12
- package/dist/lib/runtime/app-bind.d.ts +4 -1
- package/dist/lib/runtime/app-bind.js +63 -3
- package/dist/lib/runtime/automatic-account-checks.js +6 -5
- package/dist/lib/runtime/automatic-subscription-checks.js +4 -3
- package/dist/lib/runtime/model-discovery-status.js +2 -2
- package/dist/lib/runtime/reset-credit-routing.d.ts +11 -3
- package/dist/lib/runtime/reset-credit-routing.js +66 -21
- package/dist/lib/runtime/reset-credits.d.ts +31 -7
- package/dist/lib/runtime/reset-credits.js +135 -28
- package/dist/lib/runtime/responses-websocket.d.ts +30 -0
- package/dist/lib/runtime/responses-websocket.js +243 -29
- package/dist/lib/runtime/rotation-account-selection.d.ts +5 -4
- package/dist/lib/runtime/rotation-proxy-state.d.ts +3 -0
- package/dist/lib/runtime/rotation-server-types.d.ts +4 -1
- package/dist/lib/runtime/subscription-quota-order.d.ts +1 -0
- package/dist/lib/runtime/subscription-quota-order.js +7 -3
- package/dist/lib/runtime/workspace-model-scopes.d.ts +2 -0
- package/dist/lib/runtime/workspace-model-scopes.js +14 -4
- package/dist/lib/runtime-rotation-proxy.js +138 -38
- package/dist/lib/schemas.d.ts +5 -0
- package/dist/lib/schemas.js +1 -0
- package/dist/lib/storage/snapshot-merge.js +15 -4
- package/dist/lib/web-dashboard-html.d.ts +1 -0
- package/dist/lib/web-dashboard-html.js +66 -0
- package/dist/lib/web-dashboard.d.ts +36 -0
- package/dist/lib/web-dashboard.js +322 -0
- 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 +209 -207
- package/scripts/codex-app-launcher.js +653 -653
- package/scripts/codex-app-router.js +446 -402
- package/scripts/codex-bin-resolver.js +332 -332
- package/scripts/codex-multi-auth.js +65 -65
- package/scripts/codex-routing.js +63 -62
- package/scripts/codex.js +7143 -7006
- 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
|
@@ -672,12 +672,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
672
672
|
capabilityPolicy: capabilityPolicyStore,
|
|
673
673
|
});
|
|
674
674
|
}
|
|
675
|
-
catch
|
|
676
|
-
logWarn("Runtime policy evaluation failed
|
|
677
|
-
error: policyError instanceof Error
|
|
678
|
-
? policyError.message
|
|
679
|
-
: String(policyError),
|
|
680
|
-
});
|
|
675
|
+
catch {
|
|
676
|
+
logWarn("Runtime policy evaluation failed; request blocked.");
|
|
681
677
|
}
|
|
682
678
|
usageRecorder = createRuntimeUsageRecorder({
|
|
683
679
|
source: "plugin-host",
|
|
@@ -692,6 +688,25 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
692
688
|
statusCode: null,
|
|
693
689
|
errorCode: "plugin_host_request_failed",
|
|
694
690
|
};
|
|
691
|
+
if (!runtimePolicyDecision) {
|
|
692
|
+
runtimeMetrics.failedRequests += 1;
|
|
693
|
+
runtimeMetrics.lastError = "Runtime policy unavailable";
|
|
694
|
+
syncRuntimeObservability(requestTraceId);
|
|
695
|
+
usageCompletion = {
|
|
696
|
+
outcome: "blocked",
|
|
697
|
+
statusCode: 503,
|
|
698
|
+
errorCode: "runtime_policy_unavailable",
|
|
699
|
+
};
|
|
700
|
+
return new Response(JSON.stringify({
|
|
701
|
+
error: {
|
|
702
|
+
message: "Runtime policy could not be loaded for this local request.",
|
|
703
|
+
code: "runtime_policy_unavailable",
|
|
704
|
+
},
|
|
705
|
+
}), {
|
|
706
|
+
status: 503,
|
|
707
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
708
|
+
});
|
|
709
|
+
}
|
|
695
710
|
if (runtimePolicyDecision && !runtimePolicyDecision.allowed) {
|
|
696
711
|
runtimeMetrics.failedRequests += 1;
|
|
697
712
|
runtimeMetrics.lastError = "Runtime policy blocked request";
|
|
@@ -715,6 +730,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
715
730
|
},
|
|
716
731
|
});
|
|
717
732
|
}
|
|
733
|
+
// This host cannot prove ordinary capacity and permitted reset
|
|
734
|
+
// recovery are exhausted, so emergency accounts stay reserved.
|
|
735
|
+
const blockedAccountIndexes = new Set([
|
|
736
|
+
...(runtimePolicyDecision?.blockedAccountIndexes ?? []),
|
|
737
|
+
...(runtimePolicyDecision?.emergencyAccountIndexes ?? []),
|
|
738
|
+
]);
|
|
718
739
|
const abortSignal = requestInit?.signal ?? init?.signal ?? null;
|
|
719
740
|
const sleep = createAbortableSleep(abortSignal);
|
|
720
741
|
const poolCooldownRemainingMs = getPoolExhaustionCooldownRemaining();
|
|
@@ -843,7 +864,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
843
864
|
typeof preferredSessionAccountIndex === "number") {
|
|
844
865
|
usedPreferredSessionAccount = true;
|
|
845
866
|
if (accountManager.isAccountAvailableForFamily(preferredSessionAccountIndex, modelFamily, model) &&
|
|
846
|
-
!
|
|
867
|
+
!blockedAccountIndexes.has(preferredSessionAccountIndex)) {
|
|
847
868
|
account = accountManager.getAccountByIndex(preferredSessionAccountIndex);
|
|
848
869
|
if (account) {
|
|
849
870
|
account.lastUsed = Date.now();
|
|
@@ -858,13 +879,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
858
879
|
account = accountManager.getCurrentOrNextForFamilyHybrid(modelFamily, model, {
|
|
859
880
|
pidOffsetEnabled,
|
|
860
881
|
scoreBoostByAccount: capabilityBoostByAccount,
|
|
882
|
+
blockedAccountIndexes,
|
|
861
883
|
});
|
|
862
884
|
}
|
|
863
885
|
if (!account || attempted.has(account.index)) {
|
|
864
886
|
break;
|
|
865
887
|
}
|
|
866
888
|
attempted.add(account.index);
|
|
867
|
-
if (
|
|
889
|
+
if (blockedAccountIndexes.has(account.index)) {
|
|
868
890
|
continue;
|
|
869
891
|
}
|
|
870
892
|
// Log account selection for debugging rotation
|
|
@@ -1451,7 +1473,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1451
1473
|
runtimeMetrics.cumulativeLatencyMs += fetchLatencyMs;
|
|
1452
1474
|
let responseForSuccess = response;
|
|
1453
1475
|
if (isStreaming) {
|
|
1454
|
-
const streamFallbackCandidateOrder = buildAdaptiveStreamFailoverCandidateOrder(account.index, accountSnapshotList);
|
|
1476
|
+
const streamFallbackCandidateOrder = buildAdaptiveStreamFailoverCandidateOrder(account.index, accountSnapshotList.filter((candidate) => !blockedAccountIndexes.has(candidate.index)));
|
|
1455
1477
|
runtimeMetrics.lastStreamFailoverCandidateCount =
|
|
1456
1478
|
streamFallbackCandidateOrder.length;
|
|
1457
1479
|
runtimeMetrics.streamFailoverCandidatesConsidered +=
|
|
@@ -1472,7 +1494,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1472
1494
|
if (abortSignal?.aborted) {
|
|
1473
1495
|
return null;
|
|
1474
1496
|
}
|
|
1475
|
-
if (
|
|
1497
|
+
if (blockedAccountIndexes.has(candidateIndex) ||
|
|
1498
|
+
!accountManager.isAccountAvailableForFamily(candidateIndex, modelFamily, model)) {
|
|
1476
1499
|
continue;
|
|
1477
1500
|
}
|
|
1478
1501
|
const fallbackAccount = accountManager.getAccountByIndex(candidateIndex);
|
|
@@ -7,6 +7,8 @@ export interface AccountPolicy {
|
|
|
7
7
|
priority?: number;
|
|
8
8
|
/** Opt in to periodic first-use completion for unused personal subscriptions. */
|
|
9
9
|
autoPrime?: boolean;
|
|
10
|
+
/** Pool backup only, after primary subscriptions and reset credits are exhausted. */
|
|
11
|
+
emergencyOnly?: boolean;
|
|
10
12
|
paused: boolean;
|
|
11
13
|
drained: boolean;
|
|
12
14
|
note: string | null;
|
|
@@ -37,6 +37,7 @@ function normalizePolicy(key, value) {
|
|
|
37
37
|
weight: normalizeWeight(record.weight),
|
|
38
38
|
priority: normalizePriority(record.priority),
|
|
39
39
|
autoPrime: record.autoPrime === true,
|
|
40
|
+
emergencyOnly: record.emergencyOnly === true,
|
|
40
41
|
paused: record.paused === true,
|
|
41
42
|
drained: record.drained === true,
|
|
42
43
|
note: note.length > 0 ? note.slice(0, 500) : null,
|
|
@@ -112,7 +113,7 @@ export async function loadAccountPolicyStore(options = {}) {
|
|
|
112
113
|
if (!key.startsWith("sha256:"))
|
|
113
114
|
continue;
|
|
114
115
|
// Legacy rows may omit flags; explicit malformed values are not OFF.
|
|
115
|
-
if (!isRecord(row) || ["autoPrime", "paused", "drained"].some(flag => flag in row && typeof row[flag] !== "boolean")) {
|
|
116
|
+
if (!isRecord(row) || ["autoPrime", "emergencyOnly", "paused", "drained"].some(flag => flag in row && typeof row[flag] !== "boolean")) {
|
|
116
117
|
throw new Error("Invalid account policy row");
|
|
117
118
|
}
|
|
118
119
|
}
|
|
@@ -241,6 +242,7 @@ export function upsertAccountPolicy(store, accountKey, mutate, now = Date.now())
|
|
|
241
242
|
next.weight = normalizeWeight(next.weight);
|
|
242
243
|
next.priority = normalizePriority(next.priority);
|
|
243
244
|
next.autoPrime = next.autoPrime === true;
|
|
245
|
+
next.emergencyOnly = next.emergencyOnly === true;
|
|
244
246
|
next.updatedAt = stampUpdatedAt(now, store.accounts[accountKey]?.updatedAt);
|
|
245
247
|
store.accounts[accountKey] = next;
|
|
246
248
|
return next;
|
|
@@ -15,6 +15,12 @@ export interface RateLimitState {
|
|
|
15
15
|
export interface RateLimitedEntity {
|
|
16
16
|
rateLimitResetTimes: RateLimitState;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Bound legacy persisted routing deadlines once, when loading the account.
|
|
20
|
+
* These deadlines schedule revalidation; quota snapshots retain upstream resets.
|
|
21
|
+
* Never call this on every selection: that would slide the deadline indefinitely.
|
|
22
|
+
*/
|
|
23
|
+
export declare function boundRateLimitCache(times: RateLimitState, now?: number): RateLimitState;
|
|
18
24
|
export declare function clearExpiredRateLimits(entity: RateLimitedEntity): void;
|
|
19
25
|
/**
|
|
20
26
|
* Remove every rate-limit reset entry from `entity`, regardless of whether the
|
|
@@ -2,7 +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
|
+
import { LUNA_RESERVE_MODEL, RATE_LIMIT_CACHE_TTL_MS } from "../constants.js";
|
|
6
6
|
import { nowMs } from "../utils.js";
|
|
7
7
|
export function parseRateLimitReason(code) {
|
|
8
8
|
if (!code)
|
|
@@ -28,6 +28,19 @@ export function clampNonNegativeInt(value, fallback) {
|
|
|
28
28
|
}
|
|
29
29
|
return value < 0 ? 0 : Math.floor(value);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Bound legacy persisted routing deadlines once, when loading the account.
|
|
33
|
+
* These deadlines schedule revalidation; quota snapshots retain upstream resets.
|
|
34
|
+
* Never call this on every selection: that would slide the deadline indefinitely.
|
|
35
|
+
*/
|
|
36
|
+
export function boundRateLimitCache(times, now = nowMs()) {
|
|
37
|
+
return Object.fromEntries(Object.entries(times).map(([key, until]) => [
|
|
38
|
+
key,
|
|
39
|
+
typeof until === "number"
|
|
40
|
+
? Math.min(until, now + RATE_LIMIT_CACHE_TTL_MS)
|
|
41
|
+
: until,
|
|
42
|
+
]));
|
|
43
|
+
}
|
|
31
44
|
export function clearExpiredRateLimits(entity) {
|
|
32
45
|
const now = nowMs();
|
|
33
46
|
const keys = Object.keys(entity.rateLimitResetTimes);
|
package/dist/lib/accounts.d.ts
CHANGED
|
@@ -70,6 +70,7 @@ export declare class AccountManager {
|
|
|
70
70
|
private readonly persistedWorkspaceSelections;
|
|
71
71
|
private readonly persistedWorkspaces;
|
|
72
72
|
private hadPersistedStorage;
|
|
73
|
+
private hasUnpersistedRateLimitBounds;
|
|
73
74
|
private readonly loadedAccountStorageSnapshot;
|
|
74
75
|
private cursorByFamily;
|
|
75
76
|
private currentAccountIndexByFamily;
|
|
@@ -334,7 +335,11 @@ export declare class AccountManager {
|
|
|
334
335
|
commitRefreshedAuth(source: Pick<ManagedAccount, "index" | "accountId" | "email" | "refreshToken">, auth: OAuthAuthDetails): Promise<ManagedAccount | null>;
|
|
335
336
|
toAuthDetails(account: ManagedAccount): Auth;
|
|
336
337
|
getMinWaitTime(): number;
|
|
337
|
-
|
|
338
|
+
/**
|
|
339
|
+
* Accounts in `excludedIndexes` (policy-blocked or reserved for this request)
|
|
340
|
+
* are not capacity the caller can retry into, so they never shorten the wait.
|
|
341
|
+
*/
|
|
342
|
+
getMinWaitTimeForFamily(family: ModelFamily, model?: string | null, excludedIndexes?: ReadonlySet<number>): number;
|
|
338
343
|
/**
|
|
339
344
|
* Walks forward from `start` (inclusive) looking for the next enabled
|
|
340
345
|
* account, wrapping around at most once. Returns -1 when every account in
|
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, LUNA_RESERVE_MODEL,
|
|
12
|
+
import { ERROR_MESSAGES, HTTP_STATUS, LUNA_RESERVE_MODEL, RATE_LIMIT_CACHE_TTL_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";
|
|
@@ -21,7 +21,7 @@ export { extractAccountId, extractAccountEmail, getAccountIdCandidates, selectBe
|
|
|
21
21
|
export { parseRateLimitReason, getQuotaKey, clampNonNegativeInt, clearExpiredRateLimits, clearAllRateLimits, isRateLimitedForQuotaKey, isRateLimitedForFamily, formatWaitTime, } from "./accounts/rate-limits.js";
|
|
22
22
|
export { lookupCodexCliTokensByEmail, isCodexCliSyncEnabled, } from "./codex-cli/state.js";
|
|
23
23
|
import { codexCliAccountIdFor, extractAccountId, extractAccountEmail, shouldUpdateAccountIdFromToken, sanitizeEmail, } from "./auth/token-utils.js";
|
|
24
|
-
import { clampNonNegativeInt, getQuotaKey, clearExpiredRateLimits, clearAllRateLimits, isRateLimitedForFamily, formatWaitTime, } from "./accounts/rate-limits.js";
|
|
24
|
+
import { boundRateLimitCache, clampNonNegativeInt, getQuotaKey, clearExpiredRateLimits, clearAllRateLimits, isRateLimitedForFamily, formatWaitTime, } from "./accounts/rate-limits.js";
|
|
25
25
|
const log = createLogger("accounts");
|
|
26
26
|
let nextRuntimeCircuitKeyId = 0;
|
|
27
27
|
function getAccountCircuitKey(account) {
|
|
@@ -142,6 +142,7 @@ export class AccountManager {
|
|
|
142
142
|
persistedWorkspaceSelections = new WeakMap();
|
|
143
143
|
persistedWorkspaces = new WeakMap();
|
|
144
144
|
hadPersistedStorage = false;
|
|
145
|
+
hasUnpersistedRateLimitBounds = false;
|
|
145
146
|
loadedAccountStorageSnapshot;
|
|
146
147
|
cursorByFamily = initFamilyState(0);
|
|
147
148
|
currentAccountIndexByFamily = initFamilyState(-1);
|
|
@@ -201,6 +202,19 @@ export class AccountManager {
|
|
|
201
202
|
}
|
|
202
203
|
const manager = new AccountManager(authFallback, sourceOfTruthStorage);
|
|
203
204
|
await manager.hydrateFromCodexCli();
|
|
205
|
+
if (manager.hasUnpersistedRateLimitBounds) {
|
|
206
|
+
// Short-lived wrappers may exit without another save; persisting the
|
|
207
|
+
// bound now keeps each restart from re-bounding from its own load time.
|
|
208
|
+
try {
|
|
209
|
+
await manager.saveToDisk(true);
|
|
210
|
+
manager.hasUnpersistedRateLimitBounds = false;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
log.debug("Failed to persist bounded rate-limit deadlines", {
|
|
214
|
+
error: String(error),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
204
218
|
return manager;
|
|
205
219
|
}
|
|
206
220
|
/** @internal True when construction received a parsed/recovered storage snapshot. */
|
|
@@ -337,6 +351,11 @@ export class AccountManager {
|
|
|
337
351
|
return null;
|
|
338
352
|
}
|
|
339
353
|
const matchesFallback = !!authFallback && fallbackMatchedRowIndex === index;
|
|
354
|
+
const storedRateLimits = account.rateLimitResetTimes ?? {};
|
|
355
|
+
const rateLimitResetTimes = boundRateLimitCache(storedRateLimits, baseNow);
|
|
356
|
+
if (Object.entries(rateLimitResetTimes).some(([key, until]) => until !== storedRateLimits[key])) {
|
|
357
|
+
this.hasUnpersistedRateLimitBounds = true;
|
|
358
|
+
}
|
|
340
359
|
const refreshToken = matchesFallback && authFallback
|
|
341
360
|
? authFallback.refresh
|
|
342
361
|
: account.refreshToken;
|
|
@@ -362,7 +381,7 @@ export class AccountManager {
|
|
|
362
381
|
addedAt: clampNonNegativeInt(account.addedAt, baseNow),
|
|
363
382
|
lastUsed: clampNonNegativeInt(account.lastUsed, 0),
|
|
364
383
|
lastSwitchReason: account.lastSwitchReason,
|
|
365
|
-
rateLimitResetTimes
|
|
384
|
+
rateLimitResetTimes,
|
|
366
385
|
coolingDownUntil: account.coolingDownUntil,
|
|
367
386
|
cooldownReason: account.cooldownReason,
|
|
368
387
|
authInvalidatedAt: account.authInvalidatedAt,
|
|
@@ -1062,9 +1081,9 @@ export class AccountManager {
|
|
|
1062
1081
|
// Fail closed: retain this new limit if the selection cannot be read.
|
|
1063
1082
|
log.warn("Rate limit recorded while account selection metadata is unavailable");
|
|
1064
1083
|
}
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1067
|
-
const retryMs = Math.min(Math.max(0, Math.floor(retryAfterMs)),
|
|
1084
|
+
// A local rejection is authoritative for at most an hour. The next request
|
|
1085
|
+
// then revalidates upstream, including after an out-of-band quota reset.
|
|
1086
|
+
const retryMs = Math.min(Math.max(0, Math.floor(retryAfterMs)), RATE_LIMIT_CACHE_TTL_MS);
|
|
1068
1087
|
const observedAtMs = nowMs();
|
|
1069
1088
|
const resetAt = observedAtMs + retryMs;
|
|
1070
1089
|
// Only carry forward an unsequenced entry that is still replayable.
|
|
@@ -1561,9 +1580,15 @@ export class AccountManager {
|
|
|
1561
1580
|
getMinWaitTime() {
|
|
1562
1581
|
return this.getMinWaitTimeForFamily("codex");
|
|
1563
1582
|
}
|
|
1564
|
-
|
|
1583
|
+
/**
|
|
1584
|
+
* Accounts in `excludedIndexes` (policy-blocked or reserved for this request)
|
|
1585
|
+
* are not capacity the caller can retry into, so they never shorten the wait.
|
|
1586
|
+
*/
|
|
1587
|
+
getMinWaitTimeForFamily(family, model, excludedIndexes) {
|
|
1565
1588
|
const now = nowMs();
|
|
1566
|
-
const enabledAccounts = this.accounts.filter((account) => account.enabled !== false &&
|
|
1589
|
+
const enabledAccounts = this.accounts.filter((account) => account.enabled !== false &&
|
|
1590
|
+
!this.isAccountAuthInvalidated(account) &&
|
|
1591
|
+
!excludedIndexes?.has(account.index));
|
|
1567
1592
|
const available = enabledAccounts.filter((account) => {
|
|
1568
1593
|
clearExpiredRateLimits(account);
|
|
1569
1594
|
return (!isRateLimitedForFamily(account, family, model) &&
|
|
@@ -17,6 +17,7 @@ declare const routeSchema: z.ZodObject<{
|
|
|
17
17
|
accessPrograms: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
18
18
|
modelAccessPrograms: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>>;
|
|
19
19
|
}, z.core.$strip>;
|
|
20
|
+
export declare const MAX_API_ROUTES = 100;
|
|
20
21
|
export type ApiRouteCredential = z.infer<typeof routeSchema>;
|
|
21
22
|
export declare function getApiRoutesPath(): string;
|
|
22
23
|
export declare function loadApiRoutes(path?: string): Promise<ApiRouteCredential[]>;
|
|
@@ -41,9 +41,10 @@ const routeSchema = z.object({
|
|
|
41
41
|
modelAccessPrograms: z.record(modelId, accessPrograms)
|
|
42
42
|
.refine(value => Object.keys(value).length <= 1000).optional(),
|
|
43
43
|
});
|
|
44
|
+
export const MAX_API_ROUTES = 100;
|
|
44
45
|
const schema = z.object({
|
|
45
46
|
version: z.literal(1),
|
|
46
|
-
routes: z.array(routeSchema).max(
|
|
47
|
+
routes: z.array(routeSchema).max(MAX_API_ROUTES),
|
|
47
48
|
});
|
|
48
49
|
export function getApiRoutesPath() {
|
|
49
50
|
return join(getCodexMultiAuthDir(), "api-routes.json");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CODEX_BASE_URL } from "../constants.js";
|
|
2
2
|
import { combineSignals } from "../utils.js";
|
|
3
|
+
import { fetchAuth } from "./http.js";
|
|
3
4
|
const ACCOUNTS_CHECK_PATH = "/wham/accounts/check";
|
|
4
5
|
const ACCOUNTS_CHECK_TIMEOUT_MS = 10_000;
|
|
5
6
|
function readString(value) {
|
|
@@ -49,7 +50,7 @@ export async function fetchAuthorizedAccounts(accessToken, options = {}) {
|
|
|
49
50
|
const token = readString(accessToken);
|
|
50
51
|
if (!token)
|
|
51
52
|
return null;
|
|
52
|
-
const doFetch = options.fetch ??
|
|
53
|
+
const doFetch = options.fetch ?? fetchAuth;
|
|
53
54
|
const timeout = AbortSignal.timeout(ACCOUNTS_CHECK_TIMEOUT_MS);
|
|
54
55
|
// combineSignals wraps AbortSignal.any (available on every Node the
|
|
55
56
|
// engines floor, `>=22.19`, permits) with null-tolerance; the native composite also detaches
|
package/dist/lib/auth/auth.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomBytes, webcrypto } from "node:crypto";
|
|
|
2
2
|
import { logError } from "../logger.js";
|
|
3
3
|
import { safeParseOAuthTokenResponse } from "../schemas.js";
|
|
4
4
|
import { isAbortError } from "../utils.js";
|
|
5
|
+
import { fetchAuth } from "./http.js";
|
|
5
6
|
// OAuth constants (from openai/codex)
|
|
6
7
|
export const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
7
8
|
export const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
@@ -396,7 +397,7 @@ export function parseAuthorizationInput(input) {
|
|
|
396
397
|
* @returns Token result
|
|
397
398
|
*/
|
|
398
399
|
export async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_URI) {
|
|
399
|
-
const res = await
|
|
400
|
+
const res = await fetchAuth(TOKEN_URL, {
|
|
400
401
|
method: "POST",
|
|
401
402
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
402
403
|
body: new URLSearchParams({
|
|
@@ -468,7 +469,7 @@ export function decodeJWT(token) {
|
|
|
468
469
|
}
|
|
469
470
|
export async function refreshAccessToken(refreshToken, options = {}) {
|
|
470
471
|
try {
|
|
471
|
-
const response = await
|
|
472
|
+
const response = await fetchAuth(TOKEN_URL, {
|
|
472
473
|
method: "POST",
|
|
473
474
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
474
475
|
signal: options?.signal,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CLIENT_ID, exchangeAuthorizationCode, sanitizeOAuthResponseBodyForLog, } from "./auth.js";
|
|
2
|
+
import { fetchAuth } from "./http.js";
|
|
2
3
|
const DEVICE_AUTH_BASE_URL = "https://auth.openai.com";
|
|
3
4
|
const DEVICE_AUTH_API_BASE_URL = `${DEVICE_AUTH_BASE_URL}/api/accounts`;
|
|
4
5
|
export const DEVICE_AUTH_VERIFICATION_URL = `${DEVICE_AUTH_BASE_URL}/codex/device`;
|
|
@@ -14,7 +15,7 @@ const DEVICE_AUTH_TRANSIENT_PENDING_STATUSES = new Set([
|
|
|
14
15
|
504,
|
|
15
16
|
]);
|
|
16
17
|
function getFetch(options) {
|
|
17
|
-
return options.fetchImpl ??
|
|
18
|
+
return options.fetchImpl ?? fetchAuth;
|
|
18
19
|
}
|
|
19
20
|
function getNow(options) {
|
|
20
21
|
return options.now ?? Date.now;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { fetchWithProxy as fetchAuth } from "../request/proxy-fetch.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { fetchWithProxy as fetchAuth } from "../request/proxy-fetch.js";
|
|
@@ -45,6 +45,11 @@ export declare function extractAccountId(accessToken?: string): string | undefin
|
|
|
45
45
|
*/
|
|
46
46
|
/** Whether an id is an OpenAI platform org id ("org-..."), never a ChatGPT workspace. */
|
|
47
47
|
export declare function isOpenAiOrgId(accountId: string | undefined): boolean;
|
|
48
|
+
/** Preserve the native client's org-alias mapping; this is not workspace fallback. */
|
|
49
|
+
export declare function nativeWorkspaceIdentity(account: {
|
|
50
|
+
access?: string;
|
|
51
|
+
codexCliMirror?: CodexCliMirror;
|
|
52
|
+
}, storedId: string | undefined): string | undefined;
|
|
48
53
|
export declare function resolveCodexAuthAccountId(accountId: string | undefined, accessToken?: string, idToken?: string): string | undefined;
|
|
49
54
|
/**
|
|
50
55
|
* The account id to hand the ~/.codex/auth.json writer for a saved account,
|
|
@@ -261,6 +261,16 @@ export function extractAccountId(accessToken) {
|
|
|
261
261
|
export function isOpenAiOrgId(accountId) {
|
|
262
262
|
return /^org-/i.test(accountId?.trim() ?? "");
|
|
263
263
|
}
|
|
264
|
+
/** Preserve the native client's org-alias mapping; this is not workspace fallback. */
|
|
265
|
+
export function nativeWorkspaceIdentity(account, storedId) {
|
|
266
|
+
const id = storedId?.trim();
|
|
267
|
+
if (!id)
|
|
268
|
+
return undefined;
|
|
269
|
+
if (!isOpenAiOrgId(id))
|
|
270
|
+
return id;
|
|
271
|
+
const native = codexCliAccountIdFor({ accountId: id, codexCliMirror: account.codexCliMirror }, account.access);
|
|
272
|
+
return native && !isOpenAiOrgId(native) ? native : undefined;
|
|
273
|
+
}
|
|
264
274
|
export function resolveCodexAuthAccountId(accountId, accessToken, idToken) {
|
|
265
275
|
const trimmed = accountId?.trim();
|
|
266
276
|
if (!trimmed)
|
|
@@ -7,6 +7,7 @@ function printAccountUsage(logInfo) {
|
|
|
7
7
|
" codex-multi-auth account weight <index> <0..10>",
|
|
8
8
|
" codex-multi-auth account priority <index> <0..9>",
|
|
9
9
|
" codex-multi-auth account auto-prime <index> on|off",
|
|
10
|
+
" codex-multi-auth account emergency-only <index> on|off",
|
|
10
11
|
" codex-multi-auth account pause|unpause|drain|undrain <index>",
|
|
11
12
|
" codex-multi-auth account note <index> <text>",
|
|
12
13
|
" codex-multi-auth account policy list [--json]",
|
|
@@ -37,6 +38,7 @@ function policySummary(store, storage) {
|
|
|
37
38
|
weight: policy?.weight ?? 1,
|
|
38
39
|
priority: policy?.priority ?? 1,
|
|
39
40
|
autoPrime: policy?.autoPrime ?? false,
|
|
41
|
+
emergencyOnly: policy?.emergencyOnly ?? false,
|
|
40
42
|
paused: policy?.paused ?? false,
|
|
41
43
|
drained: policy?.drained ?? false,
|
|
42
44
|
note: policy?.note ?? null,
|
|
@@ -84,6 +86,7 @@ export async function runAccountCommand(args, deps) {
|
|
|
84
86
|
`weight=${entry.weight}`,
|
|
85
87
|
`priority=${entry.priority}`,
|
|
86
88
|
`auto-prime=${entry.autoPrime ? "on" : "off"}`,
|
|
89
|
+
entry.emergencyOnly ? "emergency-only" : null,
|
|
87
90
|
entry.paused ? "paused" : null,
|
|
88
91
|
entry.drained ? "drained" : null,
|
|
89
92
|
entry.tags.length > 0 ? `tags=${entry.tags.join(",")}` : null,
|
|
@@ -120,6 +123,18 @@ export async function runAccountCommand(args, deps) {
|
|
|
120
123
|
logInfo(`${command === "tag" ? "Tagged" : "Removed tag from"} account ${resolved.index + 1}: ${policy.tags.join(",") || "none"}`);
|
|
121
124
|
return 0;
|
|
122
125
|
}
|
|
126
|
+
if (command === "emergency-only") {
|
|
127
|
+
if (rest.length !== 2 || !["on", "off"].includes(rest[1] ?? "")) {
|
|
128
|
+
logError("emergency-only requires on or off.");
|
|
129
|
+
return 1;
|
|
130
|
+
}
|
|
131
|
+
await updateStore(store => ({
|
|
132
|
+
result: upsertAccountPolicy(store, accountKey, next => { next.emergencyOnly = rest[1] === "on"; }, now),
|
|
133
|
+
dirty: true,
|
|
134
|
+
}));
|
|
135
|
+
logInfo(`Emergency-only ${rest[1]} for account ${resolved.index + 1}.`);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
123
138
|
if (command === "auto-prime") {
|
|
124
139
|
if (rest.length !== 2 || !["on", "off"].includes(rest[1] ?? "")) {
|
|
125
140
|
logError("auto-prime requires on or off.");
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type OutputLocaleSetting } from "../../locale.js";
|
|
2
|
+
export interface LocaleCommandDeps {
|
|
3
|
+
loadConfig: () => {
|
|
4
|
+
locale?: OutputLocaleSetting;
|
|
5
|
+
};
|
|
6
|
+
saveConfig: (patch: {
|
|
7
|
+
locale: OutputLocaleSetting;
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
logInfo?: (message: string) => void;
|
|
10
|
+
logError?: (message: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function runLocaleCommand(args: string[], deps: LocaleCommandDeps): Promise<number>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { localeName, normalizeOutputLocaleSetting, resolveOutputLocale, } from "../../locale.js";
|
|
2
|
+
export async function runLocaleCommand(args, deps) {
|
|
3
|
+
const logInfo = deps.logInfo ?? console.log;
|
|
4
|
+
const logError = deps.logError ?? console.error;
|
|
5
|
+
const requested = args[0]?.trim();
|
|
6
|
+
if (args.length > 1 || requested === "--help" || requested === "-h") {
|
|
7
|
+
logInfo("Usage: codex-multi-auth config locale [auto|en|zh]");
|
|
8
|
+
return requested === "--help" || requested === "-h" ? 0 : 1;
|
|
9
|
+
}
|
|
10
|
+
if (!requested) {
|
|
11
|
+
const configured = deps.loadConfig().locale ?? "auto";
|
|
12
|
+
const effective = resolveOutputLocale(configured);
|
|
13
|
+
logInfo(`Locale: ${configured} (effective: ${localeName(effective)})`);
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
const locale = normalizeOutputLocaleSetting(requested);
|
|
17
|
+
if (!locale) {
|
|
18
|
+
logError(`Unsupported locale: ${requested}. Choose auto, en, or zh.`);
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
await deps.saveConfig({ locale });
|
|
22
|
+
const effective = resolveOutputLocale(locale);
|
|
23
|
+
logInfo(`Locale saved: ${locale} (effective: ${localeName(effective)})`);
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { AccountManager } from '../../accounts.js';
|
|
2
2
|
import { loadAccounts } from '../../storage.js';
|
|
3
3
|
import { runWithGlobalStoragePath } from '../../storage/path-state.js';
|
|
4
|
-
import { createResetCreditService, resetTargetForStoredAccount, resetAccountLabel } from '../../runtime/account-reset-credits.js';
|
|
4
|
+
import { createResetCreditService, resetTargetForStoredAccount, resetAccountLabel, formatResetCreditLine } from '../../runtime/account-reset-credits.js';
|
|
5
5
|
import { withCheckProgress } from '../../ui/check-progress.js';
|
|
6
|
+
// A pending result may belong to a workspace that was since disabled or deselected.
|
|
7
|
+
function ownsPendingKey(account, key) {
|
|
8
|
+
const workspaces = account.workspaces?.map(workspace => ({ ...workspace, enabled: true }));
|
|
9
|
+
const selections = [undefined, ...(workspaces ?? []).map((_, index) => index)];
|
|
10
|
+
return selections.some(index => resetTargetForStoredAccount({ ...account, enabled: true, workspaces: index === undefined ? undefined : workspaces, currentWorkspaceIndex: index ?? account.currentWorkspaceIndex })?.key === key);
|
|
11
|
+
}
|
|
6
12
|
const usage = 'Usage: codex-multi-auth resets list [--refresh] | redeem <account-number> | auto manual|last-resort';
|
|
7
13
|
export async function runResetsCommand(args) {
|
|
8
14
|
const [command = 'list', value, ...extra] = args;
|
|
@@ -47,7 +53,13 @@ export async function runResetsCommand(args) {
|
|
|
47
53
|
const index = (storage?.accounts ?? []).findIndex(a => resetTargetForStoredAccount(a)?.key === state.lastRedemption?.key);
|
|
48
54
|
console.log(`Last confirmed reset: ${index >= 0 ? `account ${index + 1}` : "removed account"}; ${state.lastRedemption.outcome}; ${state.lastRedemption.automatic ? "automatic" : "explicit"}`);
|
|
49
55
|
}
|
|
50
|
-
(storage?.accounts ?? []).forEach((a, i) => {
|
|
56
|
+
(storage?.accounts ?? []).forEach((a, i) => {
|
|
57
|
+
const target = resetTargetForStoredAccount(a);
|
|
58
|
+
const ambiguous = target && (keyCounts.get(target.key) ?? 0) > 1;
|
|
59
|
+
const snapshot = target && !ambiguous ? (refreshed ?? state.snapshots)[target.key] : undefined;
|
|
60
|
+
console.log(`${resetAccountLabel(a, i)}: ${snapshot?.availableCount ?? 'unknown'} reset credits${snapshot ? ` (checked ${Math.max(0, Math.floor((Date.now() - snapshot.updatedAt) / 1000))}s ago)` : ''}${ambiguous ? ' [ambiguous account identity; repair duplicate records]' : ''}${target?.key === state.pending?.key ? ' [redemption pending; retry this account]' : ''}`);
|
|
61
|
+
snapshot?.credits?.forEach((c, idx) => console.log(formatResetCreditLine(c, idx)));
|
|
62
|
+
});
|
|
51
63
|
const failed = refreshed ? targets.filter(target => (keyCounts.get(target.key) ?? 0) > 1 || !refreshed[target.key]).length : 0;
|
|
52
64
|
if (failed) {
|
|
53
65
|
console.error(`${failed} of ${targets.length} reset-credit reads failed. Check account authentication and the native Codex backend (CODEX_MULTI_AUTH_USAGE_CODEX_BIN selects an executable). No credits were redeemed.`);
|
|
@@ -61,10 +73,11 @@ export async function runResetsCommand(args) {
|
|
|
61
73
|
const pending = command === 'redeem' ? await service.status().then(s => s.pending ?? null, () => ({ key: undefined })) : null;
|
|
62
74
|
// The pending record can belong to another account (a redeem is refused while
|
|
63
75
|
// any result is pending); point at the account that actually needs the retry.
|
|
64
|
-
const
|
|
76
|
+
const pendingKey = pending?.key;
|
|
77
|
+
const owner = pendingKey ? (storage?.accounts ?? []).findIndex(a => ownsPendingKey(a, pendingKey)) : -1;
|
|
65
78
|
const pendingAccount = owner >= 0 ? storage?.accounts[owner] : undefined;
|
|
66
|
-
const disabled = pendingAccount ? resetTargetForStoredAccount(pendingAccount)
|
|
67
|
-
const retry = disabled ? `account ${owner + 1} or its workspace is disabled; re-enable it before retrying the pending result` : pending?.key && owner < 0 ? 'check it there (the pending account was removed)' : owner >= 0 && owner !== Number(value) - 1 ? `retry account ${owner + 1}, which holds the pending result` : 'retry the same account';
|
|
79
|
+
const disabled = pendingAccount ? resetTargetForStoredAccount(pendingAccount)?.key !== pendingKey : false;
|
|
80
|
+
const retry = disabled ? `account ${owner + 1} or its pending workspace is disabled or no longer selected; re-enable and select it before retrying the pending result` : pending?.key && owner < 0 ? 'check it there (the pending account was removed)' : owner >= 0 && owner !== Number(value) - 1 ? `retry account ${owner + 1}, which holds the pending result` : 'retry the same account';
|
|
68
81
|
console.error(command === 'list' ? 'Reset-credit availability could not be refreshed. No credits were redeemed.' : command === 'auto' ? 'Reset-credit settings could not be updated.' : pending ? `Reset operation could not be confirmed. No new automatic redemption will be attempted while a result is pending; use resets list and ${retry}.` : 'No reset credit was redeemed. Availability could not be confirmed or a reset was just redeemed; run resets list --refresh and try again.');
|
|
69
82
|
return 1;
|
|
70
83
|
}
|
|
@@ -7,6 +7,7 @@ import type { QuotaCacheData } from "../../quota-cache.js";
|
|
|
7
7
|
import { type AppBindRouterStatus } from "../../runtime/app-bind.js";
|
|
8
8
|
import { type RuntimeAccountSignal } from "../../runtime/runtime-current-account.js";
|
|
9
9
|
import type { RuntimeObservabilitySnapshot } from "../../runtime/runtime-observability.js";
|
|
10
|
+
import { type OutputLocale } from "../../locale.js";
|
|
10
11
|
import type { AccountStorageV3, StorageHealthSummary } from "../../storage.js";
|
|
11
12
|
type LoadedStorage = AccountStorageV3 | null;
|
|
12
13
|
export interface StatusCommandDeps {
|
|
@@ -31,6 +32,9 @@ export interface StatusCommandDeps {
|
|
|
31
32
|
logInfo?: (message: string) => void;
|
|
32
33
|
/** When true, emit a single machine-readable JSON object instead of text (cli-manager-03). */
|
|
33
34
|
json?: boolean;
|
|
35
|
+
/** Compact human-facing summary. Undefined keeps the legacy detailed view for API callers. */
|
|
36
|
+
compact?: boolean;
|
|
37
|
+
locale?: OutputLocale;
|
|
34
38
|
}
|
|
35
39
|
export declare function runStatusCommand(deps: StatusCommandDeps): Promise<number>;
|
|
36
40
|
export interface FeaturesCommandDeps {
|