codexuse-cli 5.0.5 → 5.0.7
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 +3 -2
- package/dist/index.js +1040 -94
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2054,6 +2054,7 @@ var DEFAULT_AUTO_ROLL_ENABLED = false;
|
|
|
2054
2054
|
var DEFAULT_AUTO_ROLL_REARM_REMAINING_THRESHOLD = 15;
|
|
2055
2055
|
var DEFAULT_AUTO_ROLL_SWITCH_REMAINING_THRESHOLD = 5;
|
|
2056
2056
|
var DEFAULT_LAUNCH_OFFICIAL_CODEX_WHEN_CLOSED_ON_AUTO_ROLL = false;
|
|
2057
|
+
var DEFAULT_HANDOFF_RUNNING_THREADS_ON_ACCOUNT_SWITCH = false;
|
|
2057
2058
|
var DEFAULT_AUTO_ROLL_PRIORITY_ORDER = [];
|
|
2058
2059
|
var DEFAULT_LOW_REMAINING_NOTIFICATION_ENABLED = false;
|
|
2059
2060
|
var DEFAULT_LOW_REMAINING_NOTIFICATION_THRESHOLD = 1;
|
|
@@ -2157,6 +2158,7 @@ function normalizeAutoRollSettings(raw) {
|
|
|
2157
2158
|
rearmRemainingThreshold: normalizedRearm,
|
|
2158
2159
|
switchRemainingThreshold: normalizedSwitch,
|
|
2159
2160
|
launchOfficialCodexWhenClosedOnAutoRoll: raw?.launchOfficialCodexWhenClosedOnAutoRoll === true ? true : DEFAULT_LAUNCH_OFFICIAL_CODEX_WHEN_CLOSED_ON_AUTO_ROLL,
|
|
2161
|
+
handoffRunningThreadsOnAccountSwitch: raw?.handoffRunningThreadsOnAccountSwitch === true ? true : DEFAULT_HANDOFF_RUNNING_THREADS_ON_ACCOUNT_SWITCH,
|
|
2160
2162
|
priorityOrder: sanitizeAutoRollPriorityOrder(raw?.priorityOrder),
|
|
2161
2163
|
lowRemainingNotificationEnabled: raw?.lowRemainingNotificationEnabled === true ? true : DEFAULT_LOW_REMAINING_NOTIFICATION_ENABLED,
|
|
2162
2164
|
lowRemainingNotificationThreshold: sanitizeLowRemainingNotificationThreshold(
|
|
@@ -2530,6 +2532,7 @@ function createDefaultAppState() {
|
|
|
2530
2532
|
rearmRemainingThreshold: 15,
|
|
2531
2533
|
switchRemainingThreshold: 5,
|
|
2532
2534
|
launchOfficialCodexWhenClosedOnAutoRoll: false,
|
|
2535
|
+
handoffRunningThreadsOnAccountSwitch: false,
|
|
2533
2536
|
priorityOrder: [],
|
|
2534
2537
|
lowRemainingNotificationEnabled: false,
|
|
2535
2538
|
lowRemainingNotificationThreshold: 1
|
|
@@ -2613,13 +2616,15 @@ function createDefaultAppState() {
|
|
|
2613
2616
|
lastPushAt: null,
|
|
2614
2617
|
lastPullAt: null,
|
|
2615
2618
|
lastError: null,
|
|
2616
|
-
remoteUpdatedAt: null
|
|
2619
|
+
remoteUpdatedAt: null,
|
|
2620
|
+
remoteKind: "none"
|
|
2617
2621
|
},
|
|
2618
2622
|
analytics: {
|
|
2619
2623
|
anonymousId: null,
|
|
2620
2624
|
enabled: true,
|
|
2621
2625
|
lastFlushAt: null,
|
|
2622
|
-
lastError: null
|
|
2626
|
+
lastError: null,
|
|
2627
|
+
reportedNativeCrashIncidentIds: []
|
|
2623
2628
|
},
|
|
2624
2629
|
profilesByName: {}
|
|
2625
2630
|
};
|
|
@@ -2631,6 +2636,20 @@ function asString(value) {
|
|
|
2631
2636
|
const trimmed = value.trim();
|
|
2632
2637
|
return trimmed.length > 0 ? trimmed : null;
|
|
2633
2638
|
}
|
|
2639
|
+
function asStringArray(value) {
|
|
2640
|
+
if (!Array.isArray(value)) {
|
|
2641
|
+
return [];
|
|
2642
|
+
}
|
|
2643
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2644
|
+
return value.flatMap((entry) => {
|
|
2645
|
+
const normalized = asString(entry);
|
|
2646
|
+
if (!normalized || seen.has(normalized)) {
|
|
2647
|
+
return [];
|
|
2648
|
+
}
|
|
2649
|
+
seen.add(normalized);
|
|
2650
|
+
return [normalized];
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2634
2653
|
function asNumberOrNull(value) {
|
|
2635
2654
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2636
2655
|
}
|
|
@@ -2954,7 +2973,10 @@ function normalizeAppState(raw) {
|
|
|
2954
2973
|
sourceProfileKey: asString(debt.sourceProfileKey),
|
|
2955
2974
|
decisionId: asString(debt.decisionId),
|
|
2956
2975
|
attempts: asNumberOrNull(debt.attempts) ?? 0,
|
|
2957
|
-
lastReason: asString(debt.lastReason)
|
|
2976
|
+
lastReason: asString(debt.lastReason),
|
|
2977
|
+
handoffReason: debt.handoffReason === "manual" || debt.handoffReason === "auto-roll" ? debt.handoffReason : null,
|
|
2978
|
+
handoffThreadIds: asStringArray(debt.handoffThreadIds),
|
|
2979
|
+
handoffSentThreadIds: asStringArray(debt.handoffSentThreadIds)
|
|
2958
2980
|
} : null;
|
|
2959
2981
|
} else {
|
|
2960
2982
|
merged.officialCodex.pendingActivationDebt = null;
|
|
@@ -3183,6 +3205,9 @@ function normalizeAppState(raw) {
|
|
|
3183
3205
|
merged.sync.lastPullAt = asString(merged.sync.lastPullAt);
|
|
3184
3206
|
merged.sync.lastError = asString(merged.sync.lastError);
|
|
3185
3207
|
merged.sync.remoteUpdatedAt = asString(merged.sync.remoteUpdatedAt);
|
|
3208
|
+
if (merged.sync.remoteKind !== "legacy-plaintext" && merged.sync.remoteKind !== "encrypted") {
|
|
3209
|
+
merged.sync.remoteKind = "none";
|
|
3210
|
+
}
|
|
3186
3211
|
if (!isRecord2(merged.analytics)) {
|
|
3187
3212
|
merged.analytics = clone2(defaults.analytics);
|
|
3188
3213
|
}
|
|
@@ -3192,6 +3217,9 @@ function normalizeAppState(raw) {
|
|
|
3192
3217
|
}
|
|
3193
3218
|
merged.analytics.lastFlushAt = asString(merged.analytics.lastFlushAt);
|
|
3194
3219
|
merged.analytics.lastError = asString(merged.analytics.lastError);
|
|
3220
|
+
merged.analytics.reportedNativeCrashIncidentIds = asStringArray(
|
|
3221
|
+
merged.analytics.reportedNativeCrashIncidentIds
|
|
3222
|
+
).slice(-20);
|
|
3195
3223
|
if ("telemetry" in merged) {
|
|
3196
3224
|
delete merged.telemetry;
|
|
3197
3225
|
}
|
|
@@ -3496,6 +3524,7 @@ async function writeCodexSettingsJsonRaw(payload) {
|
|
|
3496
3524
|
rearmRemainingThreshold: autoRoll.rearmRemainingThreshold,
|
|
3497
3525
|
switchRemainingThreshold: autoRoll.switchRemainingThreshold,
|
|
3498
3526
|
launchOfficialCodexWhenClosedOnAutoRoll: autoRoll.launchOfficialCodexWhenClosedOnAutoRoll,
|
|
3527
|
+
handoffRunningThreadsOnAccountSwitch: autoRoll.handoffRunningThreadsOnAccountSwitch,
|
|
3499
3528
|
priorityOrder: autoRoll.priorityOrder,
|
|
3500
3529
|
lowRemainingNotificationEnabled: autoRoll.lowRemainingNotificationEnabled,
|
|
3501
3530
|
lowRemainingNotificationThreshold: autoRoll.lowRemainingNotificationThreshold
|
|
@@ -3927,7 +3956,7 @@ var import_node_crypto3 = require("crypto");
|
|
|
3927
3956
|
var import_fs = require("fs");
|
|
3928
3957
|
var import_path = __toESM(require("path"), 1);
|
|
3929
3958
|
var import_path2 = require("path");
|
|
3930
|
-
var
|
|
3959
|
+
var import_toml2 = __toESM(require_toml(), 1);
|
|
3931
3960
|
|
|
3932
3961
|
// ../../packages/contracts/src/profiles/identity.ts
|
|
3933
3962
|
function normalizeValue(value) {
|
|
@@ -3981,6 +4010,7 @@ var import_node_readline = __toESM(require("readline"), 1);
|
|
|
3981
4010
|
var import_node_fs3 = require("fs");
|
|
3982
4011
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
3983
4012
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
4013
|
+
var import_toml = __toESM(require_toml(), 1);
|
|
3984
4014
|
|
|
3985
4015
|
// ../../packages/runtime-codex/src/codex/cli.ts
|
|
3986
4016
|
var import_node_fs2 = require("fs");
|
|
@@ -4206,6 +4236,12 @@ var ACTIVATION_MODEL = "gpt-5.1-codex-mini";
|
|
|
4206
4236
|
var MAX_STDERR_CAPTURE_CHARS = 32768;
|
|
4207
4237
|
var REFRESH_TOKEN_REDEEMED_SNIPPET = "refresh token was already used";
|
|
4208
4238
|
var authFileCache = /* @__PURE__ */ new Map();
|
|
4239
|
+
var CODEX_OAUTH_REFRESH_URL = "https://auth.openai.com/oauth/token";
|
|
4240
|
+
var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
4241
|
+
var CODEX_OAUTH_REFRESH_MAX_AGE_MS = 8 * 24 * 60 * 60 * 1e3;
|
|
4242
|
+
var CODEX_OAUTH_ACCESS_TOKEN_REFRESH_SKEW_MS = 2 * 60 * 1e3;
|
|
4243
|
+
var CODEX_USAGE_DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
|
|
4244
|
+
var CODEX_USAGE_TIMEOUT_MS = 3e4;
|
|
4209
4245
|
function isRecord4(value) {
|
|
4210
4246
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4211
4247
|
}
|
|
@@ -4429,6 +4465,17 @@ function extractJwtIssuedAtMs(token) {
|
|
|
4429
4465
|
}
|
|
4430
4466
|
return issuedAt * 1e3;
|
|
4431
4467
|
}
|
|
4468
|
+
function extractJwtExpiresAtMs(token) {
|
|
4469
|
+
const payload = decodeJwtPayload(token);
|
|
4470
|
+
if (!payload) {
|
|
4471
|
+
return null;
|
|
4472
|
+
}
|
|
4473
|
+
const expiresAt = payload["exp"];
|
|
4474
|
+
if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) {
|
|
4475
|
+
return null;
|
|
4476
|
+
}
|
|
4477
|
+
return expiresAt * 1e3;
|
|
4478
|
+
}
|
|
4432
4479
|
function parseAuthRecord(content) {
|
|
4433
4480
|
try {
|
|
4434
4481
|
const parsed = JSON.parse(content);
|
|
@@ -4517,6 +4564,353 @@ async function writeAuthFileCached(filePath, content) {
|
|
|
4517
4564
|
authFileCache.delete(filePath);
|
|
4518
4565
|
}
|
|
4519
4566
|
}
|
|
4567
|
+
function nonEmptyString(value) {
|
|
4568
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
4569
|
+
}
|
|
4570
|
+
function resolveCodexAuthPathForEnv(envOverride) {
|
|
4571
|
+
return envOverride?.CODEX_HOME ? import_node_path5.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path5.default.join(
|
|
4572
|
+
envOverride?.HOME ?? process.env.HOME ?? import_node_os2.default.homedir(),
|
|
4573
|
+
".codex",
|
|
4574
|
+
"auth.json"
|
|
4575
|
+
);
|
|
4576
|
+
}
|
|
4577
|
+
function parseCodexOAuthCredentials(content) {
|
|
4578
|
+
const parsed = parseAuthRecord(content);
|
|
4579
|
+
if (!parsed) {
|
|
4580
|
+
return null;
|
|
4581
|
+
}
|
|
4582
|
+
const tokens = isRecord4(parsed.tokens) ? parsed.tokens : {};
|
|
4583
|
+
const accessToken = nonEmptyString(tokens.access_token) ?? nonEmptyString(parsed.access_token);
|
|
4584
|
+
const refreshToken = nonEmptyString(tokens.refresh_token) ?? nonEmptyString(parsed.refresh_token);
|
|
4585
|
+
if (!accessToken && !refreshToken) {
|
|
4586
|
+
return null;
|
|
4587
|
+
}
|
|
4588
|
+
return {
|
|
4589
|
+
accessToken: accessToken ?? "",
|
|
4590
|
+
refreshToken: refreshToken ?? "",
|
|
4591
|
+
idToken: nonEmptyString(tokens.id_token) ?? nonEmptyString(parsed.id_token),
|
|
4592
|
+
accountId: nonEmptyString(tokens.account_id) ?? nonEmptyString(parsed.account_id),
|
|
4593
|
+
lastRefreshMs: parseTimestamp2(tokens.last_refresh) ?? parseTimestamp2(parsed.last_refresh)
|
|
4594
|
+
};
|
|
4595
|
+
}
|
|
4596
|
+
function shouldRefreshCodexOAuthToken(credentials) {
|
|
4597
|
+
if (!credentials.refreshToken) {
|
|
4598
|
+
return false;
|
|
4599
|
+
}
|
|
4600
|
+
const accessExpiresAt = extractJwtExpiresAtMs(credentials.accessToken);
|
|
4601
|
+
if (accessExpiresAt !== null && accessExpiresAt - Date.now() <= CODEX_OAUTH_ACCESS_TOKEN_REFRESH_SKEW_MS) {
|
|
4602
|
+
return true;
|
|
4603
|
+
}
|
|
4604
|
+
if (credentials.lastRefreshMs === null) {
|
|
4605
|
+
return true;
|
|
4606
|
+
}
|
|
4607
|
+
return Date.now() - credentials.lastRefreshMs > CODEX_OAUTH_REFRESH_MAX_AGE_MS;
|
|
4608
|
+
}
|
|
4609
|
+
async function writeCodexOAuthCredentials(authPath, content, credentials) {
|
|
4610
|
+
let json = {};
|
|
4611
|
+
try {
|
|
4612
|
+
const parsed = JSON.parse(content);
|
|
4613
|
+
if (isRecord4(parsed)) {
|
|
4614
|
+
json = { ...parsed };
|
|
4615
|
+
}
|
|
4616
|
+
} catch {
|
|
4617
|
+
json = {};
|
|
4618
|
+
}
|
|
4619
|
+
const existingTokens = isRecord4(json.tokens) ? json.tokens : {};
|
|
4620
|
+
json.tokens = {
|
|
4621
|
+
...existingTokens,
|
|
4622
|
+
access_token: credentials.accessToken,
|
|
4623
|
+
refresh_token: credentials.refreshToken,
|
|
4624
|
+
...credentials.idToken ? { id_token: credentials.idToken } : {},
|
|
4625
|
+
...credentials.accountId ? { account_id: credentials.accountId } : {},
|
|
4626
|
+
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
4627
|
+
};
|
|
4628
|
+
json.last_refresh = (/* @__PURE__ */ new Date()).toISOString();
|
|
4629
|
+
await writeAuthFileCached(authPath, `${JSON.stringify(json, null, 2)}
|
|
4630
|
+
`);
|
|
4631
|
+
}
|
|
4632
|
+
async function refreshCodexOAuthCredentials(credentials) {
|
|
4633
|
+
if (!credentials.refreshToken) {
|
|
4634
|
+
return credentials;
|
|
4635
|
+
}
|
|
4636
|
+
const response = await fetch(CODEX_OAUTH_REFRESH_URL, {
|
|
4637
|
+
method: "POST",
|
|
4638
|
+
headers: { "Content-Type": "application/json" },
|
|
4639
|
+
signal: AbortSignal.timeout(CODEX_USAGE_TIMEOUT_MS),
|
|
4640
|
+
body: JSON.stringify({
|
|
4641
|
+
client_id: CODEX_OAUTH_CLIENT_ID,
|
|
4642
|
+
grant_type: "refresh_token",
|
|
4643
|
+
refresh_token: credentials.refreshToken,
|
|
4644
|
+
scope: "openid profile email"
|
|
4645
|
+
})
|
|
4646
|
+
});
|
|
4647
|
+
const body = await response.text();
|
|
4648
|
+
if (!response.ok) {
|
|
4649
|
+
let code = "";
|
|
4650
|
+
try {
|
|
4651
|
+
const parsed2 = JSON.parse(body);
|
|
4652
|
+
if (isRecord4(parsed2)) {
|
|
4653
|
+
const error = isRecord4(parsed2.error) ? parsed2.error : {};
|
|
4654
|
+
code = nonEmptyString(error.code) ?? nonEmptyString(parsed2.error) ?? nonEmptyString(parsed2.code) ?? "";
|
|
4655
|
+
}
|
|
4656
|
+
} catch {
|
|
4657
|
+
code = "";
|
|
4658
|
+
}
|
|
4659
|
+
if (code === "refresh_token_reused") {
|
|
4660
|
+
throw new Error("refresh token was already used");
|
|
4661
|
+
}
|
|
4662
|
+
if (code === "refresh_token_expired") {
|
|
4663
|
+
throw new Error("refresh token expired");
|
|
4664
|
+
}
|
|
4665
|
+
if (code === "invalid_grant" || code === "refresh_token_invalidated") {
|
|
4666
|
+
throw new Error("refresh token was revoked");
|
|
4667
|
+
}
|
|
4668
|
+
throw new Error(`Codex OAuth refresh failed HTTP ${response.status}`);
|
|
4669
|
+
}
|
|
4670
|
+
let parsed;
|
|
4671
|
+
try {
|
|
4672
|
+
parsed = JSON.parse(body);
|
|
4673
|
+
} catch {
|
|
4674
|
+
throw new Error("Codex OAuth refresh returned invalid JSON");
|
|
4675
|
+
}
|
|
4676
|
+
if (!isRecord4(parsed)) {
|
|
4677
|
+
throw new Error("Codex OAuth refresh returned invalid data");
|
|
4678
|
+
}
|
|
4679
|
+
return {
|
|
4680
|
+
accessToken: nonEmptyString(parsed.access_token) ?? credentials.accessToken,
|
|
4681
|
+
refreshToken: nonEmptyString(parsed.refresh_token) ?? credentials.refreshToken,
|
|
4682
|
+
idToken: nonEmptyString(parsed.id_token) ?? credentials.idToken,
|
|
4683
|
+
accountId: credentials.accountId,
|
|
4684
|
+
lastRefreshMs: Date.now()
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
async function loadCodexConfigContents(envOverride) {
|
|
4688
|
+
const codexHome = envOverride?.CODEX_HOME?.trim();
|
|
4689
|
+
const root = codexHome || import_node_path5.default.join(envOverride?.HOME ?? process.env.HOME ?? import_node_os2.default.homedir(), ".codex");
|
|
4690
|
+
try {
|
|
4691
|
+
return await import_node_fs3.promises.readFile(import_node_path5.default.join(root, "config.toml"), "utf8");
|
|
4692
|
+
} catch {
|
|
4693
|
+
return null;
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
function parseChatGPTBaseUrl(contents) {
|
|
4697
|
+
if (!contents) {
|
|
4698
|
+
return CODEX_USAGE_DEFAULT_BASE_URL;
|
|
4699
|
+
}
|
|
4700
|
+
try {
|
|
4701
|
+
const parsed = (0, import_toml.parse)(contents);
|
|
4702
|
+
if (isRecord4(parsed)) {
|
|
4703
|
+
return nonEmptyString(parsed.chatgpt_base_url) ?? CODEX_USAGE_DEFAULT_BASE_URL;
|
|
4704
|
+
}
|
|
4705
|
+
} catch {
|
|
4706
|
+
return CODEX_USAGE_DEFAULT_BASE_URL;
|
|
4707
|
+
}
|
|
4708
|
+
return CODEX_USAGE_DEFAULT_BASE_URL;
|
|
4709
|
+
}
|
|
4710
|
+
async function resolveCodexUsageUrl(envOverride) {
|
|
4711
|
+
let base = parseChatGPTBaseUrl(await loadCodexConfigContents(envOverride));
|
|
4712
|
+
while (base.endsWith("/")) {
|
|
4713
|
+
base = base.slice(0, -1);
|
|
4714
|
+
}
|
|
4715
|
+
if ((base.startsWith("https://chatgpt.com") || base.startsWith("https://chat.openai.com")) && !base.includes("/backend-api")) {
|
|
4716
|
+
base += "/backend-api";
|
|
4717
|
+
}
|
|
4718
|
+
return `${base}${base.includes("/backend-api") ? "/wham/usage" : "/api/codex/usage"}`;
|
|
4719
|
+
}
|
|
4720
|
+
function numberFromUnknown(value) {
|
|
4721
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
4722
|
+
return value;
|
|
4723
|
+
}
|
|
4724
|
+
if (typeof value === "string" && value.trim()) {
|
|
4725
|
+
const parsed = Number(value.trim());
|
|
4726
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
4727
|
+
}
|
|
4728
|
+
return null;
|
|
4729
|
+
}
|
|
4730
|
+
function toOAuthWindow(window) {
|
|
4731
|
+
if (!window) {
|
|
4732
|
+
return void 0;
|
|
4733
|
+
}
|
|
4734
|
+
const usedPercent = numberFromUnknown(window.used_percent);
|
|
4735
|
+
const resetAt = numberFromUnknown(window.reset_at);
|
|
4736
|
+
const limitWindowSeconds = numberFromUnknown(window.limit_window_seconds);
|
|
4737
|
+
if (usedPercent === null && resetAt === null && limitWindowSeconds === null) {
|
|
4738
|
+
return void 0;
|
|
4739
|
+
}
|
|
4740
|
+
const result = {};
|
|
4741
|
+
if (usedPercent !== null) {
|
|
4742
|
+
result.usedPercent = Math.max(0, Math.min(100, usedPercent));
|
|
4743
|
+
}
|
|
4744
|
+
if (limitWindowSeconds !== null) {
|
|
4745
|
+
result.windowMinutes = Math.round(limitWindowSeconds / 60);
|
|
4746
|
+
}
|
|
4747
|
+
if (resetAt !== null) {
|
|
4748
|
+
const resetMs = resetAt > 1e10 ? resetAt : resetAt * 1e3;
|
|
4749
|
+
result.resetsAt = new Date(resetMs).toISOString();
|
|
4750
|
+
result.resetsInSeconds = Math.max(0, Math.round((resetMs - Date.now()) / 1e3));
|
|
4751
|
+
}
|
|
4752
|
+
return result;
|
|
4753
|
+
}
|
|
4754
|
+
function firstNonEmptyString(...values) {
|
|
4755
|
+
for (const value of values) {
|
|
4756
|
+
const text = nonEmptyString(value);
|
|
4757
|
+
if (text) {
|
|
4758
|
+
return text;
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
return null;
|
|
4762
|
+
}
|
|
4763
|
+
function slugForRateLimitId(value) {
|
|
4764
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4765
|
+
}
|
|
4766
|
+
function isSparkRateLimit(limitName, meteredFeature) {
|
|
4767
|
+
return [limitName, meteredFeature].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes("spark"));
|
|
4768
|
+
}
|
|
4769
|
+
function sparkWindowIdentity(window, fallback) {
|
|
4770
|
+
const limitWindowSeconds = numberFromUnknown(window.limit_window_seconds);
|
|
4771
|
+
const windowMinutes = limitWindowSeconds !== null ? Math.round(limitWindowSeconds / 60) : null;
|
|
4772
|
+
const kind = windowMinutes !== null && windowMinutes > 0 && windowMinutes <= 6 * 60 ? "five-hour" : windowMinutes !== null && windowMinutes >= 6 * 24 * 60 ? "weekly" : fallback;
|
|
4773
|
+
return kind === "weekly" ? { id: "codex-spark-weekly", label: "Codex Spark Weekly" } : { id: "codex-spark", label: "Codex Spark 5-hour" };
|
|
4774
|
+
}
|
|
4775
|
+
function toExtraRateLimitWindow(args) {
|
|
4776
|
+
const window = toOAuthWindow(args.window);
|
|
4777
|
+
if (!window) {
|
|
4778
|
+
return null;
|
|
4779
|
+
}
|
|
4780
|
+
return {
|
|
4781
|
+
id: args.id,
|
|
4782
|
+
label: args.label,
|
|
4783
|
+
...args.meteredFeature ? { meteredFeature: args.meteredFeature } : {},
|
|
4784
|
+
...window
|
|
4785
|
+
};
|
|
4786
|
+
}
|
|
4787
|
+
function parseExtraRateLimitWindows(additionalRateLimits) {
|
|
4788
|
+
if (!Array.isArray(additionalRateLimits)) {
|
|
4789
|
+
return [];
|
|
4790
|
+
}
|
|
4791
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
4792
|
+
const extraWindows = [];
|
|
4793
|
+
for (const entry of additionalRateLimits) {
|
|
4794
|
+
if (!isRecord4(entry)) {
|
|
4795
|
+
continue;
|
|
4796
|
+
}
|
|
4797
|
+
const limitName = firstNonEmptyString(entry.limit_name);
|
|
4798
|
+
const meteredFeature = firstNonEmptyString(entry.metered_feature);
|
|
4799
|
+
const rateLimit = isRecord4(entry.rate_limit) ? entry.rate_limit : null;
|
|
4800
|
+
if (!rateLimit) {
|
|
4801
|
+
continue;
|
|
4802
|
+
}
|
|
4803
|
+
const primary = isRecord4(rateLimit.primary_window) ? rateLimit.primary_window : null;
|
|
4804
|
+
const secondary = isRecord4(rateLimit.secondary_window) ? rateLimit.secondary_window : null;
|
|
4805
|
+
if (isSparkRateLimit(limitName, meteredFeature)) {
|
|
4806
|
+
const candidates = [
|
|
4807
|
+
{ window: primary, fallback: "five-hour" },
|
|
4808
|
+
{ window: secondary, fallback: "weekly" }
|
|
4809
|
+
];
|
|
4810
|
+
for (const candidate of candidates) {
|
|
4811
|
+
if (!candidate.window) {
|
|
4812
|
+
continue;
|
|
4813
|
+
}
|
|
4814
|
+
const identity = sparkWindowIdentity(candidate.window, candidate.fallback);
|
|
4815
|
+
if (usedIds.has(identity.id)) {
|
|
4816
|
+
continue;
|
|
4817
|
+
}
|
|
4818
|
+
const extraWindow2 = toExtraRateLimitWindow({
|
|
4819
|
+
...identity,
|
|
4820
|
+
meteredFeature,
|
|
4821
|
+
window: candidate.window
|
|
4822
|
+
});
|
|
4823
|
+
if (extraWindow2) {
|
|
4824
|
+
usedIds.add(identity.id);
|
|
4825
|
+
extraWindows.push(extraWindow2);
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
continue;
|
|
4829
|
+
}
|
|
4830
|
+
const idSource = firstNonEmptyString(meteredFeature, limitName);
|
|
4831
|
+
const slug = idSource ? slugForRateLimitId(idSource) : "";
|
|
4832
|
+
if (!slug) {
|
|
4833
|
+
continue;
|
|
4834
|
+
}
|
|
4835
|
+
const id = `codex-${slug}`;
|
|
4836
|
+
if (usedIds.has(id)) {
|
|
4837
|
+
continue;
|
|
4838
|
+
}
|
|
4839
|
+
const extraWindow = toExtraRateLimitWindow({
|
|
4840
|
+
id,
|
|
4841
|
+
label: firstNonEmptyString(limitName, meteredFeature) ?? "Codex extra limit",
|
|
4842
|
+
meteredFeature,
|
|
4843
|
+
window: primary ?? secondary
|
|
4844
|
+
});
|
|
4845
|
+
if (extraWindow) {
|
|
4846
|
+
usedIds.add(id);
|
|
4847
|
+
extraWindows.push(extraWindow);
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
return extraWindows;
|
|
4851
|
+
}
|
|
4852
|
+
function parseRateLimitSnapshotFromOAuthUsage(value) {
|
|
4853
|
+
if (!isRecord4(value)) {
|
|
4854
|
+
throw new Error("Codex usage API returned invalid data");
|
|
4855
|
+
}
|
|
4856
|
+
const usage = value;
|
|
4857
|
+
const primary = toOAuthWindow(usage.rate_limit?.primary_window ?? null);
|
|
4858
|
+
const secondary = toOAuthWindow(usage.rate_limit?.secondary_window ?? null);
|
|
4859
|
+
const extraWindows = parseExtraRateLimitWindows(usage.additional_rate_limits);
|
|
4860
|
+
if (!primary && !secondary && extraWindows.length === 0) {
|
|
4861
|
+
return null;
|
|
4862
|
+
}
|
|
4863
|
+
return {
|
|
4864
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4865
|
+
source: "codex-oauth",
|
|
4866
|
+
...primary ? { primary } : {},
|
|
4867
|
+
...secondary ? { secondary } : {},
|
|
4868
|
+
...extraWindows.length > 0 ? { extraWindows } : {}
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
async function fetchRateLimitsViaOAuth(envOverride) {
|
|
4872
|
+
const authPath = resolveCodexAuthPathForEnv(envOverride);
|
|
4873
|
+
const authContent = await readAuthFileCached(authPath);
|
|
4874
|
+
if (!authContent) {
|
|
4875
|
+
return null;
|
|
4876
|
+
}
|
|
4877
|
+
let credentials = parseCodexOAuthCredentials(authContent);
|
|
4878
|
+
if (!credentials) {
|
|
4879
|
+
return null;
|
|
4880
|
+
}
|
|
4881
|
+
if (shouldRefreshCodexOAuthToken(credentials)) {
|
|
4882
|
+
credentials = await refreshCodexOAuthCredentials(credentials);
|
|
4883
|
+
await writeCodexOAuthCredentials(authPath, authContent, credentials);
|
|
4884
|
+
}
|
|
4885
|
+
if (!credentials.accessToken) {
|
|
4886
|
+
return null;
|
|
4887
|
+
}
|
|
4888
|
+
const usageUrl = await resolveCodexUsageUrl(envOverride);
|
|
4889
|
+
const response = await fetch(usageUrl, {
|
|
4890
|
+
method: "GET",
|
|
4891
|
+
headers: {
|
|
4892
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
4893
|
+
Accept: "application/json",
|
|
4894
|
+
"User-Agent": "CodexUse",
|
|
4895
|
+
...credentials.accountId ? { "ChatGPT-Account-Id": credentials.accountId } : {}
|
|
4896
|
+
},
|
|
4897
|
+
signal: AbortSignal.timeout(CODEX_USAGE_TIMEOUT_MS)
|
|
4898
|
+
});
|
|
4899
|
+
const body = await response.text();
|
|
4900
|
+
if (response.status === 401 || response.status === 403) {
|
|
4901
|
+
throw new Error(`Codex OAuth usage failed HTTP ${response.status} (${usageUrl})`);
|
|
4902
|
+
}
|
|
4903
|
+
if (!response.ok) {
|
|
4904
|
+
throw new Error(`Codex usage API failed HTTP ${response.status} (${usageUrl}): ${body.slice(0, 500)}`);
|
|
4905
|
+
}
|
|
4906
|
+
let parsed;
|
|
4907
|
+
try {
|
|
4908
|
+
parsed = JSON.parse(body);
|
|
4909
|
+
} catch {
|
|
4910
|
+
throw new Error("Codex usage API returned invalid JSON");
|
|
4911
|
+
}
|
|
4912
|
+
return parseRateLimitSnapshotFromOAuthUsage(parsed);
|
|
4913
|
+
}
|
|
4520
4914
|
function inferRefreshFailureHint(stderrOutput) {
|
|
4521
4915
|
if (!stderrOutput) {
|
|
4522
4916
|
return null;
|
|
@@ -6129,7 +6523,7 @@ var ProfileManager = class {
|
|
|
6129
6523
|
}
|
|
6130
6524
|
let parsed;
|
|
6131
6525
|
try {
|
|
6132
|
-
parsed = (0,
|
|
6526
|
+
parsed = (0, import_toml2.parse)(raw);
|
|
6133
6527
|
} catch (error) {
|
|
6134
6528
|
logWarn(`Failed to parse config.toml for profile home '${profileHome}':`, error);
|
|
6135
6529
|
return;
|
|
@@ -6139,7 +6533,7 @@ var ProfileManager = class {
|
|
|
6139
6533
|
}
|
|
6140
6534
|
delete parsed["model_reasoning_effort"];
|
|
6141
6535
|
const sanitized = this.ensureTrailingNewline(
|
|
6142
|
-
(0,
|
|
6536
|
+
(0, import_toml2.stringify)(parsed)
|
|
6143
6537
|
);
|
|
6144
6538
|
try {
|
|
6145
6539
|
await import_fs.promises.writeFile(configPath, sanitized, "utf8");
|
|
@@ -6503,7 +6897,16 @@ var ProfileManager = class {
|
|
|
6503
6897
|
profileName,
|
|
6504
6898
|
() => this.runWithPreparedProfileHome(
|
|
6505
6899
|
profileName,
|
|
6506
|
-
(env) =>
|
|
6900
|
+
async (env) => {
|
|
6901
|
+
try {
|
|
6902
|
+
const oauthSnapshot = await fetchRateLimitsViaOAuth(env);
|
|
6903
|
+
if (oauthSnapshot) {
|
|
6904
|
+
return oauthSnapshot;
|
|
6905
|
+
}
|
|
6906
|
+
} catch {
|
|
6907
|
+
}
|
|
6908
|
+
return fetchRateLimitsViaRpc(env, { codexPath: options.codexPath });
|
|
6909
|
+
},
|
|
6507
6910
|
{ syncFromActiveAuthBeforeAction: false }
|
|
6508
6911
|
)
|
|
6509
6912
|
);
|
|
@@ -6899,8 +7302,8 @@ Usage:
|
|
|
6899
7302
|
codexuse license activate <license-key>
|
|
6900
7303
|
|
|
6901
7304
|
codexuse sync status
|
|
6902
|
-
codexuse sync pull
|
|
6903
|
-
codexuse sync push
|
|
7305
|
+
codexuse sync pull [--passphrase-stdin]
|
|
7306
|
+
codexuse sync push [--passphrase-stdin]
|
|
6904
7307
|
|
|
6905
7308
|
Flags:
|
|
6906
7309
|
-h, --help Show help
|
|
@@ -6918,7 +7321,9 @@ Flags:
|
|
|
6918
7321
|
--profile=NAME Run Codex with one saved profile
|
|
6919
7322
|
--runtime=NAME Accounts Pool runtime store: desktop
|
|
6920
7323
|
--state-dir=PATH Override the runtime state dir for Accounts Pool inspection
|
|
7324
|
+
--passphrase-stdin Read Cloud Sync passphrase from stdin
|
|
6921
7325
|
Note: profile autoroll requires Pro.
|
|
7326
|
+
Cloud Sync: set CODEXUSE_SYNC_PASSPHRASE or use --passphrase-stdin unless the passphrase is saved in Keychain.
|
|
6922
7327
|
`);
|
|
6923
7328
|
}
|
|
6924
7329
|
|
|
@@ -7877,7 +8282,8 @@ function maxUsedPercent(snapshot) {
|
|
|
7877
8282
|
}
|
|
7878
8283
|
const candidates = [
|
|
7879
8284
|
snapshot.primary?.usedPercent,
|
|
7880
|
-
snapshot.secondary?.usedPercent
|
|
8285
|
+
snapshot.secondary?.usedPercent,
|
|
8286
|
+
...(snapshot.extraWindows ?? []).map((window) => window.usedPercent)
|
|
7881
8287
|
].filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
7882
8288
|
if (candidates.length === 0) {
|
|
7883
8289
|
return null;
|
|
@@ -8006,7 +8412,8 @@ function formatUsagePercent(value) {
|
|
|
8006
8412
|
function snapshotResetSeconds(snapshot) {
|
|
8007
8413
|
const values = [
|
|
8008
8414
|
snapshot?.primary?.resetsInSeconds,
|
|
8009
|
-
snapshot?.secondary?.resetsInSeconds
|
|
8415
|
+
snapshot?.secondary?.resetsInSeconds,
|
|
8416
|
+
...(snapshot?.extraWindows ?? []).map((window) => window.resetsInSeconds)
|
|
8010
8417
|
].filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
8011
8418
|
return values.length > 0 ? Math.min(...values) : null;
|
|
8012
8419
|
}
|
|
@@ -9147,7 +9554,7 @@ function buildInCodexStatusOverlayCssRules() {
|
|
|
9147
9554
|
`${id}[data-open='false']:not([data-collapsed='true']):hover .codexuse-status-trigger{padding:8px 10px;gap:8px;}`,
|
|
9148
9555
|
`${id}[data-open='false']:not([data-collapsed='true']):hover .codexuse-status-label{max-width:160px;font-size:12px;}`,
|
|
9149
9556
|
`${id}[data-open='false']:not([data-collapsed='true']):hover .codexuse-status-chip{display:inline-flex;}`,
|
|
9150
|
-
`${id} .codexuse-action-menu{display:none;position:absolute;right:0;bottom:calc(100% + 8px);width:min(280px,calc(100vw - 24px));border:1px solid var(--cu-border);border-radius:12px;background:var(--cu-popover);padding:6px;box-shadow:0 12px 36px rgba(0,0,0,.32);}`,
|
|
9557
|
+
`${id} .codexuse-action-menu{display:none;position:absolute;right:0;bottom:calc(100% + 8px);width:min(280px,calc(100vw - 24px));max-height:min(420px,calc(100vh - 32px));overflow-y:auto;border:1px solid var(--cu-border);border-radius:12px;background:var(--cu-popover);padding:6px;box-shadow:0 12px 36px rgba(0,0,0,.32);}`,
|
|
9151
9558
|
`${id}[data-drop='down'] .codexuse-action-menu{bottom:auto;top:calc(100% + 8px);}`,
|
|
9152
9559
|
`${id}[data-open='true'] .codexuse-action-menu{display:flex;flex-direction:column;gap:2px;}`,
|
|
9153
9560
|
`${id} .codexuse-target-list{display:flex;max-height:210px;flex-direction:column;gap:2px;overflow-y:auto;}`,
|
|
@@ -9169,10 +9576,10 @@ function buildInCodexStatusOverlayCssRules() {
|
|
|
9169
9576
|
`${id} .codexuse-intro-detail{margin-top:2px;font-size:10px;color:var(--cu-muted);}`,
|
|
9170
9577
|
`${id} .codexuse-decision-secondary{border:1px solid var(--cu-border)!important;color:var(--cu-muted)!important;}`,
|
|
9171
9578
|
`${id} .codexuse-menu-divider{height:1px;margin:4px 2px;background:var(--cu-border);}`,
|
|
9172
|
-
`${id} .codexuse-command-row{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;padding:6px 9px;border-radius:8px;cursor:pointer;text-align:left;transition:background 120ms ease-out;}`,
|
|
9173
|
-
`${id} .codexuse-command-row:hover{background:var(--cu-hover);}`,
|
|
9174
9579
|
`${id} .codexuse-setting-row{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;padding:6px 9px;border-radius:8px;cursor:pointer;text-align:left;transition:background 120ms ease-out;}`,
|
|
9175
9580
|
`${id} .codexuse-setting-row:hover{background:var(--cu-hover);}`,
|
|
9581
|
+
`${id} .codexuse-setting-copy{display:flex;min-width:0;flex:1;flex-direction:column;gap:1px;}`,
|
|
9582
|
+
`${id} .codexuse-setting-copy .codexuse-target-detail{min-width:0;flex-shrink:1;overflow:hidden;text-overflow:ellipsis;}`,
|
|
9176
9583
|
`${id} .codexuse-toggle{position:relative;flex-shrink:0;width:26px;height:15px;border-radius:999px;background:var(--cu-border);transition:background 120ms ease-out;}`,
|
|
9177
9584
|
`${id} .codexuse-toggle::after{content:'';position:absolute;left:2px;top:2px;width:11px;height:11px;border-radius:50%;background:var(--cu-fg,#ececf1);transition:transform 120ms ease-out;}`,
|
|
9178
9585
|
`${id} .codexuse-setting-row[data-on='true'] .codexuse-toggle{background:var(--cu-accent);}`,
|
|
@@ -9181,13 +9588,17 @@ function buildInCodexStatusOverlayCssRules() {
|
|
|
9181
9588
|
`${id} .codexuse-action-status{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--cu-muted);font-size:10px;}`,
|
|
9182
9589
|
`${id} .codexuse-overlay-hide{flex-shrink:0;padding:2px 6px;border-radius:6px;color:var(--cu-muted);font-size:10px;cursor:pointer;}`,
|
|
9183
9590
|
`${id} .codexuse-overlay-hide:hover{background:var(--cu-hover);color:var(--cu-fg,#ececf1);}`,
|
|
9184
|
-
`${chip}{${palette}position:fixed;z-index:
|
|
9185
|
-
`${chip}:hover{background:var(--cu-hover);}`,
|
|
9186
|
-
`${chip}
|
|
9591
|
+
`${chip}{${palette}position:fixed;z-index:2147483002;display:inline-flex;align-items:center;gap:4px;border-radius:999px;border:1px solid var(--cu-border);background:var(--cu-surface);color:var(--cu-fg,#ececf1);font-family:inherit;font-size:11px;font-weight:600;line-height:1.2;white-space:nowrap;box-shadow:0 4px 16px rgba(0,0,0,.2);transition:background 120ms ease-out;pointer-events:auto;user-select:none;-webkit-user-select:none;}`,
|
|
9592
|
+
`${chip}:hover,${chip}[data-open='true']{background:var(--cu-hover);}`,
|
|
9593
|
+
`${chip}[data-disabled='true']{opacity:.6;}`,
|
|
9594
|
+
`${chip} button{font:inherit;font-family:inherit;color:inherit;border:0;background:transparent;}`,
|
|
9595
|
+
`${chip} .codexuse-handoff-main{display:inline-flex;align-items:center;padding:5px 8px 5px 10px;border-radius:999px;cursor:pointer;}`,
|
|
9596
|
+
`${chip} .codexuse-handoff-hide{display:inline-flex;width:20px;height:20px;align-items:center;justify-content:center;margin-right:3px;border-radius:50%;color:var(--cu-muted);cursor:pointer;font-size:12px;line-height:1;}`,
|
|
9597
|
+
`${chip} .codexuse-handoff-hide:hover{background:var(--cu-border);color:var(--cu-fg,#ececf1);}`,
|
|
9187
9598
|
// Hand-off account picker is a standalone body child (not inside the pill),
|
|
9188
9599
|
// so the pill-scoped popover rules don't reach it — give it its own palette
|
|
9189
9600
|
// + popover styling, mirroring the chip.
|
|
9190
|
-
`${picker}{${palette}position:fixed;z-index:
|
|
9601
|
+
`${picker}{${palette}position:fixed;z-index:2147483003;display:flex;flex-direction:column;gap:2px;width:min(280px,calc(100vw - 24px));max-height:240px;overflow-y:auto;border:1px solid var(--cu-border);border-radius:12px;background:var(--cu-popover);color:var(--cu-fg,#ececf1);font-family:inherit;font-size:12px;line-height:1.4;padding:6px;box-shadow:0 12px 36px rgba(0,0,0,.32);pointer-events:auto;}`,
|
|
9191
9602
|
`${picker} button{font:inherit;font-family:inherit;color:inherit;border:0;background:transparent;}`,
|
|
9192
9603
|
`${picker} .codexuse-target-list{display:flex;flex-direction:column;gap:2px;}`,
|
|
9193
9604
|
`${picker} .codexuse-target-row{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;padding:7px 9px;border-radius:8px;cursor:pointer;text-align:left;transition:background 120ms ease-out;}`,
|
|
@@ -9219,6 +9630,9 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
9219
9630
|
)};
|
|
9220
9631
|
const overlayAutoRollEnabled = ${JSON.stringify(
|
|
9221
9632
|
typeof settings.overlayAutoRollEnabled === "boolean" ? settings.overlayAutoRollEnabled : null
|
|
9633
|
+
)};
|
|
9634
|
+
const overlayRunningThreadHandoffEnabled = ${JSON.stringify(
|
|
9635
|
+
typeof settings.overlayRunningThreadHandoffEnabled === "boolean" ? settings.overlayRunningThreadHandoffEnabled : null
|
|
9222
9636
|
)};
|
|
9223
9637
|
const versions = {
|
|
9224
9638
|
wideView: "3",
|
|
@@ -9560,6 +9974,8 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
9560
9974
|
const overlayHiddenKey = "codexuse:overlay:hidden";
|
|
9561
9975
|
const overlayIntroKey = "codexuse:overlay:intro-shown";
|
|
9562
9976
|
const overlayPosKey = "codexuse:overlay:pos";
|
|
9977
|
+
const handoffHiddenKey = "codexuse:handoff:hidden";
|
|
9978
|
+
const handoffPosKey = "codexuse:handoff:pos:v2";
|
|
9563
9979
|
const readOverlayStore = (key) => {
|
|
9564
9980
|
try {
|
|
9565
9981
|
return window.localStorage.getItem(key);
|
|
@@ -9658,6 +10074,41 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
9658
10074
|
const y = Math.max(72, Math.min(140, Math.round(window.innerHeight * 0.16)));
|
|
9659
10075
|
placeOverlay(pill, x, y);
|
|
9660
10076
|
};
|
|
10077
|
+
const rectsOverlap = (a, b, gap = 0) =>
|
|
10078
|
+
a.left < b.right + gap &&
|
|
10079
|
+
a.right > b.left - gap &&
|
|
10080
|
+
a.top < b.bottom + gap &&
|
|
10081
|
+
a.bottom > b.top - gap;
|
|
10082
|
+
const positionedRect = (rect, pos) => ({
|
|
10083
|
+
left: pos.x,
|
|
10084
|
+
top: pos.y,
|
|
10085
|
+
right: pos.x + rect.width,
|
|
10086
|
+
bottom: pos.y + rect.height
|
|
10087
|
+
});
|
|
10088
|
+
const placeInitialHandoffPosition = (chip, anchorRect = null) => {
|
|
10089
|
+
const rect = chip.getBoundingClientRect();
|
|
10090
|
+
if (anchorRect && rect.width > 0 && rect.height > 0) {
|
|
10091
|
+
const gap = 8;
|
|
10092
|
+
const middleY = Math.round(anchorRect.top + (anchorRect.height - rect.height) / 2);
|
|
10093
|
+
const candidates = [
|
|
10094
|
+
{ x: anchorRect.right + gap, y: middleY },
|
|
10095
|
+
{ x: anchorRect.left - rect.width - gap, y: middleY },
|
|
10096
|
+
{ x: anchorRect.right - rect.width, y: anchorRect.top - rect.height - gap },
|
|
10097
|
+
{ x: anchorRect.left, y: anchorRect.top - rect.height - gap },
|
|
10098
|
+
{ x: anchorRect.right - rect.width, y: anchorRect.bottom + gap }
|
|
10099
|
+
];
|
|
10100
|
+
for (const candidate of candidates) {
|
|
10101
|
+
const next = clampOverlayXY(chip, candidate.x, candidate.y);
|
|
10102
|
+
if (!rectsOverlap(positionedRect(rect, next), anchorRect, 6)) {
|
|
10103
|
+
placeOverlay(chip, next.x, next.y);
|
|
10104
|
+
return;
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
10107
|
+
}
|
|
10108
|
+
const x = Math.max(8, window.innerWidth - rect.width - 16);
|
|
10109
|
+
const y = Math.max(64, Math.min(128, Math.round(window.innerHeight * 0.14)));
|
|
10110
|
+
placeOverlay(chip, x, y);
|
|
10111
|
+
};
|
|
9661
10112
|
const setOverlayMessage = (value) => {
|
|
9662
10113
|
const node = document.querySelector("#" + statusId + " [data-codexuse-action-status]");
|
|
9663
10114
|
if (node) node.textContent = value || "";
|
|
@@ -9701,7 +10152,7 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
9701
10152
|
state.lastActionRequest = payload;
|
|
9702
10153
|
setOverlayMessage(
|
|
9703
10154
|
action === "switch" ? "Switching\u2026"
|
|
9704
|
-
: action === "continue" ? "
|
|
10155
|
+
: action === "continue" ? "Continuing in another account\u2026"
|
|
9705
10156
|
: action === "auto-roll-accept" ? "Switching\u2026"
|
|
9706
10157
|
: action === "auto-roll-cancel" ? "Staying here"
|
|
9707
10158
|
: action === "restart" ? "Waiting to restart; keeping draft\u2026"
|
|
@@ -9862,7 +10313,7 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
9862
10313
|
introTitle.textContent = "CodexUse controls";
|
|
9863
10314
|
const introDetail = document.createElement("div");
|
|
9864
10315
|
introDetail.className = "codexuse-intro-detail";
|
|
9865
|
-
introDetail.textContent = "Switch accounts,
|
|
10316
|
+
introDetail.textContent = "Switch accounts, tune CodexUse controls, and confirm Auto-roll from here. Drag to move, or Hide to dismiss.";
|
|
9866
10317
|
intro.appendChild(introTitle);
|
|
9867
10318
|
intro.appendChild(introDetail);
|
|
9868
10319
|
menu.appendChild(intro);
|
|
@@ -9986,34 +10437,92 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
9986
10437
|
empty.textContent = "No other account ready";
|
|
9987
10438
|
menu.appendChild(empty);
|
|
9988
10439
|
}
|
|
9989
|
-
const
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10440
|
+
const appendSettingRow = (item) => {
|
|
10441
|
+
const row = document.createElement("button");
|
|
10442
|
+
row.type = "button";
|
|
10443
|
+
row.className = "codexuse-setting-row";
|
|
10444
|
+
row.dataset.key = item.key;
|
|
10445
|
+
row.dataset.on = item.enabled ? "true" : "false";
|
|
10446
|
+
row.title = item.detail;
|
|
10447
|
+
const copy = document.createElement("span");
|
|
10448
|
+
copy.className = "codexuse-setting-copy";
|
|
10449
|
+
const label = document.createElement("span");
|
|
10450
|
+
label.className = "codexuse-target-label";
|
|
10451
|
+
label.textContent = item.label;
|
|
10452
|
+
copy.appendChild(label);
|
|
10453
|
+
const detail = document.createElement("span");
|
|
10454
|
+
detail.className = "codexuse-target-detail";
|
|
10455
|
+
detail.textContent = item.detail;
|
|
10456
|
+
copy.appendChild(detail);
|
|
10457
|
+
const toggle = document.createElement("span");
|
|
10458
|
+
toggle.className = "codexuse-toggle";
|
|
10459
|
+
row.appendChild(copy);
|
|
10460
|
+
row.appendChild(toggle);
|
|
10461
|
+
row.addEventListener("click", (event) => {
|
|
10462
|
+
event.preventDefault();
|
|
10463
|
+
event.stopPropagation();
|
|
10464
|
+
const next = row.dataset.on !== "true";
|
|
10465
|
+
row.dataset.on = next ? "true" : "false";
|
|
10466
|
+
requestAction("setting", null, { key: item.key, value: next });
|
|
10467
|
+
}, true);
|
|
10468
|
+
menu.appendChild(row);
|
|
10469
|
+
};
|
|
10470
|
+
const settingRows = [
|
|
10471
|
+
{
|
|
10472
|
+
key: "autoRoll",
|
|
10473
|
+
label: "Auto-roll",
|
|
10474
|
+
detail: "Switch when quota is low",
|
|
10475
|
+
enabled: overlayAutoRollEnabled === true,
|
|
10476
|
+
},
|
|
10477
|
+
{
|
|
10478
|
+
key: "runningThreadHandoff",
|
|
10479
|
+
label: "Continue running threads",
|
|
10480
|
+
detail: "Beta \xB7 after account switch",
|
|
10481
|
+
enabled: overlayRunningThreadHandoffEnabled === true,
|
|
10482
|
+
},
|
|
10483
|
+
{
|
|
10484
|
+
key: "wideView",
|
|
10485
|
+
label: "Wide conversations",
|
|
10486
|
+
detail: "More message room",
|
|
10487
|
+
enabled: wideViewEnabled,
|
|
10488
|
+
},
|
|
10489
|
+
{
|
|
10490
|
+
key: "scrollRestore",
|
|
10491
|
+
label: "Remember position",
|
|
10492
|
+
detail: "Return to last spot",
|
|
10493
|
+
enabled: scrollRestoreEnabled,
|
|
10494
|
+
},
|
|
10495
|
+
{
|
|
10496
|
+
key: "conversationTimeline",
|
|
10497
|
+
label: "Jump to user turns",
|
|
10498
|
+
detail: "Show turn markers",
|
|
10499
|
+
enabled: conversationTimelineEnabled,
|
|
10500
|
+
},
|
|
10501
|
+
];
|
|
10502
|
+
const divider = document.createElement("div");
|
|
10503
|
+
divider.className = "codexuse-menu-divider";
|
|
10504
|
+
menu.appendChild(divider);
|
|
10505
|
+
settingRows.forEach(appendSettingRow);
|
|
10011
10506
|
const footer = document.createElement("div");
|
|
10012
10507
|
footer.className = "codexuse-menu-footer";
|
|
10013
10508
|
const actionStatus = document.createElement("span");
|
|
10014
10509
|
actionStatus.className = "codexuse-action-status";
|
|
10015
10510
|
actionStatus.setAttribute("data-codexuse-action-status", "true");
|
|
10016
10511
|
footer.appendChild(actionStatus);
|
|
10512
|
+
if (handoffChipEnabled && readOverlayStore(handoffHiddenKey) === "1") {
|
|
10513
|
+
const showHandoff = document.createElement("button");
|
|
10514
|
+
showHandoff.type = "button";
|
|
10515
|
+
showHandoff.className = "codexuse-overlay-hide";
|
|
10516
|
+
showHandoff.textContent = "Show handoff";
|
|
10517
|
+
showHandoff.addEventListener("click", (event) => {
|
|
10518
|
+
event.preventDefault();
|
|
10519
|
+
event.stopPropagation();
|
|
10520
|
+
writeOverlayStore(handoffHiddenKey, null);
|
|
10521
|
+
renderStatus();
|
|
10522
|
+
renderHandoff();
|
|
10523
|
+
}, true);
|
|
10524
|
+
footer.appendChild(showHandoff);
|
|
10525
|
+
}
|
|
10017
10526
|
const hide = document.createElement("button");
|
|
10018
10527
|
hide.type = "button";
|
|
10019
10528
|
hide.className = "codexuse-overlay-hide";
|
|
@@ -10039,7 +10548,7 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10039
10548
|
}
|
|
10040
10549
|
return true;
|
|
10041
10550
|
};
|
|
10042
|
-
// Thread-scoped hand-off control:
|
|
10551
|
+
// Thread-scoped hand-off control: separate draggable chip, only shown when
|
|
10043
10552
|
// a conversation is on screen and another account is ready to take over.
|
|
10044
10553
|
// Log the hand-off chip's visibility gate only when it CHANGES \u2014 this runs
|
|
10045
10554
|
// on every mutation tick, and the reason is the first question every
|
|
@@ -10049,9 +10558,17 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10049
10558
|
state.lastHandoffReason = reason;
|
|
10050
10559
|
cuLog("handoff.render", { reason, targets: switchable.length });
|
|
10051
10560
|
};
|
|
10561
|
+
let cleanupHandoffPicker = null;
|
|
10052
10562
|
const renderHandoff = () => {
|
|
10053
|
-
document.getElementById(handoffId)
|
|
10563
|
+
const previous = document.getElementById(handoffId);
|
|
10564
|
+
if (previous?.dataset.dragging === "true") return true;
|
|
10565
|
+
const wasOpen = previous?.dataset.open === "true";
|
|
10566
|
+
previous?.remove();
|
|
10054
10567
|
document.getElementById(handoffPickerId)?.remove();
|
|
10568
|
+
if (typeof cleanupHandoffPicker === "function") {
|
|
10569
|
+
cleanupHandoffPicker();
|
|
10570
|
+
cleanupHandoffPicker = null;
|
|
10571
|
+
}
|
|
10055
10572
|
if (!inCodexStatusEnabled || !handoffChipEnabled) {
|
|
10056
10573
|
reportHandoff("disabled");
|
|
10057
10574
|
return false;
|
|
@@ -10060,6 +10577,10 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10060
10577
|
reportHandoff("overlay-hidden");
|
|
10061
10578
|
return false;
|
|
10062
10579
|
}
|
|
10580
|
+
if (readOverlayStore(handoffHiddenKey) === "1") {
|
|
10581
|
+
reportHandoff("handoff-hidden");
|
|
10582
|
+
return false;
|
|
10583
|
+
}
|
|
10063
10584
|
// Codex desktop marks turns with data-turn-key / data-user-message-bubble;
|
|
10064
10585
|
// the ChatGPT-style selectors are kept as a fallback.
|
|
10065
10586
|
const inThread = Boolean(domActiveThreadId() || document.querySelector(
|
|
@@ -10076,22 +10597,28 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10076
10597
|
reportHandoff("no-composer");
|
|
10077
10598
|
return false;
|
|
10078
10599
|
}
|
|
10079
|
-
const
|
|
10080
|
-
const rect = composer.getBoundingClientRect();
|
|
10600
|
+
const rect = textbox.getBoundingClientRect();
|
|
10081
10601
|
if (rect.width <= 0 || rect.height <= 0) {
|
|
10082
10602
|
reportHandoff("composer-hidden");
|
|
10083
10603
|
return false;
|
|
10084
10604
|
}
|
|
10085
|
-
const
|
|
10086
|
-
const chipRight = Math.max(8, window.innerWidth - rect.right);
|
|
10087
|
-
const chip = document.createElement("button");
|
|
10088
|
-
chip.type = "button";
|
|
10605
|
+
const chip = document.createElement("div");
|
|
10089
10606
|
chip.id = handoffId;
|
|
10090
10607
|
applyOverlayTheme(chip);
|
|
10091
|
-
chip.
|
|
10092
|
-
chip.
|
|
10093
|
-
|
|
10094
|
-
|
|
10608
|
+
chip.title = "Choose an account, then continue this thread there";
|
|
10609
|
+
chip.dataset.open = wasOpen ? "true" : "false";
|
|
10610
|
+
const chipMain = document.createElement("button");
|
|
10611
|
+
chipMain.type = "button";
|
|
10612
|
+
chipMain.className = "codexuse-handoff-main";
|
|
10613
|
+
chipMain.textContent = "Continue in another account";
|
|
10614
|
+
const chipHide = document.createElement("button");
|
|
10615
|
+
chipHide.type = "button";
|
|
10616
|
+
chipHide.className = "codexuse-handoff-hide";
|
|
10617
|
+
chipHide.textContent = "x";
|
|
10618
|
+
chipHide.title = "Hide Continue in another account";
|
|
10619
|
+
chipHide.setAttribute("aria-label", "Hide Continue in another account");
|
|
10620
|
+
chip.appendChild(chipMain);
|
|
10621
|
+
chip.appendChild(chipHide);
|
|
10095
10622
|
// Hand off needs BOTH a thread (this chip only shows in-thread) and a
|
|
10096
10623
|
// target account. Clicking opens a picker of ready accounts; the chosen
|
|
10097
10624
|
// account becomes the explicit continue target (the request also carries
|
|
@@ -10099,26 +10626,47 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10099
10626
|
// the "continue" message is auto-sent there).
|
|
10100
10627
|
const closePicker = () => {
|
|
10101
10628
|
document.getElementById(handoffPickerId)?.remove();
|
|
10629
|
+
if (typeof cleanupHandoffPicker === "function") {
|
|
10630
|
+
cleanupHandoffPicker();
|
|
10631
|
+
cleanupHandoffPicker = null;
|
|
10632
|
+
}
|
|
10102
10633
|
chip.dataset.open = "false";
|
|
10103
10634
|
};
|
|
10104
10635
|
const handOffTo = (target) => {
|
|
10105
10636
|
closePicker();
|
|
10106
|
-
chip.disabled = true;
|
|
10107
|
-
|
|
10637
|
+
chip.dataset.disabled = "true";
|
|
10638
|
+
chipMain.textContent = "Continuing...";
|
|
10108
10639
|
requestAction("continue", target);
|
|
10109
10640
|
};
|
|
10641
|
+
const placePicker = (picker) => {
|
|
10642
|
+
const chipRect = chip.getBoundingClientRect();
|
|
10643
|
+
const pickerRect = picker.getBoundingClientRect();
|
|
10644
|
+
const width = pickerRect.width || Math.min(280, window.innerWidth - 24);
|
|
10645
|
+
const height = pickerRect.height || 120;
|
|
10646
|
+
const left = Math.min(
|
|
10647
|
+
Math.max(8, chipRect.right - width),
|
|
10648
|
+
Math.max(8, window.innerWidth - width - 8)
|
|
10649
|
+
);
|
|
10650
|
+
const below = chipRect.bottom + 6;
|
|
10651
|
+
const above = chipRect.top - height - 6;
|
|
10652
|
+
const top = below + height <= window.innerHeight - 8
|
|
10653
|
+
? below
|
|
10654
|
+
: Math.max(8, above);
|
|
10655
|
+
picker.style.left = left + "px";
|
|
10656
|
+
picker.style.top = Math.min(top, Math.max(8, window.innerHeight - height - 8)) + "px";
|
|
10657
|
+
picker.style.right = "auto";
|
|
10658
|
+
picker.style.bottom = "auto";
|
|
10659
|
+
};
|
|
10110
10660
|
const openPicker = (skipRefresh = false) => {
|
|
10111
10661
|
document.getElementById(handoffPickerId)?.remove();
|
|
10662
|
+
if (typeof cleanupHandoffPicker === "function") {
|
|
10663
|
+
cleanupHandoffPicker();
|
|
10664
|
+
cleanupHandoffPicker = null;
|
|
10665
|
+
}
|
|
10112
10666
|
const picker = document.createElement("div");
|
|
10113
10667
|
picker.id = handoffPickerId;
|
|
10114
10668
|
picker.className = "codexuse-action-menu";
|
|
10115
10669
|
applyOverlayTheme(picker);
|
|
10116
|
-
picker.style.position = "fixed";
|
|
10117
|
-
picker.style.right = chipRight + "px";
|
|
10118
|
-
picker.style.bottom = Math.max(8, window.innerHeight - chipTop + 4) + "px";
|
|
10119
|
-
picker.style.maxHeight = "240px";
|
|
10120
|
-
picker.style.overflowY = "auto";
|
|
10121
|
-
picker.style.zIndex = "2147483647";
|
|
10122
10670
|
const list = document.createElement("div");
|
|
10123
10671
|
list.className = "codexuse-target-list";
|
|
10124
10672
|
if (switchable.length === 0) {
|
|
@@ -10131,7 +10679,7 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10131
10679
|
const row = document.createElement("button");
|
|
10132
10680
|
row.type = "button";
|
|
10133
10681
|
row.className = "codexuse-target-row";
|
|
10134
|
-
row.title = "Continue this thread
|
|
10682
|
+
row.title = "Continue this thread in " + (target.label || "account");
|
|
10135
10683
|
const label = document.createElement("span");
|
|
10136
10684
|
label.className = "codexuse-target-label";
|
|
10137
10685
|
label.textContent = typeof target.label === "string" && target.label.trim() ? target.label.trim() : "Account";
|
|
@@ -10145,12 +10693,14 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10145
10693
|
row.addEventListener("click", (event) => {
|
|
10146
10694
|
event.preventDefault();
|
|
10147
10695
|
event.stopPropagation();
|
|
10696
|
+
event.stopImmediatePropagation?.();
|
|
10148
10697
|
handOffTo(target);
|
|
10149
10698
|
}, true);
|
|
10150
10699
|
list.appendChild(row);
|
|
10151
10700
|
});
|
|
10152
10701
|
picker.appendChild(list);
|
|
10153
10702
|
document.body.appendChild(picker);
|
|
10703
|
+
placePicker(picker);
|
|
10154
10704
|
chip.dataset.open = "true";
|
|
10155
10705
|
// Rebuild the open picker once fresh targets arrive; skipRefresh stops
|
|
10156
10706
|
// the rebuilt picker from requesting again in a loop.
|
|
@@ -10161,14 +10711,56 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10161
10711
|
}
|
|
10162
10712
|
const onDocPointer = (event) => {
|
|
10163
10713
|
if (picker.contains(event.target) || chip.contains(event.target)) return;
|
|
10164
|
-
document.removeEventListener("pointerdown", onDocPointer, true);
|
|
10165
10714
|
closePicker();
|
|
10166
10715
|
};
|
|
10716
|
+
const onKeyDown = (event) => {
|
|
10717
|
+
if (event.key === "Escape") closePicker();
|
|
10718
|
+
};
|
|
10167
10719
|
document.addEventListener("pointerdown", onDocPointer, true);
|
|
10720
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
10721
|
+
cleanupHandoffPicker = () => {
|
|
10722
|
+
document.removeEventListener("pointerdown", onDocPointer, true);
|
|
10723
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
10724
|
+
};
|
|
10168
10725
|
};
|
|
10169
|
-
|
|
10726
|
+
let dragMoved = false;
|
|
10727
|
+
chip.addEventListener("pointerdown", (event) => {
|
|
10728
|
+
if (event.button !== 0 || event.target?.closest?.(".codexuse-handoff-hide")) return;
|
|
10729
|
+
event.stopPropagation();
|
|
10730
|
+
event.stopImmediatePropagation?.();
|
|
10731
|
+
const startX = event.clientX;
|
|
10732
|
+
const startY = event.clientY;
|
|
10733
|
+
const startRect = chip.getBoundingClientRect();
|
|
10734
|
+
const offsetX = startX - startRect.left;
|
|
10735
|
+
const offsetY = startY - startRect.top;
|
|
10736
|
+
dragMoved = false;
|
|
10737
|
+
const onMove = (moveEvent) => {
|
|
10738
|
+
if (!dragMoved && Math.hypot(moveEvent.clientX - startX, moveEvent.clientY - startY) < 4) return;
|
|
10739
|
+
dragMoved = true;
|
|
10740
|
+
chip.dataset.dragging = "true";
|
|
10741
|
+
closePicker();
|
|
10742
|
+
placeOverlay(chip, moveEvent.clientX - offsetX, moveEvent.clientY - offsetY);
|
|
10743
|
+
};
|
|
10744
|
+
const onUp = () => {
|
|
10745
|
+
window.removeEventListener("pointermove", onMove, true);
|
|
10746
|
+
window.removeEventListener("pointerup", onUp, true);
|
|
10747
|
+
if (chip.dataset.dragging === "true") {
|
|
10748
|
+
delete chip.dataset.dragging;
|
|
10749
|
+
const chipRect = chip.getBoundingClientRect();
|
|
10750
|
+
writeOverlayStore(handoffPosKey, JSON.stringify({ x: chipRect.left, y: chipRect.top }));
|
|
10751
|
+
}
|
|
10752
|
+
};
|
|
10753
|
+
window.addEventListener("pointermove", onMove, true);
|
|
10754
|
+
window.addEventListener("pointerup", onUp, true);
|
|
10755
|
+
}, true);
|
|
10756
|
+
chipMain.addEventListener("click", (event) => {
|
|
10170
10757
|
event.preventDefault();
|
|
10171
10758
|
event.stopPropagation();
|
|
10759
|
+
event.stopImmediatePropagation?.();
|
|
10760
|
+
if (dragMoved || chip.dataset.disabled === "true") {
|
|
10761
|
+
dragMoved = false;
|
|
10762
|
+
return;
|
|
10763
|
+
}
|
|
10172
10764
|
// Always make the user pick the destination account explicitly \u2014 never
|
|
10173
10765
|
// auto-hand-off to a server-chosen account (which could be out of quota).
|
|
10174
10766
|
if (chip.dataset.open === "true") {
|
|
@@ -10177,7 +10769,38 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10177
10769
|
openPicker();
|
|
10178
10770
|
}
|
|
10179
10771
|
}, true);
|
|
10772
|
+
chipHide.addEventListener("click", (event) => {
|
|
10773
|
+
event.preventDefault();
|
|
10774
|
+
event.stopPropagation();
|
|
10775
|
+
event.stopImmediatePropagation?.();
|
|
10776
|
+
writeOverlayStore(handoffHiddenKey, "1");
|
|
10777
|
+
closePicker();
|
|
10778
|
+
renderHandoff();
|
|
10779
|
+
renderStatus();
|
|
10780
|
+
}, true);
|
|
10180
10781
|
document.body.appendChild(chip);
|
|
10782
|
+
const rawPos = readOverlayStore(handoffPosKey);
|
|
10783
|
+
if (rawPos) {
|
|
10784
|
+
try {
|
|
10785
|
+
const pos = JSON.parse(rawPos);
|
|
10786
|
+
if (typeof pos?.x === "number" && typeof pos?.y === "number") {
|
|
10787
|
+
const next = clampOverlayXY(chip, pos.x, pos.y);
|
|
10788
|
+
const chipRect = chip.getBoundingClientRect();
|
|
10789
|
+
if (rectsOverlap(positionedRect(chipRect, next), rect, 6)) {
|
|
10790
|
+
placeInitialHandoffPosition(chip, rect);
|
|
10791
|
+
} else {
|
|
10792
|
+
placeOverlay(chip, next.x, next.y);
|
|
10793
|
+
}
|
|
10794
|
+
} else {
|
|
10795
|
+
placeInitialHandoffPosition(chip, rect);
|
|
10796
|
+
}
|
|
10797
|
+
} catch {
|
|
10798
|
+
placeInitialHandoffPosition(chip, rect);
|
|
10799
|
+
}
|
|
10800
|
+
} else {
|
|
10801
|
+
placeInitialHandoffPosition(chip, rect);
|
|
10802
|
+
}
|
|
10803
|
+
if (wasOpen) openPicker();
|
|
10181
10804
|
reportHandoff("rendered");
|
|
10182
10805
|
return true;
|
|
10183
10806
|
};
|
|
@@ -10229,12 +10852,16 @@ function createBridgeApplyQolExpression(settings) {
|
|
|
10229
10852
|
clearTimelineTargets();
|
|
10230
10853
|
document.getElementById(statusId)?.remove();
|
|
10231
10854
|
document.getElementById(handoffId)?.remove();
|
|
10855
|
+
document.getElementById(handoffPickerId)?.remove();
|
|
10856
|
+
if (typeof cleanupHandoffPicker === "function") cleanupHandoffPicker();
|
|
10232
10857
|
};
|
|
10233
10858
|
} else {
|
|
10234
10859
|
document.querySelectorAll("." + timelineClass).forEach((node) => node.remove());
|
|
10235
10860
|
clearTimelineTargets();
|
|
10236
10861
|
document.getElementById(statusId)?.remove();
|
|
10237
10862
|
document.getElementById(handoffId)?.remove();
|
|
10863
|
+
document.getElementById(handoffPickerId)?.remove();
|
|
10864
|
+
if (typeof cleanupHandoffPicker === "function") cleanupHandoffPicker();
|
|
10238
10865
|
}
|
|
10239
10866
|
|
|
10240
10867
|
state.wideViewEnabled = wideViewEnabled;
|
|
@@ -11529,7 +12156,8 @@ function normalizeOfficialCodexQolSettings2(value) {
|
|
|
11529
12156
|
actionTargets,
|
|
11530
12157
|
canonicalSyncLine: typeof value.canonicalSyncLine === "string" && value.canonicalSyncLine.trim() ? value.canonicalSyncLine.trim().slice(0, 40) : null,
|
|
11531
12158
|
autoRollDecision: normalizeOverlayAutoRollDecision(value.autoRollDecision),
|
|
11532
|
-
overlayAutoRollEnabled: typeof value.overlayAutoRollEnabled === "boolean" ? value.overlayAutoRollEnabled : null
|
|
12159
|
+
overlayAutoRollEnabled: typeof value.overlayAutoRollEnabled === "boolean" ? value.overlayAutoRollEnabled : null,
|
|
12160
|
+
overlayRunningThreadHandoffEnabled: typeof value.overlayRunningThreadHandoffEnabled === "boolean" ? value.overlayRunningThreadHandoffEnabled : null
|
|
11533
12161
|
};
|
|
11534
12162
|
}
|
|
11535
12163
|
function hasActiveQolSettings(settings) {
|
|
@@ -12356,7 +12984,7 @@ function startOfficialCodexBridgeActionListener(args) {
|
|
|
12356
12984
|
if (!action) {
|
|
12357
12985
|
return;
|
|
12358
12986
|
}
|
|
12359
|
-
const settingKey = payload.settingKey === "autoRoll" || payload.settingKey === "wideView" || payload.settingKey === "scrollRestore" || payload.settingKey === "conversationTimeline" || payload.settingKey === "handoffChip" ? payload.settingKey : null;
|
|
12987
|
+
const settingKey = payload.settingKey === "autoRoll" || payload.settingKey === "runningThreadHandoff" || payload.settingKey === "wideView" || payload.settingKey === "scrollRestore" || payload.settingKey === "conversationTimeline" || payload.settingKey === "handoffChip" ? payload.settingKey : null;
|
|
12360
12988
|
const settingValue = typeof payload.settingValue === "boolean" ? payload.settingValue : null;
|
|
12361
12989
|
if (action === "setting" && (!settingKey || settingValue === null)) {
|
|
12362
12990
|
return;
|
|
@@ -12743,9 +13371,12 @@ function isMainProcessRow(row, candidate) {
|
|
|
12743
13371
|
}
|
|
12744
13372
|
function findAppServerPid(rows, mainPid) {
|
|
12745
13373
|
return rows.find(
|
|
12746
|
-
(row) => row.ppid === mainPid && row
|
|
13374
|
+
(row) => row.ppid === mainPid && isAppServerProcessRow(row)
|
|
12747
13375
|
)?.pid ?? null;
|
|
12748
13376
|
}
|
|
13377
|
+
function isAppServerProcessRow(row) {
|
|
13378
|
+
return row.args.includes("/Contents/Resources/codex app-server");
|
|
13379
|
+
}
|
|
12749
13380
|
async function processMatchesProfileHome(pid, profileHome) {
|
|
12750
13381
|
const result = await runCommand2(
|
|
12751
13382
|
"/bin/ps",
|
|
@@ -12759,7 +13390,7 @@ async function processMatchesProfileHome(pid, profileHome) {
|
|
|
12759
13390
|
}
|
|
12760
13391
|
async function findAppServerPidForProfile(rows, mainPid, profileHome) {
|
|
12761
13392
|
const candidates = rows.filter(
|
|
12762
|
-
(row) => row.ppid === mainPid && row
|
|
13393
|
+
(row) => row.ppid === mainPid && isAppServerProcessRow(row)
|
|
12763
13394
|
);
|
|
12764
13395
|
for (const row of candidates) {
|
|
12765
13396
|
if (row.args.includes(profileHome) || await processMatchesProfileHome(row.pid, profileHome)) {
|
|
@@ -12768,6 +13399,19 @@ async function findAppServerPidForProfile(rows, mainPid, profileHome) {
|
|
|
12768
13399
|
}
|
|
12769
13400
|
return null;
|
|
12770
13401
|
}
|
|
13402
|
+
async function resolveStoredAppServerPid(instance, rows) {
|
|
13403
|
+
if (!instance.appServerPid) {
|
|
13404
|
+
return null;
|
|
13405
|
+
}
|
|
13406
|
+
const row = rows.find((entry) => entry.pid === instance.appServerPid);
|
|
13407
|
+
if (!row || !isAppServerProcessRow(row)) {
|
|
13408
|
+
return null;
|
|
13409
|
+
}
|
|
13410
|
+
if (instance.profileHome && !row.args.includes(instance.profileHome) && !await processMatchesProfileHome(row.pid, instance.profileHome)) {
|
|
13411
|
+
return null;
|
|
13412
|
+
}
|
|
13413
|
+
return row.pid;
|
|
13414
|
+
}
|
|
12771
13415
|
function parseRemoteDebuggingPort(args) {
|
|
12772
13416
|
const match = args.match(/--remote-debugging-port(?:=|\s+)(\d{2,5})\b/);
|
|
12773
13417
|
const port = Number(match?.[1]);
|
|
@@ -12896,11 +13540,27 @@ async function readManagedInstance(profileName) {
|
|
|
12896
13540
|
async function resolveInstanceRuntimeState(args) {
|
|
12897
13541
|
const pid = args.instance.pid;
|
|
12898
13542
|
if (!pid) {
|
|
12899
|
-
|
|
13543
|
+
const appServerPid2 = await resolveStoredAppServerPid(
|
|
13544
|
+
args.instance,
|
|
13545
|
+
args.rows
|
|
13546
|
+
);
|
|
13547
|
+
return {
|
|
13548
|
+
running: appServerPid2 !== null,
|
|
13549
|
+
appServerPid: appServerPid2,
|
|
13550
|
+
startedAt: null
|
|
13551
|
+
};
|
|
12900
13552
|
}
|
|
12901
13553
|
const mainRow = args.rows.find((row) => row.pid === pid);
|
|
12902
13554
|
if (!mainRow || !isMainProcessRow(mainRow, args.candidate)) {
|
|
12903
|
-
|
|
13555
|
+
const appServerPid2 = await resolveStoredAppServerPid(
|
|
13556
|
+
args.instance,
|
|
13557
|
+
args.rows
|
|
13558
|
+
);
|
|
13559
|
+
return {
|
|
13560
|
+
running: appServerPid2 !== null,
|
|
13561
|
+
appServerPid: appServerPid2,
|
|
13562
|
+
startedAt: null
|
|
13563
|
+
};
|
|
12904
13564
|
}
|
|
12905
13565
|
const appServerPid = args.instance.profileHome ? await findAppServerPidForProfile(args.rows, pid, args.instance.profileHome) : findAppServerPid(args.rows, pid);
|
|
12906
13566
|
if (args.instance.profileHome && appServerPid === null) {
|
|
@@ -13813,8 +14473,8 @@ function formatResetText(window) {
|
|
|
13813
14473
|
if (durationText === "now") return "now";
|
|
13814
14474
|
return `in ${durationText}`;
|
|
13815
14475
|
}
|
|
13816
|
-
function formatWindowUsage(window) {
|
|
13817
|
-
const label = formatWindowLabel(window.windowMinutes) ?? "window";
|
|
14476
|
+
function formatWindowUsage(window, labelOverride) {
|
|
14477
|
+
const label = labelOverride ?? formatWindowLabel(window.windowMinutes) ?? "window";
|
|
13818
14478
|
const usedPercent = typeof window.usedPercent === "number" && Number.isFinite(window.usedPercent) ? `${Math.round(Math.max(0, Math.min(100, window.usedPercent)))}%` : "n/a";
|
|
13819
14479
|
const resetText = formatResetText(window);
|
|
13820
14480
|
return {
|
|
@@ -13943,6 +14603,10 @@ async function resolveProfileUsage(manager, profile, codexPath) {
|
|
|
13943
14603
|
const row = formatWindowUsage(snapshot.secondary);
|
|
13944
14604
|
if (row) rows.push(row);
|
|
13945
14605
|
}
|
|
14606
|
+
for (const window of snapshot.extraWindows ?? []) {
|
|
14607
|
+
const row = formatWindowUsage(window, window.label);
|
|
14608
|
+
if (row) rows.push(row);
|
|
14609
|
+
}
|
|
13946
14610
|
const maxPercent = maxUsedPercent(snapshot);
|
|
13947
14611
|
const summary = typeof maxPercent === "number" ? `${Math.round(maxPercent)}%` : "n/a";
|
|
13948
14612
|
return { summary, rows: rows.length > 0 ? rows : null, usageSummary: summarizeRateLimitSnapshot(snapshot) };
|
|
@@ -14685,7 +15349,8 @@ async function handleProfileCommand(args, version) {
|
|
|
14685
15349
|
}
|
|
14686
15350
|
|
|
14687
15351
|
// ../../packages/contracts/src/cloud-sync/types.ts
|
|
14688
|
-
var
|
|
15352
|
+
var CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION = 1;
|
|
15353
|
+
var CLOUD_SYNC_ENCRYPTED_SCHEMA_VERSION = 2;
|
|
14689
15354
|
|
|
14690
15355
|
// ../../packages/shared/src/core/type-guards.ts
|
|
14691
15356
|
function isRecord6(value) {
|
|
@@ -14725,6 +15390,12 @@ function buildSyncUrl(pathname) {
|
|
|
14725
15390
|
const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
14726
15391
|
return `${baseUrl}${normalizedPath}`;
|
|
14727
15392
|
}
|
|
15393
|
+
function normalizeSnapshotKind(value) {
|
|
15394
|
+
if (value === "encrypted" || value === "legacy-plaintext") {
|
|
15395
|
+
return value;
|
|
15396
|
+
}
|
|
15397
|
+
return "none";
|
|
15398
|
+
}
|
|
14728
15399
|
function normalizeSnapshot(value) {
|
|
14729
15400
|
if (!isRecord6(value)) {
|
|
14730
15401
|
return null;
|
|
@@ -14734,7 +15405,37 @@ function normalizeSnapshot(value) {
|
|
|
14734
15405
|
return null;
|
|
14735
15406
|
}
|
|
14736
15407
|
const schemaVersion = Number(value.schemaVersion);
|
|
14737
|
-
if (
|
|
15408
|
+
if (schemaVersion === CLOUD_SYNC_ENCRYPTED_SCHEMA_VERSION) {
|
|
15409
|
+
if (!isRecord6(value.encryption)) {
|
|
15410
|
+
return null;
|
|
15411
|
+
}
|
|
15412
|
+
const encryption = value.encryption;
|
|
15413
|
+
const params = isRecord6(encryption.kdfParams) ? encryption.kdfParams : null;
|
|
15414
|
+
if (!params) {
|
|
15415
|
+
return null;
|
|
15416
|
+
}
|
|
15417
|
+
const N = Number(params.N);
|
|
15418
|
+
const r = Number(params.r);
|
|
15419
|
+
const p = Number(params.p);
|
|
15420
|
+
const keyLength = Number(params.keyLength);
|
|
15421
|
+
if (encryption.cipher !== "aes-256-gcm" || encryption.kdf !== "scrypt" || !Number.isSafeInteger(N) || !Number.isSafeInteger(r) || !Number.isSafeInteger(p) || !Number.isSafeInteger(keyLength) || keyLength !== 32 || typeof encryption.salt !== "string" || typeof encryption.iv !== "string" || typeof encryption.tag !== "string" || typeof encryption.ciphertext !== "string") {
|
|
15422
|
+
return null;
|
|
15423
|
+
}
|
|
15424
|
+
return {
|
|
15425
|
+
schemaVersion: CLOUD_SYNC_ENCRYPTED_SCHEMA_VERSION,
|
|
15426
|
+
updatedAt,
|
|
15427
|
+
encryption: {
|
|
15428
|
+
cipher: "aes-256-gcm",
|
|
15429
|
+
kdf: "scrypt",
|
|
15430
|
+
kdfParams: { N, r, p, keyLength },
|
|
15431
|
+
salt: encryption.salt,
|
|
15432
|
+
iv: encryption.iv,
|
|
15433
|
+
tag: encryption.tag,
|
|
15434
|
+
ciphertext: encryption.ciphertext
|
|
15435
|
+
}
|
|
15436
|
+
};
|
|
15437
|
+
}
|
|
15438
|
+
if (schemaVersion !== CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION) {
|
|
14738
15439
|
return null;
|
|
14739
15440
|
}
|
|
14740
15441
|
const rawProfiles = Array.isArray(value.profiles) ? value.profiles : [];
|
|
@@ -14758,7 +15459,7 @@ function normalizeSnapshot(value) {
|
|
|
14758
15459
|
const rawSettings = value.settingsJson;
|
|
14759
15460
|
const settingsJson = isRecord6(rawSettings) ? rawSettings : null;
|
|
14760
15461
|
return {
|
|
14761
|
-
schemaVersion:
|
|
15462
|
+
schemaVersion: CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION,
|
|
14762
15463
|
updatedAt,
|
|
14763
15464
|
profiles,
|
|
14764
15465
|
configTomlContent: typeof value.configTomlContent === "string" ? value.configTomlContent : null,
|
|
@@ -14810,7 +15511,12 @@ async function fetchRemoteSnapshotMeta(licenseKey) {
|
|
|
14810
15511
|
"/v1/sync/snapshot/meta",
|
|
14811
15512
|
licenseKey
|
|
14812
15513
|
);
|
|
14813
|
-
|
|
15514
|
+
const updatedAt = toIsoOrNull(response.updatedAt);
|
|
15515
|
+
const snapshotKind = normalizeSnapshotKind(response.snapshotKind);
|
|
15516
|
+
return {
|
|
15517
|
+
updatedAt,
|
|
15518
|
+
snapshotKind: updatedAt && response.snapshotKind === void 0 ? "legacy-plaintext" : snapshotKind
|
|
15519
|
+
};
|
|
14814
15520
|
}
|
|
14815
15521
|
async function pushRemoteSnapshot(licenseKey, snapshot, options) {
|
|
14816
15522
|
const serializedSnapshot = typeof options?.serializedSnapshot === "string" ? options.serializedSnapshot : JSON.stringify(snapshot);
|
|
@@ -14833,7 +15539,7 @@ async function pushRemoteSnapshot(licenseKey, snapshot, options) {
|
|
|
14833
15539
|
}
|
|
14834
15540
|
|
|
14835
15541
|
// ../../packages/runtime-codex/src/codex/config.ts
|
|
14836
|
-
var
|
|
15542
|
+
var import_toml3 = __toESM(require_toml(), 1);
|
|
14837
15543
|
|
|
14838
15544
|
// ../../packages/runtime-codex/src/codex/config-metadata.ts
|
|
14839
15545
|
var import_node_child_process6 = require("child_process");
|
|
@@ -15472,7 +16178,7 @@ function formatUserFacingError(error, options = {}) {
|
|
|
15472
16178
|
// ../../packages/runtime-codex/src/codex/config.ts
|
|
15473
16179
|
function tryParseToml(content) {
|
|
15474
16180
|
try {
|
|
15475
|
-
const parsed = (0,
|
|
16181
|
+
const parsed = (0, import_toml3.parse)(content);
|
|
15476
16182
|
return { data: parsed ?? {}, error: null };
|
|
15477
16183
|
} catch (error) {
|
|
15478
16184
|
const maybe = error;
|
|
@@ -15791,6 +16497,170 @@ async function saveCodexConfigContent(content, options) {
|
|
|
15791
16497
|
return buildSnapshot(normalized, true, options);
|
|
15792
16498
|
}
|
|
15793
16499
|
|
|
16500
|
+
// ../../packages/runtime-profiles/src/cloud-sync/encryption.ts
|
|
16501
|
+
var import_node_child_process7 = require("child_process");
|
|
16502
|
+
var import_node_crypto6 = require("crypto");
|
|
16503
|
+
var KEYCHAIN_SERVICE = "CODEXUSE_CLOUD_SYNC_PASSPHRASE";
|
|
16504
|
+
var KEYCHAIN_ACCOUNT_PREFIX = "license-sha256:";
|
|
16505
|
+
var ENCRYPTION_AAD = Buffer.from("codexuse-cloud-sync-v2", "utf8");
|
|
16506
|
+
var SCRYPT_PARAMS = {
|
|
16507
|
+
N: 32768,
|
|
16508
|
+
r: 8,
|
|
16509
|
+
p: 1,
|
|
16510
|
+
keyLength: 32
|
|
16511
|
+
};
|
|
16512
|
+
function normalizePassphrase(value) {
|
|
16513
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
16514
|
+
return null;
|
|
16515
|
+
}
|
|
16516
|
+
return value;
|
|
16517
|
+
}
|
|
16518
|
+
function requireCloudSyncPassphrase(value) {
|
|
16519
|
+
const normalized = normalizePassphrase(value);
|
|
16520
|
+
if (!normalized) {
|
|
16521
|
+
throw new Error("Cloud Sync passphrase is required.");
|
|
16522
|
+
}
|
|
16523
|
+
return normalized;
|
|
16524
|
+
}
|
|
16525
|
+
async function deriveKey(passphrase, salt, params = SCRYPT_PARAMS) {
|
|
16526
|
+
return new Promise((resolve, reject) => {
|
|
16527
|
+
(0, import_node_crypto6.scrypt)(passphrase, salt, params.keyLength, {
|
|
16528
|
+
N: params.N,
|
|
16529
|
+
r: params.r,
|
|
16530
|
+
p: params.p,
|
|
16531
|
+
maxmem: 64 * 1024 * 1024
|
|
16532
|
+
}, (error, key) => {
|
|
16533
|
+
if (error) {
|
|
16534
|
+
reject(error);
|
|
16535
|
+
return;
|
|
16536
|
+
}
|
|
16537
|
+
resolve(Buffer.from(key));
|
|
16538
|
+
});
|
|
16539
|
+
});
|
|
16540
|
+
}
|
|
16541
|
+
function toBase64(value) {
|
|
16542
|
+
return value.toString("base64");
|
|
16543
|
+
}
|
|
16544
|
+
function fromBase64(value) {
|
|
16545
|
+
return Buffer.from(value, "base64");
|
|
16546
|
+
}
|
|
16547
|
+
function normalizeDecryptedSnapshot(value) {
|
|
16548
|
+
if (!isRecord6(value) || Number(value.schemaVersion) !== CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION) {
|
|
16549
|
+
throw new Error("Cloud Sync snapshot payload is invalid.");
|
|
16550
|
+
}
|
|
16551
|
+
const updatedAt = toIsoOrNull(value.updatedAt);
|
|
16552
|
+
if (!updatedAt) {
|
|
16553
|
+
throw new Error("Cloud Sync snapshot timestamp is invalid.");
|
|
16554
|
+
}
|
|
16555
|
+
return {
|
|
16556
|
+
schemaVersion: CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION,
|
|
16557
|
+
updatedAt,
|
|
16558
|
+
profiles: Array.isArray(value.profiles) ? value.profiles : [],
|
|
16559
|
+
configTomlContent: typeof value.configTomlContent === "string" ? value.configTomlContent : null,
|
|
16560
|
+
settingsJson: isRecord6(value.settingsJson) ? value.settingsJson : null
|
|
16561
|
+
};
|
|
16562
|
+
}
|
|
16563
|
+
async function encryptCloudSyncSnapshot(snapshot, passphrase) {
|
|
16564
|
+
const salt = (0, import_node_crypto6.randomBytes)(16);
|
|
16565
|
+
const iv = (0, import_node_crypto6.randomBytes)(12);
|
|
16566
|
+
const key = await deriveKey(requireCloudSyncPassphrase(passphrase), salt);
|
|
16567
|
+
const cipher = (0, import_node_crypto6.createCipheriv)("aes-256-gcm", key, iv);
|
|
16568
|
+
cipher.setAAD(ENCRYPTION_AAD);
|
|
16569
|
+
const plaintext = Buffer.from(JSON.stringify(snapshot), "utf8");
|
|
16570
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16571
|
+
const tag = cipher.getAuthTag();
|
|
16572
|
+
const encryption = {
|
|
16573
|
+
cipher: "aes-256-gcm",
|
|
16574
|
+
kdf: "scrypt",
|
|
16575
|
+
kdfParams: SCRYPT_PARAMS,
|
|
16576
|
+
salt: toBase64(salt),
|
|
16577
|
+
iv: toBase64(iv),
|
|
16578
|
+
tag: toBase64(tag),
|
|
16579
|
+
ciphertext: toBase64(ciphertext)
|
|
16580
|
+
};
|
|
16581
|
+
return {
|
|
16582
|
+
schemaVersion: CLOUD_SYNC_ENCRYPTED_SCHEMA_VERSION,
|
|
16583
|
+
updatedAt: snapshot.updatedAt,
|
|
16584
|
+
encryption
|
|
16585
|
+
};
|
|
16586
|
+
}
|
|
16587
|
+
async function decryptCloudSyncSnapshot(snapshot, passphrase) {
|
|
16588
|
+
try {
|
|
16589
|
+
const salt = fromBase64(snapshot.encryption.salt);
|
|
16590
|
+
const iv = fromBase64(snapshot.encryption.iv);
|
|
16591
|
+
const tag = fromBase64(snapshot.encryption.tag);
|
|
16592
|
+
const ciphertext = fromBase64(snapshot.encryption.ciphertext);
|
|
16593
|
+
const key = await deriveKey(requireCloudSyncPassphrase(passphrase), salt, snapshot.encryption.kdfParams);
|
|
16594
|
+
const decipher = (0, import_node_crypto6.createDecipheriv)("aes-256-gcm", key, iv);
|
|
16595
|
+
decipher.setAAD(ENCRYPTION_AAD);
|
|
16596
|
+
decipher.setAuthTag(tag);
|
|
16597
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
16598
|
+
return normalizeDecryptedSnapshot(JSON.parse(plaintext.toString("utf8")));
|
|
16599
|
+
} catch (error) {
|
|
16600
|
+
if (error instanceof Error && error.message === "Cloud Sync passphrase is required.") {
|
|
16601
|
+
throw error;
|
|
16602
|
+
}
|
|
16603
|
+
const wrapped = new Error("Cloud Sync passphrase is incorrect or snapshot is corrupted.");
|
|
16604
|
+
wrapped.cause = error;
|
|
16605
|
+
throw wrapped;
|
|
16606
|
+
}
|
|
16607
|
+
}
|
|
16608
|
+
function keychainAccountForLicense(licenseKey) {
|
|
16609
|
+
const digest = (0, import_node_crypto6.createHash)("sha256").update(licenseKey).digest("hex");
|
|
16610
|
+
return KEYCHAIN_ACCOUNT_PREFIX + digest;
|
|
16611
|
+
}
|
|
16612
|
+
function runSecurityCommand(args) {
|
|
16613
|
+
return new Promise((resolve, reject) => {
|
|
16614
|
+
const child = (0, import_node_child_process7.spawn)("security", args, { stdio: ["ignore", "pipe", "ignore"] });
|
|
16615
|
+
let stdout = "";
|
|
16616
|
+
child.stdout?.on("data", (chunk) => {
|
|
16617
|
+
stdout += String(chunk);
|
|
16618
|
+
});
|
|
16619
|
+
child.on("error", () => {
|
|
16620
|
+
reject(new Error("keychain-command-failed"));
|
|
16621
|
+
});
|
|
16622
|
+
child.on("close", (code) => {
|
|
16623
|
+
resolve({ code, stdout });
|
|
16624
|
+
});
|
|
16625
|
+
});
|
|
16626
|
+
}
|
|
16627
|
+
async function readRememberedCloudSyncPassphrase(licenseKey) {
|
|
16628
|
+
if (process.platform !== "darwin") {
|
|
16629
|
+
return null;
|
|
16630
|
+
}
|
|
16631
|
+
const result = await runSecurityCommand([
|
|
16632
|
+
"find-generic-password",
|
|
16633
|
+
"-a",
|
|
16634
|
+
keychainAccountForLicense(licenseKey),
|
|
16635
|
+
"-s",
|
|
16636
|
+
KEYCHAIN_SERVICE,
|
|
16637
|
+
"-w"
|
|
16638
|
+
]);
|
|
16639
|
+
if (result.code !== 0) {
|
|
16640
|
+
return null;
|
|
16641
|
+
}
|
|
16642
|
+
return result.stdout.length > 0 ? result.stdout.replace(/\n$/, "") : null;
|
|
16643
|
+
}
|
|
16644
|
+
async function writeRememberedCloudSyncPassphrase(licenseKey, passphrase) {
|
|
16645
|
+
if (process.platform !== "darwin") {
|
|
16646
|
+
throw new Error("keychain-unsupported");
|
|
16647
|
+
}
|
|
16648
|
+
const value = requireCloudSyncPassphrase(passphrase);
|
|
16649
|
+
const result = await runSecurityCommand([
|
|
16650
|
+
"add-generic-password",
|
|
16651
|
+
"-U",
|
|
16652
|
+
"-a",
|
|
16653
|
+
keychainAccountForLicense(licenseKey),
|
|
16654
|
+
"-s",
|
|
16655
|
+
KEYCHAIN_SERVICE,
|
|
16656
|
+
"-w",
|
|
16657
|
+
value
|
|
16658
|
+
]);
|
|
16659
|
+
if (result.code !== 0) {
|
|
16660
|
+
throw new Error("keychain-write-failed");
|
|
16661
|
+
}
|
|
16662
|
+
}
|
|
16663
|
+
|
|
15794
16664
|
// ../../packages/runtime-profiles/src/cloud-sync/service.ts
|
|
15795
16665
|
var SYNC_SIZE_WARN_BYTES = 1 * 1024 * 1024;
|
|
15796
16666
|
var SYNC_SIZE_MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -15843,7 +16713,8 @@ async function readState() {
|
|
|
15843
16713
|
lastPushAt: state.sync.lastPushAt ?? void 0,
|
|
15844
16714
|
lastPullAt: state.sync.lastPullAt ?? void 0,
|
|
15845
16715
|
lastError: state.sync.lastError ?? void 0,
|
|
15846
|
-
remoteUpdatedAt: state.sync.remoteUpdatedAt ?? void 0
|
|
16716
|
+
remoteUpdatedAt: state.sync.remoteUpdatedAt ?? void 0,
|
|
16717
|
+
remoteKind: state.sync.remoteKind ?? "none"
|
|
15847
16718
|
};
|
|
15848
16719
|
}
|
|
15849
16720
|
async function writeState(patch) {
|
|
@@ -15852,7 +16723,8 @@ async function writeState(patch) {
|
|
|
15852
16723
|
...typeof patch.lastPushAt === "string" ? { lastPushAt: patch.lastPushAt } : {},
|
|
15853
16724
|
...typeof patch.lastPullAt === "string" ? { lastPullAt: patch.lastPullAt } : {},
|
|
15854
16725
|
...typeof patch.lastError === "string" ? { lastError: patch.lastError } : patch.lastError === void 0 ? { lastError: null } : {},
|
|
15855
|
-
...typeof patch.remoteUpdatedAt === "string" ? { remoteUpdatedAt: patch.remoteUpdatedAt } : {}
|
|
16726
|
+
...typeof patch.remoteUpdatedAt === "string" ? { remoteUpdatedAt: patch.remoteUpdatedAt } : patch.remoteUpdatedAt === void 0 ? {} : { remoteUpdatedAt: null },
|
|
16727
|
+
...patch.remoteKind ? { remoteKind: patch.remoteKind } : {}
|
|
15856
16728
|
}
|
|
15857
16729
|
});
|
|
15858
16730
|
}
|
|
@@ -15905,7 +16777,7 @@ async function buildLocalSnapshot(profileManager, options = {}) {
|
|
|
15905
16777
|
throw new Error("Refusing to push an empty cloud sync snapshot.");
|
|
15906
16778
|
}
|
|
15907
16779
|
const snapshot = {
|
|
15908
|
-
schemaVersion:
|
|
16780
|
+
schemaVersion: CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION,
|
|
15909
16781
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15910
16782
|
profiles,
|
|
15911
16783
|
configTomlContent: config.exists ? config.content : null,
|
|
@@ -15930,6 +16802,34 @@ async function applyRemoteSnapshot(profileManager, snapshot) {
|
|
|
15930
16802
|
await writeCodexSettingsJsonRaw({});
|
|
15931
16803
|
}
|
|
15932
16804
|
}
|
|
16805
|
+
function getSnapshotKind(snapshot) {
|
|
16806
|
+
if (!snapshot) {
|
|
16807
|
+
return "none";
|
|
16808
|
+
}
|
|
16809
|
+
return snapshot.schemaVersion === CLOUD_SYNC_ENCRYPTED_SCHEMA_VERSION ? "encrypted" : "legacy-plaintext";
|
|
16810
|
+
}
|
|
16811
|
+
async function resolvePassphrase(licenseKey, options) {
|
|
16812
|
+
if (typeof options?.passphrase === "string" && options.passphrase.length > 0) {
|
|
16813
|
+
return { value: requireCloudSyncPassphrase(options.passphrase), source: "input" };
|
|
16814
|
+
}
|
|
16815
|
+
const remembered = await readRememberedCloudSyncPassphrase(licenseKey);
|
|
16816
|
+
if (remembered) {
|
|
16817
|
+
return { value: remembered, source: "keychain" };
|
|
16818
|
+
}
|
|
16819
|
+
throw new Error("Cloud Sync passphrase is required.");
|
|
16820
|
+
}
|
|
16821
|
+
async function rememberPassphraseIfRequested(licenseKey, options, passphrase) {
|
|
16822
|
+
if (options?.rememberPassphrase !== true || passphrase.source !== "input") {
|
|
16823
|
+
return;
|
|
16824
|
+
}
|
|
16825
|
+
try {
|
|
16826
|
+
await writeRememberedCloudSyncPassphrase(licenseKey, passphrase.value);
|
|
16827
|
+
} catch (error) {
|
|
16828
|
+
logWarn("[cloud-sync] failed to remember passphrase in keychain", {
|
|
16829
|
+
error: error instanceof Error ? error.message : String(error)
|
|
16830
|
+
});
|
|
16831
|
+
}
|
|
16832
|
+
}
|
|
15933
16833
|
function toRunError(mode, error) {
|
|
15934
16834
|
const message = formatUserFacingError(error, {
|
|
15935
16835
|
fallback: `Cloud sync ${mode} failed.`
|
|
@@ -15946,14 +16846,23 @@ async function getCloudSyncStatus() {
|
|
|
15946
16846
|
const eligibility = await resolveEligibility();
|
|
15947
16847
|
const state = await readState();
|
|
15948
16848
|
let remoteUpdatedAt = state.remoteUpdatedAt ?? null;
|
|
16849
|
+
let remoteKind = state.remoteKind ?? "none";
|
|
16850
|
+
let hasRememberedPassphrase = false;
|
|
15949
16851
|
if (eligibility.canSync && eligibility.licenseKey) {
|
|
16852
|
+
hasRememberedPassphrase = Boolean(
|
|
16853
|
+
await readRememberedCloudSyncPassphrase(eligibility.licenseKey).catch(() => null)
|
|
16854
|
+
);
|
|
15950
16855
|
try {
|
|
15951
|
-
|
|
16856
|
+
const remoteMeta = await fetchRemoteSnapshotMeta(eligibility.licenseKey);
|
|
16857
|
+
remoteUpdatedAt = remoteMeta.updatedAt;
|
|
16858
|
+
remoteKind = remoteMeta.snapshotKind;
|
|
16859
|
+
await writeState({ remoteUpdatedAt, remoteKind });
|
|
15952
16860
|
} catch (error) {
|
|
15953
16861
|
if (error instanceof CloudSyncClientError && error.status === 404) {
|
|
15954
16862
|
try {
|
|
15955
16863
|
const remote = await fetchRemoteSnapshot(eligibility.licenseKey);
|
|
15956
16864
|
remoteUpdatedAt = remote?.updatedAt ?? null;
|
|
16865
|
+
remoteKind = getSnapshotKind(remote);
|
|
15957
16866
|
} catch {
|
|
15958
16867
|
}
|
|
15959
16868
|
}
|
|
@@ -15964,13 +16873,15 @@ async function getCloudSyncStatus() {
|
|
|
15964
16873
|
reason: eligibility.reason,
|
|
15965
16874
|
licenseState: eligibility.licenseState,
|
|
15966
16875
|
hasLicenseKey: Boolean(eligibility.licenseKey),
|
|
16876
|
+
hasRememberedPassphrase,
|
|
15967
16877
|
lastPushAt: state.lastPushAt ?? null,
|
|
15968
16878
|
lastPullAt: state.lastPullAt ?? null,
|
|
15969
16879
|
lastError: state.lastError ?? null,
|
|
15970
|
-
remoteUpdatedAt
|
|
16880
|
+
remoteUpdatedAt,
|
|
16881
|
+
remoteKind
|
|
15971
16882
|
};
|
|
15972
16883
|
}
|
|
15973
|
-
async function pushCloudSync() {
|
|
16884
|
+
async function pushCloudSync(options = {}) {
|
|
15974
16885
|
const eligibility = await resolveEligibility();
|
|
15975
16886
|
if (!eligibility.canSync || !eligibility.licenseKey) {
|
|
15976
16887
|
return {
|
|
@@ -15985,7 +16896,9 @@ async function pushCloudSync() {
|
|
|
15985
16896
|
try {
|
|
15986
16897
|
await profileManager.initialize();
|
|
15987
16898
|
const localSnapshot = await buildLocalSnapshot(profileManager, { enforcePushGuards: true });
|
|
15988
|
-
const
|
|
16899
|
+
const passphrase = await resolvePassphrase(eligibility.licenseKey, options);
|
|
16900
|
+
const encryptedSnapshot = await encryptCloudSyncSnapshot(localSnapshot, passphrase.value);
|
|
16901
|
+
const serializedSnapshot = JSON.stringify(encryptedSnapshot);
|
|
15989
16902
|
const snapshotBytes = Buffer.byteLength(serializedSnapshot, "utf8");
|
|
15990
16903
|
if (snapshotBytes > SYNC_SIZE_WARN_BYTES) {
|
|
15991
16904
|
logWarn("[cloud-sync] push snapshot is large", {
|
|
@@ -16001,12 +16914,14 @@ async function pushCloudSync() {
|
|
|
16001
16914
|
if (process.env.NODE_ENV !== "production") {
|
|
16002
16915
|
logInfo("[cloud-sync] push snapshot summary", summarizeSnapshot(localSnapshot));
|
|
16003
16916
|
}
|
|
16004
|
-
const remote = await pushRemoteSnapshot(eligibility.licenseKey,
|
|
16917
|
+
const remote = await pushRemoteSnapshot(eligibility.licenseKey, encryptedSnapshot, {
|
|
16005
16918
|
serializedSnapshot
|
|
16006
16919
|
});
|
|
16920
|
+
const remoteKind = getSnapshotKind(remote.snapshot);
|
|
16007
16921
|
await writeState({
|
|
16008
16922
|
lastPushAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16009
16923
|
remoteUpdatedAt: remote.snapshot.updatedAt,
|
|
16924
|
+
remoteKind,
|
|
16010
16925
|
lastError: void 0
|
|
16011
16926
|
});
|
|
16012
16927
|
if (remote.status === "stale") {
|
|
@@ -16018,6 +16933,7 @@ async function pushCloudSync() {
|
|
|
16018
16933
|
remoteUpdatedAt: remote.snapshot.updatedAt
|
|
16019
16934
|
};
|
|
16020
16935
|
}
|
|
16936
|
+
await rememberPassphraseIfRequested(eligibility.licenseKey, options, passphrase);
|
|
16021
16937
|
return {
|
|
16022
16938
|
mode: "push",
|
|
16023
16939
|
status: "applied",
|
|
@@ -16032,7 +16948,7 @@ async function pushCloudSync() {
|
|
|
16032
16948
|
return result;
|
|
16033
16949
|
}
|
|
16034
16950
|
}
|
|
16035
|
-
async function pullCloudSync() {
|
|
16951
|
+
async function pullCloudSync(options = {}) {
|
|
16036
16952
|
const eligibility = await resolveEligibility();
|
|
16037
16953
|
if (!eligibility.canSync || !eligibility.licenseKey) {
|
|
16038
16954
|
return {
|
|
@@ -16048,6 +16964,7 @@ async function pullCloudSync() {
|
|
|
16048
16964
|
await profileManager.initialize();
|
|
16049
16965
|
const remote = await fetchRemoteSnapshot(eligibility.licenseKey);
|
|
16050
16966
|
if (!remote) {
|
|
16967
|
+
await writeState({ remoteUpdatedAt: null, remoteKind: "none" });
|
|
16051
16968
|
return {
|
|
16052
16969
|
mode: "pull",
|
|
16053
16970
|
status: "skipped",
|
|
@@ -16056,17 +16973,30 @@ async function pullCloudSync() {
|
|
|
16056
16973
|
remoteUpdatedAt: null
|
|
16057
16974
|
};
|
|
16058
16975
|
}
|
|
16059
|
-
|
|
16976
|
+
const remoteKind = getSnapshotKind(remote);
|
|
16977
|
+
let passphrase = null;
|
|
16978
|
+
let plainSnapshot;
|
|
16979
|
+
if (remote.schemaVersion === CLOUD_SYNC_ENCRYPTED_SCHEMA_VERSION) {
|
|
16980
|
+
passphrase = await resolvePassphrase(eligibility.licenseKey, options);
|
|
16981
|
+
plainSnapshot = await decryptCloudSyncSnapshot(remote, passphrase.value);
|
|
16982
|
+
} else {
|
|
16983
|
+
plainSnapshot = remote;
|
|
16984
|
+
}
|
|
16985
|
+
await applyRemoteSnapshot(profileManager, plainSnapshot);
|
|
16060
16986
|
await writeState({
|
|
16061
16987
|
lastPullAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16062
16988
|
remoteUpdatedAt: remote.updatedAt,
|
|
16989
|
+
remoteKind,
|
|
16063
16990
|
lastError: void 0
|
|
16064
16991
|
});
|
|
16992
|
+
if (passphrase) {
|
|
16993
|
+
await rememberPassphraseIfRequested(eligibility.licenseKey, options, passphrase);
|
|
16994
|
+
}
|
|
16065
16995
|
return {
|
|
16066
16996
|
mode: "pull",
|
|
16067
16997
|
status: "applied",
|
|
16068
|
-
message: "Cloud sync pull completed.",
|
|
16069
|
-
localUpdatedAt:
|
|
16998
|
+
message: remoteKind === "legacy-plaintext" ? "Legacy cloud sync pull completed. Push again to encrypt the remote snapshot." : "Cloud sync pull completed.",
|
|
16999
|
+
localUpdatedAt: plainSnapshot.updatedAt,
|
|
16070
17000
|
remoteUpdatedAt: remote.updatedAt
|
|
16071
17001
|
};
|
|
16072
17002
|
} catch (error) {
|
|
@@ -16078,6 +17008,20 @@ async function pullCloudSync() {
|
|
|
16078
17008
|
}
|
|
16079
17009
|
|
|
16080
17010
|
// src/commands/sync.ts
|
|
17011
|
+
async function readPassphraseFromStdin() {
|
|
17012
|
+
let value = "";
|
|
17013
|
+
for await (const chunk of process.stdin) {
|
|
17014
|
+
value += String(chunk);
|
|
17015
|
+
}
|
|
17016
|
+
return value.replace(/\r?\n$/, "");
|
|
17017
|
+
}
|
|
17018
|
+
async function resolvePassphraseOptions(flags) {
|
|
17019
|
+
if (hasFlag(flags, "--passphrase-stdin")) {
|
|
17020
|
+
return { passphrase: await readPassphraseFromStdin() };
|
|
17021
|
+
}
|
|
17022
|
+
const fromEnv = process.env.CODEXUSE_SYNC_PASSPHRASE;
|
|
17023
|
+
return { passphrase: typeof fromEnv === "string" && fromEnv.length > 0 ? fromEnv : null };
|
|
17024
|
+
}
|
|
16081
17025
|
function printSyncResult(result) {
|
|
16082
17026
|
console.log(result.message);
|
|
16083
17027
|
if (result.localUpdatedAt) {
|
|
@@ -16109,6 +17053,8 @@ async function handleSync(args, version) {
|
|
|
16109
17053
|
if (status.remoteUpdatedAt) {
|
|
16110
17054
|
console.log(`Remote snapshot: ${status.remoteUpdatedAt}`);
|
|
16111
17055
|
}
|
|
17056
|
+
console.log(`Remote kind: ${status.remoteKind}`);
|
|
17057
|
+
console.log(`Remembered passphrase: ${status.hasRememberedPassphrase ? "yes" : "no"}`);
|
|
16112
17058
|
if (status.lastPushAt) {
|
|
16113
17059
|
console.log(`Last push: ${status.lastPushAt}`);
|
|
16114
17060
|
}
|
|
@@ -16121,7 +17067,7 @@ async function handleSync(args, version) {
|
|
|
16121
17067
|
return;
|
|
16122
17068
|
}
|
|
16123
17069
|
case "pull": {
|
|
16124
|
-
const result = await pullCloudSync();
|
|
17070
|
+
const result = await pullCloudSync(await resolvePassphraseOptions(flags));
|
|
16125
17071
|
printSyncResult(result);
|
|
16126
17072
|
if (result.status === "error") {
|
|
16127
17073
|
process.exitCode = 1;
|
|
@@ -16129,7 +17075,7 @@ async function handleSync(args, version) {
|
|
|
16129
17075
|
return;
|
|
16130
17076
|
}
|
|
16131
17077
|
case "push": {
|
|
16132
|
-
const result = await pushCloudSync();
|
|
17078
|
+
const result = await pushCloudSync(await resolvePassphraseOptions(flags));
|
|
16133
17079
|
printSyncResult(result);
|
|
16134
17080
|
if (result.status === "error") {
|
|
16135
17081
|
process.exitCode = 1;
|
|
@@ -16157,7 +17103,7 @@ async function ensureCliStorageReady() {
|
|
|
16157
17103
|
}
|
|
16158
17104
|
|
|
16159
17105
|
// src/app/main.ts
|
|
16160
|
-
var VERSION = true ? "5.0.
|
|
17106
|
+
var VERSION = true ? "5.0.7" : "0.0.0";
|
|
16161
17107
|
async function runCli() {
|
|
16162
17108
|
const args = process.argv.slice(2);
|
|
16163
17109
|
if (args.length === 0) {
|