@synkro-sh/cli 1.10.6 → 1.10.9

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/bootstrap.js CHANGED
@@ -81,10 +81,10 @@ import { createHash } from "crypto";
81
81
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
82
82
  import { homedir as homedir2, hostname, platform } from "os";
83
83
  import { join as join2 } from "path";
84
- function readConfigValue(key) {
85
- if (process.env[key]) return String(process.env[key]).toLowerCase();
84
+ function readConfigValue(key2) {
85
+ if (process.env[key2]) return String(process.env[key2]).toLowerCase();
86
86
  try {
87
- const re = key === "SYNKRO_GRADING_MODE" ? /^SYNKRO_GRADING_MODE=['"]?([^'"\n]*)/m : /^SYNKRO_STORAGE_MODE=['"]?([^'"\n]*)/m;
87
+ const re = key2 === "SYNKRO_GRADING_MODE" ? /^SYNKRO_GRADING_MODE=['"]?([^'"\n]*)/m : /^SYNKRO_STORAGE_MODE=['"]?([^'"\n]*)/m;
88
88
  const m = readFileSync2(CONFIG_PATH, "utf-8").match(re);
89
89
  return m ? m[1].toLowerCase() : "";
90
90
  } catch {
@@ -147,7 +147,7 @@ function getIdentity() {
147
147
  if (cached2) return cached2;
148
148
  let cliVersion2 = "0.0.0";
149
149
  try {
150
- cliVersion2 = "1.10.6";
150
+ cliVersion2 = "1.10.9";
151
151
  } catch {
152
152
  }
153
153
  const creds = loadCredentialsIdentity();
@@ -789,9 +789,9 @@ function sanitize(raw, maxLen = 256) {
789
789
  function shellQuoteSingle(value) {
790
790
  return `'${value.replace(/'/g, "'\\''")}'`;
791
791
  }
792
- function writeConfigEnvFlag(key, value) {
792
+ function writeConfigEnvFlag(key2, value) {
793
793
  const safe = sanitize(value, 8);
794
- const line = `${key}=${shellQuoteSingle(safe)}`;
794
+ const line = `${key2}=${shellQuoteSingle(safe)}`;
795
795
  let content = "";
796
796
  if (existsSync5(CONFIG_PATH2)) {
797
797
  try {
@@ -800,7 +800,7 @@ function writeConfigEnvFlag(key, value) {
800
800
  content = "";
801
801
  }
802
802
  }
803
- const re = new RegExp(`^${key}=.*$`, "m");
803
+ const re = new RegExp(`^${key2}=.*$`, "m");
804
804
  if (re.test(content)) {
805
805
  content = content.replace(re, line);
806
806
  } else {
@@ -1231,15 +1231,15 @@ async function exportEvents(path) {
1231
1231
  writeFileSync4(path, "", "utf-8");
1232
1232
  return;
1233
1233
  }
1234
- const lines = [];
1234
+ const lines2 = [];
1235
1235
  try {
1236
1236
  const rows = await sql`SELECT * FROM telemetry_events ORDER BY occurred_at ASC`;
1237
1237
  for (const row2 of rows) {
1238
- lines.push(JSON.stringify(row2));
1238
+ lines2.push(JSON.stringify(row2));
1239
1239
  }
1240
1240
  } catch {
1241
1241
  }
1242
- writeFileSync4(path, lines.join("\n") + (lines.length > 0 ? "\n" : ""), "utf-8");
1242
+ writeFileSync4(path, lines2.join("\n") + (lines2.length > 0 ? "\n" : ""), "utf-8");
1243
1243
  }
1244
1244
  function pendingFileSize() {
1245
1245
  try {
@@ -1856,21 +1856,21 @@ function validateHooksPath(path) {
1856
1856
  return resolved;
1857
1857
  }
1858
1858
  function readHooksFile(rawPath) {
1859
- const safePath = validateHooksPath(rawPath);
1859
+ const safePath2 = validateHooksPath(rawPath);
1860
1860
  try {
1861
- const raw = readFileSync10(safePath, "utf-8");
1861
+ const raw = readFileSync10(safePath2, "utf-8");
1862
1862
  return JSON.parse(raw);
1863
1863
  } catch (err) {
1864
1864
  if (err?.code === "ENOENT") return { version: 1, hooks: {} };
1865
- throw new Error(`Failed to parse ${safePath}: ${err.message}`);
1865
+ throw new Error(`Failed to parse ${safePath2}: ${err.message}`);
1866
1866
  }
1867
1867
  }
1868
1868
  function writeHooksFileAtomic(rawPath, data) {
1869
- const safePath = validateHooksPath(rawPath);
1870
- mkdirSync5(dirname2(safePath), { recursive: true });
1871
- const tmpPath = `${safePath}.synkro.tmp`;
1869
+ const safePath2 = validateHooksPath(rawPath);
1870
+ mkdirSync5(dirname2(safePath2), { recursive: true });
1871
+ const tmpPath = `${safePath2}.synkro.tmp`;
1872
1872
  writeFileSync7(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
1873
- renameSync4(tmpPath, safePath);
1873
+ renameSync4(tmpPath, safePath2);
1874
1874
  }
1875
1875
  function isSynkroEntry2(entry) {
1876
1876
  if (entry?.[SYNKRO_MARKER2]) return true;
@@ -2158,20 +2158,20 @@ function validateHooksPath2(path) {
2158
2158
  return resolved;
2159
2159
  }
2160
2160
  function readHooksFile2(rawPath) {
2161
- const safePath = validateHooksPath2(rawPath);
2162
- if (!existsSync12(safePath)) return { hooks: {} };
2161
+ const safePath2 = validateHooksPath2(rawPath);
2162
+ if (!existsSync12(safePath2)) return { hooks: {} };
2163
2163
  try {
2164
- return JSON.parse(readFileSync11(safePath, "utf-8"));
2164
+ return JSON.parse(readFileSync11(safePath2, "utf-8"));
2165
2165
  } catch (err) {
2166
- throw new Error(`Failed to parse ${safePath}: ${err.message}`);
2166
+ throw new Error(`Failed to parse ${safePath2}: ${err.message}`);
2167
2167
  }
2168
2168
  }
2169
2169
  function writeHooksFileAtomic2(rawPath, data) {
2170
- const safePath = validateHooksPath2(rawPath);
2171
- mkdirSync6(dirname3(safePath), { recursive: true });
2172
- const tmpPath = `${safePath}.synkro.tmp`;
2170
+ const safePath2 = validateHooksPath2(rawPath);
2171
+ mkdirSync6(dirname3(safePath2), { recursive: true });
2172
+ const tmpPath = `${safePath2}.synkro.tmp`;
2173
2173
  writeFileSync8(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
2174
- renameSync5(tmpPath, safePath);
2174
+ renameSync5(tmpPath, safePath2);
2175
2175
  }
2176
2176
  function isSynkroEntry3(entry) {
2177
2177
  if (entry?.[SYNKRO_MARKER3]) return true;
@@ -2351,7 +2351,7 @@ function buildCodexHookTrustEdits(hooks) {
2351
2351
  return [...edits.values()];
2352
2352
  }
2353
2353
  function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTrust = false) {
2354
- return new Promise((resolve7) => {
2354
+ return new Promise((resolve9) => {
2355
2355
  let settled = false;
2356
2356
  let stdout = "";
2357
2357
  let pending = "";
@@ -2369,7 +2369,7 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
2369
2369
  child.kill();
2370
2370
  } catch {
2371
2371
  }
2372
- resolve7(summary);
2372
+ resolve9(summary);
2373
2373
  };
2374
2374
  const timer = setTimeout(() => finish(null), 1e4);
2375
2375
  try {
@@ -2384,9 +2384,9 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
2384
2384
  child.stdout.on("data", (chunk) => {
2385
2385
  stdout += chunk;
2386
2386
  pending += chunk;
2387
- const lines = pending.split("\n");
2388
- pending = lines.pop() || "";
2389
- for (const line of lines) {
2387
+ const lines2 = pending.split("\n");
2388
+ pending = lines2.pop() || "";
2389
+ for (const line of lines2) {
2390
2390
  let message;
2391
2391
  try {
2392
2392
  message = JSON.parse(line);
@@ -2620,11 +2620,11 @@ function writeCodexTomlAtomic(path, content) {
2620
2620
  renameSync6(tmpPath, path);
2621
2621
  }
2622
2622
  function removeCodexManagedBlock(content, path) {
2623
- const lines = content.split("\n");
2623
+ const lines2 = content.split("\n");
2624
2624
  const out = [];
2625
2625
  let removed = false;
2626
2626
  let inside = false;
2627
- for (const line of lines) {
2627
+ for (const line of lines2) {
2628
2628
  if (!inside && line.trim() === CODEX_MCP_BEGIN) {
2629
2629
  inside = true;
2630
2630
  removed = true;
@@ -2652,8 +2652,8 @@ function findCodexMcpSection(content) {
2652
2652
  body: content.slice(match.index, next?.index ?? content.length)
2653
2653
  };
2654
2654
  }
2655
- function readTomlString(block, key) {
2656
- const match = block.match(new RegExp(`^\\s*${key}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")\\s*$`, "m"));
2655
+ function readTomlString(block, key2) {
2656
+ const match = block.match(new RegExp(`^\\s*${key2}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")\\s*$`, "m"));
2657
2657
  if (!match) return null;
2658
2658
  try {
2659
2659
  return JSON.parse(match[1]);
@@ -5181,7 +5181,7 @@ function createCallbackServer() {
5181
5181
  "Access-Control-Allow-Headers": "Content-Type",
5182
5182
  "Vary": "Origin"
5183
5183
  };
5184
- return new Promise((resolve7, reject) => {
5184
+ return new Promise((resolve9, reject) => {
5185
5185
  const server = createServer((req, res) => {
5186
5186
  if (req.method === "OPTIONS") {
5187
5187
  const origin = req.headers.origin;
@@ -5270,7 +5270,7 @@ function createCallbackServer() {
5270
5270
  res.end(JSON.stringify({ ok: true }));
5271
5271
  setTimeout(() => {
5272
5272
  server.close();
5273
- resolve7(authData);
5273
+ resolve9(authData);
5274
5274
  }, 200);
5275
5275
  });
5276
5276
  req.on("error", (e) => {
@@ -5606,7 +5606,7 @@ function detectSubdirRepos() {
5606
5606
  }
5607
5607
  }
5608
5608
  function ask(rl, question) {
5609
- return new Promise((resolve7) => rl.question(question, resolve7));
5609
+ return new Promise((resolve9) => rl.question(question, resolve9));
5610
5610
  }
5611
5611
  async function linkRepo(repo, linkedNames) {
5612
5612
  try {
@@ -5845,7 +5845,7 @@ async function runClaudeDesktopTap(opts = {}) {
5845
5845
  writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
5846
5846
  const runnerPath = join13(sessionDir, "run.sh");
5847
5847
  writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
5848
- await new Promise((resolve7) => {
5848
+ await new Promise((resolve9) => {
5849
5849
  const child = spawn3("bash", [runnerPath], {
5850
5850
  stdio: "inherit",
5851
5851
  env: { ...process.env, SYNKRO_CAPTURE_URL: CAPTURE_URL, SYNKRO_SCAN_URL: SCAN_URL, SYNKRO_SCAN_TURN_URL: SCAN_TURN_URL, SYNKRO_DLP_POLICY_URL: DLP_POLICY_URL, SYNKRO_TURN_VERDICTS_URL: TURN_VERDICTS_URL, SYNKRO_TURN_VERDICT_URL: TURN_VERDICT_URL, SYNKRO_MCP_EVENT_URL: MCP_EVENT_URL, SYNKRO_TAP_TOKEN: token, SYNKRO_TAP_TOKEN_FILE: JWT_PATH, SYNKRO_CD_BACKFILL: opts.backfill ? "1" : "" }
@@ -5861,7 +5861,7 @@ async function runClaudeDesktopTap(opts = {}) {
5861
5861
  child.on("exit", () => {
5862
5862
  process.off("SIGINT", forward);
5863
5863
  process.off("SIGTERM", forward);
5864
- resolve7();
5864
+ resolve9();
5865
5865
  });
5866
5866
  });
5867
5867
  }
@@ -7034,8 +7034,8 @@ function cursorApiKeyConfigured() {
7034
7034
  return false;
7035
7035
  }
7036
7036
  }
7037
- function writeCursorApiKey(key) {
7038
- const trimmed = key.trim();
7037
+ function writeCursorApiKey(key2) {
7038
+ const trimmed = key2.trim();
7039
7039
  if (!trimmed) return;
7040
7040
  mkdirSync11(CURSOR_CREDS_DIR, { recursive: true });
7041
7041
  chmodSync2(CURSOR_CREDS_DIR, 448);
@@ -7043,15 +7043,15 @@ function writeCursorApiKey(key) {
7043
7043
  chmodSync2(CURSOR_API_KEY_FILE, 384);
7044
7044
  }
7045
7045
  async function validateCursorApiKey() {
7046
- let key;
7046
+ let key2;
7047
7047
  try {
7048
- key = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
7048
+ key2 = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
7049
7049
  } catch {
7050
7050
  return null;
7051
7051
  }
7052
- if (!key) return null;
7052
+ if (!key2) return null;
7053
7053
  try {
7054
- const auth = Buffer.from(`${key}:`).toString("base64");
7054
+ const auth = Buffer.from(`${key2}:`).toString("base64");
7055
7055
  const r = await fetch("https://api.cursor.com/v1/me", {
7056
7056
  headers: { Authorization: `Basic ${auth}` },
7057
7057
  signal: AbortSignal.timeout(8e3)
@@ -7391,15 +7391,15 @@ function parseSynkroToml(raw) {
7391
7391
  }
7392
7392
  const eq = line.indexOf("=");
7393
7393
  if (eq === -1) continue;
7394
- const key = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
7394
+ const key2 = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
7395
7395
  let valRaw = line.slice(eq + 1).trim();
7396
7396
  if (!valRaw.startsWith('"') && !valRaw.startsWith("'") && !valRaw.startsWith("[")) {
7397
7397
  const h = valRaw.indexOf("#");
7398
7398
  if (h !== -1) valRaw = valRaw.slice(0, h).trim();
7399
7399
  }
7400
7400
  const value = parseTomlValue(valRaw);
7401
- if (section) result[section][key] = value;
7402
- else result[key] = value;
7401
+ if (section) result[section][key2] = value;
7402
+ else result[key2] = value;
7403
7403
  }
7404
7404
  return result;
7405
7405
  }
@@ -7676,7 +7676,7 @@ async function dockerInstall(opts = {}) {
7676
7676
  "SYNKRO_CODEX_MODEL",
7677
7677
  "SYNKRO_CONDUCTOR_MODEL",
7678
7678
  "SYNKRO_ROUTE_MODEL"
7679
- ].flatMap((key) => process.env[key] ? ["-e", `${key}=${process.env[key]}`] : []),
7679
+ ].flatMap((key2) => process.env[key2] ? ["-e", `${key2}=${process.env[key2]}`] : []),
7680
7680
  // Fix-poll kill switch. Default ON in the image; a benchmark/headless run
7681
7681
  // (e.g. sec-code-bench) sets SYNKRO_FIX_POLL=0 so ask-mode violations skip the
7682
7682
  // interactive AskUserQuestion poll and fall through to generate-the-fix. Only
@@ -7934,7 +7934,7 @@ var init_dockerInstall = __esm({
7934
7934
  HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
7935
7935
  CONTAINER_NAME = resolveContainerName();
7936
7936
  defaultImageVersion = () => {
7937
- if (true) return "1.10.6";
7937
+ if (true) return "1.10.9";
7938
7938
  try {
7939
7939
  const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
7940
7940
  if (pkg.version) return pkg.version;
@@ -7966,7 +7966,7 @@ function captureClaudeSetupToken() {
7966
7966
  const bin = "script";
7967
7967
  const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
7968
7968
  const OAUTH_HINT = 'The browser approval did not return a token. This usually means claude.ai rejected the OAuth request (its "Authorization failed \u2014 Unsupported media type" page), most often because a browser extension (Grammarly, ad/script blockers, AI-assistant toolbars) stripped the request headers, or you approved on the wrong Claude account. Fix: retry in a clean incognito window with extensions disabled, and approve on the Claude account you want the cloud workers to use.';
7969
- return new Promise((resolve7, reject) => {
7969
+ return new Promise((resolve9, reject) => {
7970
7970
  const proc = nodeSpawn(bin, args2, {
7971
7971
  stdio: "inherit",
7972
7972
  env: { ...process.env, FORCE_COLOR: "3", COLORTERM: "truecolor", TERM: "xterm-256color" }
@@ -8033,7 +8033,7 @@ function captureClaudeSetupToken() {
8033
8033
  reject(new Error(`Captured no setup token from claude setup-token output. ${reason}`));
8034
8034
  return;
8035
8035
  }
8036
- resolve7(token);
8036
+ resolve9(token);
8037
8037
  });
8038
8038
  });
8039
8039
  }
@@ -8059,13 +8059,13 @@ function findCodexBinary() {
8059
8059
  function runCodexLogin(codexBin, codexHome) {
8060
8060
  mkdirSync13(codexHome, { recursive: true, mode: 448 });
8061
8061
  writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
8062
- return new Promise((resolve7, reject) => {
8062
+ return new Promise((resolve9, reject) => {
8063
8063
  const proc = nodeSpawn2(codexBin, ["login"], {
8064
8064
  stdio: "inherit",
8065
8065
  env: { ...process.env, CODEX_HOME: codexHome }
8066
8066
  });
8067
8067
  proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
8068
- proc.on("close", (code) => code === 0 ? resolve7() : reject(new Error(`codex login exited with code ${code}`)));
8068
+ proc.on("close", (code) => code === 0 ? resolve9() : reject(new Error(`codex login exited with code ${code}`)));
8069
8069
  });
8070
8070
  }
8071
8071
  async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
@@ -8477,8 +8477,8 @@ function addUsage(a, b) {
8477
8477
  }
8478
8478
  function parseCodexTranscriptUsage(transcript, options = {}) {
8479
8479
  if (!transcript) return null;
8480
- const lines = transcript.split("\n");
8481
- const hasCanonicalAssistant = lines.some((line) => {
8480
+ const lines2 = transcript.split("\n");
8481
+ const hasCanonicalAssistant = lines2.some((line) => {
8482
8482
  try {
8483
8483
  const entry = JSON.parse(line);
8484
8484
  return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
@@ -8492,8 +8492,8 @@ function parseCodexTranscriptUsage(transcript, options = {}) {
8492
8492
  let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
8493
8493
  let latest = null;
8494
8494
  let sawSnapshot = false;
8495
- for (let i = 0; i < lines.length; i++) {
8496
- const line = lines[i].trim();
8495
+ for (let i = 0; i < lines2.length; i++) {
8496
+ const line = lines2[i].trim();
8497
8497
  if (!line) continue;
8498
8498
  let entry;
8499
8499
  try {
@@ -8675,8 +8675,8 @@ function parseClaudeTranscriptUsage(transcript, fallbackDay = (/* @__PURE__ */ n
8675
8675
  const entryModel = typeof message.model === "string" && message.model ? message.model : "unknown";
8676
8676
  if (entryModel !== "<synthetic>") model = entryModel;
8677
8677
  const day = isoDay(entry.timestamp, fallbackDay);
8678
- const key = `${day}\0${entryModel}`;
8679
- const row2 = rollups.get(key) ?? {
8678
+ const key2 = `${day}\0${entryModel}`;
8679
+ const row2 = rollups.get(key2) ?? {
8680
8680
  day,
8681
8681
  model: entryModel,
8682
8682
  turns: 0,
@@ -8690,7 +8690,7 @@ function parseClaudeTranscriptUsage(transcript, fallbackDay = (/* @__PURE__ */ n
8690
8690
  row2.output_tokens += counts.output_tokens;
8691
8691
  row2.cache_creation_input_tokens += counts.cache_creation_input_tokens;
8692
8692
  row2.cache_read_input_tokens += counts.cache_read_input_tokens;
8693
- rollups.set(key, row2);
8693
+ rollups.set(key2, row2);
8694
8694
  turns += 1;
8695
8695
  usage2.input_tokens += counts.input_tokens;
8696
8696
  usage2.output_tokens += counts.output_tokens;
@@ -8767,20 +8767,20 @@ async function promptAgentSelection(detected) {
8767
8767
  detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
8768
8768
  console.log(` ${detected.length + 1}. Both / all (default)`);
8769
8769
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8770
- const ask3 = () => new Promise((resolve7) => {
8770
+ const ask3 = () => new Promise((resolve9) => {
8771
8771
  rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
8772
8772
  const t = answer.trim().toLowerCase();
8773
8773
  if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
8774
8774
  rl.close();
8775
- return resolve7(detected);
8775
+ return resolve9(detected);
8776
8776
  }
8777
8777
  const n = parseInt(t, 10);
8778
8778
  if (Number.isInteger(n) && n >= 1 && n <= detected.length) {
8779
8779
  rl.close();
8780
- return resolve7([detected[n - 1]]);
8780
+ return resolve9([detected[n - 1]]);
8781
8781
  }
8782
8782
  console.log("Invalid choice. Try again.");
8783
- resolve7(ask3());
8783
+ resolve9(ask3());
8784
8784
  });
8785
8785
  });
8786
8786
  return ask3();
@@ -8803,17 +8803,17 @@ async function promptCursorApiKey(opts) {
8803
8803
  return;
8804
8804
  }
8805
8805
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8806
- const key = await new Promise((resolve7) => {
8806
+ const key2 = await new Promise((resolve9) => {
8807
8807
  rl.question(
8808
8808
  "Cursor grading needs a Cursor API key (cursor.com \u2192 Settings \u2192 API Keys).\nPaste it now, or press Enter to skip (Cursor workers stay idle until set): ",
8809
8809
  (answer) => {
8810
8810
  rl.close();
8811
- resolve7(answer.trim());
8811
+ resolve9(answer.trim());
8812
8812
  }
8813
8813
  );
8814
8814
  });
8815
- if (key) {
8816
- writeCursorApiKey2(key);
8815
+ if (key2) {
8816
+ writeCursorApiKey2(key2);
8817
8817
  console.log(" \u2713 Cursor API key saved.");
8818
8818
  } else {
8819
8819
  console.log(" \u26A0 Skipped \u2014 Cursor workers will be idle. Re-run install or pass --cursor-api-key=\u2026 later.");
@@ -8823,7 +8823,7 @@ async function promptDeployLocation(current = "local") {
8823
8823
  if (!process.stdin.isTTY) return current;
8824
8824
  const other = current === "cloud" ? "local" : "cloud";
8825
8825
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8826
- return new Promise((resolve7) => {
8826
+ return new Promise((resolve9) => {
8827
8827
  rl.question(
8828
8828
  `Where should Synkro run?
8829
8829
  local \u2014 a grading container on this machine (Docker)
@@ -8832,7 +8832,7 @@ Each worker uses the account credentials you authorize. Choose [${current}] / ${
8832
8832
  (answer) => {
8833
8833
  rl.close();
8834
8834
  const a = answer.trim().toLowerCase();
8835
- resolve7(a === "cloud" ? "cloud" : a === "local" ? "local" : current);
8835
+ resolve9(a === "cloud" ? "cloud" : a === "local" ? "local" : current);
8836
8836
  }
8837
8837
  );
8838
8838
  });
@@ -8986,7 +8986,7 @@ function writeConfigEnv(opts) {
8986
8986
  const safeTier = sanitizeConfigValue(opts.tier ?? "pro", 32);
8987
8987
  const safeInference = sanitizeConfigValue(opts.inference ?? "fast", 16);
8988
8988
  const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? "", 1024);
8989
- const lines = [
8989
+ const lines2 = [
8990
8990
  "# Synkro CLI config (managed by synkro install)",
8991
8991
  "# JWT auth \u2014 the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime",
8992
8992
  "# and send Authorization: Bearer <access_token> on every gateway call.",
@@ -8994,27 +8994,27 @@ function writeConfigEnv(opts) {
8994
8994
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
8995
8995
  `SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
8996
8996
  `SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
8997
- `SYNKRO_VERSION=${shellQuoteSingle2("1.10.6")}`
8997
+ `SYNKRO_VERSION=${shellQuoteSingle2("1.10.9")}`
8998
8998
  ];
8999
- if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
9000
- if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
9001
- if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle2(safeOrgId)}`);
9002
- if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle2(safeEmail)}`);
8999
+ if (safeSynkroBin) lines2.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
9000
+ if (safeUserId) lines2.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
9001
+ if (safeOrgId) lines2.push(`SYNKRO_ORG_ID=${shellQuoteSingle2(safeOrgId)}`);
9002
+ if (safeEmail) lines2.push(`SYNKRO_EMAIL=${shellQuoteSingle2(safeEmail)}`);
9003
9003
  if (opts.transcriptConsent !== void 0) {
9004
- lines.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle2(opts.transcriptConsent ? "yes" : "no")}`);
9004
+ lines2.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle2(opts.transcriptConsent ? "yes" : "no")}`);
9005
9005
  }
9006
- if (opts.transcriptConsentCC !== void 0) lines.push(`SYNKRO_TRANSCRIPT_CONSENT_CC=${shellQuoteSingle2(opts.transcriptConsentCC ? "yes" : "no")}`);
9007
- if (opts.transcriptConsentCursor !== void 0) lines.push(`SYNKRO_TRANSCRIPT_CONSENT_CURSOR=${shellQuoteSingle2(opts.transcriptConsentCursor ? "yes" : "no")}`);
9008
- if (opts.transcriptConsentCodex !== void 0) lines.push(`SYNKRO_TRANSCRIPT_CONSENT_CODEX=${shellQuoteSingle2(opts.transcriptConsentCodex ? "yes" : "no")}`);
9009
- lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle2(opts.localInference ? "yes" : "no")}`);
9006
+ if (opts.transcriptConsentCC !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CC=${shellQuoteSingle2(opts.transcriptConsentCC ? "yes" : "no")}`);
9007
+ if (opts.transcriptConsentCursor !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CURSOR=${shellQuoteSingle2(opts.transcriptConsentCursor ? "yes" : "no")}`);
9008
+ if (opts.transcriptConsentCodex !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CODEX=${shellQuoteSingle2(opts.transcriptConsentCodex ? "yes" : "no")}`);
9009
+ lines2.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle2(opts.localInference ? "yes" : "no")}`);
9010
9010
  const safeMode = sanitizeConfigValue(opts.deploymentMode ?? "docker", 16);
9011
- lines.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle2(safeMode)}`);
9012
- lines.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.gradingMode ?? "local", 16))}`);
9013
- lines.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.storageMode ?? "local", 16))}`);
9014
- lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
9015
- lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
9016
- lines.push("");
9017
- writeFileSync17(CONFIG_PATH4, lines.join("\n"), "utf-8");
9011
+ lines2.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle2(safeMode)}`);
9012
+ lines2.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.gradingMode ?? "local", 16))}`);
9013
+ lines2.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.storageMode ?? "local", 16))}`);
9014
+ lines2.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
9015
+ lines2.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
9016
+ lines2.push("");
9017
+ writeFileSync17(CONFIG_PATH4, lines2.join("\n"), "utf-8");
9018
9018
  chmodSync5(CONFIG_PATH4, 384);
9019
9019
  }
9020
9020
  function persistedTranscriptConsent(source) {
@@ -9741,7 +9741,7 @@ async function installCommand(opts = {}) {
9741
9741
  await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
9742
9742
  emit("install", {
9743
9743
  phase: "started",
9744
- cli_version_to: "1.10.6",
9744
+ cli_version_to: "1.10.9",
9745
9745
  agents_detected: agents.map((a) => a.kind),
9746
9746
  with_github: false,
9747
9747
  with_local_cc: false,
@@ -10303,15 +10303,15 @@ function parseSynkroToml2(raw) {
10303
10303
  }
10304
10304
  const eq = line.indexOf("=");
10305
10305
  if (eq === -1) continue;
10306
- const key = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
10306
+ const key2 = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
10307
10307
  let valRaw = line.slice(eq + 1).trim();
10308
10308
  if (!valRaw.startsWith('"') && !valRaw.startsWith("'") && !valRaw.startsWith("[")) {
10309
10309
  const h = valRaw.indexOf("#");
10310
10310
  if (h !== -1) valRaw = valRaw.slice(0, h).trim();
10311
10311
  }
10312
10312
  const value = parseTomlValue2(valRaw);
10313
- if (section) result[section][key] = value;
10314
- else result[key] = value;
10313
+ if (section) result[section][key2] = value;
10314
+ else result[key2] = value;
10315
10315
  }
10316
10316
  return result;
10317
10317
  }
@@ -10592,8 +10592,8 @@ async function syncSkillFiles() {
10592
10592
  console.log(` \u2298 skill ${source}: empty file, skipped`);
10593
10593
  return null;
10594
10594
  }
10595
- const lines = content.split("\n").length;
10596
- console.log(` \u2192 read ${source} (${lines} lines, ${(content.length / 1024).toFixed(1)} KB)`);
10595
+ const lines2 = content.split("\n").length;
10596
+ console.log(` \u2192 read ${source} (${lines2} lines, ${(content.length / 1024).toFixed(1)} KB)`);
10597
10597
  return { source, content };
10598
10598
  }).filter(Boolean);
10599
10599
  if (tasks.length === 0) return;
@@ -10809,8 +10809,8 @@ function ensureReachabilityGitHook() {
10809
10809
  }
10810
10810
  return "updated";
10811
10811
  }
10812
- const sep3 = cur.endsWith("\n") ? "" : "\n";
10813
- writeFileSync17(hookPath, cur + sep3 + "\n" + block + "\n");
10812
+ const sep4 = cur.endsWith("\n") ? "" : "\n";
10813
+ writeFileSync17(hookPath, cur + sep4 + "\n" + block + "\n");
10814
10814
  try {
10815
10815
  chmodSync5(hookPath, 493);
10816
10816
  } catch {
@@ -10872,10 +10872,10 @@ function extractSessionInsights(projectsDir) {
10872
10872
  const filePath = join19(projectsDir, file);
10873
10873
  try {
10874
10874
  const content = readFileSync21(filePath, "utf-8");
10875
- const lines = content.split("\n").filter(Boolean);
10876
- for (let i = 0; i < lines.length; i++) {
10875
+ const lines2 = content.split("\n").filter(Boolean);
10876
+ for (let i = 0; i < lines2.length; i++) {
10877
10877
  try {
10878
- const entry = JSON.parse(lines[i]);
10878
+ const entry = JSON.parse(lines2[i]);
10879
10879
  if (entry.type === "user" && typeof entry.message?.content === "string" && entry.message.content.startsWith("This session is being continued")) {
10880
10880
  insights.push({
10881
10881
  session_id: sessionId,
@@ -10888,9 +10888,9 @@ function extractSessionInsights(projectsDir) {
10888
10888
  }
10889
10889
  }
10890
10890
  const userMessages = [];
10891
- for (let i = lines.length - 1; i >= 0 && userMessages.length < 20; i--) {
10891
+ for (let i = lines2.length - 1; i >= 0 && userMessages.length < 20; i--) {
10892
10892
  try {
10893
- const entry = JSON.parse(lines[i]);
10893
+ const entry = JSON.parse(lines2[i]);
10894
10894
  if (entry.type === "user") {
10895
10895
  const text = typeof entry.message?.content === "string" ? entry.message.content : Array.isArray(entry.message?.content) ? entry.message.content.map((b) => b.text ?? b).filter((t) => typeof t === "string").join(" ") : null;
10896
10896
  if (text && text.length > 10 && text.length < 2e3 && !text.startsWith("This session is being continued")) {
@@ -10949,13 +10949,13 @@ function extractTextContent(content) {
10949
10949
  function getCodexTranscriptFiles(repo) {
10950
10950
  const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
10951
10951
  if (!existsSync22(sessionsDir)) return [];
10952
- let relative2 = [];
10952
+ let relative3 = [];
10953
10953
  try {
10954
- relative2 = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
10954
+ relative3 = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
10955
10955
  } catch {
10956
10956
  return [];
10957
10957
  }
10958
- return relative2.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
10958
+ return relative3.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
10959
10959
  try {
10960
10960
  const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
10961
10961
  const meta = JSON.parse(first);
@@ -10972,11 +10972,11 @@ function isJsonSyntaxError(error) {
10972
10972
  }
10973
10973
  function parseCodexTranscriptFile(filePath) {
10974
10974
  const transcript = readFileSync21(filePath, "utf-8");
10975
- const lines = transcript.split("\n");
10975
+ const lines2 = transcript.split("\n");
10976
10976
  const transcriptUsage = parseCodexTranscriptUsage(transcript);
10977
10977
  let sessionId = "";
10978
10978
  let model = "";
10979
- for (const line of lines) {
10979
+ for (const line of lines2) {
10980
10980
  try {
10981
10981
  const entry = JSON.parse(line);
10982
10982
  if (entry.type === "session_meta") {
@@ -11060,11 +11060,11 @@ function isSafeConvId(id) {
11060
11060
  }
11061
11061
  function parseCursorTranscriptFile(filePath) {
11062
11062
  const content = readFileSync21(filePath, "utf-8");
11063
- const lines = content.split("\n").filter(Boolean);
11063
+ const lines2 = content.split("\n").filter(Boolean);
11064
11064
  const messages = [];
11065
- for (let i = 0; i < lines.length; i++) {
11065
+ for (let i = 0; i < lines2.length; i++) {
11066
11066
  try {
11067
- const entry = JSON.parse(lines[i]);
11067
+ const entry = JSON.parse(lines2[i]);
11068
11068
  const role = entry.role || entry.message?.role;
11069
11069
  if (role !== "user" && role !== "assistant") continue;
11070
11070
  const text = extractTextContent(entry.message?.content ?? entry.content);
@@ -11124,11 +11124,11 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
11124
11124
  }
11125
11125
  function parseTranscriptFile(filePath) {
11126
11126
  const content = readFileSync21(filePath, "utf-8");
11127
- const lines = content.split("\n").filter(Boolean);
11127
+ const lines2 = content.split("\n").filter(Boolean);
11128
11128
  const messages = [];
11129
- for (let i = 0; i < lines.length; i++) {
11129
+ for (let i = 0; i < lines2.length; i++) {
11130
11130
  try {
11131
- const entry = JSON.parse(lines[i]);
11131
+ const entry = JSON.parse(lines2[i]);
11132
11132
  if (entry.type !== "user" && entry.type !== "assistant") continue;
11133
11133
  const msg = {
11134
11134
  message_index: i,
@@ -12030,10 +12030,10 @@ function confirmPurge() {
12030
12030
  return Promise.resolve(false);
12031
12031
  }
12032
12032
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
12033
- return new Promise((resolve7) => {
12033
+ return new Promise((resolve9) => {
12034
12034
  rl.question(" Type 'yes' to wipe everything (anything else cancels): ", (answer) => {
12035
12035
  rl.close();
12036
- resolve7(answer.trim().toLowerCase() === "yes");
12036
+ resolve9(answer.trim().toLowerCase() === "yes");
12037
12037
  });
12038
12038
  });
12039
12039
  }
@@ -12194,8 +12194,8 @@ function readRecentTurns(n = 20) {
12194
12194
  const size = statSync3(TURN_LOG_PATH).size;
12195
12195
  if (size === 0) return [];
12196
12196
  const text = readFileSync23(TURN_LOG_PATH, "utf-8");
12197
- const lines = text.split("\n").filter(Boolean);
12198
- const lastN = lines.slice(-n).reverse();
12197
+ const lines2 = text.split("\n").filter(Boolean);
12198
+ const lastN = lines2.slice(-n).reverse();
12199
12199
  return lastN.map((line) => {
12200
12200
  try {
12201
12201
  return JSON.parse(line);
@@ -12286,7 +12286,7 @@ async function submitToChannel(role, payload, opts = {}) {
12286
12286
  const port = opts.port ?? CHANNEL_PORT;
12287
12287
  const startedAt = Date.now();
12288
12288
  try {
12289
- const result = await new Promise((resolve7, reject) => {
12289
+ const result = await new Promise((resolve9, reject) => {
12290
12290
  const req = httpRequest({
12291
12291
  host: CHANNEL_HOST,
12292
12292
  port,
@@ -12312,7 +12312,7 @@ async function submitToChannel(role, payload, opts = {}) {
12312
12312
  reject(new LocalCCError(parsed.error));
12313
12313
  return;
12314
12314
  }
12315
- resolve7(String(parsed.result ?? ""));
12315
+ resolve9(String(parsed.result ?? ""));
12316
12316
  } catch (err) {
12317
12317
  reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
12318
12318
  }
@@ -12338,14 +12338,14 @@ async function submitToChannel(role, payload, opts = {}) {
12338
12338
  }
12339
12339
  }
12340
12340
  function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500) {
12341
- return new Promise((resolve7) => {
12341
+ return new Promise((resolve9) => {
12342
12342
  const sock = connect(port, CHANNEL_HOST);
12343
12343
  const done = (ok) => {
12344
12344
  try {
12345
12345
  sock.destroy();
12346
12346
  } catch {
12347
12347
  }
12348
- resolve7(ok);
12348
+ resolve9(ok);
12349
12349
  };
12350
12350
  sock.once("connect", () => done(true));
12351
12351
  sock.once("error", () => done(false));
@@ -12377,10 +12377,10 @@ __export(grade_exports, {
12377
12377
  gradeCommand: () => gradeCommand
12378
12378
  });
12379
12379
  async function readStdin() {
12380
- return new Promise((resolve7, reject) => {
12380
+ return new Promise((resolve9, reject) => {
12381
12381
  const chunks = [];
12382
12382
  process.stdin.on("data", (c) => chunks.push(c));
12383
- process.stdin.on("end", () => resolve7(Buffer.concat(chunks).toString("utf-8")));
12383
+ process.stdin.on("end", () => resolve9(Buffer.concat(chunks).toString("utf-8")));
12384
12384
  process.stdin.on("error", reject);
12385
12385
  });
12386
12386
  }
@@ -12467,9 +12467,9 @@ async function fetchOrgRules(gatewayUrl, apiKey) {
12467
12467
  function applyLiteralMatchNegative(rules, file) {
12468
12468
  if (!file.patch) return [];
12469
12469
  const findings = [];
12470
- const lines = file.patch.split("\n");
12470
+ const lines2 = file.patch.split("\n");
12471
12471
  let currentNewLine = 0;
12472
- for (const line of lines) {
12472
+ for (const line of lines2) {
12473
12473
  if (line.startsWith("@@")) {
12474
12474
  const m = line.match(/\+(\d+)(?:,\d+)?/);
12475
12475
  if (m) currentNewLine = parseInt(m[1], 10);
@@ -12597,12 +12597,12 @@ async function fetchScanContext(gatewayUrl, apiKey, repo, prNumber, sha) {
12597
12597
  }
12598
12598
  function getFileDiffWithLines(file) {
12599
12599
  if (!file.patch) return { hunks: "", newFileLineMap: /* @__PURE__ */ new Map() };
12600
- const lines = file.patch.split("\n");
12600
+ const lines2 = file.patch.split("\n");
12601
12601
  const annotated = [];
12602
12602
  const lineMap = /* @__PURE__ */ new Map();
12603
12603
  let currentNewLine = 0;
12604
12604
  let patchIndex = 0;
12605
- for (const line of lines) {
12605
+ for (const line of lines2) {
12606
12606
  patchIndex++;
12607
12607
  if (line.startsWith("@@")) {
12608
12608
  const match = line.match(/\+(\d+)(?:,\d+)?/);
@@ -12634,7 +12634,7 @@ function spawnClaudeJudge(file, claudeToken, promptHeader) {
12634
12634
  Diff:
12635
12635
  ${hunks}`;
12636
12636
  const fullPrompt = promptHeader + userMessage;
12637
- return new Promise((resolve7) => {
12637
+ return new Promise((resolve9) => {
12638
12638
  const t0 = Date.now();
12639
12639
  const proc = spawn6(
12640
12640
  "claude",
@@ -12662,7 +12662,7 @@ ${hunks}`;
12662
12662
  const latencyMs = Date.now() - t0;
12663
12663
  if (code !== 0) {
12664
12664
  console.warn(` claude exited ${code}: ${(stderr || stdout).slice(0, 500)}`);
12665
- resolve7({ findings: [], latencyMs });
12665
+ resolve9({ findings: [], latencyMs });
12666
12666
  return;
12667
12667
  }
12668
12668
  try {
@@ -12681,10 +12681,10 @@ ${hunks}`;
12681
12681
  description: f.description,
12682
12682
  fix: f.fix
12683
12683
  }));
12684
- resolve7({ findings, latencyMs });
12684
+ resolve9({ findings, latencyMs });
12685
12685
  } catch (parseErr) {
12686
12686
  console.warn(` failed to parse claude response: ${stdout.slice(0, 300)}`);
12687
- resolve7({ findings: [], latencyMs });
12687
+ resolve9({ findings: [], latencyMs });
12688
12688
  }
12689
12689
  });
12690
12690
  });
@@ -12733,7 +12733,7 @@ ${JSON.stringify(findings, null, 2)}
12733
12733
  `;
12734
12734
  }
12735
12735
  function spawnOpusConsolidator(findings, claudeToken) {
12736
- return new Promise((resolve7) => {
12736
+ return new Promise((resolve9) => {
12737
12737
  const prompt = buildConsolidationPrompt(findings);
12738
12738
  const proc = spawn6(
12739
12739
  "claude",
@@ -12760,7 +12760,7 @@ function spawnOpusConsolidator(findings, claudeToken) {
12760
12760
  proc.on("close", (code) => {
12761
12761
  if (code !== 0) {
12762
12762
  console.warn(` opus consolidation exited ${code}: ${(stderr || stdout).slice(0, 300)}`);
12763
- resolve7(fallbackReview(findings));
12763
+ resolve9(fallbackReview(findings));
12764
12764
  return;
12765
12765
  }
12766
12766
  try {
@@ -12781,10 +12781,10 @@ function spawnOpusConsolidator(findings, claudeToken) {
12781
12781
  const order = ["low", "medium", "high", "critical"];
12782
12782
  return order.indexOf(f.severity) > order.indexOf(max) ? f.severity : max;
12783
12783
  }, "low");
12784
- resolve7({ summary: review.summary || "", comments, severity: maxSeverity });
12784
+ resolve9({ summary: review.summary || "", comments, severity: maxSeverity });
12785
12785
  } catch {
12786
12786
  console.warn(` failed to parse opus response, using fallback`);
12787
- resolve7(fallbackReview(findings));
12787
+ resolve9(fallbackReview(findings));
12788
12788
  }
12789
12789
  });
12790
12790
  });
@@ -12792,15 +12792,15 @@ function spawnOpusConsolidator(findings, claudeToken) {
12792
12792
  function fallbackReview(findings) {
12793
12793
  const grouped = /* @__PURE__ */ new Map();
12794
12794
  for (const f of findings) {
12795
- const key = `${f.file}::${f.category}`;
12796
- if (!grouped.has(key)) grouped.set(key, []);
12797
- grouped.get(key).push(f);
12795
+ const key2 = `${f.file}::${f.category}`;
12796
+ if (!grouped.has(key2)) grouped.set(key2, []);
12797
+ grouped.get(key2).push(f);
12798
12798
  }
12799
12799
  const comments = [];
12800
12800
  for (const [, group] of grouped) {
12801
12801
  const first = group[0];
12802
- const lines = group.map((f) => f.line);
12803
- const linesStr = lines.length > 1 ? `Lines ${lines.join(", ")}` : `Line ${lines[0]}`;
12802
+ const lines2 = group.map((f) => f.line);
12803
+ const linesStr = lines2.length > 1 ? `Lines ${lines2.join(", ")}` : `Line ${lines2[0]}`;
12804
12804
  const severityEmoji = first.severity === "critical" ? "\u{1F534}" : first.severity === "high" ? "\u{1F7E0}" : first.severity === "medium" ? "\u{1F7E1}" : "\u{1F535}";
12805
12805
  comments.push({
12806
12806
  path: first.file,
@@ -13254,10 +13254,10 @@ function stopTask(channel = CHANNEL_PRIMARY) {
13254
13254
  t = findTask(channel);
13255
13255
  }
13256
13256
  }
13257
- function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
13257
+ function tailLogs(lines2 = 80, channel = CHANNEL_PRIMARY) {
13258
13258
  const t = findTask(channel);
13259
13259
  if (!t) return `(no ${channel.taskLabel} task)`;
13260
- const r = spawnSync9("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
13260
+ const r = spawnSync9("pueue", ["log", "--lines", String(lines2), String(t.id)], { encoding: "utf-8" });
13261
13261
  return r.stdout || r.stderr || "(no output)";
13262
13262
  }
13263
13263
  function ensureRunning(opts = {}) {
@@ -13267,14 +13267,14 @@ function ensureRunning(opts = {}) {
13267
13267
  return startTask(opts);
13268
13268
  }
13269
13269
  function probePort(host, port, timeoutMs = 500) {
13270
- return new Promise((resolve7) => {
13270
+ return new Promise((resolve9) => {
13271
13271
  const sock = connect2(port, host);
13272
13272
  const done = (ok) => {
13273
13273
  try {
13274
13274
  sock.destroy();
13275
13275
  } catch {
13276
13276
  }
13277
- resolve7(ok);
13277
+ resolve9(ok);
13278
13278
  };
13279
13279
  sock.once("connect", () => done(true));
13280
13280
  sock.once("error", () => done(false));
@@ -13824,7 +13824,7 @@ function cmdLogs(rest) {
13824
13824
  if (!raw) console.log(" " + colorize("(use --raw / -r to see full payloads, --live / -f to follow)", 90));
13825
13825
  return;
13826
13826
  }
13827
- return new Promise((resolve7) => {
13827
+ return new Promise((resolve9) => {
13828
13828
  console.log(" " + colorize("\u2014 following new turns (Ctrl-C to exit) \u2014", 90));
13829
13829
  const stop = followTurns((t) => {
13830
13830
  console.log(" " + formatTurn(t, raw));
@@ -13832,7 +13832,7 @@ function cmdLogs(rest) {
13832
13832
  const onSigint = () => {
13833
13833
  stop();
13834
13834
  process.removeListener("SIGINT", onSigint);
13835
- resolve7();
13835
+ resolve9();
13836
13836
  };
13837
13837
  process.on("SIGINT", onSigint);
13838
13838
  });
@@ -14050,7 +14050,7 @@ function extractToolResultText(content, e) {
14050
14050
  function parseSession(file, seenStableIds) {
14051
14051
  const { filePath, sessionId, parentSessionId } = file;
14052
14052
  const transcript = readFileSync27(filePath, "utf-8");
14053
- const lines = transcript.split("\n").filter(Boolean);
14053
+ const lines2 = transcript.split("\n").filter(Boolean);
14054
14054
  const transcriptUsage = parseClaudeTranscriptUsage(
14055
14055
  transcript,
14056
14056
  statSync4(filePath).mtime.toISOString().slice(0, 10),
@@ -14062,10 +14062,10 @@ function parseSession(file, seenStableIds) {
14062
14062
  const messages = [];
14063
14063
  const actions = [];
14064
14064
  let step = 0;
14065
- for (let i = 0; i < lines.length; i++) {
14065
+ for (let i = 0; i < lines2.length; i++) {
14066
14066
  let e;
14067
14067
  try {
14068
- e = JSON.parse(lines[i]);
14068
+ e = JSON.parse(lines2[i]);
14069
14069
  } catch {
14070
14070
  continue;
14071
14071
  }
@@ -14121,9 +14121,9 @@ function parseSession(file, seenStableIds) {
14121
14121
  }
14122
14122
  function ask2(q) {
14123
14123
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
14124
- return new Promise((resolve7) => rl.question(q, (a) => {
14124
+ return new Promise((resolve9) => rl.question(q, (a) => {
14125
14125
  rl.close();
14126
- resolve7(/^y(es)?$/i.test(a.trim()));
14126
+ resolve9(/^y(es)?$/i.test(a.trim()));
14127
14127
  }));
14128
14128
  }
14129
14129
  async function importCommand() {
@@ -14261,8 +14261,8 @@ function computeDigest(canonical2) {
14261
14261
  }
14262
14262
  function verifySignature(digest, signatureB64, publicKeyPem) {
14263
14263
  try {
14264
- const key = crypto.createPublicKey(publicKeyPem);
14265
- return crypto.verify(null, Buffer.from(digest, "utf8"), key, Buffer.from(signatureB64, "base64"));
14264
+ const key2 = crypto.createPublicKey(publicKeyPem);
14265
+ return crypto.verify(null, Buffer.from(digest, "utf8"), key2, Buffer.from(signatureB64, "base64"));
14266
14266
  } catch {
14267
14267
  return false;
14268
14268
  }
@@ -14650,14 +14650,23 @@ function buildSpawnAgent(opts) {
14650
14650
  // and its own history scrolls — without it only the last screen is
14651
14651
  // reachable once the session is nested inside the layout.
14652
14652
  ["tmux", "set-option", "-t", session, "mouse", "on"],
14653
+ ...buildClipboardBindings(session),
14653
14654
  ["tmux", "set-option", "-t", session, "-q", "@synkro_harness", opts.harness],
14654
14655
  ["tmux", "set-option", "-t", session, "-q", "@synkro_space", opts.space],
14655
14656
  ["tmux", "set-option", "-t", session, "-q", "@synkro_backend", opts.backend],
14657
+ ["tmux", "set-option", "-t", session, "-q", "@synkro_mode", opts.mode || "native"],
14656
14658
  // Keep the pane visible after exit so the sidebar can render 'done'
14657
14659
  // instead of the agent silently vanishing.
14658
14660
  ["tmux", "set-option", "-t", session, "remain-on-exit", "on"]
14659
14661
  ];
14660
14662
  }
14663
+ function buildClipboardBindings(session) {
14664
+ return [
14665
+ ["tmux", "set-option", "-t", session, "set-clipboard", "on"],
14666
+ ["tmux", "bind-key", "-T", "copy-mode", "MouseDragEnd1Pane", "send-keys", "-X", "copy-pipe-and-cancel", SYSTEM_CLIPBOARD],
14667
+ ["tmux", "bind-key", "-T", "copy-mode-vi", "MouseDragEnd1Pane", "send-keys", "-X", "copy-pipe-and-cancel", SYSTEM_CLIPBOARD]
14668
+ ];
14669
+ }
14661
14670
  function buildListAgents() {
14662
14671
  return ["tmux", "list-sessions", "-F", ["#{session_name}", "#{?pane_dead,dead,alive}", "#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}", "#{@synkro_pueue}", "#{pane_current_path}"].join(FIELD_SEP)];
14663
14672
  }
@@ -14667,7 +14676,7 @@ function buildAgentSnapshot() {
14667
14676
  return ["sh", "-c", script];
14668
14677
  }
14669
14678
  function parseAgentSnapshot(output) {
14670
- const lines = String(output || "").split("\n");
14679
+ const lines2 = String(output || "").split("\n");
14671
14680
  const captures = /* @__PURE__ */ new Map();
14672
14681
  const listLines = [];
14673
14682
  let current = null;
@@ -14675,7 +14684,7 @@ function parseAgentSnapshot(output) {
14675
14684
  const flush2 = () => {
14676
14685
  if (current) captures.set(current, chunk.join("\n"));
14677
14686
  };
14678
- for (const line of lines) {
14687
+ for (const line of lines2) {
14679
14688
  if (line.startsWith("===")) {
14680
14689
  flush2();
14681
14690
  current = line.slice(3).trim();
@@ -14701,6 +14710,9 @@ function buildSendText(session, text) {
14701
14710
  function buildEnableMouse(session) {
14702
14711
  return ["tmux", "set-option", "-t", session, "mouse", "on"];
14703
14712
  }
14713
+ function buildKeepPaneAfterExit(pane) {
14714
+ return ["tmux", "set-option", "-p", "-t", pane, "remain-on-exit", "on"];
14715
+ }
14704
14716
  function buildInterrupt(session) {
14705
14717
  return ["tmux", "send-keys", "-t", session, "Escape"];
14706
14718
  }
@@ -14767,7 +14779,7 @@ function parseWorktrees(porcelain) {
14767
14779
  }
14768
14780
  return rows;
14769
14781
  }
14770
- var execFileAsync, AGENT_PREFIX, UI_SESSION, CONTAINER_USER, FIELD_SEP;
14782
+ var execFileAsync, AGENT_PREFIX, UI_SESSION, CONTAINER_USER, SYSTEM_CLIPBOARD, FIELD_SEP;
14771
14783
  var init_tmux = __esm({
14772
14784
  "cli/ui/tmux.ts"() {
14773
14785
  "use strict";
@@ -14775,6 +14787,7 @@ var init_tmux = __esm({
14775
14787
  AGENT_PREFIX = "synkro-agent-";
14776
14788
  UI_SESSION = "synkro-ui";
14777
14789
  CONTAINER_USER = "synkro";
14790
+ SYSTEM_CLIPBOARD = "if command -v pbcopy >/dev/null 2>&1; then pbcopy; elif command -v wl-copy >/dev/null 2>&1; then wl-copy; elif command -v xclip >/dev/null 2>&1; then xclip -selection clipboard; else cat >/dev/null; fi";
14778
14791
  FIELD_SEP = "|";
14779
14792
  }
14780
14793
  });
@@ -14907,6 +14920,10 @@ function visibleAgents(all, space, filter, grouped) {
14907
14920
  if (grouped || filter === "all" || !space) return all;
14908
14921
  return all.filter((agent) => agent.repo === space.path);
14909
14922
  }
14923
+ function nextLiveAgent(all, preferredRepo = "") {
14924
+ const live = all.filter((agent) => agent.status !== "offline" && agent.status !== "done");
14925
+ return live.find((agent) => Boolean(preferredRepo) && agent.repo === preferredRepo) || live[0];
14926
+ }
14910
14927
  async function discoverAgents(runner, backend, memory) {
14911
14928
  const snapshot = await run(runner, buildAgentSnapshot());
14912
14929
  const { list, captures } = parseAgentSnapshot(snapshot.ok ? snapshot.stdout : "");
@@ -14946,12 +14963,12 @@ async function discoverAgents(runner, backend, memory) {
14946
14963
  }
14947
14964
  function offlineAgents(live, records) {
14948
14965
  const alive = new Set(live.map((agent) => agent.session));
14949
- return records.filter((record) => !alive.has(record.session)).map((record) => ({
14950
- name: record.name,
14951
- session: record.session,
14952
- harness: record.harness,
14953
- space: record.space,
14954
- backend: record.backend,
14966
+ return records.filter((record2) => !alive.has(record2.session)).map((record2) => ({
14967
+ name: record2.name,
14968
+ session: record2.session,
14969
+ harness: record2.harness,
14970
+ space: record2.space,
14971
+ backend: record2.backend,
14955
14972
  status: "offline"
14956
14973
  }));
14957
14974
  }
@@ -14988,8 +15005,8 @@ function saveRecords(records) {
14988
15005
  } catch {
14989
15006
  }
14990
15007
  }
14991
- function recordSession(record) {
14992
- saveRecords([...loadRecords().filter((row2) => row2.session !== record.session), record]);
15008
+ function recordSession(record2) {
15009
+ saveRecords([...loadRecords().filter((row2) => row2.session !== record2.session), record2]);
14993
15010
  }
14994
15011
  function forgetSession(session) {
14995
15012
  saveRecords(loadRecords().filter((row2) => row2.session !== session));
@@ -15024,11 +15041,11 @@ function lastAgentFor(space) {
15024
15041
  return loadLastAgents()[String(space || "").replace(/\/+$/, "")] || "";
15025
15042
  }
15026
15043
  function rememberLastAgent(space, session) {
15027
- const key = String(space || "").replace(/\/+$/, "");
15028
- if (!key || !session) return;
15044
+ const key2 = String(space || "").replace(/\/+$/, "");
15045
+ if (!key2 || !session) return;
15029
15046
  try {
15030
15047
  mkdirSync21(dirname10(LAST_AGENT_FILE), { recursive: true });
15031
- writeFileSync24(LAST_AGENT_FILE, JSON.stringify({ ...loadLastAgents(), [key]: session }, null, 2));
15048
+ writeFileSync24(LAST_AGENT_FILE, JSON.stringify({ ...loadLastAgents(), [key2]: session }, null, 2));
15032
15049
  } catch {
15033
15050
  }
15034
15051
  }
@@ -15067,9 +15084,17 @@ var init_manifest = __esm({
15067
15084
  });
15068
15085
 
15069
15086
  // cli/ui/launch.ts
15070
- import { mkdirSync as mkdirSync22, writeFileSync as writeFileSync25 } from "fs";
15087
+ import { mkdirSync as mkdirSync22, statSync as statSync5, writeFileSync as writeFileSync25 } from "fs";
15071
15088
  import { homedir as homedir34 } from "os";
15072
15089
  import { join as join34 } from "path";
15090
+ function buildStamp(bootPath) {
15091
+ try {
15092
+ const stat = statSync5(bootPath);
15093
+ return String(stat.size) + ":" + String(Math.floor(stat.mtimeMs));
15094
+ } catch {
15095
+ return bootPath;
15096
+ }
15097
+ }
15073
15098
  function sidebarColumns(totalColumns) {
15074
15099
  return String(Math.max(20, Math.min(34, Math.round(totalColumns * 0.24))));
15075
15100
  }
@@ -15141,6 +15166,7 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
15141
15166
  const tabPopup = "display-popup -E -w 60 -h 18 -S fg=colour111 " + shellQuote3(tabLauncher);
15142
15167
  const dispatch = 'if-shell -F "#{==:#{mouse_status_range},tabmenu}" "' + tabPopup + '" "select-window -t="';
15143
15168
  const style = [
15169
+ ["set-option", "-t", UI_SESSION, "-q", "@synkro_build", buildStamp(bootPath)],
15144
15170
  // Mouse: drag the pane border to resize the sidebar, click a pane to
15145
15171
  // focus it, click rows/chips. Without this the fixed split reads as
15146
15172
  // "blocked in".
@@ -15172,6 +15198,39 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
15172
15198
  ["bind-key", "-n", "M-s", "select-pane", "-L"]
15173
15199
  ];
15174
15200
  for (const argv of style) await run(HOST, ["tmux", ...argv]);
15201
+ for (const argv of buildClipboardBindings(UI_SESSION)) await run(HOST, argv);
15202
+ }
15203
+ function parseUiPanes(output) {
15204
+ return String(output || "").split("\n").map((line) => line.split("|")).filter((cols) => cols.length >= 4).map(([window, pane, width, ...command]) => ({
15205
+ window,
15206
+ pane,
15207
+ width: Number(width) || 0,
15208
+ command: command.join("|")
15209
+ })).filter((row2) => Boolean(row2.window && row2.pane));
15210
+ }
15211
+ async function refreshUiShell(bootPath, repoCwd) {
15212
+ const expected = buildStamp(bootPath);
15213
+ const current = await run(HOST, ["tmux", "show-options", "-t", UI_SESSION, "-v", "@synkro_build"]);
15214
+ if (current.ok && current.stdout.trim() === expected) return;
15215
+ const listed = await run(HOST, [
15216
+ "tmux",
15217
+ "list-panes",
15218
+ "-s",
15219
+ "-t",
15220
+ UI_SESSION,
15221
+ "-F",
15222
+ "#{window_id}|#{pane_id}|#{pane_width}|#{pane_start_command}"
15223
+ ]);
15224
+ if (!listed.ok) return;
15225
+ const panes = parseUiPanes(listed.stdout);
15226
+ const sidebars = panes.filter((row2) => row2.command.includes("--sidebar"));
15227
+ if (sidebars.length === 0) return;
15228
+ await styleOuterSession(bootPath, repoCwd, sidebars[0].width || 30);
15229
+ for (const sidebar of sidebars) {
15230
+ const center = panes.find((row2) => row2.window === sidebar.window && row2.pane !== sidebar.pane);
15231
+ if (!center) continue;
15232
+ await run(HOST, ["tmux", "respawn-pane", "-k", "-t", sidebar.pane, sidebarCommand(bootPath, center.pane, repoCwd)]);
15233
+ }
15175
15234
  }
15176
15235
  async function buildTab(bootPath, repoCwd, spec) {
15177
15236
  await run(HOST, ["tmux", "set-option", "-g", "history-limit", "50000"]);
@@ -15214,6 +15273,7 @@ async function buildTab(bootPath, repoCwd, spec) {
15214
15273
  const sidebarPane = split.stdout.trim();
15215
15274
  const panes = await run(HOST, ["tmux", "list-panes", "-t", windowTarget, "-F", "#{pane_id}"]);
15216
15275
  const centerPane = panes.stdout.split("\n").map((line) => line.trim()).filter(Boolean).find((id) => id !== sidebarPane) || "";
15276
+ await run(HOST, buildKeepPaneAfterExit(centerPane));
15217
15277
  if (spec.agentSession) {
15218
15278
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_agent", spec.agentSession]);
15219
15279
  }
@@ -15264,6 +15324,8 @@ async function launchUi(bootPath, repoCwd) {
15264
15324
  rememberSpace(repoCwd);
15265
15325
  if (!await uiSessionExists()) {
15266
15326
  await buildTab(bootPath, repoCwd, { cwd: repoCwd, center: makeTerminalCommand(bootPath), focus: "sidebar" });
15327
+ } else {
15328
+ await refreshUiShell(bootPath, repoCwd);
15267
15329
  }
15268
15330
  await pruneCollapsedClients();
15269
15331
  return runInherit(process.env.TMUX ? ["tmux", "switch-client", "-t", UI_SESSION] : ["tmux", "attach-session", "-t", UI_SESSION]);
@@ -15320,10 +15382,10 @@ function row(selected, width, content) {
15320
15382
  return STYLE.select + body.split(STYLE.reset).join(STYLE.reset + STYLE.select) + STYLE.reset;
15321
15383
  }
15322
15384
  function stripForPad(text, width) {
15323
- let visible = 0;
15385
+ let visible2 = 0;
15324
15386
  let out = "";
15325
15387
  let index = 0;
15326
- while (index < text.length && visible < width) {
15388
+ while (index < text.length && visible2 < width) {
15327
15389
  if (text.startsWith(ESC, index)) {
15328
15390
  const end = text.indexOf("m", index);
15329
15391
  if (end === -1) break;
@@ -15332,13 +15394,13 @@ function stripForPad(text, width) {
15332
15394
  } else {
15333
15395
  out += text[index];
15334
15396
  index += 1;
15335
- visible += 1;
15397
+ visible2 += 1;
15336
15398
  }
15337
15399
  }
15338
- return out + " ".repeat(Math.max(0, width - visible));
15400
+ return out + " ".repeat(Math.max(0, width - visible2));
15339
15401
  }
15340
15402
  function visibleLength(text) {
15341
- let visible = 0;
15403
+ let visible2 = 0;
15342
15404
  let index = 0;
15343
15405
  while (index < text.length) {
15344
15406
  if (text.startsWith(ESC, index)) {
@@ -15346,11 +15408,11 @@ function visibleLength(text) {
15346
15408
  if (end === -1) break;
15347
15409
  index = end + 1;
15348
15410
  } else {
15349
- visible += 1;
15411
+ visible2 += 1;
15350
15412
  index += 1;
15351
15413
  }
15352
15414
  }
15353
- return visible;
15415
+ return visible2;
15354
15416
  }
15355
15417
  function splitRow(width, left, right) {
15356
15418
  const gap = Math.max(1, width - 2 - visibleLength(left) - visibleLength(right));
@@ -15362,10 +15424,10 @@ function windowAround(count, selected, capacity) {
15362
15424
  return { start, end: start + capacity };
15363
15425
  }
15364
15426
  function renderCollapsed(state, width, height) {
15365
- const lines = [];
15427
+ const lines2 = [];
15366
15428
  const targets = [];
15367
15429
  const push2 = (text, target = null) => {
15368
- lines.push(stripForPad(" " + text, width));
15430
+ lines2.push(stripForPad(" " + text, width));
15369
15431
  targets.push(target);
15370
15432
  };
15371
15433
  push2("");
@@ -15382,16 +15444,16 @@ function renderCollapsed(state, width, height) {
15382
15444
  });
15383
15445
  push2("");
15384
15446
  push2(STYLE.dim + "+" + STYLE.reset, { kind: "new" });
15385
- while (lines.length < height - 1) push2("");
15447
+ while (lines2.length < height - 1) push2("");
15386
15448
  push2(STYLE.dim + "\u203A\u203A" + STYLE.reset, { kind: "collapse" });
15387
- return { lines: lines.slice(0, height), targets: targets.slice(0, height) };
15449
+ return { lines: lines2.slice(0, height), targets: targets.slice(0, height) };
15388
15450
  }
15389
15451
  function renderLayout(state, width = 30, height = 40) {
15390
15452
  if (state.collapsed) return renderCollapsed(state, width, height);
15391
- const lines = [];
15453
+ const lines2 = [];
15392
15454
  const targets = [];
15393
15455
  const push2 = (line, target = null) => {
15394
- lines.push(line);
15456
+ lines2.push(line);
15395
15457
  targets.push(target);
15396
15458
  };
15397
15459
  const chrome = 8;
@@ -15413,7 +15475,7 @@ function renderLayout(state, width = 30, height = 40) {
15413
15475
  push2(row(selected, width, " " + STYLE.branch + clip(space.branch, width - 6 - (space.track ? space.track.length + 1 : 0)) + STYLE.reset + drift), target);
15414
15476
  });
15415
15477
  if (state.spaces.length === 0) push2(row(false, width, STYLE.dim + "no spaces found" + STYLE.reset));
15416
- while (lines.length < 3 + spacesCapacity * 2) push2(pad("", width));
15478
+ while (lines2.length < 3 + spacesCapacity * 2) push2(pad("", width));
15417
15479
  push2("");
15418
15480
  push2(splitRow(width, "new", "menu"), { kind: "new" });
15419
15481
  push2(STYLE.dim + "\u2500".repeat(Math.max(0, width)) + STYLE.reset);
@@ -15458,13 +15520,13 @@ function renderLayout(state, width = 30, height = 40) {
15458
15520
  push2("");
15459
15521
  push2(row(false, width, STYLE.blocked + "\u26D4 needs consent" + STYLE.reset));
15460
15522
  for (const action of actionsForAsk(selectedAgent.ask)) {
15461
- const key = action === "track" ? "g" : action === "skip" ? "s" : "y";
15462
- push2(row(false, width, STYLE.dim + " " + key + " \u2014 " + action + STYLE.reset));
15523
+ const key2 = action === "track" ? "g" : action === "skip" ? "s" : "y";
15524
+ push2(row(false, width, STYLE.dim + " " + key2 + " \u2014 " + action + STYLE.reset));
15463
15525
  }
15464
15526
  }
15465
- while (lines.length < height - 1) push2(pad("", width));
15527
+ while (lines2.length < height - 1) push2(pad("", width));
15466
15528
  push2(stripForPad(pad("", width - 3) + STYLE.dim + "\u2039\u2039 " + STYLE.reset, width), { kind: "collapse" });
15467
- return { lines: lines.slice(0, height), targets: targets.slice(0, height) };
15529
+ return { lines: lines2.slice(0, height), targets: targets.slice(0, height) };
15468
15530
  }
15469
15531
  function nextSelection(state, spaces, agents, delta) {
15470
15532
  const flat = state.section === "spaces" ? state.spaceIndex : spaces + state.agentIndex;
@@ -15593,6 +15655,9 @@ async function detectContainerBackend() {
15593
15655
  }
15594
15656
  return { runner: { kind: "host" }, backend: "host", note: "runtime: host (container unavailable)" };
15595
15657
  }
15658
+ function harnessCommand(request) {
15659
+ return request.resume ? resumeCommand(request.harness) : HARNESS_COMMANDS[request.harness] || "claude";
15660
+ }
15596
15661
  async function provisionContainerWorkspace(runner, slug) {
15597
15662
  const dir = CONTAINER_WORK + "/ui-" + slug;
15598
15663
  await run(runner, [
@@ -15610,8 +15675,8 @@ async function spawnAgent(info, request) {
15610
15675
  if (request.backend === "container") {
15611
15676
  cwd = request.cwd.startsWith(CONTAINER_WORK) ? request.cwd : await provisionContainerWorkspace(runner, slug);
15612
15677
  }
15613
- const command = request.resume ? resumeCommand(request.harness) : HARNESS_COMMANDS[request.harness] || "claude";
15614
- for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend })) {
15678
+ const command = request.command || harnessCommand(request);
15679
+ for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend, mode: request.mode })) {
15615
15680
  const result = await run(runner, argv);
15616
15681
  if (!result.ok && argv[1] === "new-session") {
15617
15682
  return { ok: false, session, error: result.stderr.trim() || "tmux new-session failed" };
@@ -15625,7 +15690,8 @@ async function spawnAgent(info, request) {
15625
15690
  harness: request.harness,
15626
15691
  space: cwd,
15627
15692
  spaceName: request.spaceName,
15628
- backend: request.backend
15693
+ backend: request.backend,
15694
+ mode: request.mode || "native"
15629
15695
  });
15630
15696
  return { ok: true, session };
15631
15697
  }
@@ -15901,6 +15967,24 @@ async function runSidebar() {
15901
15967
  const agent = state.agents[state.agentIndex];
15902
15968
  if (agent) await showAgent(agent);
15903
15969
  }
15970
+ async function showWorkspaceTerminal(cwd) {
15971
+ if (!centerPane || !cwd) return;
15972
+ const command = makeTerminalCommand(bootPath);
15973
+ const argv = ["tmux", "respawn-pane", "-k", "-t", centerPane, "-c", cwd];
15974
+ if (command) argv.push(command);
15975
+ const respawned = await run(host, argv);
15976
+ if (!respawned.ok) {
15977
+ state.message = "could not restore workspace terminal";
15978
+ return;
15979
+ }
15980
+ const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
15981
+ await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-u", "@synkro_agent"]);
15982
+ await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_cwd", cwd]);
15983
+ await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_kind", "terminal"]);
15984
+ await run(host, ["tmux", "rename-window", "-t", centerPane, tabTitle("terminal", spaceName)]);
15985
+ state.viewing = "";
15986
+ await snapshotTabs();
15987
+ }
15904
15988
  async function showAgent(agent) {
15905
15989
  if (!centerPane) return;
15906
15990
  if (agent.status === "offline") {
@@ -15919,6 +16003,9 @@ async function runSidebar() {
15919
16003
  return;
15920
16004
  }
15921
16005
  await run(runnerFor(agent.backend), buildEnableMouse(agent.session));
16006
+ for (const argv of buildClipboardBindings(agent.session)) {
16007
+ await run(runnerFor(agent.backend), argv);
16008
+ }
15922
16009
  const command = buildCenterAttachCommand(runnerFor(agent.backend), agent.session);
15923
16010
  await run(host, ["tmux", "respawn-pane", "-k", "-t", centerPane, command]);
15924
16011
  const where = agent.space ? agent.space.split("/").filter(Boolean).pop() || "" : "";
@@ -15931,7 +16018,15 @@ async function runSidebar() {
15931
16018
  async function confirmKillAgent() {
15932
16019
  const agent = state.agents[state.agentIndex];
15933
16020
  if (!agent) return;
16021
+ const wasViewing = state.viewing === agent.session;
15934
16022
  await openDialog("kill", [agent.backend, agent.session]);
16023
+ await refresh();
16024
+ if (allAgents.some((candidate) => candidate.session === agent.session && candidate.status !== "offline")) return;
16025
+ state.message = "closed " + agent.name;
16026
+ if (!wasViewing) return;
16027
+ await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
16028
+ const successor = nextLiveAgent(allAgents, agent.repo || "");
16029
+ if (successor) await showAgent(successor);
15935
16030
  }
15936
16031
  async function consent(action) {
15937
16032
  const agent = state.agents[state.agentIndex];
@@ -15954,6 +16049,7 @@ async function runSidebar() {
15954
16049
  const remembered = space ? lastAgentFor(space.path) : "";
15955
16050
  const agent = allAgents.find((row2) => row2.session === remembered && row2.status !== "offline");
15956
16051
  if (agent) await showAgent(agent);
16052
+ else if (space) await showWorkspaceTerminal(space.path);
15957
16053
  } else if (target.kind === "agent") {
15958
16054
  state.section = "agents";
15959
16055
  state.agentIndex = target.index;
@@ -15995,45 +16091,45 @@ async function runSidebar() {
15995
16091
  }
15996
16092
  await activate(target);
15997
16093
  }
15998
- async function handleKey(key) {
15999
- if (key === " ") state.section = state.section === "spaces" ? "agents" : "spaces";
16000
- else if (key === "j" || key === CSI + "B") moveSelection(1);
16001
- else if (key === "k" || key === CSI + "A") moveSelection(-1);
16002
- else if (key === "\r") {
16094
+ async function handleKey(key2) {
16095
+ if (key2 === " ") state.section = state.section === "spaces" ? "agents" : "spaces";
16096
+ else if (key2 === "j" || key2 === CSI + "B") moveSelection(1);
16097
+ else if (key2 === "k" || key2 === CSI + "A") moveSelection(-1);
16098
+ else if (key2 === "\r") {
16003
16099
  if (state.section === "agents") await attachSelected();
16004
16100
  else await openDialog("new-tab");
16005
- } else if (key === "n" || key === "T") {
16101
+ } else if (key2 === "n" || key2 === "T") {
16006
16102
  worldChanged = true;
16007
16103
  await openDialog("new-tab");
16008
- } else if (key === "m") await mainMenu();
16009
- else if (key === "O") void openDialog("new-workspace");
16010
- else if (key === "C") {
16104
+ } else if (key2 === "m") await mainMenu();
16105
+ else if (key2 === "O") void openDialog("new-workspace");
16106
+ else if (key2 === "C") {
16011
16107
  worldChanged = true;
16012
16108
  closeSelectedWorkspace();
16013
- } else if (key === "r") {
16109
+ } else if (key2 === "r") {
16014
16110
  worldChanged = true;
16015
16111
  await restoreSelected();
16016
- } else if (key === "d") {
16112
+ } else if (key2 === "d") {
16017
16113
  const client = await attachedClient();
16018
16114
  await run(host, client ? ["tmux", "detach-client", "-t", client] : ["tmux", "detach-client"]);
16019
- } else if (key === "K") await keybindsMenu();
16020
- else if (key === "G") {
16115
+ } else if (key2 === "K") await keybindsMenu();
16116
+ else if (key2 === "G") {
16021
16117
  state.grouped = !state.grouped;
16022
16118
  applyFilter();
16023
- } else if (key === "a") {
16119
+ } else if (key2 === "a") {
16024
16120
  state.filter = state.filter === "space" ? "all" : "space";
16025
16121
  applyFilter();
16026
- } else if (key === "<" || key === ">" || key === "," || key === ".") await toggleCollapsed();
16027
- else if (key === "x") {
16122
+ } else if (key2 === "<" || key2 === ">" || key2 === "," || key2 === ".") await toggleCollapsed();
16123
+ else if (key2 === "x") {
16028
16124
  worldChanged = true;
16029
16125
  await confirmKillAgent();
16030
- } else if (key === "i") {
16126
+ } else if (key2 === "i") {
16031
16127
  const agent = state.agents[state.agentIndex];
16032
16128
  if (agent) await run(runnerFor(agent.backend), buildInterrupt(agent.session));
16033
- } else if (key === "g") await consent("track");
16034
- else if (key === "s") await consent("skip");
16035
- else if (key === "y") await consent("stay");
16036
- else if (key === "q" || key === KEY_CTRL_C) {
16129
+ } else if (key2 === "g") await consent("track");
16130
+ else if (key2 === "s") await consent("skip");
16131
+ else if (key2 === "y") await consent("stay");
16132
+ else if (key2 === "q" || key2 === KEY_CTRL_C) {
16037
16133
  await snapshotTabs();
16038
16134
  releaseAwake();
16039
16135
  await run(host, ["tmux", "kill-session", "-t", outerSession]);
@@ -16155,6 +16251,9 @@ var init_repos = __esm({
16155
16251
 
16156
16252
  // cli/ui/tabs.ts
16157
16253
  import { execSync as execSync7 } from "child_process";
16254
+ function embeddedSessionCommand(bootPath, harness, cwd) {
16255
+ return ["node", bootPath, "ui", "--run", harness, cwd].map(shellQuote3).join(" ");
16256
+ }
16158
16257
  function repoRoot() {
16159
16258
  try {
16160
16259
  return execSync7("git rev-parse --show-toplevel", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim() || process.cwd();
@@ -16175,12 +16274,25 @@ async function createTab(bootPath, kind, spacePath) {
16175
16274
  });
16176
16275
  return;
16177
16276
  }
16178
- if (kind === "cursor-synkro") {
16277
+ if (kind === "cursor-synkro" || kind === "codex-synkro") {
16278
+ const harness2 = kind === "codex-synkro" ? "codex" : "cursor";
16279
+ const command = embeddedSessionCommand(bootPath, harness2, spacePath);
16280
+ const spawned2 = await spawnAgent({ runner: HOST2, backend: "host", note: "runtime: host" }, {
16281
+ name: space + "-" + harness2 + "-synkro-" + String(process.pid % 1e4),
16282
+ harness: harness2,
16283
+ spaceName: space,
16284
+ cwd: spacePath,
16285
+ backend: "host",
16286
+ command,
16287
+ mode: "embedded"
16288
+ });
16289
+ if (!spawned2.ok) return;
16179
16290
  await buildTab(bootPath, repo, {
16180
16291
  cwd: spacePath,
16181
- center: ["node", bootPath, "ui", "--run", "cursor", spacePath].map(shellQuote3).join(" "),
16182
- title: tabTitle("cursor", space),
16183
- kind: "cursor-synkro",
16292
+ center: buildCenterAttachCommand(HOST2, spawned2.session),
16293
+ title: tabTitle(harness2, space),
16294
+ kind,
16295
+ agentSession: spawned2.session,
16184
16296
  focus: "center"
16185
16297
  });
16186
16298
  return;
@@ -16195,12 +16307,13 @@ async function createTab(bootPath, kind, spacePath) {
16195
16307
  });
16196
16308
  return;
16197
16309
  }
16310
+ if (!["claude", "codex", "cursor"].includes(kind)) return;
16198
16311
  const info = await detectContainerBackend();
16199
16312
  const spaceName = space;
16200
16313
  const stamp = String(process.pid % 1e4);
16201
- const harness = ["claude", "codex", "cursor"].includes(kind) ? kind : "claude";
16314
+ const harness = kind;
16202
16315
  const reachable = info.backend === "container" && (await run(info.runner, ["test", "-d", spacePath])).ok;
16203
- const backend = reachable ? "container" : "host";
16316
+ const backend = harness === "codex" ? "host" : reachable ? "container" : "host";
16204
16317
  const spawned = await spawnAgent(info, {
16205
16318
  name: spaceName + "-" + harness + "-" + stamp,
16206
16319
  harness,
@@ -16227,25 +16340,26 @@ async function openAgentTab(bootPath, session) {
16227
16340
  "-p",
16228
16341
  "-t",
16229
16342
  session,
16230
- ["#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}"].join("|")
16343
+ ["#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}", "#{@synkro_mode}"].join("|")
16231
16344
  ]);
16232
- const [harness, space, backend] = (meta.stdout.trim() || "||").split("|");
16345
+ const [harness, space, backend, mode] = (meta.stdout.trim() || "|||").split("|");
16233
16346
  const containerHosted = backend === "container";
16234
- const runner = containerHosted ? info.runner : HOST2;
16347
+ const runner = mode === "embedded" ? HOST2 : containerHosted ? info.runner : HOST2;
16348
+ const spaceName = (space || "").split("/").filter(Boolean).pop() || "space";
16235
16349
  const alive = (await run(runner, ["tmux", "has-session", "-t", session])).ok;
16236
16350
  if (!alive) return;
16237
- const spaceName = (space || "").split("/").filter(Boolean).pop() || "space";
16238
16351
  await buildTab(bootPath, repoRoot(), {
16239
16352
  cwd: space || repoRoot(),
16240
16353
  center: buildCenterAttachCommand(runner, session),
16241
16354
  title: tabTitle(harness || "claude", spaceName),
16242
- kind: harness || "claude",
16355
+ kind: mode === "embedded" ? (harness || "cursor") + "-synkro" : harness || "claude",
16243
16356
  agentSession: session,
16244
16357
  focus: "center"
16245
16358
  });
16246
16359
  }
16247
16360
  async function restoreTabs(bootPath, repoCwd) {
16248
16361
  const tabs = loadTabs();
16362
+ const records = loadRecords();
16249
16363
  if (tabs.length === 0) return false;
16250
16364
  const info = await detectContainerBackend();
16251
16365
  const hostSessions = (await run(HOST2, ["tmux", "list-sessions", "-F", "#{session_name}"])).stdout;
@@ -16255,31 +16369,42 @@ async function restoreTabs(bootPath, repoCwd) {
16255
16369
  for (const tab of tabs) {
16256
16370
  const space = tab.cwd.split("/").filter(Boolean).pop() || "space";
16257
16371
  const containerHosted = info.backend === "container" && (await run(info.runner, ["test", "-d", tab.cwd])).ok;
16258
- const harness = ["claude", "codex", "cursor"].includes(tab.kind) ? tab.kind : "";
16259
- if (tab.agentSession && alive.has(tab.agentSession)) {
16260
- await run(containerHosted ? info.runner : HOST2, buildEnableMouse(tab.agentSession));
16372
+ const embedded = tab.kind === "cursor-synkro" || tab.kind === "codex-synkro";
16373
+ const harness = embedded ? tab.kind.replace("-synkro", "") : ["claude", "codex", "cursor"].includes(tab.kind) ? tab.kind : "";
16374
+ const sessionRunner = embedded || harness === "codex" ? HOST2 : containerHosted ? info.runner : HOST2;
16375
+ const sessionAlive = Boolean(tab.agentSession && alive.has(tab.agentSession));
16376
+ const record2 = records.find((row2) => row2.session === tab.agentSession);
16377
+ if (tab.agentSession && sessionAlive) {
16378
+ await run(sessionRunner, buildEnableMouse(tab.agentSession));
16379
+ for (const argv of buildClipboardBindings(tab.agentSession)) {
16380
+ await run(sessionRunner, argv);
16381
+ }
16261
16382
  await buildTab(bootPath, repoCwd, {
16262
16383
  cwd: tab.cwd,
16263
- center: buildCenterAttachCommand(containerHosted ? info.runner : HOST2, tab.agentSession),
16384
+ center: buildCenterAttachCommand(sessionRunner, tab.agentSession),
16264
16385
  title: tab.title,
16265
16386
  kind: tab.kind,
16266
16387
  agentSession: tab.agentSession,
16267
16388
  focus: "center"
16268
16389
  });
16269
16390
  } else if (harness) {
16391
+ const command = embedded ? embeddedSessionCommand(bootPath, harness, tab.cwd) : void 0;
16270
16392
  const spawned = await spawnAgent(info, {
16271
- name: space + "-" + harness + "-" + String(process.pid % 1e4),
16393
+ name: record2?.name || space + "-" + harness + "-" + String(process.pid % 1e4),
16272
16394
  harness,
16273
16395
  spaceName: space,
16274
16396
  cwd: tab.cwd,
16275
- backend: containerHosted ? "container" : "host",
16276
- resume: true
16397
+ backend: embedded || harness === "codex" ? "host" : containerHosted ? "container" : "host",
16398
+ resume: !embedded,
16399
+ command,
16400
+ mode: embedded ? "embedded" : "native"
16277
16401
  });
16402
+ const restoredInContainer = !embedded && harness !== "codex" && containerHosted;
16278
16403
  await buildTab(bootPath, repoCwd, spawned.ok ? {
16279
16404
  cwd: tab.cwd,
16280
- center: buildCenterAttachCommand(containerHosted ? info.runner : HOST2, spawned.session),
16405
+ center: buildCenterAttachCommand(restoredInContainer ? info.runner : HOST2, spawned.session),
16281
16406
  title: tabTitle(harness, space),
16282
- kind: harness,
16407
+ kind: embedded ? tab.kind : harness,
16283
16408
  agentSession: spawned.session,
16284
16409
  focus: "center"
16285
16410
  } : { cwd: tab.cwd, center: makeTerminalCommand(bootPath), title: tabTitle("terminal", space), kind: "terminal", focus: "center" });
@@ -16311,11 +16436,20 @@ var init_tabs = __esm({
16311
16436
  // cli/ui/dialog.ts
16312
16437
  import { existsSync as existsSync37 } from "fs";
16313
16438
  import { homedir as homedir37 } from "os";
16439
+ function providerChoices(harnesses) {
16440
+ return [
16441
+ { value: "terminal", label: TAB_GLYPHS.terminal + " Terminal" },
16442
+ ...harnesses.map((harness) => ({
16443
+ value: harness === "cursor" || harness === "codex" ? harness + "-synkro" : harness,
16444
+ label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
16445
+ }))
16446
+ ];
16447
+ }
16314
16448
  function write(text) {
16315
16449
  process.stdout.write(text);
16316
16450
  }
16317
16451
  function visibleLength2(text) {
16318
- let visible = 0;
16452
+ let visible2 = 0;
16319
16453
  let index = 0;
16320
16454
  while (index < text.length) {
16321
16455
  if (text.startsWith(CSI2, index)) {
@@ -16323,11 +16457,11 @@ function visibleLength2(text) {
16323
16457
  if (end === -1) break;
16324
16458
  index = end + 1;
16325
16459
  } else {
16326
- visible += 1;
16460
+ visible2 += 1;
16327
16461
  index += 1;
16328
16462
  }
16329
16463
  }
16330
- return visible;
16464
+ return visible2;
16331
16465
  }
16332
16466
  function clipPath(text, max) {
16333
16467
  const value = String(text || "");
@@ -16370,34 +16504,34 @@ async function pick(opts) {
16370
16504
  const perItem = opts.choices.some((choice) => choice.detail) ? 2 : 1;
16371
16505
  const visibleItems = Math.max(1, Math.floor(Math.max(2, height - 5) / perItem));
16372
16506
  const firstRow = 4;
16373
- return new Promise((resolve7) => {
16507
+ return new Promise((resolve9) => {
16374
16508
  const view = () => opts.choices.filter((choice) => matches(choice, filter));
16375
16509
  const draw = () => {
16376
16510
  const shown = view();
16377
16511
  if (selected >= shown.length) selected = Math.max(0, shown.length - 1);
16378
16512
  if (selected < top) top = selected;
16379
16513
  if (selected >= top + visibleItems) top = selected - visibleItems + 1;
16380
- const lines = [];
16381
- lines.push(" " + S.title + opts.title + S.reset);
16514
+ const lines2 = [];
16515
+ lines2.push(" " + S.title + opts.title + S.reset);
16382
16516
  const left = filter && !opts.menu ? " " + S.accent + "/ " + S.reset + filter + "\u258C" : " " + S.dim + opts.hint + S.reset;
16383
16517
  const right = opts.menu ? "" : S.dim + String(shown.length) + (shown.length === 1 ? " match" : " matches") + S.reset;
16384
- lines.push(padRow(left, Math.max(0, width - visibleLength2(right) - 1)) + right);
16385
- lines.push("");
16386
- if (shown.length === 0) lines.push(" " + S.dim + (opts.emptyNote || "nothing matches") + S.reset);
16518
+ lines2.push(padRow(left, Math.max(0, width - visibleLength2(right) - 1)) + right);
16519
+ lines2.push("");
16520
+ if (shown.length === 0) lines2.push(" " + S.dim + (opts.emptyNote || "nothing matches") + S.reset);
16387
16521
  shown.slice(top, top + visibleItems).forEach((choice, offset) => {
16388
16522
  const index = top + offset;
16389
16523
  const isSelected = index === selected;
16390
16524
  const marker = isSelected ? S.accent + "\u203A" + S.reset + " " : " ";
16391
16525
  const note = choice.note ? S.dim + choice.note + S.reset : "";
16392
16526
  const head = " " + marker + S.bold + clip2(choice.label, width - 6 - visibleLength2(note)) + S.reset;
16393
- lines.push(selectable(padRow(head, Math.max(0, width - visibleLength2(note) - 1)) + note, width, isSelected));
16527
+ lines2.push(selectable(padRow(head, Math.max(0, width - visibleLength2(note) - 1)) + note, width, isSelected));
16394
16528
  if (perItem === 2) {
16395
- lines.push(selectable(" " + S.muted + clipPath(choice.detail || "", width - 6) + S.reset, width, isSelected));
16529
+ lines2.push(selectable(" " + S.muted + clipPath(choice.detail || "", width - 6) + S.reset, width, isSelected));
16396
16530
  }
16397
16531
  });
16398
- while (lines.length < height - 1) lines.push("");
16399
- lines.push(" " + S.dim + opts.footer + S.reset);
16400
- write(CSI2 + "H" + lines.slice(0, height).map((line) => padRow(line, width)).join("\n"));
16532
+ while (lines2.length < height - 1) lines2.push("");
16533
+ lines2.push(" " + S.dim + opts.footer + S.reset);
16534
+ write(CSI2 + "H" + lines2.slice(0, height).map((line) => padRow(line, width)).join("\n"));
16401
16535
  };
16402
16536
  const rowOfItem = (index) => firstRow + (index - top) * perItem;
16403
16537
  const MOUSE = /\[<(\d+);(\d+);(\d+)([Mm])/g;
@@ -16418,7 +16552,7 @@ async function pick(opts) {
16418
16552
  const hit = shown.findIndex((_, index) => y >= rowOfItem(index) && y < rowOfItem(index) + perItem);
16419
16553
  if (hit >= 0) {
16420
16554
  process.stdin.off("data", onData);
16421
- resolve7(shown[hit].value);
16555
+ resolve9(shown[hit].value);
16422
16556
  return;
16423
16557
  }
16424
16558
  }
@@ -16426,13 +16560,13 @@ async function pick(opts) {
16426
16560
  if (!sawMouse) {
16427
16561
  if (input === KEY_ESC && !input.includes("[<") || input === KEY_CTRL_C2) {
16428
16562
  process.stdin.off("data", onData);
16429
- resolve7(null);
16563
+ resolve9(null);
16430
16564
  return;
16431
16565
  }
16432
16566
  if (input === "\r") {
16433
16567
  if (shown.length === 0) return;
16434
16568
  process.stdin.off("data", onData);
16435
- resolve7(shown[selected].value);
16569
+ resolve9(shown[selected].value);
16436
16570
  return;
16437
16571
  }
16438
16572
  const down = input === CSI2 + "B" || opts.menu && (input === "j" || input === CSI2 + "C");
@@ -16456,9 +16590,9 @@ async function readLine(opts) {
16456
16590
  let value = "";
16457
16591
  let error = "";
16458
16592
  const width = Math.max(20, Number(process.stdout.columns || 80));
16459
- return new Promise((resolve7) => {
16593
+ return new Promise((resolve9) => {
16460
16594
  const draw = () => {
16461
- const lines = [
16595
+ const lines2 = [
16462
16596
  " " + S.title + opts.title + S.reset,
16463
16597
  "",
16464
16598
  " " + S.dim + opts.label + S.reset,
@@ -16469,13 +16603,13 @@ async function readLine(opts) {
16469
16603
  "",
16470
16604
  " " + S.dim + opts.footer + S.reset
16471
16605
  ];
16472
- write(CSI2 + "2J" + CSI2 + "H" + lines.map((line) => padRow(line, width)).join("\n"));
16606
+ write(CSI2 + "2J" + CSI2 + "H" + lines2.map((line) => padRow(line, width)).join("\n"));
16473
16607
  };
16474
16608
  const onData = (chunk) => {
16475
16609
  const input = chunk.toString("utf8");
16476
16610
  if (input === KEY_ESC || input === KEY_CTRL_C2) {
16477
16611
  process.stdin.off("data", onData);
16478
- resolve7(null);
16612
+ resolve9(null);
16479
16613
  return;
16480
16614
  }
16481
16615
  if (input === "\r") {
@@ -16487,7 +16621,7 @@ async function readLine(opts) {
16487
16621
  return;
16488
16622
  }
16489
16623
  process.stdin.off("data", onData);
16490
- resolve7(value.trim());
16624
+ resolve9(value.trim());
16491
16625
  })();
16492
16626
  return;
16493
16627
  }
@@ -16747,16 +16881,7 @@ async function runDialog(kind, repoCwd, argA = "", argB = "") {
16747
16881
  process.exit(0);
16748
16882
  }
16749
16883
  const harnesses = await detectHarnesses();
16750
- const sessions = [
16751
- { value: "terminal", label: TAB_GLYPHS.terminal + " Terminal" },
16752
- ...harnesses.map((harness) => ({
16753
- value: harness,
16754
- label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
16755
- }))
16756
- ];
16757
- if (harnesses.includes("cursor")) {
16758
- sessions.push({ value: "cursor-synkro", label: TAB_GLYPHS.cursor + " Cursor in Synkro UX" });
16759
- }
16884
+ const sessions = providerChoices(harnesses);
16760
16885
  for (; ; ) {
16761
16886
  const session = await pick({
16762
16887
  menu: true,
@@ -16815,21 +16940,386 @@ var init_dialog = __esm({
16815
16940
  }
16816
16941
  });
16817
16942
 
16943
+ // cli/harness/render.ts
16944
+ function spinnerFrame(tick) {
16945
+ return SPINNER[Math.abs(tick) % SPINNER.length];
16946
+ }
16947
+ function terminalText(text) {
16948
+ return String(text).replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
16949
+ }
16950
+ function clip3(text, max) {
16951
+ const value = cleanOutput(text).replace(/\s+/g, " ").trim();
16952
+ if (max <= 1) return value;
16953
+ return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
16954
+ }
16955
+ function cleanOutput(text) {
16956
+ return String(text || "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
16957
+ }
16958
+ function seconds(ms) {
16959
+ const total = Math.max(0, Math.round(ms / 1e3));
16960
+ if (total < 60) return total + "s";
16961
+ return Math.floor(total / 60) + "m" + String(total % 60).padStart(2, "0") + "s";
16962
+ }
16963
+ function blockNextStep(reason) {
16964
+ if (/tracking (?:decision|is required)|task tracking|skip tracking|\bconductor\b/i.test(reason)) {
16965
+ return "Next: reply \u201Ctrack it\u201D to create a task, or \u201Cskip tracking\u201D to continue untracked.";
16966
+ }
16967
+ if (/\bCWE-\d+\b|\bCVE-\d{4}-\d+\b/i.test(reason)) {
16968
+ return "Next: revise the flagged code, then retry. Do not bypass the rule.";
16969
+ }
16970
+ return "Next: resolve the rule above, then retry.";
16971
+ }
16972
+ function statusLine(opts) {
16973
+ const hint = opts.hint ? " \xB7 " + opts.hint : "";
16974
+ const body = opts.text + " (" + seconds(opts.elapsedMs) + hint + ")";
16975
+ const width = Math.max(20, (opts.width || 100) - 4);
16976
+ return S2.think + " " + spinnerFrame(opts.tick) + " " + clip3(body, width) + S2.reset;
16977
+ }
16978
+ function renderEvent(event, width = 100) {
16979
+ const body = Math.max(30, width - 4);
16980
+ switch (event.type) {
16981
+ case "session-start":
16982
+ return [];
16983
+ case "user-message":
16984
+ return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset];
16985
+ // Live state, not transcript. The runner promotes these to the status line.
16986
+ case "thinking":
16987
+ return [];
16988
+ case "assistant-message": {
16989
+ const lines2 = wrap(event.text, body - 2);
16990
+ return [
16991
+ "",
16992
+ ...lines2.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset)
16993
+ ];
16994
+ }
16995
+ case "tool-start": {
16996
+ const label = TOOL_LABEL[event.kind] || TOOL_LABEL.other;
16997
+ return [
16998
+ "",
16999
+ S2.panel + S2.tool + " " + BULLET + " " + label + S2.reset + S2.panel + S2.secondary + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
17000
+ ];
17001
+ }
17002
+ case "tool-end": {
17003
+ if (event.blocked) {
17004
+ return [
17005
+ S2.panel + S2.blocked + " \u29C9 Blocked" + S2.reset + S2.panel + S2.secondary + " " + clip3(terminalText(event.target), body - 21) + S2.reset,
17006
+ ...wrap(terminalText(event.reason), body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset),
17007
+ ...wrap(blockNextStep(event.reason), body - 6).map((line) => S2.secondary + " " + line + S2.reset)
17008
+ ];
17009
+ }
17010
+ const timing = event.durationMs == null ? "" : " \xB7 " + seconds(event.durationMs);
17011
+ const detail = event.ok ? event.output.trim() ? clip3(terminalText(event.output), body - 10) : "done" : "failed" + (event.exitCode === null ? "" : " (exit " + event.exitCode + ")");
17012
+ const tint = event.ok ? S2.ok : S2.blocked;
17013
+ const changes = (event.fileChanges || []).flatMap((change) => wrap(change.path, body - 20).map((line, index) => S2.panel + S2.secondary + (index === 0 ? " " : " ") + line + (index === 0 ? " " + S2.ok + "+" + change.additions + S2.secondary + " " + S2.blocked + "-" + change.deletions : "") + S2.reset));
17014
+ return [
17015
+ S2.panel + tint + " " + ELBOW + " " + S2.reset + S2.panel + S2.secondary + detail + timing + S2.reset,
17016
+ ...changes
17017
+ ];
17018
+ }
17019
+ // A clean finish needs no announcement: the prompt returning IS the signal.
17020
+ case "turn-end":
17021
+ return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
17022
+ case "notice":
17023
+ return [S2.secondary + " " + clip3(event.text, body) + S2.reset];
17024
+ case "plan":
17025
+ return [
17026
+ S2.panel + S2.tool + " Plan" + S2.reset,
17027
+ ...event.steps.map((row2) => S2.panel + (row2.status === "completed" ? S2.ok + " \u2713 " : S2.secondary + " \xB7 ") + clip3(row2.step, body - 4) + S2.reset)
17028
+ ];
17029
+ case "assistant-delta":
17030
+ case "usage":
17031
+ return [];
17032
+ default:
17033
+ return [];
17034
+ }
17035
+ }
17036
+ function wrap(text, width) {
17037
+ const words = cleanOutput(text).replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
17038
+ if (words.length === 0) return [];
17039
+ const lines2 = [];
17040
+ let line = "";
17041
+ for (const word of words) {
17042
+ if (!line) line = word;
17043
+ else if ((line + " " + word).length <= width) line += " " + word;
17044
+ else {
17045
+ lines2.push(line);
17046
+ line = word;
17047
+ }
17048
+ }
17049
+ if (line) lines2.push(line);
17050
+ return lines2;
17051
+ }
17052
+ var ESC2, color, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
17053
+ var init_render2 = __esm({
17054
+ "cli/harness/render.ts"() {
17055
+ "use strict";
17056
+ ESC2 = "\x1B[";
17057
+ color = (hex, plane = 38) => {
17058
+ const [r, g, b] = hex.match(/../g).map((part) => Number.parseInt(part, 16));
17059
+ return ESC2 + plane + ";2;" + r + ";" + g + ";" + b + "m";
17060
+ };
17061
+ S2 = {
17062
+ reset: ESC2 + "0m",
17063
+ dim: ESC2 + "2m",
17064
+ bold: ESC2 + "1m",
17065
+ canvas: color("101416", 48),
17066
+ panel: color("171D1F", 48),
17067
+ composer: color("13191B", 48),
17068
+ text: color("C4C8C4"),
17069
+ secondary: color("7F8A86"),
17070
+ border: color("2A3334"),
17071
+ user: color("87A68E"),
17072
+ agent: color("C4C8C4"),
17073
+ think: color("B29A6A"),
17074
+ tool: color("7F9CA5"),
17075
+ ok: color("789B82"),
17076
+ blocked: color("B87474"),
17077
+ rule: color("B87474")
17078
+ };
17079
+ CLEAR_LINE = "\r" + ESC2 + "2K";
17080
+ BULLET = "\u23FA";
17081
+ ELBOW = "\u23BF";
17082
+ SPINNER = ["\xB7", "\u2722", "\u2733", "\u2217", "\u273B", "\u273D"];
17083
+ TOOL_LABEL = {
17084
+ shell: "Shell",
17085
+ read: "Read",
17086
+ edit: "Edit",
17087
+ write: "Write",
17088
+ delete: "Delete",
17089
+ search: "Search",
17090
+ list: "List",
17091
+ todo: "Todo",
17092
+ other: "Tool"
17093
+ };
17094
+ }
17095
+ });
17096
+
17097
+ // cli/harness/composer.ts
17098
+ import { execFileSync as execFileSync5, spawnSync as spawnSync12 } from "child_process";
17099
+ import { existsSync as existsSync38, mkdtempSync as mkdtempSync2, rmSync as rmSync7, statSync as statSync6, writeFileSync as writeFileSync27 } from "fs";
17100
+ import { homedir as homedir38, tmpdir } from "os";
17101
+ import { basename as basename3, dirname as dirname12, extname, isAbsolute as isAbsolute2, join as join37, resolve as resolve5, sep as sep3 } from "path";
17102
+ import { createInterface as createInterface5 } from "readline";
17103
+ var init_composer = __esm({
17104
+ "cli/harness/composer.ts"() {
17105
+ "use strict";
17106
+ init_render2();
17107
+ }
17108
+ });
17109
+
16818
17110
  // cli/harness/events.ts
17111
+ function fixPollId(raw) {
17112
+ const text = String(raw || "");
17113
+ return FIX_POLL_MARKER.exec(text)?.[1] || LEGACY_FIX_POLL_MARKER.exec(text)?.[1] || "";
17114
+ }
17115
+ function cleanGuardText(raw) {
17116
+ return String(raw || "").replace(FIX_POLL_MARKER, "").replace(/\n*\s*SYNKRO FIX POLL[\s\S]*$/i, "").replace(/\n?\d{4}-\d\d-\d\dT[^\n]*\sERROR\s+codex_core::tools::router:[^\n]*/gi, "").replace(/\s*Checking command\s*$/i, "").trim();
17117
+ }
16819
17118
  function blockReason(raw) {
16820
- const text = String(raw || "").trim();
17119
+ const text = cleanGuardText(raw);
16821
17120
  if (!text) return "blocked by policy";
17121
+ const guardAt = text.lastIndexOf("Guard:");
17122
+ if (guardAt >= 0) return text.slice(guardAt + "Guard:".length).trim();
16822
17123
  const afterTag = text.match(/\[synkro:[^\]]*\]\s*(.+)/is);
16823
17124
  if (afterTag) return afterTag[1].trim();
16824
17125
  const afterHook = text.match(/blocked by a hook:\s*(.+)/is);
16825
17126
  if (afterHook) return afterHook[1].trim();
16826
17127
  return text;
16827
17128
  }
16828
- var BLOCK_MARKER;
17129
+ var BLOCK_MARKER, FIX_POLL_MARKER, LEGACY_FIX_POLL_MARKER;
16829
17130
  var init_events = __esm({
16830
17131
  "cli/harness/events.ts"() {
16831
17132
  "use strict";
16832
17133
  BLOCK_MARKER = /blocked by a hook|\[synkro:/i;
17134
+ FIX_POLL_MARKER = /\[synkro:fix-poll\s+item_id=\\?["']?([A-Za-z0-9_-]+)\\?["']?\]/i;
17135
+ LEGACY_FIX_POLL_MARKER = /SYNKRO FIX POLL\s*\(item_id=([A-Za-z0-9_-]+)\)/i;
17136
+ }
17137
+ });
17138
+
17139
+ // cli/harness/changes.ts
17140
+ import { lstatSync as lstatSync2, readFileSync as readFileSync33, readlinkSync } from "fs";
17141
+ import { resolve as resolve6, relative } from "path";
17142
+ function lines(text) {
17143
+ if (!text) return [];
17144
+ const rows = text.replace(/\r\n/g, "\n").split("\n");
17145
+ if (rows[rows.length - 1] === "") rows.pop();
17146
+ return rows;
17147
+ }
17148
+ function lineChangeCounts(before, after) {
17149
+ const a = lines(before);
17150
+ const b = lines(after);
17151
+ if (a.length === 0) return { additions: b.length, deletions: 0 };
17152
+ if (b.length === 0) return { additions: 0, deletions: a.length };
17153
+ const max = a.length + b.length;
17154
+ let frontier = /* @__PURE__ */ new Map([[1, 0]]);
17155
+ for (let distance = 0; distance <= Math.min(max, MAX_DIFF_DISTANCE); distance++) {
17156
+ const next = /* @__PURE__ */ new Map();
17157
+ for (let diagonal = -distance; diagonal <= distance; diagonal += 2) {
17158
+ const down = frontier.get(diagonal + 1) ?? -1;
17159
+ const right = (frontier.get(diagonal - 1) ?? -1) + 1;
17160
+ let x = diagonal === -distance || diagonal !== distance && right < down ? down : right;
17161
+ if (x < 0) x = 0;
17162
+ let y = x - diagonal;
17163
+ while (x < a.length && y < b.length && a[x] === b[y]) {
17164
+ x++;
17165
+ y++;
17166
+ }
17167
+ next.set(diagonal, x);
17168
+ if (x >= a.length && y >= b.length) {
17169
+ return {
17170
+ additions: (distance + b.length - a.length) / 2,
17171
+ deletions: (distance + a.length - b.length) / 2
17172
+ };
17173
+ }
17174
+ }
17175
+ frontier = next;
17176
+ }
17177
+ let start = 0;
17178
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
17179
+ let aEnd = a.length;
17180
+ let bEnd = b.length;
17181
+ while (aEnd > start && bEnd > start && a[aEnd - 1] === b[bEnd - 1]) {
17182
+ aEnd--;
17183
+ bEnd--;
17184
+ }
17185
+ return { additions: bEnd - start, deletions: aEnd - start };
17186
+ }
17187
+ function unifiedDiffCounts(diff) {
17188
+ let additions = 0;
17189
+ let deletions = 0;
17190
+ for (const line of String(diff || "").split("\n")) {
17191
+ if (line.startsWith("+++") || line.startsWith("---")) continue;
17192
+ if (line.startsWith("+")) additions++;
17193
+ else if (line.startsWith("-")) deletions++;
17194
+ }
17195
+ return { additions, deletions };
17196
+ }
17197
+ function safePath(cwd, file) {
17198
+ const absolute = resolve6(cwd, file);
17199
+ const rel = relative(cwd, absolute);
17200
+ return rel && rel !== ".." && !rel.startsWith("../") ? rel : null;
17201
+ }
17202
+ function worktreeContent(cwd, file) {
17203
+ const rel = safePath(cwd, file);
17204
+ if (!rel) return null;
17205
+ try {
17206
+ const absolute = resolve6(cwd, rel);
17207
+ const stat = lstatSync2(absolute);
17208
+ if (stat.isSymbolicLink()) return "symlink:" + readlinkSync(absolute);
17209
+ if (!stat.isFile() || stat.size > MAX_TEXT_BYTES) return null;
17210
+ const content = readFileSync33(absolute);
17211
+ return content.includes(0) ? null : content.toString("utf8");
17212
+ } catch {
17213
+ return null;
17214
+ }
17215
+ }
17216
+ function fileOperation(event) {
17217
+ return (event.type === "tool-start" || event.type === "tool-end") && /^(edit|write|delete)$/.test(event.kind);
17218
+ }
17219
+ function eventPaths(cwd, event) {
17220
+ const provided = event.type === "tool-end" ? (event.fileChanges || []).map((change) => change.path) : [];
17221
+ const targets = String(event.target || "").split(/,\s*/);
17222
+ return [...new Set([...provided, ...targets].map((file) => safePath(cwd, file)).filter((file) => Boolean(file)))];
17223
+ }
17224
+ function snapshotOperation(cwd, event) {
17225
+ return new Map(eventPaths(cwd, event).map((file) => [file, worktreeContent(cwd, file)]));
17226
+ }
17227
+ var MAX_TEXT_BYTES, MAX_DIFF_DISTANCE, OperationChangeTracker;
17228
+ var init_changes = __esm({
17229
+ "cli/harness/changes.ts"() {
17230
+ "use strict";
17231
+ MAX_TEXT_BYTES = 2 * 1024 * 1024;
17232
+ MAX_DIFF_DISTANCE = 4e3;
17233
+ OperationChangeTracker = class {
17234
+ constructor(cwd, capture) {
17235
+ this.cwd = cwd;
17236
+ this.capture = capture;
17237
+ }
17238
+ cwd;
17239
+ capture;
17240
+ starts = /* @__PURE__ */ new Map();
17241
+ snapshot(event) {
17242
+ return this.capture ? this.capture() : snapshotOperation(this.cwd, event);
17243
+ }
17244
+ observe(event) {
17245
+ if (event.type === "tool-start") {
17246
+ if (fileOperation(event)) this.starts.set(event.id, this.snapshot(event));
17247
+ return event;
17248
+ }
17249
+ if (event.type !== "tool-end") return event;
17250
+ const before = this.starts.get(event.id);
17251
+ this.starts.delete(event.id);
17252
+ if (!before) return event;
17253
+ const after = this.snapshot(event);
17254
+ const files = [.../* @__PURE__ */ new Set([...before.keys(), ...after.keys()])].sort();
17255
+ const fileChanges2 = [];
17256
+ for (const file of files) {
17257
+ const oldText = before.get(file);
17258
+ const newText = after.has(file) ? after.get(file) : worktreeContent(this.cwd, file);
17259
+ if (oldText === newText || oldText === null && newText === null) continue;
17260
+ const counts = lineChangeCounts(oldText || "", newText || "");
17261
+ fileChanges2.push({ path: file, ...counts });
17262
+ }
17263
+ const provided = event.fileChanges || [];
17264
+ const measured = new Map(fileChanges2.map((change) => [change.path, change]));
17265
+ const merged = provided.map((change) => {
17266
+ const actual = measured.get(change.path) || measured.get(safePath(this.cwd, change.path) || "");
17267
+ if (actual && change.additions === 0 && change.deletions === 0) return actual;
17268
+ return change;
17269
+ });
17270
+ const providedPaths = new Set(provided.flatMap((change) => [change.path, safePath(this.cwd, change.path) || ""]));
17271
+ for (const change of fileChanges2) if (!providedPaths.has(change.path)) merged.push(change);
17272
+ return { ...event, fileChanges: merged.length ? merged : fileChanges2 };
17273
+ }
17274
+ };
17275
+ }
17276
+ });
17277
+
17278
+ // cli/codexUsage.ts
17279
+ function record(value) {
17280
+ return value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
17281
+ }
17282
+ function finiteNumber(value) {
17283
+ const n = Number(value);
17284
+ return Number.isFinite(n) ? n : null;
17285
+ }
17286
+ function rateWindow(value) {
17287
+ const row2 = record(value);
17288
+ if (!row2) return null;
17289
+ const usedPercent = finiteNumber(row2.usedPercent);
17290
+ if (usedPercent == null) return null;
17291
+ return {
17292
+ durationMins: finiteNumber(row2.windowDurationMins),
17293
+ usedPercent,
17294
+ resetsAt: finiteNumber(row2.resetsAt)
17295
+ };
17296
+ }
17297
+ function codexUsageFromRateLimitsResponse(value) {
17298
+ const response = record(value);
17299
+ if (!response) return null;
17300
+ const byLimitId = record(response.rateLimitsByLimitId);
17301
+ const snapshot = record(byLimitId?.codex) || record(response.rateLimits);
17302
+ if (!snapshot) return null;
17303
+ const primary = rateWindow(snapshot.primary);
17304
+ const secondary = rateWindow(snapshot.secondary);
17305
+ const windows = [primary, secondary].filter((w) => w != null);
17306
+ if (windows.length === 0) return null;
17307
+ const fiveHour = windows.find((w) => w.durationMins === 300) || windows.find((w) => w.durationMins != null && w.durationMins <= 360) || (primary?.durationMins == null ? primary : null);
17308
+ const sevenDay = windows.find((w) => w.durationMins === 10080) || windows.find((w) => w.durationMins != null && w.durationMins >= 1440) || (secondary !== fiveHour && secondary?.durationMins == null ? secondary : null);
17309
+ const status = typeof snapshot.rateLimitReachedType === "string" ? snapshot.rateLimitReachedType : snapshot.spendControlReached === true ? "spend_control_reached" : null;
17310
+ return {
17311
+ util5h: fiveHour ? fiveHour.usedPercent / 100 : null,
17312
+ util7d: sevenDay ? sevenDay.usedPercent / 100 : null,
17313
+ reset5h: fiveHour?.resetsAt ?? null,
17314
+ reset7d: sevenDay?.resetsAt ?? null,
17315
+ status,
17316
+ planType: typeof snapshot.planType === "string" ? snapshot.planType : null,
17317
+ limitId: typeof snapshot.limitId === "string" ? snapshot.limitId : null
17318
+ };
17319
+ }
17320
+ var init_codexUsage = __esm({
17321
+ "cli/codexUsage.ts"() {
17322
+ "use strict";
16833
17323
  }
16834
17324
  });
16835
17325
 
@@ -16845,10 +17335,10 @@ function textOf(message) {
16845
17335
  }
16846
17336
  function toolPayload(toolCall) {
16847
17337
  if (!toolCall) return { kind: "other", body: {} };
16848
- for (const [key, kind] of Object.entries(TOOL_KINDS)) {
16849
- if (toolCall[key]) return { kind, body: toolCall[key] };
17338
+ for (const [key2, kind] of Object.entries(TOOL_KINDS)) {
17339
+ if (toolCall[key2]) return { kind, body: toolCall[key2] };
16850
17340
  }
16851
- const fallback = Object.keys(toolCall).find((key) => key.endsWith("ToolCall"));
17341
+ const fallback = Object.keys(toolCall).find((key2) => key2.endsWith("ToolCall"));
16852
17342
  return fallback ? { kind: "other", body: toolCall[fallback] } : { kind: "other", body: {} };
16853
17343
  }
16854
17344
  function targetOf(kind, body) {
@@ -16857,7 +17347,25 @@ function targetOf(kind, body) {
16857
17347
  const text = typeof candidate === "string" ? candidate : JSON.stringify(candidate ?? "");
16858
17348
  return text || body?.description || kind;
16859
17349
  }
16860
- function parseCursorLine(line) {
17350
+ function completedFileChanges(body) {
17351
+ const success = body?.result?.success || {};
17352
+ const rows = Array.isArray(success.changes) ? success.changes : Array.isArray(success.files) ? success.files : [];
17353
+ return rows.flatMap((change) => {
17354
+ const path = String(change?.path || change?.filePath || change?.file_path || "");
17355
+ if (!path) return [];
17356
+ const diff = String(change?.diff || change?.patch || "");
17357
+ const hasAdditions = change?.additions != null || change?.linesAdded != null;
17358
+ const hasDeletions = change?.deletions != null || change?.linesRemoved != null;
17359
+ if (!diff && !hasAdditions && !hasDeletions) return [];
17360
+ const counted = unifiedDiffCounts(diff);
17361
+ return [{
17362
+ path,
17363
+ additions: hasAdditions ? Number(change?.additions ?? change?.linesAdded) : counted.additions,
17364
+ deletions: hasDeletions ? Number(change?.deletions ?? change?.linesRemoved) : counted.deletions
17365
+ }];
17366
+ });
17367
+ }
17368
+ function parseCursorLine(line, streamPartial = false) {
16861
17369
  const trimmed = String(line || "").trim();
16862
17370
  if (!trimmed) return [];
16863
17371
  let frame;
@@ -16883,7 +17391,7 @@ function parseCursorLine(line) {
16883
17391
  }
16884
17392
  if (type === "assistant") {
16885
17393
  const text = textOf(frame.message);
16886
- return text ? [{ type: "assistant-message", text }] : [];
17394
+ return text ? [streamPartial ? { type: "assistant-delta", id: String(frame.model_call_id || ""), text } : { type: "assistant-message", text }] : [];
16887
17395
  }
16888
17396
  if (type === "thinking" && subtype === "delta" && frame.text) {
16889
17397
  return [{ type: "thinking", text: String(frame.text) }];
@@ -16910,8 +17418,10 @@ function parseCursorLine(line) {
16910
17418
  ok: Boolean(success) && Number(success?.exitCode ?? 0) === 0,
16911
17419
  blocked,
16912
17420
  reason: rejected ? blocked ? blockReason(rawReason) : rawReason || "rejected" : "",
17421
+ pollId: blocked ? fixPollId(rawReason) : "",
16913
17422
  exitCode: success ? Number(success.exitCode ?? 0) : null,
16914
- output: String(success?.stdout || success?.stderr || "")
17423
+ output: String(success?.stdout || success?.stderr || ""),
17424
+ fileChanges: completedFileChanges(body)
16915
17425
  }];
16916
17426
  }
16917
17427
  return [];
@@ -16926,11 +17436,15 @@ function parseCursorLine(line) {
16926
17436
  }];
16927
17437
  }
16928
17438
  if (type === "result") {
16929
- return [{
16930
- type: "turn-end",
16931
- ok: !frame.is_error,
16932
- text: String(frame.result || "")
16933
- }];
17439
+ const result = String(frame.result || "");
17440
+ return [
17441
+ ...streamPartial && result ? [{ type: "assistant-message", text: result }] : [],
17442
+ {
17443
+ type: "turn-end",
17444
+ ok: !frame.is_error,
17445
+ text: result
17446
+ }
17447
+ ];
16934
17448
  }
16935
17449
  return [];
16936
17450
  }
@@ -16949,12 +17463,14 @@ function feed(buffer, chunk) {
16949
17463
  const rest = parts.pop() ?? "";
16950
17464
  return { lines: parts, rest };
16951
17465
  }
16952
- function cursorArgs(prompt) {
17466
+ function cursorArgs(prompt, resumeId = "") {
16953
17467
  return [
16954
17468
  "-p",
16955
17469
  prompt,
16956
17470
  "--output-format",
16957
17471
  "stream-json",
17472
+ "--stream-partial-output",
17473
+ ...resumeId ? ["--resume", resumeId] : [],
16958
17474
  // --force auto-runs tools but does NOT bypass hooks (verified live), so
16959
17475
  // Synkro's guards still gate every call; --trust loads workspace hooks.
16960
17476
  "--force",
@@ -16966,6 +17482,7 @@ var init_cursor = __esm({
16966
17482
  "cli/harness/cursor.ts"() {
16967
17483
  "use strict";
16968
17484
  init_events();
17485
+ init_changes();
16969
17486
  TOOL_KINDS = {
16970
17487
  shellToolCall: "shell",
16971
17488
  readToolCall: "read",
@@ -16981,131 +17498,9 @@ var init_cursor = __esm({
16981
17498
  }
16982
17499
  });
16983
17500
 
16984
- // cli/harness/render.ts
16985
- function spinnerFrame(tick) {
16986
- return SPINNER[Math.abs(tick) % SPINNER.length];
16987
- }
16988
- function clip3(text, max) {
16989
- const value = String(text || "").replace(/\s+/g, " ").trim();
16990
- if (max <= 1) return value;
16991
- return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
16992
- }
16993
- function seconds(ms) {
16994
- const total = Math.max(0, Math.round(ms / 1e3));
16995
- if (total < 60) return total + "s";
16996
- return Math.floor(total / 60) + "m" + String(total % 60).padStart(2, "0") + "s";
16997
- }
16998
- function statusLine(opts) {
16999
- const hint = opts.hint ? " \xB7 " + opts.hint : "";
17000
- const body = opts.text + " (" + seconds(opts.elapsedMs) + hint + ")";
17001
- const width = Math.max(20, (opts.width || 100) - 4);
17002
- return S2.think + " " + spinnerFrame(opts.tick) + " " + clip3(body, width) + S2.reset;
17003
- }
17004
- function renderEvent(event, width = 100) {
17005
- const body = Math.max(30, width - 4);
17006
- switch (event.type) {
17007
- // The workspace and both accounts are already in the session header, so
17008
- // this line carries only what the header could not know before the harness
17009
- // started: which model answered, and whether a subscription or a key paid.
17010
- case "session-start":
17011
- return [
17012
- "",
17013
- S2.dim + " " + clip3(event.model, body - 20) + (event.authSource === "login" ? " \xB7 subscription" : " \xB7 api key") + S2.reset,
17014
- ""
17015
- ];
17016
- case "user-message":
17017
- return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset, ""];
17018
- // Live state, not transcript. The runner promotes these to the status line.
17019
- case "thinking":
17020
- return [];
17021
- case "assistant-message": {
17022
- const lines = wrap(event.text, body - 2);
17023
- return [
17024
- "",
17025
- ...lines.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset),
17026
- ""
17027
- ];
17028
- }
17029
- case "tool-start": {
17030
- const label = TOOL_LABEL[event.kind] || TOOL_LABEL.other;
17031
- return [
17032
- S2.tool + " " + BULLET + " " + label + S2.reset + S2.dim + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
17033
- ];
17034
- }
17035
- case "tool-end": {
17036
- if (event.blocked) {
17037
- return [
17038
- S2.blocked + " " + BULLET + " Blocked" + S2.reset + S2.dim + " " + clip3(event.target, body - 14) + S2.reset,
17039
- ...wrap(event.reason, body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset)
17040
- ];
17041
- }
17042
- const detail = event.ok ? event.output.trim() ? clip3(event.output, body - 10) : "done" : "failed" + (event.exitCode === null ? "" : " (exit " + event.exitCode + ")");
17043
- const tint = event.ok ? S2.ok : S2.blocked;
17044
- return [tint + " " + ELBOW + " " + S2.reset + S2.dim + detail + S2.reset];
17045
- }
17046
- // A clean finish needs no announcement: the prompt returning IS the signal.
17047
- case "turn-end":
17048
- return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
17049
- case "notice":
17050
- return [S2.dim + " " + clip3(event.text, body) + S2.reset];
17051
- default:
17052
- return [];
17053
- }
17054
- }
17055
- function wrap(text, width) {
17056
- const words = String(text || "").replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
17057
- if (words.length === 0) return [];
17058
- const lines = [];
17059
- let line = "";
17060
- for (const word of words) {
17061
- if (!line) line = word;
17062
- else if ((line + " " + word).length <= width) line += " " + word;
17063
- else {
17064
- lines.push(line);
17065
- line = word;
17066
- }
17067
- }
17068
- if (line) lines.push(line);
17069
- return lines;
17070
- }
17071
- var ESC2, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
17072
- var init_render2 = __esm({
17073
- "cli/harness/render.ts"() {
17074
- "use strict";
17075
- ESC2 = "\x1B[";
17076
- S2 = {
17077
- reset: ESC2 + "0m",
17078
- dim: ESC2 + "2m",
17079
- bold: ESC2 + "1m",
17080
- user: ESC2 + "38;5;111m",
17081
- agent: ESC2 + "38;5;252m",
17082
- think: ESC2 + "38;5;244m",
17083
- tool: ESC2 + "38;5;180m",
17084
- ok: ESC2 + "38;5;114m",
17085
- blocked: ESC2 + "38;5;203m",
17086
- rule: ESC2 + "38;5;211m"
17087
- };
17088
- CLEAR_LINE = "\r" + ESC2 + "2K";
17089
- BULLET = "\u23FA";
17090
- ELBOW = "\u23BF";
17091
- SPINNER = ["\xB7", "\u2722", "\u2733", "\u2217", "\u273B", "\u273D"];
17092
- TOOL_LABEL = {
17093
- shell: "Shell",
17094
- read: "Read",
17095
- edit: "Edit",
17096
- write: "Write",
17097
- delete: "Delete",
17098
- search: "Search",
17099
- list: "List",
17100
- todo: "Todo",
17101
- other: "Tool"
17102
- };
17103
- }
17104
- });
17105
-
17106
- // cli/harness/run.ts
17107
- import { spawn as spawn9 } from "child_process";
17108
- function replayKey(event) {
17501
+ // cli/harness/run.ts
17502
+ import { spawn as spawn9 } from "child_process";
17503
+ function replayKey(event) {
17109
17504
  switch (event.type) {
17110
17505
  case "user-message":
17111
17506
  return "u:" + event.text;
@@ -17124,6 +17519,7 @@ function createTurnSink(opts) {
17124
17519
  const now = opts.now || (() => Date.now());
17125
17520
  const showPrompt = opts.showPrompt !== false;
17126
17521
  let blocked = 0;
17522
+ let sessionId = "";
17127
17523
  let replaying = false;
17128
17524
  const seen = /* @__PURE__ */ new Set();
17129
17525
  let sawTurnEnd = false;
@@ -17131,6 +17527,7 @@ function createTurnSink(opts) {
17131
17527
  let buffer = "";
17132
17528
  let reconnecting = false;
17133
17529
  let lastWasAnswer = false;
17530
+ const toolStartedAt = /* @__PURE__ */ new Map();
17134
17531
  let statusText = "";
17135
17532
  let statusKind = "progress";
17136
17533
  let statusShown = false;
@@ -17203,6 +17600,7 @@ function createTurnSink(opts) {
17203
17600
  };
17204
17601
  const emit2 = (event) => {
17205
17602
  events.push(event);
17603
+ if (event.type === "session-start" && event.sessionId) sessionId = event.sessionId;
17206
17604
  if (event.type === "tool-end" && event.blocked) blocked += 1;
17207
17605
  opts.onEvent?.(event);
17208
17606
  if (event.type === "notice" && event.kind === "retry") {
@@ -17233,7 +17631,17 @@ function createTurnSink(opts) {
17233
17631
  opts.write(rendered.join("\n") + "\n");
17234
17632
  drawStatus();
17235
17633
  };
17236
- const take = (event) => {
17634
+ const take = (incoming) => {
17635
+ let event = incoming;
17636
+ if (event.type === "tool-start" && event.id && !toolStartedAt.has(event.id)) {
17637
+ toolStartedAt.set(event.id, now());
17638
+ } else if (event.type === "tool-end") {
17639
+ const started = toolStartedAt.get(event.id);
17640
+ if ((event.durationMs === null || event.durationMs === void 0) && started !== void 0) {
17641
+ event = { ...event, durationMs: Math.max(0, now() - started) };
17642
+ }
17643
+ toolStartedAt.delete(event.id);
17644
+ }
17237
17645
  const isRetry = event.type === "notice" && event.kind === "retry";
17238
17646
  if (!isRetry) disarmStall();
17239
17647
  if (isRetry) {
@@ -17243,29 +17651,32 @@ function createTurnSink(opts) {
17243
17651
  }
17244
17652
  replaying = true;
17245
17653
  }
17246
- const key = replayKey(event);
17247
- if (key) {
17248
- if (replaying && seen.has(key)) {
17654
+ const key2 = replayKey(event);
17655
+ if (key2) {
17656
+ if (replaying && seen.has(key2)) {
17249
17657
  lastWasAnswer = event.type === "assistant-message";
17250
17658
  settle();
17251
17659
  return;
17252
17660
  }
17253
- seen.add(key);
17661
+ seen.add(key2);
17254
17662
  }
17255
- if (key || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
17663
+ if (key2 || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
17256
17664
  if (event.type === "turn-end") sawTurnEnd = true;
17257
17665
  if (event.type === "assistant-message") sawAnswer = true;
17258
17666
  emit2(event);
17259
17667
  };
17260
17668
  return {
17261
17669
  chunk(text) {
17262
- const { lines, rest } = feed(buffer, text);
17670
+ const { lines: lines2, rest } = feed(buffer, text);
17263
17671
  buffer = rest;
17264
- for (const line of lines) for (const event of parseCursorLine(line)) take(event);
17672
+ for (const line of lines2) for (const event of parseCursorLine(line, opts.streamPartial)) take(event);
17265
17673
  },
17266
17674
  notice(text) {
17267
17675
  emit2({ type: "notice", text });
17268
17676
  },
17677
+ event(event) {
17678
+ take(event);
17679
+ },
17269
17680
  finish(exit, interrupted = false) {
17270
17681
  disarmStall();
17271
17682
  stopStatus();
@@ -17281,7 +17692,7 @@ function createTurnSink(opts) {
17281
17692
  }
17282
17693
  }
17283
17694
  stopStatus();
17284
- return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit };
17695
+ return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit, sessionId };
17285
17696
  }
17286
17697
  };
17287
17698
  }
@@ -17297,10 +17708,11 @@ async function runCursorTurn(opts) {
17297
17708
  animate: Boolean(process.stdout.isTTY),
17298
17709
  showPrompt: opts.showPrompt,
17299
17710
  showHeader: opts.showHeader,
17300
- onGiveUp: () => stopHarness()
17711
+ onGiveUp: () => stopHarness(),
17712
+ streamPartial: true
17301
17713
  });
17302
- return new Promise((resolve7) => {
17303
- const child = spawn9("cursor-agent", cursorArgs(opts.prompt), {
17714
+ return new Promise((resolve9) => {
17715
+ const child = spawn9("cursor-agent", cursorArgs(opts.prompt, opts.resumeId), {
17304
17716
  cwd: opts.cwd,
17305
17717
  stdio: ["ignore", "pipe", "pipe"]
17306
17718
  });
@@ -17328,11 +17740,11 @@ async function runCursorTurn(opts) {
17328
17740
  });
17329
17741
  child.on("close", (code) => {
17330
17742
  opts.signal?.removeEventListener("abort", onAbort);
17331
- resolve7(sink.finish(code ?? 0, interrupted));
17743
+ resolve9(sink.finish(code ?? 0, interrupted));
17332
17744
  });
17333
17745
  child.on("error", (error) => {
17334
17746
  sink.notice("failed to start cursor-agent: " + String(error));
17335
- resolve7(sink.finish(1, interrupted));
17747
+ resolve9(sink.finish(1, interrupted));
17336
17748
  });
17337
17749
  });
17338
17750
  }
@@ -17346,128 +17758,884 @@ var init_run = __esm({
17346
17758
  }
17347
17759
  });
17348
17760
 
17349
- // cli/harness/identity.ts
17350
- import { execFileSync as execFileSync5 } from "child_process";
17351
- import { homedir as homedir38 } from "os";
17352
- function shortenPath(path, home = homedir38()) {
17353
- const value = String(path || "");
17354
- if (home && value === home) return "~";
17355
- if (home && value.startsWith(home + "/")) return "~" + value.slice(home.length);
17356
- return value;
17357
- }
17358
- function parseCursorAccount(output) {
17359
- const match = String(output || "").match(/logged in as\s+(\S+)/i);
17360
- return match ? match[1].trim() : "";
17361
- }
17362
- function cursorAccount() {
17761
+ // cli/harness/codex.ts
17762
+ import { spawn as spawn10 } from "child_process";
17763
+ import { createInterface as createInterface6 } from "readline";
17764
+ function stringify(value) {
17765
+ if (typeof value === "string") return value;
17363
17766
  try {
17364
- const out = execFileSync5("cursor-agent", ["status"], {
17365
- encoding: "utf8",
17366
- timeout: 5e3,
17367
- stdio: ["ignore", "pipe", "pipe"]
17368
- });
17369
- return parseCursorAccount(out);
17767
+ return JSON.stringify(value);
17370
17768
  } catch {
17371
- return "";
17769
+ return String(value ?? "");
17770
+ }
17771
+ }
17772
+ function toolKind(item) {
17773
+ if (item.type === "commandExecution") return "shell";
17774
+ if (item.type === "fileChange") return "edit";
17775
+ if (item.type === "webSearch") return "search";
17776
+ return "other";
17777
+ }
17778
+ function toolTarget(item) {
17779
+ if (item.type === "commandExecution") return item.command || "command";
17780
+ if (item.type === "fileChange") return (item.changes || []).map((change) => change.path || change.filePath || "").filter(Boolean).join(", ") || "workspace files";
17781
+ if (item.type === "mcpToolCall") return [item.server, item.tool].filter(Boolean).join(".") || "MCP tool";
17782
+ if (item.type === "dynamicToolCall") return [item.namespace, item.tool].filter(Boolean).join(".") || "tool";
17783
+ return item.query || item.type || "tool";
17784
+ }
17785
+ function fileChanges(item) {
17786
+ if (item.type !== "fileChange" || !Array.isArray(item.changes)) return [];
17787
+ return item.changes.flatMap((change) => {
17788
+ const path = String(change.path || change.filePath || "");
17789
+ if (!path) return [];
17790
+ const diff = String(change.diff || change.patch || "");
17791
+ const hasAdditions = change.additions != null || change.linesAdded != null;
17792
+ const hasDeletions = change.deletions != null || change.linesRemoved != null;
17793
+ if (!diff && !hasAdditions && !hasDeletions) return [];
17794
+ const counted = unifiedDiffCounts(diff);
17795
+ const additions = hasAdditions ? Number(change.additions ?? change.linesAdded) : counted.additions;
17796
+ const deletions = hasDeletions ? Number(change.deletions ?? change.linesRemoved) : counted.deletions;
17797
+ return [{ path, additions, deletions }];
17798
+ });
17799
+ }
17800
+ function codexAccountUsageEvent(value) {
17801
+ const usage2 = codexUsageFromRateLimitsResponse(value);
17802
+ if (usage2?.util7d == null) return null;
17803
+ return {
17804
+ type: "account-usage",
17805
+ period: "weekly",
17806
+ remainingPercent: Math.max(0, Math.min(100, Math.round((1 - usage2.util7d) * 100))),
17807
+ resetsAt: usage2.reset7d
17808
+ };
17809
+ }
17810
+ function codexMessageTurnId(message) {
17811
+ return String(message.params?.turnId || message.params?.turn?.id || "");
17812
+ }
17813
+ function codexMessageIsTurnScoped(message) {
17814
+ const method = String(message.method || "");
17815
+ return method.startsWith("item/") || method === "hook/completed" || method === "turn/plan/updated";
17816
+ }
17817
+ function codexEvents(message) {
17818
+ const method = String(message.method || "");
17819
+ const params = message.params || {};
17820
+ if (method === "account/rateLimits/updated") {
17821
+ const event = codexAccountUsageEvent(params);
17822
+ return event ? [event] : [];
17823
+ }
17824
+ const item = params.item || {};
17825
+ if (method === "item/agentMessage/delta") return [{ type: "assistant-delta", id: params.itemId || "", text: params.delta || "" }];
17826
+ if (method === "item/reasoning/summaryTextDelta") return [{ type: "thinking", text: params.delta || "Thinking" }];
17827
+ const tools = ["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall", "webSearch"];
17828
+ if (method === "item/started" && tools.includes(item.type)) {
17829
+ return [{ type: "tool-start", id: item.id || "", kind: toolKind(item), target: toolTarget(item), description: "" }];
17830
+ }
17831
+ if (method === "item/completed" && item.type === "agentMessage") return [{ type: "assistant-message", text: item.text || "" }];
17832
+ if (method === "item/completed" && tools.includes(item.type)) {
17833
+ const raw = stringify(item.aggregatedOutput || item.error?.message || item.result?.content || item.contentItems || "");
17834
+ const status = String(item.status || "");
17835
+ const blocked = BLOCK_MARKER.test(raw) || /declined|blocked|denied/.test(status);
17836
+ const ok = !blocked && !/failed|error/.test(status) && (item.exitCode === null || item.exitCode === void 0 || item.exitCode === 0);
17837
+ return [{
17838
+ type: "tool-end",
17839
+ id: item.id || "",
17840
+ kind: toolKind(item),
17841
+ target: toolTarget(item),
17842
+ ok,
17843
+ blocked,
17844
+ reason: blocked ? blockReason(raw || status) : "",
17845
+ pollId: blocked ? fixPollId(raw) : "",
17846
+ exitCode: typeof item.exitCode === "number" ? item.exitCode : null,
17847
+ output: raw,
17848
+ durationMs: typeof item.durationMs === "number" ? item.durationMs : null,
17849
+ fileChanges: fileChanges(item)
17850
+ }];
17851
+ }
17852
+ if (method === "hook/completed") {
17853
+ const run2 = params.run || {};
17854
+ if (run2.status !== "blocked") return [];
17855
+ const reason = (run2.entries || []).map((entry) => entry.text || "").filter(Boolean).join("\n") || run2.statusMessage || "blocked by policy";
17856
+ return [{
17857
+ type: "tool-end",
17858
+ id: "hook:" + (run2.id || ""),
17859
+ kind: "other",
17860
+ target: run2.eventName || "Guard",
17861
+ ok: false,
17862
+ blocked: true,
17863
+ reason: blockReason(reason),
17864
+ exitCode: null,
17865
+ output: reason,
17866
+ pollId: fixPollId(reason),
17867
+ durationMs: typeof run2.durationMs === "number" ? run2.durationMs : null
17868
+ }];
17869
+ }
17870
+ if (method === "turn/plan/updated") {
17871
+ return [{ type: "plan", steps: (params.plan || []).map((row2) => ({ step: row2.step || "", status: row2.status || "pending" })) }];
17872
+ }
17873
+ if (method === "thread/tokenUsage/updated") {
17874
+ const usage2 = params.tokenUsage || {};
17875
+ const total = usage2.total || usage2.last || {};
17876
+ const used = Number(total.totalTokens ?? total.total_tokens ?? usage2.totalTokens ?? 0);
17877
+ const context = Number(usage2.modelContextWindow ?? usage2.model_context_window ?? 0);
17878
+ return [{ type: "usage", used, contextWindow: context || null }];
17372
17879
  }
17880
+ if (method === "warning" || method === "error") return [{ type: "notice", text: params.message || params.error?.message || "Codex transport warning" }];
17881
+ return [];
17373
17882
  }
17374
- function synkroAccount() {
17375
- let email = "";
17883
+ var CodexSession;
17884
+ var init_codex = __esm({
17885
+ "cli/harness/codex.ts"() {
17886
+ "use strict";
17887
+ init_composer();
17888
+ init_events();
17889
+ init_changes();
17890
+ init_codexUsage();
17891
+ init_run();
17892
+ init_render2();
17893
+ CodexSession = class {
17894
+ constructor(cwd, resumeId = "") {
17895
+ this.cwd = cwd;
17896
+ this.resumeId = resumeId;
17897
+ }
17898
+ cwd;
17899
+ resumeId;
17900
+ child = null;
17901
+ nextId = 1;
17902
+ pending = /* @__PURE__ */ new Map();
17903
+ onEvent = null;
17904
+ activeTurn = "";
17905
+ turnDone = null;
17906
+ completedTurns = /* @__PURE__ */ new Map();
17907
+ accountUsage = null;
17908
+ threadId = "";
17909
+ model = "";
17910
+ request(method, params, timeoutMs = 0) {
17911
+ if (!this.child) return Promise.reject(new Error("Codex app-server is not running"));
17912
+ const id = this.nextId++;
17913
+ this.child.stdin.write(JSON.stringify({ method, id, params }) + "\n");
17914
+ return new Promise((resolve9, reject) => {
17915
+ const timer = timeoutMs > 0 ? setTimeout(() => {
17916
+ this.pending.delete(id);
17917
+ reject(new Error(method + " timed out"));
17918
+ }, timeoutMs) : null;
17919
+ this.pending.set(id, {
17920
+ resolve: (value) => {
17921
+ if (timer) clearTimeout(timer);
17922
+ resolve9(value);
17923
+ },
17924
+ reject: (error) => {
17925
+ if (timer) clearTimeout(timer);
17926
+ reject(error);
17927
+ }
17928
+ });
17929
+ });
17930
+ }
17931
+ notify(method, params = {}) {
17932
+ this.child?.stdin.write(JSON.stringify({ method, params }) + "\n");
17933
+ }
17934
+ receive(message) {
17935
+ if (message.id !== void 0 && !message.method) {
17936
+ const pending = this.pending.get(message.id);
17937
+ if (!pending) return;
17938
+ this.pending.delete(message.id);
17939
+ if (message.error) pending.reject(new Error(message.error.message || stringify(message.error)));
17940
+ else pending.resolve(message.result);
17941
+ return;
17942
+ }
17943
+ if (message.id !== void 0 && message.method) {
17944
+ this.child?.stdin.write(JSON.stringify({ id: message.id, result: { decision: "decline" } }) + "\n");
17945
+ this.onEvent?.({ type: "notice", text: "Codex requested an approval the embedded policy did not permit." });
17946
+ return;
17947
+ }
17948
+ const messageTurnId = codexMessageTurnId(message);
17949
+ const staleTurnEvent = codexMessageIsTurnScoped(message) && (!this.activeTurn || messageTurnId && messageTurnId !== this.activeTurn);
17950
+ for (const event of codexEvents(message)) {
17951
+ if (event.type === "account-usage") this.accountUsage = event;
17952
+ if (!staleTurnEvent) this.onEvent?.(event);
17953
+ }
17954
+ if (message.method === "turn/completed" && message.params?.turn?.id === this.activeTurn) {
17955
+ const status = String(message.params.turn.status || "");
17956
+ this.completedTurns.set(message.params.turn.id, status);
17957
+ this.turnDone?.(status);
17958
+ } else if (message.method === "turn/completed" && message.params?.turn?.id) {
17959
+ this.completedTurns.set(message.params.turn.id, String(message.params.turn.status || ""));
17960
+ }
17961
+ }
17962
+ async start() {
17963
+ this.child = spawn10("codex", ["app-server", "--stdio"], { cwd: this.cwd, stdio: ["pipe", "pipe", "pipe"] });
17964
+ this.child.on("error", (cause) => {
17965
+ const error = cause instanceof Error ? cause : new Error(String(cause));
17966
+ for (const pending of this.pending.values()) pending.reject(error);
17967
+ this.pending.clear();
17968
+ this.turnDone?.("failed");
17969
+ });
17970
+ createInterface6({ input: this.child.stdout }).on("line", (line) => {
17971
+ try {
17972
+ this.receive(JSON.parse(line));
17973
+ } catch {
17974
+ }
17975
+ });
17976
+ this.child.stderr.on("data", (chunk) => {
17977
+ const message = String(chunk || "").trim();
17978
+ if (message) this.onEvent?.({ type: "notice", text: message });
17979
+ });
17980
+ this.child.once("exit", (code) => {
17981
+ const error = new Error("Codex app-server exited" + (code === null ? "" : " (" + code + ")"));
17982
+ for (const pending of this.pending.values()) pending.reject(error);
17983
+ this.pending.clear();
17984
+ this.turnDone?.("failed");
17985
+ });
17986
+ await this.request("initialize", { clientInfo: { name: "synkro-terminal", title: "Synkro", version: "1" }, capabilities: { experimentalApi: true } });
17987
+ this.notify("initialized");
17988
+ try {
17989
+ const event = codexAccountUsageEvent(await this.request("account/rateLimits/read", null, 4e3));
17990
+ if (event) this.accountUsage = event;
17991
+ } catch {
17992
+ }
17993
+ let result;
17994
+ try {
17995
+ result = this.resumeId ? await this.request("thread/resume", { threadId: this.resumeId, cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write" }) : await this.request("thread/start", { cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write", serviceName: "Synkro" });
17996
+ } catch (error) {
17997
+ if (!this.resumeId) throw error;
17998
+ result = await this.request("thread/start", { cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write", serviceName: "Synkro" });
17999
+ }
18000
+ this.threadId = result.thread?.id || "";
18001
+ this.model = result.model || "";
18002
+ }
18003
+ async runTurn(prompt, onEvent, signal) {
18004
+ let blocked = 0;
18005
+ let turnId = "";
18006
+ let interrupt = null;
18007
+ this.onEvent = (event) => {
18008
+ if (event.type === "tool-end" && event.blocked) blocked++;
18009
+ onEvent(event);
18010
+ };
18011
+ try {
18012
+ onEvent({ type: "session-start", sessionId: this.threadId, model: this.model || "Codex", cwd: this.cwd, authSource: "login" });
18013
+ if (this.accountUsage) onEvent(this.accountUsage);
18014
+ onEvent({ type: "user-message", text: prompt });
18015
+ const response = await this.request("turn/start", { threadId: this.threadId, input: [{ type: "text", text: prompt }] });
18016
+ turnId = response.turn?.id || "";
18017
+ this.activeTurn = turnId;
18018
+ let aborted = false;
18019
+ let interruptSent = false;
18020
+ interrupt = () => {
18021
+ aborted = true;
18022
+ if (interruptSent || !this.activeTurn) return;
18023
+ interruptSent = true;
18024
+ void this.request("turn/interrupt", { threadId: this.threadId, turnId: this.activeTurn }).catch(() => {
18025
+ });
18026
+ };
18027
+ if (signal?.aborted) interrupt();
18028
+ else signal?.addEventListener("abort", interrupt, { once: true });
18029
+ const alreadyDone = this.completedTurns.get(turnId);
18030
+ const status = alreadyDone ?? await new Promise((resolve9) => {
18031
+ this.turnDone = resolve9;
18032
+ });
18033
+ this.completedTurns.delete(turnId);
18034
+ signal?.removeEventListener("abort", interrupt);
18035
+ this.activeTurn = "";
18036
+ this.turnDone = null;
18037
+ this.onEvent = null;
18038
+ onEvent({ type: "turn-end", ok: aborted || /completed/.test(status), text: aborted ? "interrupted" : status });
18039
+ return { exitCode: aborted ? 130 : /completed/.test(status) ? 0 : 1, blocked, sessionId: this.threadId };
18040
+ } finally {
18041
+ if (interrupt) signal?.removeEventListener("abort", interrupt);
18042
+ if (turnId) this.completedTurns.delete(turnId);
18043
+ this.activeTurn = "";
18044
+ this.turnDone = null;
18045
+ this.onEvent = null;
18046
+ }
18047
+ }
18048
+ close() {
18049
+ this.child?.kill("SIGTERM");
18050
+ this.child = null;
18051
+ }
18052
+ };
18053
+ }
18054
+ });
18055
+
18056
+ // cli/harness/screen.ts
18057
+ var ANSI, REVERSE, STREAM_DRAW_MS, STATUS_TICK_MS, resetDateFormat, visible, pathLabel, SynkroScreen;
18058
+ var init_screen = __esm({
18059
+ "cli/harness/screen.ts"() {
18060
+ "use strict";
18061
+ init_render2();
18062
+ init_changes();
18063
+ ANSI = /\x1b\[[0-?]*[ -\/]*[@-~]/g;
18064
+ REVERSE = "\x1B[7m";
18065
+ STREAM_DRAW_MS = 80;
18066
+ STATUS_TICK_MS = 240;
18067
+ resetDateFormat = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" });
18068
+ visible = (value) => value.replace(ANSI, "").length;
18069
+ pathLabel = (cwd) => {
18070
+ const parts = cwd.split("/").filter(Boolean);
18071
+ return parts.length > 2 ? "\u2026/" + parts.slice(-2).join("/") : cwd;
18072
+ };
18073
+ SynkroScreen = class {
18074
+ constructor(harness, cwd, write2 = (text) => process.stdout.write(text)) {
18075
+ this.harness = harness;
18076
+ this.cwd = cwd;
18077
+ this.write = write2;
18078
+ this.changes = new OperationChangeTracker(cwd);
18079
+ }
18080
+ harness;
18081
+ cwd;
18082
+ write;
18083
+ transcript = [];
18084
+ draft = "";
18085
+ input = "";
18086
+ selectionAnchor = null;
18087
+ selectionFocus = null;
18088
+ status = "Ready";
18089
+ statusSince = Date.now();
18090
+ tick = 0;
18091
+ used = 0;
18092
+ context = null;
18093
+ weeklyRemaining = null;
18094
+ weeklyResetsAt = null;
18095
+ model = "";
18096
+ timer = null;
18097
+ drawTimer = null;
18098
+ lastDrawAt = 0;
18099
+ scrollOffset = 0;
18100
+ changes;
18101
+ resize = () => this.draw();
18102
+ enter() {
18103
+ this.write("\x1B[?1049h\x1B[?25l\x1B[?1000h\x1B[?1002h\x1B[?1006h");
18104
+ process.stdout.on("resize", this.resize);
18105
+ this.timer = setInterval(() => {
18106
+ if (this.status === "Ready") return;
18107
+ this.tick++;
18108
+ this.queueDraw();
18109
+ }, STATUS_TICK_MS);
18110
+ this.timer.unref();
18111
+ this.draw();
18112
+ }
18113
+ leave() {
18114
+ if (this.timer) clearInterval(this.timer);
18115
+ if (this.drawTimer) clearTimeout(this.drawTimer);
18116
+ process.stdout.removeListener("resize", this.resize);
18117
+ this.write(S2.reset + "\x1B[?1006l\x1B[?1002l\x1B[?1000l\x1B[?25h\x1B[?1049l");
18118
+ }
18119
+ setInput(value) {
18120
+ this.input = cleanOutput(value);
18121
+ this.selectionAnchor = null;
18122
+ this.selectionFocus = null;
18123
+ this.draw();
18124
+ }
18125
+ selectionRange() {
18126
+ if (this.selectionAnchor === null || this.selectionFocus === null || this.selectionAnchor === this.selectionFocus) return null;
18127
+ return [Math.min(this.selectionAnchor, this.selectionFocus), Math.max(this.selectionAnchor, this.selectionFocus)];
18128
+ }
18129
+ clearSelection() {
18130
+ this.selectionAnchor = null;
18131
+ this.selectionFocus = null;
18132
+ this.draw();
18133
+ }
18134
+ scrollBy(lines2) {
18135
+ this.scrollOffset = Math.max(0, this.scrollOffset + lines2);
18136
+ this.draw();
18137
+ }
18138
+ scrollToBottom() {
18139
+ this.scrollOffset = 0;
18140
+ this.draw();
18141
+ }
18142
+ /** Select text using one-based SGR mouse coordinates from the terminal. */
18143
+ selectInputAt(x, y, extend, width = this.width(), height = this.height()) {
18144
+ const rows = this.inputRows(width);
18145
+ const composerLength = rows.length + 2;
18146
+ const viewport = Math.max(3, height - composerLength - 2);
18147
+ const lineIndex = y - (viewport + 2);
18148
+ const row2 = rows[lineIndex];
18149
+ if (!row2) return;
18150
+ const index = Math.min(row2.end, row2.start + Math.max(0, x - 5));
18151
+ if (!extend || this.selectionAnchor === null) this.selectionAnchor = index;
18152
+ this.selectionFocus = index;
18153
+ this.draw();
18154
+ }
18155
+ updateStatus(value) {
18156
+ if (this.status === value) return false;
18157
+ this.status = value;
18158
+ this.statusSince = Date.now();
18159
+ return true;
18160
+ }
18161
+ setStatus(value) {
18162
+ if (this.updateStatus(value)) this.draw();
18163
+ }
18164
+ add(event) {
18165
+ event = this.changes.observe(event);
18166
+ if (event.type === "session-start") {
18167
+ this.model = event.model;
18168
+ this.draw();
18169
+ return;
18170
+ }
18171
+ if (event.type === "assistant-delta") {
18172
+ this.draft += event.text;
18173
+ this.updateStatus("Writing\u2026");
18174
+ this.queueDraw();
18175
+ return;
18176
+ }
18177
+ if (event.type === "assistant-message") this.draft = "";
18178
+ if (event.type === "usage") {
18179
+ this.used = event.used;
18180
+ this.context = event.contextWindow;
18181
+ if (event.model) this.model = event.model;
18182
+ this.draw();
18183
+ return;
18184
+ }
18185
+ if (event.type === "account-usage") {
18186
+ this.weeklyRemaining = event.remainingPercent;
18187
+ this.weeklyResetsAt = event.resetsAt;
18188
+ this.draw();
18189
+ return;
18190
+ }
18191
+ if (event.type === "thinking") {
18192
+ if (this.updateStatus(event.text || "Thinking\u2026")) this.draw();
18193
+ return;
18194
+ }
18195
+ if (event.type === "tool-start") this.updateStatus("Working\u2026");
18196
+ if (event.type === "tool-end") this.updateStatus(event.blocked ? "Guard enforced" : "Working\u2026");
18197
+ if (event.type === "turn-end") this.updateStatus(event.ok ? "Ready" : "Turn ended");
18198
+ const rendered = renderEvent(event, this.width());
18199
+ if (this.scrollOffset > 0) this.scrollOffset += rendered.length;
18200
+ this.transcript.push(...rendered);
18201
+ this.draw();
18202
+ }
18203
+ width() {
18204
+ return Math.max(50, Number(process.stdout.columns || 100));
18205
+ }
18206
+ height() {
18207
+ return Math.max(16, Number(process.stdout.rows || 30));
18208
+ }
18209
+ inputRows(width) {
18210
+ const lineWidth = Math.max(1, Math.max(30, width - 4) - 4);
18211
+ if (!this.input) return [{ text: "", start: 0, end: 0 }];
18212
+ const rows = [];
18213
+ for (let start = 0; start < this.input.length; start += lineWidth) {
18214
+ const text = this.input.slice(start, start + lineWidth);
18215
+ rows.push({ text, start, end: start + text.length });
18216
+ }
18217
+ return rows.slice(-3);
18218
+ }
18219
+ selectedText(row2) {
18220
+ const range = this.selectionRange();
18221
+ if (!range || range[1] <= row2.start || range[0] >= row2.end) return S2.text + row2.text;
18222
+ const from = Math.max(range[0], row2.start) - row2.start;
18223
+ const to = Math.min(range[1], row2.end) - row2.start;
18224
+ return S2.text + row2.text.slice(0, from) + REVERSE + row2.text.slice(from, to) + S2.reset + S2.composer + S2.text + row2.text.slice(to);
18225
+ }
18226
+ frame(width = this.width(), height = this.height()) {
18227
+ const inner = Math.max(30, width - 4);
18228
+ const provider = this.harness === "codex" ? "Codex" : "Cursor";
18229
+ const draft = this.draft ? ["", ...wrap(this.draft, inner - 2).map((line, i) => S2.agent + " " + (i ? " " : "\u23FA ") + line + S2.reset)] : [];
18230
+ const status = this.status === "Ready" ? S2.secondary + " \xB7 Ready" + S2.reset : statusLine({ tick: this.tick, text: this.status, elapsedMs: Date.now() - this.statusSince, width });
18231
+ const rule = S2.border + " " + "\u2500".repeat(inner) + S2.reset;
18232
+ const inputRows = this.inputRows(width);
18233
+ const composer = [rule, ...inputRows.map((row2, i) => S2.composer + (i ? " " : S2.user + " \u276F ") + this.selectedText(row2) + (i === inputRows.length - 1 ? S2.user + " \u258C" : "") + S2.reset), rule];
18234
+ const usedPercent = this.context ? Math.min(100, Math.round(this.used / this.context * 100)) : null;
18235
+ const contextUsage = usedPercent === null ? "\u2014 context" : usedPercent + "% context";
18236
+ const resetDate = this.weeklyResetsAt == null ? "" : " \xB7 " + resetDateFormat.format(new Date(this.weeklyResetsAt * 1e3));
18237
+ const accountUsage = this.weeklyRemaining === null ? "Weekly \u2014" : "Weekly " + this.weeklyRemaining + "% left" + resetDate;
18238
+ const usage2 = contextUsage + " \xB7 " + accountUsage;
18239
+ const left = S2.secondary + " " + pathLabel(this.cwd) + S2.reset + S2.canvas;
18240
+ const badge = S2.panel + S2.secondary + " " + usage2 + " " + S2.reset + S2.canvas;
18241
+ const right = badge + S2.secondary + " \xB7 " + provider + (this.model ? " " + cleanOutput(this.model) : "") + " \xB7 \u29C9 powered by synkro " + S2.reset + S2.canvas;
18242
+ const footer = left + " ".repeat(Math.max(1, width - visible(left) - visible(right))) + right;
18243
+ const viewport = Math.max(3, height - composer.length - 2);
18244
+ const fullContent = [...this.transcript, ...draft, status];
18245
+ const maxScroll = Math.max(0, fullContent.length - viewport);
18246
+ const effectiveOffset = Math.min(this.scrollOffset, maxScroll);
18247
+ const end = fullContent.length - effectiveOffset;
18248
+ const content = fullContent.slice(Math.max(0, end - viewport), end);
18249
+ while (content.length < viewport) content.unshift("");
18250
+ const rows = [...content, ...composer, footer].slice(0, height);
18251
+ while (rows.length < height) rows.push("");
18252
+ return rows.map((line) => {
18253
+ const fill = line.startsWith(S2.panel) ? S2.panel : line.includes(S2.composer) ? S2.composer : S2.canvas;
18254
+ return S2.canvas + line + fill + " ".repeat(Math.max(0, width - visible(line))) + S2.reset;
18255
+ }).join("\n");
18256
+ }
18257
+ queueDraw() {
18258
+ if (this.drawTimer) return;
18259
+ const delay = Math.max(0, STREAM_DRAW_MS - (Date.now() - this.lastDrawAt));
18260
+ this.drawTimer = setTimeout(() => {
18261
+ this.drawTimer = null;
18262
+ this.draw();
18263
+ }, delay);
18264
+ this.drawTimer.unref?.();
18265
+ }
18266
+ draw() {
18267
+ if (this.drawTimer) {
18268
+ clearTimeout(this.drawTimer);
18269
+ this.drawTimer = null;
18270
+ }
18271
+ this.lastDrawAt = Date.now();
18272
+ this.write("\x1B[H" + this.frame());
18273
+ }
18274
+ };
18275
+ }
18276
+ });
18277
+
18278
+ // cli/harness/state.ts
18279
+ import { mkdirSync as mkdirSync24, readFileSync as readFileSync34, writeFileSync as writeFileSync28 } from "fs";
18280
+ import { homedir as homedir39 } from "os";
18281
+ import { dirname as dirname13, join as join38 } from "path";
18282
+ function load(file = STATE_FILE) {
17376
18283
  try {
17377
- email = String(getUserInfo().email || "");
18284
+ const value = JSON.parse(readFileSync34(file, "utf8"));
18285
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
17378
18286
  } catch {
17379
- return { email: "", needsLogin: false };
18287
+ return {};
17380
18288
  }
17381
- let needsLogin = false;
18289
+ }
18290
+ function loadSessionId(harness, cwd, file = STATE_FILE) {
18291
+ return load(file)[key(harness, cwd)] || "";
18292
+ }
18293
+ function saveSessionId(harness, cwd, sessionId, file = STATE_FILE) {
18294
+ if (!sessionId) return;
17382
18295
  try {
17383
- needsLogin = isTokenExpired() && !loadCredentials()?.refresh_token;
18296
+ mkdirSync24(dirname13(file), { recursive: true });
18297
+ writeFileSync28(file, JSON.stringify({ ...load(file), [key(harness, cwd)]: sessionId }, null, 2));
17384
18298
  } catch {
17385
18299
  }
17386
- return { email, needsLogin };
17387
18300
  }
17388
- function readIdentity(harness) {
17389
- const synkro = synkroAccount();
18301
+ var STATE_FILE, key;
18302
+ var init_state = __esm({
18303
+ "cli/harness/state.ts"() {
18304
+ "use strict";
18305
+ STATE_FILE = join38(homedir39(), ".synkro", "terminal-sessions.json");
18306
+ key = (harness, cwd) => harness + ":" + cwd.replace(/\/+$/, "");
18307
+ }
18308
+ });
18309
+
18310
+ // cli/harness/fixPoll.ts
18311
+ function pollBaseUrl() {
18312
+ const port = String(process.env.SYNKRO_MCP_PORT || "18931");
18313
+ return "http://127.0.0.1:" + port + "/api/local/fix-poll";
18314
+ }
18315
+ function neutralizeTerminalControls(value) {
18316
+ return value.replace(/[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u001B]/g, "");
18317
+ }
18318
+ async function loadFixPoll(itemId, fetchImpl = fetch) {
18319
+ const response = await fetchImpl(pollBaseUrl() + "?item_id=" + encodeURIComponent(itemId));
18320
+ if (!response.ok) return null;
18321
+ const body = await response.json();
18322
+ const candidates = Array.isArray(body.candidates) ? body.candidates.filter((candidate) => typeof candidate === "string").slice(0, 8) : [];
18323
+ if (!candidates.length || body.status !== "pending") return null;
17390
18324
  return {
17391
- harness: harness === "cursor" ? cursorAccount() : "",
17392
- synkro: synkro.email,
17393
- needsLogin: synkro.needsLogin
18325
+ itemId,
18326
+ filePath: String(body.file_path || ""),
18327
+ ruleId: String(body.rule_id || ""),
18328
+ candidates
17394
18329
  };
17395
18330
  }
17396
- function identityHeader(opts) {
17397
- const lines = ["", S2.bold + " " + shortenPath(opts.cwd, opts.home) + S2.reset];
17398
- const label = (name, value, note = "") => S2.dim + " " + name.padEnd(7) + S2.reset + S2.think + value + S2.reset + note;
17399
- if (opts.identity.harness) lines.push(label(opts.harness, opts.identity.harness));
17400
- if (opts.identity.synkro) {
17401
- lines.push(label("synkro", opts.identity.synkro, opts.identity.needsLogin ? S2.blocked + " \xB7 session ended, run synkro login" + S2.reset : ""));
17402
- }
17403
- lines.push("");
17404
- return lines;
18331
+ async function recordFixPoll(itemId, chosenIdx, fetchImpl = fetch) {
18332
+ const response = await fetchImpl(pollBaseUrl() + "/record", {
18333
+ method: "POST",
18334
+ headers: { "content-type": "application/json" },
18335
+ body: JSON.stringify({ item_id: itemId, chosen_idx: chosenIdx })
18336
+ });
18337
+ return response.ok;
17405
18338
  }
17406
- var init_identity2 = __esm({
17407
- "cli/harness/identity.ts"() {
18339
+ function fixPollLines(poll, selected, width = 100) {
18340
+ const max = Math.max(32, width - 10);
18341
+ const choices = [...poll.candidates, "None of the above"];
18342
+ const scope = [poll.ruleId, poll.filePath].filter(Boolean).join(" \xB7 ");
18343
+ const lines2 = [
18344
+ "",
18345
+ S2.bold + S2.rule + " Synkro needs your decision" + S2.reset,
18346
+ ...scope ? [S2.dim + " " + neutralizeTerminalControls(scope) + S2.reset] : [],
18347
+ ""
18348
+ ];
18349
+ choices.forEach((choice, index) => {
18350
+ const prefix = index === selected ? S2.user + " \u276F " : S2.dim + " ";
18351
+ const text = wrap(index + 1 + ". " + neutralizeTerminalControls(choice), max);
18352
+ lines2.push(prefix + (text[0] || "") + S2.reset);
18353
+ for (const continuation of text.slice(1)) lines2.push(" " + continuation);
18354
+ });
18355
+ lines2.push("", S2.dim + " \u2191/\u2193 select \xB7 Enter confirm \xB7 1-" + choices.length + " choose" + S2.reset);
18356
+ return lines2;
18357
+ }
18358
+ async function pickFixPoll(poll, io = {}) {
18359
+ const input = io.input || process.stdin;
18360
+ const output = io.output || process.stdout;
18361
+ if (!input.isTTY || !output.isTTY) return -1;
18362
+ let selected = 0;
18363
+ let rendered = 0;
18364
+ const draw = () => {
18365
+ if (rendered) output.write("\x1B[" + rendered + "A\x1B[J");
18366
+ const lines2 = fixPollLines(poll, selected, Number(output.columns || 100));
18367
+ output.write(lines2.join("\n") + "\n");
18368
+ rendered = lines2.length;
18369
+ };
18370
+ const choice = await new Promise((resolve9) => {
18371
+ const choices = poll.candidates.length + 1;
18372
+ const wasRaw = Boolean(input.isRaw);
18373
+ const done = (index) => {
18374
+ input.off("data", onData);
18375
+ if (input.setRawMode) input.setRawMode(wasRaw);
18376
+ resolve9(index === poll.candidates.length ? -1 : index);
18377
+ };
18378
+ const onData = (chunk) => {
18379
+ const key2 = chunk.toString("utf8");
18380
+ if (key2 === "") return done(poll.candidates.length);
18381
+ if (key2 === "\x1B" || key2.toLowerCase() === "n") return done(poll.candidates.length);
18382
+ if (key2 === "\r" || key2 === "\n") return done(selected);
18383
+ if (key2 === "\x1B[A" || key2 === "k") selected = (selected - 1 + choices) % choices;
18384
+ else if (key2 === "\x1B[B" || key2 === "j") selected = (selected + 1) % choices;
18385
+ else if (/^[1-9]$/.test(key2)) {
18386
+ const index = Number(key2) - 1;
18387
+ if (index < choices) return done(index);
18388
+ } else return;
18389
+ draw();
18390
+ };
18391
+ if (input.setRawMode) input.setRawMode(true);
18392
+ input.resume();
18393
+ input.on("data", onData);
18394
+ draw();
18395
+ });
18396
+ return choice;
18397
+ }
18398
+ async function resolveFixPolls(events, io = {}) {
18399
+ const ids = Array.from(new Set(events.filter((event) => event.type === "tool-end").map((event) => event.pollId || "").filter(Boolean)));
18400
+ const recorded = [];
18401
+ for (const itemId of ids) {
18402
+ const poll = await loadFixPoll(itemId, io.fetchImpl).catch(() => null);
18403
+ if (!poll) continue;
18404
+ const chosen = await pickFixPoll(poll, io);
18405
+ const ok = await recordFixPoll(itemId, chosen, io.fetchImpl).catch(() => false);
18406
+ if (ok) recorded.push(chosen);
18407
+ else (io.output || process.stdout).write(S2.blocked + " Could not record that Synkro decision.\n" + S2.reset);
18408
+ }
18409
+ return recorded;
18410
+ }
18411
+ var init_fixPoll = __esm({
18412
+ "cli/harness/fixPoll.ts"() {
17408
18413
  "use strict";
17409
- init_auth();
17410
18414
  init_render2();
17411
18415
  }
17412
18416
  });
17413
18417
 
17414
18418
  // cli/harness/session.ts
17415
- import { createInterface as createInterface5 } from "readline";
18419
+ function deleteSelectedText(value, range) {
18420
+ if (!range) return value.slice(0, -1);
18421
+ return value.slice(0, range[0]) + value.slice(range[1]);
18422
+ }
18423
+ function handleScrollInput(screen, data) {
18424
+ let handled = false;
18425
+ const mousePattern = /\x1b\[<(\d+);\d+;\d+[Mm]/g;
18426
+ let mouse;
18427
+ while ((mouse = mousePattern.exec(data)) !== null) {
18428
+ const button = Number(mouse[1]);
18429
+ if (button === 64 || button === 65) {
18430
+ screen.scrollBy(button === 64 ? 3 : -3);
18431
+ handled = true;
18432
+ }
18433
+ }
18434
+ if (data.includes("\x1B[5~")) {
18435
+ screen.scrollBy(10);
18436
+ handled = true;
18437
+ }
18438
+ if (data.includes("\x1B[6~")) {
18439
+ screen.scrollBy(-10);
18440
+ handled = true;
18441
+ }
18442
+ return handled;
18443
+ }
18444
+ function printEvent(event) {
18445
+ const lines2 = renderEvent(event, Number(process.stdout.columns || 100));
18446
+ if (lines2.length) process.stdout.write(lines2.join("\n") + "\n");
18447
+ }
17416
18448
  async function runOnce(harness, cwd, prompt, echoPrompt = true, showHeader = true, signal) {
17417
- if (harness !== "cursor") {
18449
+ if (!supported(harness)) {
17418
18450
  process.stdout.write(S2.dim + " " + harness + " sessions are not embedded yet\n" + S2.reset);
17419
18451
  return 1;
17420
18452
  }
17421
- const result = await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal });
17422
- if (result.blocked > 0) {
17423
- process.stdout.write(
17424
- S2.blocked + " " + result.blocked + " action" + (result.blocked === 1 ? "" : "s") + " blocked by policy" + S2.reset + "\n"
17425
- );
18453
+ const resumeId = loadSessionId(harness, cwd);
18454
+ if (harness === "cursor") {
18455
+ const result = await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal, resumeId });
18456
+ saveSessionId(harness, cwd, result.sessionId || "");
18457
+ await resolveFixPolls(result.events);
18458
+ return result.exitCode;
18459
+ }
18460
+ const session = new CodexSession(cwd, resumeId);
18461
+ const events = [];
18462
+ try {
18463
+ await session.start();
18464
+ const result = await session.runTurn(prompt, (event) => {
18465
+ events.push(event);
18466
+ printEvent(event);
18467
+ }, signal);
18468
+ saveSessionId(harness, cwd, result.sessionId);
18469
+ await resolveFixPolls(events);
18470
+ return result.exitCode;
18471
+ } finally {
18472
+ session.close();
17426
18473
  }
17427
- return result.exitCode;
17428
18474
  }
17429
- async function runGovernedSession(harness, cwd, prompt) {
17430
- if (prompt) return runOnce(harness, cwd, prompt);
17431
- process.stdout.write(identityHeader({ cwd, harness, identity: readIdentity(harness) }).join("\n") + "\n");
17432
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
17433
- const ask3 = () => new Promise((resolve7) => {
17434
- const onClose = () => resolve7(null);
17435
- rl.once("close", onClose);
17436
- rl.question(S2.user + " \u276F " + S2.reset, (answer) => {
17437
- rl.removeListener("close", onClose);
17438
- resolve7(answer);
17439
- });
18475
+ function readPrompt(screen) {
18476
+ return new Promise((resolve9) => {
18477
+ let value = "";
18478
+ const finish = (answer) => {
18479
+ process.stdin.removeListener("data", onData);
18480
+ screen.setInput("");
18481
+ resolve9(answer);
18482
+ };
18483
+ const onData = (chunk) => {
18484
+ const data = chunk.toString("utf8");
18485
+ const scrolled = handleScrollInput(screen, data);
18486
+ const mousePattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
18487
+ let mouse;
18488
+ let sawMouse = false;
18489
+ while ((mouse = mousePattern.exec(data)) !== null) {
18490
+ sawMouse = true;
18491
+ const button = Number(mouse[1]);
18492
+ if (button === 0 || button === 32) {
18493
+ screen.selectInputAt(Number(mouse[2]), Number(mouse[3]), button === 32 || mouse[4] === "m");
18494
+ }
18495
+ }
18496
+ if (sawMouse || scrolled) return;
18497
+ const selection = screen.selectionRange();
18498
+ if (data === "\x1B[3~") {
18499
+ value = deleteSelectedText(value, selection);
18500
+ screen.setInput(value);
18501
+ return;
18502
+ }
18503
+ if (data === "\x1B") {
18504
+ if (selection) {
18505
+ value = deleteSelectedText(value, selection);
18506
+ screen.setInput(value);
18507
+ } else finish(null);
18508
+ return;
18509
+ }
18510
+ if (data.startsWith("\x1B")) return;
18511
+ for (const char of data) {
18512
+ if (char === "" || char === "") {
18513
+ finish(null);
18514
+ return;
18515
+ }
18516
+ if (char === "\r" || char === "\n") {
18517
+ finish(value);
18518
+ return;
18519
+ }
18520
+ if (char === "\x7F" || char === "\b") value = deleteSelectedText(value, screen.selectionRange());
18521
+ else if (char === "") value = "";
18522
+ else if (char === "") value = value.replace(/\s*\S+\s*$/, "");
18523
+ else if (char >= " ") {
18524
+ const range = screen.selectionRange();
18525
+ value = range ? value.slice(0, range[0]) + char + value.slice(range[1]) : value + char;
18526
+ }
18527
+ screen.setInput(value);
18528
+ }
18529
+ };
18530
+ process.stdin.on("data", onData);
18531
+ screen.setInput(value);
17440
18532
  });
17441
- let turn = null;
17442
- rl.on("SIGINT", () => {
17443
- if (turn) {
17444
- turn.abort();
17445
- return;
18533
+ }
18534
+ async function cancellable(screen, run2) {
18535
+ const controller = new AbortController();
18536
+ const onData = (chunk) => {
18537
+ const text = chunk.toString("utf8");
18538
+ if (handleScrollInput(screen, text)) return;
18539
+ if (text.includes("") || text === "\x1B") {
18540
+ screen.setStatus("Interrupting\u2026");
18541
+ controller.abort();
17446
18542
  }
17447
- process.stdout.write("\n");
17448
- rl.close();
17449
- });
17450
- let first = true;
17451
- for (; ; ) {
17452
- const answer = await ask3();
17453
- if (answer === null) break;
17454
- const line = answer.trim();
17455
- if (!line) continue;
17456
- if (line === "exit" || line === "quit") break;
17457
- turn = new AbortController();
17458
- await runOnce(harness, cwd, line, false, first, turn.signal);
17459
- turn = null;
17460
- first = false;
18543
+ };
18544
+ process.stdin.on("data", onData);
18545
+ try {
18546
+ return await run2(controller.signal);
18547
+ } finally {
18548
+ process.stdin.removeListener("data", onData);
18549
+ }
18550
+ }
18551
+ async function resolveScreenFixPolls(screen, events) {
18552
+ if (!events.some((event) => event.type === "tool-end" && Boolean(event.pollId))) return;
18553
+ screen.leave();
18554
+ try {
18555
+ await resolveFixPolls(events);
18556
+ } finally {
18557
+ screen.enter();
18558
+ }
18559
+ }
18560
+ async function runSynkroSession(harness, cwd, prompt) {
18561
+ if (prompt) return runOnce(harness, cwd, prompt);
18562
+ if (!supported(harness)) return runOnce(harness, cwd, "");
18563
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
18564
+ process.stderr.write("synkro terminal requires a TTY\n");
18565
+ return 1;
18566
+ }
18567
+ const screen = new SynkroScreen(harness, cwd);
18568
+ const resumeId = loadSessionId(harness, cwd);
18569
+ let codex = null;
18570
+ process.stdin.setRawMode(true);
18571
+ process.stdin.resume();
18572
+ screen.enter();
18573
+ try {
18574
+ if (harness === "codex") {
18575
+ screen.setStatus("Connecting to Codex\u2026");
18576
+ codex = new CodexSession(cwd, resumeId);
18577
+ await codex.start();
18578
+ saveSessionId(harness, cwd, codex.threadId);
18579
+ screen.add({ type: "session-start", sessionId: codex.threadId, model: codex.model || "Codex", cwd, authSource: "login" });
18580
+ screen.setStatus("Ready");
18581
+ }
18582
+ for (; ; ) {
18583
+ const answer = await readPrompt(screen);
18584
+ if (answer === null) break;
18585
+ const line = answer.trim();
18586
+ if (!line) continue;
18587
+ if (line === "/exit" || line === "/quit" || line === "exit" || line === "quit") break;
18588
+ screen.setStatus("Working\u2026");
18589
+ const events = [];
18590
+ try {
18591
+ if (harness === "cursor") {
18592
+ const result = await cancellable(screen, (signal) => runCursorTurn({
18593
+ prompt: line,
18594
+ cwd,
18595
+ signal,
18596
+ resumeId: loadSessionId(harness, cwd),
18597
+ write: () => {
18598
+ },
18599
+ onEvent: (event) => {
18600
+ events.push(event);
18601
+ screen.add(event);
18602
+ },
18603
+ showHeader: false
18604
+ }));
18605
+ saveSessionId(harness, cwd, result.sessionId || "");
18606
+ } else {
18607
+ const result = await cancellable(screen, (signal) => codex.runTurn(line, (event) => {
18608
+ events.push(event);
18609
+ screen.add(event);
18610
+ }, signal));
18611
+ saveSessionId(harness, cwd, result.sessionId);
18612
+ }
18613
+ await resolveScreenFixPolls(screen, events);
18614
+ } catch (error) {
18615
+ screen.add({ type: "notice", text: error instanceof Error ? error.message : String(error) });
18616
+ screen.add({ type: "turn-end", ok: false, text: "" });
18617
+ }
18618
+ screen.setStatus("Ready");
18619
+ }
18620
+ return 0;
18621
+ } finally {
18622
+ codex?.close();
18623
+ screen.leave();
18624
+ process.stdin.setRawMode(false);
18625
+ process.stdin.pause();
17461
18626
  }
17462
- rl.close();
17463
- return 0;
17464
18627
  }
18628
+ var supported;
17465
18629
  var init_session = __esm({
17466
18630
  "cli/harness/session.ts"() {
17467
18631
  "use strict";
18632
+ init_codex();
17468
18633
  init_run();
17469
- init_identity2();
17470
18634
  init_render2();
18635
+ init_screen();
18636
+ init_state();
18637
+ init_fixPoll();
18638
+ supported = (harness) => harness === "cursor" || harness === "codex";
17471
18639
  }
17472
18640
  });
17473
18641
 
@@ -17489,7 +18657,7 @@ async function takeover(kind, cwd) {
17489
18657
  const harness = ["claude", "codex", "cursor"].includes(kind) ? kind : "claude";
17490
18658
  const info = await detectContainerBackend();
17491
18659
  const reachable = info.backend === "container" ? (await run(info.runner, ["test", "-d", cwd])).ok : false;
17492
- const backend = reachable ? "container" : "host";
18660
+ const backend = harness === "codex" ? "host" : reachable ? "container" : "host";
17493
18661
  const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
17494
18662
  const spawned = await spawnAgent(info, {
17495
18663
  name: spaceName + "-" + harness + "-" + String(process.pid % 1e4),
@@ -17508,17 +18676,19 @@ async function takeover(kind, cwd) {
17508
18676
  const runner = backend === "container" ? info.runner : HOST3;
17509
18677
  return runInherit(["env", "TMUX=", ...runnerInteractiveArgs(runner, ["tmux", "attach-session", "-t", spawned.session])]);
17510
18678
  }
17511
- async function restoreSession(session) {
17512
- const record = loadRecords().find((row2) => row2.session === session);
17513
- if (!record) return;
18679
+ async function restoreSession(session, bootPath) {
18680
+ const record2 = loadRecords().find((row2) => row2.session === session);
18681
+ if (!record2) return;
17514
18682
  const info = await detectContainerBackend();
17515
18683
  await spawnAgent(info, {
17516
- name: record.name,
17517
- harness: record.harness,
17518
- spaceName: record.spaceName,
17519
- cwd: record.space,
17520
- backend: record.backend,
17521
- resume: true
18684
+ name: record2.name,
18685
+ harness: record2.harness,
18686
+ spaceName: record2.spaceName,
18687
+ cwd: record2.space,
18688
+ backend: record2.mode === "embedded" ? "host" : record2.backend,
18689
+ resume: record2.mode !== "embedded",
18690
+ mode: record2.mode,
18691
+ command: record2.mode === "embedded" ? embeddedSessionCommand(bootPath, record2.harness, record2.space) : void 0
17522
18692
  });
17523
18693
  }
17524
18694
  async function printStatus() {
@@ -17571,7 +18741,7 @@ async function uiCommand(args2) {
17571
18741
  const harness = args2[runAt + 1] || "cursor";
17572
18742
  const cwd = args2[runAt + 2] || repoRoot();
17573
18743
  const prompt = args2.slice(runAt + 3).join(" ").trim();
17574
- process.exitCode = await runGovernedSession(harness, cwd, prompt);
18744
+ process.exitCode = await runSynkroSession(harness, cwd, prompt);
17575
18745
  return;
17576
18746
  }
17577
18747
  const takeoverAt = args2.indexOf("--takeover");
@@ -17581,7 +18751,7 @@ async function uiCommand(args2) {
17581
18751
  }
17582
18752
  const restoreAt = args2.indexOf("--restore");
17583
18753
  if (restoreAt !== -1) {
17584
- await restoreSession(args2[restoreAt + 1] || "");
18754
+ await restoreSession(args2[restoreAt + 1] || "", bootPath);
17585
18755
  return;
17586
18756
  }
17587
18757
  if (args2.includes("--status")) {
@@ -17659,12 +18829,12 @@ __export(linear_exports, {
17659
18829
  formatLinks: () => formatLinks,
17660
18830
  linearCommand: () => linearCommand
17661
18831
  });
17662
- import { readFileSync as readFileSync33 } from "fs";
17663
- import { homedir as homedir39 } from "os";
17664
- import { join as join37 } from "path";
18832
+ import { readFileSync as readFileSync35 } from "fs";
18833
+ import { homedir as homedir40 } from "os";
18834
+ import { join as join39 } from "path";
17665
18835
  function mcpJwt() {
17666
18836
  try {
17667
- return readFileSync33(join37(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
18837
+ return readFileSync35(join39(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
17668
18838
  } catch {
17669
18839
  return "";
17670
18840
  }
@@ -17703,7 +18873,7 @@ var SYNKRO_DIR14, PORT2, BASE;
17703
18873
  var init_linear = __esm({
17704
18874
  "cli/commands/linear.ts"() {
17705
18875
  "use strict";
17706
- SYNKRO_DIR14 = join37(homedir39(), ".synkro");
18876
+ SYNKRO_DIR14 = join39(homedir40(), ".synkro");
17707
18877
  PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
17708
18878
  BASE = `http://127.0.0.1:${PORT2}`;
17709
18879
  }
@@ -17711,13 +18881,13 @@ var init_linear = __esm({
17711
18881
 
17712
18882
  // cli/scanning/cveReachability.ts
17713
18883
  import { parse } from "@babel/parser";
17714
- import { readFileSync as readFileSync34 } from "fs";
18884
+ import { readFileSync as readFileSync36 } from "fs";
17715
18885
  function walk(node, visit) {
17716
18886
  if (!node || typeof node.type !== "string") return;
17717
18887
  visit(node);
17718
- for (const key of Object.keys(node)) {
17719
- if (key === "loc" || key === "start" || key === "end" || key === "range" || key === "leadingComments" || key === "trailingComments") continue;
17720
- const child = node[key];
18888
+ for (const key2 of Object.keys(node)) {
18889
+ if (key2 === "loc" || key2 === "start" || key2 === "end" || key2 === "range" || key2 === "leadingComments" || key2 === "trailingComments") continue;
18890
+ const child = node[key2];
17721
18891
  if (Array.isArray(child)) {
17722
18892
  for (const c of child) if (c && typeof c.type === "string") walk(c, visit);
17723
18893
  } else if (child && typeof child.type === "string") walk(child, visit);
@@ -17852,10 +19022,10 @@ var init_cveReachability = __esm({
17852
19022
  });
17853
19023
 
17854
19024
  // cli/reachability/reachabilityScan.ts
17855
- import { spawnSync as spawnSync12, execFileSync as execFileSync6 } from "child_process";
17856
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync27, existsSync as existsSync38, readdirSync as readdirSync10 } from "fs";
17857
- import { join as join38 } from "path";
17858
- import { homedir as homedir40 } from "os";
19025
+ import { spawnSync as spawnSync13, execFileSync as execFileSync6 } from "child_process";
19026
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync29, existsSync as existsSync39, readdirSync as readdirSync10 } from "fs";
19027
+ import { join as join40 } from "path";
19028
+ import { homedir as homedir41 } from "os";
17859
19029
  import { createRequire } from "module";
17860
19030
  function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
17861
19031
  const SKIP2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
@@ -17872,7 +19042,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
17872
19042
  }
17873
19043
  for (const e of ents) {
17874
19044
  if (files.length >= maxFiles) break;
17875
- const full = join38(dir, e.name);
19045
+ const full = join40(dir, e.name);
17876
19046
  if (e.isDirectory()) {
17877
19047
  if (!SKIP2.has(e.name) && !e.name.startsWith(".")) stack.push(full);
17878
19048
  continue;
@@ -17880,7 +19050,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
17880
19050
  if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
17881
19051
  const rel = full.startsWith(repoRoot3 + "/") ? full.slice(repoRoot3.length + 1) : full;
17882
19052
  try {
17883
- const content = readFileSync35(full, "utf8");
19053
+ const content = readFileSync37(full, "utf8");
17884
19054
  if (content.length <= maxBytes) files.push({ path: rel, content });
17885
19055
  } catch {
17886
19056
  }
@@ -17899,12 +19069,12 @@ function cleanVersion(spec) {
17899
19069
  function gatherManifestVersions(repoRoot3) {
17900
19070
  const out = {};
17901
19071
  const dirs = [repoRoot3];
17902
- const pkgsDir = join38(repoRoot3, "packages");
17903
- if (existsSync38(pkgsDir)) {
19072
+ const pkgsDir = join40(repoRoot3, "packages");
19073
+ if (existsSync39(pkgsDir)) {
17904
19074
  try {
17905
19075
  for (const d of readdirSync10(pkgsDir)) {
17906
- const pd = join38(pkgsDir, d);
17907
- if (existsSync38(join38(pd, "package.json"))) dirs.push(pd);
19076
+ const pd = join40(pkgsDir, d);
19077
+ if (existsSync39(join40(pd, "package.json"))) dirs.push(pd);
17908
19078
  }
17909
19079
  } catch {
17910
19080
  }
@@ -17913,7 +19083,7 @@ function gatherManifestVersions(repoRoot3) {
17913
19083
  for (const dir of dirs) {
17914
19084
  let pkg;
17915
19085
  try {
17916
- pkg = JSON.parse(readFileSync35(join38(dir, "package.json"), "utf8"));
19086
+ pkg = JSON.parse(readFileSync37(join40(dir, "package.json"), "utf8"));
17917
19087
  } catch {
17918
19088
  continue;
17919
19089
  }
@@ -17933,28 +19103,28 @@ function findJelly(repoRoot3) {
17933
19103
  try {
17934
19104
  const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
17935
19105
  const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
17936
- const pkg = JSON.parse(readFileSync35(pkgJson, "utf8"));
19106
+ const pkg = JSON.parse(readFileSync37(pkgJson, "utf8"));
17937
19107
  const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
17938
19108
  if (bin) {
17939
- const p = join38(dir, bin);
17940
- if (existsSync38(p)) return p;
19109
+ const p = join40(dir, bin);
19110
+ if (existsSync39(p)) return p;
17941
19111
  }
17942
19112
  } catch {
17943
19113
  }
17944
19114
  for (const base of [repoRoot3, process.cwd()]) {
17945
- const b = join38(base, "node_modules", ".bin", "jelly");
17946
- if (existsSync38(b)) return b;
19115
+ const b = join40(base, "node_modules", ".bin", "jelly");
19116
+ if (existsSync39(b)) return b;
17947
19117
  }
17948
19118
  return null;
17949
19119
  }
17950
19120
  function findEntries(repoRoot3) {
17951
19121
  const dirs = [repoRoot3];
17952
- const pkgsDir = join38(repoRoot3, "packages");
17953
- if (existsSync38(pkgsDir)) {
19122
+ const pkgsDir = join40(repoRoot3, "packages");
19123
+ if (existsSync39(pkgsDir)) {
17954
19124
  try {
17955
19125
  for (const d of readdirSync10(pkgsDir)) {
17956
- const pd = join38(pkgsDir, d);
17957
- if (existsSync38(join38(pd, "package.json"))) dirs.push(pd);
19126
+ const pd = join40(pkgsDir, d);
19127
+ if (existsSync39(join40(pd, "package.json"))) dirs.push(pd);
17958
19128
  }
17959
19129
  } catch {
17960
19130
  }
@@ -17962,12 +19132,12 @@ function findEntries(repoRoot3) {
17962
19132
  const entries = [];
17963
19133
  for (const dir of dirs) {
17964
19134
  try {
17965
- const pkg = JSON.parse(readFileSync35(join38(dir, "package.json"), "utf8"));
19135
+ const pkg = JSON.parse(readFileSync37(join40(dir, "package.json"), "utf8"));
17966
19136
  const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
17967
19137
  for (const c of cands) {
17968
19138
  if (typeof c !== "string") continue;
17969
- const f = join38(dir, c);
17970
- if (existsSync38(f)) {
19139
+ const f = join40(dir, c);
19140
+ if (existsSync39(f)) {
17971
19141
  entries.push(f);
17972
19142
  break;
17973
19143
  }
@@ -18000,9 +19170,9 @@ function parseApiUsage(log) {
18000
19170
  }
18001
19171
  function runReachabilityScan(repoRoot3, opts = {}) {
18002
19172
  const commit = currentCommit(repoRoot3);
18003
- if (!opts.force && commit && existsSync38(REACHABILITY_PATH)) {
19173
+ if (!opts.force && commit && existsSync39(REACHABILITY_PATH)) {
18004
19174
  try {
18005
- const prev = JSON.parse(readFileSync35(REACHABILITY_PATH, "utf8"));
19175
+ const prev = JSON.parse(readFileSync37(REACHABILITY_PATH, "utf8"));
18006
19176
  if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
18007
19177
  } catch {
18008
19178
  }
@@ -18053,7 +19223,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
18053
19223
  if (jelly) {
18054
19224
  const entries = findEntries(repoRoot3);
18055
19225
  if (entries.length > 0) {
18056
- const r = spawnSync12(
19226
+ const r = spawnSync13(
18057
19227
  process.execPath,
18058
19228
  [jelly, "-b", repoRoot3, "--api-usage", ...entries],
18059
19229
  { encoding: "utf8", timeout: opts.timeoutMs ?? 18e4, maxBuffer: 2e8 }
@@ -18091,7 +19261,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
18091
19261
  if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
18092
19262
  const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot3) };
18093
19263
  try {
18094
- writeFileSync27(REACHABILITY_PATH, JSON.stringify(file, null, 2));
19264
+ writeFileSync29(REACHABILITY_PATH, JSON.stringify(file, null, 2));
18095
19265
  } catch (e) {
18096
19266
  return { ok: false, reason: "write failed: " + String(e.message || e) };
18097
19267
  }
@@ -18103,7 +19273,7 @@ var init_reachabilityScan = __esm({
18103
19273
  "use strict";
18104
19274
  init_cveReachability();
18105
19275
  require2 = createRequire(import.meta.url);
18106
- REACHABILITY_PATH = join38(homedir40(), ".synkro", "reachability.json");
19276
+ REACHABILITY_PATH = join40(homedir41(), ".synkro", "reachability.json");
18107
19277
  }
18108
19278
  });
18109
19279
 
@@ -18112,15 +19282,15 @@ var reachabilityScan_exports = {};
18112
19282
  __export(reachabilityScan_exports, {
18113
19283
  reachabilityScanCommand: () => reachabilityScanCommand
18114
19284
  });
18115
- import { readFileSync as readFileSync36, existsSync as existsSync39 } from "fs";
18116
- import { join as join39 } from "path";
18117
- import { homedir as homedir41 } from "os";
19285
+ import { readFileSync as readFileSync38, existsSync as existsSync40 } from "fs";
19286
+ import { join as join41 } from "path";
19287
+ import { homedir as homedir42 } from "os";
18118
19288
  import { execFileSync as execFileSync7 } from "child_process";
18119
19289
  function readConfigEnv4() {
18120
- const p = join39(SYNKRO_DIR15, "config.env");
18121
- if (!existsSync39(p)) return {};
19290
+ const p = join41(SYNKRO_DIR15, "config.env");
19291
+ if (!existsSync40(p)) return {};
18122
19292
  const out = {};
18123
- for (const line of readFileSync36(p, "utf-8").split("\n")) {
19293
+ for (const line of readFileSync38(p, "utf-8").split("\n")) {
18124
19294
  const t = line.trim();
18125
19295
  if (!t || t.startsWith("#")) continue;
18126
19296
  const eq = t.indexOf("=");
@@ -18152,11 +19322,11 @@ async function pushToCloud(cfg, repo) {
18152
19322
  while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
18153
19323
  let jwt2 = "";
18154
19324
  try {
18155
- jwt2 = readFileSync36(join39(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
19325
+ jwt2 = readFileSync38(join41(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
18156
19326
  } catch {
18157
19327
  }
18158
- if (!jwt2 || !existsSync39(REACHABILITY_PATH)) return;
18159
- const body = readFileSync36(REACHABILITY_PATH, "utf-8");
19328
+ if (!jwt2 || !existsSync40(REACHABILITY_PATH)) return;
19329
+ const body = readFileSync38(REACHABILITY_PATH, "utf-8");
18160
19330
  try {
18161
19331
  const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
18162
19332
  method: "POST",
@@ -18188,7 +19358,7 @@ var init_reachabilityScan2 = __esm({
18188
19358
  "cli/commands/reachabilityScan.ts"() {
18189
19359
  "use strict";
18190
19360
  init_reachabilityScan();
18191
- SYNKRO_DIR15 = join39(homedir41(), ".synkro");
19361
+ SYNKRO_DIR15 = join41(homedir42(), ".synkro");
18192
19362
  }
18193
19363
  });
18194
19364
 
@@ -18318,13 +19488,13 @@ var config_exports = {};
18318
19488
  __export(config_exports, {
18319
19489
  configCommand: () => configCommand
18320
19490
  });
18321
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync28, existsSync as existsSync40 } from "fs";
18322
- import { join as join40 } from "path";
18323
- import { homedir as homedir42 } from "os";
19491
+ import { readFileSync as readFileSync39, writeFileSync as writeFileSync30, existsSync as existsSync41 } from "fs";
19492
+ import { join as join42 } from "path";
19493
+ import { homedir as homedir43 } from "os";
18324
19494
  function readConfigEnv5() {
18325
- if (!existsSync40(CONFIG_PATH9)) return {};
19495
+ if (!existsSync41(CONFIG_PATH9)) return {};
18326
19496
  const out = {};
18327
- for (const line of readFileSync37(CONFIG_PATH9, "utf-8").split("\n")) {
19497
+ for (const line of readFileSync39(CONFIG_PATH9, "utf-8").split("\n")) {
18328
19498
  const t = line.trim();
18329
19499
  if (!t || t.startsWith("#")) continue;
18330
19500
  const eq = t.indexOf("=");
@@ -18332,23 +19502,23 @@ function readConfigEnv5() {
18332
19502
  }
18333
19503
  return out;
18334
19504
  }
18335
- function updateConfigValue(key, value) {
18336
- if (!existsSync40(CONFIG_PATH9)) {
19505
+ function updateConfigValue(key2, value) {
19506
+ if (!existsSync41(CONFIG_PATH9)) {
18337
19507
  console.error("No config found. Run `synkro install` first.");
18338
19508
  process.exit(1);
18339
19509
  }
18340
- const lines = readFileSync37(CONFIG_PATH9, "utf-8").split("\n");
18341
- const pattern = new RegExp(`^${key}=`);
19510
+ const lines2 = readFileSync39(CONFIG_PATH9, "utf-8").split("\n");
19511
+ const pattern = new RegExp(`^${key2}=`);
18342
19512
  let found = false;
18343
- const updated = lines.map((line) => {
19513
+ const updated = lines2.map((line) => {
18344
19514
  if (pattern.test(line.trim())) {
18345
19515
  found = true;
18346
- return `${key}='${value}'`;
19516
+ return `${key2}='${value}'`;
18347
19517
  }
18348
19518
  return line;
18349
19519
  });
18350
- if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
18351
- writeFileSync28(CONFIG_PATH9, updated.join("\n"), "utf-8");
19520
+ if (!found) updated.splice(updated.length - 1, 0, `${key2}='${value}'`);
19521
+ writeFileSync30(CONFIG_PATH9, updated.join("\n"), "utf-8");
18352
19522
  }
18353
19523
  function resolveInferenceMode(cfg) {
18354
19524
  if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
@@ -18506,8 +19676,8 @@ var init_config = __esm({
18506
19676
  "use strict";
18507
19677
  init_stub();
18508
19678
  init_optout();
18509
- SYNKRO_DIR16 = join40(homedir42(), ".synkro");
18510
- CONFIG_PATH9 = join40(SYNKRO_DIR16, "config.env");
19679
+ SYNKRO_DIR16 = join42(homedir43(), ".synkro");
19680
+ CONFIG_PATH9 = join42(SYNKRO_DIR16, "config.env");
18511
19681
  }
18512
19682
  });
18513
19683
 
@@ -18516,7 +19686,7 @@ var telemetry_exports2 = {};
18516
19686
  __export(telemetry_exports2, {
18517
19687
  telemetryCommand: () => telemetryCommand
18518
19688
  });
18519
- import { createInterface as createInterface6 } from "readline";
19689
+ import { createInterface as createInterface7 } from "readline";
18520
19690
  function parseFlag(args2, name) {
18521
19691
  const prefix = `--${name}=`;
18522
19692
  for (const a of args2) if (a.startsWith(prefix)) return a.slice(prefix.length);
@@ -18597,12 +19767,12 @@ async function runExport(args2) {
18597
19767
  }
18598
19768
  function confirmYesNo(question) {
18599
19769
  if (!process.stdin.isTTY) return Promise.resolve(false);
18600
- return new Promise((resolve7) => {
18601
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
19770
+ return new Promise((resolve9) => {
19771
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
18602
19772
  rl.question(`${question} (y/N): `, (answer) => {
18603
19773
  rl.close();
18604
19774
  const t = answer.trim().toLowerCase();
18605
- resolve7(t === "y" || t === "yes");
19775
+ resolve9(t === "y" || t === "yes");
18606
19776
  });
18607
19777
  });
18608
19778
  }
@@ -18697,11 +19867,11 @@ Usage:
18697
19867
 
18698
19868
  // cli/inventory/identity.ts
18699
19869
  import { randomUUID as randomUUID5 } from "crypto";
18700
- import { existsSync as existsSync41, mkdirSync as mkdirSync24, readFileSync as readFileSync38, renameSync as renameSync9, writeFileSync as writeFileSync29 } from "fs";
18701
- import { homedir as homedir43 } from "os";
18702
- import { dirname as dirname12, join as join41 } from "path";
19870
+ import { existsSync as existsSync42, mkdirSync as mkdirSync25, readFileSync as readFileSync40, renameSync as renameSync9, writeFileSync as writeFileSync31 } from "fs";
19871
+ import { homedir as homedir44 } from "os";
19872
+ import { dirname as dirname14, join as join43 } from "path";
18703
19873
  function operationalIdentityPath() {
18704
- return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join41(homedir43(), ".synkro", "installation.json");
19874
+ return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join43(homedir44(), ".synkro", "installation.json");
18705
19875
  }
18706
19876
  function validIdentity(value) {
18707
19877
  if (!value || typeof value !== "object") return false;
@@ -18709,17 +19879,17 @@ function validIdentity(value) {
18709
19879
  return typeof row2.installation_id === "string" && UUID_RE.test(row2.installation_id) && typeof row2.created_at === "string" && Number.isFinite(Date.parse(row2.created_at));
18710
19880
  }
18711
19881
  function writeIdentity(path, identity) {
18712
- mkdirSync24(dirname12(path), { recursive: true, mode: 448 });
19882
+ mkdirSync25(dirname14(path), { recursive: true, mode: 448 });
18713
19883
  const temp = `${path}.${process.pid}.${randomUUID5()}.tmp`;
18714
- writeFileSync29(temp, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf8", mode: 384 });
19884
+ writeFileSync31(temp, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf8", mode: 384 });
18715
19885
  renameSync9(temp, path);
18716
19886
  }
18717
19887
  function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
18718
19888
  const prior = cached4.get(path);
18719
19889
  if (prior) return prior;
18720
- if (existsSync41(path)) {
19890
+ if (existsSync42(path)) {
18721
19891
  try {
18722
- const parsed = JSON.parse(readFileSync38(path, "utf8"));
19892
+ const parsed = JSON.parse(readFileSync40(path, "utf8"));
18723
19893
  if (validIdentity(parsed)) {
18724
19894
  cached4.set(path, parsed);
18725
19895
  return parsed;
@@ -18733,7 +19903,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
18733
19903
  return identity;
18734
19904
  }
18735
19905
  var UUID_RE, cached4;
18736
- var init_identity3 = __esm({
19906
+ var init_identity2 = __esm({
18737
19907
  "cli/inventory/identity.ts"() {
18738
19908
  "use strict";
18739
19909
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -18744,13 +19914,13 @@ var init_identity3 = __esm({
18744
19914
  // cli/inventory/collector.ts
18745
19915
  import { createHash as createHash5 } from "crypto";
18746
19916
  import {
18747
- existsSync as existsSync42,
18748
- readFileSync as readFileSync39,
19917
+ existsSync as existsSync43,
19918
+ readFileSync as readFileSync41,
18749
19919
  readdirSync as readdirSync11,
18750
- statSync as statSync5
19920
+ statSync as statSync7
18751
19921
  } from "fs";
18752
- import { arch, homedir as homedir44, hostname as hostname2, platform as platform6, release as release2 } from "os";
18753
- import { basename as basename3, join as join42, relative, resolve as resolve5 } from "path";
19922
+ import { arch, homedir as homedir45, hostname as hostname2, platform as platform6, release as release2 } from "os";
19923
+ import { basename as basename4, join as join44, relative as relative2, resolve as resolve7 } from "path";
18754
19924
  import { fileURLToPath } from "url";
18755
19925
  function sha256(value) {
18756
19926
  return createHash5("sha256").update(value).digest("hex");
@@ -18760,15 +19930,15 @@ function pseudonymousHostnameHash(installationId, host) {
18760
19930
  }
18761
19931
  function cliVersion() {
18762
19932
  try {
18763
- return "1.10.6";
19933
+ return "1.10.9";
18764
19934
  } catch {
18765
19935
  return "0.0.0";
18766
19936
  }
18767
19937
  }
18768
19938
  function readJson(path) {
18769
19939
  try {
18770
- if (!existsSync42(path)) return null;
18771
- const parsed = JSON.parse(readFileSync39(path, "utf8"));
19940
+ if (!existsSync43(path)) return null;
19941
+ const parsed = JSON.parse(readFileSync41(path, "utf8"));
18772
19942
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
18773
19943
  } catch {
18774
19944
  return null;
@@ -18776,8 +19946,8 @@ function readJson(path) {
18776
19946
  }
18777
19947
  function readText(path) {
18778
19948
  try {
18779
- if (!existsSync42(path)) return "";
18780
- return readFileSync39(path, "utf8");
19949
+ if (!existsSync43(path)) return "";
19950
+ return readFileSync41(path, "utf8");
18781
19951
  } catch {
18782
19952
  return "";
18783
19953
  }
@@ -18797,7 +19967,7 @@ function canonical(raw) {
18797
19967
  }
18798
19968
  function safePackageName(command, args2) {
18799
19969
  if (typeof command !== "string" || !command.trim()) return void 0;
18800
- const runner = basename3(command.trim()).replace(/\.exe$/i, "");
19970
+ const runner = basename4(command.trim()).replace(/\.exe$/i, "");
18801
19971
  if (Array.isArray(args2) && ["npx", "bunx", "uvx"].includes(runner)) {
18802
19972
  const pkg = args2.find((arg) => typeof arg === "string" && !arg.startsWith("-"));
18803
19973
  if (typeof pkg === "string") {
@@ -18805,7 +19975,7 @@ function safePackageName(command, args2) {
18805
19975
  if (/^(?:@[a-z0-9_.-]+\/)?[a-z0-9_.-]+(?:@[a-z0-9_.+~-]+)?$/i.test(candidate)) {
18806
19976
  return candidate;
18807
19977
  }
18808
- return basename3(candidate);
19978
+ return basename4(candidate);
18809
19979
  }
18810
19980
  }
18811
19981
  return runner;
@@ -18863,16 +20033,16 @@ function mcpArtifactsFromJson(harness, config, configScope = "user") {
18863
20033
  }
18864
20034
  function claudeDesktopConfigCandidates(home, targetPlatform) {
18865
20035
  if (targetPlatform === "darwin") {
18866
- return [join42(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
20036
+ return [join44(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
18867
20037
  }
18868
20038
  if (targetPlatform === "linux") {
18869
20039
  return [
18870
- join42(home, ".config", "Claude", "claude_desktop_config.json"),
18871
- join42(home, ".config", "claude", "claude_desktop_config.json")
20040
+ join44(home, ".config", "Claude", "claude_desktop_config.json"),
20041
+ join44(home, ".config", "claude", "claude_desktop_config.json")
18872
20042
  ];
18873
20043
  }
18874
20044
  if (targetPlatform === "win32" && process.env.APPDATA) {
18875
- return [join42(process.env.APPDATA, "Claude", "claude_desktop_config.json")];
20045
+ return [join44(process.env.APPDATA, "Claude", "claude_desktop_config.json")];
18876
20046
  }
18877
20047
  return [];
18878
20048
  }
@@ -18880,7 +20050,7 @@ function claudeManagedMcpConfigCandidates(targetPlatform) {
18880
20050
  if (targetPlatform === "darwin") return ["/Library/Application Support/ClaudeCode/managed-mcp.json"];
18881
20051
  if (targetPlatform === "linux") return ["/etc/claude-code/managed-mcp.json"];
18882
20052
  if (targetPlatform === "win32" && process.env.ProgramFiles) {
18883
- return [join42(process.env.ProgramFiles, "ClaudeCode", "managed-mcp.json")];
20053
+ return [join44(process.env.ProgramFiles, "ClaudeCode", "managed-mcp.json")];
18884
20054
  }
18885
20055
  return [];
18886
20056
  }
@@ -18888,8 +20058,8 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
18888
20058
  const roots = /* @__PURE__ */ new Set();
18889
20059
  const add = (value) => {
18890
20060
  if (typeof value !== "string" || !value.trim()) return;
18891
- const path = resolve5(value);
18892
- if (existsSync42(path)) roots.add(path);
20061
+ const path = resolve7(value);
20062
+ if (existsSync43(path)) roots.add(path);
18893
20063
  };
18894
20064
  add(currentDirectory);
18895
20065
  for (const path of explicit) add(path);
@@ -18900,17 +20070,17 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
18900
20070
  return [...roots];
18901
20071
  }
18902
20072
  function cursorWorkspaceStorageCandidates(home, targetPlatform) {
18903
- if (targetPlatform === "darwin") return [join42(home, "Library", "Application Support", "Cursor", "User", "workspaceStorage")];
18904
- if (targetPlatform === "linux") return [join42(home, ".config", "Cursor", "User", "workspaceStorage")];
20073
+ if (targetPlatform === "darwin") return [join44(home, "Library", "Application Support", "Cursor", "User", "workspaceStorage")];
20074
+ if (targetPlatform === "linux") return [join44(home, ".config", "Cursor", "User", "workspaceStorage")];
18905
20075
  if (targetPlatform === "win32" && process.env.APPDATA) {
18906
- return [join42(process.env.APPDATA, "Cursor", "User", "workspaceStorage")];
20076
+ return [join44(process.env.APPDATA, "Cursor", "User", "workspaceStorage")];
18907
20077
  }
18908
20078
  return [];
18909
20079
  }
18910
20080
  function cursorWorkspaceRoots(home, targetPlatform) {
18911
20081
  const roots = /* @__PURE__ */ new Set();
18912
20082
  for (const storage of cursorWorkspaceStorageCandidates(home, targetPlatform)) {
18913
- if (!existsSync42(storage)) continue;
20083
+ if (!existsSync43(storage)) continue;
18914
20084
  let entries = [];
18915
20085
  try {
18916
20086
  entries = readdirSync11(storage, { withFileTypes: true });
@@ -18919,12 +20089,12 @@ function cursorWorkspaceRoots(home, targetPlatform) {
18919
20089
  }
18920
20090
  for (const entry of entries) {
18921
20091
  if (!entry.isDirectory() || entry.isSymbolicLink?.()) continue;
18922
- const state = readJson(join42(storage, entry.name, "workspace.json"));
20092
+ const state = readJson(join44(storage, entry.name, "workspace.json"));
18923
20093
  const raw = state?.folder;
18924
20094
  if (typeof raw !== "string" || !raw.trim()) continue;
18925
20095
  try {
18926
20096
  const path = raw.startsWith("file:") ? fileURLToPath(raw) : raw;
18927
- if (existsSync42(path)) roots.add(resolve5(path));
20097
+ if (existsSync43(path)) roots.add(resolve7(path));
18928
20098
  } catch {
18929
20099
  }
18930
20100
  }
@@ -18936,8 +20106,8 @@ function codexMcpArtifacts(content) {
18936
20106
  const sections = [...content.matchAll(/^\s*\[\s*([^\]]+)\s*\]\s*$/gm)];
18937
20107
  for (let index = 0; index < sections.length && artifacts.length < 1e3; index++) {
18938
20108
  const section = sections[index];
18939
- const key = section[1].trim();
18940
- const root = key.match(/^mcp_servers\s*\.\s*(?:"((?:[^"\\]|\\.)+)"|'([^']+)'|([A-Za-z0-9_-]+))$/);
20109
+ const key2 = section[1].trim();
20110
+ const root = key2.match(/^mcp_servers\s*\.\s*(?:"((?:[^"\\]|\\.)+)"|'([^']+)'|([A-Za-z0-9_-]+))$/);
18941
20111
  if (!root) continue;
18942
20112
  let name = root[1] || root[2] || root[3];
18943
20113
  if (root[1]) {
@@ -18950,8 +20120,8 @@ function codexMcpArtifacts(content) {
18950
20120
  const start = (section.index ?? 0) + section[0].length;
18951
20121
  const end = sections[index + 1]?.index ?? content.length;
18952
20122
  const block = content.slice(start, end);
18953
- const stringValue = (key2) => {
18954
- const found = block.match(new RegExp(`^\\s*${key2}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")`, "m"));
20123
+ const stringValue = (key3) => {
20124
+ const found = block.match(new RegExp(`^\\s*${key3}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")`, "m"));
18955
20125
  if (!found) return void 0;
18956
20126
  try {
18957
20127
  return JSON.parse(found[1]);
@@ -18983,9 +20153,9 @@ function flattenHookEntries(value) {
18983
20153
  const out = [];
18984
20154
  for (const entry of value) {
18985
20155
  if (!entry || typeof entry !== "object") continue;
18986
- const record = entry;
18987
- if (typeof record.command === "string") out.push(record);
18988
- if (Array.isArray(record.hooks)) out.push(...flattenHookEntries(record.hooks));
20156
+ const record2 = entry;
20157
+ if (typeof record2.command === "string") out.push(record2);
20158
+ if (Array.isArray(record2.hooks)) out.push(...flattenHookEntries(record2.hooks));
18989
20159
  }
18990
20160
  return out;
18991
20161
  }
@@ -19000,10 +20170,10 @@ function hookArtifacts(harness, config) {
19000
20170
  harness,
19001
20171
  type: "hook",
19002
20172
  canonical_id: `${canonical(event)}:${commandHash.slice(0, 20)}`,
19003
- display_name: `${event} \xB7 ${basename3(String(entry.command).split(/\s+/)[0] || "hook")}`,
20173
+ display_name: `${event} \xB7 ${basename4(String(entry.command).split(/\s+/)[0] || "hook")}`,
19004
20174
  enabled: entry.enabled !== false,
19005
20175
  config_scope: "user",
19006
- package_name: basename3(String(entry.command).split(/\s+/)[0] || "") || void 0,
20176
+ package_name: basename4(String(entry.command).split(/\s+/)[0] || "") || void 0,
19007
20177
  config_hash: commandHash,
19008
20178
  metadata: { managed, events: [event] }
19009
20179
  });
@@ -19014,11 +20184,11 @@ function hookArtifacts(harness, config) {
19014
20184
  function parseFrontmatter(content) {
19015
20185
  const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
19016
20186
  if (!match) return {};
19017
- const value = (key) => match[1].match(new RegExp(`^${key}:\\s*["']?([^"'\\n]+)`, "m"))?.[1]?.trim();
20187
+ const value = (key2) => match[1].match(new RegExp(`^${key2}:\\s*["']?([^"'\\n]+)`, "m"))?.[1]?.trim();
19018
20188
  return { name: value("name"), version: value("version") };
19019
20189
  }
19020
20190
  function skillArtifacts(harness, root) {
19021
- if (!existsSync42(root)) return [];
20191
+ if (!existsSync43(root)) return [];
19022
20192
  const manifests = [];
19023
20193
  const visit = (dir) => {
19024
20194
  let entries;
@@ -19029,7 +20199,7 @@ function skillArtifacts(harness, root) {
19029
20199
  }
19030
20200
  for (const entry of entries) {
19031
20201
  if (entry.isSymbolicLink?.()) continue;
19032
- const path = join42(dir, entry.name);
20202
+ const path = join44(dir, entry.name);
19033
20203
  if (entry.isFile() && entry.name === "SKILL.md") manifests.push(path);
19034
20204
  else if (entry.isDirectory()) visit(path);
19035
20205
  }
@@ -19038,8 +20208,8 @@ function skillArtifacts(harness, root) {
19038
20208
  return manifests.map((path) => {
19039
20209
  const content = readText(path);
19040
20210
  const frontmatter = parseFrontmatter(content);
19041
- const rel = relative(root, path).replaceAll("\\", "/");
19042
- const name = frontmatter.name || basename3(join42(path, "..")) || "skill";
20211
+ const rel = relative2(root, path).replaceAll("\\", "/");
20212
+ const name = frontmatter.name || basename4(join44(path, "..")) || "skill";
19043
20213
  return {
19044
20214
  harness,
19045
20215
  type: "skill",
@@ -19054,7 +20224,7 @@ function skillArtifacts(harness, root) {
19054
20224
  });
19055
20225
  }
19056
20226
  function cursorExtensionArtifacts(root) {
19057
- if (!existsSync42(root)) return [];
20227
+ if (!existsSync43(root)) return [];
19058
20228
  let dirs = [];
19059
20229
  try {
19060
20230
  dirs = readdirSync11(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.isSymbolicLink());
@@ -19063,7 +20233,7 @@ function cursorExtensionArtifacts(root) {
19063
20233
  }
19064
20234
  const artifacts = [];
19065
20235
  for (const dir of dirs) {
19066
- const pkg = readJson(join42(root, dir.name, "package.json"));
20236
+ const pkg = readJson(join44(root, dir.name, "package.json"));
19067
20237
  if (!pkg) continue;
19068
20238
  const publisher = typeof pkg.publisher === "string" ? pkg.publisher : void 0;
19069
20239
  const name = typeof pkg.name === "string" ? pkg.name : dir.name;
@@ -19083,21 +20253,21 @@ function cursorExtensionArtifacts(root) {
19083
20253
  return artifacts;
19084
20254
  }
19085
20255
  function deploymentMode2(home) {
19086
- const raw = readText(join42(home, ".synkro", "config.env"));
19087
- const value = (key) => raw.match(new RegExp(`^${key}=['"]?([^'"\\n]*)`, "m"))?.[1]?.toLowerCase();
20256
+ const raw = readText(join44(home, ".synkro", "config.env"));
20257
+ const value = (key2) => raw.match(new RegExp(`^${key2}=['"]?([^'"\\n]*)`, "m"))?.[1]?.toLowerCase();
19088
20258
  if (value("SYNKRO_GRADING_MODE") === "byok") return "byok";
19089
20259
  if (value("SYNKRO_STORAGE_MODE") === "cloud") return "cloud";
19090
20260
  return "local";
19091
20261
  }
19092
20262
  function telemetryHealth(home) {
19093
- const meta = readJson(join42(home, ".synkro", "telemetry-meta.json"));
20263
+ const meta = readJson(join44(home, ".synkro", "telemetry-meta.json"));
19094
20264
  const health = {};
19095
20265
  if (meta?.last_flush_ok_at && Number.isFinite(Date.parse(meta.last_flush_ok_at))) health.telemetry_last_flush_at = meta.last_flush_ok_at;
19096
20266
  if (meta?.last_flush_error) health.telemetry_last_error = "flush_failed";
19097
- const queue = join42(home, ".synkro", "telemetry-pending.jsonl");
20267
+ const queue = join44(home, ".synkro", "telemetry-pending.jsonl");
19098
20268
  try {
19099
- const size = statSync5(queue).size;
19100
- health.telemetry_backlog = size <= 5 * 1024 * 1024 ? readFileSync39(queue, "utf8").split("\n").filter(Boolean).length : Math.ceil(size / 1024);
20269
+ const size = statSync7(queue).size;
20270
+ health.telemetry_backlog = size <= 5 * 1024 * 1024 ? readFileSync41(queue, "utf8").split("\n").filter(Boolean).length : Math.ceil(size / 1024);
19101
20271
  } catch {
19102
20272
  }
19103
20273
  return health;
@@ -19138,7 +20308,7 @@ function harnessSnapshot(agent) {
19138
20308
  }
19139
20309
  const config = readJson(agent.settingsPath);
19140
20310
  const coverage = inspectCodexHooks(agent.settingsPath);
19141
- const toml = readText(join42(agent.configDir, "config.toml"));
20311
+ const toml = readText(join44(agent.configDir, "config.toml"));
19142
20312
  const permission = toml.match(/^\s*approval_policy\s*=\s*["']([^"']+)/m)?.[1];
19143
20313
  return {
19144
20314
  row: {
@@ -19155,11 +20325,11 @@ function harnessSnapshot(agent) {
19155
20325
  };
19156
20326
  }
19157
20327
  function collectOperationalInventory(options = {}) {
19158
- const home = options.homeDir ?? homedir44();
20328
+ const home = options.homeDir ?? homedir45();
19159
20329
  const detected = options.detectedAgents ?? detectAgents();
19160
20330
  const identity = getOperationalInstallationIdentity(options.identityPath);
19161
20331
  const targetPlatform = options.platformName ?? platform6();
19162
- const codexHome = options.homeDir ? join42(home, ".codex") : process.env.CODEX_HOME || join42(home, ".codex");
20332
+ const codexHome = options.homeDir ? join44(home, ".codex") : process.env.CODEX_HOME || join44(home, ".codex");
19163
20333
  const harnesses = [];
19164
20334
  const artifacts = [];
19165
20335
  for (const agent of detected) {
@@ -19167,7 +20337,7 @@ function collectOperationalInventory(options = {}) {
19167
20337
  harnesses.push(row2);
19168
20338
  artifacts.push(...hookArtifacts(row2.harness, config));
19169
20339
  }
19170
- const claudeJson = readJson(join42(home, ".claude.json"));
20340
+ const claudeJson = readJson(join44(home, ".claude.json"));
19171
20341
  artifacts.push(...mcpArtifactsFromJson("claude_code", claudeJson));
19172
20342
  if (claudeJson?.projects && typeof claudeJson.projects === "object") {
19173
20343
  for (const [projectPath, project] of Object.entries(claudeJson.projects)) {
@@ -19175,8 +20345,8 @@ function collectOperationalInventory(options = {}) {
19175
20345
  artifacts.push(...mcpArtifactsFromJson("claude_code", project, `local:${sha256(projectPath).slice(0, 16)}`));
19176
20346
  }
19177
20347
  }
19178
- artifacts.push(...mcpArtifactsFromJson("cursor", readJson(join42(home, ".cursor", "mcp.json"))));
19179
- artifacts.push(...codexMcpArtifacts(readText(join42(codexHome, "config.toml"))));
20348
+ artifacts.push(...mcpArtifactsFromJson("cursor", readJson(join44(home, ".cursor", "mcp.json"))));
20349
+ artifacts.push(...codexMcpArtifacts(readText(join44(codexHome, "config.toml"))));
19180
20350
  const projectRoots = discoveredProjectRoots(
19181
20351
  claudeJson,
19182
20352
  options.currentDirectory ?? process.cwd(),
@@ -19187,11 +20357,11 @@ function collectOperationalInventory(options = {}) {
19187
20357
  const scopeHash = sha256(projectRoot).slice(0, 16);
19188
20358
  artifacts.push(...mcpArtifactsFromJson(
19189
20359
  "claude_code",
19190
- readJson(join42(projectRoot, ".mcp.json")),
20360
+ readJson(join44(projectRoot, ".mcp.json")),
19191
20361
  `project:${scopeHash}`
19192
20362
  ));
19193
- const cursorProjectConfig = join42(projectRoot, ".cursor", "mcp.json");
19194
- if (resolve5(cursorProjectConfig) !== resolve5(join42(home, ".cursor", "mcp.json"))) {
20363
+ const cursorProjectConfig = join44(projectRoot, ".cursor", "mcp.json");
20364
+ if (resolve7(cursorProjectConfig) !== resolve7(join44(home, ".cursor", "mcp.json"))) {
19195
20365
  artifacts.push(...mcpArtifactsFromJson(
19196
20366
  "cursor",
19197
20367
  readJson(cursorProjectConfig),
@@ -19202,7 +20372,7 @@ function collectOperationalInventory(options = {}) {
19202
20372
  for (const managedPath of claudeManagedMcpConfigCandidates(targetPlatform)) {
19203
20373
  artifacts.push(...mcpArtifactsFromJson("claude_code", readJson(managedPath), "managed"));
19204
20374
  }
19205
- const desktopConfigPath = claudeDesktopConfigCandidates(home, targetPlatform).find((path) => existsSync42(path));
20375
+ const desktopConfigPath = claudeDesktopConfigCandidates(home, targetPlatform).find((path) => existsSync43(path));
19206
20376
  if (desktopConfigPath) {
19207
20377
  const desktopConfig = readJson(desktopConfigPath);
19208
20378
  harnesses.push({
@@ -19213,7 +20383,7 @@ function collectOperationalInventory(options = {}) {
19213
20383
  });
19214
20384
  artifacts.push(...mcpArtifactsFromJson("claude_desktop", desktopConfig));
19215
20385
  }
19216
- const claudeSettings = readJson(join42(home, ".claude", "settings.json"));
20386
+ const claudeSettings = readJson(join44(home, ".claude", "settings.json"));
19217
20387
  if (claudeSettings?.enabledPlugins && typeof claudeSettings.enabledPlugins === "object") {
19218
20388
  for (const [name, enabled] of Object.entries(claudeSettings.enabledPlugins)) {
19219
20389
  artifacts.push({
@@ -19227,14 +20397,14 @@ function collectOperationalInventory(options = {}) {
19227
20397
  });
19228
20398
  }
19229
20399
  }
19230
- artifacts.push(...skillArtifacts("claude_code", join42(home, ".claude", "skills")));
19231
- artifacts.push(...skillArtifacts("cursor", join42(home, ".cursor", "skills")));
19232
- artifacts.push(...skillArtifacts("codex", join42(codexHome, "skills")));
19233
- artifacts.push(...cursorExtensionArtifacts(join42(home, ".cursor", "extensions")));
20400
+ artifacts.push(...skillArtifacts("claude_code", join44(home, ".claude", "skills")));
20401
+ artifacts.push(...skillArtifacts("cursor", join44(home, ".cursor", "skills")));
20402
+ artifacts.push(...skillArtifacts("codex", join44(codexHome, "skills")));
20403
+ artifacts.push(...cursorExtensionArtifacts(join44(home, ".cursor", "extensions")));
19234
20404
  const uniqueArtifacts = /* @__PURE__ */ new Map();
19235
20405
  for (const artifact of artifacts) {
19236
- const key = `${artifact.harness || "global"}:${artifact.type}:${artifact.canonical_id}`;
19237
- uniqueArtifacts.set(key, artifact);
20406
+ const key2 = `${artifact.harness || "global"}:${artifact.type}:${artifact.canonical_id}`;
20407
+ uniqueArtifacts.set(key2, artifact);
19238
20408
  }
19239
20409
  const codingHarnesses = harnesses.filter((row2) => row2.harness === "claude_code" || row2.harness === "cursor" || row2.harness === "codex");
19240
20410
  const health = telemetryHealth(home) ?? {};
@@ -19267,7 +20437,7 @@ var init_collector = __esm({
19267
20437
  init_ccHookConfig();
19268
20438
  init_cursorHookConfig();
19269
20439
  init_codexHookConfig();
19270
- init_identity3();
20440
+ init_identity2();
19271
20441
  }
19272
20442
  });
19273
20443
 
@@ -19283,22 +20453,22 @@ __export(sync_exports2, {
19283
20453
  syncOperationalInventoryDetached: () => syncOperationalInventoryDetached
19284
20454
  });
19285
20455
  import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
19286
- import { spawn as spawn10 } from "child_process";
20456
+ import { spawn as spawn11 } from "child_process";
19287
20457
  import {
19288
- existsSync as existsSync43,
19289
- mkdirSync as mkdirSync25,
19290
- readFileSync as readFileSync40,
20458
+ existsSync as existsSync44,
20459
+ mkdirSync as mkdirSync26,
20460
+ readFileSync as readFileSync42,
19291
20461
  renameSync as renameSync10,
19292
- writeFileSync as writeFileSync30
20462
+ writeFileSync as writeFileSync32
19293
20463
  } from "fs";
19294
- import { homedir as homedir45 } from "os";
19295
- import { dirname as dirname13, join as join43 } from "path";
20464
+ import { homedir as homedir46 } from "os";
20465
+ import { dirname as dirname15, join as join45 } from "path";
19296
20466
  function syncStatePath() {
19297
- return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join43(homedir45(), ".synkro", "inventory-sync.json");
20467
+ return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join45(homedir46(), ".synkro", "inventory-sync.json");
19298
20468
  }
19299
20469
  function readState(path = syncStatePath()) {
19300
20470
  try {
19301
- const parsed = JSON.parse(readFileSync40(path, "utf8"));
20471
+ const parsed = JSON.parse(readFileSync42(path, "utf8"));
19302
20472
  return parsed && typeof parsed === "object" ? parsed : {};
19303
20473
  } catch {
19304
20474
  return {};
@@ -19306,9 +20476,9 @@ function readState(path = syncStatePath()) {
19306
20476
  }
19307
20477
  function writeState(state, path = syncStatePath()) {
19308
20478
  try {
19309
- mkdirSync25(dirname13(path), { recursive: true, mode: 448 });
20479
+ mkdirSync26(dirname15(path), { recursive: true, mode: 448 });
19310
20480
  const temp = `${path}.${process.pid}.tmp`;
19311
- writeFileSync30(temp, JSON.stringify(state, null, 2) + "\n", { encoding: "utf8", mode: 384 });
20481
+ writeFileSync32(temp, JSON.stringify(state, null, 2) + "\n", { encoding: "utf8", mode: 384 });
19312
20482
  renameSync10(temp, path);
19313
20483
  } catch {
19314
20484
  }
@@ -19321,18 +20491,18 @@ function shouldSyncInventory(state, now = Date.now(), target) {
19321
20491
  return !Number.isFinite(lastAttempt) || lastAttempt <= 0 || now - lastAttempt >= FAILURE_RETRY_MS;
19322
20492
  }
19323
20493
  function readConfig() {
19324
- const path = join43(homedir45(), ".synkro", "config.env");
20494
+ const path = join45(homedir46(), ".synkro", "config.env");
19325
20495
  const out = {};
19326
20496
  try {
19327
- for (const rawLine of readFileSync40(path, "utf8").split("\n")) {
20497
+ for (const rawLine of readFileSync42(path, "utf8").split("\n")) {
19328
20498
  const line = rawLine.trim();
19329
20499
  if (!line || line.startsWith("#")) continue;
19330
20500
  const index = line.indexOf("=");
19331
20501
  if (index <= 0) continue;
19332
- const key = line.slice(0, index).trim();
20502
+ const key2 = line.slice(0, index).trim();
19333
20503
  let value = line.slice(index + 1).trim();
19334
20504
  if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
19335
- out[key] = value;
20505
+ out[key2] = value;
19336
20506
  }
19337
20507
  } catch {
19338
20508
  }
@@ -19363,7 +20533,7 @@ function resolveInventoryGateway(raw) {
19363
20533
  }
19364
20534
  async function loadToken() {
19365
20535
  try {
19366
- const durable = readFileSync40(join43(homedir45(), ".synkro", ".mcp-jwt"), "utf8").trim();
20536
+ const durable = readFileSync42(join45(homedir46(), ".synkro", ".mcp-jwt"), "utf8").trim();
19367
20537
  if (durable) return durable;
19368
20538
  } catch {
19369
20539
  }
@@ -19379,7 +20549,7 @@ async function loadToken() {
19379
20549
  function stable(value) {
19380
20550
  if (Array.isArray(value)) return value.map(stable);
19381
20551
  if (!value || typeof value !== "object") return value;
19382
- return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => [key, stable(child)]));
20552
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key2, child]) => [key2, stable(child)]));
19383
20553
  }
19384
20554
  function inventorySnapshotChunks(snapshot, maxBytes = INVENTORY_CHUNK_BYTES) {
19385
20555
  const { collected_at: _heartbeat, ...material } = snapshot;
@@ -19481,8 +20651,8 @@ function syncOperationalInventoryDetached() {
19481
20651
  writeState({ ...state, last_attempt_at: (/* @__PURE__ */ new Date()).toISOString(), last_target: target }, path);
19482
20652
  try {
19483
20653
  const script = process.argv[1];
19484
- if (!script || !existsSync43(script)) return;
19485
- const child = spawn10(process.execPath, [script, "inventory-sync", "--detached"], {
20654
+ if (!script || !existsSync44(script)) return;
20655
+ const child = spawn11(process.execPath, [script, "inventory-sync", "--detached"], {
19486
20656
  detached: true,
19487
20657
  stdio: "ignore",
19488
20658
  env: { ...process.env, SYNKRO_INVENTORY_DETACHED: "1" }
@@ -19505,23 +20675,23 @@ var init_sync2 = __esm({
19505
20675
  });
19506
20676
 
19507
20677
  // cli/bootstrap.js
19508
- import { readFileSync as readFileSync41, existsSync as existsSync44 } from "fs";
19509
- import { resolve as resolve6 } from "path";
20678
+ import { readFileSync as readFileSync43, existsSync as existsSync45 } from "fs";
20679
+ import { resolve as resolve8 } from "path";
19510
20680
  process.title = "synkro";
19511
20681
  var envCandidates = [
19512
- resolve6(process.env.HOME ?? "", ".synkro", "config.env")
20682
+ resolve8(process.env.HOME ?? "", ".synkro", "config.env")
19513
20683
  ];
19514
20684
  for (const envPath of envCandidates) {
19515
- if (!existsSync44(envPath)) continue;
19516
- const envContent = readFileSync41(envPath, "utf-8");
20685
+ if (!existsSync45(envPath)) continue;
20686
+ const envContent = readFileSync43(envPath, "utf-8");
19517
20687
  for (const line of envContent.split("\n")) {
19518
20688
  const trimmed = line.trim();
19519
20689
  if (!trimmed || trimmed.startsWith("#")) continue;
19520
20690
  const eqIndex = trimmed.indexOf("=");
19521
20691
  if (eqIndex <= 0) continue;
19522
- const key = trimmed.slice(0, eqIndex).trim();
20692
+ const key2 = trimmed.slice(0, eqIndex).trim();
19523
20693
  const value = trimmed.slice(eqIndex + 1).trim().replace(/^['"]|['"]$/g, "");
19524
- if (!process.env[key] && !value.startsWith("op://")) process.env[key] = value;
20694
+ if (!process.env[key2] && !value.startsWith("op://")) process.env[key2] = value;
19525
20695
  }
19526
20696
  }
19527
20697
  var args = process.argv.slice(2);
@@ -19530,7 +20700,7 @@ var subArgs = args.slice(1);
19530
20700
  var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
19531
20701
  var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "inventory-sync", "version", "--version", "-v", "help", "--help", "-h", ""]);
19532
20702
  function printVersion() {
19533
- console.log("1.10.6");
20703
+ console.log("1.10.9");
19534
20704
  }
19535
20705
  function printHelp2() {
19536
20706
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents