codexuse-cli 5.0.6 → 5.0.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/dist/index.js CHANGED
@@ -2616,13 +2616,15 @@ function createDefaultAppState() {
2616
2616
  lastPushAt: null,
2617
2617
  lastPullAt: null,
2618
2618
  lastError: null,
2619
- remoteUpdatedAt: null
2619
+ remoteUpdatedAt: null,
2620
+ remoteKind: "none"
2620
2621
  },
2621
2622
  analytics: {
2622
2623
  anonymousId: null,
2623
2624
  enabled: true,
2624
2625
  lastFlushAt: null,
2625
- lastError: null
2626
+ lastError: null,
2627
+ reportedNativeCrashIncidentIds: []
2626
2628
  },
2627
2629
  profilesByName: {}
2628
2630
  };
@@ -3203,6 +3205,9 @@ function normalizeAppState(raw) {
3203
3205
  merged.sync.lastPullAt = asString(merged.sync.lastPullAt);
3204
3206
  merged.sync.lastError = asString(merged.sync.lastError);
3205
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
+ }
3206
3211
  if (!isRecord2(merged.analytics)) {
3207
3212
  merged.analytics = clone2(defaults.analytics);
3208
3213
  }
@@ -3212,6 +3217,9 @@ function normalizeAppState(raw) {
3212
3217
  }
3213
3218
  merged.analytics.lastFlushAt = asString(merged.analytics.lastFlushAt);
3214
3219
  merged.analytics.lastError = asString(merged.analytics.lastError);
3220
+ merged.analytics.reportedNativeCrashIncidentIds = asStringArray(
3221
+ merged.analytics.reportedNativeCrashIncidentIds
3222
+ ).slice(-20);
3215
3223
  if ("telemetry" in merged) {
3216
3224
  delete merged.telemetry;
3217
3225
  }
@@ -3575,12 +3583,12 @@ function getActiveOffer() {
3575
3583
  return {
3576
3584
  basePriceUsd,
3577
3585
  basePriceDisplay: `$${basePriceUsd}`,
3578
- couponCode: "SPRING50",
3586
+ couponCode: "SUMMER50",
3579
3587
  discountPercent,
3580
3588
  salePriceUsd,
3581
3589
  salePriceDisplay: `$${salePriceUsd.toFixed(2)}`,
3582
3590
  isActive: true,
3583
- campaign: "spring-2026",
3591
+ campaign: "summer-2026",
3584
3592
  productPermalink: "codex-use",
3585
3593
  checkoutBaseUrl: "https://hweihwang.gumroad.com/l/codex-use"
3586
3594
  };
@@ -7294,8 +7302,8 @@ Usage:
7294
7302
  codexuse license activate <license-key>
7295
7303
 
7296
7304
  codexuse sync status
7297
- codexuse sync pull
7298
- codexuse sync push
7305
+ codexuse sync pull [--passphrase-stdin]
7306
+ codexuse sync push [--passphrase-stdin]
7299
7307
 
7300
7308
  Flags:
7301
7309
  -h, --help Show help
@@ -7313,7 +7321,9 @@ Flags:
7313
7321
  --profile=NAME Run Codex with one saved profile
7314
7322
  --runtime=NAME Accounts Pool runtime store: desktop
7315
7323
  --state-dir=PATH Override the runtime state dir for Accounts Pool inspection
7324
+ --passphrase-stdin Read Cloud Sync passphrase from stdin
7316
7325
  Note: profile autoroll requires Pro.
7326
+ Cloud Sync: set CODEXUSE_SYNC_PASSPHRASE or use --passphrase-stdin unless the passphrase is saved in Keychain.
7317
7327
  `);
7318
7328
  }
7319
7329
 
@@ -9544,7 +9554,7 @@ function buildInCodexStatusOverlayCssRules() {
9544
9554
  `${id}[data-open='false']:not([data-collapsed='true']):hover .codexuse-status-trigger{padding:8px 10px;gap:8px;}`,
9545
9555
  `${id}[data-open='false']:not([data-collapsed='true']):hover .codexuse-status-label{max-width:160px;font-size:12px;}`,
9546
9556
  `${id}[data-open='false']:not([data-collapsed='true']):hover .codexuse-status-chip{display:inline-flex;}`,
9547
- `${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);}`,
9548
9558
  `${id}[data-drop='down'] .codexuse-action-menu{bottom:auto;top:calc(100% + 8px);}`,
9549
9559
  `${id}[data-open='true'] .codexuse-action-menu{display:flex;flex-direction:column;gap:2px;}`,
9550
9560
  `${id} .codexuse-target-list{display:flex;max-height:210px;flex-direction:column;gap:2px;overflow-y:auto;}`,
@@ -9566,10 +9576,10 @@ function buildInCodexStatusOverlayCssRules() {
9566
9576
  `${id} .codexuse-intro-detail{margin-top:2px;font-size:10px;color:var(--cu-muted);}`,
9567
9577
  `${id} .codexuse-decision-secondary{border:1px solid var(--cu-border)!important;color:var(--cu-muted)!important;}`,
9568
9578
  `${id} .codexuse-menu-divider{height:1px;margin:4px 2px;background:var(--cu-border);}`,
9569
- `${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;}`,
9570
- `${id} .codexuse-command-row:hover{background:var(--cu-hover);}`,
9571
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;}`,
9572
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;}`,
9573
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;}`,
9574
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;}`,
9575
9585
  `${id} .codexuse-setting-row[data-on='true'] .codexuse-toggle{background:var(--cu-accent);}`,
@@ -9578,13 +9588,17 @@ function buildInCodexStatusOverlayCssRules() {
9578
9588
  `${id} .codexuse-action-status{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--cu-muted);font-size:10px;}`,
9579
9589
  `${id} .codexuse-overlay-hide{flex-shrink:0;padding:2px 6px;border-radius:6px;color:var(--cu-muted);font-size:10px;cursor:pointer;}`,
9580
9590
  `${id} .codexuse-overlay-hide:hover{background:var(--cu-hover);color:var(--cu-fg,#ececf1);}`,
9581
- `${chip}{${palette}position:fixed;z-index:2147483001;display:inline-flex;align-items:center;gap:6px;padding:5px 10px;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;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.2);transition:background 120ms ease-out;}`,
9582
- `${chip}:hover{background:var(--cu-hover);}`,
9583
- `${chip}:disabled{opacity:.6;cursor:default;}`,
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);}`,
9584
9598
  // Hand-off account picker is a standalone body child (not inside the pill),
9585
9599
  // so the pill-scoped popover rules don't reach it — give it its own palette
9586
9600
  // + popover styling, mirroring the chip.
9587
- `${picker}{${palette}position:fixed;z-index:2147483001;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);}`,
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;}`,
9588
9602
  `${picker} button{font:inherit;font-family:inherit;color:inherit;border:0;background:transparent;}`,
