codexuse-cli 3.9.2 → 3.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/index.js +2111 -521
- package/dist/index.js.map +1 -1
- package/dist/server/index.mjs +2902 -697
- package/dist/server/{open-DX6_a9Ta.mjs → open-BWXrZXJl.mjs} +6 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2094,6 +2094,117 @@ function normalizeCommitMessagePrompt(value) {
|
|
|
2094
2094
|
return normalized.length > 0 ? normalized : null;
|
|
2095
2095
|
}
|
|
2096
2096
|
|
|
2097
|
+
// ../../packages/contracts/src/settings/auto-roll.ts
|
|
2098
|
+
var DEFAULT_AUTO_ROLL_ENABLED = false;
|
|
2099
|
+
var DEFAULT_AUTO_ROLL_REARM_REMAINING_THRESHOLD = 15;
|
|
2100
|
+
var DEFAULT_AUTO_ROLL_SWITCH_REMAINING_THRESHOLD = 5;
|
|
2101
|
+
var DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL = false;
|
|
2102
|
+
var DEFAULT_LAUNCH_OFFICIAL_CODEX_WHEN_CLOSED_ON_AUTO_ROLL = false;
|
|
2103
|
+
var DEFAULT_AUTO_ROLL_PRIORITY_ORDER = [];
|
|
2104
|
+
var DEFAULT_LOW_REMAINING_NOTIFICATION_ENABLED = false;
|
|
2105
|
+
var DEFAULT_LOW_REMAINING_NOTIFICATION_THRESHOLD = 1;
|
|
2106
|
+
var AUTO_ROLL_SWITCH_REMAINING_MIN = 0;
|
|
2107
|
+
var AUTO_ROLL_SWITCH_REMAINING_MAX = 50;
|
|
2108
|
+
var AUTO_ROLL_REARM_REMAINING_MAX = 100;
|
|
2109
|
+
var LOW_REMAINING_NOTIFICATION_MIN = 1;
|
|
2110
|
+
var LOW_REMAINING_NOTIFICATION_MAX = 50;
|
|
2111
|
+
function clampNumber(value, min, max) {
|
|
2112
|
+
return Math.min(max, Math.max(min, value));
|
|
2113
|
+
}
|
|
2114
|
+
function resolveFiniteNumber(value, fallback) {
|
|
2115
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
2116
|
+
}
|
|
2117
|
+
function legacyUsedThresholdToRemaining(value, fallbackUsed) {
|
|
2118
|
+
const used = clampNumber(resolveFiniteNumber(value, fallbackUsed), 0, 100);
|
|
2119
|
+
return 100 - used;
|
|
2120
|
+
}
|
|
2121
|
+
function sanitizeAutoRollSwitchRemainingThreshold(value) {
|
|
2122
|
+
const numeric = resolveFiniteNumber(
|
|
2123
|
+
value,
|
|
2124
|
+
DEFAULT_AUTO_ROLL_SWITCH_REMAINING_THRESHOLD
|
|
2125
|
+
);
|
|
2126
|
+
return clampNumber(
|
|
2127
|
+
numeric,
|
|
2128
|
+
AUTO_ROLL_SWITCH_REMAINING_MIN,
|
|
2129
|
+
AUTO_ROLL_SWITCH_REMAINING_MAX
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
function sanitizeAutoRollRearmRemainingThreshold(value, switchRemainingThreshold) {
|
|
2133
|
+
const sanitizedSwitch = sanitizeAutoRollSwitchRemainingThreshold(
|
|
2134
|
+
switchRemainingThreshold
|
|
2135
|
+
);
|
|
2136
|
+
const numeric = resolveFiniteNumber(
|
|
2137
|
+
value,
|
|
2138
|
+
DEFAULT_AUTO_ROLL_REARM_REMAINING_THRESHOLD
|
|
2139
|
+
);
|
|
2140
|
+
return clampNumber(
|
|
2141
|
+
numeric,
|
|
2142
|
+
sanitizedSwitch + 1,
|
|
2143
|
+
AUTO_ROLL_REARM_REMAINING_MAX
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
function sanitizeAutoRollThresholds(rearmRemainingThreshold, switchRemainingThreshold) {
|
|
2147
|
+
const sanitizedSwitch = sanitizeAutoRollSwitchRemainingThreshold(
|
|
2148
|
+
switchRemainingThreshold
|
|
2149
|
+
);
|
|
2150
|
+
const sanitizedRearm = sanitizeAutoRollRearmRemainingThreshold(
|
|
2151
|
+
rearmRemainingThreshold,
|
|
2152
|
+
sanitizedSwitch
|
|
2153
|
+
);
|
|
2154
|
+
return {
|
|
2155
|
+
rearmRemainingThreshold: sanitizedRearm,
|
|
2156
|
+
switchRemainingThreshold: sanitizedSwitch
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
function sanitizeAutoRollPriorityOrder(value) {
|
|
2160
|
+
if (!Array.isArray(value)) {
|
|
2161
|
+
return [...DEFAULT_AUTO_ROLL_PRIORITY_ORDER];
|
|
2162
|
+
}
|
|
2163
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2164
|
+
const normalized = [];
|
|
2165
|
+
for (const item of value) {
|
|
2166
|
+
if (typeof item !== "string") {
|
|
2167
|
+
continue;
|
|
2168
|
+
}
|
|
2169
|
+
const trimmed = item.trim();
|
|
2170
|
+
if (!trimmed || seen.has(trimmed)) {
|
|
2171
|
+
continue;
|
|
2172
|
+
}
|
|
2173
|
+
seen.add(trimmed);
|
|
2174
|
+
normalized.push(trimmed);
|
|
2175
|
+
}
|
|
2176
|
+
return normalized;
|
|
2177
|
+
}
|
|
2178
|
+
function sanitizeLowRemainingNotificationThreshold(value) {
|
|
2179
|
+
const numeric = resolveFiniteNumber(value, DEFAULT_LOW_REMAINING_NOTIFICATION_THRESHOLD);
|
|
2180
|
+
return clampNumber(
|
|
2181
|
+
numeric,
|
|
2182
|
+
LOW_REMAINING_NOTIFICATION_MIN,
|
|
2183
|
+
LOW_REMAINING_NOTIFICATION_MAX
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
function normalizeAutoRollSettings(raw) {
|
|
2187
|
+
const enabled = typeof raw?.enabled === "boolean" ? raw.enabled : DEFAULT_AUTO_ROLL_ENABLED;
|
|
2188
|
+
const rawSwitchRemaining = typeof raw?.switchRemainingThreshold === "number" ? raw.switchRemainingThreshold : legacyUsedThresholdToRemaining(raw?.switchThreshold, 95);
|
|
2189
|
+
const rawRearmRemaining = typeof raw?.rearmRemainingThreshold === "number" ? raw.rearmRemainingThreshold : legacyUsedThresholdToRemaining(raw?.warningThreshold, 85);
|
|
2190
|
+
const {
|
|
2191
|
+
rearmRemainingThreshold: normalizedRearm,
|
|
2192
|
+
switchRemainingThreshold: normalizedSwitch
|
|
2193
|
+
} = sanitizeAutoRollThresholds(rawRearmRemaining, rawSwitchRemaining);
|
|
2194
|
+
return {
|
|
2195
|
+
enabled,
|
|
2196
|
+
rearmRemainingThreshold: normalizedRearm,
|
|
2197
|
+
switchRemainingThreshold: normalizedSwitch,
|
|
2198
|
+
restartOfficialCodexOnAutoRoll: raw?.restartOfficialCodexOnAutoRoll === true ? true : DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL,
|
|
2199
|
+
launchOfficialCodexWhenClosedOnAutoRoll: raw?.launchOfficialCodexWhenClosedOnAutoRoll === true ? true : DEFAULT_LAUNCH_OFFICIAL_CODEX_WHEN_CLOSED_ON_AUTO_ROLL,
|
|
2200
|
+
priorityOrder: sanitizeAutoRollPriorityOrder(raw?.priorityOrder),
|
|
2201
|
+
lowRemainingNotificationEnabled: raw?.lowRemainingNotificationEnabled === true ? true : DEFAULT_LOW_REMAINING_NOTIFICATION_ENABLED,
|
|
2202
|
+
lowRemainingNotificationThreshold: sanitizeLowRemainingNotificationThreshold(
|
|
2203
|
+
typeof raw?.lowRemainingNotificationThreshold === "number" ? raw.lowRemainingNotificationThreshold : Number.NaN
|
|
2204
|
+
)
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2097
2208
|
// ../../packages/runtime-app-state/src/storage/documents.ts
|
|
2098
2209
|
var import_node_fs = require("fs");
|
|
2099
2210
|
var import_node_path = __toESM(require("path"), 1);
|
|
@@ -2351,9 +2462,25 @@ function createDefaultAppState() {
|
|
|
2351
2462
|
schemaVersion: 1,
|
|
2352
2463
|
autoRoll: {
|
|
2353
2464
|
enabled: false,
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
restartOfficialCodexOnAutoRoll: false
|
|
2465
|
+
rearmRemainingThreshold: 15,
|
|
2466
|
+
switchRemainingThreshold: 5,
|
|
2467
|
+
restartOfficialCodexOnAutoRoll: false,
|
|
2468
|
+
launchOfficialCodexWhenClosedOnAutoRoll: false,
|
|
2469
|
+
priorityOrder: [],
|
|
2470
|
+
lowRemainingNotificationEnabled: false,
|
|
2471
|
+
lowRemainingNotificationThreshold: 1
|
|
2472
|
+
},
|
|
2473
|
+
officialCodex: {
|
|
2474
|
+
lastProfileSwitchAt: null,
|
|
2475
|
+
lastProfileSwitchProfileKey: null,
|
|
2476
|
+
lastVerifiedLaunchAt: null,
|
|
2477
|
+
lastVerifiedLaunchProfileKey: null,
|
|
2478
|
+
lastObservedPid: null,
|
|
2479
|
+
lastRestartStatus: null,
|
|
2480
|
+
lastRestartReason: null,
|
|
2481
|
+
activity: [],
|
|
2482
|
+
instancesByProfileName: {},
|
|
2483
|
+
pendingRestartDebt: null
|
|
2357
2484
|
},
|
|
2358
2485
|
app: {
|
|
2359
2486
|
lastAppVersion: null,
|
|
@@ -2454,34 +2581,114 @@ function asString(value) {
|
|
|
2454
2581
|
const trimmed = value.trim();
|
|
2455
2582
|
return trimmed.length > 0 ? trimmed : null;
|
|
2456
2583
|
}
|
|
2584
|
+
function asNumberOrNull(value) {
|
|
2585
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2586
|
+
}
|
|
2587
|
+
function asBooleanOrNull(value) {
|
|
2588
|
+
return typeof value === "boolean" ? value : null;
|
|
2589
|
+
}
|
|
2457
2590
|
function normalizeAppState(raw) {
|
|
2458
2591
|
const defaults = createDefaultAppState();
|
|
2459
2592
|
if (!isRecord2(raw)) {
|
|
2460
2593
|
return defaults;
|
|
2461
2594
|
}
|
|
2595
|
+
const rawAutoRoll = isRecord2(raw.autoRoll) ? raw.autoRoll : void 0;
|
|
2462
2596
|
const next = deepMerge(defaults, raw);
|
|
2463
2597
|
const merged = clone2(next);
|
|
2464
2598
|
merged.schemaVersion = 1;
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
if (!Number.isFinite(merged.autoRoll.warningThreshold)) {
|
|
2469
|
-
merged.autoRoll.warningThreshold = 85;
|
|
2470
|
-
}
|
|
2471
|
-
if (!Number.isFinite(merged.autoRoll.switchThreshold)) {
|
|
2472
|
-
merged.autoRoll.switchThreshold = 95;
|
|
2473
|
-
}
|
|
2474
|
-
if (merged.autoRoll.warningThreshold < 50 || merged.autoRoll.warningThreshold > 99) {
|
|
2475
|
-
merged.autoRoll.warningThreshold = 85;
|
|
2599
|
+
merged.autoRoll = normalizeAutoRollSettings(rawAutoRoll);
|
|
2600
|
+
if (!isRecord2(merged.officialCodex)) {
|
|
2601
|
+
merged.officialCodex = clone2(defaults.officialCodex);
|
|
2476
2602
|
}
|
|
2477
|
-
|
|
2478
|
-
merged.
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
merged.
|
|
2603
|
+
merged.officialCodex.lastProfileSwitchAt = asNumberOrNull(
|
|
2604
|
+
merged.officialCodex.lastProfileSwitchAt
|
|
2605
|
+
);
|
|
2606
|
+
merged.officialCodex.lastProfileSwitchProfileKey = asString(
|
|
2607
|
+
merged.officialCodex.lastProfileSwitchProfileKey
|
|
2608
|
+
);
|
|
2609
|
+
merged.officialCodex.lastVerifiedLaunchAt = asNumberOrNull(
|
|
2610
|
+
merged.officialCodex.lastVerifiedLaunchAt
|
|
2611
|
+
);
|
|
2612
|
+
merged.officialCodex.lastVerifiedLaunchProfileKey = asString(
|
|
2613
|
+
merged.officialCodex.lastVerifiedLaunchProfileKey
|
|
2614
|
+
);
|
|
2615
|
+
merged.officialCodex.lastObservedPid = asNumberOrNull(
|
|
2616
|
+
merged.officialCodex.lastObservedPid
|
|
2617
|
+
);
|
|
2618
|
+
merged.officialCodex.lastRestartStatus = asString(
|
|
2619
|
+
merged.officialCodex.lastRestartStatus
|
|
2620
|
+
);
|
|
2621
|
+
merged.officialCodex.lastRestartReason = asString(
|
|
2622
|
+
merged.officialCodex.lastRestartReason
|
|
2623
|
+
);
|
|
2624
|
+
merged.officialCodex.activity = Array.isArray(merged.officialCodex.activity) ? merged.officialCodex.activity.filter((entry) => isRecord2(entry)).map((entry) => ({
|
|
2625
|
+
id: asString(entry.id) ?? "",
|
|
2626
|
+
at: asString(entry.at) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2627
|
+
kind: entry.kind === "auto-roll-eval" || entry.kind === "profile-switch" || entry.kind === "auth-verified" || entry.kind === "official-codex-restart" || entry.kind === "low-remaining-alert" ? entry.kind : "auto-roll-eval",
|
|
2628
|
+
status: asString(entry.status) ?? "unknown",
|
|
2629
|
+
reason: asString(entry.reason),
|
|
2630
|
+
decisionId: asString(entry.decisionId),
|
|
2631
|
+
profileName: asString(entry.profileName),
|
|
2632
|
+
sourceProfileName: asString(entry.sourceProfileName),
|
|
2633
|
+
targetProfileName: asString(entry.targetProfileName),
|
|
2634
|
+
remainingPercent: asNumberOrNull(entry.remainingPercent),
|
|
2635
|
+
threshold: asNumberOrNull(entry.threshold),
|
|
2636
|
+
snapshotAgeMs: asNumberOrNull(entry.snapshotAgeMs),
|
|
2637
|
+
snapshotSource: asString(entry.snapshotSource),
|
|
2638
|
+
phase: asString(entry.phase),
|
|
2639
|
+
pid: asNumberOrNull(entry.pid),
|
|
2640
|
+
profileKeyHash: asString(entry.profileKeyHash),
|
|
2641
|
+
switchVerified: asBooleanOrNull(entry.switchVerified),
|
|
2642
|
+
restartRequested: asBooleanOrNull(entry.restartRequested),
|
|
2643
|
+
restartResult: asString(entry.restartResult),
|
|
2644
|
+
observedProfileName: asString(entry.observedProfileName),
|
|
2645
|
+
observedProfileKeyHash: asString(entry.observedProfileKeyHash),
|
|
2646
|
+
observedProfileMatchSource: asString(entry.observedProfileMatchSource)
|
|
2647
|
+
})).filter((entry) => entry.id && entry.at).slice(-50) : [];
|
|
2648
|
+
if (!isRecord2(merged.officialCodex.instancesByProfileName)) {
|
|
2649
|
+
merged.officialCodex.instancesByProfileName = {};
|
|
2650
|
+
} else {
|
|
2651
|
+
const nextInstances = {};
|
|
2652
|
+
for (const [key, value] of Object.entries(
|
|
2653
|
+
merged.officialCodex.instancesByProfileName
|
|
2654
|
+
)) {
|
|
2655
|
+
if (!isRecord2(value)) {
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
const profileName = asString(value.profileName) ?? asString(key);
|
|
2659
|
+
if (!profileName) {
|
|
2660
|
+
continue;
|
|
2661
|
+
}
|
|
2662
|
+
nextInstances[profileName] = {
|
|
2663
|
+
profileName,
|
|
2664
|
+
profileKey: asString(value.profileKey),
|
|
2665
|
+
profileHome: asString(value.profileHome),
|
|
2666
|
+
appPath: asString(value.appPath),
|
|
2667
|
+
bundleId: asString(value.bundleId),
|
|
2668
|
+
pid: asNumberOrNull(value.pid),
|
|
2669
|
+
appServerPid: asNumberOrNull(value.appServerPid),
|
|
2670
|
+
launchedAt: asNumberOrNull(value.launchedAt),
|
|
2671
|
+
lastVerifiedAt: asNumberOrNull(value.lastVerifiedAt),
|
|
2672
|
+
lastStatus: asString(value.lastStatus),
|
|
2673
|
+
lastError: asString(value.lastError)
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2676
|
+
merged.officialCodex.instancesByProfileName = nextInstances;
|
|
2677
|
+
}
|
|
2678
|
+
if (isRecord2(merged.officialCodex.pendingRestartDebt)) {
|
|
2679
|
+
const debt = merged.officialCodex.pendingRestartDebt;
|
|
2680
|
+
const targetProfileName = asString(debt.targetProfileName);
|
|
2681
|
+
merged.officialCodex.pendingRestartDebt = targetProfileName ? {
|
|
2682
|
+
targetProfileName,
|
|
2683
|
+
targetProfileKey: asString(debt.targetProfileKey),
|
|
2684
|
+
sourceProfileName: asString(debt.sourceProfileName),
|
|
2685
|
+
sourceProfileKey: asString(debt.sourceProfileKey),
|
|
2686
|
+
decisionId: asString(debt.decisionId),
|
|
2687
|
+
attempts: asNumberOrNull(debt.attempts) ?? 0,
|
|
2688
|
+
lastReason: asString(debt.lastReason)
|
|
2689
|
+
} : null;
|
|
2690
|
+
} else {
|
|
2691
|
+
merged.officialCodex.pendingRestartDebt = null;
|
|
2485
2692
|
}
|
|
2486
2693
|
merged.app.lastAppVersion = asString(merged.app.lastAppVersion);
|
|
2487
2694
|
merged.app.pendingUpdateVersion = asString(merged.app.pendingUpdateVersion);
|
|
@@ -2977,59 +3184,6 @@ async function writeAppSettings(settings, onUpdated) {
|
|
|
2977
3184
|
// ../../packages/runtime-profiles/src/license/service.ts
|
|
2978
3185
|
var import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
2979
3186
|
|
|
2980
|
-
// ../../packages/contracts/src/settings/auto-roll.ts
|
|
2981
|
-
var DEFAULT_AUTO_ROLL_ENABLED = false;
|
|
2982
|
-
var DEFAULT_AUTO_ROLL_WARNING_THRESHOLD = 85;
|
|
2983
|
-
var DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD = 95;
|
|
2984
|
-
var DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL = false;
|
|
2985
|
-
var AUTO_ROLL_WARNING_MIN = 50;
|
|
2986
|
-
var AUTO_ROLL_WARNING_MAX = 99;
|
|
2987
|
-
var AUTO_ROLL_SWITCH_MAX = 100;
|
|
2988
|
-
function clampNumber(value, min, max) {
|
|
2989
|
-
return Math.min(max, Math.max(min, value));
|
|
2990
|
-
}
|
|
2991
|
-
function resolveFiniteNumber(value, fallback) {
|
|
2992
|
-
return Number.isFinite(value) ? value : fallback;
|
|
2993
|
-
}
|
|
2994
|
-
function sanitizeAutoRollWarningThreshold(value) {
|
|
2995
|
-
const numeric = resolveFiniteNumber(value, DEFAULT_AUTO_ROLL_WARNING_THRESHOLD);
|
|
2996
|
-
return clampNumber(numeric, AUTO_ROLL_WARNING_MIN, AUTO_ROLL_WARNING_MAX);
|
|
2997
|
-
}
|
|
2998
|
-
function sanitizeAutoRollSwitchThreshold(value, warningThreshold) {
|
|
2999
|
-
const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
|
|
3000
|
-
const numeric = resolveFiniteNumber(value, DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD);
|
|
3001
|
-
return clampNumber(numeric, sanitizedWarning + 1, AUTO_ROLL_SWITCH_MAX);
|
|
3002
|
-
}
|
|
3003
|
-
function sanitizeAutoRollThresholds(warningThreshold, switchThreshold) {
|
|
3004
|
-
const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
|
|
3005
|
-
const sanitizedSwitch = sanitizeAutoRollSwitchThreshold(switchThreshold, sanitizedWarning);
|
|
3006
|
-
return {
|
|
3007
|
-
warningThreshold: sanitizedWarning,
|
|
3008
|
-
switchThreshold: sanitizedSwitch
|
|
3009
|
-
};
|
|
3010
|
-
}
|
|
3011
|
-
function normalizeAutoRollSettings(raw) {
|
|
3012
|
-
const enabled = typeof raw?.enabled === "boolean" ? raw.enabled : DEFAULT_AUTO_ROLL_ENABLED;
|
|
3013
|
-
const rawWarning = resolveFiniteNumber(
|
|
3014
|
-
typeof raw?.warningThreshold === "number" ? raw.warningThreshold : Number.NaN,
|
|
3015
|
-
DEFAULT_AUTO_ROLL_WARNING_THRESHOLD
|
|
3016
|
-
);
|
|
3017
|
-
const rawSwitch = resolveFiniteNumber(
|
|
3018
|
-
typeof raw?.switchThreshold === "number" ? raw.switchThreshold : Number.NaN,
|
|
3019
|
-
DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD
|
|
3020
|
-
);
|
|
3021
|
-
const {
|
|
3022
|
-
warningThreshold: normalizedWarning,
|
|
3023
|
-
switchThreshold: normalizedSwitch
|
|
3024
|
-
} = sanitizeAutoRollThresholds(rawWarning, rawSwitch);
|
|
3025
|
-
return {
|
|
3026
|
-
enabled,
|
|
3027
|
-
warningThreshold: normalizedWarning,
|
|
3028
|
-
switchThreshold: normalizedSwitch,
|
|
3029
|
-
restartOfficialCodexOnAutoRoll: raw?.restartOfficialCodexOnAutoRoll === true ? true : DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL
|
|
3030
|
-
};
|
|
3031
|
-
}
|
|
3032
|
-
|
|
3033
3187
|
// ../../packages/runtime-codex/src/codex/settings.ts
|
|
3034
3188
|
function asString2(value) {
|
|
3035
3189
|
if (typeof value !== "string") {
|
|
@@ -3071,17 +3225,6 @@ function parseStoredLicense(raw) {
|
|
|
3071
3225
|
);
|
|
3072
3226
|
return hasValue ? license : null;
|
|
3073
3227
|
}
|
|
3074
|
-
async function getLastProfileName() {
|
|
3075
|
-
const state = await getAppState();
|
|
3076
|
-
return asString2(state.app.lastProfileName);
|
|
3077
|
-
}
|
|
3078
|
-
async function persistLastProfileName(profileName) {
|
|
3079
|
-
await patchAppState({
|
|
3080
|
-
app: {
|
|
3081
|
-
lastProfileName: asString2(profileName)
|
|
3082
|
-
}
|
|
3083
|
-
});
|
|
3084
|
-
}
|
|
3085
3228
|
async function getStoredLicense() {
|
|
3086
3229
|
const state = await getAppState();
|
|
3087
3230
|
return parseStoredLicense(state.license);
|
|
@@ -3150,9 +3293,13 @@ async function writeCodexSettingsJsonRaw(payload) {
|
|
|
3150
3293
|
},
|
|
3151
3294
|
autoRoll: autoRoll ? {
|
|
3152
3295
|
enabled: autoRoll.enabled,
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll
|
|
3296
|
+
rearmRemainingThreshold: autoRoll.rearmRemainingThreshold,
|
|
3297
|
+
switchRemainingThreshold: autoRoll.switchRemainingThreshold,
|
|
3298
|
+
restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll,
|
|
3299
|
+
launchOfficialCodexWhenClosedOnAutoRoll: autoRoll.launchOfficialCodexWhenClosedOnAutoRoll,
|
|
3300
|
+
priorityOrder: autoRoll.priorityOrder,
|
|
3301
|
+
lowRemainingNotificationEnabled: autoRoll.lowRemainingNotificationEnabled,
|
|
3302
|
+
lowRemainingNotificationThreshold: autoRoll.lowRemainingNotificationThreshold
|
|
3156
3303
|
} : void 0,
|
|
3157
3304
|
license: license ? {
|
|
3158
3305
|
licenseKey: license.licenseKey ?? null,
|
|
@@ -3882,10 +4029,10 @@ function logError(...args) {
|
|
|
3882
4029
|
console.error(...args);
|
|
3883
4030
|
}
|
|
3884
4031
|
function logInfo(...args) {
|
|
3885
|
-
if (isTestEnv && !isMocked(console.
|
|
4032
|
+
if (isTestEnv && !isMocked(console.info)) {
|
|
3886
4033
|
return;
|
|
3887
4034
|
}
|
|
3888
|
-
console.
|
|
4035
|
+
console.info(...args);
|
|
3889
4036
|
}
|
|
3890
4037
|
|
|
3891
4038
|
// ../../packages/runtime-codex/src/codex/rpc.ts
|
|
@@ -4245,21 +4392,6 @@ var ProfileManager = class {
|
|
|
4245
4392
|
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
4246
4393
|
);
|
|
4247
4394
|
}
|
|
4248
|
-
async readPreferredProfileName() {
|
|
4249
|
-
try {
|
|
4250
|
-
return await getLastProfileName();
|
|
4251
|
-
} catch (error) {
|
|
4252
|
-
logWarn("Failed to read preferred profile name:", error);
|
|
4253
|
-
return null;
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
|
-
async persistPreferredProfileName(name) {
|
|
4257
|
-
try {
|
|
4258
|
-
await persistLastProfileName(name);
|
|
4259
|
-
} catch (error) {
|
|
4260
|
-
logWarn("Failed to persist preferred profile name:", error);
|
|
4261
|
-
}
|
|
4262
|
-
}
|
|
4263
4395
|
toStateRecord(record) {
|
|
4264
4396
|
return {
|
|
4265
4397
|
name: record.name,
|
|
@@ -4379,47 +4511,36 @@ var ProfileManager = class {
|
|
|
4379
4511
|
return null;
|
|
4380
4512
|
}
|
|
4381
4513
|
}
|
|
4382
|
-
|
|
4383
|
-
|
|
4514
|
+
emptyAuthSnapshot(fingerprint = null) {
|
|
4515
|
+
return {
|
|
4516
|
+
fingerprint,
|
|
4517
|
+
email: null,
|
|
4518
|
+
accountId: null,
|
|
4519
|
+
userId: null,
|
|
4520
|
+
chatgptUserId: null,
|
|
4521
|
+
chatgptAccountUserId: null,
|
|
4522
|
+
workspaceId: null
|
|
4523
|
+
};
|
|
4524
|
+
}
|
|
4525
|
+
async captureAuthSnapshotAtPath(authPath) {
|
|
4384
4526
|
let raw;
|
|
4385
4527
|
try {
|
|
4386
|
-
raw = await import_fs.promises.readFile(
|
|
4528
|
+
raw = await import_fs.promises.readFile(authPath, "utf8");
|
|
4387
4529
|
} catch (error) {
|
|
4388
4530
|
if (this.isNotFoundError(error)) {
|
|
4389
|
-
return
|
|
4390
|
-
fingerprint: null,
|
|
4391
|
-
email: null,
|
|
4392
|
-
accountId: null,
|
|
4393
|
-
userId: null,
|
|
4394
|
-
chatgptUserId: null,
|
|
4395
|
-
workspaceId: null
|
|
4396
|
-
};
|
|
4531
|
+
return this.emptyAuthSnapshot();
|
|
4397
4532
|
}
|
|
4398
4533
|
const message = error instanceof Error ? error.message : "unknown error";
|
|
4399
|
-
const signature = typeof message === "string" ? `${message}:${
|
|
4534
|
+
const signature = typeof message === "string" ? `${message}:${authPath}` : authPath;
|
|
4400
4535
|
if (this.lastActiveAuthErrorSignature !== signature) {
|
|
4401
|
-
logWarn("Failed to snapshot
|
|
4536
|
+
logWarn("Failed to snapshot auth file:", error);
|
|
4402
4537
|
this.lastActiveAuthErrorSignature = signature;
|
|
4403
4538
|
}
|
|
4404
|
-
return
|
|
4405
|
-
fingerprint: null,
|
|
4406
|
-
email: null,
|
|
4407
|
-
accountId: null,
|
|
4408
|
-
userId: null,
|
|
4409
|
-
chatgptUserId: null,
|
|
4410
|
-
workspaceId: null
|
|
4411
|
-
};
|
|
4539
|
+
return this.emptyAuthSnapshot();
|
|
4412
4540
|
}
|
|
4413
4541
|
const trimmed = raw.trim();
|
|
4414
4542
|
if (!trimmed) {
|
|
4415
|
-
return
|
|
4416
|
-
fingerprint: null,
|
|
4417
|
-
email: null,
|
|
4418
|
-
accountId: null,
|
|
4419
|
-
userId: null,
|
|
4420
|
-
chatgptUserId: null,
|
|
4421
|
-
workspaceId: null
|
|
4422
|
-
};
|
|
4543
|
+
return this.emptyAuthSnapshot();
|
|
4423
4544
|
}
|
|
4424
4545
|
const fingerprint = (0, import_node_crypto3.createHash)("sha256").update(trimmed).digest("hex");
|
|
4425
4546
|
try {
|
|
@@ -4433,18 +4554,26 @@ var ProfileManager = class {
|
|
|
4433
4554
|
accountId: this.getAccountIdFromData(normalized) ?? null,
|
|
4434
4555
|
userId: metadata?.userId ?? null,
|
|
4435
4556
|
chatgptUserId: metadata?.chatgptUserId ?? null,
|
|
4557
|
+
chatgptAccountUserId: metadata?.chatgptAccountUserId ?? null,
|
|
4436
4558
|
workspaceId: workspace.id ?? null
|
|
4437
4559
|
};
|
|
4438
4560
|
} catch {
|
|
4439
|
-
return
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4561
|
+
return this.emptyAuthSnapshot(fingerprint);
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
async captureAuthSnapshot(authPath) {
|
|
4565
|
+
const targetPath = authPath ?? this.activeAuth;
|
|
4566
|
+
if (targetPath === this.activeAuth) {
|
|
4567
|
+
return this.enqueueAuthSwap(async () => {
|
|
4568
|
+
await this.initialize();
|
|
4569
|
+
return this.captureAuthSnapshotAtPath(targetPath);
|
|
4570
|
+
});
|
|
4447
4571
|
}
|
|
4572
|
+
await this.initialize();
|
|
4573
|
+
return this.captureAuthSnapshotAtPath(targetPath);
|
|
4574
|
+
}
|
|
4575
|
+
async captureActiveAuthSnapshot() {
|
|
4576
|
+
return this.captureAuthSnapshot(this.activeAuth);
|
|
4448
4577
|
}
|
|
4449
4578
|
/**
|
|
4450
4579
|
* Decode a JWT payload into an object without validating the signature.
|
|
@@ -4842,6 +4971,130 @@ var ProfileManager = class {
|
|
|
4842
4971
|
metadata: record.metadata
|
|
4843
4972
|
}) ?? resolveFallbackAccountKey(record.name);
|
|
4844
4973
|
}
|
|
4974
|
+
describeActiveAuth(auth) {
|
|
4975
|
+
const normalized = this.normalizeProfileData(auth);
|
|
4976
|
+
if (typeof normalized.auth_method === "string") {
|
|
4977
|
+
normalized.auth_method = normalized.auth_method.trim().toLowerCase();
|
|
4978
|
+
}
|
|
4979
|
+
normalized.auth_method = normalized.auth_method ?? "codex-cli";
|
|
4980
|
+
const metadata = this.extractProfileMetadata(normalized);
|
|
4981
|
+
const workspace = this.resolveWorkspaceIdentity(normalized, metadata);
|
|
4982
|
+
const workspaceId = workspace.id || DEFAULT_WORKSPACE_ID;
|
|
4983
|
+
const workspaceName = workspace.name ?? null;
|
|
4984
|
+
normalized.workspace_id = normalized.workspace_id ?? workspaceId;
|
|
4985
|
+
if (workspaceName) {
|
|
4986
|
+
normalized.workspace_name = normalized.workspace_name ?? workspaceName;
|
|
4987
|
+
}
|
|
4988
|
+
const email = this.resolveProfileEmail(normalized, metadata) ?? null;
|
|
4989
|
+
if (email) {
|
|
4990
|
+
normalized.email = email;
|
|
4991
|
+
}
|
|
4992
|
+
const accountId = this.getAccountIdFromData(normalized) ?? null;
|
|
4993
|
+
const identityKey = this.resolveProfileIdentityFromData(null, normalized, metadata);
|
|
4994
|
+
const hasTokenPayload = Boolean(
|
|
4995
|
+
normalized.id_token || normalized.access_token || normalized.refresh_token || accountId || email
|
|
4996
|
+
);
|
|
4997
|
+
return {
|
|
4998
|
+
normalized,
|
|
4999
|
+
metadata,
|
|
5000
|
+
workspaceId,
|
|
5001
|
+
workspaceName,
|
|
5002
|
+
email,
|
|
5003
|
+
accountId,
|
|
5004
|
+
identityKey,
|
|
5005
|
+
hasTokenPayload
|
|
5006
|
+
};
|
|
5007
|
+
}
|
|
5008
|
+
async findProfileForActiveAuth(identityKey, workspaceId) {
|
|
5009
|
+
return this.getProfileRowByIdentityAndWorkspace(identityKey, workspaceId, {
|
|
5010
|
+
preferAuthMethod: "codex-cli"
|
|
5011
|
+
});
|
|
5012
|
+
}
|
|
5013
|
+
async syncMatchedProfileFromActiveAuth(record, description) {
|
|
5014
|
+
if (!this.hasTokenChanges(record.data, description.normalized)) {
|
|
5015
|
+
return;
|
|
5016
|
+
}
|
|
5017
|
+
const { profile: merged, metadata } = this.mergeProfileRecords(record.data, description.normalized);
|
|
5018
|
+
delete merged.tokenAlert;
|
|
5019
|
+
await this.persistProfileRecord(record.name, merged, metadata);
|
|
5020
|
+
}
|
|
5021
|
+
async assertActiveAuthMatchesRecord(record, message) {
|
|
5022
|
+
const activeAuth = await this.readActiveAuthFile();
|
|
5023
|
+
if (!activeAuth) {
|
|
5024
|
+
throw new Error("Active Codex auth is missing. Refresh profiles and try again.");
|
|
5025
|
+
}
|
|
5026
|
+
const activeDescription = this.describeActiveAuth(activeAuth);
|
|
5027
|
+
const targetIdentity = this.resolveProfileIdentityFromRecord(record);
|
|
5028
|
+
const targetWorkspaceId = this.normalizeWorkspaceId(record.workspaceId ?? record.data.workspace_id);
|
|
5029
|
+
if (activeDescription.identityKey !== targetIdentity || this.normalizeWorkspaceId(activeDescription.workspaceId) !== targetWorkspaceId) {
|
|
5030
|
+
throw new Error(message);
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
async writeActiveAuthForRecord(record) {
|
|
5034
|
+
const profileData = record.data;
|
|
5035
|
+
const targetIdentity = this.resolveProfileIdentityFromRecord(record);
|
|
5036
|
+
const targetWorkspaceId = this.normalizeWorkspaceId(record.workspaceId ?? profileData.workspace_id);
|
|
5037
|
+
const codexFormat = this.convertToCodexFormat(profileData);
|
|
5038
|
+
await this.writeAtomic(this.activeAuth, JSON.stringify(codexFormat, null, 2));
|
|
5039
|
+
const written = await this.readActiveAuthFile();
|
|
5040
|
+
if (!written) {
|
|
5041
|
+
throw new Error("Codex auth file was not written.");
|
|
5042
|
+
}
|
|
5043
|
+
const writtenDescription = this.describeActiveAuth(written);
|
|
5044
|
+
if (writtenDescription.identityKey !== targetIdentity) {
|
|
5045
|
+
throw new Error("Codex auth verification failed after profile switch.");
|
|
5046
|
+
}
|
|
5047
|
+
if (this.normalizeWorkspaceId(writtenDescription.workspaceId) !== targetWorkspaceId) {
|
|
5048
|
+
throw new Error("Codex auth workspace verification failed after profile switch.");
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
async syncActiveAuthFromProfileIfCurrent(name) {
|
|
5052
|
+
const profileName = this.normalizeProfileName(name);
|
|
5053
|
+
const record = await this.getProfileRowByName(profileName);
|
|
5054
|
+
if (!record) {
|
|
5055
|
+
return;
|
|
5056
|
+
}
|
|
5057
|
+
const activeAuth = await this.readActiveAuthFile();
|
|
5058
|
+
if (!activeAuth) {
|
|
5059
|
+
return;
|
|
5060
|
+
}
|
|
5061
|
+
const activeDescription = this.describeActiveAuth(activeAuth);
|
|
5062
|
+
const targetIdentity = this.resolveProfileIdentityFromRecord(record);
|
|
5063
|
+
const targetWorkspaceId = this.normalizeWorkspaceId(record.workspaceId ?? record.data.workspace_id);
|
|
5064
|
+
if (activeDescription.identityKey !== targetIdentity || this.normalizeWorkspaceId(activeDescription.workspaceId) !== targetWorkspaceId) {
|
|
5065
|
+
return;
|
|
5066
|
+
}
|
|
5067
|
+
await this.writeActiveAuthForRecord(record);
|
|
5068
|
+
}
|
|
5069
|
+
async removeActiveAuthFiles() {
|
|
5070
|
+
await import_fs.promises.rm(this.activeAuth, { force: true });
|
|
5071
|
+
await import_fs.promises.rm(this.activeAuthBackup, { force: true });
|
|
5072
|
+
await import_fs.promises.rm(`${this.activeAuth}.tmp`, { force: true });
|
|
5073
|
+
}
|
|
5074
|
+
resolveAutoImportedProfileName(email, existingRecords) {
|
|
5075
|
+
const source = email ? email.split("@")[0] : "codex-account";
|
|
5076
|
+
const base = source.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "codex-account";
|
|
5077
|
+
const used = new Set(existingRecords.map((record) => record.name));
|
|
5078
|
+
let candidate = this.normalizeProfileName(base);
|
|
5079
|
+
let suffix = 2;
|
|
5080
|
+
while (used.has(candidate)) {
|
|
5081
|
+
candidate = this.normalizeProfileName(`${base}-${suffix}`);
|
|
5082
|
+
suffix += 1;
|
|
5083
|
+
}
|
|
5084
|
+
return candidate;
|
|
5085
|
+
}
|
|
5086
|
+
buildActiveCodexAuthStatus(state, description, options) {
|
|
5087
|
+
return {
|
|
5088
|
+
state,
|
|
5089
|
+
profileName: options?.profileName ?? null,
|
|
5090
|
+
email: description?.email ?? null,
|
|
5091
|
+
accountId: description?.accountId ?? null,
|
|
5092
|
+
workspaceId: description?.workspaceId ?? null,
|
|
5093
|
+
workspaceName: description?.workspaceName ?? null,
|
|
5094
|
+
importedProfileName: options?.importedProfileName ?? null,
|
|
5095
|
+
importBlockedReason: options?.importBlockedReason ?? null
|
|
5096
|
+
};
|
|
5097
|
+
}
|
|
4845
5098
|
resolveWorkspaceIdentity(data, metadata) {
|
|
4846
5099
|
const directId = "workspace_id" in data && typeof data.workspace_id === "string" ? data.workspace_id.trim() : void 0;
|
|
4847
5100
|
const directName = "workspace_name" in data && typeof data.workspace_name === "string" ? data.workspace_name.trim() : void 0;
|
|
@@ -4862,22 +5115,6 @@ var ProfileManager = class {
|
|
|
4862
5115
|
const record = await this.readProfileRecord(normalized);
|
|
4863
5116
|
return record ?? void 0;
|
|
4864
5117
|
}
|
|
4865
|
-
async getProfileRowByIdentityKey(identityKey, options) {
|
|
4866
|
-
const records = await this.listProfileRecords();
|
|
4867
|
-
const matches = records.filter((record) => this.resolveProfileIdentityFromRecord(record) === identityKey);
|
|
4868
|
-
if (matches.length === 0) {
|
|
4869
|
-
return void 0;
|
|
4870
|
-
}
|
|
4871
|
-
const mustMatch = typeof options?.authMethod === "string" ? options.authMethod.trim().toLowerCase() : null;
|
|
4872
|
-
if (mustMatch) {
|
|
4873
|
-
return matches.find((record) => this.resolveAuthMethod(record.data) === mustMatch);
|
|
4874
|
-
}
|
|
4875
|
-
const prefer = typeof options?.preferAuthMethod === "string" ? options.preferAuthMethod.trim().toLowerCase() : null;
|
|
4876
|
-
if (prefer) {
|
|
4877
|
-
return matches.find((record) => this.resolveAuthMethod(record.data) === prefer) ?? matches[0];
|
|
4878
|
-
}
|
|
4879
|
-
return matches[0];
|
|
4880
|
-
}
|
|
4881
5118
|
async getProfileRowByIdentityAndWorkspace(identityKey, workspaceId, options) {
|
|
4882
5119
|
const workspaceKey = workspaceId && workspaceId.trim().length > 0 ? workspaceId.trim() : DEFAULT_WORKSPACE_ID;
|
|
4883
5120
|
const records = await this.listProfileRecords();
|
|
@@ -4901,6 +5138,9 @@ var ProfileManager = class {
|
|
|
4901
5138
|
}
|
|
4902
5139
|
return matches[0];
|
|
4903
5140
|
}
|
|
5141
|
+
normalizeWorkspaceId(value) {
|
|
5142
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : DEFAULT_WORKSPACE_ID;
|
|
5143
|
+
}
|
|
4904
5144
|
async persistProfileRecord(name, data, metadata, options) {
|
|
4905
5145
|
const resolvedName = this.normalizeProfileName(name);
|
|
4906
5146
|
const existingRecord = await this.readProfileRecord(resolvedName);
|
|
@@ -4960,6 +5200,22 @@ var ProfileManager = class {
|
|
|
4960
5200
|
});
|
|
4961
5201
|
return profile;
|
|
4962
5202
|
}
|
|
5203
|
+
async deleteProfileRecordAndHome(record) {
|
|
5204
|
+
const profileName = this.normalizeProfileName(record.name);
|
|
5205
|
+
if (!record.accountId) {
|
|
5206
|
+
const dashboardState = await this.readProfileDashboardState();
|
|
5207
|
+
delete dashboardState.customGroupsByAccountKey[resolveFallbackAccountKey(profileName)];
|
|
5208
|
+
await this.writeProfileDashboardState(dashboardState);
|
|
5209
|
+
}
|
|
5210
|
+
try {
|
|
5211
|
+
await import_fs.promises.rm(this.getProfileHomePath(profileName), { recursive: true, force: true });
|
|
5212
|
+
} catch (error) {
|
|
5213
|
+
if (!this.isNotFoundError(error)) {
|
|
5214
|
+
logWarn(`Failed to remove profile home for '${profileName}':`, error);
|
|
5215
|
+
}
|
|
5216
|
+
}
|
|
5217
|
+
await this.deleteProfileRecord(profileName);
|
|
5218
|
+
}
|
|
4963
5219
|
buildProfileFromData(name, data, fallback) {
|
|
4964
5220
|
const metadata = fallback?.metadata ?? this.extractProfileMetadata(data);
|
|
4965
5221
|
const workspace = this.resolveWorkspaceIdentity(data, metadata);
|
|
@@ -5019,6 +5275,18 @@ var ProfileManager = class {
|
|
|
5019
5275
|
if (normalizedLastRefresh) {
|
|
5020
5276
|
codexAuth.last_refresh = normalizedLastRefresh;
|
|
5021
5277
|
}
|
|
5278
|
+
const email = typeof data.email === "string" ? data.email.trim() : "";
|
|
5279
|
+
if (email) {
|
|
5280
|
+
codexAuth.email = email;
|
|
5281
|
+
}
|
|
5282
|
+
const workspaceId = typeof data.workspace_id === "string" ? data.workspace_id.trim() : "";
|
|
5283
|
+
if (workspaceId) {
|
|
5284
|
+
codexAuth.workspace_id = workspaceId;
|
|
5285
|
+
}
|
|
5286
|
+
const workspaceName = typeof data.workspace_name === "string" ? data.workspace_name.trim() : "";
|
|
5287
|
+
if (workspaceName) {
|
|
5288
|
+
codexAuth.workspace_name = workspaceName;
|
|
5289
|
+
}
|
|
5022
5290
|
return codexAuth;
|
|
5023
5291
|
}
|
|
5024
5292
|
/**
|
|
@@ -5041,8 +5309,17 @@ var ProfileManager = class {
|
|
|
5041
5309
|
if (normalizedLastRefresh) {
|
|
5042
5310
|
profile.last_refresh = normalizedLastRefresh;
|
|
5043
5311
|
}
|
|
5044
|
-
|
|
5045
|
-
|
|
5312
|
+
const email = typeof data.email === "string" ? data.email.trim() : "";
|
|
5313
|
+
if (email) {
|
|
5314
|
+
profile.email = email;
|
|
5315
|
+
}
|
|
5316
|
+
const workspaceId = typeof data.workspace_id === "string" ? data.workspace_id.trim() : "";
|
|
5317
|
+
if (workspaceId) {
|
|
5318
|
+
profile.workspace_id = workspaceId;
|
|
5319
|
+
}
|
|
5320
|
+
const workspaceName = typeof data.workspace_name === "string" ? data.workspace_name.trim() : "";
|
|
5321
|
+
if (workspaceName) {
|
|
5322
|
+
profile.workspace_name = workspaceName;
|
|
5046
5323
|
}
|
|
5047
5324
|
return profile;
|
|
5048
5325
|
}
|
|
@@ -5169,11 +5446,17 @@ var ProfileManager = class {
|
|
|
5169
5446
|
const existingIdentity = this.resolveProfileIdentityFromData(profileName, existing, existingMetadata);
|
|
5170
5447
|
const nextIdentity = this.resolveProfileIdentityFromData(profileName, normalized, metadata);
|
|
5171
5448
|
if (existingIdentity && nextIdentity && existingIdentity !== nextIdentity) {
|
|
5172
|
-
|
|
5449
|
+
logInfo(
|
|
5173
5450
|
`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`
|
|
5174
5451
|
);
|
|
5175
5452
|
return;
|
|
5176
5453
|
}
|
|
5454
|
+
if (this.normalizeWorkspaceId(existing.workspace_id) !== this.normalizeWorkspaceId(normalized.workspace_id)) {
|
|
5455
|
+
logInfo(
|
|
5456
|
+
`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different workspace.`
|
|
5457
|
+
);
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
5177
5460
|
if (!this.hasTokenChanges(existing, normalized)) {
|
|
5178
5461
|
return;
|
|
5179
5462
|
}
|
|
@@ -5371,94 +5654,80 @@ var ProfileManager = class {
|
|
|
5371
5654
|
}
|
|
5372
5655
|
if (finalAuthContent) {
|
|
5373
5656
|
await this.syncProfileTokensFromAuthContent(profileName, record.data, finalAuthContent);
|
|
5657
|
+
await this.syncActiveAuthFromProfileIfCurrent(profileName);
|
|
5374
5658
|
return;
|
|
5375
5659
|
}
|
|
5376
5660
|
await this.syncProfileTokensFromActiveAuth(profileName, record.data, authPath);
|
|
5661
|
+
await this.syncActiveAuthFromProfileIfCurrent(profileName);
|
|
5377
5662
|
})
|
|
5378
5663
|
);
|
|
5379
5664
|
}
|
|
5380
5665
|
/**
|
|
5381
5666
|
* Create a new profile by running codex login
|
|
5382
5667
|
*/
|
|
5383
|
-
async
|
|
5384
|
-
|
|
5385
|
-
const profileName = this.normalizeProfileName(name);
|
|
5386
|
-
if (await this.getProfileRowByName(profileName)) {
|
|
5387
|
-
throw new Error(`Profile '${profileName}' already exists!`);
|
|
5388
|
-
}
|
|
5389
|
-
let authData;
|
|
5668
|
+
async readAuthFileForImport(authPath, actionLabel) {
|
|
5669
|
+
let authRaw;
|
|
5390
5670
|
try {
|
|
5391
|
-
|
|
5392
|
-
authData = JSON.parse(authRaw);
|
|
5671
|
+
authRaw = await import_fs.promises.readFile(authPath, "utf8");
|
|
5393
5672
|
} catch (error) {
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
if (typeof normalizedProfile.auth_method === "string") {
|
|
5400
|
-
normalizedProfile.auth_method = normalizedProfile.auth_method.trim().toLowerCase();
|
|
5401
|
-
}
|
|
5402
|
-
normalizedProfile.auth_method = normalizedProfile.auth_method ?? "codex-cli";
|
|
5403
|
-
const metadata = this.extractProfileMetadata(normalizedProfile);
|
|
5404
|
-
const workspace = this.resolveWorkspaceIdentity(normalizedProfile, metadata);
|
|
5405
|
-
normalizedProfile.workspace_id = normalizedProfile.workspace_id ?? workspace.id ?? DEFAULT_WORKSPACE_ID;
|
|
5406
|
-
if (workspace.name) {
|
|
5407
|
-
normalizedProfile.workspace_name = normalizedProfile.workspace_name ?? workspace.name;
|
|
5408
|
-
}
|
|
5409
|
-
const resolvedEmail = this.resolveProfileEmail(normalizedProfile, metadata);
|
|
5410
|
-
if (resolvedEmail) {
|
|
5411
|
-
normalizedProfile.email = resolvedEmail;
|
|
5673
|
+
if (this.isNotFoundError(error)) {
|
|
5674
|
+
throw new Error(`Codex CLI did not produce an auth file. Complete the login before ${actionLabel}.`);
|
|
5675
|
+
}
|
|
5676
|
+
logError(`Failed to read Codex auth file during ${actionLabel}:`, error);
|
|
5677
|
+
throw new Error("Failed to read Codex auth file. Complete the login and try again.");
|
|
5412
5678
|
}
|
|
5413
5679
|
try {
|
|
5414
|
-
|
|
5415
|
-
const stored = await this.getProfileRowByName(profileName);
|
|
5416
|
-
return stored ? this.buildProfileFromRow(stored) : null;
|
|
5680
|
+
return JSON.parse(authRaw);
|
|
5417
5681
|
} catch (error) {
|
|
5418
|
-
logError(
|
|
5419
|
-
throw new Error("Failed to
|
|
5682
|
+
logError(`Failed to parse Codex auth file during ${actionLabel}:`, error);
|
|
5683
|
+
throw new Error("Failed to parse Codex auth file. Complete the login and try again.");
|
|
5420
5684
|
}
|
|
5421
5685
|
}
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
await this.
|
|
5686
|
+
async persistNewProfileFromAuth(profileName, authData) {
|
|
5687
|
+
const description = this.describeActiveAuth(authData);
|
|
5688
|
+
const normalizedProfile = description.normalized;
|
|
5689
|
+
normalizedProfile.created_at = normalizedProfile.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5690
|
+
await this.persistProfileRecord(profileName, normalizedProfile, description.metadata);
|
|
5691
|
+
const stored = await this.getProfileRowByName(profileName);
|
|
5692
|
+
return stored ? this.buildProfileFromRow(stored) : null;
|
|
5693
|
+
}
|
|
5694
|
+
async createProfile(name) {
|
|
5427
5695
|
const profileName = this.normalizeProfileName(name);
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5696
|
+
return this.enqueueProfileOperation(
|
|
5697
|
+
profileName,
|
|
5698
|
+
() => this.enqueueAuthSwap(async () => {
|
|
5699
|
+
await this.initialize();
|
|
5700
|
+
if (await this.getProfileRowByName(profileName)) {
|
|
5701
|
+
throw new Error(`Profile '${profileName}' already exists!`);
|
|
5702
|
+
}
|
|
5703
|
+
try {
|
|
5704
|
+
const authData = await this.readAuthFileForImport(this.activeAuth, "profile creation");
|
|
5705
|
+
return await this.persistNewProfileFromAuth(profileName, authData);
|
|
5706
|
+
} catch (error) {
|
|
5707
|
+
logError("Error creating profile:", error);
|
|
5708
|
+
throw new Error("Failed to create profile. Make sure Codex CLI is installed and you are logged in.");
|
|
5709
|
+
}
|
|
5710
|
+
})
|
|
5711
|
+
);
|
|
5442
5712
|
}
|
|
5443
|
-
async
|
|
5444
|
-
await this.initialize();
|
|
5713
|
+
async createProfileFromAuthFile(name, authPath) {
|
|
5445
5714
|
const profileName = this.normalizeProfileName(name);
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
const existing = record.data;
|
|
5451
|
-
let activeAuth;
|
|
5452
|
-
try {
|
|
5453
|
-
const authContent = await import_fs.promises.readFile(this.activeAuth, "utf8");
|
|
5454
|
-
activeAuth = JSON.parse(authContent);
|
|
5455
|
-
} catch (error) {
|
|
5456
|
-
if (this.isNotFoundError(error)) {
|
|
5457
|
-
throw new Error("Codex CLI did not produce an auth file. Complete the login before refreshing this profile.");
|
|
5715
|
+
return this.enqueueProfileOperation(profileName, async () => {
|
|
5716
|
+
await this.initialize();
|
|
5717
|
+
if (await this.getProfileRowByName(profileName)) {
|
|
5718
|
+
throw new Error(`Profile '${profileName}' already exists!`);
|
|
5458
5719
|
}
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5720
|
+
try {
|
|
5721
|
+
const authData = await this.readAuthFileForImport(authPath, "profile creation");
|
|
5722
|
+
return await this.persistNewProfileFromAuth(profileName, authData);
|
|
5723
|
+
} catch (error) {
|
|
5724
|
+
logError("Error creating profile:", error);
|
|
5725
|
+
throw new Error("Failed to create profile. Make sure Codex CLI is installed and you are logged in.");
|
|
5726
|
+
}
|
|
5727
|
+
});
|
|
5728
|
+
}
|
|
5729
|
+
async refreshProfileAuthWithData(profileName, record, activeAuth) {
|
|
5730
|
+
const existing = record.data;
|
|
5462
5731
|
const normalized = this.normalizeProfileData(activeAuth);
|
|
5463
5732
|
const existingMetadata = record.metadata ?? this.extractProfileMetadata(existing);
|
|
5464
5733
|
const currentWorkspace = this.resolveWorkspaceIdentity(existing, existingMetadata);
|
|
@@ -5507,6 +5776,60 @@ var ProfileManager = class {
|
|
|
5507
5776
|
}
|
|
5508
5777
|
return profile;
|
|
5509
5778
|
}
|
|
5779
|
+
/**
|
|
5780
|
+
* Switch to a different profile
|
|
5781
|
+
*/
|
|
5782
|
+
async switchToProfile(name) {
|
|
5783
|
+
const profileName = this.normalizeProfileName(name);
|
|
5784
|
+
return this.enqueueProfileOperation(
|
|
5785
|
+
profileName,
|
|
5786
|
+
() => this.enqueueAuthSwap(async () => {
|
|
5787
|
+
await this.initialize();
|
|
5788
|
+
const record = await this.getProfileRowByName(profileName);
|
|
5789
|
+
if (!record) {
|
|
5790
|
+
throw new Error(`Profile '${profileName}' not found!`);
|
|
5791
|
+
}
|
|
5792
|
+
try {
|
|
5793
|
+
await this.writeActiveAuthForRecord(record);
|
|
5794
|
+
return true;
|
|
5795
|
+
} catch (error) {
|
|
5796
|
+
logError("Error switching profile:", error);
|
|
5797
|
+
throw new Error("Failed to switch profile");
|
|
5798
|
+
}
|
|
5799
|
+
})
|
|
5800
|
+
);
|
|
5801
|
+
}
|
|
5802
|
+
async refreshProfileAuth(name) {
|
|
5803
|
+
const profileName = this.normalizeProfileName(name);
|
|
5804
|
+
return this.enqueueProfileOperation(
|
|
5805
|
+
profileName,
|
|
5806
|
+
() => this.enqueueAuthSwap(async () => {
|
|
5807
|
+
await this.initialize();
|
|
5808
|
+
const record = await this.getProfileRowByName(profileName);
|
|
5809
|
+
if (!record) {
|
|
5810
|
+
throw new Error(`Profile '${profileName}' not found!`);
|
|
5811
|
+
}
|
|
5812
|
+
const activeAuth = await this.readAuthFileForImport(this.activeAuth, "profile refresh");
|
|
5813
|
+
return this.refreshProfileAuthWithData(profileName, record, activeAuth);
|
|
5814
|
+
})
|
|
5815
|
+
);
|
|
5816
|
+
}
|
|
5817
|
+
async refreshProfileAuthFromFile(name, authPath) {
|
|
5818
|
+
const profileName = this.normalizeProfileName(name);
|
|
5819
|
+
return this.enqueueProfileOperation(profileName, async () => {
|
|
5820
|
+
await this.initialize();
|
|
5821
|
+
const record = await this.getProfileRowByName(profileName);
|
|
5822
|
+
if (!record) {
|
|
5823
|
+
throw new Error(`Profile '${profileName}' not found!`);
|
|
5824
|
+
}
|
|
5825
|
+
const activeAuth = await this.readAuthFileForImport(authPath, "profile refresh");
|
|
5826
|
+
const profile = await this.refreshProfileAuthWithData(profileName, record, activeAuth);
|
|
5827
|
+
await this.enqueueAuthSwap(async () => {
|
|
5828
|
+
await this.syncActiveAuthFromProfileIfCurrent(profileName);
|
|
5829
|
+
});
|
|
5830
|
+
return profile;
|
|
5831
|
+
});
|
|
5832
|
+
}
|
|
5510
5833
|
/**
|
|
5511
5834
|
* Execute an action with a profile's auth injected.
|
|
5512
5835
|
* Creates an isolated CODEX_HOME with the profile's auth/config, invokes the action
|
|
@@ -5515,22 +5838,35 @@ var ProfileManager = class {
|
|
|
5515
5838
|
async runWithProfileAuth(name, action) {
|
|
5516
5839
|
return this.enqueueProfileOperation(
|
|
5517
5840
|
name,
|
|
5518
|
-
() => this.enqueueAuthSwap(
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5841
|
+
() => this.enqueueAuthSwap(async () => {
|
|
5842
|
+
try {
|
|
5843
|
+
return await this.runWithPreparedProfileHome(name, action, {
|
|
5844
|
+
syncFromActiveAuthBeforeAction: true
|
|
5845
|
+
});
|
|
5846
|
+
} finally {
|
|
5847
|
+
await this.syncActiveAuthFromProfileIfCurrent(name);
|
|
5848
|
+
}
|
|
5849
|
+
})
|
|
5523
5850
|
);
|
|
5524
5851
|
}
|
|
5525
5852
|
async readLiveRateLimits(name, options = {}) {
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
(
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5853
|
+
const profileName = this.normalizeProfileName(name);
|
|
5854
|
+
try {
|
|
5855
|
+
return await this.enqueueProfileOperation(
|
|
5856
|
+
profileName,
|
|
5857
|
+
() => this.runWithPreparedProfileHome(
|
|
5858
|
+
profileName,
|
|
5859
|
+
(env) => fetchRateLimitsViaRpc(env, { codexPath: options.codexPath }),
|
|
5860
|
+
{ syncFromActiveAuthBeforeAction: false }
|
|
5861
|
+
)
|
|
5862
|
+
);
|
|
5863
|
+
} finally {
|
|
5864
|
+
await this.enqueueAuthSwap(async () => {
|
|
5865
|
+
await this.syncActiveAuthFromProfileIfCurrent(profileName);
|
|
5866
|
+
}).catch((error) => {
|
|
5867
|
+
logWarn(`Failed to sync active auth after rate-limit probe for '${profileName}':`, error);
|
|
5868
|
+
});
|
|
5869
|
+
}
|
|
5534
5870
|
}
|
|
5535
5871
|
/**
|
|
5536
5872
|
* Rename a profile
|
|
@@ -5546,10 +5882,6 @@ var ProfileManager = class {
|
|
|
5546
5882
|
if (await this.getProfileRowByName(targetName)) {
|
|
5547
5883
|
throw new Error(`Profile '${targetName}' already exists!`);
|
|
5548
5884
|
}
|
|
5549
|
-
const preferred = await this.readPreferredProfileName();
|
|
5550
|
-
if (preferred && preferred === sourceName) {
|
|
5551
|
-
await this.persistPreferredProfileName(targetName);
|
|
5552
|
-
}
|
|
5553
5885
|
const oldHome = this.getProfileHomePath(sourceName);
|
|
5554
5886
|
const newHome = this.getProfileHomePath(targetName);
|
|
5555
5887
|
try {
|
|
@@ -5618,25 +5950,51 @@ var ProfileManager = class {
|
|
|
5618
5950
|
if (!record) {
|
|
5619
5951
|
throw new Error(`Profile '${profileName}' not found!`);
|
|
5620
5952
|
}
|
|
5621
|
-
|
|
5622
|
-
const dashboardState = await this.readProfileDashboardState();
|
|
5623
|
-
delete dashboardState.customGroupsByAccountKey[resolveFallbackAccountKey(profileName)];
|
|
5624
|
-
await this.writeProfileDashboardState(dashboardState);
|
|
5625
|
-
}
|
|
5626
|
-
const preferred = await this.readPreferredProfileName();
|
|
5627
|
-
if (preferred && preferred === profileName) {
|
|
5628
|
-
await this.persistPreferredProfileName(null);
|
|
5629
|
-
}
|
|
5630
|
-
try {
|
|
5631
|
-
await import_fs.promises.rm(this.getProfileHomePath(profileName), { recursive: true, force: true });
|
|
5632
|
-
} catch (error) {
|
|
5633
|
-
if (!this.isNotFoundError(error)) {
|
|
5634
|
-
logWarn(`Failed to remove profile home for '${profileName}':`, error);
|
|
5635
|
-
}
|
|
5636
|
-
}
|
|
5637
|
-
await this.deleteProfileRecord(profileName);
|
|
5953
|
+
await this.deleteProfileRecordAndHome(record);
|
|
5638
5954
|
return true;
|
|
5639
5955
|
}
|
|
5956
|
+
async deleteActiveProfileAndSwitch(name, replacementName) {
|
|
5957
|
+
const profileName = this.normalizeProfileName(name);
|
|
5958
|
+
const nextProfileName = this.normalizeProfileName(replacementName);
|
|
5959
|
+
if (profileName === nextProfileName) {
|
|
5960
|
+
throw new Error("Replacement profile must be different from the deleted profile.");
|
|
5961
|
+
}
|
|
5962
|
+
return this.enqueueAuthSwap(async () => {
|
|
5963
|
+
await this.initialize();
|
|
5964
|
+
const record = await this.getProfileRowByName(profileName);
|
|
5965
|
+
if (!record) {
|
|
5966
|
+
throw new Error(`Profile '${profileName}' not found!`);
|
|
5967
|
+
}
|
|
5968
|
+
const replacement = await this.getProfileRowByName(nextProfileName);
|
|
5969
|
+
if (!replacement) {
|
|
5970
|
+
throw new Error(`Profile '${nextProfileName}' not found!`);
|
|
5971
|
+
}
|
|
5972
|
+
await this.assertActiveAuthMatchesRecord(
|
|
5973
|
+
record,
|
|
5974
|
+
"Active Codex login changed before deletion. Refresh profiles and try again."
|
|
5975
|
+
);
|
|
5976
|
+
await this.writeActiveAuthForRecord(replacement);
|
|
5977
|
+
await this.deleteProfileRecordAndHome(record);
|
|
5978
|
+
return true;
|
|
5979
|
+
});
|
|
5980
|
+
}
|
|
5981
|
+
async deleteActiveProfileAndAuth(name) {
|
|
5982
|
+
const profileName = this.normalizeProfileName(name);
|
|
5983
|
+
return this.enqueueAuthSwap(async () => {
|
|
5984
|
+
await this.initialize();
|
|
5985
|
+
const record = await this.getProfileRowByName(profileName);
|
|
5986
|
+
if (!record) {
|
|
5987
|
+
throw new Error(`Profile '${profileName}' not found!`);
|
|
5988
|
+
}
|
|
5989
|
+
await this.assertActiveAuthMatchesRecord(
|
|
5990
|
+
record,
|
|
5991
|
+
"Active Codex login changed before deletion. Refresh profiles and try again."
|
|
5992
|
+
);
|
|
5993
|
+
await this.removeActiveAuthFiles();
|
|
5994
|
+
await this.deleteProfileRecordAndHome(record);
|
|
5995
|
+
return true;
|
|
5996
|
+
});
|
|
5997
|
+
}
|
|
5640
5998
|
/**
|
|
5641
5999
|
* Delete every profile that does not appear in the allow-list.
|
|
5642
6000
|
* Used to enforce plan limits when local storage was tampered with.
|
|
@@ -5673,64 +6031,61 @@ var ProfileManager = class {
|
|
|
5673
6031
|
}
|
|
5674
6032
|
}
|
|
5675
6033
|
}
|
|
5676
|
-
const preferred = await this.readPreferredProfileName();
|
|
5677
|
-
if (preferred && removed.includes(preferred)) {
|
|
5678
|
-
await this.persistPreferredProfileName(null);
|
|
5679
|
-
}
|
|
5680
6034
|
return removed;
|
|
5681
6035
|
}
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
if (preferredName) {
|
|
5689
|
-
try {
|
|
5690
|
-
const normalizedPreferred = this.normalizeProfileName(preferredName);
|
|
5691
|
-
const preferredRecord = await this.getProfileRowByName(normalizedPreferred);
|
|
5692
|
-
if (preferredRecord) {
|
|
5693
|
-
return { name: normalizedPreferred, trusted: true };
|
|
5694
|
-
}
|
|
5695
|
-
} catch {
|
|
6036
|
+
async getActiveCodexAuthStatus(options = {}) {
|
|
6037
|
+
return this.enqueueAuthSwap(async () => {
|
|
6038
|
+
await this.initialize();
|
|
6039
|
+
const activeAuth = await this.readActiveAuthFile();
|
|
6040
|
+
if (!activeAuth) {
|
|
6041
|
+
return this.buildActiveCodexAuthStatus("missing");
|
|
5696
6042
|
}
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
const
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
6043
|
+
const description = this.describeActiveAuth(activeAuth);
|
|
6044
|
+
if (!description.hasTokenPayload || !description.identityKey) {
|
|
6045
|
+
return this.buildActiveCodexAuthStatus("unknown", description);
|
|
6046
|
+
}
|
|
6047
|
+
const matching = await this.findProfileForActiveAuth(
|
|
6048
|
+
description.identityKey,
|
|
6049
|
+
description.workspaceId
|
|
6050
|
+
);
|
|
6051
|
+
if (matching) {
|
|
6052
|
+
const normalized = this.normalizeProfileName(matching.name);
|
|
6053
|
+
await this.syncMatchedProfileFromActiveAuth(matching, description);
|
|
6054
|
+
return this.buildActiveCodexAuthStatus("matched", description, {
|
|
6055
|
+
profileName: normalized
|
|
5707
6056
|
});
|
|
5708
|
-
if (scoped) {
|
|
5709
|
-
const normalized = this.normalizeProfileName(scoped.name);
|
|
5710
|
-
await this.persistPreferredProfileName(normalized);
|
|
5711
|
-
return { name: normalized, trusted: true };
|
|
5712
|
-
}
|
|
5713
|
-
const matching = await this.getProfileRowByIdentityKey(activeIdentityKey, { preferAuthMethod: "codex-cli" });
|
|
5714
|
-
if (matching) {
|
|
5715
|
-
const normalized = this.normalizeProfileName(matching.name);
|
|
5716
|
-
await this.persistPreferredProfileName(normalized);
|
|
5717
|
-
return { name: normalized, trusted: true };
|
|
5718
|
-
}
|
|
5719
6057
|
}
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
return { name: normalized, trusted: true };
|
|
5728
|
-
}
|
|
5729
|
-
} catch {
|
|
5730
|
-
await this.persistPreferredProfileName(null);
|
|
5731
|
-
return { name: null, trusted: false };
|
|
6058
|
+
if (!options.autoImport) {
|
|
6059
|
+
return this.buildActiveCodexAuthStatus("unmanaged", description);
|
|
6060
|
+
}
|
|
6061
|
+
if (options.canAutoImport === false) {
|
|
6062
|
+
return this.buildActiveCodexAuthStatus("unmanaged", description, {
|
|
6063
|
+
importBlockedReason: options.importBlockedReason ?? "Profile limit reached. Upgrade to CodexUse Pro or remove a profile before importing this Codex login."
|
|
6064
|
+
});
|
|
5732
6065
|
}
|
|
5733
|
-
await this.
|
|
6066
|
+
const records = await this.listProfileRecords();
|
|
6067
|
+
const profileName = this.resolveAutoImportedProfileName(description.email, records);
|
|
6068
|
+
const profileData = {
|
|
6069
|
+
...description.normalized,
|
|
6070
|
+
auth_method: "codex-cli",
|
|
6071
|
+
created_at: description.normalized.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6072
|
+
};
|
|
6073
|
+
await this.persistProfileRecord(profileName, profileData, description.metadata, {
|
|
6074
|
+
displayName: description.email ?? profileName
|
|
6075
|
+
});
|
|
6076
|
+
return this.buildActiveCodexAuthStatus("matched", description, {
|
|
6077
|
+
profileName,
|
|
6078
|
+
importedProfileName: profileName
|
|
6079
|
+
});
|
|
6080
|
+
});
|
|
6081
|
+
}
|
|
6082
|
+
/**
|
|
6083
|
+
* Check if the real Codex auth file matches a saved profile.
|
|
6084
|
+
*/
|
|
6085
|
+
async getCurrentProfile() {
|
|
6086
|
+
const status = await this.getActiveCodexAuthStatus({ autoImport: false });
|
|
6087
|
+
if (status.state === "matched" && status.profileName) {
|
|
6088
|
+
return { name: status.profileName, trusted: true };
|
|
5734
6089
|
}
|
|
5735
6090
|
return { name: null, trusted: false };
|
|
5736
6091
|
}
|
|
@@ -5831,17 +6186,6 @@ var ProfileManager = class {
|
|
|
5831
6186
|
}
|
|
5832
6187
|
}
|
|
5833
6188
|
}
|
|
5834
|
-
const preferred = await this.readPreferredProfileName();
|
|
5835
|
-
if (preferred) {
|
|
5836
|
-
try {
|
|
5837
|
-
const normalizedPreferred = this.normalizeProfileName(preferred);
|
|
5838
|
-
if (!normalized.has(normalizedPreferred)) {
|
|
5839
|
-
await this.persistPreferredProfileName(null);
|
|
5840
|
-
}
|
|
5841
|
-
} catch {
|
|
5842
|
-
await this.persistPreferredProfileName(null);
|
|
5843
|
-
}
|
|
5844
|
-
}
|
|
5845
6189
|
return {
|
|
5846
6190
|
imported: normalized.size,
|
|
5847
6191
|
removed
|
|
@@ -5874,8 +6218,8 @@ Usage:
|
|
|
5874
6218
|
codexuse profile current
|
|
5875
6219
|
codexuse profile add <name> [--skip-login] [--device-auth] [--login=browser|device]
|
|
5876
6220
|
codexuse profile refresh <name> [--skip-login] [--device-auth] [--login=browser|device]
|
|
5877
|
-
codexuse profile switch <name>
|
|
5878
|
-
codexuse profile autoroll [--threshold=50-100] [--dry-run] [--watch] [--interval=seconds]
|
|
6221
|
+
codexuse profile switch <name> [--restart-codex]
|
|
6222
|
+
codexuse profile autoroll [--switch-left=0-50] [--threshold=50-100] [--dry-run] [--watch] [--interval=seconds] [--restart-codex|--no-restart-codex]
|
|
5879
6223
|
codexuse profile delete <name>
|
|
5880
6224
|
codexuse profile rename <old> <new>
|
|
5881
6225
|
|
|
@@ -5895,22 +6239,26 @@ Flags:
|
|
|
5895
6239
|
--compact Names only
|
|
5896
6240
|
--device-auth Use device auth for Codex login
|
|
5897
6241
|
--login=MODE Login mode: browser | device
|
|
5898
|
-
--
|
|
6242
|
+
--switch-left=NN Auto-roll switch threshold by percent left (0-50)
|
|
6243
|
+
--threshold=NN Legacy auto-roll switch threshold by percent used (50-100)
|
|
5899
6244
|
--watch Keep checking and auto-switch when threshold is reached
|
|
5900
6245
|
--interval=SEC Watch interval in seconds (default: 30)
|
|
5901
6246
|
--dry-run Print planned switch without changing active profile
|
|
6247
|
+
--restart-codex Restart official Codex after a profile switch
|
|
6248
|
+
--no-restart-codex Override saved auto-roll restart setting
|
|
5902
6249
|
--profile=NAME Run Codex with one saved profile
|
|
5903
6250
|
--runtime=NAME Accounts Pool runtime store: desktop | daemon
|
|
5904
6251
|
--state-dir=PATH Override the runtime state dir for Accounts Pool inspection
|
|
5905
6252
|
--port=NNNN Stable daemon/API port (or use with account-pool status for daemon base URL)
|
|
5906
6253
|
--telegram-bot-token=TOKEN Enable Telegram remote control for daemon mode
|
|
5907
6254
|
--project-path=PATH Optional path to bootstrap as the default Projects root in daemon mode
|
|
6255
|
+
|
|
6256
|
+
Note: profile autoroll requires Pro.
|
|
5908
6257
|
`);
|
|
5909
6258
|
}
|
|
5910
6259
|
|
|
5911
6260
|
// src/platform/args.ts
|
|
5912
6261
|
var DEFAULT_AUTOROLL_INTERVAL_SECONDS = 30;
|
|
5913
|
-
var DEFAULT_AUTOROLL_THRESHOLD = 95;
|
|
5914
6262
|
function hasFlag(args, flag) {
|
|
5915
6263
|
return args.includes(flag);
|
|
5916
6264
|
}
|
|
@@ -7694,77 +8042,976 @@ async function assertProfileCreationAllowed(profileManager) {
|
|
|
7694
8042
|
}
|
|
7695
8043
|
}
|
|
7696
8044
|
|
|
7697
|
-
// src/
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
8045
|
+
// ../../packages/runtime-codex/src/codex/officialAppRestart.ts
|
|
8046
|
+
var import_node_child_process7 = require("child_process");
|
|
8047
|
+
var import_node_crypto6 = require("crypto");
|
|
8048
|
+
var import_node_fs8 = require("fs");
|
|
8049
|
+
var import_node_os5 = require("os");
|
|
8050
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
8051
|
+
var COMMAND_TIMEOUT_MS = 3e3;
|
|
8052
|
+
var EXIT_WAIT_MS = 1e4;
|
|
8053
|
+
var LAUNCH_WAIT_MS = 15e3;
|
|
8054
|
+
var POLL_INTERVAL_MS = 250;
|
|
8055
|
+
var APP_DISCOVERY_CACHE_TTL_MS = 6e4;
|
|
8056
|
+
var APP_DISCOVERY_MISS_CACHE_TTL_MS = 5e3;
|
|
8057
|
+
var OFFICIAL_CODEX_ACTIVITY_LIMIT = 50;
|
|
8058
|
+
var profileActionLock = Promise.resolve();
|
|
8059
|
+
var appDiscoveryCache = null;
|
|
8060
|
+
function logOfficialCodexRestart(level, message, context) {
|
|
8061
|
+
const logger = level === "warn" ? console.warn : level === "error" ? console.error : console.info;
|
|
8062
|
+
if (context && Object.keys(context).length > 0) {
|
|
8063
|
+
logger(`[official-codex-restart] ${message}`, context);
|
|
8064
|
+
return;
|
|
7701
8065
|
}
|
|
7702
|
-
|
|
8066
|
+
logger(`[official-codex-restart] ${message}`);
|
|
7703
8067
|
}
|
|
7704
|
-
function
|
|
7705
|
-
|
|
8068
|
+
function hashProfileKey(profileKey) {
|
|
8069
|
+
if (!profileKey) {
|
|
8070
|
+
return null;
|
|
8071
|
+
}
|
|
8072
|
+
return (0, import_node_crypto6.createHash)("sha256").update(profileKey).digest("hex").slice(0, 12);
|
|
8073
|
+
}
|
|
8074
|
+
async function recordOfficialCodexActivity(input) {
|
|
8075
|
+
const now = /* @__PURE__ */ new Date();
|
|
8076
|
+
const entry = {
|
|
8077
|
+
id: `${now.getTime().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
8078
|
+
at: now.toISOString(),
|
|
8079
|
+
kind: input.kind,
|
|
8080
|
+
status: input.status,
|
|
8081
|
+
reason: input.reason ?? null,
|
|
8082
|
+
decisionId: input.decisionId ?? null,
|
|
8083
|
+
profileName: input.profileName ?? null,
|
|
8084
|
+
sourceProfileName: input.sourceProfileName ?? null,
|
|
8085
|
+
targetProfileName: input.targetProfileName ?? null,
|
|
8086
|
+
remainingPercent: input.remainingPercent ?? null,
|
|
8087
|
+
threshold: input.threshold ?? null,
|
|
8088
|
+
snapshotAgeMs: input.snapshotAgeMs ?? null,
|
|
8089
|
+
snapshotSource: input.snapshotSource ?? null,
|
|
8090
|
+
phase: input.phase ?? null,
|
|
8091
|
+
pid: input.pid ?? null,
|
|
8092
|
+
profileKeyHash: input.profileKeyHash ?? hashProfileKey(input.profileKey),
|
|
8093
|
+
switchVerified: input.switchVerified ?? null,
|
|
8094
|
+
restartRequested: input.restartRequested ?? null,
|
|
8095
|
+
restartResult: input.restartResult ?? null,
|
|
8096
|
+
observedProfileName: input.observedProfileName ?? null,
|
|
8097
|
+
observedProfileKeyHash: input.observedProfileKeyHash ?? hashProfileKey(input.observedProfileKey),
|
|
8098
|
+
observedProfileMatchSource: input.observedProfileMatchSource ?? null
|
|
8099
|
+
};
|
|
8100
|
+
try {
|
|
8101
|
+
await updateAppState((state) => ({
|
|
8102
|
+
officialCodex: {
|
|
8103
|
+
activity: [...state.officialCodex.activity, entry].slice(
|
|
8104
|
+
-OFFICIAL_CODEX_ACTIVITY_LIMIT
|
|
8105
|
+
)
|
|
8106
|
+
}
|
|
8107
|
+
}));
|
|
8108
|
+
} catch (error) {
|
|
8109
|
+
logOfficialCodexRestart("warn", "Failed to record official Codex activity.", {
|
|
8110
|
+
kind: entry.kind,
|
|
8111
|
+
status: entry.status,
|
|
8112
|
+
profileKeyHash: entry.profileKeyHash,
|
|
8113
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8114
|
+
});
|
|
8115
|
+
}
|
|
7706
8116
|
}
|
|
7707
|
-
function
|
|
7708
|
-
return
|
|
8117
|
+
function runCommand2(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
|
|
8118
|
+
return new Promise((resolve) => {
|
|
8119
|
+
const child = (0, import_node_child_process7.spawn)(command, args, {
|
|
8120
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
8121
|
+
});
|
|
8122
|
+
let stdout = "";
|
|
8123
|
+
let stderr = "";
|
|
8124
|
+
let timedOut = false;
|
|
8125
|
+
const timer = setTimeout(() => {
|
|
8126
|
+
timedOut = true;
|
|
8127
|
+
child.kill("SIGTERM");
|
|
8128
|
+
}, timeoutMs);
|
|
8129
|
+
child.stdout?.on("data", (chunk) => {
|
|
8130
|
+
stdout += String(chunk);
|
|
8131
|
+
});
|
|
8132
|
+
child.stderr?.on("data", (chunk) => {
|
|
8133
|
+
stderr += String(chunk);
|
|
8134
|
+
});
|
|
8135
|
+
child.on("error", (error) => {
|
|
8136
|
+
clearTimeout(timer);
|
|
8137
|
+
resolve({
|
|
8138
|
+
code: -1,
|
|
8139
|
+
stdout,
|
|
8140
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
8141
|
+
timedOut
|
|
8142
|
+
});
|
|
8143
|
+
});
|
|
8144
|
+
child.on("close", (code) => {
|
|
8145
|
+
clearTimeout(timer);
|
|
8146
|
+
resolve({ code, stdout, stderr, timedOut });
|
|
8147
|
+
});
|
|
8148
|
+
});
|
|
7709
8149
|
}
|
|
7710
|
-
function
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
return date.toLocaleDateString();
|
|
8150
|
+
function enqueueProfileAction(work) {
|
|
8151
|
+
const run = profileActionLock.then(work, work);
|
|
8152
|
+
profileActionLock = run.then(
|
|
8153
|
+
() => void 0,
|
|
8154
|
+
() => void 0
|
|
8155
|
+
);
|
|
8156
|
+
return run;
|
|
7718
8157
|
}
|
|
7719
|
-
function
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
if (
|
|
7726
|
-
return
|
|
7727
|
-
}
|
|
7728
|
-
const minutes = Math.round(absMs / 6e4);
|
|
7729
|
-
if (minutes < 60) {
|
|
7730
|
-
return diffMs >= 0 ? `in ${minutes}m` : `${minutes}m ago`;
|
|
8158
|
+
async function readProcessRows() {
|
|
8159
|
+
const result = await runCommand2(
|
|
8160
|
+
"/bin/ps",
|
|
8161
|
+
["-axo", "pid=,ppid=,lstart=,args="],
|
|
8162
|
+
2e3
|
|
8163
|
+
);
|
|
8164
|
+
if (result.code !== 0) {
|
|
8165
|
+
return [];
|
|
7731
8166
|
}
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
8167
|
+
return result.stdout.split(/\r?\n/).flatMap((line) => {
|
|
8168
|
+
const match = line.match(
|
|
8169
|
+
/^\s*(\d+)\s+(\d+)\s+([A-Z][a-z]{2}\s+[A-Z][a-z]{2}\s+\d+\s+\d+:\d+:\d+\s+\d{4})\s+(.+)$/
|
|
8170
|
+
);
|
|
8171
|
+
if (!match) {
|
|
8172
|
+
return [];
|
|
8173
|
+
}
|
|
8174
|
+
const pid = Number(match[1]);
|
|
8175
|
+
const ppid = Number(match[2]);
|
|
8176
|
+
const parsedStartedAt = Date.parse(match[3] ?? "");
|
|
8177
|
+
const startedAt = Number.isFinite(parsedStartedAt) ? parsedStartedAt : null;
|
|
8178
|
+
const args = match[4] ?? "";
|
|
8179
|
+
if (!Number.isInteger(pid) || !Number.isInteger(ppid) || !args) {
|
|
8180
|
+
return [];
|
|
8181
|
+
}
|
|
8182
|
+
return [{ pid, ppid, startedAt, args }];
|
|
8183
|
+
});
|
|
8184
|
+
}
|
|
8185
|
+
function getMainExecutablePath(candidate) {
|
|
8186
|
+
return import_node_path11.default.join(candidate.appPath, "Contents", "MacOS", candidate.executableName);
|
|
8187
|
+
}
|
|
8188
|
+
function isMainProcessRow(row, candidate) {
|
|
8189
|
+
return row.args.includes(getMainExecutablePath(candidate));
|
|
8190
|
+
}
|
|
8191
|
+
function findAppServerPid(rows, mainPid) {
|
|
8192
|
+
return rows.find(
|
|
8193
|
+
(row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server")
|
|
8194
|
+
)?.pid ?? null;
|
|
8195
|
+
}
|
|
8196
|
+
async function processMatchesProfileHome(pid, profileHome) {
|
|
8197
|
+
const result = await runCommand2(
|
|
8198
|
+
"/bin/ps",
|
|
8199
|
+
["eww", "-p", String(pid), "-o", "command="],
|
|
8200
|
+
2e3
|
|
8201
|
+
);
|
|
8202
|
+
if (result.code !== 0) {
|
|
8203
|
+
return false;
|
|
7735
8204
|
}
|
|
7736
|
-
|
|
7737
|
-
return diffMs >= 0 ? `in ${days}d` : `${days}d ago`;
|
|
8205
|
+
return result.stdout.includes(`CODEX_HOME=${profileHome}`) || result.stdout.includes(profileHome);
|
|
7738
8206
|
}
|
|
7739
|
-
function
|
|
7740
|
-
|
|
7741
|
-
|
|
8207
|
+
async function findAppServerPidForProfile(rows, mainPid, profileHome) {
|
|
8208
|
+
const candidates = rows.filter(
|
|
8209
|
+
(row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server")
|
|
8210
|
+
);
|
|
8211
|
+
for (const row of candidates) {
|
|
8212
|
+
if (row.args.includes(profileHome) || await processMatchesProfileHome(row.pid, profileHome)) {
|
|
8213
|
+
return row.pid;
|
|
8214
|
+
}
|
|
7742
8215
|
}
|
|
7743
|
-
|
|
7744
|
-
if (!org) return null;
|
|
7745
|
-
const name = org.title || org.id;
|
|
7746
|
-
if (!name) return null;
|
|
7747
|
-
return org.role ? `${name} (${org.role})` : name;
|
|
7748
|
-
}).filter((value) => Boolean(value));
|
|
7749
|
-
return parts.length > 0 ? parts.join(", ") : null;
|
|
8216
|
+
return null;
|
|
7750
8217
|
}
|
|
7751
|
-
function
|
|
7752
|
-
if (
|
|
7753
|
-
return
|
|
8218
|
+
async function describeUnmanagedProfileHint(appServerPid) {
|
|
8219
|
+
if (!appServerPid) {
|
|
8220
|
+
return {};
|
|
7754
8221
|
}
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
8222
|
+
const result = await runCommand2(
|
|
8223
|
+
"/bin/ps",
|
|
8224
|
+
["eww", "-p", String(appServerPid), "-o", "command="],
|
|
8225
|
+
2e3
|
|
8226
|
+
);
|
|
8227
|
+
if (result.code !== 0) {
|
|
8228
|
+
return {};
|
|
7760
8229
|
}
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
8230
|
+
const telemetryMatch = result.stdout.match(
|
|
8231
|
+
/CODEX_TELEMETRY_LABEL=codexuse-profile-([^\s]+)/
|
|
8232
|
+
);
|
|
8233
|
+
if (telemetryMatch?.[1]) {
|
|
8234
|
+
const encodedProfileName = telemetryMatch[1];
|
|
8235
|
+
let profileName = encodedProfileName;
|
|
8236
|
+
try {
|
|
8237
|
+
profileName = decodeURIComponent(encodedProfileName);
|
|
8238
|
+
} catch {
|
|
8239
|
+
profileName = encodedProfileName;
|
|
8240
|
+
}
|
|
8241
|
+
return {
|
|
8242
|
+
profileName,
|
|
8243
|
+
profileMatchSource: "telemetry-label"
|
|
8244
|
+
};
|
|
7766
8245
|
}
|
|
7767
|
-
const
|
|
8246
|
+
const profileHomeMatch = result.stdout.match(/profile-homes\/([^\s]+)/);
|
|
8247
|
+
if (profileHomeMatch?.[1]) {
|
|
8248
|
+
return {
|
|
8249
|
+
profileName: profileHomeMatch[1],
|
|
8250
|
+
profileMatchSource: "profile-home"
|
|
8251
|
+
};
|
|
8252
|
+
}
|
|
8253
|
+
return {};
|
|
8254
|
+
}
|
|
8255
|
+
function getDescendantPids(rootPid, rows) {
|
|
8256
|
+
const descendants = [];
|
|
8257
|
+
const queue = [rootPid];
|
|
8258
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8259
|
+
while (queue.length > 0) {
|
|
8260
|
+
const current = queue.shift();
|
|
8261
|
+
if (seen.has(current)) {
|
|
8262
|
+
continue;
|
|
8263
|
+
}
|
|
8264
|
+
seen.add(current);
|
|
8265
|
+
for (const row of rows) {
|
|
8266
|
+
if (row.ppid !== current || seen.has(row.pid)) {
|
|
8267
|
+
continue;
|
|
8268
|
+
}
|
|
8269
|
+
descendants.push(row.pid);
|
|
8270
|
+
queue.push(row.pid);
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
return descendants;
|
|
8274
|
+
}
|
|
8275
|
+
async function waitForNewMainPid(candidate, previousPids) {
|
|
8276
|
+
const deadline = Date.now() + LAUNCH_WAIT_MS;
|
|
8277
|
+
while (Date.now() < deadline) {
|
|
8278
|
+
const rows = await readProcessRows();
|
|
8279
|
+
const pid = rows.find((row) => isMainProcessRow(row, candidate) && !previousPids.has(row.pid))?.pid ?? null;
|
|
8280
|
+
if (pid !== null) {
|
|
8281
|
+
return pid;
|
|
8282
|
+
}
|
|
8283
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
8284
|
+
}
|
|
8285
|
+
return null;
|
|
8286
|
+
}
|
|
8287
|
+
async function waitForMainPid(candidate, pid, timeoutMs) {
|
|
8288
|
+
const deadline = Date.now() + timeoutMs;
|
|
8289
|
+
while (Date.now() < deadline) {
|
|
8290
|
+
const rows = await readProcessRows();
|
|
8291
|
+
if (rows.some((row) => row.pid === pid && isMainProcessRow(row, candidate))) {
|
|
8292
|
+
return true;
|
|
8293
|
+
}
|
|
8294
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
8295
|
+
}
|
|
8296
|
+
return false;
|
|
8297
|
+
}
|
|
8298
|
+
async function waitForAppServerPid(mainPid, profileHome) {
|
|
8299
|
+
const deadline = Date.now() + LAUNCH_WAIT_MS;
|
|
8300
|
+
while (Date.now() < deadline) {
|
|
8301
|
+
const pid = await findAppServerPidForProfile(
|
|
8302
|
+
await readProcessRows(),
|
|
8303
|
+
mainPid,
|
|
8304
|
+
profileHome
|
|
8305
|
+
);
|
|
8306
|
+
if (pid !== null) {
|
|
8307
|
+
return pid;
|
|
8308
|
+
}
|
|
8309
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
8310
|
+
}
|
|
8311
|
+
return null;
|
|
8312
|
+
}
|
|
8313
|
+
async function waitForMainPidExit(pid, timeoutMs) {
|
|
8314
|
+
const deadline = Date.now() + timeoutMs;
|
|
8315
|
+
while (Date.now() < deadline) {
|
|
8316
|
+
const rows = await readProcessRows();
|
|
8317
|
+
if (!rows.some((row) => row.pid === pid)) {
|
|
8318
|
+
return true;
|
|
8319
|
+
}
|
|
8320
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
8321
|
+
}
|
|
8322
|
+
return false;
|
|
8323
|
+
}
|
|
8324
|
+
function managedInstanceToPayload(instance, running, appServerPid = instance.appServerPid, startedAt = null) {
|
|
8325
|
+
const runningStartedAt = startedAt ?? instance.launchedAt;
|
|
8326
|
+
return {
|
|
8327
|
+
profileName: instance.profileName,
|
|
8328
|
+
profileKey: instance.profileKey,
|
|
8329
|
+
appPath: instance.appPath,
|
|
8330
|
+
bundleId: instance.bundleId,
|
|
8331
|
+
pid: running ? instance.pid : null,
|
|
8332
|
+
appServerPid: running ? appServerPid : null,
|
|
8333
|
+
running,
|
|
8334
|
+
startedAt: running ? toIso(runningStartedAt) : null,
|
|
8335
|
+
uptimeMs: running && typeof runningStartedAt === "number" ? Math.max(0, Date.now() - runningStartedAt) : null,
|
|
8336
|
+
launchedAt: toIso(instance.launchedAt),
|
|
8337
|
+
lastVerifiedAt: toIso(instance.lastVerifiedAt),
|
|
8338
|
+
lastStatus: instance.lastStatus,
|
|
8339
|
+
lastError: instance.lastError
|
|
8340
|
+
};
|
|
8341
|
+
}
|
|
8342
|
+
async function patchManagedInstance(instance) {
|
|
8343
|
+
await patchAppState({
|
|
8344
|
+
officialCodex: {
|
|
8345
|
+
instancesByProfileName: {
|
|
8346
|
+
[instance.profileName]: instance
|
|
8347
|
+
}
|
|
8348
|
+
}
|
|
8349
|
+
});
|
|
8350
|
+
}
|
|
8351
|
+
async function readManagedInstance(profileName) {
|
|
8352
|
+
const state = await getAppState();
|
|
8353
|
+
return state.officialCodex.instancesByProfileName[profileName] ?? null;
|
|
8354
|
+
}
|
|
8355
|
+
async function resolveInstanceRuntimeState(args) {
|
|
8356
|
+
const pid = args.instance.pid;
|
|
8357
|
+
if (!pid) {
|
|
8358
|
+
return { running: false, appServerPid: null, startedAt: null };
|
|
8359
|
+
}
|
|
8360
|
+
const mainRow = args.rows.find((row) => row.pid === pid);
|
|
8361
|
+
if (!mainRow || !isMainProcessRow(mainRow, args.candidate)) {
|
|
8362
|
+
return { running: false, appServerPid: null, startedAt: null };
|
|
8363
|
+
}
|
|
8364
|
+
const appServerPid = args.instance.profileHome ? await findAppServerPidForProfile(args.rows, pid, args.instance.profileHome) : findAppServerPid(args.rows, pid);
|
|
8365
|
+
if (args.instance.profileHome && appServerPid === null) {
|
|
8366
|
+
return { running: false, appServerPid: null, startedAt: null };
|
|
8367
|
+
}
|
|
8368
|
+
return {
|
|
8369
|
+
running: true,
|
|
8370
|
+
appServerPid,
|
|
8371
|
+
startedAt: mainRow.startedAt
|
|
8372
|
+
};
|
|
8373
|
+
}
|
|
8374
|
+
async function openCodexWithProfileHome(candidate, profileHome, profileName, previousPids) {
|
|
8375
|
+
const executablePath = getMainExecutablePath(candidate);
|
|
8376
|
+
if (!(0, import_node_fs8.existsSync)(executablePath)) {
|
|
8377
|
+
return { opened: false, pid: null };
|
|
8378
|
+
}
|
|
8379
|
+
const telemetryLabel = `codexuse-profile-${encodeURIComponent(profileName)}`;
|
|
8380
|
+
let child;
|
|
8381
|
+
try {
|
|
8382
|
+
child = (0, import_node_child_process7.spawn)(executablePath, [], {
|
|
8383
|
+
detached: true,
|
|
8384
|
+
env: {
|
|
8385
|
+
...process.env,
|
|
8386
|
+
CODEX_HOME: profileHome,
|
|
8387
|
+
CODEX_TELEMETRY_LABEL: telemetryLabel
|
|
8388
|
+
},
|
|
8389
|
+
stdio: "ignore"
|
|
8390
|
+
});
|
|
8391
|
+
} catch {
|
|
8392
|
+
return { opened: false, pid: null };
|
|
8393
|
+
}
|
|
8394
|
+
const childPid = child.pid ?? null;
|
|
8395
|
+
child.unref();
|
|
8396
|
+
const spawnError = await new Promise((resolve) => {
|
|
8397
|
+
let settled = false;
|
|
8398
|
+
const settle = (failed) => {
|
|
8399
|
+
if (settled) {
|
|
8400
|
+
return;
|
|
8401
|
+
}
|
|
8402
|
+
settled = true;
|
|
8403
|
+
resolve(failed);
|
|
8404
|
+
};
|
|
8405
|
+
child.once("error", () => settle(true));
|
|
8406
|
+
setTimeout(() => settle(false), POLL_INTERVAL_MS);
|
|
8407
|
+
});
|
|
8408
|
+
if (spawnError) {
|
|
8409
|
+
return { opened: false, pid: null };
|
|
8410
|
+
}
|
|
8411
|
+
if (childPid !== null) {
|
|
8412
|
+
const verified = await waitForMainPid(candidate, childPid, 2e3);
|
|
8413
|
+
if (verified) {
|
|
8414
|
+
return { opened: true, pid: childPid };
|
|
8415
|
+
}
|
|
8416
|
+
}
|
|
8417
|
+
return {
|
|
8418
|
+
opened: true,
|
|
8419
|
+
pid: await waitForNewMainPid(candidate, previousPids)
|
|
8420
|
+
};
|
|
8421
|
+
}
|
|
8422
|
+
async function readBundleString(appPath, key) {
|
|
8423
|
+
const plistPath = import_node_path11.default.join(appPath, "Contents", "Info.plist");
|
|
8424
|
+
if (!(0, import_node_fs8.existsSync)(plistPath)) {
|
|
8425
|
+
return null;
|
|
8426
|
+
}
|
|
8427
|
+
const result = await runCommand2("/usr/bin/plutil", [
|
|
8428
|
+
"-extract",
|
|
8429
|
+
key,
|
|
8430
|
+
"raw",
|
|
8431
|
+
"-o",
|
|
8432
|
+
"-",
|
|
8433
|
+
plistPath
|
|
8434
|
+
]);
|
|
8435
|
+
if (result.code !== 0) {
|
|
8436
|
+
return null;
|
|
8437
|
+
}
|
|
8438
|
+
const bundleId = result.stdout.trim();
|
|
8439
|
+
return bundleId.length > 0 ? bundleId : null;
|
|
8440
|
+
}
|
|
8441
|
+
async function discoverCodexAppCandidates() {
|
|
8442
|
+
const now = Date.now();
|
|
8443
|
+
const envPath = process.env.CODEXUSE_OFFICIAL_CODEX_APP_PATH?.trim() || null;
|
|
8444
|
+
if (appDiscoveryCache && appDiscoveryCache.envPath === envPath && appDiscoveryCache.expiresAt > now) {
|
|
8445
|
+
return appDiscoveryCache.candidates;
|
|
8446
|
+
}
|
|
8447
|
+
const rawPaths = /* @__PURE__ */ new Set();
|
|
8448
|
+
if (envPath) {
|
|
8449
|
+
rawPaths.add(envPath);
|
|
8450
|
+
}
|
|
8451
|
+
rawPaths.add("/Applications/Codex.app");
|
|
8452
|
+
rawPaths.add(import_node_path11.default.join((0, import_node_os5.homedir)(), "Applications", "Codex.app"));
|
|
8453
|
+
const mdfind = await runCommand2(
|
|
8454
|
+
"/usr/bin/mdfind",
|
|
8455
|
+
["kMDItemFSName == 'Codex.app'"],
|
|
8456
|
+
1500
|
|
8457
|
+
);
|
|
8458
|
+
if (mdfind.code === 0) {
|
|
8459
|
+
for (const line of mdfind.stdout.split(/\r?\n/)) {
|
|
8460
|
+
const trimmed = line.trim();
|
|
8461
|
+
if (trimmed.endsWith("/Codex.app")) {
|
|
8462
|
+
rawPaths.add(trimmed);
|
|
8463
|
+
}
|
|
8464
|
+
}
|
|
8465
|
+
}
|
|
8466
|
+
const candidates = [];
|
|
8467
|
+
for (const appPath of rawPaths) {
|
|
8468
|
+
if (!(0, import_node_fs8.existsSync)(appPath) || import_node_path11.default.basename(appPath) !== "Codex.app") {
|
|
8469
|
+
continue;
|
|
8470
|
+
}
|
|
8471
|
+
const bundleId = await readBundleString(appPath, "CFBundleIdentifier");
|
|
8472
|
+
if (!bundleId || bundleId.toLowerCase().includes("codexuse")) {
|
|
8473
|
+
continue;
|
|
8474
|
+
}
|
|
8475
|
+
candidates.push({
|
|
8476
|
+
appPath,
|
|
8477
|
+
bundleId,
|
|
8478
|
+
version: await readBundleString(appPath, "CFBundleShortVersionString"),
|
|
8479
|
+
executableName: await readBundleString(appPath, "CFBundleExecutable") ?? "Codex"
|
|
8480
|
+
});
|
|
8481
|
+
}
|
|
8482
|
+
const sorted = candidates.sort((a, b) => a.appPath.localeCompare(b.appPath));
|
|
8483
|
+
appDiscoveryCache = {
|
|
8484
|
+
envPath,
|
|
8485
|
+
expiresAt: now + (sorted.length > 0 ? APP_DISCOVERY_CACHE_TTL_MS : APP_DISCOVERY_MISS_CACHE_TTL_MS),
|
|
8486
|
+
candidates: sorted
|
|
8487
|
+
};
|
|
8488
|
+
return sorted;
|
|
8489
|
+
}
|
|
8490
|
+
async function getRunningCodexPids(candidate) {
|
|
8491
|
+
const result = await runCommand2("/bin/ps", ["-axo", "pid=,args="], 2e3);
|
|
8492
|
+
const pids = /* @__PURE__ */ new Set();
|
|
8493
|
+
const appContentsRoot = `${import_node_path11.default.join(candidate.appPath, "Contents")}${import_node_path11.default.sep}`;
|
|
8494
|
+
if (result.code === 0) {
|
|
8495
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
8496
|
+
const match = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
8497
|
+
if (!match) {
|
|
8498
|
+
continue;
|
|
8499
|
+
}
|
|
8500
|
+
const pid = Number(match[1]);
|
|
8501
|
+
const args = match[2] ?? "";
|
|
8502
|
+
if (Number.isInteger(pid) && args.includes(appContentsRoot)) {
|
|
8503
|
+
pids.add(pid);
|
|
8504
|
+
}
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
return Array.from(pids).sort((a, b) => a - b);
|
|
8508
|
+
}
|
|
8509
|
+
async function getRunningCodexMainPids(candidate) {
|
|
8510
|
+
const result = await runCommand2("/bin/ps", ["-axo", "pid=,args="], 2e3);
|
|
8511
|
+
const pids = /* @__PURE__ */ new Set();
|
|
8512
|
+
const executablePath = import_node_path11.default.join(
|
|
8513
|
+
candidate.appPath,
|
|
8514
|
+
"Contents",
|
|
8515
|
+
"MacOS",
|
|
8516
|
+
candidate.executableName
|
|
8517
|
+
);
|
|
8518
|
+
if (result.code === 0) {
|
|
8519
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
8520
|
+
const match = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
8521
|
+
if (!match) {
|
|
8522
|
+
continue;
|
|
8523
|
+
}
|
|
8524
|
+
const pid = Number(match[1]);
|
|
8525
|
+
const args = match[2] ?? "";
|
|
8526
|
+
if (Number.isInteger(pid) && args.includes(executablePath)) {
|
|
8527
|
+
pids.add(pid);
|
|
8528
|
+
}
|
|
8529
|
+
}
|
|
8530
|
+
}
|
|
8531
|
+
return Array.from(pids).sort((a, b) => a - b);
|
|
8532
|
+
}
|
|
8533
|
+
async function resolveCodexAppTarget() {
|
|
8534
|
+
const candidates = await discoverCodexAppCandidates();
|
|
8535
|
+
const fallback = candidates[0] ?? null;
|
|
8536
|
+
if (!fallback) {
|
|
8537
|
+
return { candidate: null, runningPids: [], mainPids: [] };
|
|
8538
|
+
}
|
|
8539
|
+
for (const candidate of candidates) {
|
|
8540
|
+
const [runningPids2, mainPids2] = await Promise.all([
|
|
8541
|
+
getRunningCodexPids(candidate),
|
|
8542
|
+
getRunningCodexMainPids(candidate)
|
|
8543
|
+
]);
|
|
8544
|
+
if (mainPids2.length > 0) {
|
|
8545
|
+
return { candidate, runningPids: runningPids2, mainPids: mainPids2 };
|
|
8546
|
+
}
|
|
8547
|
+
}
|
|
8548
|
+
const [runningPids, mainPids] = await Promise.all([
|
|
8549
|
+
getRunningCodexPids(fallback),
|
|
8550
|
+
getRunningCodexMainPids(fallback)
|
|
8551
|
+
]);
|
|
8552
|
+
return { candidate: fallback, runningPids, mainPids };
|
|
8553
|
+
}
|
|
8554
|
+
async function signalPids(pids, signal) {
|
|
8555
|
+
const signaled = [];
|
|
8556
|
+
for (const pid of pids) {
|
|
8557
|
+
try {
|
|
8558
|
+
process.kill(pid, signal);
|
|
8559
|
+
signaled.push(pid);
|
|
8560
|
+
} catch {
|
|
8561
|
+
}
|
|
8562
|
+
}
|
|
8563
|
+
return signaled;
|
|
8564
|
+
}
|
|
8565
|
+
function toIso(value) {
|
|
8566
|
+
return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : null;
|
|
8567
|
+
}
|
|
8568
|
+
async function rememberProfileSwitch(profileKey) {
|
|
8569
|
+
await patchAppState({
|
|
8570
|
+
officialCodex: {
|
|
8571
|
+
lastProfileSwitchAt: Date.now(),
|
|
8572
|
+
lastProfileSwitchProfileKey: profileKey
|
|
8573
|
+
}
|
|
8574
|
+
});
|
|
8575
|
+
}
|
|
8576
|
+
async function rememberRestartResult(args) {
|
|
8577
|
+
const now = Date.now();
|
|
8578
|
+
const verified = args.status === "restarted" || args.status === "started";
|
|
8579
|
+
await patchAppState({
|
|
8580
|
+
officialCodex: {
|
|
8581
|
+
lastVerifiedLaunchAt: verified ? now : void 0,
|
|
8582
|
+
lastVerifiedLaunchProfileKey: verified ? args.profileKey ?? null : void 0,
|
|
8583
|
+
lastObservedPid: verified ? args.pid ?? null : void 0,
|
|
8584
|
+
lastRestartStatus: args.status,
|
|
8585
|
+
lastRestartReason: args.reason ?? null
|
|
8586
|
+
}
|
|
8587
|
+
});
|
|
8588
|
+
}
|
|
8589
|
+
function recordOfficialCodexRestartResult(args) {
|
|
8590
|
+
return rememberRestartResult(args);
|
|
8591
|
+
}
|
|
8592
|
+
function recordOfficialCodexProfileSwitch(profileKey) {
|
|
8593
|
+
return rememberProfileSwitch(profileKey);
|
|
8594
|
+
}
|
|
8595
|
+
async function getOfficialCodexProfileInstances() {
|
|
8596
|
+
if (process.platform !== "darwin") {
|
|
8597
|
+
return {
|
|
8598
|
+
state: "unsupported",
|
|
8599
|
+
appPath: null,
|
|
8600
|
+
bundleId: null,
|
|
8601
|
+
version: null,
|
|
8602
|
+
instances: [],
|
|
8603
|
+
unmanaged: []
|
|
8604
|
+
};
|
|
8605
|
+
}
|
|
8606
|
+
const { candidate, mainPids } = await resolveCodexAppTarget();
|
|
8607
|
+
if (!candidate) {
|
|
8608
|
+
return {
|
|
8609
|
+
state: "not-found",
|
|
8610
|
+
appPath: null,
|
|
8611
|
+
bundleId: null,
|
|
8612
|
+
version: null,
|
|
8613
|
+
instances: [],
|
|
8614
|
+
unmanaged: []
|
|
8615
|
+
};
|
|
8616
|
+
}
|
|
8617
|
+
const [state, rows] = await Promise.all([getAppState(), readProcessRows()]);
|
|
8618
|
+
const managed = [];
|
|
8619
|
+
for (const instance of Object.values(state.officialCodex.instancesByProfileName)) {
|
|
8620
|
+
const runtime = await resolveInstanceRuntimeState({ instance, candidate, rows });
|
|
8621
|
+
managed.push(
|
|
8622
|
+
managedInstanceToPayload(
|
|
8623
|
+
instance,
|
|
8624
|
+
runtime.running,
|
|
8625
|
+
runtime.appServerPid,
|
|
8626
|
+
runtime.startedAt
|
|
8627
|
+
)
|
|
8628
|
+
);
|
|
8629
|
+
}
|
|
8630
|
+
managed.sort((a, b) => a.profileName.localeCompare(b.profileName));
|
|
8631
|
+
const managedPids = new Set(
|
|
8632
|
+
managed.flatMap((entry) => entry.running && entry.pid ? [entry.pid] : [])
|
|
8633
|
+
);
|
|
8634
|
+
const unmanaged = [];
|
|
8635
|
+
for (const pid of mainPids.filter((entry) => !managedPids.has(entry))) {
|
|
8636
|
+
const appServerPid = findAppServerPid(rows, pid);
|
|
8637
|
+
const startedAt = rows.find((row) => row.pid === pid)?.startedAt ?? null;
|
|
8638
|
+
unmanaged.push({
|
|
8639
|
+
pid,
|
|
8640
|
+
appServerPid,
|
|
8641
|
+
startedAt: toIso(startedAt),
|
|
8642
|
+
uptimeMs: typeof startedAt === "number" ? Math.max(0, Date.now() - startedAt) : null,
|
|
8643
|
+
...await describeUnmanagedProfileHint(appServerPid)
|
|
8644
|
+
});
|
|
8645
|
+
}
|
|
8646
|
+
return {
|
|
8647
|
+
state: "found",
|
|
8648
|
+
appPath: candidate.appPath,
|
|
8649
|
+
bundleId: candidate.bundleId,
|
|
8650
|
+
version: candidate.version,
|
|
8651
|
+
instances: managed,
|
|
8652
|
+
unmanaged
|
|
8653
|
+
};
|
|
8654
|
+
}
|
|
8655
|
+
function launchOfficialCodexProfileInstance(options) {
|
|
8656
|
+
return enqueueProfileAction(async () => {
|
|
8657
|
+
if (process.platform !== "darwin") {
|
|
8658
|
+
return {
|
|
8659
|
+
status: "skipped",
|
|
8660
|
+
profileName: options.profileName,
|
|
8661
|
+
instance: null,
|
|
8662
|
+
reason: "unsupported-platform"
|
|
8663
|
+
};
|
|
8664
|
+
}
|
|
8665
|
+
const { candidate, mainPids } = await resolveCodexAppTarget();
|
|
8666
|
+
if (!candidate) {
|
|
8667
|
+
return {
|
|
8668
|
+
status: "skipped",
|
|
8669
|
+
profileName: options.profileName,
|
|
8670
|
+
instance: null,
|
|
8671
|
+
reason: "official-codex-app-not-found"
|
|
8672
|
+
};
|
|
8673
|
+
}
|
|
8674
|
+
const existing = await readManagedInstance(options.profileName);
|
|
8675
|
+
if (existing) {
|
|
8676
|
+
const rows = await readProcessRows();
|
|
8677
|
+
const runtime = await resolveInstanceRuntimeState({ instance: existing, candidate, rows });
|
|
8678
|
+
if (runtime.running) {
|
|
8679
|
+
const verified = {
|
|
8680
|
+
...existing,
|
|
8681
|
+
profileHome: existing.profileHome ?? options.profileHome,
|
|
8682
|
+
appServerPid: runtime.appServerPid,
|
|
8683
|
+
lastVerifiedAt: Date.now(),
|
|
8684
|
+
lastStatus: "already-running",
|
|
8685
|
+
lastError: null
|
|
8686
|
+
};
|
|
8687
|
+
await patchManagedInstance(verified);
|
|
8688
|
+
return {
|
|
8689
|
+
status: "already-running",
|
|
8690
|
+
profileName: options.profileName,
|
|
8691
|
+
instance: managedInstanceToPayload(
|
|
8692
|
+
verified,
|
|
8693
|
+
true,
|
|
8694
|
+
runtime.appServerPid,
|
|
8695
|
+
runtime.startedAt
|
|
8696
|
+
),
|
|
8697
|
+
reason: null
|
|
8698
|
+
};
|
|
8699
|
+
}
|
|
8700
|
+
}
|
|
8701
|
+
const launch = await openCodexWithProfileHome(
|
|
8702
|
+
candidate,
|
|
8703
|
+
options.profileHome,
|
|
8704
|
+
options.profileName,
|
|
8705
|
+
new Set(mainPids)
|
|
8706
|
+
);
|
|
8707
|
+
if (!launch.opened) {
|
|
8708
|
+
const failed = {
|
|
8709
|
+
profileName: options.profileName,
|
|
8710
|
+
profileKey: options.profileKey,
|
|
8711
|
+
profileHome: options.profileHome,
|
|
8712
|
+
appPath: candidate.appPath,
|
|
8713
|
+
bundleId: candidate.bundleId,
|
|
8714
|
+
pid: null,
|
|
8715
|
+
appServerPid: null,
|
|
8716
|
+
launchedAt: null,
|
|
8717
|
+
lastVerifiedAt: null,
|
|
8718
|
+
lastStatus: "failed",
|
|
8719
|
+
lastError: "open-failed"
|
|
8720
|
+
};
|
|
8721
|
+
await patchManagedInstance(failed);
|
|
8722
|
+
return {
|
|
8723
|
+
status: "failed",
|
|
8724
|
+
profileName: options.profileName,
|
|
8725
|
+
instance: managedInstanceToPayload(failed, false),
|
|
8726
|
+
reason: "open-failed"
|
|
8727
|
+
};
|
|
8728
|
+
}
|
|
8729
|
+
const launchedPid = launch.pid;
|
|
8730
|
+
if (launchedPid === null) {
|
|
8731
|
+
const failed = {
|
|
8732
|
+
profileName: options.profileName,
|
|
8733
|
+
profileKey: options.profileKey,
|
|
8734
|
+
profileHome: options.profileHome,
|
|
8735
|
+
appPath: candidate.appPath,
|
|
8736
|
+
bundleId: candidate.bundleId,
|
|
8737
|
+
pid: null,
|
|
8738
|
+
appServerPid: null,
|
|
8739
|
+
launchedAt: null,
|
|
8740
|
+
lastVerifiedAt: null,
|
|
8741
|
+
lastStatus: "failed",
|
|
8742
|
+
lastError: "launch-timeout"
|
|
8743
|
+
};
|
|
8744
|
+
await patchManagedInstance(failed);
|
|
8745
|
+
return {
|
|
8746
|
+
status: "failed",
|
|
8747
|
+
profileName: options.profileName,
|
|
8748
|
+
instance: managedInstanceToPayload(failed, false),
|
|
8749
|
+
reason: "launch-timeout"
|
|
8750
|
+
};
|
|
8751
|
+
}
|
|
8752
|
+
const appServerPid = await waitForAppServerPid(
|
|
8753
|
+
launchedPid,
|
|
8754
|
+
options.profileHome
|
|
8755
|
+
);
|
|
8756
|
+
if (appServerPid === null) {
|
|
8757
|
+
const failed = {
|
|
8758
|
+
profileName: options.profileName,
|
|
8759
|
+
profileKey: options.profileKey,
|
|
8760
|
+
profileHome: options.profileHome,
|
|
8761
|
+
appPath: candidate.appPath,
|
|
8762
|
+
bundleId: candidate.bundleId,
|
|
8763
|
+
pid: null,
|
|
8764
|
+
appServerPid: null,
|
|
8765
|
+
launchedAt: null,
|
|
8766
|
+
lastVerifiedAt: Date.now(),
|
|
8767
|
+
lastStatus: "failed",
|
|
8768
|
+
lastError: "app-server-not-verified"
|
|
8769
|
+
};
|
|
8770
|
+
await patchManagedInstance(failed);
|
|
8771
|
+
return {
|
|
8772
|
+
status: "failed",
|
|
8773
|
+
profileName: options.profileName,
|
|
8774
|
+
instance: managedInstanceToPayload(failed, false),
|
|
8775
|
+
reason: "app-server-not-verified"
|
|
8776
|
+
};
|
|
8777
|
+
}
|
|
8778
|
+
const now = Date.now();
|
|
8779
|
+
const instance = {
|
|
8780
|
+
profileName: options.profileName,
|
|
8781
|
+
profileKey: options.profileKey,
|
|
8782
|
+
profileHome: options.profileHome,
|
|
8783
|
+
appPath: candidate.appPath,
|
|
8784
|
+
bundleId: candidate.bundleId,
|
|
8785
|
+
pid: launchedPid,
|
|
8786
|
+
appServerPid,
|
|
8787
|
+
launchedAt: now,
|
|
8788
|
+
lastVerifiedAt: now,
|
|
8789
|
+
lastStatus: "started",
|
|
8790
|
+
lastError: null
|
|
8791
|
+
};
|
|
8792
|
+
await patchManagedInstance(instance);
|
|
8793
|
+
return {
|
|
8794
|
+
status: "started",
|
|
8795
|
+
profileName: options.profileName,
|
|
8796
|
+
instance: managedInstanceToPayload(instance, true, appServerPid),
|
|
8797
|
+
reason: null
|
|
8798
|
+
};
|
|
8799
|
+
});
|
|
8800
|
+
}
|
|
8801
|
+
function stopOfficialCodexProfileInstance(profileName) {
|
|
8802
|
+
return enqueueProfileAction(async () => {
|
|
8803
|
+
const existing = await readManagedInstance(profileName);
|
|
8804
|
+
if (!existing) {
|
|
8805
|
+
return {
|
|
8806
|
+
status: "not-running",
|
|
8807
|
+
profileName,
|
|
8808
|
+
instance: null,
|
|
8809
|
+
reason: "not-managed"
|
|
8810
|
+
};
|
|
8811
|
+
}
|
|
8812
|
+
const { candidate } = await resolveCodexAppTarget();
|
|
8813
|
+
if (!candidate) {
|
|
8814
|
+
const stopped2 = {
|
|
8815
|
+
...existing,
|
|
8816
|
+
pid: null,
|
|
8817
|
+
appServerPid: null,
|
|
8818
|
+
lastVerifiedAt: Date.now(),
|
|
8819
|
+
lastStatus: "not-running",
|
|
8820
|
+
lastError: "official-codex-app-not-found"
|
|
8821
|
+
};
|
|
8822
|
+
await patchManagedInstance(stopped2);
|
|
8823
|
+
return {
|
|
8824
|
+
status: "not-running",
|
|
8825
|
+
profileName,
|
|
8826
|
+
instance: managedInstanceToPayload(stopped2, false),
|
|
8827
|
+
reason: "official-codex-app-not-found"
|
|
8828
|
+
};
|
|
8829
|
+
}
|
|
8830
|
+
const rows = await readProcessRows();
|
|
8831
|
+
const runtime = await resolveInstanceRuntimeState({ instance: existing, candidate, rows });
|
|
8832
|
+
if (!runtime.running || !existing.pid) {
|
|
8833
|
+
const stopped2 = {
|
|
8834
|
+
...existing,
|
|
8835
|
+
pid: null,
|
|
8836
|
+
appServerPid: null,
|
|
8837
|
+
lastVerifiedAt: Date.now(),
|
|
8838
|
+
lastStatus: "not-running",
|
|
8839
|
+
lastError: null
|
|
8840
|
+
};
|
|
8841
|
+
await patchManagedInstance(stopped2);
|
|
8842
|
+
return {
|
|
8843
|
+
status: "not-running",
|
|
8844
|
+
profileName,
|
|
8845
|
+
instance: managedInstanceToPayload(stopped2, false),
|
|
8846
|
+
reason: null
|
|
8847
|
+
};
|
|
8848
|
+
}
|
|
8849
|
+
const tree = [existing.pid, ...getDescendantPids(existing.pid, rows)].filter((pid, index, all) => all.indexOf(pid) === index).sort((a, b) => b - a);
|
|
8850
|
+
await signalPids(tree, "SIGTERM");
|
|
8851
|
+
const exited = await waitForMainPidExit(existing.pid, 5e3);
|
|
8852
|
+
if (!exited) {
|
|
8853
|
+
await signalPids(tree, "SIGKILL");
|
|
8854
|
+
await waitForMainPidExit(existing.pid, EXIT_WAIT_MS);
|
|
8855
|
+
}
|
|
8856
|
+
const stillRunning = (await readProcessRows()).some((row) => row.pid === existing.pid);
|
|
8857
|
+
const stopped = {
|
|
8858
|
+
...existing,
|
|
8859
|
+
pid: stillRunning ? existing.pid : null,
|
|
8860
|
+
appServerPid: stillRunning ? runtime.appServerPid : null,
|
|
8861
|
+
lastVerifiedAt: Date.now(),
|
|
8862
|
+
lastStatus: stillRunning ? "failed" : "stopped",
|
|
8863
|
+
lastError: stillRunning ? "stop-timeout" : null
|
|
8864
|
+
};
|
|
8865
|
+
await patchManagedInstance(stopped);
|
|
8866
|
+
return {
|
|
8867
|
+
status: stillRunning ? "failed" : "stopped",
|
|
8868
|
+
profileName,
|
|
8869
|
+
instance: managedInstanceToPayload(
|
|
8870
|
+
stopped,
|
|
8871
|
+
stillRunning,
|
|
8872
|
+
stopped.appServerPid,
|
|
8873
|
+
runtime.startedAt
|
|
8874
|
+
),
|
|
8875
|
+
reason: stillRunning ? "stop-timeout" : null
|
|
8876
|
+
};
|
|
8877
|
+
});
|
|
8878
|
+
}
|
|
8879
|
+
function stopOfficialCodexObservedProfileInstance(profileName, pid, appServerPid) {
|
|
8880
|
+
return enqueueProfileAction(async () => {
|
|
8881
|
+
const { candidate } = await resolveCodexAppTarget();
|
|
8882
|
+
if (!candidate) {
|
|
8883
|
+
return {
|
|
8884
|
+
status: "not-running",
|
|
8885
|
+
profileName,
|
|
8886
|
+
instance: null,
|
|
8887
|
+
reason: "official-codex-app-not-found"
|
|
8888
|
+
};
|
|
8889
|
+
}
|
|
8890
|
+
const rows = await readProcessRows();
|
|
8891
|
+
const mainRow = rows.find((row) => row.pid === pid);
|
|
8892
|
+
if (!mainRow || !isMainProcessRow(mainRow, candidate)) {
|
|
8893
|
+
return {
|
|
8894
|
+
status: "not-running",
|
|
8895
|
+
profileName,
|
|
8896
|
+
instance: null,
|
|
8897
|
+
reason: null
|
|
8898
|
+
};
|
|
8899
|
+
}
|
|
8900
|
+
const runtimeAppServerPid = findAppServerPid(rows, pid) ?? appServerPid;
|
|
8901
|
+
const tree = [pid, ...getDescendantPids(pid, rows)].filter((entry, index, all) => all.indexOf(entry) === index).sort((a, b) => b - a);
|
|
8902
|
+
await signalPids(tree, "SIGTERM");
|
|
8903
|
+
const exited = await waitForMainPidExit(pid, 5e3);
|
|
8904
|
+
if (!exited) {
|
|
8905
|
+
await signalPids(tree, "SIGKILL");
|
|
8906
|
+
await waitForMainPidExit(pid, EXIT_WAIT_MS);
|
|
8907
|
+
}
|
|
8908
|
+
const stillRunning = (await readProcessRows()).some((row) => row.pid === pid);
|
|
8909
|
+
const instance = {
|
|
8910
|
+
profileName,
|
|
8911
|
+
profileKey: null,
|
|
8912
|
+
appPath: candidate.appPath,
|
|
8913
|
+
bundleId: candidate.bundleId,
|
|
8914
|
+
pid: stillRunning ? pid : null,
|
|
8915
|
+
appServerPid: stillRunning ? runtimeAppServerPid : null,
|
|
8916
|
+
running: stillRunning,
|
|
8917
|
+
startedAt: stillRunning ? toIso(mainRow.startedAt) : null,
|
|
8918
|
+
uptimeMs: stillRunning && typeof mainRow.startedAt === "number" ? Math.max(0, Date.now() - mainRow.startedAt) : null,
|
|
8919
|
+
launchedAt: null,
|
|
8920
|
+
lastVerifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8921
|
+
lastStatus: stillRunning ? "failed" : "stopped",
|
|
8922
|
+
lastError: stillRunning ? "stop-timeout" : null
|
|
8923
|
+
};
|
|
8924
|
+
return {
|
|
8925
|
+
status: stillRunning ? "failed" : "stopped",
|
|
8926
|
+
profileName,
|
|
8927
|
+
instance,
|
|
8928
|
+
reason: stillRunning ? "stop-timeout" : null
|
|
8929
|
+
};
|
|
8930
|
+
});
|
|
8931
|
+
}
|
|
8932
|
+
async function restartOfficialCodexProfileInstance(options) {
|
|
8933
|
+
const stopped = await stopOfficialCodexProfileInstance(options.profileName);
|
|
8934
|
+
if (stopped.status === "failed") {
|
|
8935
|
+
return stopped;
|
|
8936
|
+
}
|
|
8937
|
+
const launched = await launchOfficialCodexProfileInstance(options);
|
|
8938
|
+
return {
|
|
8939
|
+
...launched,
|
|
8940
|
+
status: launched.status === "started" ? "restarted" : launched.status
|
|
8941
|
+
};
|
|
8942
|
+
}
|
|
8943
|
+
|
|
8944
|
+
// src/commands/profile.ts
|
|
8945
|
+
function formatProfileLabel(name, displayName) {
|
|
8946
|
+
if (displayName && displayName.trim() && displayName !== name) {
|
|
8947
|
+
return `${displayName} (${name})`;
|
|
8948
|
+
}
|
|
8949
|
+
return name;
|
|
8950
|
+
}
|
|
8951
|
+
function profileRateLimitKey(profile) {
|
|
8952
|
+
return resolveProfileIdentityKeyFromProfile(profile);
|
|
8953
|
+
}
|
|
8954
|
+
function toTitleCase(value) {
|
|
8955
|
+
return value.replace(/\w\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
|
|
8956
|
+
}
|
|
8957
|
+
function formatShortDate(value) {
|
|
8958
|
+
if (!value) return null;
|
|
8959
|
+
const date = new Date(value);
|
|
8960
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
8961
|
+
if (typeof Intl !== "undefined" && Intl.DateTimeFormat) {
|
|
8962
|
+
return new Intl.DateTimeFormat(void 0, { month: "short", day: "numeric" }).format(date);
|
|
8963
|
+
}
|
|
8964
|
+
return date.toLocaleDateString();
|
|
8965
|
+
}
|
|
8966
|
+
function formatRelativeTime(value) {
|
|
8967
|
+
if (!value) return null;
|
|
8968
|
+
const parsed = Date.parse(value);
|
|
8969
|
+
if (Number.isNaN(parsed)) return null;
|
|
8970
|
+
const diffMs = parsed - Date.now();
|
|
8971
|
+
const absMs = Math.abs(diffMs);
|
|
8972
|
+
if (absMs < 6e4) {
|
|
8973
|
+
return null;
|
|
8974
|
+
}
|
|
8975
|
+
const minutes = Math.round(absMs / 6e4);
|
|
8976
|
+
if (minutes < 60) {
|
|
8977
|
+
return diffMs >= 0 ? `in ${minutes}m` : `${minutes}m ago`;
|
|
8978
|
+
}
|
|
8979
|
+
const hours = Math.round(minutes / 60);
|
|
8980
|
+
if (hours < 24) {
|
|
8981
|
+
return diffMs >= 0 ? `in ${hours}h` : `${hours}h ago`;
|
|
8982
|
+
}
|
|
8983
|
+
const days = Math.round(hours / 24);
|
|
8984
|
+
return diffMs >= 0 ? `in ${days}d` : `${days}d ago`;
|
|
8985
|
+
}
|
|
8986
|
+
function formatOrganizations(organizations) {
|
|
8987
|
+
if (!organizations || !Array.isArray(organizations) || organizations.length === 0) {
|
|
8988
|
+
return null;
|
|
8989
|
+
}
|
|
8990
|
+
const parts = organizations.map((org) => {
|
|
8991
|
+
if (!org) return null;
|
|
8992
|
+
const name = org.title || org.id;
|
|
8993
|
+
if (!name) return null;
|
|
8994
|
+
return org.role ? `${name} (${org.role})` : name;
|
|
8995
|
+
}).filter((value) => Boolean(value));
|
|
8996
|
+
return parts.length > 0 ? parts.join(", ") : null;
|
|
8997
|
+
}
|
|
8998
|
+
function formatWindowLabel(windowMinutes) {
|
|
8999
|
+
if (typeof windowMinutes !== "number" || Number.isNaN(windowMinutes)) {
|
|
9000
|
+
return null;
|
|
9001
|
+
}
|
|
9002
|
+
if (windowMinutes >= 2880) {
|
|
9003
|
+
const days = windowMinutes / 1440;
|
|
9004
|
+
const rounded = Math.round(days * 10) / 10;
|
|
9005
|
+
const display = Number.isInteger(rounded) ? rounded.toFixed(0) : rounded.toFixed(1);
|
|
9006
|
+
return `${display.replace(/\.0$/, "")}d window`;
|
|
9007
|
+
}
|
|
9008
|
+
if (windowMinutes >= 60) {
|
|
9009
|
+
const hours = windowMinutes / 60;
|
|
9010
|
+
const rounded = Math.round(hours * 10) / 10;
|
|
9011
|
+
const display = Number.isInteger(rounded) ? rounded.toFixed(0) : rounded.toFixed(1);
|
|
9012
|
+
return `${display.replace(/\.0$/, "")}h window`;
|
|
9013
|
+
}
|
|
9014
|
+
const minutes = Math.round(windowMinutes);
|
|
7768
9015
|
return `${minutes}m window`;
|
|
7769
9016
|
}
|
|
7770
9017
|
function formatDurationFromSeconds(seconds) {
|
|
@@ -7812,6 +9059,7 @@ function formatWindowUsage(window) {
|
|
|
7812
9059
|
var EMPTY_USAGE_SUMMARY = {
|
|
7813
9060
|
windowKey: "primary",
|
|
7814
9061
|
usagePercent: 0,
|
|
9062
|
+
remainingPercent: 100,
|
|
7815
9063
|
hasUsage: false,
|
|
7816
9064
|
resetsInSeconds: null,
|
|
7817
9065
|
windowMinutes: null
|
|
@@ -7835,6 +9083,7 @@ function summarizeRateLimitSnapshot(snapshot) {
|
|
|
7835
9083
|
}
|
|
7836
9084
|
const usageValueRaw = toFiniteNumberOrNull(entry.window.usedPercent);
|
|
7837
9085
|
const usageValue = usageValueRaw === null ? 0 : Math.max(0, Math.min(100, usageValueRaw));
|
|
9086
|
+
const remainingValue = 100 - usageValue;
|
|
7838
9087
|
const hasUsage = usageValueRaw !== null;
|
|
7839
9088
|
const resetsInSeconds = toFiniteNumberOrNull(entry.window.resetsInSeconds);
|
|
7840
9089
|
const windowMinutes = toFiniteNumberOrNull(entry.window.windowMinutes);
|
|
@@ -7843,6 +9092,7 @@ function summarizeRateLimitSnapshot(snapshot) {
|
|
|
7843
9092
|
summary = {
|
|
7844
9093
|
windowKey: entry.key,
|
|
7845
9094
|
usagePercent: usageValue,
|
|
9095
|
+
remainingPercent: remainingValue,
|
|
7846
9096
|
hasUsage,
|
|
7847
9097
|
resetsInSeconds,
|
|
7848
9098
|
windowMinutes
|
|
@@ -7853,6 +9103,7 @@ function summarizeRateLimitSnapshot(snapshot) {
|
|
|
7853
9103
|
summary = {
|
|
7854
9104
|
windowKey: entry.key,
|
|
7855
9105
|
usagePercent: usageValue,
|
|
9106
|
+
remainingPercent: remainingValue,
|
|
7856
9107
|
hasUsage,
|
|
7857
9108
|
resetsInSeconds,
|
|
7858
9109
|
windowMinutes
|
|
@@ -7864,6 +9115,7 @@ function summarizeRateLimitSnapshot(snapshot) {
|
|
|
7864
9115
|
summary = {
|
|
7865
9116
|
windowKey: entry.key,
|
|
7866
9117
|
usagePercent: usageValue,
|
|
9118
|
+
remainingPercent: remainingValue,
|
|
7867
9119
|
hasUsage,
|
|
7868
9120
|
resetsInSeconds,
|
|
7869
9121
|
windowMinutes
|
|
@@ -7875,6 +9127,7 @@ function summarizeRateLimitSnapshot(snapshot) {
|
|
|
7875
9127
|
summary = {
|
|
7876
9128
|
windowKey: entry.key,
|
|
7877
9129
|
usagePercent: usageValue,
|
|
9130
|
+
remainingPercent: remainingValue,
|
|
7878
9131
|
hasUsage,
|
|
7879
9132
|
resetsInSeconds,
|
|
7880
9133
|
windowMinutes
|
|
@@ -7885,6 +9138,7 @@ function summarizeRateLimitSnapshot(snapshot) {
|
|
|
7885
9138
|
summary = {
|
|
7886
9139
|
windowKey: entry.key,
|
|
7887
9140
|
usagePercent: usageValue,
|
|
9141
|
+
remainingPercent: remainingValue,
|
|
7888
9142
|
hasUsage,
|
|
7889
9143
|
resetsInSeconds,
|
|
7890
9144
|
windowMinutes
|
|
@@ -7929,9 +9183,16 @@ async function resolveProfileUsage(manager, profile, codexPath) {
|
|
|
7929
9183
|
return fallbackUsage(profile);
|
|
7930
9184
|
}
|
|
7931
9185
|
}
|
|
7932
|
-
function compareAutoRollCandidates(a, b) {
|
|
7933
|
-
|
|
7934
|
-
|
|
9186
|
+
function compareAutoRollCandidates(a, b, priorityRanks) {
|
|
9187
|
+
const aRank = priorityRanks.get(resolveProfileIdentityKeyFromProfile(a.profile));
|
|
9188
|
+
const bRank = priorityRanks.get(resolveProfileIdentityKeyFromProfile(b.profile));
|
|
9189
|
+
if (aRank !== void 0 || bRank !== void 0) {
|
|
9190
|
+
if (aRank === void 0) return 1;
|
|
9191
|
+
if (bRank === void 0) return -1;
|
|
9192
|
+
if (aRank !== bRank) return aRank - bRank;
|
|
9193
|
+
}
|
|
9194
|
+
if (a.usageSummary.remainingPercent !== b.usageSummary.remainingPercent) {
|
|
9195
|
+
return b.usageSummary.remainingPercent - a.usageSummary.remainingPercent;
|
|
7935
9196
|
}
|
|
7936
9197
|
const aReset = a.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
|
|
7937
9198
|
const bReset = b.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
|
|
@@ -7940,37 +9201,38 @@ function compareAutoRollCandidates(a, b) {
|
|
|
7940
9201
|
}
|
|
7941
9202
|
return a.profile.name.localeCompare(b.profile.name);
|
|
7942
9203
|
}
|
|
7943
|
-
function computeAutoRollCandidate(profiles, usageMap, currentProfileName, currentSummary,
|
|
7944
|
-
const
|
|
9204
|
+
function computeAutoRollCandidate(profiles, usageMap, currentProfileName, currentSummary, switchRemainingThreshold, priorityOrder = [], blockedProfileNames = /* @__PURE__ */ new Set()) {
|
|
9205
|
+
const priorityRanks = /* @__PURE__ */ new Map();
|
|
9206
|
+
for (const [index, key] of priorityOrder.entries()) {
|
|
9207
|
+
const normalized = key.trim();
|
|
9208
|
+
if (normalized && !priorityRanks.has(normalized)) {
|
|
9209
|
+
priorityRanks.set(normalized, index);
|
|
9210
|
+
}
|
|
9211
|
+
}
|
|
9212
|
+
const candidates = profiles.filter(
|
|
9213
|
+
(profile) => profile.name !== currentProfileName && !blockedProfileNames.has(profile.name) && profile.isValid && !profile.tokenStatus?.requiresUserAction
|
|
9214
|
+
).map((profile) => ({
|
|
7945
9215
|
profile,
|
|
7946
9216
|
usageSummary: usageMap.get(profile.name)?.usageSummary ?? summarizeRateLimitSnapshot(profile.rateLimit ?? null)
|
|
7947
9217
|
}));
|
|
7948
9218
|
if (candidates.length === 0) {
|
|
7949
9219
|
return null;
|
|
7950
9220
|
}
|
|
7951
|
-
const belowThreshold = candidates.filter(
|
|
9221
|
+
const belowThreshold = candidates.filter(
|
|
9222
|
+
(candidate) => candidate.usageSummary.hasUsage && candidate.usageSummary.remainingPercent > switchRemainingThreshold
|
|
9223
|
+
).sort((a, b) => compareAutoRollCandidates(a, b, priorityRanks));
|
|
7952
9224
|
let selected = belowThreshold[0] ?? null;
|
|
7953
9225
|
if (!selected) {
|
|
7954
|
-
selected = candidates.filter((candidate) => candidate.usageSummary.hasUsage).sort(compareAutoRollCandidates)[0] ?? null;
|
|
7955
|
-
}
|
|
7956
|
-
if (!selected) {
|
|
7957
|
-
selected = candidates.filter((candidate) => !candidate.usageSummary.hasUsage).sort((a, b) => {
|
|
7958
|
-
const aReset = a.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
|
|
7959
|
-
const bReset = b.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
|
|
7960
|
-
if (aReset !== bReset) {
|
|
7961
|
-
return aReset - bReset;
|
|
7962
|
-
}
|
|
7963
|
-
return a.profile.name.localeCompare(b.profile.name);
|
|
7964
|
-
})[0] ?? null;
|
|
9226
|
+
selected = candidates.filter((candidate) => !candidate.usageSummary.hasUsage).sort((a, b) => compareAutoRollCandidates(a, b, priorityRanks))[0] ?? null;
|
|
7965
9227
|
}
|
|
7966
9228
|
if (!selected) {
|
|
7967
9229
|
return null;
|
|
7968
9230
|
}
|
|
7969
9231
|
if (selected.usageSummary.hasUsage && currentSummary.hasUsage) {
|
|
7970
|
-
if (selected.usageSummary.
|
|
9232
|
+
if (selected.usageSummary.remainingPercent < currentSummary.remainingPercent) {
|
|
7971
9233
|
return null;
|
|
7972
9234
|
}
|
|
7973
|
-
if (selected.usageSummary.
|
|
9235
|
+
if (selected.usageSummary.remainingPercent === currentSummary.remainingPercent) {
|
|
7974
9236
|
const selectedReset = selected.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
|
|
7975
9237
|
const currentReset = currentSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
|
|
7976
9238
|
if (selectedReset >= currentReset) {
|
|
@@ -8054,18 +9316,31 @@ async function mapWithConcurrency(items, limit, fn) {
|
|
|
8054
9316
|
await Promise.all(workers);
|
|
8055
9317
|
return results;
|
|
8056
9318
|
}
|
|
8057
|
-
function
|
|
8058
|
-
const
|
|
8059
|
-
|
|
8060
|
-
|
|
9319
|
+
function parseAutoRollSwitchRemainingThreshold(flags, fallbackThreshold) {
|
|
9320
|
+
const explicitLeft = parseNumericFlag(flags, "--switch-left");
|
|
9321
|
+
const explicitLegacyUsed = parseNumericFlag(flags, "--threshold");
|
|
9322
|
+
if (explicitLeft !== null && explicitLegacyUsed !== null) {
|
|
9323
|
+
throw new Error("Use either --switch-left or legacy --threshold, not both.");
|
|
8061
9324
|
}
|
|
8062
|
-
if (
|
|
8063
|
-
|
|
9325
|
+
if (explicitLeft !== null) {
|
|
9326
|
+
if (Number.isNaN(explicitLeft)) {
|
|
9327
|
+
throw new Error("Invalid --switch-left value. Use --switch-left=0-50.");
|
|
9328
|
+
}
|
|
9329
|
+
if (explicitLeft < AUTO_ROLL_SWITCH_REMAINING_MIN || explicitLeft > AUTO_ROLL_SWITCH_REMAINING_MAX) {
|
|
9330
|
+
throw new Error("Invalid --switch-left value. Use a number between 0 and 50.");
|
|
9331
|
+
}
|
|
9332
|
+
return sanitizeAutoRollSwitchRemainingThreshold(explicitLeft);
|
|
8064
9333
|
}
|
|
8065
|
-
if (
|
|
8066
|
-
|
|
9334
|
+
if (explicitLegacyUsed !== null) {
|
|
9335
|
+
if (Number.isNaN(explicitLegacyUsed)) {
|
|
9336
|
+
throw new Error("Invalid --threshold value. Use --threshold=50-100.");
|
|
9337
|
+
}
|
|
9338
|
+
if (explicitLegacyUsed < 50 || explicitLegacyUsed > 100) {
|
|
9339
|
+
throw new Error("Invalid --threshold value. Use a number between 50 and 100.");
|
|
9340
|
+
}
|
|
9341
|
+
return sanitizeAutoRollSwitchRemainingThreshold(100 - explicitLegacyUsed);
|
|
8067
9342
|
}
|
|
8068
|
-
return
|
|
9343
|
+
return sanitizeAutoRollSwitchRemainingThreshold(fallbackThreshold);
|
|
8069
9344
|
}
|
|
8070
9345
|
function parseAutoRollIntervalSeconds(flags) {
|
|
8071
9346
|
const explicitInterval = parseIntegerFlag(flags, "--interval");
|
|
@@ -8077,6 +9352,231 @@ function parseAutoRollIntervalSeconds(flags) {
|
|
|
8077
9352
|
}
|
|
8078
9353
|
return explicitInterval;
|
|
8079
9354
|
}
|
|
9355
|
+
function resolveRestartCodexFlag(flags, fallback) {
|
|
9356
|
+
const restart = hasFlag(flags, "--restart-codex");
|
|
9357
|
+
const noRestart = hasFlag(flags, "--no-restart-codex");
|
|
9358
|
+
if (restart && noRestart) {
|
|
9359
|
+
throw new Error("Use either --restart-codex or --no-restart-codex, not both.");
|
|
9360
|
+
}
|
|
9361
|
+
if (restart) {
|
|
9362
|
+
return true;
|
|
9363
|
+
}
|
|
9364
|
+
if (noRestart) {
|
|
9365
|
+
return false;
|
|
9366
|
+
}
|
|
9367
|
+
return fallback;
|
|
9368
|
+
}
|
|
9369
|
+
function findProfileKey(profiles, name) {
|
|
9370
|
+
const profile = profiles.find((entry) => entry.name === name);
|
|
9371
|
+
return profile ? profileRateLimitKey(profile) : null;
|
|
9372
|
+
}
|
|
9373
|
+
function rearmBlockedProfiles(blockedProfiles, usageMap, rearmRemainingThreshold) {
|
|
9374
|
+
if (!blockedProfiles) {
|
|
9375
|
+
return;
|
|
9376
|
+
}
|
|
9377
|
+
for (const profileName of Array.from(blockedProfiles)) {
|
|
9378
|
+
const summary = usageMap.get(profileName)?.usageSummary;
|
|
9379
|
+
if (!summary || !summary.hasUsage || summary.remainingPercent > rearmRemainingThreshold) {
|
|
9380
|
+
blockedProfiles.delete(profileName);
|
|
9381
|
+
}
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9384
|
+
async function rememberOfficialCodexSwitch(profileKey) {
|
|
9385
|
+
try {
|
|
9386
|
+
await recordOfficialCodexProfileSwitch(profileKey);
|
|
9387
|
+
} catch (error) {
|
|
9388
|
+
console.warn(
|
|
9389
|
+
`Could not record official Codex sync state: ${error instanceof Error ? error.message : String(error)}`
|
|
9390
|
+
);
|
|
9391
|
+
}
|
|
9392
|
+
}
|
|
9393
|
+
function formatOfficialCodexProfileAction(result) {
|
|
9394
|
+
if (result.status === "restarted") {
|
|
9395
|
+
return "Official Codex restarted.";
|
|
9396
|
+
}
|
|
9397
|
+
if (result.status === "started") {
|
|
9398
|
+
return "Official Codex started.";
|
|
9399
|
+
}
|
|
9400
|
+
if (result.status === "already-running") {
|
|
9401
|
+
return "Official Codex already running.";
|
|
9402
|
+
}
|
|
9403
|
+
if (result.status === "failed") {
|
|
9404
|
+
return `Official Codex restart failed: ${result.reason}.`;
|
|
9405
|
+
}
|
|
9406
|
+
if (result.status === "skipped" || result.status === "not-running") {
|
|
9407
|
+
return `Official Codex restart skipped: ${result.reason}.`;
|
|
9408
|
+
}
|
|
9409
|
+
return "Official Codex restart status unknown.";
|
|
9410
|
+
}
|
|
9411
|
+
function findUnmanagedOfficialCodexRestartTarget(instances, profileName, profileKey) {
|
|
9412
|
+
const restartable = instances.unmanaged.filter((instance) => instance.pid);
|
|
9413
|
+
const matched = restartable.find((instance) => {
|
|
9414
|
+
if (profileKey && instance.profileKey) {
|
|
9415
|
+
return instance.profileKey === profileKey;
|
|
9416
|
+
}
|
|
9417
|
+
if (instance.profileKey) {
|
|
9418
|
+
return false;
|
|
9419
|
+
}
|
|
9420
|
+
return instance.profileName === profileName;
|
|
9421
|
+
}) ?? null;
|
|
9422
|
+
if (matched) {
|
|
9423
|
+
return { target: matched, ambiguous: false, unverified: false };
|
|
9424
|
+
}
|
|
9425
|
+
if (restartable.length > 1) {
|
|
9426
|
+
return { target: null, ambiguous: true, unverified: false };
|
|
9427
|
+
}
|
|
9428
|
+
return { target: null, ambiguous: false, unverified: restartable.length === 1 };
|
|
9429
|
+
}
|
|
9430
|
+
async function restartUnmanagedOfficialCodexWindow(profileName, launchOptions, target) {
|
|
9431
|
+
const stopped = await stopOfficialCodexObservedProfileInstance(
|
|
9432
|
+
target.profileName ?? profileName,
|
|
9433
|
+
target.pid,
|
|
9434
|
+
target.appServerPid
|
|
9435
|
+
);
|
|
9436
|
+
if (stopped.status === "failed") {
|
|
9437
|
+
return stopped;
|
|
9438
|
+
}
|
|
9439
|
+
const launched = await launchOfficialCodexProfileInstance(launchOptions);
|
|
9440
|
+
return {
|
|
9441
|
+
...launched,
|
|
9442
|
+
status: stopped.status === "stopped" && launched.status === "started" ? "restarted" : launched.status
|
|
9443
|
+
};
|
|
9444
|
+
}
|
|
9445
|
+
async function recordCliOfficialCodexRestartResult(profileName, profileKey, action) {
|
|
9446
|
+
const pid = action.status === "started" || action.status === "restarted" ? action.instance?.pid ?? null : null;
|
|
9447
|
+
const status = action.status === "started" || action.status === "restarted" ? action.status : action.status === "failed" ? "failed" : "skipped";
|
|
9448
|
+
const reason = action.status === "started" || action.status === "restarted" ? null : action.reason ?? action.status;
|
|
9449
|
+
try {
|
|
9450
|
+
await recordOfficialCodexRestartResult({
|
|
9451
|
+
status,
|
|
9452
|
+
reason,
|
|
9453
|
+
profileKey,
|
|
9454
|
+
pid
|
|
9455
|
+
});
|
|
9456
|
+
await recordOfficialCodexActivity({
|
|
9457
|
+
kind: "official-codex-restart",
|
|
9458
|
+
status,
|
|
9459
|
+
reason,
|
|
9460
|
+
targetProfileName: profileName,
|
|
9461
|
+
profileKey,
|
|
9462
|
+
phase: "cli",
|
|
9463
|
+
pid,
|
|
9464
|
+
restartRequested: true,
|
|
9465
|
+
restartResult: status
|
|
9466
|
+
});
|
|
9467
|
+
} catch (error) {
|
|
9468
|
+
console.warn(
|
|
9469
|
+
`Could not record official Codex restart state: ${error instanceof Error ? error.message : String(error)}`
|
|
9470
|
+
);
|
|
9471
|
+
}
|
|
9472
|
+
}
|
|
9473
|
+
async function recordCliOfficialCodexRestartSkipped(profileName, profileKey, reason) {
|
|
9474
|
+
await recordCliOfficialCodexRestartResult(profileName, profileKey, {
|
|
9475
|
+
status: "skipped",
|
|
9476
|
+
profileName,
|
|
9477
|
+
instance: null,
|
|
9478
|
+
reason
|
|
9479
|
+
});
|
|
9480
|
+
}
|
|
9481
|
+
async function maybeRestartOfficialCodex(options) {
|
|
9482
|
+
if (!options.enabled) {
|
|
9483
|
+
return null;
|
|
9484
|
+
}
|
|
9485
|
+
try {
|
|
9486
|
+
const runtime = await options.manager.prepareProfileRuntime(options.profileName);
|
|
9487
|
+
const launchOptions = {
|
|
9488
|
+
profileName: options.profileName,
|
|
9489
|
+
profileKey: options.profileKey,
|
|
9490
|
+
profileHome: runtime.profileHome
|
|
9491
|
+
};
|
|
9492
|
+
const instances = await getOfficialCodexProfileInstances();
|
|
9493
|
+
if (instances.state === "unsupported") {
|
|
9494
|
+
await recordCliOfficialCodexRestartSkipped(
|
|
9495
|
+
options.profileName,
|
|
9496
|
+
options.profileKey,
|
|
9497
|
+
"unsupported-platform"
|
|
9498
|
+
);
|
|
9499
|
+
return "Official Codex restart skipped: unsupported-platform.";
|
|
9500
|
+
}
|
|
9501
|
+
if (instances.state === "not-found") {
|
|
9502
|
+
await recordCliOfficialCodexRestartSkipped(
|
|
9503
|
+
options.profileName,
|
|
9504
|
+
options.profileKey,
|
|
9505
|
+
"official-codex-app-not-found"
|
|
9506
|
+
);
|
|
9507
|
+
return "Official Codex restart skipped: official-codex-app-not-found.";
|
|
9508
|
+
}
|
|
9509
|
+
const managedRunning = instances.instances.some(
|
|
9510
|
+
(instance) => instance.profileName === options.profileName && instance.running
|
|
9511
|
+
);
|
|
9512
|
+
const unmanaged = findUnmanagedOfficialCodexRestartTarget(
|
|
9513
|
+
instances,
|
|
9514
|
+
options.profileName,
|
|
9515
|
+
options.profileKey
|
|
9516
|
+
);
|
|
9517
|
+
if (unmanaged.target && !managedRunning) {
|
|
9518
|
+
const action2 = await restartUnmanagedOfficialCodexWindow(
|
|
9519
|
+
options.profileName,
|
|
9520
|
+
launchOptions,
|
|
9521
|
+
unmanaged.target
|
|
9522
|
+
);
|
|
9523
|
+
await recordCliOfficialCodexRestartResult(
|
|
9524
|
+
options.profileName,
|
|
9525
|
+
options.profileKey,
|
|
9526
|
+
action2
|
|
9527
|
+
);
|
|
9528
|
+
return formatOfficialCodexProfileAction(action2);
|
|
9529
|
+
}
|
|
9530
|
+
if (unmanaged.ambiguous && !managedRunning) {
|
|
9531
|
+
await recordCliOfficialCodexRestartSkipped(
|
|
9532
|
+
options.profileName,
|
|
9533
|
+
options.profileKey,
|
|
9534
|
+
"ambiguous-official-codex-windows"
|
|
9535
|
+
);
|
|
9536
|
+
return "Official Codex restart skipped: ambiguous-official-codex-windows.";
|
|
9537
|
+
}
|
|
9538
|
+
if (unmanaged.unverified && !managedRunning) {
|
|
9539
|
+
await recordCliOfficialCodexRestartSkipped(
|
|
9540
|
+
options.profileName,
|
|
9541
|
+
options.profileKey,
|
|
9542
|
+
"unverified-official-codex-window"
|
|
9543
|
+
);
|
|
9544
|
+
return "Official Codex restart skipped: unverified-official-codex-window.";
|
|
9545
|
+
}
|
|
9546
|
+
if (options.launchIfNotRunning === false) {
|
|
9547
|
+
if (!managedRunning) {
|
|
9548
|
+
await recordCliOfficialCodexRestartSkipped(
|
|
9549
|
+
options.profileName,
|
|
9550
|
+
options.profileKey,
|
|
9551
|
+
"official-codex-app-not-running"
|
|
9552
|
+
);
|
|
9553
|
+
return "Official Codex restart skipped: official-codex-app-not-running.";
|
|
9554
|
+
}
|
|
9555
|
+
const action2 = await restartOfficialCodexProfileInstance(launchOptions);
|
|
9556
|
+
await recordCliOfficialCodexRestartResult(
|
|
9557
|
+
options.profileName,
|
|
9558
|
+
options.profileKey,
|
|
9559
|
+
action2
|
|
9560
|
+
);
|
|
9561
|
+
return formatOfficialCodexProfileAction(action2);
|
|
9562
|
+
}
|
|
9563
|
+
const action = await restartOfficialCodexProfileInstance(launchOptions);
|
|
9564
|
+
await recordCliOfficialCodexRestartResult(
|
|
9565
|
+
options.profileName,
|
|
9566
|
+
options.profileKey,
|
|
9567
|
+
action
|
|
9568
|
+
);
|
|
9569
|
+
return formatOfficialCodexProfileAction(action);
|
|
9570
|
+
} catch (error) {
|
|
9571
|
+
await recordCliOfficialCodexRestartResult(options.profileName, options.profileKey, {
|
|
9572
|
+
status: "failed",
|
|
9573
|
+
profileName: options.profileName,
|
|
9574
|
+
instance: null,
|
|
9575
|
+
reason: error instanceof Error ? error.message : "restart-threw"
|
|
9576
|
+
});
|
|
9577
|
+
return `Official Codex restart failed: ${error instanceof Error ? error.message : String(error)}.`;
|
|
9578
|
+
}
|
|
9579
|
+
}
|
|
8080
9580
|
async function collectUsageMap(manager, profiles, codexPath) {
|
|
8081
9581
|
const usageMap = /* @__PURE__ */ new Map();
|
|
8082
9582
|
const uniqueProfilesByAccount = /* @__PURE__ */ new Map();
|
|
@@ -8126,6 +9626,11 @@ async function runAutoRollPass(manager, options) {
|
|
|
8126
9626
|
throw new Error("Codex CLI binary not found on PATH. Auto-roll requires codex for live rate limits.");
|
|
8127
9627
|
}
|
|
8128
9628
|
const usageMap = await collectUsageMap(manager, profiles, codexPath);
|
|
9629
|
+
rearmBlockedProfiles(
|
|
9630
|
+
options.blockedProfiles,
|
|
9631
|
+
usageMap,
|
|
9632
|
+
options.rearmRemainingThreshold
|
|
9633
|
+
);
|
|
8129
9634
|
const currentProfile = profiles.find((profile) => profile.name === current.name);
|
|
8130
9635
|
if (!currentProfile) {
|
|
8131
9636
|
return {
|
|
@@ -8144,10 +9649,31 @@ async function runAutoRollPass(manager, options) {
|
|
|
8144
9649
|
target: null
|
|
8145
9650
|
};
|
|
8146
9651
|
}
|
|
8147
|
-
if (
|
|
9652
|
+
if (options.blockedProfiles?.has(current.name)) {
|
|
9653
|
+
if (currentUsage.remainingPercent > options.rearmRemainingThreshold) {
|
|
9654
|
+
options.blockedProfiles.delete(current.name);
|
|
9655
|
+
} else {
|
|
9656
|
+
return {
|
|
9657
|
+
switched: false,
|
|
9658
|
+
message: `'${current.name}' at ${Math.round(currentUsage.remainingPercent)}% left; waiting for re-arm above ${options.rearmRemainingThreshold}% left.`,
|
|
9659
|
+
source: current.name,
|
|
9660
|
+
target: null
|
|
9661
|
+
};
|
|
9662
|
+
}
|
|
9663
|
+
}
|
|
9664
|
+
if (options.blockCurrentUntilRearm && currentUsage.remainingPercent <= options.switchRemainingThreshold) {
|
|
9665
|
+
options.blockedProfiles?.add(current.name);
|
|
9666
|
+
return {
|
|
9667
|
+
switched: false,
|
|
9668
|
+
message: `'${current.name}' at ${Math.round(currentUsage.remainingPercent)}% left; waiting for re-arm above ${options.rearmRemainingThreshold}% left.`,
|
|
9669
|
+
source: current.name,
|
|
9670
|
+
target: null
|
|
9671
|
+
};
|
|
9672
|
+
}
|
|
9673
|
+
if (currentUsage.remainingPercent > options.switchRemainingThreshold) {
|
|
8148
9674
|
return {
|
|
8149
9675
|
switched: false,
|
|
8150
|
-
message: `'${current.name}' at ${Math.round(currentUsage.
|
|
9676
|
+
message: `'${current.name}' at ${Math.round(currentUsage.remainingPercent)}% left, above switch threshold ${options.switchRemainingThreshold}% left.`,
|
|
8151
9677
|
source: current.name,
|
|
8152
9678
|
target: null
|
|
8153
9679
|
};
|
|
@@ -8157,29 +9683,42 @@ async function runAutoRollPass(manager, options) {
|
|
|
8157
9683
|
usageMap,
|
|
8158
9684
|
current.name,
|
|
8159
9685
|
currentUsage,
|
|
8160
|
-
options.
|
|
9686
|
+
options.switchRemainingThreshold,
|
|
9687
|
+
options.priorityOrder ?? [],
|
|
9688
|
+
options.blockedProfiles
|
|
8161
9689
|
);
|
|
8162
9690
|
if (!candidate) {
|
|
8163
9691
|
return {
|
|
8164
9692
|
switched: false,
|
|
8165
|
-
message: `No eligible profile to switch from '${current.name}' at ${Math.round(currentUsage.
|
|
9693
|
+
message: `No eligible profile to switch from '${current.name}' at ${Math.round(currentUsage.remainingPercent)}% left.`,
|
|
8166
9694
|
source: current.name,
|
|
8167
9695
|
target: null
|
|
8168
9696
|
};
|
|
8169
9697
|
}
|
|
8170
|
-
const candidateUsage = candidate.usageSummary.hasUsage ? `${Math.round(candidate.usageSummary.
|
|
9698
|
+
const candidateUsage = candidate.usageSummary.hasUsage ? `${Math.round(candidate.usageSummary.remainingPercent)}% left` : "n/a";
|
|
8171
9699
|
if (options.dryRun) {
|
|
9700
|
+
const restartSuffix = options.restartOfficialCodex ? " Official Codex would restart after the switch if running." : "";
|
|
8172
9701
|
return {
|
|
8173
9702
|
switched: false,
|
|
8174
|
-
message: `[dry-run] Would switch '${current.name}' (${Math.round(currentUsage.
|
|
9703
|
+
message: `[dry-run] Would switch '${current.name}' (${Math.round(currentUsage.remainingPercent)}% left) -> '${candidate.profile.name}' (${candidateUsage}).${restartSuffix}`,
|
|
8175
9704
|
source: current.name,
|
|
8176
9705
|
target: candidate.profile.name
|
|
8177
9706
|
};
|
|
8178
9707
|
}
|
|
8179
9708
|
await manager.switchToProfile(candidate.profile.name);
|
|
9709
|
+
const targetProfileKey = profileRateLimitKey(candidate.profile);
|
|
9710
|
+
await rememberOfficialCodexSwitch(targetProfileKey);
|
|
9711
|
+
const restartMessage = await maybeRestartOfficialCodex({
|
|
9712
|
+
manager,
|
|
9713
|
+
enabled: options.restartOfficialCodex,
|
|
9714
|
+
profileName: candidate.profile.name,
|
|
9715
|
+
profileKey: targetProfileKey,
|
|
9716
|
+
launchIfNotRunning: options.launchOfficialCodexWhenClosed === true
|
|
9717
|
+
});
|
|
9718
|
+
options.blockedProfiles?.add(current.name);
|
|
8180
9719
|
return {
|
|
8181
9720
|
switched: true,
|
|
8182
|
-
message: `Auto-rolled '${current.name}' (${Math.round(currentUsage.
|
|
9721
|
+
message: `Auto-rolled '${current.name}' (${Math.round(currentUsage.remainingPercent)}% left) -> '${candidate.profile.name}' (${candidateUsage}).${restartMessage ? ` ${restartMessage}` : ""}`,
|
|
8183
9722
|
source: current.name,
|
|
8184
9723
|
target: candidate.profile.name
|
|
8185
9724
|
};
|
|
@@ -8279,20 +9818,52 @@ async function handleProfileCommand(args, version) {
|
|
|
8279
9818
|
if (!name) {
|
|
8280
9819
|
throw new Error("Profile name is required.");
|
|
8281
9820
|
}
|
|
9821
|
+
const restartOfficialCodex = resolveRestartCodexFlag(flags, false);
|
|
9822
|
+
const profiles = await manager.listProfiles();
|
|
9823
|
+
const targetProfileKey = findProfileKey(profiles, name);
|
|
8282
9824
|
await manager.switchToProfile(name);
|
|
8283
9825
|
console.log(`Switched to profile: ${name}`);
|
|
9826
|
+
await rememberOfficialCodexSwitch(targetProfileKey);
|
|
9827
|
+
const restartMessage = await maybeRestartOfficialCodex({
|
|
9828
|
+
manager,
|
|
9829
|
+
enabled: restartOfficialCodex,
|
|
9830
|
+
profileName: name,
|
|
9831
|
+
profileKey: targetProfileKey
|
|
9832
|
+
});
|
|
9833
|
+
if (restartMessage) {
|
|
9834
|
+
console.log(restartMessage);
|
|
9835
|
+
}
|
|
8284
9836
|
return;
|
|
8285
9837
|
}
|
|
8286
9838
|
case "autoroll":
|
|
8287
9839
|
case "auto-roll": {
|
|
9840
|
+
const license = await licenseService.getStatus();
|
|
9841
|
+
if (!license.isPro) {
|
|
9842
|
+
throw new Error("Auto-roll requires a Pro license.");
|
|
9843
|
+
}
|
|
8288
9844
|
const watch = hasFlag(flags, "--watch");
|
|
8289
9845
|
const dryRun = hasFlag(flags, "--dry-run");
|
|
8290
9846
|
const settings = await getStoredAutoRollSettings().catch(() => null);
|
|
8291
|
-
const
|
|
8292
|
-
|
|
9847
|
+
const restartOfficialCodex = resolveRestartCodexFlag(
|
|
9848
|
+
flags,
|
|
9849
|
+
settings?.restartOfficialCodexOnAutoRoll === true
|
|
9850
|
+
);
|
|
9851
|
+
const fallbackSwitchRemainingThreshold = typeof settings?.switchRemainingThreshold === "number" && Number.isFinite(settings.switchRemainingThreshold) ? settings.switchRemainingThreshold : DEFAULT_AUTO_ROLL_SWITCH_REMAINING_THRESHOLD;
|
|
9852
|
+
const switchRemainingThreshold = parseAutoRollSwitchRemainingThreshold(
|
|
9853
|
+
flags,
|
|
9854
|
+
fallbackSwitchRemainingThreshold
|
|
9855
|
+
);
|
|
9856
|
+
const rearmRemainingThreshold = sanitizeAutoRollRearmRemainingThreshold(
|
|
9857
|
+
settings?.rearmRemainingThreshold ?? DEFAULT_AUTO_ROLL_REARM_REMAINING_THRESHOLD,
|
|
9858
|
+
switchRemainingThreshold
|
|
9859
|
+
);
|
|
8293
9860
|
const intervalSeconds = parseAutoRollIntervalSeconds(flags);
|
|
8294
9861
|
if (watch) {
|
|
8295
|
-
console.log(
|
|
9862
|
+
console.log(
|
|
9863
|
+
`Auto-roll watch: switch at/below ${switchRemainingThreshold}% left | re-arm above ${rearmRemainingThreshold}% left | interval ${intervalSeconds}s${dryRun ? " | dry-run" : ""}${restartOfficialCodex ? " | restart-codex" : ""}`
|
|
9864
|
+
);
|
|
9865
|
+
const blockedProfiles = /* @__PURE__ */ new Set();
|
|
9866
|
+
let lastObservedProfile = null;
|
|
8296
9867
|
let stop = false;
|
|
8297
9868
|
process.on("SIGINT", () => {
|
|
8298
9869
|
stop = true;
|
|
@@ -8301,8 +9872,23 @@ async function handleProfileCommand(args, version) {
|
|
|
8301
9872
|
stop = true;
|
|
8302
9873
|
});
|
|
8303
9874
|
while (!stop) {
|
|
8304
|
-
const
|
|
9875
|
+
const current = await manager.getCurrentProfile();
|
|
9876
|
+
const currentName = current.name ?? null;
|
|
9877
|
+
const blockCurrentUntilRearm = Boolean(
|
|
9878
|
+
currentName && lastObservedProfile && currentName !== lastObservedProfile
|
|
9879
|
+
);
|
|
9880
|
+
const result2 = await runAutoRollPass(manager, {
|
|
9881
|
+
switchRemainingThreshold,
|
|
9882
|
+
rearmRemainingThreshold,
|
|
9883
|
+
dryRun,
|
|
9884
|
+
priorityOrder: settings?.priorityOrder ?? [],
|
|
9885
|
+
restartOfficialCodex,
|
|
9886
|
+
launchOfficialCodexWhenClosed: settings?.launchOfficialCodexWhenClosedOnAutoRoll === true,
|
|
9887
|
+
blockedProfiles,
|
|
9888
|
+
blockCurrentUntilRearm
|
|
9889
|
+
});
|
|
8305
9890
|
console.log(result2.message);
|
|
9891
|
+
lastObservedProfile = result2.switched ? result2.target ?? currentName : currentName ?? result2.source;
|
|
8306
9892
|
if (stop) {
|
|
8307
9893
|
break;
|
|
8308
9894
|
}
|
|
@@ -8311,7 +9897,14 @@ async function handleProfileCommand(args, version) {
|
|
|
8311
9897
|
console.log("Auto-roll watch stopped.");
|
|
8312
9898
|
return;
|
|
8313
9899
|
}
|
|
8314
|
-
const result = await runAutoRollPass(manager, {
|
|
9900
|
+
const result = await runAutoRollPass(manager, {
|
|
9901
|
+
switchRemainingThreshold,
|
|
9902
|
+
rearmRemainingThreshold,
|
|
9903
|
+
dryRun,
|
|
9904
|
+
priorityOrder: settings?.priorityOrder ?? [],
|
|
9905
|
+
restartOfficialCodex,
|
|
9906
|
+
launchOfficialCodexWhenClosed: settings?.launchOfficialCodexWhenClosedOnAutoRoll === true
|
|
9907
|
+
});
|
|
8315
9908
|
console.log(result.message);
|
|
8316
9909
|
return;
|
|
8317
9910
|
}
|
|
@@ -8341,8 +9934,8 @@ async function handleProfileCommand(args, version) {
|
|
|
8341
9934
|
}
|
|
8342
9935
|
|
|
8343
9936
|
// ../../packages/runtime-profiles/src/cloud-sync/service.ts
|
|
8344
|
-
var
|
|
8345
|
-
var
|
|
9937
|
+
var import_node_fs9 = require("fs");
|
|
9938
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
8346
9939
|
|
|
8347
9940
|
// ../../packages/contracts/src/cloud-sync/types.ts
|
|
8348
9941
|
var CLOUD_SYNC_SCHEMA_VERSION = 1;
|
|
@@ -8502,9 +10095,9 @@ async function pushRemoteSnapshot(licenseKey, snapshot, options) {
|
|
|
8502
10095
|
var import_toml2 = __toESM(require_toml(), 1);
|
|
8503
10096
|
|
|
8504
10097
|
// ../../packages/runtime-codex/src/codex/config-metadata.ts
|
|
8505
|
-
var
|
|
10098
|
+
var import_node_child_process8 = require("child_process");
|
|
8506
10099
|
var import_promises = require("fs/promises");
|
|
8507
|
-
var
|
|
10100
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
8508
10101
|
var CONFIG_SCHEMA_URL = "https://raw.githubusercontent.com/openai/codex/main/codex-rs/core/config.schema.json";
|
|
8509
10102
|
var METADATA_CACHE_TTL_MS = 6e4;
|
|
8510
10103
|
var FALLBACK_SCHEMA_TOP_LEVEL_KEYS = [
|
|
@@ -8612,9 +10205,9 @@ function isRecord5(value) {
|
|
|
8612
10205
|
function uniqueSorted(values) {
|
|
8613
10206
|
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
8614
10207
|
}
|
|
8615
|
-
async function
|
|
10208
|
+
async function runCommand3(command, args, timeoutMs = 3e3) {
|
|
8616
10209
|
return new Promise((resolve, reject) => {
|
|
8617
|
-
const child = (0,
|
|
10210
|
+
const child = (0, import_node_child_process8.spawn)(command, args, {
|
|
8618
10211
|
stdio: ["ignore", "pipe", "pipe"],
|
|
8619
10212
|
env: process.env
|
|
8620
10213
|
});
|
|
@@ -8649,7 +10242,7 @@ async function runCodexCommand(args) {
|
|
|
8649
10242
|
const isJsLauncher = codexPath.endsWith(".js");
|
|
8650
10243
|
const command = isJsLauncher ? process.execPath : codexPath;
|
|
8651
10244
|
const commandArgs = isJsLauncher ? [codexPath, ...args] : args;
|
|
8652
|
-
return
|
|
10245
|
+
return runCommand3(command, commandArgs);
|
|
8653
10246
|
}
|
|
8654
10247
|
function parseFeatureCatalog(output) {
|
|
8655
10248
|
const entries = [];
|
|
@@ -8690,8 +10283,8 @@ function parseSchemaKeys(schemaRaw) {
|
|
|
8690
10283
|
async function readSchemaFromLocal() {
|
|
8691
10284
|
const candidates = [
|
|
8692
10285
|
process.env.CODEX_CONFIG_SCHEMA_PATH,
|
|
8693
|
-
|
|
8694
|
-
|
|
10286
|
+
import_node_path12.default.join(process.cwd(), "codex-rs", "core", "config.schema.json"),
|
|
10287
|
+
import_node_path12.default.join(process.cwd(), "node_modules", "@openai", "codex", "codex-rs", "core", "config.schema.json")
|
|
8695
10288
|
].filter((candidate) => Boolean(candidate));
|
|
8696
10289
|
for (const candidate of candidates) {
|
|
8697
10290
|
try {
|
|
@@ -8796,20 +10389,20 @@ async function getCodexConfigMetadata(forceRefresh = false) {
|
|
|
8796
10389
|
|
|
8797
10390
|
// ../../packages/runtime-codex/src/codex/config-io.ts
|
|
8798
10391
|
var import_promises2 = require("fs/promises");
|
|
8799
|
-
var
|
|
10392
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
8800
10393
|
|
|
8801
10394
|
// ../../packages/runtime-codex/src/codex/home.ts
|
|
8802
|
-
var
|
|
8803
|
-
var
|
|
10395
|
+
var import_node_os6 = __toESM(require("os"), 1);
|
|
10396
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
8804
10397
|
function resolveHomeDir() {
|
|
8805
|
-
return process.env.HOME || process.env.USERPROFILE ||
|
|
10398
|
+
return process.env.HOME || process.env.USERPROFILE || import_node_os6.default.homedir();
|
|
8806
10399
|
}
|
|
8807
10400
|
function expandHomePrefix(input, homeDir) {
|
|
8808
10401
|
if (input === "~") {
|
|
8809
10402
|
return homeDir;
|
|
8810
10403
|
}
|
|
8811
10404
|
if (input.startsWith("~/") || input.startsWith("~\\")) {
|
|
8812
|
-
return
|
|
10405
|
+
return import_node_path13.default.join(homeDir, input.slice(2));
|
|
8813
10406
|
}
|
|
8814
10407
|
return input;
|
|
8815
10408
|
}
|
|
@@ -8825,13 +10418,10 @@ function normalizeCodexHomePath(input) {
|
|
|
8825
10418
|
if (!configured) {
|
|
8826
10419
|
return null;
|
|
8827
10420
|
}
|
|
8828
|
-
return
|
|
10421
|
+
return import_node_path13.default.resolve(expandHomePrefix(configured, resolveHomeDir()));
|
|
8829
10422
|
}
|
|
8830
10423
|
function resolveDefaultCodexHomeDir() {
|
|
8831
|
-
return
|
|
8832
|
-
}
|
|
8833
|
-
function resolveProfileCodexHomeDir(profileName) {
|
|
8834
|
-
return import_node_path12.default.join(getUserDataDir(), "profile-homes", profileName);
|
|
10424
|
+
return import_node_path13.default.join(resolveHomeDir(), ".codex");
|
|
8835
10425
|
}
|
|
8836
10426
|
function resolveCodexHomeDir(options) {
|
|
8837
10427
|
return normalizeCodexHomePath(options?.codexHomePath) ?? resolveDefaultCodexHomeDir();
|
|
@@ -8846,14 +10436,6 @@ async function resolveCodexRuntimeContext(options) {
|
|
|
8846
10436
|
};
|
|
8847
10437
|
}
|
|
8848
10438
|
const state = await getAppState();
|
|
8849
|
-
const profileName = normalizePathCandidate(state.app.lastProfileName);
|
|
8850
|
-
if (profileName && state.profilesByName[profileName]) {
|
|
8851
|
-
return {
|
|
8852
|
-
codexHomePath: resolveProfileCodexHomeDir(profileName),
|
|
8853
|
-
profileName,
|
|
8854
|
-
source: "profile"
|
|
8855
|
-
};
|
|
8856
|
-
}
|
|
8857
10439
|
const configured = normalizeCodexHomePath(
|
|
8858
10440
|
state.runtimeSettings?.codexHome
|
|
8859
10441
|
);
|
|
@@ -8912,10 +10494,10 @@ var YOLO_PRESET = {
|
|
|
8912
10494
|
|
|
8913
10495
|
// ../../packages/runtime-codex/src/codex/config-io.ts
|
|
8914
10496
|
function getConfigPath(options) {
|
|
8915
|
-
return
|
|
10497
|
+
return import_node_path14.default.join(resolveCodexHomeDir(options), "config.toml");
|
|
8916
10498
|
}
|
|
8917
10499
|
async function ensureConfigDirExists(filePath) {
|
|
8918
|
-
const dir =
|
|
10500
|
+
const dir = import_node_path14.default.dirname(filePath);
|
|
8919
10501
|
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
8920
10502
|
}
|
|
8921
10503
|
function normalizeLineEndings2(content) {
|
|
@@ -9569,13 +11151,13 @@ function formatMegabytes(bytes) {
|
|
|
9569
11151
|
return (bytes / MB_DIVISOR).toFixed(2);
|
|
9570
11152
|
}
|
|
9571
11153
|
function resolveSyncBackupsDir() {
|
|
9572
|
-
return
|
|
11154
|
+
return import_node_path15.default.join(getUserDataDir(), SYNC_BACKUP_DIR);
|
|
9573
11155
|
}
|
|
9574
11156
|
function toSafeIsoForFileName(iso) {
|
|
9575
11157
|
return iso.replace(/:/g, "-");
|
|
9576
11158
|
}
|
|
9577
11159
|
async function pruneOldPrePullBackups(backupsDir) {
|
|
9578
|
-
const entries = await
|
|
11160
|
+
const entries = await import_node_fs9.promises.readdir(backupsDir, { withFileTypes: true });
|
|
9579
11161
|
const files = entries.filter(
|
|
9580
11162
|
(entry) => entry.isFile() && entry.name.startsWith(PRE_PULL_BACKUP_PREFIX) && entry.name.endsWith(PRE_PULL_BACKUP_SUFFIX)
|
|
9581
11163
|
).map((entry) => entry.name).sort((a, b) => b.localeCompare(a));
|
|
@@ -9583,7 +11165,7 @@ async function pruneOldPrePullBackups(backupsDir) {
|
|
|
9583
11165
|
return;
|
|
9584
11166
|
}
|
|
9585
11167
|
for (const stale of files.slice(PRE_PULL_BACKUP_KEEP_COUNT)) {
|
|
9586
|
-
await
|
|
11168
|
+
await import_node_fs9.promises.rm(import_node_path15.default.join(backupsDir, stale), { force: true });
|
|
9587
11169
|
}
|
|
9588
11170
|
}
|
|
9589
11171
|
async function createPrePullBackup(profileManager) {
|
|
@@ -9591,13 +11173,13 @@ async function createPrePullBackup(profileManager) {
|
|
|
9591
11173
|
enforcePushGuards: false
|
|
9592
11174
|
});
|
|
9593
11175
|
const backupsDir = resolveSyncBackupsDir();
|
|
9594
|
-
await
|
|
11176
|
+
await import_node_fs9.promises.mkdir(backupsDir, { recursive: true });
|
|
9595
11177
|
const timestamp = toSafeIsoForFileName((/* @__PURE__ */ new Date()).toISOString());
|
|
9596
|
-
const backupPath =
|
|
11178
|
+
const backupPath = import_node_path15.default.join(
|
|
9597
11179
|
backupsDir,
|
|
9598
11180
|
`${PRE_PULL_BACKUP_PREFIX}${timestamp}${PRE_PULL_BACKUP_SUFFIX}`
|
|
9599
11181
|
);
|
|
9600
|
-
await
|
|
11182
|
+
await import_node_fs9.promises.writeFile(backupPath, `${JSON.stringify(snapshot, null, 2)}
|
|
9601
11183
|
`, "utf8");
|
|
9602
11184
|
await pruneOldPrePullBackups(backupsDir);
|
|
9603
11185
|
logInfo("[cloud-sync] created pre-pull backup", { backupPath });
|
|
@@ -9866,9 +11448,9 @@ async function handleSync(args, version) {
|
|
|
9866
11448
|
}
|
|
9867
11449
|
|
|
9868
11450
|
// ../../packages/runtime-app-state/src/storage/migrations/v1.ts
|
|
9869
|
-
var
|
|
9870
|
-
var
|
|
9871
|
-
var
|
|
11451
|
+
var import_node_fs10 = require("fs");
|
|
11452
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
11453
|
+
var import_node_os7 = __toESM(require("os"), 1);
|
|
9872
11454
|
|
|
9873
11455
|
// ../../packages/contracts/src/settings/legacy-localstorage-keys.ts
|
|
9874
11456
|
var LEGACY_LOCALSTORAGE_KEYS = [
|
|
@@ -9907,20 +11489,20 @@ function isMissingPathError(error) {
|
|
|
9907
11489
|
return error.code === "ENOENT";
|
|
9908
11490
|
}
|
|
9909
11491
|
function resolveHomeDir2() {
|
|
9910
|
-
const home = process.env.HOME || process.env.USERPROFILE ||
|
|
11492
|
+
const home = process.env.HOME || process.env.USERPROFILE || import_node_os7.default.homedir();
|
|
9911
11493
|
if (!home) {
|
|
9912
11494
|
throw new Error("HOME is not set.");
|
|
9913
11495
|
}
|
|
9914
11496
|
return home;
|
|
9915
11497
|
}
|
|
9916
11498
|
function resolveCodexDir() {
|
|
9917
|
-
return
|
|
11499
|
+
return import_node_path16.default.join(resolveHomeDir2(), ".codex");
|
|
9918
11500
|
}
|
|
9919
11501
|
function resolveLegacyPath(...segments) {
|
|
9920
|
-
return
|
|
11502
|
+
return import_node_path16.default.join(resolveCodexDir(), ...segments);
|
|
9921
11503
|
}
|
|
9922
11504
|
async function readJsonFile(filePath) {
|
|
9923
|
-
const raw = await
|
|
11505
|
+
const raw = await import_node_fs10.promises.readFile(filePath, "utf8");
|
|
9924
11506
|
return JSON.parse(raw);
|
|
9925
11507
|
}
|
|
9926
11508
|
async function readJsonFileIfExists(filePath) {
|
|
@@ -9935,14 +11517,14 @@ async function readJsonFileIfExists(filePath) {
|
|
|
9935
11517
|
}
|
|
9936
11518
|
}
|
|
9937
11519
|
async function copyDirectoryManual(source, destination) {
|
|
9938
|
-
await
|
|
9939
|
-
const entries = await
|
|
11520
|
+
await import_node_fs10.promises.mkdir(destination, { recursive: true });
|
|
11521
|
+
const entries = await import_node_fs10.promises.readdir(source, { withFileTypes: true });
|
|
9940
11522
|
for (const entry of entries) {
|
|
9941
|
-
const srcPath =
|
|
9942
|
-
const destPath =
|
|
11523
|
+
const srcPath = import_node_path16.default.join(source, entry.name);
|
|
11524
|
+
const destPath = import_node_path16.default.join(destination, entry.name);
|
|
9943
11525
|
if (entry.isSymbolicLink()) {
|
|
9944
|
-
const linkTarget = await
|
|
9945
|
-
await
|
|
11526
|
+
const linkTarget = await import_node_fs10.promises.readlink(srcPath);
|
|
11527
|
+
await import_node_fs10.promises.symlink(linkTarget, destPath).catch((error) => {
|
|
9946
11528
|
if (error.code !== "EEXIST") {
|
|
9947
11529
|
throw error;
|
|
9948
11530
|
}
|
|
@@ -9954,14 +11536,14 @@ async function copyDirectoryManual(source, destination) {
|
|
|
9954
11536
|
continue;
|
|
9955
11537
|
}
|
|
9956
11538
|
if (entry.isFile()) {
|
|
9957
|
-
await
|
|
11539
|
+
await import_node_fs10.promises.copyFile(srcPath, destPath);
|
|
9958
11540
|
}
|
|
9959
11541
|
}
|
|
9960
11542
|
}
|
|
9961
11543
|
async function copyDirIfExists(source, destination) {
|
|
9962
11544
|
let stats = null;
|
|
9963
11545
|
try {
|
|
9964
|
-
stats = await
|
|
11546
|
+
stats = await import_node_fs10.promises.stat(source);
|
|
9965
11547
|
} catch (error) {
|
|
9966
11548
|
if (error.code === "ENOENT") {
|
|
9967
11549
|
return;
|
|
@@ -9971,10 +11553,10 @@ async function copyDirIfExists(source, destination) {
|
|
|
9971
11553
|
if (!stats.isDirectory()) {
|
|
9972
11554
|
return;
|
|
9973
11555
|
}
|
|
9974
|
-
await
|
|
9975
|
-
await
|
|
11556
|
+
await import_node_fs10.promises.rm(destination, { recursive: true, force: true });
|
|
11557
|
+
await import_node_fs10.promises.mkdir(import_node_path16.default.dirname(destination), { recursive: true });
|
|
9976
11558
|
try {
|
|
9977
|
-
await
|
|
11559
|
+
await import_node_fs10.promises.cp(source, destination, {
|
|
9978
11560
|
recursive: true,
|
|
9979
11561
|
errorOnExist: false,
|
|
9980
11562
|
force: true,
|
|
@@ -9991,7 +11573,7 @@ async function copyDirIfExists(source, destination) {
|
|
|
9991
11573
|
}
|
|
9992
11574
|
async function copyFileIfExists(source, destination, options) {
|
|
9993
11575
|
try {
|
|
9994
|
-
const sourceStat = await
|
|
11576
|
+
const sourceStat = await import_node_fs10.promises.stat(source);
|
|
9995
11577
|
if (!sourceStat.isFile()) {
|
|
9996
11578
|
return;
|
|
9997
11579
|
}
|
|
@@ -10002,7 +11584,7 @@ async function copyFileIfExists(source, destination, options) {
|
|
|
10002
11584
|
throw error;
|
|
10003
11585
|
}
|
|
10004
11586
|
try {
|
|
10005
|
-
const destinationStat = await
|
|
11587
|
+
const destinationStat = await import_node_fs10.promises.stat(destination);
|
|
10006
11588
|
if (destinationStat.isFile()) {
|
|
10007
11589
|
return;
|
|
10008
11590
|
}
|
|
@@ -10011,17 +11593,17 @@ async function copyFileIfExists(source, destination, options) {
|
|
|
10011
11593
|
throw error;
|
|
10012
11594
|
}
|
|
10013
11595
|
}
|
|
10014
|
-
await
|
|
10015
|
-
await
|
|
11596
|
+
await import_node_fs10.promises.mkdir(import_node_path16.default.dirname(destination), { recursive: true });
|
|
11597
|
+
await import_node_fs10.promises.copyFile(source, destination);
|
|
10016
11598
|
if (typeof options?.mode === "number") {
|
|
10017
|
-
await
|
|
11599
|
+
await import_node_fs10.promises.chmod(destination, options.mode).catch(() => void 0);
|
|
10018
11600
|
}
|
|
10019
11601
|
}
|
|
10020
11602
|
async function cleanupCopiedSkillsMetadata(skillsDir) {
|
|
10021
|
-
await removeIfExists(
|
|
11603
|
+
await removeIfExists(import_node_path16.default.join(skillsDir, LEGACY_SKILLS_REPOS_FILE));
|
|
10022
11604
|
let entries = [];
|
|
10023
11605
|
try {
|
|
10024
|
-
entries = await
|
|
11606
|
+
entries = await import_node_fs10.promises.readdir(skillsDir, { withFileTypes: true });
|
|
10025
11607
|
} catch {
|
|
10026
11608
|
return;
|
|
10027
11609
|
}
|
|
@@ -10029,30 +11611,30 @@ async function cleanupCopiedSkillsMetadata(skillsDir) {
|
|
|
10029
11611
|
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
10030
11612
|
continue;
|
|
10031
11613
|
}
|
|
10032
|
-
await removeIfExists(
|
|
11614
|
+
await removeIfExists(import_node_path16.default.join(skillsDir, entry.name, LEGACY_SKILL_MANIFEST));
|
|
10033
11615
|
}
|
|
10034
11616
|
}
|
|
10035
11617
|
async function migrateLegacyDirectoriesToUserData() {
|
|
10036
11618
|
const userDataDir = getUserDataDir();
|
|
10037
11619
|
const legacyCodexDir = resolveCodexDir();
|
|
10038
11620
|
await copyDirIfExists(
|
|
10039
|
-
|
|
10040
|
-
|
|
11621
|
+
import_node_path16.default.join(legacyCodexDir, LEGACY_PROFILE_HOMES_DIR),
|
|
11622
|
+
import_node_path16.default.join(userDataDir, LEGACY_PROFILE_HOMES_DIR)
|
|
10041
11623
|
);
|
|
10042
11624
|
await copyDirIfExists(
|
|
10043
|
-
|
|
10044
|
-
|
|
11625
|
+
import_node_path16.default.join(legacyCodexDir, LEGACY_SKILLS_DIR),
|
|
11626
|
+
import_node_path16.default.join(userDataDir, LEGACY_SKILLS_DIR)
|
|
10045
11627
|
);
|
|
10046
11628
|
await copyDirIfExists(
|
|
10047
|
-
|
|
10048
|
-
|
|
11629
|
+
import_node_path16.default.join(legacyCodexDir, LEGACY_SKILL_CACHE_DIR),
|
|
11630
|
+
import_node_path16.default.join(userDataDir, LEGACY_SKILL_CACHE_DIR)
|
|
10049
11631
|
);
|
|
10050
11632
|
await copyFileIfExists(
|
|
10051
|
-
|
|
10052
|
-
|
|
11633
|
+
import_node_path16.default.join(legacyCodexDir, LEGACY_LICENSE_SECRET_FILE2),
|
|
11634
|
+
import_node_path16.default.join(userDataDir, LEGACY_LICENSE_SECRET_FILE2),
|
|
10053
11635
|
{ mode: 384 }
|
|
10054
11636
|
);
|
|
10055
|
-
await cleanupCopiedSkillsMetadata(
|
|
11637
|
+
await cleanupCopiedSkillsMetadata(import_node_path16.default.join(userDataDir, LEGACY_SKILLS_DIR));
|
|
10056
11638
|
}
|
|
10057
11639
|
function pickAutoRoll(raw) {
|
|
10058
11640
|
if (!raw) {
|
|
@@ -10137,9 +11719,13 @@ async function loadLegacySettingsPatch() {
|
|
|
10137
11719
|
license,
|
|
10138
11720
|
autoRoll: autoRoll ? {
|
|
10139
11721
|
enabled: autoRoll.enabled,
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll
|
|
11722
|
+
rearmRemainingThreshold: autoRoll.rearmRemainingThreshold,
|
|
11723
|
+
switchRemainingThreshold: autoRoll.switchRemainingThreshold,
|
|
11724
|
+
restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll,
|
|
11725
|
+
launchOfficialCodexWhenClosedOnAutoRoll: autoRoll.launchOfficialCodexWhenClosedOnAutoRoll,
|
|
11726
|
+
priorityOrder: autoRoll.priorityOrder,
|
|
11727
|
+
lowRemainingNotificationEnabled: autoRoll.lowRemainingNotificationEnabled,
|
|
11728
|
+
lowRemainingNotificationThreshold: autoRoll.lowRemainingNotificationThreshold
|
|
10143
11729
|
} : void 0
|
|
10144
11730
|
};
|
|
10145
11731
|
}
|
|
@@ -10159,7 +11745,7 @@ async function loadLegacySyncPatch() {
|
|
|
10159
11745
|
};
|
|
10160
11746
|
}
|
|
10161
11747
|
async function loadLegacyAppSettingsParityPatch() {
|
|
10162
|
-
const filePath =
|
|
11748
|
+
const filePath = import_node_path16.default.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE);
|
|
10163
11749
|
const raw = await readJsonFileIfExists(filePath);
|
|
10164
11750
|
if (!isRecord6(raw)) {
|
|
10165
11751
|
return {};
|
|
@@ -10172,7 +11758,7 @@ async function loadLegacyProfilesPatch() {
|
|
|
10172
11758
|
const root = resolveLegacyPath(LEGACY_PROFILE_HOMES_DIR);
|
|
10173
11759
|
let entries = [];
|
|
10174
11760
|
try {
|
|
10175
|
-
entries = await
|
|
11761
|
+
entries = await import_node_fs10.promises.readdir(root, { withFileTypes: true });
|
|
10176
11762
|
} catch (error) {
|
|
10177
11763
|
if (error.code === "ENOENT") {
|
|
10178
11764
|
return {};
|
|
@@ -10184,7 +11770,7 @@ async function loadLegacyProfilesPatch() {
|
|
|
10184
11770
|
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
10185
11771
|
continue;
|
|
10186
11772
|
}
|
|
10187
|
-
const profileFile =
|
|
11773
|
+
const profileFile = import_node_path16.default.join(root, entry.name, "profile.json");
|
|
10188
11774
|
const raw = await readJsonFileIfExists(profileFile);
|
|
10189
11775
|
if (!raw) {
|
|
10190
11776
|
continue;
|
|
@@ -10220,12 +11806,12 @@ function parseSkillInstallMetadata(raw) {
|
|
|
10220
11806
|
}
|
|
10221
11807
|
async function loadLegacySkillsPatch() {
|
|
10222
11808
|
const skillsRoot = resolveLegacyPath(LEGACY_SKILLS_DIR);
|
|
10223
|
-
const reposPath =
|
|
11809
|
+
const reposPath = import_node_path16.default.join(skillsRoot, LEGACY_SKILLS_REPOS_FILE);
|
|
10224
11810
|
const reposRaw = await readJsonFileIfExists(reposPath);
|
|
10225
11811
|
const installsBySlug = {};
|
|
10226
11812
|
let dirEntries = [];
|
|
10227
11813
|
try {
|
|
10228
|
-
dirEntries = await
|
|
11814
|
+
dirEntries = await import_node_fs10.promises.readdir(skillsRoot, { withFileTypes: true });
|
|
10229
11815
|
} catch (error) {
|
|
10230
11816
|
if (error.code !== "ENOENT") {
|
|
10231
11817
|
throw error;
|
|
@@ -10238,7 +11824,7 @@ async function loadLegacySkillsPatch() {
|
|
|
10238
11824
|
if (entry.name.startsWith(".")) {
|
|
10239
11825
|
continue;
|
|
10240
11826
|
}
|
|
10241
|
-
const manifestPath =
|
|
11827
|
+
const manifestPath = import_node_path16.default.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST);
|
|
10242
11828
|
const manifestRaw = await readJsonFileIfExists(manifestPath);
|
|
10243
11829
|
const parsed = parseSkillInstallMetadata(manifestRaw);
|
|
10244
11830
|
if (!parsed) {
|
|
@@ -10334,9 +11920,13 @@ function mergeLegacyLocalStoragePatch(payload) {
|
|
|
10334
11920
|
if (autoRoll) {
|
|
10335
11921
|
patch.autoRoll = {
|
|
10336
11922
|
enabled: autoRoll.enabled,
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll
|
|
11923
|
+
rearmRemainingThreshold: autoRoll.rearmRemainingThreshold,
|
|
11924
|
+
switchRemainingThreshold: autoRoll.switchRemainingThreshold,
|
|
11925
|
+
restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll,
|
|
11926
|
+
launchOfficialCodexWhenClosedOnAutoRoll: autoRoll.launchOfficialCodexWhenClosedOnAutoRoll,
|
|
11927
|
+
priorityOrder: autoRoll.priorityOrder,
|
|
11928
|
+
lowRemainingNotificationEnabled: autoRoll.lowRemainingNotificationEnabled,
|
|
11929
|
+
lowRemainingNotificationThreshold: autoRoll.lowRemainingNotificationThreshold
|
|
10340
11930
|
};
|
|
10341
11931
|
markSkippedIfPresent("codex:auto-roll-settings", true);
|
|
10342
11932
|
} else {
|
|
@@ -10349,13 +11939,13 @@ function mergeLegacyLocalStoragePatch(payload) {
|
|
|
10349
11939
|
};
|
|
10350
11940
|
}
|
|
10351
11941
|
async function removeIfExists(target) {
|
|
10352
|
-
await
|
|
11942
|
+
await import_node_fs10.promises.rm(target, { recursive: true, force: true });
|
|
10353
11943
|
}
|
|
10354
11944
|
async function cleanupLegacyCanonicalSources() {
|
|
10355
11945
|
const homeDir = resolveHomeDir2();
|
|
10356
11946
|
await removeIfExists(resolveLegacyAppStatePath());
|
|
10357
|
-
await removeIfExists(
|
|
10358
|
-
await removeIfExists(
|
|
11947
|
+
await removeIfExists(import_node_path16.default.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE));
|
|
11948
|
+
await removeIfExists(import_node_path16.default.join(homeDir, SQLITE_STORAGE_DIR));
|
|
10359
11949
|
await removeIfExists(resolveLegacyPath(LEGACY_SETTINGS_FILE));
|
|
10360
11950
|
await removeIfExists(resolveLegacyPath(LEGACY_SETTINGS_BACKUP_FILE));
|
|
10361
11951
|
await removeIfExists(resolveLegacyPath(LEGACY_SYNC_STATE_FILE));
|
|
@@ -10363,15 +11953,15 @@ async function cleanupLegacyCanonicalSources() {
|
|
|
10363
11953
|
await removeIfExists(resolveLegacyPath(LEGACY_SKILLS_DIR, LEGACY_SKILLS_REPOS_FILE));
|
|
10364
11954
|
const profileHomesRoot = resolveLegacyPath(LEGACY_PROFILE_HOMES_DIR);
|
|
10365
11955
|
try {
|
|
10366
|
-
const profileHomes = await
|
|
11956
|
+
const profileHomes = await import_node_fs10.promises.readdir(profileHomesRoot, { withFileTypes: true });
|
|
10367
11957
|
for (const profileHome of profileHomes) {
|
|
10368
11958
|
if (!profileHome.isDirectory()) {
|
|
10369
11959
|
continue;
|
|
10370
11960
|
}
|
|
10371
|
-
const profileDir =
|
|
11961
|
+
const profileDir = import_node_path16.default.join(profileHomesRoot, profileHome.name);
|
|
10372
11962
|
let files = [];
|
|
10373
11963
|
try {
|
|
10374
|
-
files = await
|
|
11964
|
+
files = await import_node_fs10.promises.readdir(profileDir);
|
|
10375
11965
|
} catch {
|
|
10376
11966
|
continue;
|
|
10377
11967
|
}
|
|
@@ -10379,7 +11969,7 @@ async function cleanupLegacyCanonicalSources() {
|
|
|
10379
11969
|
if (!file.startsWith("profile.json")) {
|
|
10380
11970
|
continue;
|
|
10381
11971
|
}
|
|
10382
|
-
await removeIfExists(
|
|
11972
|
+
await removeIfExists(import_node_path16.default.join(profileDir, file));
|
|
10383
11973
|
}
|
|
10384
11974
|
}
|
|
10385
11975
|
} catch (error) {
|
|
@@ -10392,12 +11982,12 @@ async function cleanupLegacyCanonicalSources() {
|
|
|
10392
11982
|
}
|
|
10393
11983
|
const skillsRoot = resolveLegacyPath(LEGACY_SKILLS_DIR);
|
|
10394
11984
|
try {
|
|
10395
|
-
const skillDirs = await
|
|
11985
|
+
const skillDirs = await import_node_fs10.promises.readdir(skillsRoot, { withFileTypes: true });
|
|
10396
11986
|
for (const entry of skillDirs) {
|
|
10397
11987
|
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
10398
11988
|
continue;
|
|
10399
11989
|
}
|
|
10400
|
-
await removeIfExists(
|
|
11990
|
+
await removeIfExists(import_node_path16.default.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST));
|
|
10401
11991
|
}
|
|
10402
11992
|
} catch (error) {
|
|
10403
11993
|
if (!isMissingPathError(error)) {
|
|
@@ -10610,7 +12200,7 @@ async function ensureCliStorageReady() {
|
|
|
10610
12200
|
}
|
|
10611
12201
|
|
|
10612
12202
|
// src/app/main.ts
|
|
10613
|
-
var VERSION = true ? "3.9.
|
|
12203
|
+
var VERSION = true ? "3.9.8" : "0.0.0";
|
|
10614
12204
|
async function runCli() {
|
|
10615
12205
|
const args = process.argv.slice(2);
|
|
10616
12206
|
if (args.length === 0) {
|