@synkro-sh/cli 1.7.88 → 1.7.89

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
@@ -147,7 +147,7 @@ function getIdentity() {
147
147
  if (cached2) return cached2;
148
148
  let cliVersion = "0.0.0";
149
149
  try {
150
- cliVersion = "1.7.88";
150
+ cliVersion = "1.7.89";
151
151
  } catch {
152
152
  }
153
153
  const creds = loadCredentialsIdentity();
@@ -283,6 +283,100 @@ var init_emit = __esm({
283
283
  }
284
284
  });
285
285
 
286
+ // cli/telemetry/httpSql.ts
287
+ import { readFileSync as readFileSync3 } from "fs";
288
+ import { homedir as homedir4 } from "os";
289
+ import { join as join4 } from "path";
290
+ function quoteIdentifier(value) {
291
+ if (!/^[a-z_][a-z0-9_]*$/i.test(value)) throw new Error("invalid SQL identifier");
292
+ return `"${value}"`;
293
+ }
294
+ function shiftPlaceholders(text, offset) {
295
+ return text.replace(/\$(\d+)/g, (_, raw) => `$${Number(raw) + offset}`);
296
+ }
297
+ function bulkFragment(rows, columns) {
298
+ if (!rows.length || !columns.length) throw new Error("empty SQL helper");
299
+ const params = [];
300
+ const tuples = rows.map((row) => {
301
+ const placeholders = columns.map((column) => {
302
+ let value = row[column];
303
+ let cast = "";
304
+ if (column === "context" && typeof value === "object" && value !== null) {
305
+ value = JSON.stringify(value);
306
+ cast = "::jsonb";
307
+ }
308
+ params.push(value ?? null);
309
+ return `$${params.length}${cast}`;
310
+ });
311
+ return `(${placeholders.join(", ")})`;
312
+ });
313
+ return {
314
+ [FRAGMENT]: true,
315
+ text: `(${columns.map(quoteIdentifier).join(", ")}) VALUES ${tuples.join(", ")}`,
316
+ params
317
+ };
318
+ }
319
+ function createTelemetryHttpSql(options = {}) {
320
+ const port = options.port ?? Number.parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
321
+ const endpoint = `http://127.0.0.1:${port}/api/local/telemetry/query`;
322
+ const token = options.token ?? readFileSync3(join4(homedir4(), ".synkro", ".mcp-jwt"), "utf-8").trim();
323
+ const fetchImpl = options.fetchImpl ?? fetch;
324
+ const query = async (text, params = []) => {
325
+ const response = await fetchImpl(endpoint, {
326
+ method: "POST",
327
+ headers: {
328
+ Authorization: `Bearer ${token}`,
329
+ "Content-Type": "application/json"
330
+ },
331
+ body: JSON.stringify({ text, params }),
332
+ signal: AbortSignal.timeout(3e4)
333
+ });
334
+ const payload = await response.json().catch(() => ({}));
335
+ if (!response.ok) throw new Error(payload.error || `local telemetry query failed (${response.status})`);
336
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
337
+ Object.defineProperty(rows, "count", {
338
+ value: Number(payload.count ?? rows.length),
339
+ enumerable: false,
340
+ configurable: true
341
+ });
342
+ return rows;
343
+ };
344
+ const sql = function(first, ...values) {
345
+ if (Array.isArray(first) && "raw" in first) {
346
+ const strings = first;
347
+ let text = strings[0];
348
+ const params = [];
349
+ for (let i = 0; i < values.length; i++) {
350
+ const value = values[i];
351
+ if (value && typeof value === "object" && value[FRAGMENT]) {
352
+ text += shiftPlaceholders(value.text, params.length);
353
+ params.push(...value.params);
354
+ } else {
355
+ params.push(value);
356
+ text += `$${params.length}`;
357
+ }
358
+ text += strings[i + 1];
359
+ }
360
+ return query(text, params);
361
+ }
362
+ if (Array.isArray(first)) {
363
+ return bulkFragment(first, values);
364
+ }
365
+ throw new Error("unsupported SQL invocation");
366
+ };
367
+ sql.unsafe = query;
368
+ sql.end = async () => {
369
+ };
370
+ return sql;
371
+ }
372
+ var FRAGMENT;
373
+ var init_httpSql = __esm({
374
+ "cli/telemetry/httpSql.ts"() {
375
+ "use strict";
376
+ FRAGMENT = /* @__PURE__ */ Symbol("synkro-http-sql-fragment");
377
+ }
378
+ });
379
+
286
380
  // cli/telemetry/db.ts
287
381
  var db_exports = {};