9589
9603
  `${picker} .codexuse-target-list{display:flex;flex-direction:column;gap:2px;}`,
9590
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;}`,
@@ -9616,6 +9630,9 @@ function createBridgeApplyQolExpression(settings) {
9616
9630
  )};
9617
9631
  const overlayAutoRollEnabled = ${JSON.stringify(
9618
9632
  typeof settings.overlayAutoRollEnabled === "boolean" ? settings.overlayAutoRollEnabled : null
9633
+ )};
9634
+ const overlayRunningThreadHandoffEnabled = ${JSON.stringify(
9635
+ typeof settings.overlayRunningThreadHandoffEnabled === "boolean" ? settings.overlayRunningThreadHandoffEnabled : null
9619
9636
  )};
9620
9637
  const versions = {
9621
9638
  wideView: "3",
@@ -9957,6 +9974,8 @@ function createBridgeApplyQolExpression(settings) {
9957
9974
  const overlayHiddenKey = "codexuse:overlay:hidden";
9958
9975
  const overlayIntroKey = "codexuse:overlay:intro-shown";
9959
9976
  const overlayPosKey = "codexuse:overlay:pos";
9977
+ const handoffHiddenKey = "codexuse:handoff:hidden";
9978
+ const handoffPosKey = "codexuse:handoff:pos:v2";
9960
9979
  const readOverlayStore = (key) => {
9961
9980
  try {
9962
9981
  return window.localStorage.getItem(key);
@@ -10055,6 +10074,41 @@ function createBridgeApplyQolExpression(settings) {
10055
10074
  const y = Math.max(72, Math.min(140, Math.round(window.innerHeight * 0.16)));
10056
10075
  placeOverlay(pill, x, y);
10057
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
+ };
10058
10112
  const setOverlayMessage = (value) => {
10059
10113
  const node = document.querySelector("#" + statusId + " [data-codexuse-action-status]");
10060
10114
  if (node) node.textContent = value || "";
@@ -10098,7 +10152,7 @@ function createBridgeApplyQolExpression(settings) {
10098
10152
  state.lastActionRequest = payload;
10099
10153
  setOverlayMessage(
10100
10154
  action === "switch" ? "Switching\u2026"
10101
- : action === "continue" ? "Handing off\u2026"
10155
+ : action === "continue" ? "Continuing in another account\u2026"
10102
10156
  : action === "auto-roll-accept" ? "Switching\u2026"
10103
10157
  : action === "auto-roll-cancel" ? "Staying here"
10104
10158
  : action === "restart" ? "Waiting to restart; keeping draft\u2026"
@@ -10259,7 +10313,7 @@ function createBridgeApplyQolExpression(settings) {
10259
10313
  introTitle.textContent = "CodexUse controls";
10260
10314
  const introDetail = document.createElement("div");
10261
10315
  introDetail.className = "codexuse-intro-detail";
10262
- introDetail.textContent = "Switch accounts, restart Codex, and confirm auto-roll from here. Drag to move, or Hide to dismiss.";
10316
+ introDetail.textContent = "Switch accounts, tune CodexUse controls, and confirm Auto-roll from here. Drag to move, or Hide to dismiss.";
10263
10317
  intro.appendChild(introTitle);
10264
10318
  intro.appendChild(introDetail);
10265
10319
  menu.appendChild(intro);
@@ -10383,34 +10437,92 @@ function createBridgeApplyQolExpression(settings) {
10383
10437
  empty.textContent = "No other account ready";
10384
10438
  menu.appendChild(empty);
10385
10439
  }
10386
- const restartRow = document.createElement("button");
10387
- restartRow.type = "button";
10388
- restartRow.className = "codexuse-command-row";
10389
- restartRow.title = "Restart Official Codex";
10390
- const restartLabel = document.createElement("span");
10391
- restartLabel.textContent = "Restart Codex";
10392
- restartRow.appendChild(restartLabel);
10393
- const restartDetail = document.createElement("span");
10394
- restartDetail.className = "codexuse-target-detail";
10395
- restartDetail.textContent = "Keeps draft when possible";
10396
- restartRow.appendChild(restartDetail);
10397
- restartRow.addEventListener("click", (event) => {
10398
- event.preventDefault();
10399
- event.stopPropagation();
10400
- pill.dataset.open = "false";
10401
- state.overlayMenuOpen = false;
10402
- requestAction("restart");
10403
- }, true);
10404
- menu.appendChild(restartRow);
10405
- // In-app tweaks (auto-roll, wide view, scroll restore, timeline, hand-off)
10406
- // are configured in the CodexUse cockpit's Advanced section, not here \u2014
10407
- // the overlay stays focused on account status, switching, and recovery.
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);
10408
10506
  const footer = document.createElement("div");
10409
10507
  footer.className = "codexuse-menu-footer";
10410
10508
  const actionStatus = document.createElement("span");
10411
10509
  actionStatus.className = "codexuse-action-status";
10412
10510
  actionStatus.setAttribute("data-codexuse-action-status", "true");
10413
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
+ }
10414
10526
  const hide = document.createElement("button");
10415
10527
  hide.type = "button";
10416
10528
  hide.className = "codexuse-overlay-hide";
@@ -10436,7 +10548,7 @@ function createBridgeApplyQolExpression(settings) {
10436
10548
  }
10437
10549
  return true;
10438
10550
  };
10439
- // Thread-scoped hand-off control: pinned to the composer, only shown when
10551
+ // Thread-scoped hand-off control: separate draggable chip, only shown when
10440
10552
  // a conversation is on screen and another account is ready to take over.
10441
10553
  // Log the hand-off chip's visibility gate only when it CHANGES \u2014 this runs
10442
10554
  // on every mutation tick, and the reason is the first question every
@@ -10446,9 +10558,17 @@ function createBridgeApplyQolExpression(settings) {
10446
10558
  state.lastHandoffReason = reason;
10447
10559
  cuLog("handoff.render", { reason, targets: switchable.length });
10448
10560
  };
10561
+ let cleanupHandoffPicker = null;
10449
10562
  const renderHandoff = () => {
10450
- document.getElementById(handoffId)?.remove();
10563
+ const previous = document.getElementById(handoffId);
10564
+ if (previous?.dataset.dragging === "true") return true;
10565
+ const wasOpen = previous?.dataset.open === "true";
10566
+ previous?.remove();
10451
10567
  document.getElementById(handoffPickerId)?.remove();
10568
+ if (typeof cleanupHandoffPicker === "function") {
10569
+ cleanupHandoffPicker();
10570
+ cleanupHandoffPicker = null;
10571
+ }
10452
10572
  if (!inCodexStatusEnabled || !handoffChipEnabled) {
10453
10573
  reportHandoff("disabled");
10454
10574
  return false;
@@ -10457,6 +10577,10 @@ function createBridgeApplyQolExpression(settings) {
10457
10577
  reportHandoff("overlay-hidden");
10458
10578
  return false;
10459
10579
  }
10580
+ if (readOverlayStore(handoffHiddenKey) === "1") {
10581
+ reportHandoff("handoff-hidden");
10582
+ return false;
10583
+ }
10460
10584
  // Codex desktop marks turns with data-turn-key / data-user-message-bubble;
10461
10585
  // the ChatGPT-style selectors are kept as a fallback.
10462
10586
  const inThread = Boolean(domActiveThreadId() || document.querySelector(
@@ -10473,22 +10597,28 @@ function createBridgeApplyQolExpression(settings) {
10473
10597
  reportHandoff("no-composer");
10474
10598
  return false;
10475
10599
  }
10476
- const composer = textbox.closest("form,[class*='composer'],[data-testid*='composer']") || textbox;
10477
- const rect = composer.getBoundingClientRect();
10600
+ const rect = textbox.getBoundingClientRect();
10478
10601
  if (rect.width <= 0 || rect.height <= 0) {
10479
10602
  reportHandoff("composer-hidden");
10480
10603
  return false;
10481
10604
  }
10482
- const chipTop = Math.max(8, rect.top - 36);
10483
- const chipRight = Math.max(8, window.innerWidth - rect.right);
10484
- const chip = document.createElement("button");
10485
- chip.type = "button";
10605
+ const chip = document.createElement("div");
10486
10606
  chip.id = handoffId;
10487
10607
  applyOverlayTheme(chip);
10488
- chip.textContent = "Continue there";
10489
- chip.title = "Continue this thread on another account";
10490
- chip.style.top = chipTop + "px";
10491
- chip.style.right = chipRight + "px";
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);
10492
10622
  // Hand off needs BOTH a thread (this chip only shows in-thread) and a
10493
10623
  // target account. Clicking opens a picker of ready accounts; the chosen
10494
10624
  // account becomes the explicit continue target (the request also carries
@@ -10496,26 +10626,47 @@ function createBridgeApplyQolExpression(settings) {
10496
10626
  // the "continue" message is auto-sent there).
10497
10627
  const closePicker = () => {
10498
10628
  document.getElementById(handoffPickerId)?.remove();
10629
+ if (typeof cleanupHandoffPicker === "function") {
10630
+ cleanupHandoffPicker();
10631
+ cleanupHandoffPicker = null;
10632
+ }
10499
10633
  chip.dataset.open = "false";
10500
10634
  };
10501
10635
  const handOffTo = (target) => {
10502
10636
  closePicker();
10503
- chip.disabled = true;
10504
- chip.textContent = "Handing off\u2026";
10637
+ chip.dataset.disabled = "true";
10638
+ chipMain.textContent = "Continuing...";
10505
10639
  requestAction("continue", target);
10506
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
+ };
10507
10660
  const openPicker = (skipRefresh = false) => {
10508
10661
  document.getElementById(handoffPickerId)?.remove();
10662
+ if (typeof cleanupHandoffPicker === "function") {
10663
+ cleanupHandoffPicker();
10664
+ cleanupHandoffPicker = null;
10665
+ }
10509
10666
  const picker = document.createElement("div");
10510
10667
  picker.id = handoffPickerId;
10511
10668
  picker.className = "codexuse-action-menu";
10512
10669
  applyOverlayTheme(picker);
10513
- picker.style.position = "fixed";
10514
- picker.style.right = chipRight + "px";
10515
- picker.style.bottom = Math.max(8, window.innerHeight - chipTop + 4) + "px";
10516
- picker.style.maxHeight = "240px";
10517
- picker.style.overflowY = "auto";
10518
- picker.style.zIndex = "2147483647";
10519
10670
  const list = document.createElement("div");
10520
10671
  list.className = "codexuse-target-list";
10521
10672
  if (switchable.length === 0) {
@@ -10528,7 +10679,7 @@ function createBridgeApplyQolExpression(settings) {
10528
10679
  const row = document.createElement("button");
10529
10680
  row.type = "button";
10530
10681
  row.className = "codexuse-target-row";
10531
- row.title = "Continue this thread on " + (target.label || "account");
10682
+ row.title = "Continue this thread in " + (target.label || "account");
10532
10683
  const label = document.createElement("span");
10533
10684
  label.className = "codexuse-target-label";
10534
10685
  label.textContent = typeof target.label === "string" && target.label.trim() ? target.label.trim() : "Account";
@@ -10542,12 +10693,14 @@ function createBridgeApplyQolExpression(settings) {
10542
10693
  row.addEventListener("click", (event) => {
10543
10694
  event.preventDefault();
10544
10695
  event.stopPropagation();
10696
+ event.stopImmediatePropagation?.();
10545
10697
  handOffTo(target);
10546
10698
  }, true);
10547
10699
  list.appendChild(row);
10548
10700
  });
10549
10701
  picker.appendChild(list);
10550
10702
  document.body.appendChild(picker);
10703
+ placePicker(picker);
10551
10704
  chip.dataset.open = "true";
10552
10705
  // Rebuild the open picker once fresh targets arrive; skipRefresh stops
10553
10706
  // the rebuilt picker from requesting again in a loop.
@@ -10558,14 +10711,56 @@ function createBridgeApplyQolExpression(settings) {
10558
10711
  }
10559
10712
  const onDocPointer = (event) => {
10560
10713
  if (picker.contains(event.target) || chip.contains(event.target)) return;
10561
- document.removeEventListener("pointerdown", onDocPointer, true);
10562
10714
  closePicker();
10563
10715
  };
10716
+ const onKeyDown = (event) => {
10717
+ if (event.key === "Escape") closePicker();
10718
+ };
10564
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
+ };
10565
10725
  };
10566
- chip.addEventListener("click", (event) => {
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) => {
10567
10757
  event.preventDefault();
10568
10758
  event.stopPropagation();
10759
+ event.stopImmediatePropagation?.();
10760
+ if (dragMoved || chip.dataset.disabled === "true") {
10761
+ dragMoved = false;
10762
+ return;
10763
+ }
10569
10764
  // Always make the user pick the destination account explicitly \u2014 never
10570
10765
  // auto-hand-off to a server-chosen account (which could be out of quota).
10571
10766
  if (chip.dataset.open === "true") {
@@ -10574,7 +10769,38 @@ function createBridgeApplyQolExpression(settings) {
10574
10769
  openPicker();
10575
10770
  }
10576
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);
10577
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();
10578
10804
  reportHandoff("rendered");
10579
10805
  return true;
10580
10806
  };
@@ -10626,12 +10852,16 @@ function createBridgeApplyQolExpression(settings) {
10626
10852
  clearTimelineTargets();
10627
10853
  document.getElementById(statusId)?.remove();
10628
10854
  document.getElementById(handoffId)?.remove();
10855
+ document.getElementById(handoffPickerId)?.remove();
10856
+ if (typeof cleanupHandoffPicker === "function") cleanupHandoffPicker();
10629
10857
  };
10630
10858
  } else {
10631
10859
  document.querySelectorAll("." + timelineClass).forEach((node) => node.remove());
10632
10860
  clearTimelineTargets();
10633
10861
  document.getElementById(statusId)?.remove();
10634
10862
  document.getElementById(handoffId)?.remove();
10863
+ document.getElementById(handoffPickerId)?.remove();
10864
+ if (typeof cleanupHandoffPicker === "function") cleanupHandoffPicker();
10635
10865
  }
10636
10866
 
10637
10867
  state.wideViewEnabled = wideViewEnabled;
@@ -11926,7 +12156,8 @@ function normalizeOfficialCodexQolSettings2(value) {
11926
12156
  actionTargets,
11927
12157
  canonicalSyncLine: typeof value.canonicalSyncLine === "string" && value.canonicalSyncLine.trim() ? value.canonicalSyncLine.trim().slice(0, 40) : null,
11928
12158
  autoRollDecision: normalizeOverlayAutoRollDecision(value.autoRollDecision),
11929
- 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
11930
12161
  };
11931
12162
  }
11932
12163
  function hasActiveQolSettings(settings) {
@@ -12753,7 +12984,7 @@ function startOfficialCodexBridgeActionListener(args) {
12753
12984
  if (!action) {
12754
12985
  return;
12755
12986
  }
12756
- 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;
12757
12988
  const settingValue = typeof payload.settingValue === "boolean" ? payload.settingValue : null;
12758
12989
  if (action === "setting" && (!settingKey || settingValue === null)) {
12759
12990
  return;
@@ -15118,7 +15349,8 @@ async function handleProfileCommand(args, version) {
15118
15349
  }
15119
15350
 
15120
15351
  // ../../packages/contracts/src/cloud-sync/types.ts
15121
- var CLOUD_SYNC_SCHEMA_VERSION = 1;
15352
+ var CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION = 1;
15353
+ var CLOUD_SYNC_ENCRYPTED_SCHEMA_VERSION = 2;
15122
15354
 
15123
15355
  // ../../packages/shared/src/core/type-guards.ts
15124
15356
  function isRecord6(value) {
@@ -15158,6 +15390,12 @@ function buildSyncUrl(pathname) {
15158
15390
  const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
15159
15391
  return `${baseUrl}${normalizedPath}`;
15160
15392
  }
15393
+ function normalizeSnapshotKind(value) {
15394
+ if (value === "encrypted" || value === "legacy-plaintext") {
15395
+ return value;
15396
+ }
15397
+ return "none";
15398
+ }
15161
15399
  function normalizeSnapshot(value) {
15162
15400
  if (!isRecord6(value)) {
15163
15401
  return null;
@@ -15167,7 +15405,37 @@ function normalizeSnapshot(value) {
15167
15405
  return null;
15168
15406
  }
15169
15407
  const schemaVersion = Number(value.schemaVersion);
15170
- if (!Number.isFinite(schemaVersion) || schemaVersion !== CLOUD_SYNC_SCHEMA_VERSION) {
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) {
15171
15439
  return null;
15172
15440
  }
15173
15441
  const rawProfiles = Array.isArray(value.profiles) ? value.profiles : [];
@@ -15191,7 +15459,7 @@ function normalizeSnapshot(value) {
15191
15459
  const rawSettings = value.settingsJson;
15192
15460
  const settingsJson = isRecord6(rawSettings) ? rawSettings : null;
15193
15461
  return {
15194
- schemaVersion: CLOUD_SYNC_SCHEMA_VERSION,
15462
+ schemaVersion: CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION,
15195
15463
  updatedAt,
15196
15464
  profiles,
15197
15465
  configTomlContent: typeof value.configTomlContent === "string" ? value.configTomlContent : null,
@@ -15243,7 +15511,12 @@ async function fetchRemoteSnapshotMeta(licenseKey) {
15243
15511
  "/v1/sync/snapshot/meta",
15244
15512
  licenseKey
15245
15513
  );
15246
- return toIsoOrNull(response.updatedAt);
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
+ };
15247
15520
  }
15248
15521
  async function pushRemoteSnapshot(licenseKey, snapshot, options) {
15249
15522
  const serializedSnapshot = typeof options?.serializedSnapshot === "string" ? options.serializedSnapshot : JSON.stringify(snapshot);
@@ -16224,6 +16497,170 @@ async function saveCodexConfigContent(content, options) {
16224
16497
  return buildSnapshot(normalized, true, options);
16225
16498
  }
16226
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
+
16227
16664
  // ../../packages/runtime-profiles/src/cloud-sync/service.ts
16228
16665
  var SYNC_SIZE_WARN_BYTES = 1 * 1024 * 1024;
16229
16666
  var SYNC_SIZE_MAX_BYTES = 5 * 1024 * 1024;
@@ -16276,7 +16713,8 @@ async function readState() {
16276
16713
  lastPushAt: state.sync.lastPushAt ?? void 0,
16277
16714
  lastPullAt: state.sync.lastPullAt ?? void 0,
16278
16715
  lastError: state.sync.lastError ?? void 0,
16279
- remoteUpdatedAt: state.sync.remoteUpdatedAt ?? void 0
16716
+ remoteUpdatedAt: state.sync.remoteUpdatedAt ?? void 0,
16717
+ remoteKind: state.sync.remoteKind ?? "none"
16280
16718
  };
16281
16719
  }
16282
16720
  async function writeState(patch) {
@@ -16285,7 +16723,8 @@ async function writeState(patch) {
16285
16723
  ...typeof patch.lastPushAt === "string" ? { lastPushAt: patch.lastPushAt } : {},
16286
16724
  ...typeof patch.lastPullAt === "string" ? { lastPullAt: patch.lastPullAt } : {},
16287
16725
  ...typeof patch.lastError === "string" ? { lastError: patch.lastError } : patch.lastError === void 0 ? { lastError: null } : {},
16288
- ...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 } : {}
16289
16728
  }
16290
16729
  });
16291
16730
  }
@@ -16338,7 +16777,7 @@ async function buildLocalSnapshot(profileManager, options = {}) {
16338
16777
  throw new Error("Refusing to push an empty cloud sync snapshot.");
16339
16778
  }
16340
16779
  const snapshot = {
16341
- schemaVersion: CLOUD_SYNC_SCHEMA_VERSION,
16780
+ schemaVersion: CLOUD_SYNC_PLAINTEXT_SCHEMA_VERSION,
16342
16781
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
16343
16782
  profiles,
16344
16783
  configTomlContent: config.exists ? config.content : null,
@@ -16363,6 +16802,34 @@ async function applyRemoteSnapshot(profileManager, snapshot) {
16363
16802
  await writeCodexSettingsJsonRaw({});
16364
16803
  }
16365
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
+ }
16366
16833
  function toRunError(mode, error) {
16367
16834
  const message = formatUserFacingError(error, {
16368
16835
  fallback: `Cloud sync ${mode} failed.`
@@ -16379,14 +16846,23 @@ async function getCloudSyncStatus() {
16379
16846
  const eligibility = await resolveEligibility();
16380
16847
  const state = await readState();
16381
16848
  let remoteUpdatedAt = state.remoteUpdatedAt ?? null;
16849
+ let remoteKind = state.remoteKind ?? "none";
16850
+ let hasRememberedPassphrase = false;
16382
16851
  if (eligibility.canSync && eligibility.licenseKey) {
16852
+ hasRememberedPassphrase = Boolean(
16853
+ await readRememberedCloudSyncPassphrase(eligibility.licenseKey).catch(() => null)
16854
+ );
16383
16855
  try {
16384
- remoteUpdatedAt = await fetchRemoteSnapshotMeta(eligibility.licenseKey);
16856
+ const remoteMeta = await fetchRemoteSnapshotMeta(eligibility.licenseKey);
16857
+ remoteUpdatedAt = remoteMeta.updatedAt;
16858
+ remoteKind = remoteMeta.snapshotKind;
16859
+ await writeState({ remoteUpdatedAt, remoteKind });
16385
16860
  } catch (error) {
16386
16861
  if (error instanceof CloudSyncClientError && error.status === 404) {
16387
16862
  try {
16388
16863
  const remote = await fetchRemoteSnapshot(eligibility.licenseKey);
16389
16864
  remoteUpdatedAt = remote?.updatedAt ?? null;
16865
+ remoteKind = getSnapshotKind(remote);
16390
16866
  } catch {
16391
16867
  }
16392
16868
  }
@@ -16397,13 +16873,15 @@ async function getCloudSyncStatus() {
16397
16873
  reason: eligibility.reason,
16398
16874
  licenseState: eligibility.licenseState,
16399
16875
  hasLicenseKey: Boolean(eligibility.licenseKey),
16876
+ hasRememberedPassphrase,
16400
16877
  lastPushAt: state.lastPushAt ?? null,
16401
16878
  lastPullAt: state.lastPullAt ?? null,
16402
16879
  lastError: state.lastError ?? null,
16403
- remoteUpdatedAt
16880
+ remoteUpdatedAt,
16881
+ remoteKind
16404
16882
  };
16405
16883
  }
16406
- async function pushCloudSync() {
16884
+ async function pushCloudSync(options = {}) {
16407
16885
  const eligibility = await resolveEligibility();
16408
16886
  if (!eligibility.canSync || !eligibility.licenseKey) {
16409
16887
  return {
@@ -16418,7 +16896,9 @@ async function pushCloudSync() {
16418
16896
  try {
16419
16897
  await profileManager.initialize();
16420
16898
  const localSnapshot = await buildLocalSnapshot(profileManager, { enforcePushGuards: true });
16421
- const serializedSnapshot = JSON.stringify(localSnapshot);
16899
+ const passphrase = await resolvePassphrase(eligibility.licenseKey, options);
16900
+ const encryptedSnapshot = await encryptCloudSyncSnapshot(localSnapshot, passphrase.value);
16901
+ const serializedSnapshot = JSON.stringify(encryptedSnapshot);
16422
16902
  const snapshotBytes = Buffer.byteLength(serializedSnapshot, "utf8");
16423
16903
  if (snapshotBytes > SYNC_SIZE_WARN_BYTES) {
16424
16904
  logWarn("[cloud-sync] push snapshot is large", {
@@ -16434,12 +16914,14 @@ async function pushCloudSync() {
16434
16914
  if (process.env.NODE_ENV !== "production") {
16435
16915
  logInfo("[cloud-sync] push snapshot summary", summarizeSnapshot(localSnapshot));
16436
16916
  }
16437
- const remote = await pushRemoteSnapshot(eligibility.licenseKey, localSnapshot, {
16917
+ const remote = await pushRemoteSnapshot(eligibility.licenseKey, encryptedSnapshot, {
16438
16918
  serializedSnapshot
16439
16919
  });
16920
+ const remoteKind = getSnapshotKind(remote.snapshot);
16440
16921
  await writeState({
16441
16922
  lastPushAt: (/* @__PURE__ */ new Date()).toISOString(),
16442
16923
  remoteUpdatedAt: remote.snapshot.updatedAt,
16924
+ remoteKind,
16443
16925
  lastError: void 0
16444
16926
  });
16445
16927
  if (remote.status === "stale") {
@@ -16451,6 +16933,7 @@ async function pushCloudSync() {
16451
16933
  remoteUpdatedAt: remote.snapshot.updatedAt
16452
16934
  };
16453
16935
  }
16936
+ await rememberPassphraseIfRequested(eligibility.licenseKey, options, passphrase);
16454
16937
  return {
16455
16938
  mode: "push",
16456
16939
  status: "applied",
@@ -16465,7 +16948,7 @@ async function pushCloudSync() {
16465
16948
  return result;
16466
16949
  }
16467
16950
  }
16468
- async function pullCloudSync() {
16951
+ async function pullCloudSync(options = {}) {
16469
16952
  const eligibility = await resolveEligibility();
16470
16953
  if (!eligibility.canSync || !eligibility.licenseKey) {
16471
16954
  return {
@@ -16481,6 +16964,7 @@ async function pullCloudSync() {
16481
16964
  await profileManager.initialize();
16482
16965
  const remote = await fetchRemoteSnapshot(eligibility.licenseKey);
16483
16966
  if (!remote) {
16967
+ await writeState({ remoteUpdatedAt: null, remoteKind: "none" });
16484
16968
  return {
16485
16969
  mode: "pull",
16486
16970
  status: "skipped",
@@ -16489,17 +16973,30 @@ async function pullCloudSync() {
16489
16973
  remoteUpdatedAt: null
16490
16974
  };
16491
16975
  }
16492
- await applyRemoteSnapshot(profileManager, remote);
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);
16493
16986
  await writeState({
16494
16987
  lastPullAt: (/* @__PURE__ */ new Date()).toISOString(),
16495
16988
  remoteUpdatedAt: remote.updatedAt,
16989
+ remoteKind,
16496
16990
  lastError: void 0
16497
16991
  });
16992
+ if (passphrase) {
16993
+ await rememberPassphraseIfRequested(eligibility.licenseKey, options, passphrase);
16994
+ }
16498
16995
  return {
16499
16996
  mode: "pull",
16500
16997
  status: "applied",
16501
- message: "Cloud sync pull completed.",
16502
- localUpdatedAt: remote.updatedAt,
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,
16503
17000
  remoteUpdatedAt: remote.updatedAt
16504
17001
  };
16505
17002
  } catch (error) {
@@ -16511,6 +17008,20 @@ async function pullCloudSync() {
16511
17008
  }
16512
17009
 
16513
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
+ }
16514
17025
  function printSyncResult(result) {
16515
17026
  console.log(result.message);
16516
17027
  if (result.localUpdatedAt) {
@@ -16542,6 +17053,8 @@ async function handleSync(args, version) {
16542
17053
  if (status.remoteUpdatedAt) {
16543
17054
  console.log(`Remote snapshot: ${status.remoteUpdatedAt}`);
16544
17055
  }
17056
+ console.log(`Remote kind: ${status.remoteKind}`);
17057
+ console.log(`Remembered passphrase: ${status.hasRememberedPassphrase ? "yes" : "no"}`);
16545
17058
  if (status.lastPushAt) {
16546
17059
  console.log(`Last push: ${status.lastPushAt}`);
16547
17060
  }
@@ -16554,7 +17067,7 @@ async function handleSync(args, version) {
16554
17067
  return;
16555
17068
  }
16556
17069
  case "pull": {
16557
- const result = await pullCloudSync();
17070
+ const result = await pullCloudSync(await resolvePassphraseOptions(flags));
16558
17071
  printSyncResult(result);
16559
17072
  if (result.status === "error") {
16560
17073
  process.exitCode = 1;
@@ -16562,7 +17075,7 @@ async function handleSync(args, version) {
16562
17075
  return;
16563
17076
  }
16564
17077
  case "push": {
16565
- const result = await pushCloudSync();
17078
+ const result = await pushCloudSync(await resolvePassphraseOptions(flags));
16566
17079
  printSyncResult(result);
16567
17080
  if (result.status === "error") {
16568
17081
  process.exitCode = 1;
@@ -16590,7 +17103,7 @@ async function ensureCliStorageReady() {
16590
17103
  }
16591
17104
 
16592
17105
  // src/app/main.ts
16593
- var VERSION = true ? "5.0.6" : "0.0.0";
17106
+ var VERSION = true ? "5.0.8" : "0.0.0";
16594
17107
  async function runCli() {
16595
17108
  const args = process.argv.slice(2);
16596
17109
  if (args.length === 0) {