288
382
  __export(db_exports, {
@@ -290,24 +384,11 @@ __export(db_exports, {
290
384
  connectDb: () => connectDb,
291
385
  resetDbForTests: () => resetDbForTests
292
386
  });
293
- import postgres from "postgres";
294
387
  async function connectDb() {
295
388
  if (cached3) return cached3;
296
389
  if (initFailedAt && Date.now() - initFailedAt < INIT_RETRY_MS) return null;
297
390
  try {
298
- const sql = postgres({
299
- host: PGLITE_HOST,
300
- port: PGLITE_PORT,
301
- username: PGLITE_USER,
302
- database: PGLITE_DB,
303
- // max:1 mirrors synkro-server — PGlite is single-threaded; multiple
304
- // sockets just queue at the multiplexer.
305
- max: 1,
306
- connect_timeout: 2,
307
- idle_timeout: 30,
308
- onnotice: () => {
309
- }
310
- });
391
+ const sql = createTelemetryHttpSql();
311
392
  await sql`SELECT 1`;
312
393
  cached3 = sql;
313
394
  initFailedAt = 0;
@@ -317,7 +398,7 @@ async function connectDb() {
317
398
  initFailedAt = Date.now();
318
399
  if (process.env.SYNKRO_TELEMETRY_DEBUG === "1") {
319
400
  const msg = err instanceof Error ? err.message : String(err);
320
- process.stderr.write(`[synkro] telemetry pglite unreachable at ${PGLITE_HOST}:${PGLITE_PORT}: ${msg}
401
+ process.stderr.write(`[synkro] local telemetry API unreachable: ${msg}
321
402
  `);
322
403
  }
323
404
  return null;
@@ -354,17 +435,11 @@ function resetDbForTests() {
354
435
  initFailedAt = 0;
355
436
  migrated = false;
356
437
  }
357
- var PGLITE_HOST, PGLITE_PORT, PGLITE_USER, PGLITE_DB, SCHEMA_VERSION, cached3, migrated, INIT_RETRY_MS, initFailedAt, MIGRATION_V1;
438
+ var SCHEMA_VERSION, cached3, migrated, INIT_RETRY_MS, initFailedAt, MIGRATION_V1;
358
439
  var init_db = __esm({
359
440
  "cli/telemetry/db.ts"() {
360
441
  "use strict";
361
- PGLITE_HOST = process.env.SYNKRO_PGLITE_HOST || "127.0.0.1";
362
- PGLITE_PORT = parseInt(
363
- process.env.SYNKRO_PGLITE_PORT || process.env.SYNKRO_HOST_PGLITE_PORT || "15433",
364
- 10
365
- );
366
- PGLITE_USER = process.env.SYNKRO_PGLITE_USER || "postgres";
367
- PGLITE_DB = process.env.SYNKRO_PGLITE_DB || "postgres";
442
+ init_httpSql();
368
443
  SCHEMA_VERSION = 1;
369
444
  cached3 = null;
370
445
  migrated = false;
@@ -438,9 +513,9 @@ var init_db = __esm({
438
513
  });
439
514
 
440
515
  // cli/telemetry/drain.ts
441
- import { openSync, closeSync, fstatSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, unlinkSync, mkdirSync as mkdirSync3, renameSync as renameSync2, existsSync as existsSync4 } from "fs";
442
- import { homedir as homedir4 } from "os";
443
- import { join as join4 } from "path";
516
+ import { openSync, closeSync, fstatSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, unlinkSync, mkdirSync as mkdirSync3, renameSync as renameSync2, existsSync as existsSync4 } from "fs";
517
+ import { homedir as homedir5 } from "os";
518
+ import { join as join5 } from "path";
444
519
  function ensureDir3() {
445
520
  if (!existsSync4(SYNKRO_DIR3)) mkdirSync3(SYNKRO_DIR3, { recursive: true, mode: 448 });
446
521
  }
@@ -494,7 +569,7 @@ function parseQueueFile(path) {
494
569
  if (!existsSync4(path)) return { rows: [], malformed: 0 };
495
570
  let raw;
496
571
  try {
497
- raw = readFileSync3(path, "utf-8");
572
+ raw = readFileSync4(path, "utf-8");
498
573
  } catch {
499
574
  return { rows: [], malformed: 0 };
500
575
  }
@@ -534,7 +609,7 @@ function recoverProcessing() {
534
609
  ensureDir3();
535
610
  let body;
536
611
  try {
537
- body = readFileSync3(PROCESSING_PATH, "utf-8");
612
+ body = readFileSync4(PROCESSING_PATH, "utf-8");
538
613
  } catch {
539
614
  return;
540
615
  }
@@ -663,39 +738,41 @@ async function insertEvents(sql, rows, mirroredIds) {
663
738
  return inserted;
664
739
  }
665
740
  async function drainToPglite() {
666
- const { rows } = readQueue();
667
- if (rows.length === 0) return { ok: true, ingested: 0, pending: 0, reason: "no_pending" };
741
+ const queued = readQueue().rows;
742
+ if (queued.length === 0) return { ok: true, ingested: 0, pending: 0, reason: "no_pending" };
743
+ const rows = queued.length > MIRROR_MAX_PER_DRAIN ? queued.slice(-MIRROR_MAX_PER_DRAIN) : queued;
668
744
  const sql = await connectDb();
669
- if (!sql) return { ok: false, ingested: 0, pending: rows.length, reason: "db_unavailable" };
745
+ if (!sql) return { ok: false, ingested: 0, pending: queued.length, reason: "db_unavailable" };
670
746
  try {
671
747
  const ingested = await insertEvents(sql, rows);
672
748
  patchMetaCache({ last_drained_at: (/* @__PURE__ */ new Date()).toISOString() });
673
- return { ok: true, ingested, pending: rows.length };
749
+ return { ok: true, ingested, pending: queued.length };
674
750
  } catch (err) {
675
751
  const msg = err instanceof Error ? err.message : String(err);
676
- return { ok: false, ingested: 0, pending: rows.length, reason: "error", error: msg };
752
+ return { ok: false, ingested: 0, pending: queued.length, reason: "error", error: msg };
677
753
  }
678
754
  }
679
- var SYNKRO_DIR3, QUEUE_LOCK, PROCESSING_PATH, CHUNK, STALE_LOCK_MS, MAX_QUEUE_ROWS;
755
+ var SYNKRO_DIR3, QUEUE_LOCK, PROCESSING_PATH, CHUNK, MIRROR_MAX_PER_DRAIN, STALE_LOCK_MS, MAX_QUEUE_ROWS;
680
756
  var init_drain = __esm({
681
757
  "cli/telemetry/drain.ts"() {
682
758
  "use strict";
683
759
  init_db();
684
760
  init_emit();
685
761
  init_metaCache();
686
- SYNKRO_DIR3 = join4(homedir4(), ".synkro");
687
- QUEUE_LOCK = join4(SYNKRO_DIR3, ".telemetry-queue.lock");
762
+ SYNKRO_DIR3 = join5(homedir5(), ".synkro");
763
+ QUEUE_LOCK = join5(SYNKRO_DIR3, ".telemetry-queue.lock");
688
764
  PROCESSING_PATH = `${PENDING_PATH}.processing`;
689
765
  CHUNK = 500;
766
+ MIRROR_MAX_PER_DRAIN = 5e3;
690
767
  STALE_LOCK_MS = 6e4;
691
768
  MAX_QUEUE_ROWS = 5e4;
692
769
  }
693
770
  });
694
771
 
695
772
  // cli/telemetry/optout.ts
696
- import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
697
- import { homedir as homedir5 } from "os";
698
- import { join as join5 } from "path";
773
+ import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
774
+ import { homedir as homedir6 } from "os";
775
+ import { join as join6 } from "path";
699
776
  function sanitize(raw, maxLen = 256) {
700
777
  return raw.replace(/[^\x20-\x7E]/g, "").slice(0, maxLen);
701
778
  }
@@ -708,7 +785,7 @@ function writeConfigEnvFlag(key, value) {
708
785
  let content = "";
709
786
  if (existsSync5(CONFIG_PATH2)) {
710
787
  try {
711
- content = readFileSync4(CONFIG_PATH2, "utf-8");
788
+ content = readFileSync5(CONFIG_PATH2, "utf-8");
712
789
  } catch {
713
790
  content = "";
714
791
  }
@@ -778,8 +855,8 @@ var init_optout = __esm({
778
855
  "use strict";
779
856
  init_emit();
780
857
  init_metaCache();
781
- SYNKRO_DIR4 = join5(homedir5(), ".synkro");
782
- CONFIG_PATH2 = join5(SYNKRO_DIR4, "config.env");
858
+ SYNKRO_DIR4 = join6(homedir6(), ".synkro");
859
+ CONFIG_PATH2 = join6(SYNKRO_DIR4, "config.env");
783
860
  KEY_ENABLED = "SYNKRO_TELEMETRY_ENABLED";
784
861
  KEY_REMOTE = "SYNKRO_TELEMETRY_REMOTE_FLUSH";
785
862
  }
@@ -828,15 +905,15 @@ __export(flush_exports, {
828
905
  flushDetached: () => flushDetached
829
906
  });
830
907
  import { spawn } from "child_process";
831
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
832
- import { homedir as homedir6 } from "os";
833
- import { join as join6 } from "path";
908
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
909
+ import { homedir as homedir7 } from "os";
910
+ import { join as join7 } from "path";
834
911
  function readConfigEnv() {
835
912
  if (!existsSync6(CONFIG_PATH3)) return {};
836
913
  const out = {};
837
914
  let raw;
838
915
  try {
839
- raw = readFileSync5(CONFIG_PATH3, "utf-8");
916
+ raw = readFileSync6(CONFIG_PATH3, "utf-8");
840
917
  } catch {
841
918
  return {};
842
919
  }
@@ -877,14 +954,14 @@ function resolveGatewayUrl() {
877
954
  function loadJwt() {
878
955
  if ((process.env.SYNKRO_STORAGE_MODE || "") === "cloud") {
879
956
  try {
880
- const mcp = readFileSync5(join6(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
957
+ const mcp = readFileSync6(join7(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
881
958
  if (mcp) return mcp;
882
959
  } catch {
883
960
  }
884
961
  }
885
962
  try {
886
963
  if (!existsSync6(CREDS_PATH2)) return null;
887
- const creds = JSON.parse(readFileSync5(CREDS_PATH2, "utf-8"));
964
+ const creds = JSON.parse(readFileSync6(CREDS_PATH2, "utf-8"));
888
965
  return creds.access_token || null;
889
966
  } catch {
890
967
  return null;
@@ -1039,20 +1116,20 @@ var init_flush = __esm({
1039
1116
  init_optout();
1040
1117
  init_metaCache();
1041
1118
  init_wire();
1042
- SYNKRO_DIR5 = join6(homedir6(), ".synkro");
1043
- CONFIG_PATH3 = join6(SYNKRO_DIR5, "config.env");
1044
- CREDS_PATH2 = process.env.SYNKRO_CREDENTIALS_PATH || join6(SYNKRO_DIR5, "credentials.json");
1119
+ SYNKRO_DIR5 = join7(homedir7(), ".synkro");
1120
+ CONFIG_PATH3 = join7(SYNKRO_DIR5, "config.env");
1121
+ CREDS_PATH2 = process.env.SYNKRO_CREDENTIALS_PATH || join7(SYNKRO_DIR5, "credentials.json");
1045
1122
  THROTTLE_MS = 6e4;
1046
1123
  DEFAULT_GATEWAY = "https://api.synkro.sh";
1047
1124
  }
1048
1125
  });
1049
1126
 
1050
1127
  // cli/telemetry/stats.ts
1051
- import { existsSync as existsSync7, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync4 } from "fs";
1128
+ import { existsSync as existsSync7, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync4 } from "fs";
1052
1129
  function countPending() {
1053
1130
  if (!existsSync7(PENDING_PATH)) return 0;
1054
1131
  try {
1055
- const raw = readFileSync6(PENDING_PATH, "utf-8");
1132
+ const raw = readFileSync7(PENDING_PATH, "utf-8");
1056
1133
  if (!raw) return 0;
1057
1134
  let n = 0;
1058
1135
  for (const line of raw.split("\n")) if (line.trim()) n++;
@@ -1174,13 +1251,13 @@ var init_stats = __esm({
1174
1251
  });
1175
1252
 
1176
1253
  // cli/telemetry/purge.ts
1177
- import { existsSync as existsSync8, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
1254
+ import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
1178
1255
  import { randomUUID as randomUUID3 } from "crypto";
1179
1256
  function unlinkPendingCount() {
1180
1257
  if (!existsSync8(PENDING_PATH)) return 0;
1181
1258
  let n = 0;
1182
1259
  try {
1183
- const raw = readFileSync7(PENDING_PATH, "utf-8");
1260
+ const raw = readFileSync8(PENDING_PATH, "utf-8");
1184
1261
  for (const line of raw.split("\n")) if (line.trim()) n++;
1185
1262
  } catch {
1186
1263
  }
@@ -1345,8 +1422,8 @@ var init_telemetry = __esm({
1345
1422
 
1346
1423
  // cli/installer/agentDetect.ts
1347
1424
  import { existsSync as existsSync9 } from "fs";
1348
- import { homedir as homedir7 } from "os";
1349
- import { join as join7 } from "path";
1425
+ import { homedir as homedir8 } from "os";
1426
+ import { join as join8 } from "path";
1350
1427
  import { execFileSync as execFileSync2, execSync } from "child_process";
1351
1428
  function which(cmd3) {
1352
1429
  try {
@@ -1367,21 +1444,21 @@ function getVersion(cmd3) {
1367
1444
  }
1368
1445
  function detectAgents() {
1369
1446
  const agents = [];
1370
- const home = homedir7();
1447
+ const home = homedir8();
1371
1448
  const claudeBinary = which("claude");
1372
- const claudeConfigDir = join7(home, ".claude");
1449
+ const claudeConfigDir = join8(home, ".claude");
1373
1450
  if (claudeBinary) {
1374
1451
  agents.push({
1375
1452
  kind: "claude_code",
1376
1453
  name: "Claude Code",
1377
1454
  binaryPath: claudeBinary,
1378
1455
  configDir: claudeConfigDir,
1379
- settingsPath: join7(claudeConfigDir, "settings.json"),
1456
+ settingsPath: join8(claudeConfigDir, "settings.json"),
1380
1457
  version: getVersion("claude")
1381
1458
  });
1382
1459
  }
1383
1460
  const cursorBinary = which("cursor");
1384
- const cursorConfigDir = join7(home, ".cursor");
1461
+ const cursorConfigDir = join8(home, ".cursor");
1385
1462
  const cursorApp = process.platform === "darwin" && existsSync9("/Applications/Cursor.app");
1386
1463
  if (cursorBinary || cursorApp || existsSync9(cursorConfigDir)) {
1387
1464
  let version;
@@ -1399,19 +1476,19 @@ function detectAgents() {
1399
1476
  name: "Cursor",
1400
1477
  binaryPath: cursorBinary,
1401
1478
  configDir: cursorConfigDir,
1402
- settingsPath: join7(cursorConfigDir, "hooks.json"),
1479
+ settingsPath: join8(cursorConfigDir, "hooks.json"),
1403
1480
  version
1404
1481
  });
1405
1482
  }
1406
1483
  const codexBinary = which("codex");
1407
- const codexConfigDir = process.env.CODEX_HOME || join7(home, ".codex");
1484
+ const codexConfigDir = process.env.CODEX_HOME || join8(home, ".codex");
1408
1485
  if (codexBinary || existsSync9(codexConfigDir)) {
1409
1486
  agents.push({
1410
1487
  kind: "codex",
1411
1488
  name: "Codex",
1412
1489
  binaryPath: codexBinary,
1413
1490
  configDir: codexConfigDir,
1414
- settingsPath: join7(codexConfigDir, "hooks.json"),
1491
+ settingsPath: join8(codexConfigDir, "hooks.json"),
1415
1492
  version: codexBinary ? getVersion("codex") : void 0
1416
1493
  });
1417
1494
  }
@@ -1425,16 +1502,16 @@ var init_agentDetect = __esm({
1425
1502
 
1426
1503
  // cli/installer/platform.ts
1427
1504
  import { existsSync as existsSync10 } from "fs";
1428
- import { homedir as homedir8 } from "os";
1429
- import { join as join8 } from "path";
1505
+ import { homedir as homedir9 } from "os";
1506
+ import { join as join9 } from "path";
1430
1507
  import { spawnSync } from "child_process";
1431
1508
  function resolveBunBin() {
1432
1509
  const finder = IS_WINDOWS ? "where" : "which";
1433
1510
  const r = spawnSync(finder, ["bun"], { encoding: "utf-8", timeout: 5e3 });
1434
1511
  const resolved = (r.stdout || "").split(/\r?\n/).map((s) => s.trim()).find(Boolean);
1435
1512
  if (resolved) return resolved;
1436
- const home = process.env.USERPROFILE || homedir8();
1437
- const candidates = IS_WINDOWS ? [join8(home, ".bun", "bin", "bun.exe"), "C:\\Program Files\\bun\\bin\\bun.exe"] : ["/opt/homebrew/bin/bun", "/usr/local/bin/bun", join8(home, ".bun", "bin", "bun")];
1513
+ const home = process.env.USERPROFILE || homedir9();
1514
+ const candidates = IS_WINDOWS ? [join9(home, ".bun", "bin", "bun.exe"), "C:\\Program Files\\bun\\bin\\bun.exe"] : ["/opt/homebrew/bin/bun", "/usr/local/bin/bun", join9(home, ".bun", "bin", "bun")];
1438
1515
  for (const p of candidates) if (existsSync10(p)) return p;
1439
1516
  return IS_WINDOWS ? "bun.exe" : "bun";
1440
1517
  }
@@ -1458,12 +1535,12 @@ var init_platform = __esm({
1458
1535
  });
1459
1536
 
1460
1537
  // cli/installer/ccHookConfig.ts
1461
- import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync6, renameSync as renameSync3, mkdirSync as mkdirSync4 } from "fs";
1538
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, mkdirSync as mkdirSync4 } from "fs";
1462
1539
  import { dirname } from "path";
1463
1540
  function readSettings(path) {
1464
1541
  if (!existsSync11(path)) return {};
1465
1542
  try {
1466
- const raw = readFileSync8(path, "utf-8");
1543
+ const raw = readFileSync9(path, "utf-8");
1467
1544
  return JSON.parse(raw);
1468
1545
  } catch (err) {
1469
1546
  throw new Error(`Failed to parse ${path}: ${err.message}`);
@@ -1609,7 +1686,7 @@ function installCCHooks(settingsPath, config) {
1609
1686
  }
1610
1687
  if (config.taskActivateIntentScriptPath) {
1611
1688
  settings.hooks.PreToolUse.push({
1612
- matcher: "mcp__synkro-guardrails__activate_standard",
1689
+ matcher: "mcp__synkro[-_]guardrails__activate_standard",
1613
1690
  hooks: [
1614
1691
  {
1615
1692
  type: "command",
@@ -1723,9 +1800,9 @@ var init_ccHookConfig = __esm({
1723
1800
  });
1724
1801
 
1725
1802
  // cli/installer/cursorHookConfig.ts
1726
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, renameSync as renameSync4, mkdirSync as mkdirSync5 } from "fs";
1803
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync4, mkdirSync as mkdirSync5 } from "fs";
1727
1804
  import { dirname as dirname2, resolve, normalize } from "path";
1728
- import { homedir as homedir9 } from "os";
1805
+ import { homedir as homedir10 } from "os";
1729
1806
  function shellQuote(s) {
1730
1807
  return "'" + s.replace(/'/g, "'\\''") + "'";
1731
1808
  }
@@ -1745,7 +1822,7 @@ function validateHooksPath(path) {
1745
1822
  function readHooksFile(rawPath) {
1746
1823
  const safePath = validateHooksPath(rawPath);
1747
1824
  try {
1748
- const raw = readFileSync9(safePath, "utf-8");
1825
+ const raw = readFileSync10(safePath, "utf-8");
1749
1826
  return JSON.parse(raw);
1750
1827
  } catch (err) {
1751
1828
  if (err?.code === "ENOENT") return { version: 1, hooks: {} };
@@ -1927,8 +2004,8 @@ var init_cursorHookConfig = __esm({
1927
2004
  init_platform();
1928
2005
  SYNKRO_MARKER2 = "__synkro_managed__";
1929
2006
  ALLOWED_PARENT_DIRS = [
1930
- resolve(homedir9(), ".cursor"),
1931
- resolve(homedir9(), ".config", "cursor")
2007
+ resolve(homedir10(), ".cursor"),
2008
+ resolve(homedir10(), ".config", "cursor")
1932
2009
  ];
1933
2010
  ALL_EVENTS = [
1934
2011
  "sessionStart",
@@ -1948,9 +2025,9 @@ var init_cursorHookConfig = __esm({
1948
2025
  });
1949
2026
 
1950
2027
  // cli/installer/codexHookConfig.ts
1951
- import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync5, mkdirSync as mkdirSync6 } from "fs";
2028
+ import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync5, mkdirSync as mkdirSync6 } from "fs";
1952
2029
  import { dirname as dirname3, resolve as resolve2, normalize as normalize2 } from "path";
1953
- import { homedir as homedir10 } from "os";
2030
+ import { homedir as homedir11 } from "os";
1954
2031
  function shellQuote2(s) {
1955
2032
  return "'" + s.replace(/'/g, "'\\''") + "'";
1956
2033
  }
@@ -1967,8 +2044,8 @@ function codexCmd(scriptPath) {
1967
2044
  function validateHooksPath2(path) {
1968
2045
  const resolved = resolve2(normalize2(path));
1969
2046
  const allowedParentDirs = [
1970
- resolve2(homedir10(), ".codex"),
1971
- resolve2(homedir10(), ".config", "codex"),
2047
+ resolve2(homedir11(), ".codex"),
2048
+ resolve2(homedir11(), ".config", "codex"),
1972
2049
  ...process.env.CODEX_HOME ? [resolve2(process.env.CODEX_HOME)] : []
1973
2050
  ];
1974
2051
  if (!allowedParentDirs.some((dir) => resolved.startsWith(dir + "/") || resolved === dir)) {
@@ -1980,7 +2057,7 @@ function readHooksFile2(rawPath) {
1980
2057
  const safePath = validateHooksPath2(rawPath);
1981
2058
  if (!existsSync12(safePath)) return { hooks: {} };
1982
2059
  try {
1983
- return JSON.parse(readFileSync10(safePath, "utf-8"));
2060
+ return JSON.parse(readFileSync11(safePath, "utf-8"));
1984
2061
  } catch (err) {
1985
2062
  throw new Error(`Failed to parse ${safePath}: ${err.message}`);
1986
2063
  }
@@ -2026,13 +2103,19 @@ function installCodexHooks(hooksJsonPath, config) {
2026
2103
  cmd(config.cvePrecheckScriptPath, 15),
2027
2104
  ...config.skillJudgeScriptPath ? [cmd(config.skillJudgeScriptPath, 50)] : []
2028
2105
  ], M_EDIT);
2106
+ push(h, "PostToolUse", [
2107
+ cmd(config.editFollowupScriptPath, 30, "Recording edit")
2108
+ ], M_EDIT);
2029
2109
  push(h, "PreToolUse", [cmd(config.agentJudgeScriptPath, 50)], M_AGENT);
2030
2110
  if (config.mcpGateScriptPath) push(h, "PreToolUse", [cmd(config.mcpGateScriptPath, 50)], M_MCP);
2031
2111
  if (config.taskActivateIntentScriptPath) push(h, "PreToolUse", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
2112
+ if (config.taskActivateIntentScriptPath) push(h, "PermissionRequest", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
2032
2113
  push(h, "PostToolUse", [cmd(config.bashFollowupScriptPath, 10)], M_BASH);
2033
2114
  push(h, "UserPromptSubmit", [cmd(config.userPromptSubmitScriptPath, 5)]);
2034
2115
  if (config.promptRouteScriptPath) push(h, "UserPromptSubmit", [cmd(config.promptRouteScriptPath, 5)]);
2035
2116
  push(h, "SessionStart", [cmd(config.sessionStartScriptPath, 5)]);
2117
+ push(h, "SubagentStart", [cmd(config.subagentStartScriptPath, 5)]);
2118
+ if (!config.skipTranscriptSync) push(h, "SubagentStop", [cmd(config.subagentStopScriptPath, 5)]);
2036
2119
  push(h, "SessionEnd", [cmd(config.stopSummaryScriptPath, 3)]);
2037
2120
  if (!config.skipTranscriptSync) push(h, "Stop", [cmd(config.transcriptSyncScriptPath, 3)]);
2038
2121
  writeHooksFileAtomic2(hooksJsonPath, file);
@@ -2054,30 +2137,31 @@ function uninstallCodexHooks(hooksJsonPath) {
2054
2137
  writeHooksFileAtomic2(hooksJsonPath, file);
2055
2138
  return true;
2056
2139
  }
2057
- var SYNKRO_MARKER3, M_BASH, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
2140
+ var SYNKRO_MARKER3, M_BASH, CODEX_EDIT_MATCHER, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
2058
2141
  var init_codexHookConfig = __esm({
2059
2142
  "cli/installer/codexHookConfig.ts"() {
2060
2143
  "use strict";
2061
2144
  init_platform();
2062
2145
  SYNKRO_MARKER3 = "__synkro_managed__";
2063
2146
  M_BASH = "Bash";
2064
- M_EDIT = "apply_patch|Edit|Write";
2147
+ CODEX_EDIT_MATCHER = "^(?:apply_patch|ApplyPatch|Edit|Write|functions[._]apply_patch)$";
2148
+ M_EDIT = CODEX_EDIT_MATCHER;
2065
2149
  M_AGENT = "Agent";
2066
2150
  M_MCP = "mcp__.*";
2067
- M_ACTIVATE = "mcp__synkro-guardrails__activate_standard";
2068
- ALL_EVENTS2 = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "Stop"];
2151
+ M_ACTIVATE = "mcp__synkro[-_]guardrails__activate_standard";
2152
+ ALL_EVENTS2 = ["PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "SubagentStart", "SubagentStop", "Stop"];
2069
2153
  }
2070
2154
  });
2071
2155
 
2072
2156
  // cli/installer/mcpConfig.ts
2073
- import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync7 } from "fs";
2074
- import { homedir as homedir11 } from "os";
2075
- import { dirname as dirname4, join as join9 } from "path";
2157
+ import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync7 } from "fs";
2158
+ import { homedir as homedir12 } from "os";
2159
+ import { dirname as dirname4, join as join10 } from "path";
2076
2160
  import { randomBytes } from "crypto";
2077
2161
  function readClaudeJson() {
2078
2162
  if (!existsSync13(CC_CONFIG_PATH)) return {};
2079
2163
  try {
2080
- const raw = readFileSync11(CC_CONFIG_PATH, "utf-8");
2164
+ const raw = readFileSync12(CC_CONFIG_PATH, "utf-8");
2081
2165
  return JSON.parse(raw);
2082
2166
  } catch (err) {
2083
2167
  throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${err.message}`);
@@ -2096,7 +2180,7 @@ function installMcpConfig(opts) {
2096
2180
  if (entry?.[SYNKRO_MARKER4] === true) delete config.mcpServers[name];
2097
2181
  }
2098
2182
  if (opts.local) {
2099
- const proxyScript = join9(homedir11(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
2183
+ const proxyScript = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
2100
2184
  config.mcpServers[SYNKRO_SERVER_NAME] = {
2101
2185
  type: "stdio",
2102
2186
  command: "bun",
@@ -2135,7 +2219,7 @@ function uninstallMcpConfig() {
2135
2219
  function readCursorMcpJson() {
2136
2220
  if (!existsSync13(CURSOR_MCP_PATH)) return {};
2137
2221
  try {
2138
- const raw = readFileSync11(CURSOR_MCP_PATH, "utf-8");
2222
+ const raw = readFileSync12(CURSOR_MCP_PATH, "utf-8");
2139
2223
  return JSON.parse(raw);
2140
2224
  } catch (err) {
2141
2225
  throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${err.message}`);
@@ -2156,10 +2240,10 @@ function installCursorMcpConfig(opts) {
2156
2240
  if (opts.local) {
2157
2241
  const port = process.env.SYNKRO_MCP_PORT || "18931";
2158
2242
  const url2 = `http://127.0.0.1:${port}/`;
2159
- const jwtPath = join9(homedir11(), ".synkro", ".mcp-jwt");
2243
+ const jwtPath = join10(homedir12(), ".synkro", ".mcp-jwt");
2160
2244
  let jwt2 = "";
2161
2245
  try {
2162
- jwt2 = readFileSync11(jwtPath, "utf-8").trim();
2246
+ jwt2 = readFileSync12(jwtPath, "utf-8").trim();
2163
2247
  } catch {
2164
2248
  }
2165
2249
  config.mcpServers[SYNKRO_SERVER_NAME] = {
@@ -2201,7 +2285,7 @@ function codexConfigPath(path) {
2201
2285
  function readCodexToml(path) {
2202
2286
  if (!existsSync13(path)) return "";
2203
2287
  try {
2204
- return readFileSync11(path, "utf-8");
2288
+ return readFileSync12(path, "utf-8");
2205
2289
  } catch (err) {
2206
2290
  throw new Error(`Failed to read ${path}: ${err.message}`);
2207
2291
  }
@@ -2290,7 +2374,7 @@ function resolveBunBin2() {
2290
2374
  function readDesktopJson() {
2291
2375
  if (!existsSync13(CLAUDE_DESKTOP_CONFIG_PATH)) return {};
2292
2376
  try {
2293
- return JSON.parse(readFileSync11(CLAUDE_DESKTOP_CONFIG_PATH, "utf-8"));
2377
+ return JSON.parse(readFileSync12(CLAUDE_DESKTOP_CONFIG_PATH, "utf-8"));
2294
2378
  } catch (err) {
2295
2379
  throw new Error(`Failed to parse ${CLAUDE_DESKTOP_CONFIG_PATH}: ${err.message}`);
2296
2380
  }
@@ -2347,31 +2431,31 @@ var init_mcpConfig = __esm({
2347
2431
  init_platform();
2348
2432
  SYNKRO_MARKER4 = "__synkro_managed__";
2349
2433
  SYNKRO_SERVER_NAME = "synkro-guardrails";
2350
- CC_CONFIG_PATH = join9(homedir11(), ".claude.json");
2351
- CURSOR_MCP_PATH = join9(homedir11(), ".cursor", "mcp.json");
2352
- CODEX_CONFIG_PATH = join9(process.env.CODEX_HOME || join9(homedir11(), ".codex"), "config.toml");
2434
+ CC_CONFIG_PATH = join10(homedir12(), ".claude.json");
2435
+ CURSOR_MCP_PATH = join10(homedir12(), ".cursor", "mcp.json");
2436
+ CODEX_CONFIG_PATH = join10(process.env.CODEX_HOME || join10(homedir12(), ".codex"), "config.toml");
2353
2437
  CODEX_MCP_BEGIN = "# >>> synkro managed MCP: synkro-guardrails";
2354
2438
  CODEX_MCP_END = "# <<< synkro managed MCP: synkro-guardrails";
2355
2439
  CODEX_MCP_SECTION = 'mcp_servers."synkro-guardrails"';
2356
- CLAUDE_DESKTOP_CONFIG_PATH = join9(
2357
- homedir11(),
2440
+ CLAUDE_DESKTOP_CONFIG_PATH = join10(
2441
+ homedir12(),
2358
2442
  "Library",
2359
2443
  "Application Support",
2360
2444
  "Claude",
2361
2445
  "claude_desktop_config.json"
2362
2446
  );
2363
- MCP_STDIO_PROXY_PATH = join9(homedir11(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
2447
+ MCP_STDIO_PROXY_PATH = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
2364
2448
  }
2365
2449
  });
2366
2450
 
2367
2451
  // cli/installer/synkroCommand.ts
2368
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
2369
- import { homedir as homedir12 } from "os";
2370
- import { join as join10 } from "path";
2452
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
2453
+ import { homedir as homedir13 } from "os";
2454
+ import { join as join11 } from "path";
2371
2455
  function installSynkroCommand() {
2372
2456
  try {
2373
2457
  if (existsSync14(COMMAND_PATH)) {
2374
- const current = readFileSync12(COMMAND_PATH, "utf-8");
2458
+ const current = readFileSync13(COMMAND_PATH, "utf-8");
2375
2459
  if (!current.includes(MARKER)) return null;
2376
2460
  }
2377
2461
  mkdirSync8(COMMANDS_DIR, { recursive: true });
@@ -2384,7 +2468,7 @@ function installSynkroCommand() {
2384
2468
  function uninstallSynkroCommand() {
2385
2469
  try {
2386
2470
  if (!existsSync14(COMMAND_PATH)) return false;
2387
- const current = readFileSync12(COMMAND_PATH, "utf-8");
2471
+ const current = readFileSync13(COMMAND_PATH, "utf-8");
2388
2472
  if (!current.includes(MARKER)) return false;
2389
2473
  unlinkSync4(COMMAND_PATH);
2390
2474
  return true;
@@ -2397,8 +2481,8 @@ var init_synkroCommand = __esm({
2397
2481
  "cli/installer/synkroCommand.ts"() {
2398
2482
  "use strict";
2399
2483
  MARKER = "__synkro_managed__";
2400
- COMMANDS_DIR = join10(homedir12(), ".claude", "commands");
2401
- COMMAND_PATH = join10(COMMANDS_DIR, "synkro.md");
2484
+ COMMANDS_DIR = join11(homedir13(), ".claude", "commands");
2485
+ COMMAND_PATH = join11(COMMANDS_DIR, "synkro.md");
2402
2486
  COMMAND_BODY = `<!-- ${MARKER} -->
2403
2487
  ---
2404
2488
  description: Set up Synkro \u2014 create a starter rule set, review suggestions, and watch a live block
@@ -2456,14 +2540,14 @@ var init_skillParser = __esm({
2456
2540
  function stubHook(surface, optsLiteral) {
2457
2541
  return "#!/usr/bin/env bun\nimport { runStub } from './_synkro-stub-common.ts';\nrunStub(" + JSON.stringify(surface) + ", " + optsLiteral + ");\n";
2458
2542
  }
2459
- var STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_INSTALL_SCAN_TS, STUB_AGENT_JUDGE_TS, STUB_MCP_GATE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_BASH_FOLLOWUP_TS, STUB_PROMPT_ROUTE_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS;
2543
+ var STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_EDIT_FOLLOWUP_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_INSTALL_SCAN_TS, STUB_AGENT_JUDGE_TS, STUB_MCP_GATE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_SUBAGENT_START_TS, STUB_SUBAGENT_STOP_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_BASH_FOLLOWUP_TS, STUB_PROMPT_ROUTE_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS;
2460
2544
  var init_hookScriptsTs = __esm({
2461
2545
  "cli/installer/hookScriptsTs.ts"() {
2462
2546
  "use strict";
2463
- STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';
2547
+ STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, statSync, realpathSync, openSync, readSync, closeSync } from 'node:fs';
2464
2548
  import { execSync } from 'node:child_process';
2465
2549
  import { homedir } from 'node:os';
2466
- import { join, resolve, isAbsolute } from 'node:path';
2550
+ import { basename, join, resolve, relative, isAbsolute } from 'node:path';
2467
2551
  import { randomUUID, createHash } from 'node:crypto';
2468
2552
 
2469
2553
  const HOME = homedir();
@@ -2622,13 +2706,17 @@ async function cloudMcpGate(payload: any, harness: string): Promise<string> {
2622
2706
  try {
2623
2707
  const toolName = String(payload.tool_name || payload.tool || '');
2624
2708
  const parts = toolName.split('__');
2625
- const server = (parts.length >= 3 && parts[0] === 'mcp') ? parts[1] : '';
2709
+ const input = payload.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
2710
+ const server = (parts.length >= 3 && parts[0] === 'mcp')
2711
+ ? parts[1]
2712
+ : String(payload.server_name || payload.serverName || payload.server || payload.mcp_server
2713
+ || input.server_name || input.serverName || input.server || input.mcp_server || '');
2626
2714
  if (!server) return failOpen(harness);
2627
2715
  const base = (cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh').replace(/\/+$/, '');
2628
2716
  const resp = await fetch(base + '/api/mcp/gate', {
2629
2717
  method: 'POST',
2630
2718
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() },
2631
- body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') }),
2719
+ body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') || toolName, harness: harness }),
2632
2720
  signal: AbortSignal.timeout(5000),
2633
2721
  });
2634
2722
  if (!resp.ok) return failOpen(harness);
@@ -2725,48 +2813,293 @@ function gatherBaseContent(fp: string, ti: any): string | undefined {
2725
2813
  return '\n'.repeat(linesBefore) + full.slice(start, end);
2726
2814
  }
2727
2815
 
2816
+ function gatherPreEditContentFromPost(fp: string, toolName: string, ti: any, operation: string): string | undefined {
2817
+ if (operation === 'add') return '';
2818
+ if (operation === 'delete') {
2819
+ const patch = String(ti?.patch || '');
2820
+ const removed: string[] = [];
2821
+ for (const line of patch.split('\n')) {
2822
+ if (line.startsWith('*** ') || line.startsWith('@@')) continue;
2823
+ if (line.startsWith('-')) removed.push(line.slice(1));
2824
+ }
2825
+ return removed.length ? removed.join('\n') : undefined;
2826
+ }
2827
+ let full = '';
2828
+ try { full = readFileSync(fp, 'utf-8'); } catch { return undefined; }
2829
+ if (toolName === 'Edit') {
2830
+ const oldString = String(ti?.old_string || '');
2831
+ const newString = String(ti?.new_string || '');
2832
+ if (newString && full.includes(newString)) return full.replace(newString, oldString);
2833
+ return undefined;
2834
+ }
2835
+ if (toolName === 'MultiEdit' && Array.isArray(ti?.edits)) {
2836
+ let before = full;
2837
+ for (const edit of [...ti.edits].reverse()) {
2838
+ const oldString = String(edit?.old_string || '');
2839
+ const newString = String(edit?.new_string || '');
2840
+ if (!newString || !before.includes(newString)) return undefined;
2841
+ before = before.replace(newString, oldString);
2842
+ }
2843
+ return before;
2844
+ }
2845
+ return full;
2846
+ }
2847
+
2728
2848
  // Codex edits arrive as the apply_patch tool with the patch text in
2729
2849
  // tool_input.command (lines: "*** Update/Add/Delete File: PATH", then hunks where
2730
2850
  // "+" adds, "-" removes, " " is context). The edit hooks expect the CC shape
2731
- // {file_path, old_string, new_string}; translate once so filePathFromToolInput,
2732
- // editAnchorString, and gatherBaseContent all work unchanged. A patch spanning
2733
- // more than one file or hunk cannot be reconstructed faithfully as one Edit, so
2734
- // mark it for a clear deny-and-retry response instead of grading partial content.
2851
+ // {file_path, old_string, new_string}; translate every file and hunk into an
2852
+ // explicit edit batch so one apply_patch call never drops evidence.
2735
2853
  export function normalizeCodexApplyPatch(payload: any): void {
2736
2854
  try {
2737
- if (!/apply_patch/i.test(String((payload && payload.tool_name) || ''))) return;
2855
+ if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(String((payload && payload.tool_name) || ''))) return;
2738
2856
  const ti = payload.tool_input;
2739
2857
  const patch = (ti && typeof ti === 'object')
2740
2858
  ? String(ti.command || ti.patch || ti.input || '')
2741
2859
  : (typeof ti === 'string' ? ti : '');
2742
2860
  if (!patch) return;
2743
- const files: string[] = [];
2744
- let hunkCount = 0;
2745
- const oldLines: string[] = [];
2746
- const newLines: string[] = [];
2861
+ type Hunk = { oldLines: string[]; newLines: string[] };
2862
+ type PatchFile = {
2863
+ operation: 'Add' | 'Update' | 'Delete';
2864
+ path: string;
2865
+ moveTarget: string;
2866
+ hunks: Hunk[];
2867
+ fragment: string[];
2868
+ };
2869
+ const files: PatchFile[] = [];
2870
+ let current: PatchFile | null = null;
2871
+ let hunk: Hunk | null = null;
2872
+ const flushHunk = () => {
2873
+ if (current && hunk && (hunk.oldLines.length || hunk.newLines.length)) current.hunks.push(hunk);
2874
+ hunk = null;
2875
+ };
2876
+ const flushFile = () => {
2877
+ flushHunk();
2878
+ if (current) files.push(current);
2879
+ current = null;
2880
+ };
2747
2881
  for (const ln of patch.split('\n')) {
2748
2882
  const mFile = ln.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+?)\s*$/);
2749
- if (mFile) { files.push(mFile[1].trim()); continue; }
2750
- if (ln.indexOf('@@') === 0) { hunkCount++; continue; }
2883
+ if (mFile) {
2884
+ flushFile();
2885
+ const operation = ln.match(/^\*\*\*\s+(Update|Add|Delete)\s+File:/)?.[1] as 'Add' | 'Update' | 'Delete';
2886
+ current = {
2887
+ operation,
2888
+ path: mFile[1].trim(),
2889
+ moveTarget: '',
2890
+ hunks: [],
2891
+ fragment: [ln],
2892
+ };
2893
+ continue;
2894
+ }
2895
+ if (!current) continue;
2896
+ current.fragment.push(ln);
2897
+ const mMove = ln.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
2898
+ if (mMove) { current.moveTarget = mMove[1].trim(); continue; }
2899
+ if (ln.indexOf('@@') === 0) {
2900
+ flushHunk();
2901
+ hunk = { oldLines: [], newLines: [] };
2902
+ const anchor = ln.slice(2).trim();
2903
+ if (anchor) { hunk.oldLines.push(anchor); hunk.newLines.push(anchor); }
2904
+ continue;
2905
+ }
2751
2906
  if (ln.indexOf('*** ') === 0) continue;
2907
+ hunk = hunk || { oldLines: [], newLines: [] };
2752
2908
  const c = ln.charAt(0);
2753
- if (c === '+') newLines.push(ln.slice(1));
2754
- else if (c === '-') oldLines.push(ln.slice(1));
2755
- else if (c === ' ') { oldLines.push(ln.slice(1)); newLines.push(ln.slice(1)); }
2909
+ if (c === '+') hunk.newLines.push(ln.slice(1));
2910
+ else if (c === '-') hunk.oldLines.push(ln.slice(1));
2911
+ else if (c === ' ') { hunk.oldLines.push(ln.slice(1)); hunk.newLines.push(ln.slice(1)); }
2756
2912
  }
2757
- if (files.length !== 1 || hunkCount > 1) {
2913
+ flushFile();
2914
+ if (!files.length) {
2758
2915
  payload.__synkro_codex_patch_error =
2759
- 'Synkro needs Codex edits split into one file and one contiguous hunk. Retry this apply_patch as separate edits.';
2916
+ 'Synkro could not recover a file edit from this apply_patch payload.';
2760
2917
  return;
2761
2918
  }
2762
- if (!files[0]) return;
2763
2919
  const cwd = typeof payload.cwd === 'string' ? payload.cwd : '';
2764
- const filePath = !isAbsolute(files[0]) && cwd ? resolve(cwd, files[0]) : files[0];
2765
- payload.tool_name = 'Edit';
2766
- payload.tool_input = { file_path: filePath, old_string: oldLines.join('\n'), new_string: newLines.join('\n') };
2920
+ const normalized = files.map((file) => {
2921
+ const filePath = !isAbsolute(file.path) && cwd ? resolve(cwd, file.path) : file.path;
2922
+ const patchFragment = file.fragment.join('\n');
2923
+ if (file.operation === 'Add') {
2924
+ return {
2925
+ tool_name: 'Write',
2926
+ tool_input: {
2927
+ file_path: filePath,
2928
+ content: file.hunks.flatMap((item) => item.newLines).join('\n'),
2929
+ patch: patchFragment,
2930
+ },
2931
+ __synkro_codex_operation: 'add',
2932
+ };
2933
+ }
2934
+ if (file.operation === 'Delete') {
2935
+ return {
2936
+ tool_name: 'Write',
2937
+ tool_input: { file_path: filePath, content: '', patch: patchFragment },
2938
+ __synkro_codex_operation: 'delete',
2939
+ };
2940
+ }
2941
+ const edits = file.hunks.map((item) => ({
2942
+ old_string: item.oldLines.join('\n'),
2943
+ new_string: item.newLines.join('\n'),
2944
+ }));
2945
+ return {
2946
+ tool_name: edits.length > 1 ? 'MultiEdit' : 'Edit',
2947
+ tool_input: edits.length > 1
2948
+ ? { file_path: filePath, edits, patch: patchFragment }
2949
+ : { file_path: filePath, ...(edits[0] || { old_string: '', new_string: '' }), patch: patchFragment },
2950
+ __synkro_codex_operation: file.moveTarget ? 'rename' : 'modify',
2951
+ ...(file.moveTarget ? { __synkro_codex_move_target: file.moveTarget } : {}),
2952
+ };
2953
+ });
2954
+ payload.__synkro_codex_edits = normalized;
2955
+ Object.assign(payload, normalized[0]);
2767
2956
  } catch { /* leave payload untouched on any parse error */ }
2768
2957
  }
2769
2958
 
2959
+ type RecoveredCodexEdit = { payload: Record<string, any>; baseContent?: string };
2960
+
2961
+ function completedCodexToolRecord(entry: any): any {
2962
+ if (!entry || typeof entry !== 'object') return null;
2963
+ if (entry.type === 'response_item' && entry.payload && typeof entry.payload === 'object') {
2964
+ return entry.payload;
2965
+ }
2966
+ return entry.payload && typeof entry.payload === 'object' ? entry.payload : entry;
2967
+ }
2968
+
2969
+ function completedCodexToolInput(record: any): any {
2970
+ const raw = record?.input ?? record?.arguments ?? record?.command;
2971
+ if (raw && typeof raw === 'object') return raw;
2972
+ if (typeof raw !== 'string') return {};
2973
+ try {
2974
+ const parsed = JSON.parse(raw);
2975
+ if (parsed && typeof parsed === 'object') return parsed;
2976
+ if (typeof parsed === 'string') return { command: parsed };
2977
+ } catch {}
2978
+ return { command: raw };
2979
+ }
2980
+
2981
+ function containedCodexEditPath(repoRoot: string, filePath: string): string {
2982
+ if (!repoRoot || !filePath) return '';
2983
+ try {
2984
+ const lexicalRoot = resolve(repoRoot);
2985
+ const realRoot = realpathSync(lexicalRoot);
2986
+ const lexical = isAbsolute(filePath) ? resolve(filePath) : resolve(lexicalRoot, filePath);
2987
+ const isContained = (rel: string): boolean =>
2988
+ !!rel && rel !== '..' && !rel.startsWith('..' + '/') && !isAbsolute(rel);
2989
+ // Accept an absolute path expressed through either the lexical repo alias
2990
+ // (/var on macOS, a symlinked checkout) or its physical path, but always
2991
+ // continue from the physical root so new and existing files share one
2992
+ // canonical provenance path.
2993
+ let rel = relative(lexicalRoot, lexical);
2994
+ if (!isContained(rel)) {
2995
+ rel = relative(realRoot, lexical);
2996
+ if (!isContained(rel)) return '';
2997
+ }
2998
+ let candidate = resolve(realRoot, rel);
2999
+ // realpathSync requires the leaf to exist. Walk to the nearest existing
3000
+ // ancestor, resolve symlinks there, then append the missing suffix. This
3001
+ // both canonicalizes new files and rejects a new leaf beneath an in-repo
3002
+ // symlink that actually escapes the repository.
3003
+ const missing: string[] = [];
3004
+ let ancestor = candidate;
3005
+ while (!existsSync(ancestor)) {
3006
+ const parent = resolve(ancestor, '..');
3007
+ if (parent === ancestor) return '';
3008
+ missing.unshift(basename(ancestor));
3009
+ ancestor = parent;
3010
+ }
3011
+ candidate = resolve(realpathSync(ancestor), ...missing);
3012
+ const realRel = relative(realRoot, candidate);
3013
+ if (!realRel || realRel.startsWith('..') || isAbsolute(realRel)) return '';
3014
+ return existsSync(candidate) ? realpathSync(candidate) : candidate;
3015
+ } catch {
3016
+ return '';
3017
+ }
3018
+ }
3019
+
3020
+ /**
3021
+ * Recover completed Codex apply_patch calls from a bounded transcript tail.
3022
+ * A tool call is evidence only after a matching tool output exists. Calls are
3023
+ * expanded one event per file, restricted to the repo, and keyed by the stable
3024
+ * Codex call id so replay is idempotent server-side.
3025
+ */
3026
+ export function extractCompletedCodexPatchEdits(
3027
+ transcript: string,
3028
+ repoRoot: string,
3029
+ ): RecoveredCodexEdit[] {
3030
+ if (!transcript || !repoRoot) return [];
3031
+ const calls = new Map<string, { record: any; timestamp: string }>();
3032
+ const completed = new Set<string>();
3033
+ for (const line of transcript.split('\n')) {
3034
+ if (!line.trim()) continue;
3035
+ let entry: any;
3036
+ try { entry = JSON.parse(line); } catch { continue; }
3037
+ const record = completedCodexToolRecord(entry);
3038
+ const type = String(record?.type || '');
3039
+ const callId = String(record?.call_id || record?.tool_call_id || record?.id || '');
3040
+ if (!callId) continue;
3041
+ if (type === 'custom_tool_call_output' || type === 'function_call_output') {
3042
+ completed.add(callId);
3043
+ continue;
3044
+ }
3045
+ if (type !== 'custom_tool_call' && type !== 'function_call') continue;
3046
+ const name = String(record?.name || record?.tool_name || '');
3047
+ if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(name)) continue;
3048
+ calls.set(callId, {
3049
+ record,
3050
+ timestamp: String(entry?.timestamp || record?.timestamp || ''),
3051
+ });
3052
+ }
3053
+
3054
+ const results: RecoveredCodexEdit[] = [];
3055
+ const seen = new Set<string>();
3056
+ const candidates = [...calls.entries()].filter(([callId]) => completed.has(callId)).slice(-100);
3057
+ for (const [callId, call] of candidates) {
3058
+ const toolInput = completedCodexToolInput(call.record);
3059
+ const patch = String(toolInput?.command || toolInput?.patch || toolInput?.input || '');
3060
+ if (!patch || patch.length > 100000) continue;
3061
+ const raw: any = {
3062
+ cwd: repoRoot,
3063
+ tool_name: String(call.record?.name || call.record?.tool_name || 'apply_patch'),
3064
+ tool_input: toolInput,
3065
+ tool_call_id: callId,
3066
+ _ts: call.timestamp || undefined,
3067
+ };
3068
+ normalizeCodexApplyPatch(raw);
3069
+ const edits = Array.isArray(raw.__synkro_codex_edits) ? raw.__synkro_codex_edits : [];
3070
+ for (const edit of edits) {
3071
+ if (results.length >= 50) return results;
3072
+ const fp = containedCodexEditPath(repoRoot, filePathFromToolInput(edit?.tool_input));
3073
+ if (!fp) continue;
3074
+ const dedupeKey = callId + '\u0000' + fp;
3075
+ if (seen.has(dedupeKey)) continue;
3076
+ seen.add(dedupeKey);
3077
+ const payload: any = {
3078
+ cwd: repoRoot,
3079
+ tool_name: edit.tool_name,
3080
+ tool_input: { ...(edit.tool_input || {}), file_path: fp },
3081
+ tool_call_id: callId,
3082
+ _ts: call.timestamp || undefined,
3083
+ __synkro_codex_operation: edit.__synkro_codex_operation,
3084
+ __synkro_codex_move_target: edit.__synkro_codex_move_target,
3085
+ __synkro_edit_source: 'transcript',
3086
+ };
3087
+ const baseContent = gatherPreEditContentFromPost(
3088
+ fp,
3089
+ String(payload.tool_name || ''),
3090
+ payload.tool_input,
3091
+ String(payload.__synkro_codex_operation || ''),
3092
+ );
3093
+ if (baseContent === undefined) continue;
3094
+ results.push({
3095
+ payload,
3096
+ baseContent,
3097
+ });
3098
+ }
3099
+ }
3100
+ return results;
3101
+ }
3102
+
2770
3103
  interface StubOpts {
2771
3104
  needsFile?: boolean;
2772
3105
  needsTranscript?: boolean;
@@ -2776,6 +3109,115 @@ interface StubOpts {
2776
3109
  needsPlan?: boolean;
2777
3110
  telemetry?: boolean;
2778
3111
  harness?: string;
3112
+ postEdit?: boolean;
3113
+ /** Codex SubagentStart/SubagentStop report the parent as session_id and
3114
+ * the child as agent_id. Remap them to the ordinary child-session envelope. */
3115
+ subagent?: boolean;
3116
+ }
3117
+
3118
+ function isContainedFile(root: string, candidate: string): boolean {
3119
+ try {
3120
+ const rel = relative(root, candidate);
3121
+ return !!rel && !rel.startsWith('..') && !isAbsolute(rel) && statSync(candidate).isFile();
3122
+ } catch { return false; }
3123
+ }
3124
+
3125
+ function safeCodexSessionId(value: string): boolean {
3126
+ if (!value || value.length > 200) return false;
3127
+ for (const ch of value) {
3128
+ const code = ch.charCodeAt(0);
3129
+ const alphaNumeric = (code >= 48 && code <= 57)
3130
+ || (code >= 65 && code <= 90)
3131
+ || (code >= 97 && code <= 122);
3132
+ if (!alphaNumeric && ch !== '-' && ch !== '_') return false;
3133
+ }
3134
+ return true;
3135
+ }
3136
+
3137
+ function readJsonlHeader(path: string): string {
3138
+ let fd = -1;
3139
+ try {
3140
+ fd = openSync(path, 'r');
3141
+ const buf = Buffer.alloc(16384);
3142
+ const count = readSync(fd, buf, 0, buf.length, 0);
3143
+ const head = buf.subarray(0, count).toString('utf-8');
3144
+ const newline = head.indexOf('\n');
3145
+ return newline >= 0 ? head.slice(0, newline) : head;
3146
+ } catch { return ''; }
3147
+ finally { if (fd >= 0) { try { closeSync(fd); } catch {} } }
3148
+ }
3149
+
3150
+ /**
3151
+ * Codex hook payloads identify the rollout by session_id but do not always send
3152
+ * transcript_path. Resolve that ID only within CODEX_HOME/sessions (or the
3153
+ * default ~/.codex/sessions), then cache the exact contained file so subsequent
3154
+ * tool hooks do not recursively scan the rollout tree.
3155
+ */
3156
+ export function resolveHookTranscriptPath(payload: any, harness: string, sessionId: string): string {
3157
+ const explicit = [
3158
+ payload?.transcript_path,
3159
+ payload?.rollout_path,
3160
+ payload?.session_path,
3161
+ ].find((value) => typeof value === 'string' && value);
3162
+ if (typeof explicit === 'string') return explicit;
3163
+ if (harness !== 'codex' || !safeCodexSessionId(sessionId)) return '';
3164
+
3165
+ const homeRoots = [
3166
+ process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME) : '',
3167
+ resolve(join(HOME, '.codex')),
3168
+ ].filter((value, index, all) => !!value && all.indexOf(value) === index);
3169
+ const sessionRoots = homeRoots.map((root) => join(root, 'sessions')).filter((root) => existsSync(root));
3170
+ if (!sessionRoots.length) return '';
3171
+
3172
+ const cachePath = join(
3173
+ HOME,
3174
+ '.synkro',
3175
+ 'codex-transcript-' + createHash('sha256').update(sessionId).digest('hex').slice(0, 16),
3176
+ );
3177
+ try {
3178
+ const cached = resolve(readFileSync(cachePath, 'utf-8').trim());
3179
+ if (sessionRoots.some((root) => isContainedFile(root, cached))) return cached;
3180
+ } catch {}
3181
+
3182
+ const candidates: string[] = [];
3183
+ for (const root of sessionRoots) {
3184
+ let entries: string[] = [];
3185
+ try { entries = readdirSync(root, { recursive: true, encoding: 'utf-8' }) as string[]; } catch {}
3186
+ for (const entry of entries) {
3187
+ if (!entry.endsWith('.jsonl')) continue;
3188
+ const candidate = resolve(join(root, entry));
3189
+ if (isContainedFile(root, candidate)) candidates.push(candidate);
3190
+ }
3191
+ }
3192
+
3193
+ let found = candidates.find((candidate) => candidate.includes(sessionId)) || '';
3194
+ if (!found) {
3195
+ const recent = candidates
3196
+ .map((candidate) => {
3197
+ try { return { candidate, mtime: statSync(candidate).mtimeMs }; }
3198
+ catch { return { candidate, mtime: 0 }; }
3199
+ })
3200
+ .sort((a, b) => b.mtime - a.mtime)
3201
+ .slice(0, 100);
3202
+ for (const item of recent) {
3203
+ try {
3204
+ const first = JSON.parse(readJsonlHeader(item.candidate));
3205
+ const candidateId = String(first?.payload?.session_id || first?.payload?.id || '');
3206
+ if (first?.type === 'session_meta' && candidateId === sessionId) {
3207
+ found = item.candidate;
3208
+ break;
3209
+ }
3210
+ } catch {}
3211
+ }
3212
+ }
3213
+
3214
+ if (found) {
3215
+ try {
3216
+ mkdirSync(join(HOME, '.synkro'), { recursive: true });
3217
+ writeFileSync(cachePath, found, { encoding: 'utf-8', mode: 0o600 });
3218
+ } catch {}
3219
+ }
3220
+ return found;
2779
3221
  }
2780
3222
 
2781
3223
  export async function runStub(surface: string, opts: StubOpts = {}): Promise<void> {
@@ -2792,6 +3234,17 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
2792
3234
  // Codex: rewrite apply_patch tool_input into the CC edit shape before any
2793
3235
  // surface reads it (edit/cwe/cve). No-op for CC/Cursor and non-edit tools.
2794
3236
  if (harness === 'codex') normalizeCodexApplyPatch(payload);
3237
+ if (harness === 'codex' && opts.subagent) {
3238
+ const parentSessionId = String(payload?.session_id || '');
3239
+ const childSessionId = String(payload?.agent_id || '');
3240
+ if (parentSessionId && childSessionId) {
3241
+ payload.parent_session_id = parentSessionId;
3242
+ payload.session_id = childSessionId;
3243
+ if (!payload.transcript_path && payload.agent_transcript_path) {
3244
+ payload.transcript_path = payload.agent_transcript_path;
3245
+ }
3246
+ }
3247
+ }
2795
3248
  telemPayload = payload;
2796
3249
  telemCwd = (typeof payload?.cwd === 'string' ? payload.cwd : '') || '';
2797
3250
  telemSessionId = String(payload?.session_id || payload?.conversation_id || '');
@@ -2862,14 +3315,40 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
2862
3315
  const envelope: any = { payload, harness, cwd: root || cwd, sessionId, synkroFileText };
2863
3316
 
2864
3317
  if (opts.needsFile) {
2865
- const fp = filePathFromToolInput(payload.tool_input || {});
2866
- if (fp && existsSync(fp)) {
2867
- const bc = gatherBaseContent(fp, payload.tool_input || {});
2868
- if (bc !== undefined) envelope.baseContent = bc;
3318
+ const normalizedEdits = harness === 'codex' && Array.isArray(payload.__synkro_codex_edits)
3319
+ ? payload.__synkro_codex_edits
3320
+ : [{ tool_name: payload.tool_name, tool_input: payload.tool_input }];
3321
+ const editBatch = normalizedEdits.map((edit: any) => {
3322
+ const childPayload = {
3323
+ ...payload,
3324
+ tool_name: edit.tool_name,
3325
+ tool_input: edit.tool_input,
3326
+ __synkro_codex_operation: edit.__synkro_codex_operation,
3327
+ __synkro_codex_move_target: edit.__synkro_codex_move_target,
3328
+ };
3329
+ delete childPayload.__synkro_codex_edits;
3330
+ const fp = filePathFromToolInput(childPayload.tool_input || {});
3331
+ const canRead = fp && (existsSync(fp) || (opts.postEdit && childPayload.__synkro_codex_operation === 'add'));
3332
+ const bc = canRead
3333
+ ? (opts.postEdit
3334
+ ? gatherPreEditContentFromPost(
3335
+ fp,
3336
+ String(childPayload.tool_name || ''),
3337
+ childPayload.tool_input || {},
3338
+ String(childPayload.__synkro_codex_operation || ''),
3339
+ )
3340
+ : gatherBaseContent(fp, childPayload.tool_input || {}))
3341
+ : undefined;
3342
+ return { payload: childPayload, ...(bc !== undefined ? { baseContent: bc } : {}) };
3343
+ });
3344
+ if (editBatch.length) {
3345
+ envelope.editBatch = editBatch;
3346
+ envelope.payload = editBatch[0].payload;
3347
+ if (editBatch[0].baseContent !== undefined) envelope.baseContent = editBatch[0].baseContent;
2869
3348
  }
2870
3349
  }
2871
3350
  if (opts.needsTranscript) {
2872
- const tp = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';
3351
+ const tp = resolveHookTranscriptPath(payload, harness, sessionId);
2873
3352
  if (tp && existsSync(tp)) {
2874
3353
  try {
2875
3354
  const t = readFileSync(tp, 'utf-8');
@@ -2881,6 +3360,10 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
2881
3360
  // by ts, so we no longer depend on the whole file for absolute message indices.
2882
3361
  const cap = opts.fullTranscript ? 400000 : 200000;
2883
3362
  envelope.transcript = t.length <= cap ? t : t.slice(t.length - cap);
3363
+ if (harness === 'codex') {
3364
+ const recovered = extractCompletedCodexPatchEdits(envelope.transcript, root || cwd);
3365
+ if (recovered.length) envelope.transcriptEdits = recovered;
3366
+ }
2884
3367
  } catch {}
2885
3368
  }
2886
3369
  }
@@ -2896,7 +3379,8 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
2896
3379
  // prompt-submit is the opposite: it's INTERACTIVE (blocks the user's prompt from
2897
3380
  // being sent) under a short 5s hook timeout, so it gets ONE short attempt (below)
2898
3381
  // and a tight per-attempt budget that fits inside 5s — never the 6s×3 telemetry path.
2899
- const timeoutMs = surface === 'bash-followup' ? 32000
3382
+ const timeoutMs = surface === 'cwe-precheck' ? 48000
3383
+ : surface === 'bash-followup' ? 32000
2900
3384
  : surface === 'prompt-submit' ? 3500
2901
3385
  : (opts.telemetry ? 6000 : 28000);
2902
3386
  // Cloud has no local container to reach. Post the SAME envelope to the org's grader
@@ -3061,8 +3545,16 @@ function toolInputSummary(toolName: string, toolInput: any, includeCommand: bool
3061
3545
  // cc_model is derived by the caller (bounded tail read) and passed in; this stays
3062
3546
  // allocation-only so the dormant hot path never touches the transcript.
3063
3547
  function telemPayloadOpts(payload: any, harness: string, cwd: string, sessionId: string, ccModel?: string): any {
3548
+ const normalizedHarness = String(harness || '').trim().toLowerCase().replace(/[\\s-]+/g, '_');
3549
+ const agentKind = normalizedHarness === 'cursor'
3550
+ ? 'cursor'
3551
+ : normalizedHarness === 'codex'
3552
+ ? 'codex'
3553
+ : normalizedHarness === 'cc' || normalizedHarness === 'claude_code'
3554
+ ? 'claude_code'
3555
+ : undefined;
3064
3556
  return {
3065
- agent_kind: harness === 'cursor' ? 'cursor' : harness === 'codex' ? 'codex' : 'claude_code',
3557
+ agent_kind: agentKind,
3066
3558
  cc_session_id: sessionId || undefined,
3067
3559
  cc_tool_use_id: payload?.tool_use_id ? String(payload.tool_use_id) : undefined,
3068
3560
  cc_model: ccModel || undefined,
@@ -3164,6 +3656,7 @@ function emitStubTelemetry(
3164
3656
  }
3165
3657
  `;
3166
3658
  STUB_EDIT_PRECHECK_TS = stubHook("edit-precheck", "{ needsFile: true, needsTranscript: true }");
3659
+ STUB_EDIT_FOLLOWUP_TS = stubHook("edit-followup", "{ needsFile: true, needsTranscript: true, postEdit: true }");
3167
3660
  STUB_CWE_PRECHECK_TS = stubHook("cwe-precheck", "{ needsFile: true, needsTranscript: true }");
3168
3661
  STUB_CVE_PRECHECK_TS = stubHook("cve-precheck", "{ needsFile: true, needsTranscript: true }");
3169
3662
  STUB_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true }");
@@ -3175,6 +3668,8 @@ function emitStubTelemetry(
3175
3668
  STUB_STOP_SUMMARY_TS = stubHook("stop-summary", "{ needsTranscript: true, fullTranscript: true }");
3176
3669
  STUB_SESSION_START_TS = stubHook("session-start", "{ telemetry: true }");
3177
3670
  STUB_TRANSCRIPT_SYNC_TS = stubHook("transcript-sync", "{ needsTranscript: true, fullTranscript: true, telemetry: true }");
3671
+ STUB_SUBAGENT_START_TS = stubHook("subagent-start", "{ telemetry: true, subagent: true }");
3672
+ STUB_SUBAGENT_STOP_TS = stubHook("subagent-stop", "{ needsTranscript: true, fullTranscript: true, telemetry: true, subagent: true }");
3178
3673
  STUB_USER_PROMPT_SUBMIT_TS = stubHook("prompt-submit", "{ telemetry: true }");
3179
3674
  STUB_BASH_FOLLOWUP_TS = stubHook("bash-followup", "{ telemetry: true }");
3180
3675
  STUB_PROMPT_ROUTE_TS = `#!/usr/bin/env bun
@@ -3307,7 +3802,7 @@ try {
3307
3802
  const resp = await fetch('http://127.0.0.1:' + (process.env.SYNKRO_GRADER_HOST_PORT || '18929') + '/submit', {
3308
3803
  method: 'POST',
3309
3804
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
3310
- body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x', agent_kind: 'claude_code', hedge: false }),
3805
+ body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x', hedge: false }),
3311
3806
  signal: AbortSignal.timeout(1000),
3312
3807
  });
3313
3808
  if (resp.ok) {
@@ -3346,8 +3841,14 @@ const PORT = process.env.SYNKRO_MCP_PORT || '18931';
3346
3841
  function mcpJwt(): string { try { return readFileSync(join(homedir(), '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; } }
3347
3842
  const chunks: Buffer[] = [];
3348
3843
  for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3844
+ let originalToolInput: Record<string, unknown> = {};
3845
+ let hookEventName = 'PreToolUse';
3349
3846
  try {
3350
3847
  const payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');
3848
+ hookEventName = String(payload.hook_event_name || payload.hookEventName || 'PreToolUse');
3849
+ if (payload.tool_input && typeof payload.tool_input === 'object' && !Array.isArray(payload.tool_input)) {
3850
+ originalToolInput = payload.tool_input;
3851
+ }
3351
3852
  await fetch('http://127.0.0.1:' + PORT + '/api/local/task-activate-intent', {
3352
3853
  method: 'POST',
3353
3854
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
@@ -3355,7 +3856,29 @@ try {
3355
3856
  signal: AbortSignal.timeout(5000),
3356
3857
  });
3357
3858
  } catch {}
3358
- process.stdout.write('{}\\n');
3859
+ // PreToolUse enforcement and Codex's native approval are separate lifecycle
3860
+ // stages. The policy gate already allowed this exact activate_standard call;
3861
+ // approve its subsequent PermissionRequest so headless approval_policy=never
3862
+ // can execute it. This script is registered only for the activation matcher,
3863
+ // never as a blanket MCP approval hook.
3864
+ if (process.env.SYNKRO_HOOK_FORMAT === 'codex' && hookEventName === 'PermissionRequest') {
3865
+ process.stdout.write(JSON.stringify({
3866
+ hookSpecificOutput: {
3867
+ hookEventName: 'PermissionRequest',
3868
+ decision: { behavior: 'allow' },
3869
+ },
3870
+ }) + '\\n');
3871
+ } else if (process.env.SYNKRO_HOOK_FORMAT === 'codex') {
3872
+ process.stdout.write(JSON.stringify({
3873
+ hookSpecificOutput: {
3874
+ hookEventName: 'PreToolUse',
3875
+ permissionDecision: 'allow',
3876
+ updatedInput: originalToolInput,
3877
+ },
3878
+ }) + '\\n');
3879
+ } else {
3880
+ process.stdout.write('{}\\n');
3881
+ }
3359
3882
  `;
3360
3883
  STUB_CURSOR_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true, harness: 'cursor' }");
3361
3884
  STUB_CURSOR_SKILL_JUDGE_TS = stubHook("skill-judge", "{ needsFile: true, needsTranscript: true, harness: 'cursor' }");
@@ -3382,9 +3905,9 @@ __export(stub_exports, {
3382
3905
  saveCredentials: () => saveCredentials
3383
3906
  });
3384
3907
  import { createServer } from "http";
3385
- import { writeFileSync as writeFileSync11, readFileSync as readFileSync13, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync5 } from "fs";
3386
- import { homedir as homedir13, platform as platform2 } from "os";
3387
- import { join as join11, dirname as dirname5 } from "path";
3908
+ import { writeFileSync as writeFileSync11, readFileSync as readFileSync14, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync5 } from "fs";
3909
+ import { homedir as homedir14, platform as platform2 } from "os";
3910
+ import { join as join12, dirname as dirname5 } from "path";
3388
3911
  import { execFile } from "child_process";
3389
3912
  import jwt from "jsonwebtoken";
3390
3913
  function openBrowser(url) {
@@ -3423,7 +3946,7 @@ function loadCredentials() {
3423
3946
  return null;
3424
3947
  }
3425
3948
  try {
3426
- const content = readFileSync13(AUTH_FILE, "utf8");
3949
+ const content = readFileSync14(AUTH_FILE, "utf8");
3427
3950
  return JSON.parse(content);
3428
3951
  } catch (error) {
3429
3952
  return null;
@@ -3711,7 +4234,7 @@ var init_stub = __esm({
3711
4234
  PORT = 8100;
3712
4235
  RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;
3713
4236
  SYNKRO_WEB_AUTH_URL = RAW_WEB_AUTH_URL && /^https?:\/\//.test(RAW_WEB_AUTH_URL) ? RAW_WEB_AUTH_URL : "https://app.synkro.sh";
3714
- AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join11(homedir13(), ".synkro", "credentials.json");
4237
+ AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join12(homedir14(), ".synkro", "credentials.json");
3715
4238
  RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
3716
4239
  SYNKRO_API_URL = RAW_API_URL && /^https?:\/\//.test(RAW_API_URL) ? RAW_API_URL : "https://api.synkro.sh";
3717
4240
  ERROR_HTML = `
@@ -3948,9 +4471,9 @@ __export(claudeDesktopTap_exports, {
3948
4471
  runClaudeDesktopTap: () => runClaudeDesktopTap
3949
4472
  });
3950
4473
  import { spawn as spawn2 } from "child_process";
3951
- import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as readFileSync14, existsSync as existsSync17 } from "fs";
3952
- import { join as join12 } from "path";
3953
- import { homedir as homedir14 } from "os";
4474
+ import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
4475
+ import { join as join13 } from "path";
4476
+ import { homedir as homedir15 } from "os";
3954
4477
  function claudeDesktopInstalled() {
3955
4478
  return process.platform === "darwin" && existsSync17("/Applications/Claude.app/Contents/MacOS/Claude");
3956
4479
  }
@@ -4066,7 +4589,7 @@ async function runClaudeDesktopTap(opts = {}) {
4066
4589
  }
4067
4590
  let token = "";
4068
4591
  try {
4069
- token = readFileSync14(JWT_PATH, "utf-8").trim();
4592
+ token = readFileSync15(JWT_PATH, "utf-8").trim();
4070
4593
  } catch {
4071
4594
  }
4072
4595
  if (!token) {
@@ -4087,13 +4610,13 @@ async function runClaudeDesktopTap(opts = {}) {
4087
4610
  } catch (err) {
4088
4611
  console.log(` \u26A0 Could not register Claude Desktop MCP: ${err.message}`);
4089
4612
  }
4090
- const cdRoot = join12(homedir14(), ".synkro", "cd-sessions");
4613
+ const cdRoot = join13(homedir15(), ".synkro", "cd-sessions");
4091
4614
  mkdirSync10(cdRoot, { recursive: true });
4092
- const sessionDir = mkdtempSync(join12(cdRoot, "synkro-cd-"));
4093
- writeFileSync12(join12(sessionDir, "tap.py"), ADDON_PY, "utf-8");
4094
- writeFileSync12(join12(sessionDir, "mcp_proxy.py"), MCP_PROXY_PY, { mode: 493 });
4095
- writeFileSync12(join12(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
4096
- const runnerPath = join12(sessionDir, "run.sh");
4615
+ const sessionDir = mkdtempSync(join13(cdRoot, "synkro-cd-"));
4616
+ writeFileSync12(join13(sessionDir, "tap.py"), ADDON_PY, "utf-8");
4617
+ writeFileSync12(join13(sessionDir, "mcp_proxy.py"), MCP_PROXY_PY, { mode: 493 });
4618
+ writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
4619
+ const runnerPath = join13(sessionDir, "run.sh");
4097
4620
  writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
4098
4621
  await new Promise((resolve6) => {
4099
4622
  const child = spawn2("bash", [runnerPath], {
@@ -4127,7 +4650,7 @@ var init_claudeDesktopTap = __esm({
4127
4650
  TURN_VERDICTS_URL = "http://127.0.0.1:18931/api/local/turn-verdicts";
4128
4651
  TURN_VERDICT_URL = "http://127.0.0.1:18931/api/local/turn-verdict";
4129
4652
  MCP_EVENT_URL = "http://127.0.0.1:18931/api/local/mcp/event";
4130
- JWT_PATH = join12(homedir14(), ".synkro", ".mcp-jwt");
4653
+ JWT_PATH = join13(homedir15(), ".synkro", ".mcp-jwt");
4131
4654
  ADDON_PY = `import os, json, threading, urllib.request, urllib.error, re, base64, time, hashlib
4132
4655
  from mitmproxy import http
4133
4656
 
@@ -4264,6 +4787,7 @@ def _emit_mcp_event(server, tool, ev_type, eid, convo_id=""):
4264
4787
  "tool_name": (str(tool)[:120] if tool else None),
4265
4788
  "decision": ("allowed" if ev_type == "tool_call" else None),
4266
4789
  "repo": "claude-desktop",
4790
+ "harness": "claude_desktop",
4267
4791
  }).encode("utf-8")
4268
4792
  _post_json(MCP_EVENT_URL, payload, 5)
4269
4793
  except Exception:
@@ -5243,9 +5767,9 @@ __export(macKeychain_exports, {
5243
5767
  writeCursorApiKey: () => writeCursorApiKey,
5244
5768
  writeRefreshAgent: () => writeRefreshAgent
5245
5769
  });
5246
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as readFileSync15 } from "fs";
5247
- import { homedir as homedir15, platform as platform3 } from "os";
5248
- import { join as join13 } from "path";
5770
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as readFileSync16 } from "fs";
5771
+ import { homedir as homedir16, platform as platform3 } from "os";
5772
+ import { join as join14 } from "path";
5249
5773
  import { spawnSync as spawnSync2 } from "child_process";
5250
5774
  function needsKeychainBridge() {
5251
5775
  return platform3() === "darwin";
@@ -5268,7 +5792,7 @@ function exportKeychainCreds() {
5268
5792
  let changed = true;
5269
5793
  try {
5270
5794
  if (existsSync18(CLAUDE_CREDS_FILE)) {
5271
- changed = readFileSync15(CLAUDE_CREDS_FILE, "utf-8") !== blob;
5795
+ changed = readFileSync16(CLAUDE_CREDS_FILE, "utf-8") !== blob;
5272
5796
  }
5273
5797
  } catch {
5274
5798
  }
@@ -5278,7 +5802,7 @@ function exportKeychainCreds() {
5278
5802
  }
5279
5803
  function cursorApiKeyConfigured() {
5280
5804
  try {
5281
- return existsSync18(CURSOR_API_KEY_FILE) && readFileSync15(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
5805
+ return existsSync18(CURSOR_API_KEY_FILE) && readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
5282
5806
  } catch {
5283
5807
  return false;
5284
5808
  }
@@ -5294,7 +5818,7 @@ function writeCursorApiKey(key) {
5294
5818
  async function validateCursorApiKey() {
5295
5819
  let key;
5296
5820
  try {
5297
- key = readFileSync15(CURSOR_API_KEY_FILE, "utf-8").trim();
5821
+ key = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
5298
5822
  } catch {
5299
5823
  return null;
5300
5824
  }
@@ -5315,7 +5839,7 @@ async function validateCursorApiKey() {
5315
5839
  function credsAreStale() {
5316
5840
  if (!existsSync18(CLAUDE_CREDS_FILE)) return true;
5317
5841
  try {
5318
- const raw = readFileSync15(CLAUDE_CREDS_FILE, "utf-8");
5842
+ const raw = readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
5319
5843
  const exp = JSON.parse(raw)?.claudeAiOauth?.expiresAt ?? 0;
5320
5844
  if (!exp) return true;
5321
5845
  return Date.now() >= exp - REFRESH_EXPIRY_BUFFER_SECONDS * 1e3;
@@ -5330,7 +5854,7 @@ function writeRefreshAgent(synkroBinPath) {
5330
5854
  if (platform3() !== "darwin") {
5331
5855
  throw new KeychainExportError("writeRefreshAgent is darwin-only");
5332
5856
  }
5333
- mkdirSync11(join13(homedir15(), "Library", "LaunchAgents"), { recursive: true });
5857
+ mkdirSync11(join14(homedir16(), "Library", "LaunchAgents"), { recursive: true });
5334
5858
  mkdirSync11(SYNKRO_DIR6, { recursive: true });
5335
5859
  const script = `#!/bin/bash
5336
5860
  # Generated by synkro (writeRefreshAgent). Expiry-aware Claude creds refresher.
@@ -5427,7 +5951,7 @@ function refreshCreds() {
5427
5951
  }
5428
5952
  function readExportedCreds() {
5429
5953
  try {
5430
- return readFileSync15(CLAUDE_CREDS_FILE, "utf-8");
5954
+ return readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
5431
5955
  } catch {
5432
5956
  return null;
5433
5957
  }
@@ -5436,16 +5960,16 @@ var SYNKRO_DIR6, CLAUDE_CREDS_DIR, CLAUDE_CREDS_FILE, CURSOR_CREDS_DIR, CURSOR_A
5436
5960
  var init_macKeychain = __esm({
5437
5961
  "cli/local-cc/macKeychain.ts"() {
5438
5962
  "use strict";
5439
- SYNKRO_DIR6 = join13(homedir15(), ".synkro");
5440
- CLAUDE_CREDS_DIR = join13(SYNKRO_DIR6, "claude-creds");
5441
- CLAUDE_CREDS_FILE = join13(CLAUDE_CREDS_DIR, ".credentials.json");
5442
- CURSOR_CREDS_DIR = join13(SYNKRO_DIR6, "cursor-creds");
5443
- CURSOR_API_KEY_FILE = join13(CURSOR_CREDS_DIR, "api-key");
5963
+ SYNKRO_DIR6 = join14(homedir16(), ".synkro");
5964
+ CLAUDE_CREDS_DIR = join14(SYNKRO_DIR6, "claude-creds");
5965
+ CLAUDE_CREDS_FILE = join14(CLAUDE_CREDS_DIR, ".credentials.json");
5966
+ CURSOR_CREDS_DIR = join14(SYNKRO_DIR6, "cursor-creds");
5967
+ CURSOR_API_KEY_FILE = join14(CURSOR_CREDS_DIR, "api-key");
5444
5968
  KEYCHAIN_SERVICE = "Claude Code-credentials";
5445
5969
  LAUNCHD_LABEL = "com.synkro.cli.claude-creds-refresh";
5446
- LAUNCHD_PLIST = join13(homedir15(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
5447
- REFRESH_SCRIPT = join13(SYNKRO_DIR6, "claude-creds-refresh-loop.sh");
5448
- REFRESH_LOG = join13(SYNKRO_DIR6, "claude-creds-refresh.log");
5970
+ LAUNCHD_PLIST = join14(homedir16(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
5971
+ REFRESH_SCRIPT = join14(SYNKRO_DIR6, "claude-creds-refresh-loop.sh");
5972
+ REFRESH_LOG = join14(SYNKRO_DIR6, "claude-creds-refresh.log");
5449
5973
  REFRESH_EXPIRY_BUFFER_SECONDS = 120;
5450
5974
  MIN_REFRESH_INTERVAL_SECONDS = 30;
5451
5975
  MAX_REFRESH_INTERVAL_SECONDS = 5 * 60;
@@ -5464,8 +5988,11 @@ var init_macKeychain = __esm({
5464
5988
  var dockerInstall_exports = {};
5465
5989
  __export(dockerInstall_exports, {
5466
5990
  DockerInstallError: () => DockerInstallError,
5991
+ HOST_PGLITE_PORT: () => HOST_PGLITE_PORT,
5992
+ PGLITE_PASSWORD_PATH: () => PGLITE_PASSWORD_PATH,
5467
5993
  SYNKRO_DIR: () => SYNKRO_DIR7,
5468
5994
  assertDockerAvailable: () => assertDockerAvailable,
5995
+ createPgliteScramVerifier: () => createPgliteScramVerifier,
5469
5996
  dockerInstall: () => dockerInstall,
5470
5997
  dockerRemove: () => dockerRemove,
5471
5998
  dockerSafeRestart: () => dockerSafeRestart,
@@ -5474,21 +6001,71 @@ __export(dockerInstall_exports, {
5474
6001
  dockerStatus: () => dockerStatus,
5475
6002
  dockerStop: () => dockerStop,
5476
6003
  dockerUpdate: () => dockerUpdate,
6004
+ ensurePgliteProxyCredentials: () => ensurePgliteProxyCredentials,
5477
6005
  imageTag: () => imageTag,
5478
6006
  normalizeProvider: () => normalizeProvider,
5479
6007
  poolLabel: () => poolLabel,
5480
6008
  readContainerConfig: () => readContainerConfig,
5481
6009
  resolveConductorProvider: () => resolveConductorProvider,
6010
+ resolveContainerName: () => resolveContainerName,
5482
6011
  resolveGraderPool: () => resolveGraderPool,
5483
6012
  resolveWorkerConfig: () => resolveWorkerConfig,
5484
6013
  splitWorkers: () => splitWorkers,
5485
6014
  waitForContainerReady: () => waitForContainerReady,
5486
6015
  waitForWorkersReady: () => waitForWorkersReady
5487
6016
  });
5488
- import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync16, readdirSync as readdirSync2 } from "fs";
5489
- import { homedir as homedir16 } from "os";
5490
- import { join as join14 } from "path";
6017
+ import {
6018
+ chmodSync as chmodSync3,
6019
+ copyFileSync,
6020
+ existsSync as existsSync19,
6021
+ mkdirSync as mkdirSync12,
6022
+ readFileSync as readFileSync17,
6023
+ readdirSync as readdirSync2,
6024
+ renameSync as renameSync7,
6025
+ writeFileSync as writeFileSync14
6026
+ } from "fs";
6027
+ import { createHash as createHash2, createHmac, pbkdf2Sync, randomBytes as randomBytes2 } from "crypto";
6028
+ import { homedir as homedir17 } from "os";
6029
+ import { join as join15 } from "path";
5491
6030
  import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
6031
+ function resolveContainerName(raw = process.env.SYNKRO_CONTAINER_NAME) {
6032
+ const value = String(raw || "").trim();
6033
+ if (!value) return "synkro-server";
6034
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(value)) {
6035
+ throw new Error("SYNKRO_CONTAINER_NAME must be a valid Docker container name (1-128 letters, digits, _, ., or -)");
6036
+ }
6037
+ return value;
6038
+ }
6039
+ function createPgliteScramVerifier(password, salt = randomBytes2(16)) {
6040
+ const iterations = 4096;
6041
+ const saltedPassword = pbkdf2Sync(password, salt, iterations, 32, "sha256");
6042
+ const clientKey = createHmac("sha256", saltedPassword).update("Client Key").digest();
6043
+ const storedKey = createHash2("sha256").update(clientKey).digest();
6044
+ const serverKey = createHmac("sha256", saltedPassword).update("Server Key").digest();
6045
+ return `SCRAM-SHA-256$${iterations}:${salt.toString("base64")}$${storedKey.toString("base64")}:${serverKey.toString("base64")}`;
6046
+ }
6047
+ function ensurePgliteProxyCredentials() {
6048
+ const hasPassword = existsSync19(PGLITE_PASSWORD_PATH) && readFileSync17(PGLITE_PASSWORD_PATH, "utf-8").trim().length > 0;
6049
+ const hasUserlist = existsSync19(PGLITE_USERLIST_PATH) && readFileSync17(PGLITE_USERLIST_PATH, "utf-8").trim().length > 0;
6050
+ if (hasPassword && hasUserlist) {
6051
+ chmodSync3(PGLITE_PASSWORD_PATH, 384);
6052
+ chmodSync3(PGLITE_USERLIST_PATH, 384);
6053
+ return;
6054
+ }
6055
+ const password = randomBytes2(24).toString("base64url");
6056
+ const verifier = createPgliteScramVerifier(password);
6057
+ const suffix = `${process.pid}.${Date.now()}.tmp`;
6058
+ const passwordTmp = `${PGLITE_PASSWORD_PATH}.${suffix}`;
6059
+ const userlistTmp = `${PGLITE_USERLIST_PATH}.${suffix}`;
6060
+ writeFileSync14(passwordTmp, `${password}
6061
+ `, { mode: 384 });
6062
+ writeFileSync14(userlistTmp, `"synkro" "${verifier}"
6063
+ `, { mode: 384 });
6064
+ renameSync7(passwordTmp, PGLITE_PASSWORD_PATH);
6065
+ renameSync7(userlistTmp, PGLITE_USERLIST_PATH);
6066
+ chmodSync3(PGLITE_PASSWORD_PATH, 384);
6067
+ chmodSync3(PGLITE_USERLIST_PATH, 384);
6068
+ }
5492
6069
  function resolveConductorProvider(pool, counts) {
5493
6070
  if (pool !== "auto") return pool;
5494
6071
  const ranked = [
@@ -5597,9 +6174,9 @@ function readSynkroFileConfig() {
5597
6174
  try {
5598
6175
  const root = execSync3("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
5599
6176
  if (!root) return { pool: "auto", warnings: [] };
5600
- const fp = join14(root, "synkro.toml");
6177
+ const fp = join15(root, "synkro.toml");
5601
6178
  if (!existsSync19(fp)) return { pool: "auto", warnings: [] };
5602
- const parsed = parseSynkroToml(readFileSync16(fp, "utf-8"));
6179
+ const parsed = parseSynkroToml(readFileSync17(fp, "utf-8"));
5603
6180
  return resolveGraderPool(parsed);
5604
6181
  } catch {
5605
6182
  }
@@ -5682,7 +6259,7 @@ function assertDockerAvailable() {
5682
6259
  }
5683
6260
  function claudeCredsHostDir() {
5684
6261
  if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;
5685
- return join14(homedir16(), ".claude");
6262
+ return join15(homedir17(), ".claude");
5686
6263
  }
5687
6264
  function resolveSynkroBin() {
5688
6265
  const which2 = spawnSync3("which", ["synkro"], { encoding: "utf-8", timeout: 5e3 });
@@ -5741,16 +6318,17 @@ async function dockerInstall(opts = {}) {
5741
6318
  const usesClaude = claudeWorkers > 0 || conductorProvider === "claude_code";
5742
6319
  const usesCursor = cursorWorkers > 0 || conductorProvider === "cursor";
5743
6320
  const usesCodex = codexWorkers > 0 || conductorProvider === "codex";
5744
- const codexHomeDir = opts.codexHomeDir ?? join14(SYNKRO_DIR7, "codex-local-session");
5745
- if (usesCodex && !existsSync19(join14(codexHomeDir, "auth.json"))) {
6321
+ const codexHomeDir = opts.codexHomeDir ?? join15(SYNKRO_DIR7, "codex-local-session");
6322
+ if (usesCodex && !existsSync19(join15(codexHomeDir, "auth.json"))) {
5746
6323
  throw new DockerInstallError(
5747
6324
  "Codex grader credentials are missing. Re-run `synkro install` to authorize an isolated Codex session."
5748
6325
  );
5749
6326
  }
5750
6327
  mkdirSync12(PGDATA_PATH, { recursive: true });
5751
6328
  mkdirSync12(BACKUP_DIR, { recursive: true });
6329
+ ensurePgliteProxyCredentials();
5752
6330
  mkdirSync12(CLAUDE_HOST_STATE_DIR, { recursive: true });
5753
- const hostClaudeJson = join14(homedir16(), ".claude.json");
6331
+ const hostClaudeJson = join15(homedir17(), ".claude.json");
5754
6332
  if (existsSync19(hostClaudeJson)) {
5755
6333
  copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);
5756
6334
  }
@@ -5783,7 +6361,7 @@ async function dockerInstall(opts = {}) {
5783
6361
  console.warn(` Plist written to ${plist} \u2014 load manually with launchctl bootstrap when ready.`);
5784
6362
  }
5785
6363
  } else {
5786
- mkdirSync12(join14(homedir16(), ".claude"), { recursive: true });
6364
+ mkdirSync12(join15(homedir17(), ".claude"), { recursive: true });
5787
6365
  }
5788
6366
  const imageExistsLocally = () => spawnSync3("docker", ["image", "inspect", image], { stdio: "ignore", timeout: 3e4 }).status === 0;
5789
6367
  const skipPull = process.env.SYNKRO_SKIP_PULL === "1" || process.env.SYNKRO_SKIP_PULL === "true";
@@ -5822,7 +6400,7 @@ async function dockerInstall(opts = {}) {
5822
6400
  "-p",
5823
6401
  `127.0.0.1:${HOST_CWE_PORT}:8930`,
5824
6402
  "-p",
5825
- `127.0.0.1:${HOST_PGLITE_PORT}:5433`,
6403
+ `127.0.0.1:${HOST_PGLITE_PORT}:5434`,
5826
6404
  "-v",
5827
6405
  `${PGDATA_PATH}:/data/pgdata`,
5828
6406
  "-v",
@@ -5836,7 +6414,7 @@ async function dockerInstall(opts = {}) {
5836
6414
  "-v",
5837
6415
  `${credsDir}:/home/synkro/.claude:rw`,
5838
6416
  "-v",
5839
- `${join14(homedir16(), ".claude")}:/data/claude-host:ro`,
6417
+ `${join15(homedir17(), ".claude")}:/data/claude-host:ro`,
5840
6418
  "-v",
5841
6419
  `${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,
5842
6420
  // Cursor creds — mounted RW so the in-container refresher can rotate the
@@ -5856,6 +6434,8 @@ async function dockerInstall(opts = {}) {
5856
6434
  // Pass through the batch-size lever if the operator set it. Defaults
5857
6435
  // inside the container to 5; clamped to [1, 20] by synkro-server.ts.
5858
6436
  ...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
6437
+ // Full verifier prompt/response tracing is explicit opt-in because it contains source code.
6438
+ ...process.env.SYNKRO_VERIFY_TRACE === "1" ? ["-e", "SYNKRO_VERIFY_TRACE=1"] : [],
5859
6439
  // Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.
5860
6440
  ...process.env.SYNKRO_CURSOR_MODEL ? ["-e", `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`] : [],
5861
6441
  // Fix-poll kill switch. Default ON in the image; a benchmark/headless run
@@ -5887,7 +6467,14 @@ async function dockerInstall(opts = {}) {
5887
6467
  if (run.status !== 0) {
5888
6468
  throw new DockerInstallError(`docker run failed (image ${image})`);
5889
6469
  }
5890
- return { image, hostMcpPort: HOST_MCP_PORT, hostGraderPort: HOST_GRADER_PORT, hostCwePort: HOST_CWE_PORT, hostPglitePort: HOST_PGLITE_PORT };
6470
+ return {
6471
+ image,
6472
+ hostMcpPort: HOST_MCP_PORT,
6473
+ hostGraderPort: HOST_GRADER_PORT,
6474
+ hostCwePort: HOST_CWE_PORT,
6475
+ hostPglitePort: HOST_PGLITE_PORT,
6476
+ pglitePasswordPath: PGLITE_PASSWORD_PATH
6477
+ };
5891
6478
  }
5892
6479
  async function waitForContainerReady(timeoutMs = 6e4) {
5893
6480
  const start = Date.now();
@@ -5942,7 +6529,7 @@ function dockerStatus() {
5942
6529
  return {
5943
6530
  running: true,
5944
6531
  image: imageTag(),
5945
- healthz: `http://127.0.0.1:${HOST_MCP_PORT}/`
6532
+ healthz: `http://127.0.0.1:${HOST_MCP_PORT}/health`
5946
6533
  };
5947
6534
  }
5948
6535
  function readContainerConfig() {
@@ -6089,23 +6676,34 @@ function checkPgdata() {
6089
6676
  if (!hasPgControl) return { healthy: false, details: "pg_control/global directory missing" };
6090
6677
  return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };
6091
6678
  }
6092
- var SYNKRO_DIR7, MCP_JWT_PATH, PGDATA_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
6679
+ var SYNKRO_DIR7, MCP_JWT_PATH, PGDATA_PATH, PGLITE_PASSWORD_PATH, PGLITE_USERLIST_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, defaultImageVersion, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
6093
6680
  var init_dockerInstall = __esm({
6094
6681
  "cli/local-cc/dockerInstall.ts"() {
6095
6682
  "use strict";
6096
6683
  init_agentDetect();
6097
6684
  init_macKeychain();
6098
- SYNKRO_DIR7 = join14(homedir16(), ".synkro");
6099
- MCP_JWT_PATH = join14(SYNKRO_DIR7, ".mcp-jwt");
6100
- PGDATA_PATH = join14(SYNKRO_DIR7, "pgdata");
6101
- CLAUDE_HOST_STATE_DIR = join14(SYNKRO_DIR7, "claude-host-state");
6102
- CLAUDE_HOST_STATE_FILE = join14(CLAUDE_HOST_STATE_DIR, ".claude.json");
6685
+ SYNKRO_DIR7 = join15(homedir17(), ".synkro");
6686
+ MCP_JWT_PATH = join15(SYNKRO_DIR7, ".mcp-jwt");
6687
+ PGDATA_PATH = join15(SYNKRO_DIR7, "pgdata");
6688
+ PGLITE_PASSWORD_PATH = join15(SYNKRO_DIR7, ".pglite-password");
6689
+ PGLITE_USERLIST_PATH = join15(SYNKRO_DIR7, ".pglite-userlist");
6690
+ CLAUDE_HOST_STATE_DIR = join15(SYNKRO_DIR7, "claude-host-state");
6691
+ CLAUDE_HOST_STATE_FILE = join15(CLAUDE_HOST_STATE_DIR, ".claude.json");
6103
6692
  HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
6104
6693
  HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || "18929", 10);
6105
6694
  HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || "18930", 10);
6106
6695
  HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
6107
- CONTAINER_NAME = "synkro-server";
6108
- DEFAULT_IMAGE = "ghcr.io/synkro-sh/synkro-server:latest";
6696
+ CONTAINER_NAME = resolveContainerName();
6697
+ defaultImageVersion = () => {
6698
+ if (true) return "1.7.89";
6699
+ try {
6700
+ const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
6701
+ if (pkg.version) return pkg.version;
6702
+ } catch {
6703
+ }
6704
+ return process.env.npm_package_version || "0.0.0-dev";
6705
+ };
6706
+ DEFAULT_IMAGE = `ghcr.io/synkro-sh/synkro-server:${defaultImageVersion()}`;
6109
6707
  DockerInstallError = class extends Error {
6110
6708
  constructor(message, cause) {
6111
6709
  super(message);
@@ -6114,17 +6712,17 @@ var init_dockerInstall = __esm({
6114
6712
  }
6115
6713
  cause;
6116
6714
  };
6117
- BACKUP_DIR = join14(SYNKRO_DIR7, "pgdata-backups");
6715
+ BACKUP_DIR = join15(SYNKRO_DIR7, "pgdata-backups");
6118
6716
  }
6119
6717
  });
6120
6718
 
6121
6719
  // cli/local-cc/setupToken.ts
6122
6720
  import { spawn as nodeSpawn } from "child_process";
6123
- import { readFileSync as readFileSync17, unlinkSync as unlinkSync6 } from "fs";
6124
- import { homedir as homedir17, platform as platform4 } from "os";
6125
- import { join as join15 } from "path";
6721
+ import { readFileSync as readFileSync18, unlinkSync as unlinkSync6 } from "fs";
6722
+ import { homedir as homedir18, platform as platform4 } from "os";
6723
+ import { join as join16 } from "path";
6126
6724
  function captureClaudeSetupToken() {
6127
- const tmpFile = join15(SYNKRO_DIR8, `token-capture-${Date.now()}.raw`);
6725
+ const tmpFile = join16(SYNKRO_DIR8, `token-capture-${Date.now()}.raw`);
6128
6726
  const isMac = platform4() === "darwin";
6129
6727
  const bin = "script";
6130
6728
  const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
@@ -6138,7 +6736,7 @@ function captureClaudeSetupToken() {
6138
6736
  proc.on("close", (code) => {
6139
6737
  let raw = "";
6140
6738
  try {
6141
- raw = readFileSync17(tmpFile, "utf-8");
6739
+ raw = readFileSync18(tmpFile, "utf-8");
6142
6740
  } catch (e) {
6143
6741
  reject(new Error(`Could not read script output file: ${e.message}`));
6144
6742
  return;
@@ -6204,15 +6802,15 @@ var SYNKRO_DIR8;
6204
6802
  var init_setupToken = __esm({
6205
6803
  "cli/local-cc/setupToken.ts"() {
6206
6804
  "use strict";
6207
- SYNKRO_DIR8 = join15(homedir17(), ".synkro");
6805
+ SYNKRO_DIR8 = join16(homedir18(), ".synkro");
6208
6806
  }
6209
6807
  });
6210
6808
 
6211
6809
  // cli/local-cc/codexCloudSetup.ts
6212
6810
  import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
6213
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
6214
- import { homedir as homedir18 } from "os";
6215
- import { join as join16 } from "path";
6811
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
6812
+ import { homedir as homedir19 } from "os";
6813
+ import { join as join17 } from "path";
6216
6814
  function findCodexBinary() {
6217
6815
  if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
6218
6816
  const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
@@ -6221,7 +6819,7 @@ function findCodexBinary() {
6221
6819
  }
6222
6820
  function runCodexLogin(codexBin, codexHome) {
6223
6821
  mkdirSync13(codexHome, { recursive: true, mode: 448 });
6224
- writeFileSync14(join16(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
6822
+ writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
6225
6823
  return new Promise((resolve6, reject) => {
6226
6824
  const proc = nodeSpawn2(codexBin, ["login"], {
6227
6825
  stdio: "inherit",
@@ -6253,13 +6851,13 @@ async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
6253
6851
  } catch (e) {
6254
6852
  return { ok: false, error: `Codex login failed: ${e.message}` };
6255
6853
  }
6256
- const authPath = join16(CODEX_CLOUD_HOME, "auth.json");
6854
+ const authPath = join17(CODEX_CLOUD_HOME, "auth.json");
6257
6855
  if (!existsSync20(authPath)) {
6258
6856
  return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
6259
6857
  }
6260
6858
  let auth;
6261
6859
  try {
6262
- auth = JSON.parse(readFileSync18(authPath, "utf-8"));
6860
+ auth = JSON.parse(readFileSync19(authPath, "utf-8"));
6263
6861
  } catch (e) {
6264
6862
  return { ok: false, error: `could not read codex auth.json: ${e.message}` };
6265
6863
  }
@@ -6293,7 +6891,7 @@ async function setupCodexLocal(onStatus) {
6293
6891
  if (!codexBin) {
6294
6892
  return { ok: false, error: "Codex CLI not found on PATH. Install Codex, then re-run `synkro install`." };
6295
6893
  }
6296
- const authPath = join16(CODEX_LOCAL_HOME, "auth.json");
6894
+ const authPath = join17(CODEX_LOCAL_HOME, "auth.json");
6297
6895
  if (!existsSync20(authPath)) {
6298
6896
  onStatus?.("Opening your browser to authorize an isolated Codex session for the local grader\u2026");
6299
6897
  try {
@@ -6303,7 +6901,7 @@ async function setupCodexLocal(onStatus) {
6303
6901
  }
6304
6902
  }
6305
6903
  try {
6306
- const auth = JSON.parse(readFileSync18(authPath, "utf-8"));
6904
+ const auth = JSON.parse(readFileSync19(authPath, "utf-8"));
6307
6905
  if (!auth.tokens?.refresh_token) throw new Error("auth.json has no refresh token");
6308
6906
  } catch (e) {
6309
6907
  return { ok: false, error: `Codex local grader auth is invalid: ${e.message}` };
@@ -6315,9 +6913,9 @@ var SYNKRO_DIR9, CODEX_CLOUD_HOME, CODEX_LOCAL_HOME;
6315
6913
  var init_codexCloudSetup = __esm({
6316
6914
  "cli/local-cc/codexCloudSetup.ts"() {
6317
6915
  "use strict";
6318
- SYNKRO_DIR9 = join16(homedir18(), ".synkro");
6319
- CODEX_CLOUD_HOME = join16(SYNKRO_DIR9, "codex-cloud-session");
6320
- CODEX_LOCAL_HOME = join16(SYNKRO_DIR9, "codex-local-session");
6916
+ SYNKRO_DIR9 = join17(homedir19(), ".synkro");
6917
+ CODEX_CLOUD_HOME = join17(SYNKRO_DIR9, "codex-cloud-session");
6918
+ CODEX_LOCAL_HOME = join17(SYNKRO_DIR9, "codex-local-session");
6321
6919
  }
6322
6920
  });
6323
6921
 
@@ -6339,21 +6937,21 @@ __export(ptyShim_exports, {
6339
6937
  import {
6340
6938
  existsSync as existsSync21,
6341
6939
  mkdirSync as mkdirSync14,
6342
- writeFileSync as writeFileSync15,
6343
- chmodSync as chmodSync3,
6344
- readFileSync as readFileSync19,
6940
+ writeFileSync as writeFileSync16,
6941
+ chmodSync as chmodSync4,
6942
+ readFileSync as readFileSync20,
6345
6943
  rmSync as rmSync2,
6346
6944
  realpathSync,
6347
6945
  symlinkSync,
6348
6946
  lstatSync,
6349
6947
  readdirSync as readdirSync3
6350
6948
  } from "fs";
6351
- import { homedir as homedir19 } from "os";
6352
- import { join as join17 } from "path";
6949
+ import { homedir as homedir20 } from "os";
6950
+ import { join as join18 } from "path";
6353
6951
  import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
6354
6952
  function rcFiles() {
6355
- const h = homedir19();
6356
- return [join17(h, ".zshrc"), join17(h, ".bashrc"), join17(h, ".bash_profile")];
6953
+ const h = homedir20();
6954
+ return [join18(h, ".zshrc"), join18(h, ".bashrc"), join18(h, ".bash_profile")];
6357
6955
  }
6358
6956
  function resolveRealClaude() {
6359
6957
  const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
@@ -6365,7 +6963,7 @@ function resolveRealClaude() {
6365
6963
  return p;
6366
6964
  }
6367
6965
  }
6368
- for (const c of [join17(homedir19(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
6966
+ for (const c of [join18(homedir20(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
6369
6967
  if (existsSync21(c)) {
6370
6968
  try {
6371
6969
  return realpathSync(c);
@@ -6380,7 +6978,7 @@ function findClaudeLink() {
6380
6978
  const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6381
6979
  let linkPath = (r.stdout || "").trim();
6382
6980
  if (!linkPath) {
6383
- const c = join17(homedir19(), ".local", "bin", "claude");
6981
+ const c = join18(homedir20(), ".local", "bin", "claude");
6384
6982
  if (existsSync21(c)) linkPath = c;
6385
6983
  else return null;
6386
6984
  }
@@ -6394,7 +6992,7 @@ function findClaudeLink() {
6394
6992
  }
6395
6993
  function isOurShim(path) {
6396
6994
  try {
6397
- return readFileSync19(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
6995
+ return readFileSync20(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
6398
6996
  } catch {
6399
6997
  return false;
6400
6998
  }
@@ -6407,7 +7005,7 @@ function shadowClaude() {
6407
7005
  }
6408
7006
  const { linkPath, realTarget } = found;
6409
7007
  if (isOurShim(linkPath)) {
6410
- console.log(` \xB7 ${linkPath.replace(homedir19(), "~")} already shadowed`);
7008
+ console.log(` \xB7 ${linkPath.replace(homedir20(), "~")} already shadowed`);
6411
7009
  return;
6412
7010
  }
6413
7011
  let wasSymlink = false;
@@ -6417,14 +7015,14 @@ function shadowClaude() {
6417
7015
  }
6418
7016
  const state = { linkPath, realTarget, wasSymlink };
6419
7017
  try {
6420
- writeFileSync15(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
7018
+ writeFileSync16(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
6421
7019
  } catch {
6422
7020
  }
6423
7021
  try {
6424
7022
  rmSync2(linkPath, { force: true });
6425
- writeFileSync15(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
6426
- chmodSync3(linkPath, 493);
6427
- console.log(` \u2713 shadowed ${linkPath.replace(homedir19(), "~")} \u2192 shim (real: ${realTarget.replace(homedir19(), "~")})`);
7023
+ writeFileSync16(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
7024
+ chmodSync4(linkPath, 493);
7025
+ console.log(` \u2713 shadowed ${linkPath.replace(homedir20(), "~")} \u2192 shim (real: ${realTarget.replace(homedir20(), "~")})`);
6428
7026
  } catch (e) {
6429
7027
  console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
6430
7028
  }
@@ -6432,7 +7030,7 @@ function shadowClaude() {
6432
7030
  function unshadowClaude() {
6433
7031
  let state;
6434
7032
  try {
6435
- state = JSON.parse(readFileSync19(SHADOW_STATE_FILE, "utf-8"));
7033
+ state = JSON.parse(readFileSync20(SHADOW_STATE_FILE, "utf-8"));
6436
7034
  } catch {
6437
7035
  return;
6438
7036
  }
@@ -6441,7 +7039,7 @@ function unshadowClaude() {
6441
7039
  if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
6442
7040
  rmSync2(state.linkPath, { force: true });
6443
7041
  symlinkSync(state.realTarget, state.linkPath);
6444
- console.log(`\u2713 restored ${state.linkPath.replace(homedir19(), "~")} \u2192 ${state.realTarget.replace(homedir19(), "~")}`);
7042
+ console.log(`\u2713 restored ${state.linkPath.replace(homedir20(), "~")} \u2192 ${state.realTarget.replace(homedir20(), "~")}`);
6445
7043
  } catch (e) {
6446
7044
  console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
6447
7045
  }
@@ -6452,13 +7050,13 @@ function addPathBlock() {
6452
7050
  if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
6453
7051
  let body = "";
6454
7052
  try {
6455
- body = readFileSync19(rc, "utf-8");
7053
+ body = readFileSync20(rc, "utf-8");
6456
7054
  } catch {
6457
7055
  }
6458
7056
  const cleaned = stripBlock(body);
6459
7057
  const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
6460
7058
  try {
6461
- writeFileSync15(rc, next, "utf-8");
7059
+ writeFileSync16(rc, next, "utf-8");
6462
7060
  touched.push(rc);
6463
7061
  } catch {
6464
7062
  }
@@ -6477,12 +7075,12 @@ function installPtyShim() {
6477
7075
  mkdirSync14(SHIM_BIN_DIR, { recursive: true });
6478
7076
  mkdirSync14(PTY_STATE_DIR, { recursive: true });
6479
7077
  const real = resolveRealClaude();
6480
- writeFileSync15(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
6481
- chmodSync3(SHIM_PATH, 493);
7078
+ writeFileSync16(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
7079
+ chmodSync4(SHIM_PATH, 493);
6482
7080
  const touched = addPathBlock();
6483
7081
  console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
6484
7082
  shadowClaude();
6485
- if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir19(), "~")).join(", ")}`);
7083
+ if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir20(), "~")).join(", ")}`);
6486
7084
  }
6487
7085
  function uninstallPtyShim() {
6488
7086
  try {
@@ -6500,10 +7098,10 @@ function uninstallPtyShim() {
6500
7098
  for (const rc of rcFiles()) {
6501
7099
  if (!existsSync21(rc)) continue;
6502
7100
  try {
6503
- const body = readFileSync19(rc, "utf-8");
7101
+ const body = readFileSync20(rc, "utf-8");
6504
7102
  if (body.includes(RC_BEGIN)) {
6505
- writeFileSync15(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
6506
- cleaned.push(rc.replace(homedir19(), "~"));
7103
+ writeFileSync16(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
7104
+ cleaned.push(rc.replace(homedir20(), "~"));
6507
7105
  }
6508
7106
  } catch {
6509
7107
  }
@@ -6535,7 +7133,7 @@ function listSessions(opts = {}) {
6535
7133
  const out = [];
6536
7134
  for (const f of files) {
6537
7135
  try {
6538
- const r = JSON.parse(readFileSync19(join17(SESSIONS_DIR, f), "utf-8"));
7136
+ const r = JSON.parse(readFileSync20(join18(SESSIONS_DIR, f), "utf-8"));
6539
7137
  if (!r || !r.session_id) continue;
6540
7138
  if (liveOnly) {
6541
7139
  const s = r.tmux_session;
@@ -6564,7 +7162,7 @@ function resolveTargetSession(override) {
6564
7162
  const own = currentTmuxSession();
6565
7163
  const fromFile = (() => {
6566
7164
  try {
6567
- return readFileSync19(ACTIVE_SESSION_FILE, "utf-8").trim();
7165
+ return readFileSync20(ACTIVE_SESSION_FILE, "utf-8").trim();
6568
7166
  } catch {
6569
7167
  return "";
6570
7168
  }
@@ -6590,7 +7188,7 @@ function injectModel(model, sessionOverride) {
6590
7188
  sendKeys("Escape");
6591
7189
  sendKeys("-l", `/model ${model}`);
6592
7190
  sendKeys("Enter");
6593
- const pidFile = join17(PTY_STATE_DIR, `poll-${session}.pid`);
7191
+ const pidFile = join18(PTY_STATE_DIR, `poll-${session}.pid`);
6594
7192
  try {
6595
7193
  mkdirSync14(PTY_STATE_DIR, { recursive: true });
6596
7194
  } catch {
@@ -6604,13 +7202,13 @@ var SYNKRO_DIR10, SHIM_BIN_DIR, SHIM_PATH, PTY_STATE_DIR, ACTIVE_SESSION_FILE, S
6604
7202
  var init_ptyShim = __esm({
6605
7203
  "cli/local-cc/ptyShim.ts"() {
6606
7204
  "use strict";
6607
- SYNKRO_DIR10 = join17(homedir19(), ".synkro");
6608
- SHIM_BIN_DIR = join17(SYNKRO_DIR10, "bin");
6609
- SHIM_PATH = join17(SHIM_BIN_DIR, "claude");
6610
- PTY_STATE_DIR = join17(SYNKRO_DIR10, "pty");
6611
- ACTIVE_SESSION_FILE = join17(PTY_STATE_DIR, "active");
6612
- SHADOW_STATE_FILE = join17(PTY_STATE_DIR, "shadow.json");
6613
- SESSIONS_DIR = join17(PTY_STATE_DIR, "sessions");
7205
+ SYNKRO_DIR10 = join18(homedir20(), ".synkro");
7206
+ SHIM_BIN_DIR = join18(SYNKRO_DIR10, "bin");
7207
+ SHIM_PATH = join18(SHIM_BIN_DIR, "claude");
7208
+ PTY_STATE_DIR = join18(SYNKRO_DIR10, "pty");
7209
+ ACTIVE_SESSION_FILE = join18(PTY_STATE_DIR, "active");
7210
+ SHADOW_STATE_FILE = join18(PTY_STATE_DIR, "shadow.json");
7211
+ SESSIONS_DIR = join18(PTY_STATE_DIR, "sessions");
6614
7212
  SHIM_SESSION_PREFIX = "synkro-cc-";
6615
7213
  RC_BEGIN = "# >>> synkro pty shim (managed \u2014 do not edit) >>>";
6616
7214
  RC_END = "# <<< synkro pty shim <<<";
@@ -6681,6 +7279,189 @@ var init_graderSmoke = __esm({
6681
7279
  }
6682
7280
  });
6683
7281
 
7282
+ // cli/scanning/codexTranscriptUsage.ts
7283
+ function rawCounters(value) {
7284
+ return {
7285
+ input: finiteCounter(value?.input_tokens),
7286
+ output: finiteCounter(value?.output_tokens),
7287
+ cacheCreation: finiteCounter(value?.cache_write_input_tokens),
7288
+ cacheRead: finiteCounter(value?.cached_input_tokens)
7289
+ };
7290
+ }
7291
+ function counterDelta(current, previous) {
7292
+ return current >= previous ? current - previous : current;
7293
+ }
7294
+ function normalizedUsage(raw) {
7295
+ return {
7296
+ // Codex input_tokens includes cached/cache-write details. Store only the
7297
+ // uncached remainder in input_tokens so totals and pricing never count the
7298
+ // same prompt tokens twice.
7299
+ input_tokens: Math.max(0, raw.input - raw.cacheCreation - raw.cacheRead),
7300
+ output_tokens: raw.output,
7301
+ cache_creation_input_tokens: raw.cacheCreation,
7302
+ cache_read_input_tokens: raw.cacheRead
7303
+ };
7304
+ }
7305
+ function addUsage(a, b) {
7306
+ return {
7307
+ input_tokens: (a?.input_tokens || 0) + b.input_tokens,
7308
+ output_tokens: (a?.output_tokens || 0) + b.output_tokens,
7309
+ cache_creation_input_tokens: (a?.cache_creation_input_tokens || 0) + b.cache_creation_input_tokens,
7310
+ cache_read_input_tokens: (a?.cache_read_input_tokens || 0) + b.cache_read_input_tokens
7311
+ };
7312
+ }
7313
+ function parseCodexTranscriptUsage(transcript, options = {}) {
7314
+ if (!transcript) return null;
7315
+ const lines = transcript.split("\n");
7316
+ const hasCanonicalAssistant = lines.some((line) => {
7317
+ try {
7318
+ const entry = JSON.parse(line);
7319
+ return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
7320
+ } catch {
7321
+ return false;
7322
+ }
7323
+ });
7324
+ const turnsByLine = /* @__PURE__ */ new Map();
7325
+ let model = "";
7326
+ let lastAssistantLine = -1;
7327
+ let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
7328
+ let latest = null;
7329
+ let sawSnapshot = false;
7330
+ for (let i = 0; i < lines.length; i++) {
7331
+ const line = lines[i].trim();
7332
+ if (!line) continue;
7333
+ let entry;
7334
+ try {
7335
+ entry = JSON.parse(line);
7336
+ } catch {
7337
+ continue;
7338
+ }
7339
+ if (entry?.type === "turn_context" && typeof entry?.payload?.model === "string") {
7340
+ model = entry.payload.model;
7341
+ continue;
7342
+ }
7343
+ if (!hasCanonicalAssistant && entry?.type === "response_item" && entry?.payload?.type === "message" && entry?.payload?.role === "assistant") {
7344
+ lastAssistantLine = i;
7345
+ continue;
7346
+ }
7347
+ if (entry?.type !== "event_msg") continue;
7348
+ if (entry?.payload?.type === "agent_message") {
7349
+ lastAssistantLine = i;
7350
+ continue;
7351
+ }
7352
+ if (entry?.payload?.type !== "token_count") continue;
7353
+ const eventModel = entry?.payload?.model ?? entry?.model;
7354
+ if (typeof eventModel === "string" && eventModel) model = eventModel;
7355
+ const total = entry?.payload?.info?.total_token_usage;
7356
+ if (!total || typeof total !== "object") continue;
7357
+ const current = rawCounters(total);
7358
+ latest = current;
7359
+ if (options.partial && !sawSnapshot) {
7360
+ previous = current;
7361
+ sawSnapshot = true;
7362
+ continue;
7363
+ }
7364
+ sawSnapshot = true;
7365
+ const delta = normalizedUsage({
7366
+ input: counterDelta(current.input, previous.input),
7367
+ output: counterDelta(current.output, previous.output),
7368
+ cacheCreation: counterDelta(current.cacheCreation, previous.cacheCreation),
7369
+ cacheRead: counterDelta(current.cacheRead, previous.cacheRead)
7370
+ });
7371
+ previous = current;
7372
+ if (lastAssistantLine >= 0) {
7373
+ const prior = turnsByLine.get(lastAssistantLine);
7374
+ turnsByLine.set(lastAssistantLine, {
7375
+ lineIndex: lastAssistantLine,
7376
+ model,
7377
+ usage: addUsage(prior?.usage, delta)
7378
+ });
7379
+ }
7380
+ }
7381
+ if (model) {
7382
+ for (const turn of turnsByLine.values()) {
7383
+ if (!turn.model) turn.model = model;
7384
+ }
7385
+ }
7386
+ if (!latest) return null;
7387
+ return { model, total: normalizedUsage(latest), turnsByLine };
7388
+ }
7389
+ var finiteCounter;
7390
+ var init_codexTranscriptUsage = __esm({
7391
+ "cli/scanning/codexTranscriptUsage.ts"() {
7392
+ "use strict";
7393
+ finiteCounter = (value) => {
7394
+ const n = Number(value);
7395
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
7396
+ };
7397
+ }
7398
+ });
7399
+
7400
+ // cli/scanning/codexTranscriptMessages.ts
7401
+ import { createHash as createHash3 } from "crypto";
7402
+ function textContent(content) {
7403
+ if (typeof content === "string") return content.trim();
7404
+ if (!Array.isArray(content)) return "";
7405
+ return content.map((block) => {
7406
+ if (typeof block === "string") return block;
7407
+ return block?.type === "text" || block?.type === "input_text" || block?.type === "output_text" ? String(block.text || "") : "";
7408
+ }).join(" ").trim();
7409
+ }
7410
+ function isCodexConversationNoise(content) {
7411
+ const value = content.trimStart();
7412
+ return value.startsWith("[synkro:") || value.startsWith("claude-code: {") || value.startsWith("RAW claude-code agent:") || value.startsWith("Guard: (R") || value.startsWith("<developer") || value.startsWith("<system") || value.startsWith("<permissions instructions>") || value.startsWith("<app-context>") || value.startsWith("<environment_context>") || value.startsWith("<recommended_plugins>");
7413
+ }
7414
+ function parseCodexConversationMessages(transcript) {
7415
+ if (!transcript) return [];
7416
+ const entries = [];
7417
+ for (const [lineIndex, line] of transcript.split("\n").entries()) {
7418
+ const value = line.trim();
7419
+ if (!value) continue;
7420
+ try {
7421
+ entries.push({ lineIndex, entry: JSON.parse(value) });
7422
+ } catch {
7423
+ }
7424
+ }
7425
+ const hasCanonicalMessages = entries.some(({ entry }) => entry?.type === "event_msg" && (entry?.payload?.type === "user_message" || entry?.payload?.type === "agent_message"));
7426
+ const messages = [];
7427
+ for (const { lineIndex, entry } of entries) {
7428
+ let role = null;
7429
+ let content;
7430
+ let itemId = "";
7431
+ const eventType = entry?.type === "event_msg" ? entry?.payload?.type : "";
7432
+ if (eventType === "user_message" || eventType === "agent_message") {
7433
+ role = eventType === "user_message" ? "user" : "assistant";
7434
+ content = entry?.payload?.message;
7435
+ } else if (!hasCanonicalMessages && entry?.type === "response_item" && entry?.payload?.type === "message") {
7436
+ const responseRole = entry?.payload?.role;
7437
+ if (responseRole !== "user" && responseRole !== "assistant") continue;
7438
+ role = responseRole;
7439
+ content = entry?.payload?.content;
7440
+ itemId = typeof entry?.payload?.id === "string" ? entry.payload.id : "";
7441
+ } else {
7442
+ continue;
7443
+ }
7444
+ if (!role) continue;
7445
+ const text = textContent(content);
7446
+ if (!text || isCodexConversationNoise(text)) continue;
7447
+ const timestamp = typeof entry?.timestamp === "string" && entry.timestamp ? entry.timestamp : void 0;
7448
+ const uuid = typeof entry?.uuid === "string" && entry.uuid ? entry.uuid : itemId || createHash3("sha256").update((timestamp || "") + "\0" + role + "\0" + text).digest("hex").slice(0, 32);
7449
+ messages.push({
7450
+ lineIndex,
7451
+ role,
7452
+ content: text.slice(0, 8e3),
7453
+ uuid,
7454
+ ...timestamp ? { timestamp } : {}
7455
+ });
7456
+ }
7457
+ return messages;
7458
+ }
7459
+ var init_codexTranscriptMessages = __esm({
7460
+ "cli/scanning/codexTranscriptMessages.ts"() {
7461
+ "use strict";
7462
+ }
7463
+ });
7464
+
6684
7465
  // cli/commands/install.ts
6685
7466
  var install_exports = {};
6686
7467
  __export(install_exports, {
@@ -6696,12 +7477,12 @@ __export(install_exports, {
6696
7477
  syncSkillFiles: () => syncSkillFiles,
6697
7478
  writeHookScripts: () => writeHookScripts
6698
7479
  });
6699
- import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync16, chmodSync as chmodSync4, readFileSync as readFileSync20, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
6700
- import { homedir as homedir20 } from "os";
6701
- import { join as join18, isAbsolute, resolve as resolve4 } from "path";
7480
+ import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17, chmodSync as chmodSync5, readFileSync as readFileSync21, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
7481
+ import { homedir as homedir21 } from "os";
7482
+ import { join as join19, isAbsolute, resolve as resolve4 } from "path";
6702
7483
  import { execSync as execSync4, spawn as spawn4 } from "child_process";
6703
7484
  import { createInterface as createInterface2 } from "readline";
6704
- import { createHash as createHash2 } from "crypto";
7485
+ import { createHash as createHash4 } from "crypto";
6705
7486
  function resolvePersistedHookMode() {
6706
7487
  return "stub";
6707
7488
  }
@@ -6829,34 +7610,37 @@ function ensureSynkroDir() {
6829
7610
  mkdirSync15(HOOKS_DIR, { recursive: true });
6830
7611
  mkdirSync15(BIN_DIR, { recursive: true });
6831
7612
  mkdirSync15(OFFSETS_DIR, { recursive: true });
6832
- mkdirSync15(join18(SYNKRO_DIR11, "sessions"), { recursive: true });
7613
+ mkdirSync15(join19(SYNKRO_DIR11, "sessions"), { recursive: true });
6833
7614
  }
6834
7615
  function writeHookScripts() {
6835
- const installExtractCorePath = join18(HOOKS_DIR, "installExtractCore.ts");
6836
- const bashScriptPath = join18(HOOKS_DIR, "cc-bash-judge.ts");
6837
- const skillJudgeScriptPath = join18(HOOKS_DIR, "cc-skill-judge.ts");
6838
- const cursorSkillJudgePath = join18(HOOKS_DIR, "cursor-skill-judge.ts");
6839
- const bashFollowupScriptPath = join18(HOOKS_DIR, "cc-bash-followup.ts");
6840
- const editPrecheckScriptPath = join18(HOOKS_DIR, "cc-edit-precheck.ts");
6841
- const cwePrecheckScriptPath = join18(HOOKS_DIR, "cc-cwe-precheck.ts");
6842
- const cvePrecheckScriptPath = join18(HOOKS_DIR, "cc-cve-precheck.ts");
6843
- const planJudgeScriptPath = join18(HOOKS_DIR, "cc-plan-judge.ts");
6844
- const agentJudgeScriptPath = join18(HOOKS_DIR, "cc-agent-judge.ts");
6845
- const stopSummaryScriptPath = join18(HOOKS_DIR, "cc-stop-summary.ts");
6846
- const sessionStartScriptPath = join18(HOOKS_DIR, "cc-session-start.ts");
6847
- const transcriptSyncScriptPath = join18(HOOKS_DIR, "cc-transcript-sync.ts");
6848
- const userPromptSubmitScriptPath = join18(HOOKS_DIR, "cc-user-prompt-submit.ts");
6849
- const promptRouteScriptPath = join18(HOOKS_DIR, "cc-prompt-route.ts");
6850
- const commonScriptPath = join18(HOOKS_DIR, "_synkro-common.ts");
6851
- const commonBashScriptPath = join18(HOOKS_DIR, "_synkro-common.sh");
6852
- const installScanScriptPath = join18(HOOKS_DIR, "cc-install-scan.ts");
6853
- const cursorBashJudgePath = join18(HOOKS_DIR, "cursor-bash-judge.ts");
6854
- const cursorEditCapturePath = join18(HOOKS_DIR, "cursor-edit-capture.ts");
6855
- const cursorAgentCapturePath = join18(HOOKS_DIR, "cursor-agent-capture.ts");
6856
- const mcpStdioProxyPath = join18(HOOKS_DIR, "mcp-stdio-proxy.ts");
6857
- const taskActivateIntentScriptPath = join18(HOOKS_DIR, "cc-task-activate-intent.ts");
6858
- const mcpGateScriptPath = join18(HOOKS_DIR, "cc-mcp-gate.ts");
6859
- const stubCommonPath = join18(HOOKS_DIR, "_synkro-stub-common.ts");
7616
+ const installExtractCorePath = join19(HOOKS_DIR, "installExtractCore.ts");
7617
+ const bashScriptPath = join19(HOOKS_DIR, "cc-bash-judge.ts");
7618
+ const skillJudgeScriptPath = join19(HOOKS_DIR, "cc-skill-judge.ts");
7619
+ const cursorSkillJudgePath = join19(HOOKS_DIR, "cursor-skill-judge.ts");
7620
+ const bashFollowupScriptPath = join19(HOOKS_DIR, "cc-bash-followup.ts");
7621
+ const editPrecheckScriptPath = join19(HOOKS_DIR, "cc-edit-precheck.ts");
7622
+ const editFollowupScriptPath = join19(HOOKS_DIR, "cc-edit-followup.ts");
7623
+ const cwePrecheckScriptPath = join19(HOOKS_DIR, "cc-cwe-precheck.ts");
7624
+ const cvePrecheckScriptPath = join19(HOOKS_DIR, "cc-cve-precheck.ts");
7625
+ const planJudgeScriptPath = join19(HOOKS_DIR, "cc-plan-judge.ts");
7626
+ const agentJudgeScriptPath = join19(HOOKS_DIR, "cc-agent-judge.ts");
7627
+ const stopSummaryScriptPath = join19(HOOKS_DIR, "cc-stop-summary.ts");
7628
+ const sessionStartScriptPath = join19(HOOKS_DIR, "cc-session-start.ts");
7629
+ const transcriptSyncScriptPath = join19(HOOKS_DIR, "cc-transcript-sync.ts");
7630
+ const subagentStartScriptPath = join19(HOOKS_DIR, "codex-subagent-start.ts");
7631
+ const subagentStopScriptPath = join19(HOOKS_DIR, "codex-subagent-stop.ts");
7632
+ const userPromptSubmitScriptPath = join19(HOOKS_DIR, "cc-user-prompt-submit.ts");
7633
+ const promptRouteScriptPath = join19(HOOKS_DIR, "cc-prompt-route.ts");
7634
+ const commonScriptPath = join19(HOOKS_DIR, "_synkro-common.ts");
7635
+ const commonBashScriptPath = join19(HOOKS_DIR, "_synkro-common.sh");
7636
+ const installScanScriptPath = join19(HOOKS_DIR, "cc-install-scan.ts");
7637
+ const cursorBashJudgePath = join19(HOOKS_DIR, "cursor-bash-judge.ts");
7638
+ const cursorEditCapturePath = join19(HOOKS_DIR, "cursor-edit-capture.ts");
7639
+ const cursorAgentCapturePath = join19(HOOKS_DIR, "cursor-agent-capture.ts");
7640
+ const mcpStdioProxyPath = join19(HOOKS_DIR, "mcp-stdio-proxy.ts");
7641
+ const taskActivateIntentScriptPath = join19(HOOKS_DIR, "cc-task-activate-intent.ts");
7642
+ const mcpGateScriptPath = join19(HOOKS_DIR, "cc-mcp-gate.ts");
7643
+ const stubCommonPath = join19(HOOKS_DIR, "_synkro-stub-common.ts");
6860
7644
  const stubFiles = [
6861
7645
  [stubCommonPath, STUB_COMMON_TS],
6862
7646
  [bashScriptPath, STUB_BASH_JUDGE_TS],
@@ -6864,6 +7648,7 @@ function writeHookScripts() {
6864
7648
  [cursorSkillJudgePath, STUB_CURSOR_SKILL_JUDGE_TS],
6865
7649
  [bashFollowupScriptPath, STUB_BASH_FOLLOWUP_TS],
6866
7650
  [editPrecheckScriptPath, STUB_EDIT_PRECHECK_TS],
7651
+ [editFollowupScriptPath, STUB_EDIT_FOLLOWUP_TS],
6867
7652
  [cwePrecheckScriptPath, STUB_CWE_PRECHECK_TS],
6868
7653
  [cvePrecheckScriptPath, STUB_CVE_PRECHECK_TS],
6869
7654
  [planJudgeScriptPath, STUB_PLAN_JUDGE_TS],
@@ -6871,6 +7656,8 @@ function writeHookScripts() {
6871
7656
  [stopSummaryScriptPath, STUB_STOP_SUMMARY_TS],
6872
7657
  [sessionStartScriptPath, STUB_SESSION_START_TS],
6873
7658
  [transcriptSyncScriptPath, STUB_TRANSCRIPT_SYNC_TS],
7659
+ [subagentStartScriptPath, STUB_SUBAGENT_START_TS],
7660
+ [subagentStopScriptPath, STUB_SUBAGENT_STOP_TS],
6874
7661
  [userPromptSubmitScriptPath, STUB_USER_PROMPT_SUBMIT_TS],
6875
7662
  [promptRouteScriptPath, STUB_PROMPT_ROUTE_TS],
6876
7663
  [installScanScriptPath, STUB_INSTALL_SCAN_TS],
@@ -6881,14 +7668,14 @@ function writeHookScripts() {
6881
7668
  [cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS]
6882
7669
  ];
6883
7670
  for (const [p, content] of stubFiles) {
6884
- writeFileSync16(p, content, "utf-8");
6885
- chmodSync4(p, 493);
7671
+ writeFileSync17(p, content, "utf-8");
7672
+ chmodSync5(p, 493);
6886
7673
  }
6887
- writeFileSync16(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
6888
- chmodSync4(mcpStdioProxyPath, 493);
7674
+ writeFileSync17(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
7675
+ chmodSync5(mcpStdioProxyPath, 493);
6889
7676
  for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
6890
7677
  try {
6891
- unlinkSync7(join18(HOOKS_DIR, stale));
7678
+ unlinkSync7(join19(HOOKS_DIR, stale));
6892
7679
  } catch {
6893
7680
  }
6894
7681
  }
@@ -6898,6 +7685,7 @@ function writeHookScripts() {
6898
7685
  cursorSkillJudgeScript: cursorSkillJudgePath,
6899
7686
  bashFollowupScript: bashFollowupScriptPath,
6900
7687
  editPrecheckScript: editPrecheckScriptPath,
7688
+ editFollowupScript: editFollowupScriptPath,
6901
7689
  cwePrecheckScript: cwePrecheckScriptPath,
6902
7690
  cvePrecheckScript: cvePrecheckScriptPath,
6903
7691
  planJudgeScript: planJudgeScriptPath,
@@ -6905,6 +7693,8 @@ function writeHookScripts() {
6905
7693
  stopSummaryScript: stopSummaryScriptPath,
6906
7694
  sessionStartScript: sessionStartScriptPath,
6907
7695
  transcriptSyncScript: transcriptSyncScriptPath,
7696
+ subagentStartScript: subagentStartScriptPath,
7697
+ subagentStopScript: subagentStopScriptPath,
6908
7698
  userPromptSubmitScript: userPromptSubmitScriptPath,
6909
7699
  promptRouteScript: promptRouteScriptPath,
6910
7700
  installScanScript: installScanScriptPath,
@@ -6928,7 +7718,7 @@ function resolveSynkroBundle() {
6928
7718
  return null;
6929
7719
  }
6930
7720
  function writeConfigEnv(opts) {
6931
- const credsPath = join18(SYNKRO_DIR11, "credentials.json");
7721
+ const credsPath = join19(SYNKRO_DIR11, "credentials.json");
6932
7722
  const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
6933
7723
  const safeUserId = sanitizeConfigValue(opts.userId);
6934
7724
  const safeOrgId = sanitizeConfigValue(opts.orgId);
@@ -6944,7 +7734,7 @@ function writeConfigEnv(opts) {
6944
7734
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
6945
7735
  `SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
6946
7736
  `SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
6947
- `SYNKRO_VERSION=${shellQuoteSingle2("1.7.88")}`
7737
+ `SYNKRO_VERSION=${shellQuoteSingle2("1.7.89")}`
6948
7738
  ];
6949
7739
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
6950
7740
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
@@ -6964,12 +7754,12 @@ function writeConfigEnv(opts) {
6964
7754
  lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
6965
7755
  lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
6966
7756
  lines.push("");
6967
- writeFileSync16(CONFIG_PATH4, lines.join("\n"), "utf-8");
6968
- chmodSync4(CONFIG_PATH4, 384);
7757
+ writeFileSync17(CONFIG_PATH4, lines.join("\n"), "utf-8");
7758
+ chmodSync5(CONFIG_PATH4, 384);
6969
7759
  }
6970
7760
  function persistedTranscriptConsent(source) {
6971
7761
  try {
6972
- const env = readFileSync20(CONFIG_PATH4, "utf-8");
7762
+ const env = readFileSync21(CONFIG_PATH4, "utf-8");
6973
7763
  const specific = env.match(new RegExp(`^SYNKRO_TRANSCRIPT_CONSENT_${source}='(yes|no)'`, "m"));
6974
7764
  if (specific) return specific[1] === "yes";
6975
7765
  if (source !== "CODEX") {
@@ -6992,7 +7782,7 @@ async function getOrMintCloudToken(gatewayUrl) {
6992
7782
  assertGatewayAllowed(gatewayUrl);
6993
7783
  let stored = "";
6994
7784
  try {
6995
- stored = readFileSync20(CLOUD_JWT_PATH, "utf-8").trim();
7785
+ stored = readFileSync21(CLOUD_JWT_PATH, "utf-8").trim();
6996
7786
  } catch {
6997
7787
  }
6998
7788
  if (stored && !jwtExpired(stored)) return stored;
@@ -7008,7 +7798,7 @@ async function getOrMintCloudToken(gatewayUrl) {
7008
7798
  throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);
7009
7799
  }
7010
7800
  const { token } = await resp.json();
7011
- writeFileSync16(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
7801
+ writeFileSync17(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
7012
7802
  return token;
7013
7803
  }
7014
7804
  async function provisionCloudContainer(opts) {
@@ -7092,7 +7882,7 @@ async function provisionCloudContainer(opts) {
7092
7882
  let cursorApiKey = "";
7093
7883
  if (cursorWorkers > 0 || selectedKind === "cursor") {
7094
7884
  try {
7095
- cursorApiKey = readFileSync20(join18(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
7885
+ cursorApiKey = readFileSync21(join19(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
7096
7886
  } catch {
7097
7887
  }
7098
7888
  }
@@ -7278,7 +8068,7 @@ async function verifyCloudGrader(jwt2, requestedKind) {
7278
8068
  function readPersistedDeployLocation() {
7279
8069
  try {
7280
8070
  if (existsSync22(CONFIG_PATH4)) {
7281
- const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
8071
+ const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
7282
8072
  if (m?.[1] === "cloud") return "cloud";
7283
8073
  }
7284
8074
  } catch {
@@ -7287,7 +8077,7 @@ function readPersistedDeployLocation() {
7287
8077
  }
7288
8078
  function updateConfigEnvLocation(location) {
7289
8079
  if (!existsSync22(CONFIG_PATH4)) return;
7290
- let env = readFileSync20(CONFIG_PATH4, "utf-8");
8080
+ let env = readFileSync21(CONFIG_PATH4, "utf-8");
7291
8081
  const set = (k, v) => {
7292
8082
  const re = new RegExp(`^${k}=.*$`, "m");
7293
8083
  const line = `${k}='${v}'`;
@@ -7295,14 +8085,14 @@ function updateConfigEnvLocation(location) {
7295
8085
  };
7296
8086
  set("SYNKRO_DEPLOY_LOCATION", location);
7297
8087
  set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
7298
- writeFileSync16(CONFIG_PATH4, env, "utf-8");
7299
- chmodSync4(CONFIG_PATH4, 384);
8088
+ writeFileSync17(CONFIG_PATH4, env, "utf-8");
8089
+ chmodSync5(CONFIG_PATH4, 384);
7300
8090
  }
7301
8091
  async function applyMcpConfig(opts) {
7302
8092
  if (!opts.hasClaudeCode && !opts.hasCursor && !opts.hasCodex) return;
7303
8093
  let mcpJwt2 = "";
7304
8094
  try {
7305
- mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8095
+ mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
7306
8096
  } catch {
7307
8097
  }
7308
8098
  if (!mcpJwt2) {
@@ -7433,7 +8223,7 @@ function resolveDeploymentMode() {
7433
8223
  if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
7434
8224
  try {
7435
8225
  if (existsSync22(CONFIG_PATH4)) {
7436
- const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
8226
+ const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
7437
8227
  const val = m?.[1]?.toLowerCase();
7438
8228
  if (val === "bare-host" || val === "docker") return val;
7439
8229
  }
@@ -7460,16 +8250,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
7460
8250
  meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
7461
8251
  } catch {
7462
8252
  }
7463
- const claudeDir = join18(homedir20(), ".claude");
8253
+ const claudeDir = join19(homedir21(), ".claude");
7464
8254
  try {
7465
- const settings = JSON.parse(readFileSync20(join18(claudeDir, "settings.json"), "utf-8"));
8255
+ const settings = JSON.parse(readFileSync21(join19(claudeDir, "settings.json"), "utf-8"));
7466
8256
  const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
7467
8257
  if (plugins.length) meta.enabled_plugins = plugins;
7468
8258
  if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
7469
8259
  } catch {
7470
8260
  }
7471
8261
  try {
7472
- const mcpCache = JSON.parse(readFileSync20(join18(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
8262
+ const mcpCache = JSON.parse(readFileSync21(join19(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
7473
8263
  const mcpNames = Object.keys(mcpCache);
7474
8264
  if (mcpNames.length) meta.mcp_servers = mcpNames;
7475
8265
  } catch {
@@ -7481,10 +8271,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
7481
8271
  } catch {
7482
8272
  }
7483
8273
  try {
7484
- const sessionsDir = join18(claudeDir, "sessions");
8274
+ const sessionsDir = join19(claudeDir, "sessions");
7485
8275
  const files = readdirSync4(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
7486
8276
  for (const f of files) {
7487
- const s = JSON.parse(readFileSync20(join18(sessionsDir, f), "utf-8"));
8277
+ const s = JSON.parse(readFileSync21(join19(sessionsDir, f), "utf-8"));
7488
8278
  if (s.version) {
7489
8279
  meta.cc_version = meta.cc_version || s.version;
7490
8280
  break;
@@ -7686,7 +8476,7 @@ async function installCommand(opts = {}) {
7686
8476
  await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
7687
8477
  emit("install", {
7688
8478
  phase: "started",
7689
- cli_version_to: "1.7.88",
8479
+ cli_version_to: "1.7.89",
7690
8480
  agents_detected: agents.map((a) => a.kind),
7691
8481
  with_github: false,
7692
8482
  with_local_cc: false,
@@ -7698,9 +8488,9 @@ async function installCommand(opts = {}) {
7698
8488
  const scripts = writeHookScripts();
7699
8489
  console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
7700
8490
  for (const mode of ["edit", "bash"]) {
7701
- const pidFile = join18(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
8491
+ const pidFile = join19(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
7702
8492
  try {
7703
- const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
8493
+ const pid = parseInt(readFileSync21(pidFile, "utf-8").trim(), 10);
7704
8494
  if (pid > 0) {
7705
8495
  process.kill(pid, "SIGTERM");
7706
8496
  console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
@@ -7763,12 +8553,15 @@ async function installCommand(opts = {}) {
7763
8553
  skillJudgeScriptPath: scripts.skillJudgeScript,
7764
8554
  bashFollowupScriptPath: scripts.bashFollowupScript,
7765
8555
  editPrecheckScriptPath: scripts.editPrecheckScript,
8556
+ editFollowupScriptPath: scripts.editFollowupScript,
7766
8557
  cwePrecheckScriptPath: scripts.cwePrecheckScript,
7767
8558
  cvePrecheckScriptPath: scripts.cvePrecheckScript,
7768
8559
  agentJudgeScriptPath: scripts.agentJudgeScript,
7769
8560
  stopSummaryScriptPath: scripts.stopSummaryScript,
7770
8561
  sessionStartScriptPath: scripts.sessionStartScript,
7771
8562
  transcriptSyncScriptPath: scripts.transcriptSyncScript,
8563
+ subagentStartScriptPath: scripts.subagentStartScript,
8564
+ subagentStopScriptPath: scripts.subagentStopScript,
7772
8565
  userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
7773
8566
  promptRouteScriptPath: scripts.promptRouteScript,
7774
8567
  installScanScriptPath: scripts.installScanScript,
@@ -7818,7 +8611,7 @@ async function installCommand(opts = {}) {
7818
8611
  if (mintResp.ok) {
7819
8612
  const minted = await mintResp.json();
7820
8613
  mcpJwt2 = minted.token;
7821
- writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
8614
+ writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
7822
8615
  } else {
7823
8616
  console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
7824
8617
  }
@@ -7846,7 +8639,7 @@ async function installCommand(opts = {}) {
7846
8639
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7847
8640
  }
7848
8641
  const minted = await mintResp.json();
7849
- writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
8642
+ writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7850
8643
  const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
7851
8644
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7852
8645
  console.log(` url: ${mcp.url}`);
@@ -7868,7 +8661,7 @@ async function installCommand(opts = {}) {
7868
8661
  if (hasCursor && !opts.noMcp) {
7869
8662
  try {
7870
8663
  if (useLocalMcp) {
7871
- const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
8664
+ const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
7872
8665
  if (!existsSync22(jwtPath)) {
7873
8666
  const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
7874
8667
  method: "POST",
@@ -7877,7 +8670,7 @@ async function installCommand(opts = {}) {
7877
8670
  });
7878
8671
  if (mintResp.ok) {
7879
8672
  const minted = await mintResp.json();
7880
- writeFileSync16(jwtPath, minted.token + "\n", { mode: 384 });
8673
+ writeFileSync17(jwtPath, minted.token + "\n", { mode: 384 });
7881
8674
  }
7882
8675
  }
7883
8676
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
@@ -7897,7 +8690,7 @@ async function installCommand(opts = {}) {
7897
8690
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7898
8691
  }
7899
8692
  const minted = await mintResp.json();
7900
- writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
8693
+ writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7901
8694
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
7902
8695
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7903
8696
  console.log(` url: ${mcp.url}`);
@@ -7910,10 +8703,10 @@ async function installCommand(opts = {}) {
7910
8703
  }
7911
8704
  if (hasCodex && !opts.noMcp) {
7912
8705
  try {
7913
- const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
8706
+ const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
7914
8707
  let mcpJwt2 = "";
7915
8708
  try {
7916
- mcpJwt2 = readFileSync20(jwtPath, "utf-8").trim();
8709
+ mcpJwt2 = readFileSync21(jwtPath, "utf-8").trim();
7917
8710
  } catch {
7918
8711
  }
7919
8712
  if (!mcpJwt2) {
@@ -7931,7 +8724,7 @@ async function installCommand(opts = {}) {
7931
8724
  }
7932
8725
  const minted = await mintResp.json();
7933
8726
  mcpJwt2 = minted.token;
7934
- writeFileSync16(jwtPath, mcpJwt2 + "\n", { mode: 384 });
8727
+ writeFileSync17(jwtPath, mcpJwt2 + "\n", { mode: 384 });
7935
8728
  }
7936
8729
  const mcp = installCodexMcpConfig({
7937
8730
  gatewayUrl,
@@ -8043,14 +8836,15 @@ async function installCommand(opts = {}) {
8043
8836
  }
8044
8837
  console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
8045
8838
  const connectedRepo = detectGitRepo2() || void 0;
8046
- const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
8839
+ const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort, pglitePasswordPath } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
8047
8840
  console.log(` \u2713 pulled ${image}`);
8048
- console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);
8841
+ console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}`);
8842
+ console.log(` PGLite: postgresql://synkro@127.0.0.1:${hostPglitePort}/postgres (password: ${pglitePasswordPath})`);
8049
8843
  console.log(" waiting for container to be ready...");
8050
8844
  const ready = await waitForContainerReady(6e4);
8051
8845
  if (ready) {
8052
8846
  console.log(" \u2713 container ready");
8053
- const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8847
+ const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8054
8848
  try {
8055
8849
  const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
8056
8850
  method: "POST",
@@ -8093,7 +8887,7 @@ async function installCommand(opts = {}) {
8093
8887
  try {
8094
8888
  let mcpToken = "";
8095
8889
  try {
8096
- mcpToken = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8890
+ mcpToken = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8097
8891
  } catch {
8098
8892
  }
8099
8893
  if (mcpToken) {
@@ -8261,8 +9055,8 @@ function writeSynkroFileIfMissing(opts) {
8261
9055
  try {
8262
9056
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8263
9057
  if (!root) return;
8264
- if (root === homedir20()) return;
8265
- const fp = join18(root, "synkro.toml");
9058
+ if (root === homedir21()) return;
9059
+ const fp = join19(root, "synkro.toml");
8266
9060
  let hasFile = false;
8267
9061
  try {
8268
9062
  hasFile = statSync2(fp).isFile();
@@ -8294,7 +9088,7 @@ function writeSynkroFileIfMissing(opts) {
8294
9088
  "cve = true",
8295
9089
  ""
8296
9090
  ].join("\n");
8297
- writeFileSync16(fp, toml, "utf-8");
9091
+ writeFileSync17(fp, toml, "utf-8");
8298
9092
  console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);
8299
9093
  } catch {
8300
9094
  }
@@ -8302,12 +9096,12 @@ function writeSynkroFileIfMissing(opts) {
8302
9096
  function updateSynkroTomlLocation(location) {
8303
9097
  try {
8304
9098
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8305
- if (!root || root === homedir20()) return;
8306
- const fp = join18(root, "synkro.toml");
9099
+ if (!root || root === homedir21()) return;
9100
+ const fp = join19(root, "synkro.toml");
8307
9101
  let txt = "";
8308
9102
  try {
8309
9103
  if (!statSync2(fp).isFile()) return;
8310
- txt = readFileSync20(fp, "utf-8");
9104
+ txt = readFileSync21(fp, "utf-8");
8311
9105
  } catch {
8312
9106
  return;
8313
9107
  }
@@ -8315,7 +9109,7 @@ function updateSynkroTomlLocation(location) {
8315
9109
  if (!re.test(txt)) return;
8316
9110
  const next = txt.replace(re, `$1"${location}"`);
8317
9111
  if (next !== txt) {
8318
- writeFileSync16(fp, next, "utf-8");
9112
+ writeFileSync17(fp, next, "utf-8");
8319
9113
  console.log(` synkro.toml: [grader] location = "${location}"`);
8320
9114
  }
8321
9115
  } catch {
@@ -8325,9 +9119,9 @@ function readFullSynkroFile() {
8325
9119
  try {
8326
9120
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8327
9121
  if (!root) return null;
8328
- const fp = join18(root, "synkro.toml");
9122
+ const fp = join19(root, "synkro.toml");
8329
9123
  if (!existsSync22(fp)) return null;
8330
- const parsed = parseSynkroToml2(readFileSync20(fp, "utf-8"));
9124
+ const parsed = parseSynkroToml2(readFileSync21(fp, "utf-8"));
8331
9125
  const valid = ["claude-code", "cursor", "codex"];
8332
9126
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
8333
9127
  const resolved = resolveGraderPool(parsed);
@@ -8367,7 +9161,7 @@ function reconcileHarness() {
8367
9161
  console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
8368
9162
  const scripts = writeHookScripts();
8369
9163
  console.log("Wrote hook scripts to ~/.synkro/hooks/");
8370
- const ccSettings = join18(homedir20(), ".claude", "settings.json");
9164
+ const ccSettings = join19(homedir21(), ".claude", "settings.json");
8371
9165
  if (wantCC) {
8372
9166
  installCCHooks(ccSettings, {
8373
9167
  bashJudgeScriptPath: scripts.bashScript,
@@ -8390,7 +9184,7 @@ function reconcileHarness() {
8390
9184
  });
8391
9185
  console.log(" \u2713 Claude Code hooks registered");
8392
9186
  try {
8393
- const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
9187
+ const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8394
9188
  if (mcpJwt2) {
8395
9189
  installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt2, local: true });
8396
9190
  console.log(" \u2713 Claude Code MCP registered");
@@ -8402,7 +9196,7 @@ function reconcileHarness() {
8402
9196
  if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
8403
9197
  if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
8404
9198
  }
8405
- const cursorHooks = join18(homedir20(), ".cursor", "hooks.json");
9199
+ const cursorHooks = join19(homedir21(), ".cursor", "hooks.json");
8406
9200
  if (wantCursor) {
8407
9201
  installCursorHooks(cursorHooks, {
8408
9202
  bashJudgeScriptPath: scripts.cursorBashJudgeScript,
@@ -8433,19 +9227,22 @@ function reconcileHarness() {
8433
9227
  if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
8434
9228
  if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
8435
9229
  }
8436
- const codexHooks = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "hooks.json");
9230
+ const codexHooks = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "hooks.json");
8437
9231
  if (wantCodex) {
8438
9232
  installCodexHooks(codexHooks, {
8439
9233
  bashJudgeScriptPath: scripts.bashScript,
8440
9234
  skillJudgeScriptPath: scripts.skillJudgeScript,
8441
9235
  bashFollowupScriptPath: scripts.bashFollowupScript,
8442
9236
  editPrecheckScriptPath: scripts.editPrecheckScript,
9237
+ editFollowupScriptPath: scripts.editFollowupScript,
8443
9238
  cwePrecheckScriptPath: scripts.cwePrecheckScript,
8444
9239
  cvePrecheckScriptPath: scripts.cvePrecheckScript,
8445
9240
  agentJudgeScriptPath: scripts.agentJudgeScript,
8446
9241
  stopSummaryScriptPath: scripts.stopSummaryScript,
8447
9242
  sessionStartScriptPath: scripts.sessionStartScript,
8448
9243
  transcriptSyncScriptPath: scripts.transcriptSyncScript,
9244
+ subagentStartScriptPath: scripts.subagentStartScript,
9245
+ subagentStopScriptPath: scripts.subagentStopScript,
8449
9246
  userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
8450
9247
  promptRouteScriptPath: scripts.promptRouteScript,
8451
9248
  installScanScriptPath: scripts.installScanScript,
@@ -8525,7 +9322,7 @@ async function syncSkillFiles() {
8525
9322
  if (resolved.length === 0) return;
8526
9323
  const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
8527
9324
  const tasks = resolved.map((fp) => {
8528
- const content = readFileSync20(fp, "utf-8");
9325
+ const content = readFileSync21(fp, "utf-8");
8529
9326
  const source = `skill:${fp.split("/").pop()}`;
8530
9327
  if (!content.trim()) {
8531
9328
  console.log(` \u2298 skill ${source}: empty file, skipped`);
@@ -8550,11 +9347,11 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8550
9347
  } catch {
8551
9348
  }
8552
9349
  };
8553
- add(join18(homedir20(), ".claude", "skills"));
8554
- add(join18(homedir20(), ".agents", "skills"));
9350
+ add(join19(homedir21(), ".claude", "skills"));
9351
+ add(join19(homedir21(), ".agents", "skills"));
8555
9352
  if (repoRoot2) {
8556
- add(join18(repoRoot2, ".claude", "skills"));
8557
- add(join18(repoRoot2, ".agents", "skills"));
9353
+ add(join19(repoRoot2, ".claude", "skills"));
9354
+ add(join19(repoRoot2, ".agents", "skills"));
8558
9355
  }
8559
9356
  const out = [];
8560
9357
  const seen = /* @__PURE__ */ new Set();
@@ -8565,12 +9362,12 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8565
9362
  try {
8566
9363
  const st = statSync2(file);
8567
9364
  if (!st.isFile() || st.size > 2e5) return;
8568
- content = readFileSync20(file, "utf-8");
9365
+ content = readFileSync21(file, "utf-8");
8569
9366
  } catch {
8570
9367
  return;
8571
9368
  }
8572
9369
  if (!content.trim()) return;
8573
- const hash = createHash2("sha256").update(content).digest("hex");
9370
+ const hash = createHash4("sha256").update(content).digest("hex");
8574
9371
  if (seen.has(hash) || excludeHashes.has(hash)) return;
8575
9372
  seen.add(hash);
8576
9373
  const ingested = ingestedHashes.has(hash) || ingestedNames.has(normSkillName(name));
@@ -8585,7 +9382,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8585
9382
  }
8586
9383
  for (const entry of entries) {
8587
9384
  if (entry.startsWith(".")) continue;
8588
- const full = join18(root, entry);
9385
+ const full = join19(root, entry);
8589
9386
  let st;
8590
9387
  try {
8591
9388
  st = statSync2(full);
@@ -8593,7 +9390,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8593
9390
  continue;
8594
9391
  }
8595
9392
  if (st.isDirectory()) {
8596
- const skillMd = join18(full, "SKILL.md");
9393
+ const skillMd = join19(full, "SKILL.md");
8597
9394
  if (existsSync22(skillMd)) consider(skillMd, entry);
8598
9395
  } else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
8599
9396
  consider(full, entry.replace(/\.mdx?$/i, ""));
@@ -8603,7 +9400,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8603
9400
  return out;
8604
9401
  }
8605
9402
  function discoverySetHash(found) {
8606
- return createHash2("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
9403
+ return createHash4("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
8607
9404
  }
8608
9405
  async function promptSkillDiscovery(found) {
8609
9406
  if (!process.stdin.isTTY || found.length === 0) return [];
@@ -8643,7 +9440,7 @@ async function discoverAndIngestSkills() {
8643
9440
  if (sf?.skills?.length) {
8644
9441
  for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
8645
9442
  try {
8646
- excludeHashes.add(createHash2("sha256").update(readFileSync20(fp, "utf-8")).digest("hex"));
9443
+ excludeHashes.add(createHash4("sha256").update(readFileSync21(fp, "utf-8")).digest("hex"));
8647
9444
  } catch {
8648
9445
  }
8649
9446
  }
@@ -8674,13 +9471,13 @@ async function discoverAndIngestSkills() {
8674
9471
  const setHash = discoverySetHash(selectable);
8675
9472
  let prev = "";
8676
9473
  try {
8677
- prev = readFileSync20(SKILLS_DISCOVERED_PATH, "utf-8").trim();
9474
+ prev = readFileSync21(SKILLS_DISCOVERED_PATH, "utf-8").trim();
8678
9475
  } catch {
8679
9476
  }
8680
9477
  if (prev === setHash) return;
8681
9478
  const picks = await promptSkillDiscovery(found);
8682
9479
  try {
8683
- writeFileSync16(SKILLS_DISCOVERED_PATH, setHash);
9480
+ writeFileSync17(SKILLS_DISCOVERED_PATH, setHash);
8684
9481
  } catch {
8685
9482
  }
8686
9483
  if (picks.length === 0) {
@@ -8719,9 +9516,9 @@ function ensureReachabilityGitHook() {
8719
9516
  const root = run("git rev-parse --show-toplevel");
8720
9517
  if (!root) return null;
8721
9518
  let hooksDir = run("git config --get core.hooksPath");
8722
- hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join18(root, hooksDir) : join18(root, ".git", "hooks");
9519
+ hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join19(root, hooksDir) : join19(root, ".git", "hooks");
8723
9520
  if (!existsSync22(hooksDir)) mkdirSync15(hooksDir, { recursive: true });
8724
- const hookPath = join18(hooksDir, "post-commit");
9521
+ const hookPath = join19(hooksDir, "post-commit");
8725
9522
  const resolvedBin = resolveSynkroBinPath();
8726
9523
  const invoke = resolvedBin ? `"${resolvedBin}" reachability-scan --quiet` : "true";
8727
9524
  const START = "# >>> synkro reachability (managed) >>>";
@@ -8735,23 +9532,23 @@ function ensureReachabilityGitHook() {
8735
9532
  ].join("\n");
8736
9533
  const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8737
9534
  if (!existsSync22(hookPath)) {
8738
- writeFileSync16(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
9535
+ writeFileSync17(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
8739
9536
  return "installed";
8740
9537
  }
8741
- let cur = readFileSync20(hookPath, "utf-8");
9538
+ let cur = readFileSync21(hookPath, "utf-8");
8742
9539
  if (cur.includes(START)) {
8743
9540
  cur = cur.replace(new RegExp(esc(START) + "[\\s\\S]*?" + esc(END), "m"), block);
8744
- writeFileSync16(hookPath, cur);
9541
+ writeFileSync17(hookPath, cur);
8745
9542
  try {
8746
- chmodSync4(hookPath, 493);
9543
+ chmodSync5(hookPath, 493);
8747
9544
  } catch {
8748
9545
  }
8749
9546
  return "updated";
8750
9547
  }
8751
9548
  const sep = cur.endsWith("\n") ? "" : "\n";
8752
- writeFileSync16(hookPath, cur + sep + "\n" + block + "\n");
9549
+ writeFileSync17(hookPath, cur + sep + "\n" + block + "\n");
8753
9550
  try {
8754
- chmodSync4(hookPath, 493);
9551
+ chmodSync5(hookPath, 493);
8755
9552
  } catch {
8756
9553
  }
8757
9554
  return "updated";
@@ -8777,7 +9574,7 @@ function detectGitRepo2() {
8777
9574
  function getClaudeProjectsFolder() {
8778
9575
  const cwd = process.cwd();
8779
9576
  const sanitized = "-" + cwd.replace(/\//g, "-");
8780
- const projectsDir = join18(homedir20(), ".claude", "projects", sanitized);
9577
+ const projectsDir = join19(homedir21(), ".claude", "projects", sanitized);
8781
9578
  return existsSync22(projectsDir) ? projectsDir : null;
8782
9579
  }
8783
9580
  function extractSessionInsights(projectsDir) {
@@ -8785,9 +9582,9 @@ function extractSessionInsights(projectsDir) {
8785
9582
  const files = readdirSync4(projectsDir).filter((f) => f.endsWith(".jsonl"));
8786
9583
  for (const file of files) {
8787
9584
  const sessionId = file.replace(".jsonl", "");
8788
- const filePath = join18(projectsDir, file);
9585
+ const filePath = join19(projectsDir, file);
8789
9586
  try {
8790
- const content = readFileSync20(filePath, "utf-8");
9587
+ const content = readFileSync21(filePath, "utf-8");
8791
9588
  const lines = content.split("\n").filter(Boolean);
8792
9589
  for (let i = 0; i < lines.length; i++) {
8793
9590
  try {
@@ -8863,7 +9660,7 @@ function extractTextContent(content) {
8863
9660
  return "";
8864
9661
  }
8865
9662
  function getCodexTranscriptFiles(repo) {
8866
- const sessionsDir = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "sessions");
9663
+ const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
8867
9664
  if (!existsSync22(sessionsDir)) return [];
8868
9665
  let relative = [];
8869
9666
  try {
@@ -8871,9 +9668,9 @@ function getCodexTranscriptFiles(repo) {
8871
9668
  } catch {
8872
9669
  return [];
8873
9670
  }
8874
- return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join18(sessionsDir, p)).filter((filePath) => {
9671
+ return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
8875
9672
  try {
8876
- const first = readFileSync20(filePath, "utf-8").split("\n", 1)[0];
9673
+ const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
8877
9674
  const meta = JSON.parse(first);
8878
9675
  const cwd = typeof meta?.payload?.cwd === "string" ? resolve4(meta.payload.cwd) : "";
8879
9676
  const root = resolve4(repo);
@@ -8883,38 +9680,45 @@ function getCodexTranscriptFiles(repo) {
8883
9680
  }
8884
9681
  });
8885
9682
  }
9683
+ function isJsonSyntaxError(error) {
9684
+ return error instanceof SyntaxError;
9685
+ }
8886
9686
  function parseCodexTranscriptFile(filePath) {
8887
- const lines = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean);
9687
+ const transcript = readFileSync21(filePath, "utf-8");
9688
+ const lines = transcript.split("\n");
9689
+ const transcriptUsage = parseCodexTranscriptUsage(transcript);
8888
9690
  let sessionId = "";
8889
9691
  let model = "";
8890
- const messages = [];
8891
- for (let i = 0; i < lines.length; i++) {
9692
+ for (const line of lines) {
8892
9693
  try {
8893
- const entry = JSON.parse(lines[i]);
9694
+ const entry = JSON.parse(line);
8894
9695
  if (entry.type === "session_meta") {
8895
9696
  sessionId = String(entry.payload?.session_id || entry.payload?.id || sessionId);
8896
- continue;
8897
- }
8898
- if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
9697
+ } else if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
8899
9698
  model = entry.payload.model;
8900
- continue;
8901
9699
  }
8902
- if (entry.type !== "event_msg") continue;
8903
- const eventType = entry.payload?.type;
8904
- if (eventType !== "user_message" && eventType !== "agent_message") continue;
8905
- const text = String(entry.payload?.message || "").slice(0, 8e3);
8906
- if (!text) continue;
8907
- const type = eventType === "user_message" ? "user" : "assistant";
8908
- messages.push({
8909
- message_index: i,
8910
- type,
8911
- content: text,
8912
- ...type === "assistant" && model ? { model } : {}
8913
- });
8914
- } catch {
9700
+ } catch (error) {
9701
+ if (!isJsonSyntaxError(error)) throw error;
8915
9702
  }
8916
9703
  }
8917
- return { sessionId, messages };
9704
+ const messages = parseCodexConversationMessages(transcript).map((message) => {
9705
+ const turnUsage = message.role === "assistant" ? transcriptUsage?.turnsByLine.get(message.lineIndex) : void 0;
9706
+ return {
9707
+ message_index: message.lineIndex,
9708
+ uuid: message.uuid,
9709
+ type: message.role,
9710
+ content: message.content,
9711
+ ...message.role === "assistant" && (turnUsage?.model || model) ? { model: turnUsage?.model || model } : {},
9712
+ ...turnUsage ? { usage: turnUsage.usage } : {},
9713
+ ...message.timestamp ? { timestamp: message.timestamp } : {}
9714
+ };
9715
+ });
9716
+ return {
9717
+ sessionId,
9718
+ messages,
9719
+ model: transcriptUsage?.model || model,
9720
+ usage: transcriptUsage?.total
9721
+ };
8918
9722
  }
8919
9723
  async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
8920
9724
  const files = getCodexTranscriptFiles(repo);
@@ -8931,7 +9735,15 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
8931
9735
  const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {
8932
9736
  method: "POST",
8933
9737
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
8934
- body: JSON.stringify({ session_id: parsed.sessionId, repo, messages }),
9738
+ body: JSON.stringify({
9739
+ session_id: parsed.sessionId,
9740
+ repo,
9741
+ messages,
9742
+ session_usage: parsed.usage,
9743
+ model: parsed.model,
9744
+ harness: "codex",
9745
+ usage_cumulative: true
9746
+ }),
8935
9747
  signal: AbortSignal.timeout(15e3)
8936
9748
  });
8937
9749
  if (resp.ok) {
@@ -8939,7 +9751,7 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
8939
9751
  totalSessions++;
8940
9752
  totalMessages += result.ingested ?? messages.length;
8941
9753
  }
8942
- writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
9754
+ writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
8943
9755
  } catch {
8944
9756
  }
8945
9757
  if ((i + 1) % 10 === 0 || i === files.length - 1) {
@@ -8953,14 +9765,14 @@ function cursorProjectSlug(workspaceRoot) {
8953
9765
  return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
8954
9766
  }
8955
9767
  function getCursorTranscriptsDir() {
8956
- const dir = join18(homedir20(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
9768
+ const dir = join19(homedir21(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
8957
9769
  return existsSync22(dir) ? dir : null;
8958
9770
  }
8959
9771
  function isSafeConvId(id) {
8960
9772
  return /^[A-Za-z0-9_-]+$/.test(id);
8961
9773
  }
8962
9774
  function parseCursorTranscriptFile(filePath) {
8963
- const content = readFileSync20(filePath, "utf-8");
9775
+ const content = readFileSync21(filePath, "utf-8");
8964
9776
  const lines = content.split("\n").filter(Boolean);
8965
9777
  const messages = [];
8966
9778
  for (let i = 0; i < lines.length; i++) {
@@ -8992,7 +9804,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8992
9804
  for (let i = 0; i < convDirs.length; i++) {
8993
9805
  const convId = convDirs[i];
8994
9806
  if (!isSafeConvId(convId)) continue;
8995
- const filePath = join18(dir, convId, `${convId}.jsonl`);
9807
+ const filePath = join19(dir, convId, `${convId}.jsonl`);
8996
9808
  if (!existsSync22(filePath)) continue;
8997
9809
  try {
8998
9810
  const all = parseCursorTranscriptFile(filePath);
@@ -9015,8 +9827,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
9015
9827
  process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
9016
9828
  }
9017
9829
  try {
9018
- const lc = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length;
9019
- writeFileSync16(join18(OFFSETS_DIR, convId), String(lc), "utf-8");
9830
+ const lc = readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length;
9831
+ writeFileSync17(join19(OFFSETS_DIR, convId), String(lc), "utf-8");
9020
9832
  } catch {
9021
9833
  }
9022
9834
  }
@@ -9024,7 +9836,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
9024
9836
  return { sessions: totalSessions, messages: totalMessages };
9025
9837
  }
9026
9838
  function parseTranscriptFile(filePath) {
9027
- const content = readFileSync20(filePath, "utf-8");
9839
+ const content = readFileSync21(filePath, "utf-8");
9028
9840
  const lines = content.split("\n").filter(Boolean);
9029
9841
  const messages = [];
9030
9842
  for (let i = 0; i < lines.length; i++) {
@@ -9072,7 +9884,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
9072
9884
  for (let i = 0; i < files.length; i++) {
9073
9885
  const file = files[i];
9074
9886
  const sessionId = file.replace(".jsonl", "");
9075
- const filePath = join18(projectsDir, file);
9887
+ const filePath = join19(projectsDir, file);
9076
9888
  try {
9077
9889
  const allMessages = parseTranscriptFile(filePath);
9078
9890
  const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
@@ -9094,9 +9906,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
9094
9906
  process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
9095
9907
  }
9096
9908
  try {
9097
- const content = readFileSync20(join18(projectsDir, file), "utf-8");
9909
+ const content = readFileSync21(join19(projectsDir, file), "utf-8");
9098
9910
  const lineCount = content.split("\n").filter(Boolean).length;
9099
- writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9911
+ writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9100
9912
  } catch {
9101
9913
  }
9102
9914
  }
@@ -9117,7 +9929,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
9117
9929
  const sessions = [];
9118
9930
  for (const file of batch) {
9119
9931
  const sessionId = file.replace(".jsonl", "");
9120
- const filePath = join18(projectsDir, file);
9932
+ const filePath = join19(projectsDir, file);
9121
9933
  try {
9122
9934
  const allMessages = parseTranscriptFile(filePath);
9123
9935
  const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
@@ -9146,11 +9958,11 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
9146
9958
  }
9147
9959
  for (const file of batch) {
9148
9960
  const sessionId = file.replace(".jsonl", "");
9149
- const filePath = join18(projectsDir, file);
9961
+ const filePath = join19(projectsDir, file);
9150
9962
  try {
9151
- const content = readFileSync20(filePath, "utf-8");
9963
+ const content = readFileSync21(filePath, "utf-8");
9152
9964
  const lineCount = content.split("\n").filter(Boolean).length;
9153
- writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9965
+ writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9154
9966
  } catch {
9155
9967
  }
9156
9968
  }
@@ -9168,7 +9980,16 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
9168
9980
  try {
9169
9981
  const parsed = parseCodexTranscriptFile(filePath);
9170
9982
  const messages = parsed.messages.length > 500 ? parsed.messages.slice(-500) : parsed.messages;
9171
- if (parsed.sessionId && messages.length > 0) sessions.push({ cc_session_id: parsed.sessionId, messages });
9983
+ if (parsed.sessionId && messages.length > 0) {
9984
+ sessions.push({
9985
+ cc_session_id: parsed.sessionId,
9986
+ messages,
9987
+ model: parsed.model,
9988
+ session_usage: parsed.usage,
9989
+ harness: "codex",
9990
+ usage_cumulative: true
9991
+ });
9992
+ }
9172
9993
  } catch {
9173
9994
  }
9174
9995
  }
@@ -9190,7 +10011,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
9190
10011
  try {
9191
10012
  const parsed = parseCodexTranscriptFile(filePath);
9192
10013
  if (parsed.sessionId) {
9193
- writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
10014
+ writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
9194
10015
  }
9195
10016
  } catch {
9196
10017
  }
@@ -9221,10 +10042,12 @@ var init_install = __esm({
9221
10042
  init_codexCloudSetup();
9222
10043
  init_ptyShim();
9223
10044
  init_graderSmoke();
9224
- SYNKRO_DIR11 = join18(homedir20(), ".synkro");
9225
- HOOKS_DIR = join18(SYNKRO_DIR11, "hooks");
9226
- BIN_DIR = join18(SYNKRO_DIR11, "bin");
9227
- CONFIG_PATH4 = join18(SYNKRO_DIR11, "config.env");
10045
+ init_codexTranscriptUsage();
10046
+ init_codexTranscriptMessages();
10047
+ SYNKRO_DIR11 = join19(homedir21(), ".synkro");
10048
+ HOOKS_DIR = join19(SYNKRO_DIR11, "hooks");
10049
+ BIN_DIR = join19(SYNKRO_DIR11, "bin");
10050
+ CONFIG_PATH4 = join19(SYNKRO_DIR11, "config.env");
9228
10051
  MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
9229
10052
  import { readFileSync } from 'node:fs';
9230
10053
  import { homedir } from 'node:os';
@@ -9335,23 +10158,23 @@ rl.on('line', async (line) => {
9335
10158
  }
9336
10159
  });
9337
10160
  `;
9338
- OFFSETS_DIR = join18(SYNKRO_DIR11, ".transcript-offsets");
9339
- CLOUD_JWT_PATH = join18(SYNKRO_DIR11, ".cloud-jwt");
9340
- SKILLS_DISCOVERED_PATH = join18(SYNKRO_DIR11, ".skills-discovered");
10161
+ OFFSETS_DIR = join19(SYNKRO_DIR11, ".transcript-offsets");
10162
+ CLOUD_JWT_PATH = join19(SYNKRO_DIR11, ".cloud-jwt");
10163
+ SKILLS_DISCOVERED_PATH = join19(SYNKRO_DIR11, ".skills-discovered");
9341
10164
  }
9342
10165
  });
9343
10166
 
9344
10167
  // cli/local-cc/install.ts
9345
- import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as writeFileSync17, readFileSync as readFileSync21, chmodSync as chmodSync5, copyFileSync as copyFileSync2, renameSync as renameSync7, unlinkSync as unlinkSync8, openSync as openSync2, fsyncSync, closeSync as closeSync2 } from "fs";
9346
- import { join as join19 } from "path";
9347
- import { homedir as homedir21 } from "os";
10168
+ import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as writeFileSync18, readFileSync as readFileSync22, chmodSync as chmodSync6, copyFileSync as copyFileSync2, renameSync as renameSync8, unlinkSync as unlinkSync8, openSync as openSync2, fsyncSync, closeSync as closeSync2 } from "fs";
10169
+ import { join as join20 } from "path";
10170
+ import { homedir as homedir22 } from "os";
9348
10171
  import { spawnSync as spawnSync7 } from "child_process";
9349
10172
  function writePluginFiles() {
9350
10173
  for (const c of CHANNELS) {
9351
10174
  mkdirSync16(c.sessionDir, { recursive: true });
9352
10175
  mkdirSync16(c.pluginSettingsDir, { recursive: true });
9353
- writeFileSync17(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
9354
- writeFileSync17(
10176
+ writeFileSync18(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
10177
+ writeFileSync18(
9355
10178
  c.pluginSettingsPath,
9356
10179
  JSON.stringify({
9357
10180
  fastMode: true,
@@ -9366,8 +10189,8 @@ function writePluginFiles() {
9366
10189
  }, null, 2) + "\n",
9367
10190
  "utf-8"
9368
10191
  );
9369
- writeFileSync17(c.runScriptPath, c.runScriptSource, "utf-8");
9370
- chmodSync5(c.runScriptPath, 493);
10192
+ writeFileSync18(c.runScriptPath, c.runScriptSource, "utf-8");
10193
+ chmodSync6(c.runScriptPath, 493);
9371
10194
  }
9372
10195
  }
9373
10196
  function runBunInstall() {
@@ -9388,7 +10211,7 @@ function safelyMutateClaudeJson(mutator) {
9388
10211
  if (!existsSync23(CLAUDE_JSON_PATH)) {
9389
10212
  return;
9390
10213
  }
9391
- const originalText = readFileSync21(CLAUDE_JSON_PATH, "utf-8");
10214
+ const originalText = readFileSync22(CLAUDE_JSON_PATH, "utf-8");
9392
10215
  let parsed;
9393
10216
  try {
9394
10217
  parsed = JSON.parse(originalText);
@@ -9420,14 +10243,14 @@ function safelyMutateClaudeJson(mutator) {
9420
10243
  copyFileSync2(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
9421
10244
  const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
9422
10245
  try {
9423
- writeFileSync17(tmpPath, newText, "utf-8");
10246
+ writeFileSync18(tmpPath, newText, "utf-8");
9424
10247
  const fd = openSync2(tmpPath, "r");
9425
10248
  try {
9426
10249
  fsyncSync(fd);
9427
10250
  } finally {
9428
10251
  closeSync2(fd);
9429
10252
  }
9430
- renameSync7(tmpPath, CLAUDE_JSON_PATH);
10253
+ renameSync8(tmpPath, CLAUDE_JSON_PATH);
9431
10254
  } catch (err) {
9432
10255
  try {
9433
10256
  unlinkSync8(tmpPath);
@@ -9453,7 +10276,7 @@ function writeProjectMcpJson() {
9453
10276
  }
9454
10277
  }
9455
10278
  };
9456
- writeFileSync17(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
10279
+ writeFileSync18(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
9457
10280
  }
9458
10281
  }
9459
10282
  function patchClaudeJson() {
@@ -9530,42 +10353,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
9530
10353
  var init_install2 = __esm({
9531
10354
  "cli/local-cc/install.ts"() {
9532
10355
  "use strict";
9533
- CLAUDE_JSON_BACKUP_PATH = join19(homedir21(), ".claude.json.synkro-bak");
9534
- SESSION_DIR = join19(homedir21(), ".synkro", "cc_sessions");
9535
- PLUGIN_PATH = join19(SESSION_DIR, "synkro-channel.ts");
9536
- PLUGIN_PKG_PATH = join19(SESSION_DIR, "package.json");
9537
- PLUGIN_SETTINGS_DIR = join19(SESSION_DIR, ".claude");
9538
- PLUGIN_SETTINGS_PATH = join19(PLUGIN_SETTINGS_DIR, "settings.json");
9539
- PROJECT_MCP_PATH = join19(SESSION_DIR, ".mcp.json");
9540
- CLAUDE_JSON_PATH = join19(homedir21(), ".claude.json");
9541
- RUN_SCRIPT_PATH = join19(SESSION_DIR, "run-claude.sh");
10356
+ CLAUDE_JSON_BACKUP_PATH = join20(homedir22(), ".claude.json.synkro-bak");
10357
+ SESSION_DIR = join20(homedir22(), ".synkro", "cc_sessions");
10358
+ PLUGIN_PATH = join20(SESSION_DIR, "synkro-channel.ts");
10359
+ PLUGIN_PKG_PATH = join20(SESSION_DIR, "package.json");
10360
+ PLUGIN_SETTINGS_DIR = join20(SESSION_DIR, ".claude");
10361
+ PLUGIN_SETTINGS_PATH = join20(PLUGIN_SETTINGS_DIR, "settings.json");
10362
+ PROJECT_MCP_PATH = join20(SESSION_DIR, ".mcp.json");
10363
+ CLAUDE_JSON_PATH = join20(homedir22(), ".claude.json");
10364
+ RUN_SCRIPT_PATH = join20(SESSION_DIR, "run-claude.sh");
9542
10365
  TMUX_SESSION_NAME = "synkro-local-cc";
9543
10366
  CHANNEL_1_PORT = 8941;
9544
- SESSION_DIR_2 = join19(homedir21(), ".synkro", "cc_sessions_2");
9545
- PLUGIN_PATH_2 = join19(SESSION_DIR_2, "synkro-channel.ts");
9546
- PLUGIN_PKG_PATH_2 = join19(SESSION_DIR_2, "package.json");
9547
- PLUGIN_SETTINGS_DIR_2 = join19(SESSION_DIR_2, ".claude");
9548
- PLUGIN_SETTINGS_PATH_2 = join19(PLUGIN_SETTINGS_DIR_2, "settings.json");
9549
- PROJECT_MCP_PATH_2 = join19(SESSION_DIR_2, ".mcp.json");
9550
- RUN_SCRIPT_PATH_2 = join19(SESSION_DIR_2, "run-claude.sh");
10367
+ SESSION_DIR_2 = join20(homedir22(), ".synkro", "cc_sessions_2");
10368
+ PLUGIN_PATH_2 = join20(SESSION_DIR_2, "synkro-channel.ts");
10369
+ PLUGIN_PKG_PATH_2 = join20(SESSION_DIR_2, "package.json");
10370
+ PLUGIN_SETTINGS_DIR_2 = join20(SESSION_DIR_2, ".claude");
10371
+ PLUGIN_SETTINGS_PATH_2 = join20(PLUGIN_SETTINGS_DIR_2, "settings.json");
10372
+ PROJECT_MCP_PATH_2 = join20(SESSION_DIR_2, ".mcp.json");
10373
+ RUN_SCRIPT_PATH_2 = join20(SESSION_DIR_2, "run-claude.sh");
9551
10374
  TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
9552
10375
  CHANNEL_2_PORT = 8951;
9553
- SESSION_DIR_3 = join19(homedir21(), ".synkro", "cc_sessions_3");
9554
- PLUGIN_PATH_3 = join19(SESSION_DIR_3, "synkro-channel.ts");
9555
- PLUGIN_PKG_PATH_3 = join19(SESSION_DIR_3, "package.json");
9556
- PLUGIN_SETTINGS_DIR_3 = join19(SESSION_DIR_3, ".claude");
9557
- PLUGIN_SETTINGS_PATH_3 = join19(PLUGIN_SETTINGS_DIR_3, "settings.json");
9558
- PROJECT_MCP_PATH_3 = join19(SESSION_DIR_3, ".mcp.json");
9559
- RUN_SCRIPT_PATH_3 = join19(SESSION_DIR_3, "run-claude.sh");
10376
+ SESSION_DIR_3 = join20(homedir22(), ".synkro", "cc_sessions_3");
10377
+ PLUGIN_PATH_3 = join20(SESSION_DIR_3, "synkro-channel.ts");
10378
+ PLUGIN_PKG_PATH_3 = join20(SESSION_DIR_3, "package.json");
10379
+ PLUGIN_SETTINGS_DIR_3 = join20(SESSION_DIR_3, ".claude");
10380
+ PLUGIN_SETTINGS_PATH_3 = join20(PLUGIN_SETTINGS_DIR_3, "settings.json");
10381
+ PROJECT_MCP_PATH_3 = join20(SESSION_DIR_3, ".mcp.json");
10382
+ RUN_SCRIPT_PATH_3 = join20(SESSION_DIR_3, "run-claude.sh");
9560
10383
  TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
9561
10384
  CHANNEL_3_PORT = 8942;
9562
- SESSION_DIR_4 = join19(homedir21(), ".synkro", "cc_sessions_4");
9563
- PLUGIN_PATH_4 = join19(SESSION_DIR_4, "synkro-channel.ts");
9564
- PLUGIN_PKG_PATH_4 = join19(SESSION_DIR_4, "package.json");
9565
- PLUGIN_SETTINGS_DIR_4 = join19(SESSION_DIR_4, ".claude");
9566
- PLUGIN_SETTINGS_PATH_4 = join19(PLUGIN_SETTINGS_DIR_4, "settings.json");
9567
- PROJECT_MCP_PATH_4 = join19(SESSION_DIR_4, ".mcp.json");
9568
- RUN_SCRIPT_PATH_4 = join19(SESSION_DIR_4, "run-claude.sh");
10385
+ SESSION_DIR_4 = join20(homedir22(), ".synkro", "cc_sessions_4");
10386
+ PLUGIN_PATH_4 = join20(SESSION_DIR_4, "synkro-channel.ts");
10387
+ PLUGIN_PKG_PATH_4 = join20(SESSION_DIR_4, "package.json");
10388
+ PLUGIN_SETTINGS_DIR_4 = join20(SESSION_DIR_4, ".claude");
10389
+ PLUGIN_SETTINGS_PATH_4 = join20(PLUGIN_SETTINGS_DIR_4, "settings.json");
10390
+ PROJECT_MCP_PATH_4 = join20(SESSION_DIR_4, ".mcp.json");
10391
+ RUN_SCRIPT_PATH_4 = join20(SESSION_DIR_4, "run-claude.sh");
9569
10392
  TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
9570
10393
  CHANNEL_4_PORT = 8952;
9571
10394
  RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
@@ -9840,8 +10663,8 @@ __export(disconnect_exports, {
9840
10663
  disconnectCommand: () => disconnectCommand
9841
10664
  });
9842
10665
  import { existsSync as existsSync24, rmSync as rmSync3, readdirSync as readdirSync5 } from "fs";
9843
- import { homedir as homedir22 } from "os";
9844
- import { join as join20 } from "path";
10666
+ import { homedir as homedir23 } from "os";
10667
+ import { join as join21 } from "path";
9845
10668
  import { spawnSync as spawnSync8 } from "child_process";
9846
10669
  import { createInterface as createInterface3 } from "readline";
9847
10670
  async function tearDownLocalCC() {
@@ -9956,13 +10779,13 @@ async function disconnectCommand(args2 = [], opts = {}) {
9956
10779
  console.log(`\u2713 wiped ${SYNKRO_DIR12} entirely \u2014 including all scan data and backups`);
9957
10780
  } else {
9958
10781
  const keep = /* @__PURE__ */ new Set([
9959
- join20(SYNKRO_DIR12, "pgdata"),
9960
- join20(SYNKRO_DIR12, "pgdata-backups"),
9961
- join20(SYNKRO_DIR12, ".transcript-offsets")
10782
+ join21(SYNKRO_DIR12, "pgdata"),
10783
+ join21(SYNKRO_DIR12, "pgdata-backups"),
10784
+ join21(SYNKRO_DIR12, ".transcript-offsets")
9962
10785
  ]);
9963
10786
  const preserved = [];
9964
10787
  for (const entry of readdirSync5(SYNKRO_DIR12)) {
9965
- const full = join20(SYNKRO_DIR12, entry);
10788
+ const full = join21(SYNKRO_DIR12, entry);
9966
10789
  if (keep.has(full)) {
9967
10790
  preserved.push(entry);
9968
10791
  continue;
@@ -10000,14 +10823,14 @@ var init_disconnect = __esm({
10000
10823
  init_dockerInstall();
10001
10824
  init_macKeychain();
10002
10825
  init_telemetry();
10003
- SYNKRO_DIR12 = join20(homedir22(), ".synkro");
10826
+ SYNKRO_DIR12 = join21(homedir23(), ".synkro");
10004
10827
  }
10005
10828
  });
10006
10829
 
10007
10830
  // cli/local-cc/turnLog.ts
10008
- import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as readFileSync22, readSync, closeSync as closeSync3, statSync as statSync3, watchFile, unwatchFile } from "fs";
10009
- import { dirname as dirname7, join as join21 } from "path";
10010
- import { homedir as homedir23 } from "os";
10831
+ import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as readFileSync23, readSync, closeSync as closeSync3, statSync as statSync3, watchFile, unwatchFile } from "fs";
10832
+ import { dirname as dirname7, join as join22 } from "path";
10833
+ import { homedir as homedir24 } from "os";
10011
10834
  function truncate(s, max = PREVIEW_MAX) {
10012
10835
  if (s.length <= max) return s;
10013
10836
  return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
@@ -10047,7 +10870,7 @@ function readRecentTurns(n = 20) {
10047
10870
  try {
10048
10871
  const size = statSync3(TURN_LOG_PATH).size;
10049
10872
  if (size === 0) return [];
10050
- const text = readFileSync22(TURN_LOG_PATH, "utf-8");
10873
+ const text = readFileSync23(TURN_LOG_PATH, "utf-8");
10051
10874
  const lines = text.split("\n").filter(Boolean);
10052
10875
  const lastN = lines.slice(-n).reverse();
10053
10876
  return lastN.map((line) => {
@@ -10126,7 +10949,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
10126
10949
  var init_turnLog = __esm({
10127
10950
  "cli/local-cc/turnLog.ts"() {
10128
10951
  "use strict";
10129
- TURN_LOG_PATH = join21(homedir23(), ".synkro", "cc_sessions", "turns.log");
10952
+ TURN_LOG_PATH = join22(homedir24(), ".synkro", "cc_sessions", "turns.log");
10130
10953
  PREVIEW_MAX = 400;
10131
10954
  }
10132
10955
  });
@@ -10280,8 +11103,8 @@ __export(scanPr_exports, {
10280
11103
  scanPrCommand: () => scanPrCommand
10281
11104
  });
10282
11105
  import { execSync as execSync5, spawn as spawn5 } from "child_process";
10283
- import { readFileSync as readFileSync23, existsSync as existsSync26 } from "fs";
10284
- import { join as join22 } from "path";
11106
+ import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
11107
+ import { join as join23 } from "path";
10285
11108
  function parseMatchSpec(condition) {
10286
11109
  if (!condition.startsWith("match_spec:")) return null;
10287
11110
  try {
@@ -10760,10 +11583,10 @@ function shouldFail(findings, threshold) {
10760
11583
  return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
10761
11584
  }
10762
11585
  function readRepoDeps() {
10763
- const pkgPath = join22(process.cwd(), "package.json");
11586
+ const pkgPath = join23(process.cwd(), "package.json");
10764
11587
  if (!existsSync26(pkgPath)) return {};
10765
11588
  try {
10766
- const pkg = JSON.parse(readFileSync23(pkgPath, "utf-8"));
11589
+ const pkg = JSON.parse(readFileSync24(pkgPath, "utf-8"));
10767
11590
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
10768
11591
  } catch {
10769
11592
  return {};
@@ -11007,15 +11830,15 @@ var routeDecide_exports = {};
11007
11830
  __export(routeDecide_exports, {
11008
11831
  routeDecide: () => routeDecide
11009
11832
  });
11010
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "fs";
11011
- import { homedir as homedir24 } from "os";
11012
- import { join as join23 } from "path";
11833
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
11834
+ import { homedir as homedir25 } from "os";
11835
+ import { join as join24 } from "path";
11013
11836
  function safeSid(sid) {
11014
11837
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
11015
11838
  }
11016
11839
  function loadMcpJwt() {
11017
11840
  try {
11018
- return readFileSync24(join23(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
11841
+ return readFileSync25(join24(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
11019
11842
  } catch {
11020
11843
  return "";
11021
11844
  }
@@ -11025,7 +11848,7 @@ async function routeDecide(sessionId) {
11025
11848
  const sid = safeSid(sessionId);
11026
11849
  let prompt = "";
11027
11850
  try {
11028
- const rec = JSON.parse(readFileSync24(join23(SESSIONS_DIR2, sid + ".json"), "utf-8"));
11851
+ const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR2, sid + ".json"), "utf-8"));
11029
11852
  prompt = String(rec.last_prompt || "").trim();
11030
11853
  } catch {
11031
11854
  return;
@@ -11036,7 +11859,7 @@ async function routeDecide(sessionId) {
11036
11859
  const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT}/submit`, {
11037
11860
  method: "POST",
11038
11861
  headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt() },
11039
- body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, agent_kind: "claude_code", hedge: false }),
11862
+ body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
11040
11863
  // First call may spin up the route lane (a haiku worker boot), so allow headroom.
11041
11864
  signal: AbortSignal.timeout(12e4)
11042
11865
  });
@@ -11048,19 +11871,19 @@ async function routeDecide(sessionId) {
11048
11871
  return;
11049
11872
  }
11050
11873
  if (!model || !VALID_MODELS.has(model)) return;
11051
- const lastFile = join23(PTY_DIR, "route-last-" + sid);
11874
+ const lastFile = join24(PTY_DIR, "route-last-" + sid);
11052
11875
  let last = "";
11053
11876
  try {
11054
- last = readFileSync24(lastFile, "utf-8").trim();
11877
+ last = readFileSync25(lastFile, "utf-8").trim();
11055
11878
  } catch {
11056
11879
  }
11057
11880
  if (model === last) return;
11058
11881
  try {
11059
- writeFileSync18(lastFile, model);
11882
+ writeFileSync19(lastFile, model);
11060
11883
  } catch {
11061
11884
  }
11062
11885
  try {
11063
- writeFileSync18(join23(PTY_DIR, "route-" + sid), model);
11886
+ writeFileSync19(join24(PTY_DIR, "route-" + sid), model);
11064
11887
  } catch {
11065
11888
  }
11066
11889
  }
@@ -11068,9 +11891,9 @@ var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
11068
11891
  var init_routeDecide = __esm({
11069
11892
  "cli/local-cc/routeDecide.ts"() {
11070
11893
  "use strict";
11071
- SYNKRO_DIR13 = join23(homedir24(), ".synkro");
11072
- PTY_DIR = join23(SYNKRO_DIR13, "pty");
11073
- SESSIONS_DIR2 = join23(PTY_DIR, "sessions");
11894
+ SYNKRO_DIR13 = join24(homedir25(), ".synkro");
11895
+ PTY_DIR = join24(SYNKRO_DIR13, "pty");
11896
+ SESSIONS_DIR2 = join24(PTY_DIR, "sessions");
11074
11897
  VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
11075
11898
  GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
11076
11899
  }
@@ -11081,9 +11904,9 @@ var routeOrchestrate_exports = {};
11081
11904
  __export(routeOrchestrate_exports, {
11082
11905
  routeAndResubmit: () => routeAndResubmit
11083
11906
  });
11084
- import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
11085
- import { homedir as homedir25 } from "os";
11086
- import { join as join24 } from "path";
11907
+ import { readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
11908
+ import { homedir as homedir26 } from "os";
11909
+ import { join as join25 } from "path";
11087
11910
  import { spawnSync as spawnSync9 } from "child_process";
11088
11911
  function safeSid2(sid) {
11089
11912
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
@@ -11093,7 +11916,7 @@ function safeSession(s) {
11093
11916
  }
11094
11917
  function loadMcpJwt2() {
11095
11918
  try {
11096
- return readFileSync25(join24(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
11919
+ return readFileSync26(join25(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
11097
11920
  } catch {
11098
11921
  return "";
11099
11922
  }
@@ -11103,7 +11926,7 @@ async function classifyTask(prompt) {
11103
11926
  const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT2}/submit`, {
11104
11927
  method: "POST",
11105
11928
  headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt2() },
11106
- body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, agent_kind: "claude_code", hedge: false }),
11929
+ body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
11107
11930
  signal: AbortSignal.timeout(6e4)
11108
11931
  });
11109
11932
  if (!resp.ok) return null;
@@ -11119,7 +11942,7 @@ async function classifyTask(prompt) {
11119
11942
  }
11120
11943
  function lastRoutedModel(sid) {
11121
11944
  try {
11122
- const v = readFileSync25(join24(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
11945
+ const v = readFileSync26(join25(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
11123
11946
  return VALID_MODELS2.has(v) ? v : "";
11124
11947
  } catch {
11125
11948
  return "";
@@ -11129,12 +11952,12 @@ function resolveSession(sid, tmuxSession) {
11129
11952
  const candidates = [];
11130
11953
  if (tmuxSession) candidates.push(tmuxSession);
11131
11954
  try {
11132
- const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
11955
+ const rec = JSON.parse(readFileSync26(join25(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
11133
11956
  if (rec.tmux_session) candidates.push(rec.tmux_session);
11134
11957
  } catch {
11135
11958
  }
11136
11959
  try {
11137
- candidates.push(readFileSync25(ACTIVE_SESSION_FILE2, "utf-8").trim());
11960
+ candidates.push(readFileSync26(ACTIVE_SESSION_FILE2, "utf-8").trim());
11138
11961
  } catch {
11139
11962
  }
11140
11963
  for (const c of candidates) if (c && safeSession(c)) return c;
@@ -11169,12 +11992,12 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
11169
11992
  }
11170
11993
  await wait(500);
11171
11994
  try {
11172
- writeFileSync19(join24(PTY_DIR2, "route-last-" + sid), picked);
11995
+ writeFileSync20(join25(PTY_DIR2, "route-last-" + sid), picked);
11173
11996
  } catch {
11174
11997
  }
11175
11998
  }
11176
11999
  try {
11177
- writeFileSync19(join24(PTY_DIR2, "route-guard-" + sid), "1");
12000
+ writeFileSync20(join25(PTY_DIR2, "route-guard-" + sid), "1");
11178
12001
  } catch {
11179
12002
  }
11180
12003
  sk("C-u");
@@ -11188,10 +12011,10 @@ var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2,
11188
12011
  var init_routeOrchestrate = __esm({
11189
12012
  "cli/local-cc/routeOrchestrate.ts"() {
11190
12013
  "use strict";
11191
- SYNKRO_DIR14 = join24(homedir25(), ".synkro");
11192
- PTY_DIR2 = join24(SYNKRO_DIR14, "pty");
11193
- SESSIONS_DIR3 = join24(PTY_DIR2, "sessions");
11194
- ACTIVE_SESSION_FILE2 = join24(PTY_DIR2, "active");
12014
+ SYNKRO_DIR14 = join25(homedir26(), ".synkro");
12015
+ PTY_DIR2 = join25(SYNKRO_DIR14, "pty");
12016
+ SESSIONS_DIR3 = join25(PTY_DIR2, "sessions");
12017
+ ACTIVE_SESSION_FILE2 = join25(PTY_DIR2, "active");
11195
12018
  VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
11196
12019
  GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
11197
12020
  wait = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -11203,14 +12026,14 @@ var routingToggle_exports = {};
11203
12026
  __export(routingToggle_exports, {
11204
12027
  routingCommand: () => routingCommand
11205
12028
  });
11206
- import { writeFileSync as writeFileSync20, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
11207
- import { homedir as homedir26 } from "os";
11208
- import { join as join25 } from "path";
12029
+ import { writeFileSync as writeFileSync21, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
12030
+ import { homedir as homedir27 } from "os";
12031
+ import { join as join26 } from "path";
11209
12032
  function safeSid3(sid) {
11210
12033
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
11211
12034
  }
11212
12035
  function markerFor(session) {
11213
- return session ? join25(PTY_DIR3, "routing-on-" + safeSid3(session)) : join25(PTY_DIR3, "routing-on");
12036
+ return session ? join26(PTY_DIR3, "routing-on-" + safeSid3(session)) : join26(PTY_DIR3, "routing-on");
11214
12037
  }
11215
12038
  function routingCommand(args2) {
11216
12039
  const sub = (args2[0] || "status").trim();
@@ -11219,7 +12042,7 @@ function routingCommand(args2) {
11219
12042
  if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
11220
12043
  if (sub === "on") {
11221
12044
  try {
11222
- writeFileSync20(markerFor(session), "1");
12045
+ writeFileSync21(markerFor(session), "1");
11223
12046
  } catch (e) {
11224
12047
  console.error("routing on failed:", String(e));
11225
12048
  return;
@@ -11242,7 +12065,7 @@ function routingCommand(args2) {
11242
12065
  for (const f of safeList()) {
11243
12066
  if (f === "routing-on" || f.startsWith("routing-on-")) {
11244
12067
  try {
11245
- unlinkSync9(join25(PTY_DIR3, f));
12068
+ unlinkSync9(join26(PTY_DIR3, f));
11246
12069
  removed++;
11247
12070
  } catch {
11248
12071
  }
@@ -11274,14 +12097,14 @@ var PTY_DIR3;
11274
12097
  var init_routingToggle = __esm({
11275
12098
  "cli/local-cc/routingToggle.ts"() {
11276
12099
  "use strict";
11277
- PTY_DIR3 = join25(homedir26(), ".synkro", "pty");
12100
+ PTY_DIR3 = join26(homedir27(), ".synkro", "pty");
11278
12101
  }
11279
12102
  });
11280
12103
 
11281
12104
  // cli/local-cc/pueue.ts
11282
12105
  import { execFileSync as execFileSync4, spawnSync as spawnSync10, spawn as spawn6 } from "child_process";
11283
- import { homedir as homedir27 } from "os";
11284
- import { join as join26 } from "path";
12106
+ import { homedir as homedir28 } from "os";
12107
+ import { join as join27 } from "path";
11285
12108
  import { connect as connect2 } from "net";
11286
12109
  function pueueAvailable() {
11287
12110
  const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
@@ -11347,7 +12170,7 @@ function startTask(opts = {}) {
11347
12170
  spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
11348
12171
  existing = findTask(ch);
11349
12172
  }
11350
- const runScript = join26(cwd, "run-claude.sh");
12173
+ const runScript = join27(cwd, "run-claude.sh");
11351
12174
  const args2 = [
11352
12175
  "add",
11353
12176
  "--label",
@@ -11477,12 +12300,12 @@ var init_pueue = __esm({
11477
12300
  "use strict";
11478
12301
  TASK_LABEL = "synkro-local-cc";
11479
12302
  TMUX_SESSION = "synkro-local-cc";
11480
- SESSION_DIR2 = join26(homedir27(), ".synkro", "cc_sessions");
12303
+ SESSION_DIR2 = join27(homedir28(), ".synkro", "cc_sessions");
11481
12304
  TASK_LABEL_2 = "synkro-local-cc-2";
11482
12305
  TMUX_SESSION_2 = "synkro-local-cc-2";
11483
- SESSION_DIR_22 = join26(homedir27(), ".synkro", "cc_sessions_2");
11484
- SESSION_DIR_32 = join26(homedir27(), ".synkro", "cc_sessions_3");
11485
- SESSION_DIR_42 = join26(homedir27(), ".synkro", "cc_sessions_4");
12306
+ SESSION_DIR_22 = join27(homedir28(), ".synkro", "cc_sessions_2");
12307
+ SESSION_DIR_32 = join27(homedir28(), ".synkro", "cc_sessions_3");
12308
+ SESSION_DIR_42 = join27(homedir28(), ".synkro", "cc_sessions_4");
11486
12309
  PueueError = class extends Error {
11487
12310
  constructor(message, cause) {
11488
12311
  super(message);
@@ -11497,13 +12320,13 @@ var init_pueue = __esm({
11497
12320
  });
11498
12321
 
11499
12322
  // cli/local-cc/settings.ts
11500
- import { existsSync as existsSync28, readFileSync as readFileSync26 } from "fs";
11501
- import { homedir as homedir28 } from "os";
11502
- import { join as join27 } from "path";
12323
+ import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
12324
+ import { homedir as homedir29 } from "os";
12325
+ import { join as join28 } from "path";
11503
12326
  function isLocalCCEnabled() {
11504
12327
  if (!existsSync28(CONFIG_PATH5)) return false;
11505
12328
  try {
11506
- const content = readFileSync26(CONFIG_PATH5, "utf-8");
12329
+ const content = readFileSync27(CONFIG_PATH5, "utf-8");
11507
12330
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
11508
12331
  return match?.[1] === "yes";
11509
12332
  } catch {
@@ -11514,7 +12337,7 @@ var CONFIG_PATH5;
11514
12337
  var init_settings = __esm({
11515
12338
  "cli/local-cc/settings.ts"() {
11516
12339
  "use strict";
11517
- CONFIG_PATH5 = join27(homedir28(), ".synkro", "config.env");
12340
+ CONFIG_PATH5 = join28(homedir29(), ".synkro", "config.env");
11518
12341
  }
11519
12342
  });
11520
12343
 
@@ -11524,10 +12347,10 @@ __export(localCc_exports, {
11524
12347
  localCcCommand: () => localCcCommand
11525
12348
  });
11526
12349
  import { spawnSync as spawnSync11 } from "child_process";
11527
- import { homedir as homedir29 } from "os";
11528
- import { join as join28 } from "path";
12350
+ import { homedir as homedir30 } from "os";
12351
+ import { join as join29 } from "path";
11529
12352
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
11530
- import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
12353
+ import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
11531
12354
  function deploymentMode() {
11532
12355
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
11533
12356
  if (env === "docker") return "docker";
@@ -11634,14 +12457,14 @@ TROUBLESHOOTING
11634
12457
  }
11635
12458
  function readGatewayUrl() {
11636
12459
  if (existsSync29(CONFIG_PATH6)) {
11637
- const m = readFileSync27(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
12460
+ const m = readFileSync28(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
11638
12461
  if (m) return m[1];
11639
12462
  }
11640
12463
  return "https://api.synkro.sh";
11641
12464
  }
11642
12465
  function updateLocalInferenceFlag(enabled) {
11643
12466
  if (!existsSync29(CONFIG_PATH6)) return;
11644
- let content = readFileSync27(CONFIG_PATH6, "utf-8");
12467
+ let content = readFileSync28(CONFIG_PATH6, "utf-8");
11645
12468
  const flag = enabled ? "yes" : "no";
11646
12469
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
11647
12470
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -11650,7 +12473,7 @@ function updateLocalInferenceFlag(enabled) {
11650
12473
  SYNKRO_LOCAL_INFERENCE='${flag}'
11651
12474
  `;
11652
12475
  }
11653
- writeFileSync21(CONFIG_PATH6, content, "utf-8");
12476
+ writeFileSync22(CONFIG_PATH6, content, "utf-8");
11654
12477
  }
11655
12478
  async function setServerGradingProvider(provider) {
11656
12479
  await ensureValidToken();
@@ -11679,7 +12502,7 @@ async function cmdStatus() {
11679
12502
  } else {
11680
12503
  console.log(`synkro-server container: running (${status.image})`);
11681
12504
  try {
11682
- const r = await fetch(`${status.healthz}healthz`, { signal: AbortSignal.timeout(3e3) });
12505
+ const r = await fetch(status.healthz, { signal: AbortSignal.timeout(3e3) });
11683
12506
  console.log(`Health probe: ${r.ok ? "ok" : `HTTP ${r.status}`}`);
11684
12507
  } catch (err) {
11685
12508
  console.log(`Health probe: ${err.message}`);
@@ -12084,8 +12907,8 @@ var init_localCc = __esm({
12084
12907
  init_install();
12085
12908
  init_client2();
12086
12909
  init_stub();
12087
- SYNKRO_CONFIG_PATH = join28(homedir29(), ".synkro", "config.env");
12088
- CONFIG_PATH6 = join28(homedir29(), ".synkro", "config.env");
12910
+ SYNKRO_CONFIG_PATH = join29(homedir30(), ".synkro", "config.env");
12911
+ CONFIG_PATH6 = join29(homedir30(), ".synkro", "config.env");
12089
12912
  }
12090
12913
  });
12091
12914
 
@@ -12094,14 +12917,14 @@ var import_exports = {};
12094
12917
  __export(import_exports, {
12095
12918
  importCommand: () => importCommand
12096
12919
  });
12097
- import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync7 } from "fs";
12098
- import { homedir as homedir30 } from "os";
12099
- import { join as join29 } from "path";
12920
+ import { existsSync as existsSync30, readFileSync as readFileSync29, readdirSync as readdirSync7 } from "fs";
12921
+ import { homedir as homedir31 } from "os";
12922
+ import { join as join30 } from "path";
12100
12923
  import { execSync as execSync6 } from "child_process";
12101
12924
  import { createInterface as createInterface4 } from "readline";
12102
12925
  function readMcpJwt() {
12103
12926
  try {
12104
- return readFileSync28(join29(homedir30(), ".synkro", ".mcp-jwt"), "utf-8").trim();
12927
+ return readFileSync29(join30(homedir31(), ".synkro", ".mcp-jwt"), "utf-8").trim();
12105
12928
  } catch {
12106
12929
  return "";
12107
12930
  }
@@ -12109,7 +12932,7 @@ function readMcpJwt() {
12109
12932
  function readConfigEnv2() {
12110
12933
  const out = {};
12111
12934
  try {
12112
- for (const line of readFileSync28(CONFIG_PATH7, "utf-8").split("\n")) {
12935
+ for (const line of readFileSync29(CONFIG_PATH7, "utf-8").split("\n")) {
12113
12936
  const t = line.trim();
12114
12937
  if (!t || t.startsWith("#")) continue;
12115
12938
  const eq = t.indexOf("=");
@@ -12121,7 +12944,7 @@ function readConfigEnv2() {
12121
12944
  }
12122
12945
  function projectsFolder() {
12123
12946
  const sanitized = process.cwd().replace(/\//g, "-");
12124
- const dir = join29(homedir30(), ".claude", "projects", sanitized);
12947
+ const dir = join30(homedir31(), ".claude", "projects", sanitized);
12125
12948
  return existsSync30(dir) ? dir : null;
12126
12949
  }
12127
12950
  function repoName() {
@@ -12161,7 +12984,7 @@ function extractToolResultText(content, e) {
12161
12984
  return t;
12162
12985
  }
12163
12986
  function parseSession(filePath, sessionId) {
12164
- const lines = readFileSync28(filePath, "utf-8").split("\n").filter(Boolean);
12987
+ const lines = readFileSync29(filePath, "utf-8").split("\n").filter(Boolean);
12165
12988
  const messages = [];
12166
12989
  const actions = [];
12167
12990
  let step = 0;
@@ -12241,7 +13064,7 @@ async function importCommand() {
12241
13064
  return;
12242
13065
  }
12243
13066
  }
12244
- const sessions = files.map((f) => parseSession(join29(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
13067
+ const sessions = files.map((f) => parseSession(join30(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
12245
13068
  const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
12246
13069
  let ok = 0, fail = 0;
12247
13070
  if (isCloud) {
@@ -12316,7 +13139,7 @@ var init_import = __esm({
12316
13139
  "cli/commands/import.ts"() {
12317
13140
  "use strict";
12318
13141
  init_stub();
12319
- CONFIG_PATH7 = join29(homedir30(), ".synkro", "config.env");
13142
+ CONFIG_PATH7 = join30(homedir31(), ".synkro", "config.env");
12320
13143
  }
12321
13144
  });
12322
13145
 
@@ -12358,10 +13181,10 @@ var init_packVerify = __esm({
12358
13181
  });
12359
13182
 
12360
13183
  // cli/installer/lockfile.ts
12361
- import { existsSync as existsSync31, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "fs";
12362
- import { join as join30 } from "path";
13184
+ import { existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "fs";
13185
+ import { join as join31 } from "path";
12363
13186
  function lockPath(repoRoot2) {
12364
- return join30(repoRoot2, LOCK_FILE);
13187
+ return join31(repoRoot2, LOCK_FILE);
12365
13188
  }
12366
13189
  function writeLockfile(repoRoot2, entries) {
12367
13190
  const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
@@ -12379,7 +13202,7 @@ function writeLockfile(repoRoot2, entries) {
12379
13202
  ""
12380
13203
  ])
12381
13204
  ].join("\n");
12382
- writeFileSync22(lockPath(repoRoot2), body, "utf-8");
13205
+ writeFileSync23(lockPath(repoRoot2), body, "utf-8");
12383
13206
  }
12384
13207
  var LOCK_FILE;
12385
13208
  var init_lockfile = __esm({
@@ -12394,9 +13217,9 @@ var sync_exports = {};
12394
13217
  __export(sync_exports, {
12395
13218
  syncCommand: () => syncCommand
12396
13219
  });
12397
- import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync23 } from "fs";
12398
- import { homedir as homedir31 } from "os";
12399
- import { join as join31 } from "path";
13220
+ import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync24 } from "fs";
13221
+ import { homedir as homedir32 } from "os";
13222
+ import { join as join32 } from "path";
12400
13223
  function cacheKey(ref, version) {
12401
13224
  return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
12402
13225
  }
@@ -12427,7 +13250,7 @@ async function syncCommand(_args = []) {
12427
13250
  }
12428
13251
  const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
12429
13252
  const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
12430
- const cacheDir = join31(homedir31(), ".synkro", "cache", "packs");
13253
+ const cacheDir = join32(homedir32(), ".synkro", "cache", "packs");
12431
13254
  if (!cloud) mkdirSync18(cacheDir, { recursive: true });
12432
13255
  console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
12433
13256
  const lock = [];
@@ -12456,7 +13279,7 @@ async function syncCommand(_args = []) {
12456
13279
  if (!cloud) {
12457
13280
  const fname = cacheKey(ref, data.version);
12458
13281
  keptCacheFiles.add(fname);
12459
- writeFileSync23(join31(cacheDir, fname), JSON.stringify({
13282
+ writeFileSync24(join32(cacheDir, fname), JSON.stringify({
12460
13283
  ref,
12461
13284
  version: data.version,
12462
13285
  digest: data.digest,
@@ -12472,7 +13295,7 @@ async function syncCommand(_args = []) {
12472
13295
  for (const f of readdirSync8(cacheDir)) {
12473
13296
  if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
12474
13297
  try {
12475
- rmSync4(join31(cacheDir, f));
13298
+ rmSync4(join32(cacheDir, f));
12476
13299
  } catch {
12477
13300
  }
12478
13301
  }
@@ -12501,13 +13324,13 @@ var whoami_exports = {};
12501
13324
  __export(whoami_exports, {
12502
13325
  whoamiCommand: () => whoamiCommand
12503
13326
  });
12504
- import { readFileSync as readFileSync30, existsSync as existsSync33 } from "fs";
12505
- import { join as join32 } from "path";
12506
- import { homedir as homedir32 } from "os";
13327
+ import { readFileSync as readFileSync31, existsSync as existsSync33 } from "fs";
13328
+ import { join as join33 } from "path";
13329
+ import { homedir as homedir33 } from "os";
12507
13330
  function readConfigEnv3() {
12508
13331
  if (!existsSync33(CONFIG_PATH8)) return {};
12509
13332
  const out = {};
12510
- for (const line of readFileSync30(CONFIG_PATH8, "utf-8").split("\n")) {
13333
+ for (const line of readFileSync31(CONFIG_PATH8, "utf-8").split("\n")) {
12511
13334
  const t = line.trim();
12512
13335
  if (!t || t.startsWith("#")) continue;
12513
13336
  const eq = t.indexOf("=");
@@ -12518,7 +13341,7 @@ function readConfigEnv3() {
12518
13341
  function jwtStatus() {
12519
13342
  try {
12520
13343
  if (!existsSync33(JWT_PATH2)) return { status: "none" };
12521
- const jwt2 = readFileSync30(JWT_PATH2, "utf-8").trim();
13344
+ const jwt2 = readFileSync31(JWT_PATH2, "utf-8").trim();
12522
13345
  if (!jwt2) return { status: "none" };
12523
13346
  const payload = jwt2.split(".")[1];
12524
13347
  if (!payload) return { status: "valid" };
@@ -12580,9 +13403,9 @@ var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
12580
13403
  var init_whoami = __esm({
12581
13404
  "cli/commands/whoami.ts"() {
12582
13405
  "use strict";
12583
- SYNKRO_DIR15 = join32(homedir32(), ".synkro");
12584
- CONFIG_PATH8 = join32(SYNKRO_DIR15, "config.env");
12585
- JWT_PATH2 = join32(SYNKRO_DIR15, ".mcp-jwt");
13406
+ SYNKRO_DIR15 = join33(homedir33(), ".synkro");
13407
+ CONFIG_PATH8 = join33(SYNKRO_DIR15, "config.env");
13408
+ JWT_PATH2 = join33(SYNKRO_DIR15, ".mcp-jwt");
12586
13409
  GRADING_LABEL = {
12587
13410
  local: "on-device worker pool",
12588
13411
  cloud: "Synkro Cloud worker pool",
@@ -12627,12 +13450,12 @@ __export(linear_exports, {
12627
13450
  formatLinks: () => formatLinks,
12628
13451
  linearCommand: () => linearCommand
12629
13452
  });
12630
- import { readFileSync as readFileSync31 } from "fs";
12631
- import { homedir as homedir33 } from "os";
12632
- import { join as join33 } from "path";
13453
+ import { readFileSync as readFileSync32 } from "fs";
13454
+ import { homedir as homedir34 } from "os";
13455
+ import { join as join34 } from "path";
12633
13456
  function mcpJwt() {
12634
13457
  try {
12635
- return readFileSync31(join33(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
13458
+ return readFileSync32(join34(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
12636
13459
  } catch {
12637
13460
  return "";
12638
13461
  }
@@ -12671,7 +13494,7 @@ var SYNKRO_DIR16, PORT2, BASE;
12671
13494
  var init_linear = __esm({
12672
13495
  "cli/commands/linear.ts"() {
12673
13496
  "use strict";
12674
- SYNKRO_DIR16 = join33(homedir33(), ".synkro");
13497
+ SYNKRO_DIR16 = join34(homedir34(), ".synkro");
12675
13498
  PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
12676
13499
  BASE = `http://127.0.0.1:${PORT2}`;
12677
13500
  }
@@ -12679,7 +13502,7 @@ var init_linear = __esm({
12679
13502
 
12680
13503
  // cli/scanning/cveReachability.ts
12681
13504
  import { parse } from "@babel/parser";
12682
- import { readFileSync as readFileSync32 } from "fs";
13505
+ import { readFileSync as readFileSync33 } from "fs";
12683
13506
  function walk(node, visit) {
12684
13507
  if (!node || typeof node.type !== "string") return;
12685
13508
  visit(node);
@@ -12821,9 +13644,9 @@ var init_cveReachability = __esm({
12821
13644
 
12822
13645
  // cli/reachability/reachabilityScan.ts
12823
13646
  import { spawnSync as spawnSync12, execFileSync as execFileSync5 } from "child_process";
12824
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync24, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
12825
- import { join as join34 } from "path";
12826
- import { homedir as homedir34 } from "os";
13647
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync25, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
13648
+ import { join as join35 } from "path";
13649
+ import { homedir as homedir35 } from "os";
12827
13650
  import { createRequire } from "module";
12828
13651
  function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12829
13652
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
@@ -12840,7 +13663,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12840
13663
  }
12841
13664
  for (const e of ents) {
12842
13665
  if (files.length >= maxFiles) break;
12843
- const full = join34(dir, e.name);
13666
+ const full = join35(dir, e.name);
12844
13667
  if (e.isDirectory()) {
12845
13668
  if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
12846
13669
  continue;
@@ -12848,7 +13671,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12848
13671
  if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
12849
13672
  const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
12850
13673
  try {
12851
- const content = readFileSync33(full, "utf8");
13674
+ const content = readFileSync34(full, "utf8");
12852
13675
  if (content.length <= maxBytes) files.push({ path: rel, content });
12853
13676
  } catch {
12854
13677
  }
@@ -12867,12 +13690,12 @@ function cleanVersion(spec) {
12867
13690
  function gatherManifestVersions(repoRoot2) {
12868
13691
  const out = {};
12869
13692
  const dirs = [repoRoot2];
12870
- const pkgsDir = join34(repoRoot2, "packages");
13693
+ const pkgsDir = join35(repoRoot2, "packages");
12871
13694
  if (existsSync34(pkgsDir)) {
12872
13695
  try {
12873
13696
  for (const d of readdirSync9(pkgsDir)) {
12874
- const pd = join34(pkgsDir, d);
12875
- if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
13697
+ const pd = join35(pkgsDir, d);
13698
+ if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
12876
13699
  }
12877
13700
  } catch {
12878
13701
  }
@@ -12881,7 +13704,7 @@ function gatherManifestVersions(repoRoot2) {
12881
13704
  for (const dir of dirs) {
12882
13705
  let pkg;
12883
13706
  try {
12884
- pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
13707
+ pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
12885
13708
  } catch {
12886
13709
  continue;
12887
13710
  }
@@ -12901,28 +13724,28 @@ function findJelly(repoRoot2) {
12901
13724
  try {
12902
13725
  const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
12903
13726
  const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
12904
- const pkg = JSON.parse(readFileSync33(pkgJson, "utf8"));
13727
+ const pkg = JSON.parse(readFileSync34(pkgJson, "utf8"));
12905
13728
  const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
12906
13729
  if (bin) {
12907
- const p = join34(dir, bin);
13730
+ const p = join35(dir, bin);
12908
13731
  if (existsSync34(p)) return p;
12909
13732
  }
12910
13733
  } catch {
12911
13734
  }
12912
13735
  for (const base of [repoRoot2, process.cwd()]) {
12913
- const b = join34(base, "node_modules", ".bin", "jelly");
13736
+ const b = join35(base, "node_modules", ".bin", "jelly");
12914
13737
  if (existsSync34(b)) return b;
12915
13738
  }
12916
13739
  return null;
12917
13740
  }
12918
13741
  function findEntries(repoRoot2) {
12919
13742
  const dirs = [repoRoot2];
12920
- const pkgsDir = join34(repoRoot2, "packages");
13743
+ const pkgsDir = join35(repoRoot2, "packages");
12921
13744
  if (existsSync34(pkgsDir)) {
12922
13745
  try {
12923
13746
  for (const d of readdirSync9(pkgsDir)) {
12924
- const pd = join34(pkgsDir, d);
12925
- if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
13747
+ const pd = join35(pkgsDir, d);
13748
+ if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
12926
13749
  }
12927
13750
  } catch {
12928
13751
  }
@@ -12930,11 +13753,11 @@ function findEntries(repoRoot2) {
12930
13753
  const entries = [];
12931
13754
  for (const dir of dirs) {
12932
13755
  try {
12933
- const pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
13756
+ const pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
12934
13757
  const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
12935
13758
  for (const c of cands) {
12936
13759
  if (typeof c !== "string") continue;
12937
- const f = join34(dir, c);
13760
+ const f = join35(dir, c);
12938
13761
  if (existsSync34(f)) {
12939
13762
  entries.push(f);
12940
13763
  break;
@@ -12970,7 +13793,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
12970
13793
  const commit = currentCommit(repoRoot2);
12971
13794
  if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
12972
13795
  try {
12973
- const prev = JSON.parse(readFileSync33(REACHABILITY_PATH, "utf8"));
13796
+ const prev = JSON.parse(readFileSync34(REACHABILITY_PATH, "utf8"));
12974
13797
  if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
12975
13798
  } catch {
12976
13799
  }
@@ -13059,7 +13882,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
13059
13882
  if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
13060
13883
  const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
13061
13884
  try {
13062
- writeFileSync24(REACHABILITY_PATH, JSON.stringify(file, null, 2));
13885
+ writeFileSync25(REACHABILITY_PATH, JSON.stringify(file, null, 2));
13063
13886
  } catch (e) {
13064
13887
  return { ok: false, reason: "write failed: " + String(e.message || e) };
13065
13888
  }
@@ -13071,7 +13894,7 @@ var init_reachabilityScan = __esm({
13071
13894
  "use strict";
13072
13895
  init_cveReachability();
13073
13896
  require2 = createRequire(import.meta.url);
13074
- REACHABILITY_PATH = join34(homedir34(), ".synkro", "reachability.json");
13897
+ REACHABILITY_PATH = join35(homedir35(), ".synkro", "reachability.json");
13075
13898
  }
13076
13899
  });
13077
13900
 
@@ -13080,15 +13903,15 @@ var reachabilityScan_exports = {};
13080
13903
  __export(reachabilityScan_exports, {
13081
13904
  reachabilityScanCommand: () => reachabilityScanCommand
13082
13905
  });
13083
- import { readFileSync as readFileSync34, existsSync as existsSync35 } from "fs";
13084
- import { join as join35 } from "path";
13085
- import { homedir as homedir35 } from "os";
13906
+ import { readFileSync as readFileSync35, existsSync as existsSync35 } from "fs";
13907
+ import { join as join36 } from "path";
13908
+ import { homedir as homedir36 } from "os";
13086
13909
  import { execFileSync as execFileSync6 } from "child_process";
13087
13910
  function readConfigEnv4() {
13088
- const p = join35(SYNKRO_DIR17, "config.env");
13911
+ const p = join36(SYNKRO_DIR17, "config.env");
13089
13912
  if (!existsSync35(p)) return {};
13090
13913
  const out = {};
13091
- for (const line of readFileSync34(p, "utf-8").split("\n")) {
13914
+ for (const line of readFileSync35(p, "utf-8").split("\n")) {
13092
13915
  const t = line.trim();
13093
13916
  if (!t || t.startsWith("#")) continue;
13094
13917
  const eq = t.indexOf("=");
@@ -13120,11 +13943,11 @@ async function pushToCloud(cfg, repo) {
13120
13943
  while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
13121
13944
  let jwt2 = "";
13122
13945
  try {
13123
- jwt2 = readFileSync34(join35(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
13946
+ jwt2 = readFileSync35(join36(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
13124
13947
  } catch {
13125
13948
  }
13126
13949
  if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
13127
- const body = readFileSync34(REACHABILITY_PATH, "utf-8");
13950
+ const body = readFileSync35(REACHABILITY_PATH, "utf-8");
13128
13951
  try {
13129
13952
  const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
13130
13953
  method: "POST",
@@ -13156,7 +13979,7 @@ var init_reachabilityScan2 = __esm({
13156
13979
  "cli/commands/reachabilityScan.ts"() {
13157
13980
  "use strict";
13158
13981
  init_reachabilityScan();
13159
- SYNKRO_DIR17 = join35(homedir35(), ".synkro");
13982
+ SYNKRO_DIR17 = join36(homedir36(), ".synkro");
13160
13983
  }
13161
13984
  });
13162
13985
 
@@ -13188,9 +14011,9 @@ async function startCommand(rest = []) {
13188
14011
  console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor + ${cfg.codexWorkers} codex)
13189
14012
  `);
13190
14013
  await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, conductorProvider: cfg.conductorProvider, connectedRepo: resolveConnectedRepo() });
13191
- const ready = await waitForContainerReady(6e4);
14014
+ const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
13192
14015
  if (!ready) {
13193
- console.error("\n\u26A0 container did not pass /healthz within 60s");
14016
+ console.error("\n\u26A0 container did not pass /healthz within 10m");
13194
14017
  process.exit(1);
13195
14018
  }
13196
14019
  console.log("\nServer is running.");
@@ -13220,9 +14043,9 @@ async function updateCommand() {
13220
14043
  console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex worker(s)
13221
14044
  `);
13222
14045
  await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
13223
- const ready = await waitForContainerReady(9e4);
14046
+ const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
13224
14047
  if (!ready) {
13225
- console.error("\n\u26A0 container did not pass its health check within 90s \u2014 check: docker logs synkro-server");
14048
+ console.error("\n\u26A0 container did not pass its health check within 10m \u2014 check: docker logs synkro-server");
13226
14049
  process.exit(1);
13227
14050
  }
13228
14051
  try {
@@ -13257,9 +14080,9 @@ async function restartCommand(rest = []) {
13257
14080
  console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)
13258
14081
  `);
13259
14082
  await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
13260
- const ready = await waitForContainerReady(6e4);
14083
+ const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
13261
14084
  if (!ready) {
13262
- console.error("\n\u26A0 container did not pass /healthz within 60s");
14085
+ console.error("\n\u26A0 container did not pass /healthz within 10m");
13263
14086
  process.exit(1);
13264
14087
  }
13265
14088
  console.log("\nServer restarted successfully.");
@@ -13271,11 +14094,13 @@ async function restartCommand(rest = []) {
13271
14094
  console.warn("\u26A0 workers did not register within 30s \u2014 skill sync skipped");
13272
14095
  }
13273
14096
  }
14097
+ var LOCAL_CONTAINER_READY_TIMEOUT_MS;
13274
14098
  var init_lifecycle = __esm({
13275
14099
  "cli/commands/lifecycle.ts"() {
13276
14100
  "use strict";
13277
14101
  init_dockerInstall();
13278
14102
  init_install();
14103
+ LOCAL_CONTAINER_READY_TIMEOUT_MS = 6e5;
13279
14104
  }
13280
14105
  });
13281
14106
 
@@ -13284,13 +14109,13 @@ var config_exports = {};
13284
14109
  __export(config_exports, {
13285
14110
  configCommand: () => configCommand
13286
14111
  });
13287
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync25, existsSync as existsSync36 } from "fs";
13288
- import { join as join36 } from "path";
13289
- import { homedir as homedir36 } from "os";
14112
+ import { readFileSync as readFileSync36, writeFileSync as writeFileSync26, existsSync as existsSync36 } from "fs";
14113
+ import { join as join37 } from "path";
14114
+ import { homedir as homedir37 } from "os";
13290
14115
  function readConfigEnv5() {
13291
14116
  if (!existsSync36(CONFIG_PATH9)) return {};
13292
14117
  const out = {};
13293
- for (const line of readFileSync35(CONFIG_PATH9, "utf-8").split("\n")) {
14118
+ for (const line of readFileSync36(CONFIG_PATH9, "utf-8").split("\n")) {
13294
14119
  const t = line.trim();
13295
14120
  if (!t || t.startsWith("#")) continue;
13296
14121
  const eq = t.indexOf("=");
@@ -13303,7 +14128,7 @@ function updateConfigValue(key, value) {
13303
14128
  console.error("No config found. Run `synkro install` first.");
13304
14129
  process.exit(1);
13305
14130
  }
13306
- const lines = readFileSync35(CONFIG_PATH9, "utf-8").split("\n");
14131
+ const lines = readFileSync36(CONFIG_PATH9, "utf-8").split("\n");
13307
14132
  const pattern = new RegExp(`^${key}=`);
13308
14133
  let found = false;
13309
14134
  const updated = lines.map((line) => {
@@ -13314,7 +14139,7 @@ function updateConfigValue(key, value) {
13314
14139
  return line;
13315
14140
  });
13316
14141
  if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
13317
- writeFileSync25(CONFIG_PATH9, updated.join("\n"), "utf-8");
14142
+ writeFileSync26(CONFIG_PATH9, updated.join("\n"), "utf-8");
13318
14143
  }
13319
14144
  function resolveInferenceMode(cfg) {
13320
14145
  if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
@@ -13472,8 +14297,8 @@ var init_config = __esm({
13472
14297
  "use strict";
13473
14298
  init_stub();
13474
14299
  init_optout();
13475
- SYNKRO_DIR18 = join36(homedir36(), ".synkro");
13476
- CONFIG_PATH9 = join36(SYNKRO_DIR18, "config.env");
14300
+ SYNKRO_DIR18 = join37(homedir37(), ".synkro");
14301
+ CONFIG_PATH9 = join37(SYNKRO_DIR18, "config.env");
13477
14302
  }
13478
14303
  });
13479
14304
 
@@ -13662,14 +14487,14 @@ Usage:
13662
14487
  });
13663
14488
 
13664
14489
  // cli/bootstrap.js
13665
- import { readFileSync as readFileSync36, existsSync as existsSync37 } from "fs";
14490
+ import { readFileSync as readFileSync37, existsSync as existsSync37 } from "fs";
13666
14491
  import { resolve as resolve5 } from "path";
13667
14492
  var envCandidates = [
13668
14493
  resolve5(process.env.HOME ?? "", ".synkro", "config.env")
13669
14494
  ];
13670
14495
  for (const envPath of envCandidates) {
13671
14496
  if (!existsSync37(envPath)) continue;
13672
- const envContent = readFileSync36(envPath, "utf-8");
14497
+ const envContent = readFileSync37(envPath, "utf-8");
13673
14498
  for (const line of envContent.split("\n")) {
13674
14499
  const trimmed = line.trim();
13675
14500
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -13686,7 +14511,7 @@ var subArgs = args.slice(1);
13686
14511
  var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
13687
14512
  var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
13688
14513
  function printVersion() {
13689
- console.log("1.7.88");
14514
+ console.log("1.7.89");
13690
14515
  }
13691
14516
  function printHelp2() {
13692
14517
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents