@synkro-sh/cli 1.7.86 → 1.7.88

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.86";
150
+ cliVersion = "1.7.88";
151
151
  } catch {
152
152
  }
153
153
  const creds = loadCredentialsIdentity();
@@ -2500,7 +2500,7 @@ function telemCreds(): TelemCreds {
2500
2500
  }
2501
2501
 
2502
2502
  interface HookEmitOpts {
2503
- agent_kind?: 'claude_code' | 'cursor';
2503
+ agent_kind?: 'claude_code' | 'cursor' | 'codex';
2504
2504
  cc_session_id?: string;
2505
2505
  cc_tool_use_id?: string;
2506
2506
  cc_model?: string;
@@ -5478,6 +5478,7 @@ __export(dockerInstall_exports, {
5478
5478
  normalizeProvider: () => normalizeProvider,
5479
5479
  poolLabel: () => poolLabel,
5480
5480
  readContainerConfig: () => readContainerConfig,
5481
+ resolveConductorProvider: () => resolveConductorProvider,
5481
5482
  resolveGraderPool: () => resolveGraderPool,
5482
5483
  resolveWorkerConfig: () => resolveWorkerConfig,
5483
5484
  splitWorkers: () => splitWorkers,
@@ -5488,21 +5489,35 @@ import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync12, rea
5488
5489
  import { homedir as homedir16 } from "os";
5489
5490
  import { join as join14 } from "path";
5490
5491
  import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
5492
+ function resolveConductorProvider(pool, counts) {
5493
+ if (pool !== "auto") return pool;
5494
+ const ranked = [
5495
+ ["claude_code", counts.claudeWorkers],
5496
+ ["cursor", counts.cursorWorkers],
5497
+ ["codex", counts.codexWorkers]
5498
+ ];
5499
+ return ranked.reduce((best, candidate) => candidate[1] > best[1] ? candidate : best, ranked[0])[0];
5500
+ }
5491
5501
  function splitWorkers(total, providers) {
5492
5502
  const t = Math.max(0, Math.floor(total));
5493
- const hasClaude = providers.includes("claude_code");
5494
- const hasCursor = providers.includes("cursor");
5495
- if (hasClaude && hasCursor) {
5496
- const cursorWorkers = Math.floor(t / 2);
5497
- return { claudeWorkers: t - cursorWorkers, cursorWorkers };
5498
- }
5499
- if (hasCursor) return { claudeWorkers: 0, cursorWorkers: t };
5500
- return { claudeWorkers: t, cursorWorkers: 0 };
5503
+ const selected = ["claude_code", "cursor", "codex"].filter((provider) => providers.includes(provider));
5504
+ const active = selected.length > 0 ? selected : ["claude_code"];
5505
+ const base = Math.floor(t / active.length);
5506
+ let remainder = t % active.length;
5507
+ const counts = { claudeWorkers: 0, cursorWorkers: 0, codexWorkers: 0 };
5508
+ for (const provider of active) {
5509
+ const count = base + (remainder-- > 0 ? 1 : 0);
5510
+ if (provider === "claude_code") counts.claudeWorkers = count;
5511
+ else if (provider === "cursor") counts.cursorWorkers = count;
5512
+ else counts.codexWorkers = count;
5513
+ }
5514
+ return counts;
5501
5515
  }
5502
5516
  function normalizeProvider(p) {
5503
5517
  const v = p.trim().toLowerCase();
5504
5518
  if (v === "claude" || v === "claude-code" || v === "claude_code" || v === "cc") return "claude_code";
5505
5519
  if (v === "cursor") return "cursor";
5520
+ if (v === "codex" || v === "openai-codex" || v === "openai_codex") return "codex";
5506
5521
  return null;
5507
5522
  }
5508
5523
  function resolveGraderPool(parsed) {
@@ -5516,8 +5531,9 @@ function resolveGraderPool(parsed) {
5516
5531
  const np = normalizeProvider(k);
5517
5532
  if (np === "claude_code") counts.claude_code = Math.max(0, Math.floor(v));
5518
5533
  else if (np === "cursor") counts.cursor = Math.max(0, Math.floor(v));
5534
+ else if (np === "codex") counts.codex = Math.max(0, Math.floor(v));
5519
5535
  else if (flagUnknown) {
5520
- warnings.push(`synkro.toml: ${where}.${k} is not a known provider (use claude-code or cursor) \u2014 ignored`);
5536
+ warnings.push(`synkro.toml: ${where}.${k} is not a known provider (use claude-code, cursor, or codex) \u2014 ignored`);
5521
5537
  }
5522
5538
  }
5523
5539
  };
@@ -5528,10 +5544,10 @@ function resolveGraderPool(parsed) {
5528
5544
  if (rawPool != null && rawPool !== "auto") {
5529
5545
  const np = normalizeProvider(String(rawPool));
5530
5546
  if (np) pool = np;
5531
- else warnings.push(`synkro.toml: grader.pool="${rawPool}" is not recognized (use auto | claude-code | cursor) \u2014 falling back to auto`);
5547
+ else warnings.push(`synkro.toml: grader.pool="${rawPool}" is not recognized (use auto | claude-code | cursor | codex) \u2014 falling back to auto`);
5532
5548
  }
5533
- if (counts.claude_code != null || counts.cursor != null) {
5534
- return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, pool, warnings };
5549
+ if (counts.claude_code != null || counts.cursor != null || counts.codex != null) {
5550
+ return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, codexWorkers: counts.codex, pool, warnings };
5535
5551
  }
5536
5552
  return { pool, warnings };
5537
5553
  }
@@ -5624,21 +5640,29 @@ function resolveWorkerConfig(rest) {
5624
5640
  if (provs.length === 0) {
5625
5641
  const sc = readSynkroFileConfig();
5626
5642
  for (const w of sc.warnings) console.warn(` \u26A0 ${w}`);
5627
- if (sc.claudeWorkers != null || sc.cursorWorkers != null) {
5643
+ if (sc.claudeWorkers != null || sc.cursorWorkers != null || sc.codexWorkers != null) {
5628
5644
  const cw = sc.claudeWorkers || 0;
5629
5645
  const curw = sc.cursorWorkers || 0;
5630
- if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw, explicit };
5646
+ const codw = sc.codexWorkers || 0;
5647
+ if (cw + curw + codw > 0) {
5648
+ const counts2 = { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
5649
+ return { ...counts2, conductorProvider: resolveConductorProvider(sc.pool, counts2), explicit };
5650
+ }
5631
5651
  }
5632
5652
  if (sc.pool === "cursor") {
5633
5653
  provs = ["cursor"];
5634
5654
  } else if (sc.pool === "claude_code") {
5635
5655
  provs = ["claude_code"];
5656
+ } else if (sc.pool === "codex") {
5657
+ provs = ["codex"];
5636
5658
  } else {
5637
5659
  provs = detectAgents().map((a) => a.kind);
5638
5660
  if (provs.length === 0) provs = ["claude_code"];
5639
5661
  }
5640
5662
  }
5641
- return { ...splitWorkers(workers, provs), explicit };
5663
+ const counts = splitWorkers(workers, provs);
5664
+ const pool = provs.length === 1 ? provs[0] : "auto";
5665
+ return { ...counts, conductorProvider: resolveConductorProvider(pool, counts), explicit };
5642
5666
  }
5643
5667
  function imageTag() {
5644
5668
  const registry = process.env.SYNKRO_IMAGE_REGISTRY || "";
@@ -5710,7 +5734,19 @@ async function dockerInstall(opts = {}) {
5710
5734
  const image = imageTag();
5711
5735
  const claudeWorkers = opts.claudeWorkers ?? opts.workersPerPool ?? 8;
5712
5736
  const cursorWorkers = opts.cursorWorkers ?? 0;
5713
- const totalWorkers = claudeWorkers + cursorWorkers;
5737
+ const codexWorkers = opts.codexWorkers ?? 0;
5738
+ const totalWorkers = claudeWorkers + cursorWorkers + codexWorkers;
5739
+ const workerCounts = { claudeWorkers, cursorWorkers, codexWorkers };
5740
+ const conductorProvider = opts.conductorProvider ?? resolveConductorProvider("auto", workerCounts);
5741
+ const usesClaude = claudeWorkers > 0 || conductorProvider === "claude_code";
5742
+ const usesCursor = cursorWorkers > 0 || conductorProvider === "cursor";
5743
+ 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"))) {
5746
+ throw new DockerInstallError(
5747
+ "Codex grader credentials are missing. Re-run `synkro install` to authorize an isolated Codex session."
5748
+ );
5749
+ }
5714
5750
  mkdirSync12(PGDATA_PATH, { recursive: true });
5715
5751
  mkdirSync12(BACKUP_DIR, { recursive: true });
5716
5752
  mkdirSync12(CLAUDE_HOST_STATE_DIR, { recursive: true });
@@ -5727,14 +5763,14 @@ async function dockerInstall(opts = {}) {
5727
5763
  if (needsKeychainBridge()) {
5728
5764
  const claudeCredsPath = exportKeychainCreds();
5729
5765
  if (!claudeCredsPath) {
5730
- if (claudeWorkers > 0) {
5766
+ if (usesClaude) {
5731
5767
  throw new DockerInstallError(
5732
5768
  "Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container."
5733
5769
  );
5734
5770
  }
5735
5771
  console.warn(" \u26A0 Claude Code keychain entry not found \u2014 live usage telemetry stays off until you sign in to Claude Code (grading is unaffected).");
5736
5772
  }
5737
- if (cursorWorkers > 0 && !cursorApiKeyConfigured()) {
5773
+ if (usesCursor && !cursorApiKeyConfigured()) {
5738
5774
  console.warn(" \u26A0 No Cursor API key found \u2014 Cursor grader workers will be idle.");
5739
5775
  console.warn(" Generate a key at cursor.com \u2192 Settings \u2192 API Keys, then:");
5740
5776
  console.warn(` echo 'YOUR_KEY' > ~/.synkro/cursor-creds/api-key && chmod 600 ~/.synkro/cursor-creds/api-key`);
@@ -5805,13 +5841,18 @@ async function dockerInstall(opts = {}) {
5805
5841
  `${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,
5806
5842
  // Cursor creds — mounted RW so the in-container refresher can rotate the
5807
5843
  // access token in place. Only mounted when the install includes Cursor.
5808
- ...cursorWorkers > 0 ? ["-v", `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`] : [],
5844
+ ...usesCursor ? ["-v", `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`] : [],
5845
+ ...usesCodex ? ["-v", `${codexHomeDir}:/home/synkro/.codex:rw`] : [],
5809
5846
  "-e",
5810
5847
  `WORKERS_PER_POOL=${totalWorkers}`,
5811
5848
  "-e",
5812
5849
  `CLAUDE_WORKERS=${claudeWorkers}`,
5813
5850
  "-e",
5814
5851
  `CURSOR_WORKERS=${cursorWorkers}`,
5852
+ "-e",
5853
+ `CODEX_WORKERS=${codexWorkers}`,
5854
+ "-e",
5855
+ `SYNKRO_CONDUCTOR_PROVIDER=${conductorProvider}`,
5815
5856
  // Pass through the batch-size lever if the operator set it. Defaults
5816
5857
  // inside the container to 5; clamped to [1, 20] by synkro-server.ts.
5817
5858
  ...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
@@ -5929,6 +5970,8 @@ function readContainerConfig() {
5929
5970
  return {
5930
5971
  claudeWorkers: num(get("CLAUDE_WORKERS")),
5931
5972
  cursorWorkers: num(get("CURSOR_WORKERS")),
5973
+ codexWorkers: num(get("CODEX_WORKERS")),
5974
+ conductorProvider: normalizeProvider(get("SYNKRO_CONDUCTOR_PROVIDER") || "") || void 0,
5932
5975
  connectedRepo: get("SYNKRO_CONNECTED_REPO") || void 0
5933
5976
  };
5934
5977
  }
@@ -6165,6 +6208,119 @@ var init_setupToken = __esm({
6165
6208
  }
6166
6209
  });
6167
6210
 
6211
+ // cli/local-cc/codexCloudSetup.ts
6212
+ 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";
6216
+ function findCodexBinary() {
6217
+ if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
6218
+ const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
6219
+ const p = (r.stdout || "").trim().split("\n")[0];
6220
+ return p || null;
6221
+ }
6222
+ function runCodexLogin(codexBin, codexHome) {
6223
+ mkdirSync13(codexHome, { recursive: true, mode: 448 });
6224
+ writeFileSync14(join16(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
6225
+ return new Promise((resolve6, reject) => {
6226
+ const proc = nodeSpawn2(codexBin, ["login"], {
6227
+ stdio: "inherit",
6228
+ env: { ...process.env, CODEX_HOME: codexHome }
6229
+ });
6230
+ proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
6231
+ proc.on("close", (code) => code === 0 ? resolve6() : reject(new Error(`codex login exited with code ${code}`)));
6232
+ });
6233
+ }
6234
+ async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
6235
+ try {
6236
+ const status = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/status`, {
6237
+ headers: { Authorization: `Bearer ${bearerToken}` }
6238
+ });
6239
+ const body = await status.json().catch(() => ({}));
6240
+ if (status.ok && body.connected) {
6241
+ onStatus?.("\u2713 Existing Codex cloud session is connected.");
6242
+ return { ok: true };
6243
+ }
6244
+ } catch {
6245
+ }
6246
+ const codexBin = findCodexBinary();
6247
+ if (!codexBin) {
6248
+ return { ok: false, error: "Codex CLI not found on PATH. Install Codex (https://developers.openai.com/codex), then re-run `synkro install`. (Set SYNKRO_CODEX_BIN to override the binary path.)" };
6249
+ }
6250
+ onStatus?.("Opening your browser to authorize your Codex subscription for Synkro Cloud\u2026");
6251
+ try {
6252
+ await runCodexLogin(codexBin, CODEX_CLOUD_HOME);
6253
+ } catch (e) {
6254
+ return { ok: false, error: `Codex login failed: ${e.message}` };
6255
+ }
6256
+ const authPath = join16(CODEX_CLOUD_HOME, "auth.json");
6257
+ if (!existsSync20(authPath)) {
6258
+ return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
6259
+ }
6260
+ let auth;
6261
+ try {
6262
+ auth = JSON.parse(readFileSync18(authPath, "utf-8"));
6263
+ } catch (e) {
6264
+ return { ok: false, error: `could not read codex auth.json: ${e.message}` };
6265
+ }
6266
+ if (!auth || typeof auth !== "object" || !auth.tokens?.refresh_token) {
6267
+ return { ok: false, error: "codex auth.json has no refresh token \u2014 cloud refresh would fail. Re-run login." };
6268
+ }
6269
+ onStatus?.("Connecting your Codex session to Synkro Cloud\u2026");
6270
+ let resp;
6271
+ try {
6272
+ resp = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/seed`, {
6273
+ method: "POST",
6274
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
6275
+ body: JSON.stringify({ auth })
6276
+ });
6277
+ } catch (e) {
6278
+ return { ok: false, error: `failed to reach Synkro API: ${e.message}` };
6279
+ }
6280
+ if (!resp.ok) {
6281
+ const detail = await resp.text().catch(() => "");
6282
+ return { ok: false, error: `seed rejected (${resp.status}): ${detail.slice(0, 200)}` };
6283
+ }
6284
+ try {
6285
+ rmSync(CODEX_CLOUD_HOME, { recursive: true, force: true });
6286
+ } catch {
6287
+ }
6288
+ onStatus?.("\u2713 Codex subscription connected to Synkro Cloud.");
6289
+ return { ok: true };
6290
+ }
6291
+ async function setupCodexLocal(onStatus) {
6292
+ const codexBin = findCodexBinary();
6293
+ if (!codexBin) {
6294
+ return { ok: false, error: "Codex CLI not found on PATH. Install Codex, then re-run `synkro install`." };
6295
+ }
6296
+ const authPath = join16(CODEX_LOCAL_HOME, "auth.json");
6297
+ if (!existsSync20(authPath)) {
6298
+ onStatus?.("Opening your browser to authorize an isolated Codex session for the local grader\u2026");
6299
+ try {
6300
+ await runCodexLogin(codexBin, CODEX_LOCAL_HOME);
6301
+ } catch (e) {
6302
+ return { ok: false, error: `Codex login failed: ${e.message}` };
6303
+ }
6304
+ }
6305
+ try {
6306
+ const auth = JSON.parse(readFileSync18(authPath, "utf-8"));
6307
+ if (!auth.tokens?.refresh_token) throw new Error("auth.json has no refresh token");
6308
+ } catch (e) {
6309
+ return { ok: false, error: `Codex local grader auth is invalid: ${e.message}` };
6310
+ }
6311
+ onStatus?.("\u2713 Codex session ready for the local grader.");
6312
+ return { ok: true, home: CODEX_LOCAL_HOME };
6313
+ }
6314
+ var SYNKRO_DIR9, CODEX_CLOUD_HOME, CODEX_LOCAL_HOME;
6315
+ var init_codexCloudSetup = __esm({
6316
+ "cli/local-cc/codexCloudSetup.ts"() {
6317
+ "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");
6321
+ }
6322
+ });
6323
+
6168
6324
  // cli/local-cc/ptyShim.ts
6169
6325
  var ptyShim_exports = {};
6170
6326
  __export(ptyShim_exports, {
@@ -6181,26 +6337,26 @@ __export(ptyShim_exports, {
6181
6337
  uninstallPtyShim: () => uninstallPtyShim
6182
6338
  });
6183
6339
  import {
6184
- existsSync as existsSync20,
6185
- mkdirSync as mkdirSync13,
6186
- writeFileSync as writeFileSync14,
6340
+ existsSync as existsSync21,
6341
+ mkdirSync as mkdirSync14,
6342
+ writeFileSync as writeFileSync15,
6187
6343
  chmodSync as chmodSync3,
6188
- readFileSync as readFileSync18,
6189
- rmSync,
6344
+ readFileSync as readFileSync19,
6345
+ rmSync as rmSync2,
6190
6346
  realpathSync,
6191
6347
  symlinkSync,
6192
6348
  lstatSync,
6193
6349
  readdirSync as readdirSync3
6194
6350
  } from "fs";
6195
- import { homedir as homedir18 } from "os";
6196
- import { join as join16 } from "path";
6197
- import { spawnSync as spawnSync4, spawn as spawn3 } from "child_process";
6351
+ import { homedir as homedir19 } from "os";
6352
+ import { join as join17 } from "path";
6353
+ import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
6198
6354
  function rcFiles() {
6199
- const h = homedir18();
6200
- return [join16(h, ".zshrc"), join16(h, ".bashrc"), join16(h, ".bash_profile")];
6355
+ const h = homedir19();
6356
+ return [join17(h, ".zshrc"), join17(h, ".bashrc"), join17(h, ".bash_profile")];
6201
6357
  }
6202
6358
  function resolveRealClaude() {
6203
- const r = spawnSync4("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6359
+ const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6204
6360
  const p = (r.stdout || "").trim();
6205
6361
  if (p) {
6206
6362
  try {
@@ -6209,8 +6365,8 @@ function resolveRealClaude() {
6209
6365
  return p;
6210
6366
  }
6211
6367
  }
6212
- for (const c of [join16(homedir18(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
6213
- if (existsSync20(c)) {
6368
+ for (const c of [join17(homedir19(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
6369
+ if (existsSync21(c)) {
6214
6370
  try {
6215
6371
  return realpathSync(c);
6216
6372
  } catch {
@@ -6221,11 +6377,11 @@ function resolveRealClaude() {
6221
6377
  return "claude";
6222
6378
  }
6223
6379
  function findClaudeLink() {
6224
- const r = spawnSync4("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6380
+ const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6225
6381
  let linkPath = (r.stdout || "").trim();
6226
6382
  if (!linkPath) {
6227
- const c = join16(homedir18(), ".local", "bin", "claude");
6228
- if (existsSync20(c)) linkPath = c;
6383
+ const c = join17(homedir19(), ".local", "bin", "claude");
6384
+ if (existsSync21(c)) linkPath = c;
6229
6385
  else return null;
6230
6386
  }
6231
6387
  if (linkPath === SHIM_PATH) return null;
@@ -6238,7 +6394,7 @@ function findClaudeLink() {
6238
6394
  }
6239
6395
  function isOurShim(path) {
6240
6396
  try {
6241
- return readFileSync18(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
6397
+ return readFileSync19(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
6242
6398
  } catch {
6243
6399
  return false;
6244
6400
  }
@@ -6251,7 +6407,7 @@ function shadowClaude() {
6251
6407
  }
6252
6408
  const { linkPath, realTarget } = found;
6253
6409
  if (isOurShim(linkPath)) {
6254
- console.log(` \xB7 ${linkPath.replace(homedir18(), "~")} already shadowed`);
6410
+ console.log(` \xB7 ${linkPath.replace(homedir19(), "~")} already shadowed`);
6255
6411
  return;
6256
6412
  }
6257
6413
  let wasSymlink = false;
@@ -6261,14 +6417,14 @@ function shadowClaude() {
6261
6417
  }
6262
6418
  const state = { linkPath, realTarget, wasSymlink };
6263
6419
  try {
6264
- writeFileSync14(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
6420
+ writeFileSync15(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
6265
6421
  } catch {
6266
6422
  }
6267
6423
  try {
6268
- rmSync(linkPath, { force: true });
6269
- writeFileSync14(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
6424
+ rmSync2(linkPath, { force: true });
6425
+ writeFileSync15(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
6270
6426
  chmodSync3(linkPath, 493);
6271
- console.log(` \u2713 shadowed ${linkPath.replace(homedir18(), "~")} \u2192 shim (real: ${realTarget.replace(homedir18(), "~")})`);
6427
+ console.log(` \u2713 shadowed ${linkPath.replace(homedir19(), "~")} \u2192 shim (real: ${realTarget.replace(homedir19(), "~")})`);
6272
6428
  } catch (e) {
6273
6429
  console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
6274
6430
  }
@@ -6276,16 +6432,16 @@ function shadowClaude() {
6276
6432
  function unshadowClaude() {
6277
6433
  let state;
6278
6434
  try {
6279
- state = JSON.parse(readFileSync18(SHADOW_STATE_FILE, "utf-8"));
6435
+ state = JSON.parse(readFileSync19(SHADOW_STATE_FILE, "utf-8"));
6280
6436
  } catch {
6281
6437
  return;
6282
6438
  }
6283
6439
  if (!state?.linkPath) return;
6284
6440
  try {
6285
- if (existsSync20(state.linkPath) && !isOurShim(state.linkPath)) return;
6286
- rmSync(state.linkPath, { force: true });
6441
+ if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
6442
+ rmSync2(state.linkPath, { force: true });
6287
6443
  symlinkSync(state.realTarget, state.linkPath);
6288
- console.log(`\u2713 restored ${state.linkPath.replace(homedir18(), "~")} \u2192 ${state.realTarget.replace(homedir18(), "~")}`);
6444
+ console.log(`\u2713 restored ${state.linkPath.replace(homedir19(), "~")} \u2192 ${state.realTarget.replace(homedir19(), "~")}`);
6289
6445
  } catch (e) {
6290
6446
  console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
6291
6447
  }
@@ -6293,16 +6449,16 @@ function unshadowClaude() {
6293
6449
  function addPathBlock() {
6294
6450
  const touched = [];
6295
6451
  for (const rc of rcFiles()) {
6296
- if (!existsSync20(rc) && rc.endsWith(".bash_profile")) continue;
6452
+ if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
6297
6453
  let body = "";
6298
6454
  try {
6299
- body = readFileSync18(rc, "utf-8");
6455
+ body = readFileSync19(rc, "utf-8");
6300
6456
  } catch {
6301
6457
  }
6302
6458
  const cleaned = stripBlock(body);
6303
6459
  const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
6304
6460
  try {
6305
- writeFileSync14(rc, next, "utf-8");
6461
+ writeFileSync15(rc, next, "utf-8");
6306
6462
  touched.push(rc);
6307
6463
  } catch {
6308
6464
  }
@@ -6318,15 +6474,15 @@ function escapeRe(s) {
6318
6474
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6319
6475
  }
6320
6476
  function installPtyShim() {
6321
- mkdirSync13(SHIM_BIN_DIR, { recursive: true });
6322
- mkdirSync13(PTY_STATE_DIR, { recursive: true });
6477
+ mkdirSync14(SHIM_BIN_DIR, { recursive: true });
6478
+ mkdirSync14(PTY_STATE_DIR, { recursive: true });
6323
6479
  const real = resolveRealClaude();
6324
- writeFileSync14(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
6480
+ writeFileSync15(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
6325
6481
  chmodSync3(SHIM_PATH, 493);
6326
6482
  const touched = addPathBlock();
6327
6483
  console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
6328
6484
  shadowClaude();
6329
- if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir18(), "~")).join(", ")}`);
6485
+ if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir19(), "~")).join(", ")}`);
6330
6486
  }
6331
6487
  function uninstallPtyShim() {
6332
6488
  try {
@@ -6334,30 +6490,30 @@ function uninstallPtyShim() {
6334
6490
  } catch {
6335
6491
  }
6336
6492
  try {
6337
- const ls = spawnSync4("tmux", ["ls", "-F", "#{session_name}"], { encoding: "utf-8" });
6493
+ const ls = spawnSync5("tmux", ["ls", "-F", "#{session_name}"], { encoding: "utf-8" });
6338
6494
  for (const s of (ls.stdout || "").split("\n")) {
6339
- if (s.startsWith(SHIM_SESSION_PREFIX)) spawnSync4("tmux", ["kill-session", "-t", `=${s}`], { encoding: "utf-8" });
6495
+ if (s.startsWith(SHIM_SESSION_PREFIX)) spawnSync5("tmux", ["kill-session", "-t", `=${s}`], { encoding: "utf-8" });
6340
6496
  }
6341
6497
  } catch {
6342
6498
  }
6343
6499
  const cleaned = [];
6344
6500
  for (const rc of rcFiles()) {
6345
- if (!existsSync20(rc)) continue;
6501
+ if (!existsSync21(rc)) continue;
6346
6502
  try {
6347
- const body = readFileSync18(rc, "utf-8");
6503
+ const body = readFileSync19(rc, "utf-8");
6348
6504
  if (body.includes(RC_BEGIN)) {
6349
- writeFileSync14(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
6350
- cleaned.push(rc.replace(homedir18(), "~"));
6505
+ writeFileSync15(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
6506
+ cleaned.push(rc.replace(homedir19(), "~"));
6351
6507
  }
6352
6508
  } catch {
6353
6509
  }
6354
6510
  }
6355
6511
  try {
6356
- rmSync(SHIM_BIN_DIR, { recursive: true, force: true });
6512
+ rmSync2(SHIM_BIN_DIR, { recursive: true, force: true });
6357
6513
  } catch {
6358
6514
  }
6359
6515
  try {
6360
- rmSync(PTY_STATE_DIR, { recursive: true, force: true });
6516
+ rmSync2(PTY_STATE_DIR, { recursive: true, force: true });
6361
6517
  } catch {
6362
6518
  }
6363
6519
  console.log(`\u2713 removed pty routing shim${cleaned.length ? ` (cleaned PATH in: ${cleaned.join(", ")})` : ""}`);
@@ -6379,12 +6535,12 @@ function listSessions(opts = {}) {
6379
6535
  const out = [];
6380
6536
  for (const f of files) {
6381
6537
  try {
6382
- const r = JSON.parse(readFileSync18(join16(SESSIONS_DIR, f), "utf-8"));
6538
+ const r = JSON.parse(readFileSync19(join17(SESSIONS_DIR, f), "utf-8"));
6383
6539
  if (!r || !r.session_id) continue;
6384
6540
  if (liveOnly) {
6385
6541
  const s = r.tmux_session;
6386
6542
  if (!s || !isOurSession(s)) continue;
6387
- if (spawnSync4("tmux", ["has-session", "-t", `=${s}`], { encoding: "utf-8" }).status !== 0) continue;
6543
+ if (spawnSync5("tmux", ["has-session", "-t", `=${s}`], { encoding: "utf-8" }).status !== 0) continue;
6388
6544
  }
6389
6545
  out.push(r);
6390
6546
  } catch {
@@ -6398,7 +6554,7 @@ function currentTmuxSession() {
6398
6554
  try {
6399
6555
  const pane = process.env.TMUX_PANE || "";
6400
6556
  const args2 = pane ? ["display-message", "-p", "-t", pane, "#{session_name}"] : ["display-message", "-p", "#{session_name}"];
6401
- const r = spawnSync4("tmux", args2, { encoding: "utf-8" });
6557
+ const r = spawnSync5("tmux", args2, { encoding: "utf-8" });
6402
6558
  if (r.status === 0) return (r.stdout || "").trim();
6403
6559
  } catch {
6404
6560
  }
@@ -6408,7 +6564,7 @@ function resolveTargetSession(override) {
6408
6564
  const own = currentTmuxSession();
6409
6565
  const fromFile = (() => {
6410
6566
  try {
6411
- return readFileSync18(ACTIVE_SESSION_FILE, "utf-8").trim();
6567
+ return readFileSync19(ACTIVE_SESSION_FILE, "utf-8").trim();
6412
6568
  } catch {
6413
6569
  return "";
6414
6570
  }
@@ -6429,14 +6585,14 @@ function injectModel(model, sessionOverride) {
6429
6585
  console.error(`synkro route: refusing suspicious session token: ${session}`);
6430
6586
  return false;
6431
6587
  }
6432
- if (spawnSync4("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return false;
6433
- const sendKeys = (...a) => spawnSync4("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
6588
+ if (spawnSync5("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return false;
6589
+ const sendKeys = (...a) => spawnSync5("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
6434
6590
  sendKeys("Escape");
6435
6591
  sendKeys("-l", `/model ${model}`);
6436
6592
  sendKeys("Enter");
6437
- const pidFile = join16(PTY_STATE_DIR, `poll-${session}.pid`);
6593
+ const pidFile = join17(PTY_STATE_DIR, `poll-${session}.pid`);
6438
6594
  try {
6439
- mkdirSync13(PTY_STATE_DIR, { recursive: true });
6595
+ mkdirSync14(PTY_STATE_DIR, { recursive: true });
6440
6596
  } catch {
6441
6597
  }
6442
6598
  const poll = `old=$(cat '${pidFile}' 2>/dev/null); if [ -n "$old" ]; then kill "$old" 2>/dev/null; fi; rm -f '${pidFile}'; set -o noclobber; echo $$ > '${pidFile}' 2>/dev/null || exit 1; set +o noclobber; trap 'rm -f "${pidFile}"' EXIT; for i in $(seq 1 40); do if tmux capture-pane -t ${session} -p -S -25 2>/dev/null | grep -qiE 'switch model|yes, switch'; then tmux send-keys -t ${session} -l '1'; sleep 0.1; tmux send-keys -t ${session} Enter; break; fi; sleep 0.1; done`;
@@ -6444,17 +6600,17 @@ function injectModel(model, sessionOverride) {
6444
6600
  child.unref();
6445
6601
  return true;
6446
6602
  }
6447
- var SYNKRO_DIR9, SHIM_BIN_DIR, SHIM_PATH, PTY_STATE_DIR, ACTIVE_SESSION_FILE, SHADOW_STATE_FILE, SESSIONS_DIR, SHIM_SESSION_PREFIX, RC_BEGIN, RC_END, RC_BLOCK, SHIM_SOURCE;
6603
+ var SYNKRO_DIR10, SHIM_BIN_DIR, SHIM_PATH, PTY_STATE_DIR, ACTIVE_SESSION_FILE, SHADOW_STATE_FILE, SESSIONS_DIR, SHIM_SESSION_PREFIX, RC_BEGIN, RC_END, RC_BLOCK, SHIM_SOURCE;
6448
6604
  var init_ptyShim = __esm({
6449
6605
  "cli/local-cc/ptyShim.ts"() {
6450
6606
  "use strict";
6451
- SYNKRO_DIR9 = join16(homedir18(), ".synkro");
6452
- SHIM_BIN_DIR = join16(SYNKRO_DIR9, "bin");
6453
- SHIM_PATH = join16(SHIM_BIN_DIR, "claude");
6454
- PTY_STATE_DIR = join16(SYNKRO_DIR9, "pty");
6455
- ACTIVE_SESSION_FILE = join16(PTY_STATE_DIR, "active");
6456
- SHADOW_STATE_FILE = join16(PTY_STATE_DIR, "shadow.json");
6457
- SESSIONS_DIR = join16(PTY_STATE_DIR, "sessions");
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");
6458
6614
  SHIM_SESSION_PREFIX = "synkro-cc-";
6459
6615
  RC_BEGIN = "# >>> synkro pty shim (managed \u2014 do not edit) >>>";
6460
6616
  RC_END = "# <<< synkro pty shim <<<";
@@ -6512,6 +6668,19 @@ exit $RC
6512
6668
  }
6513
6669
  });
6514
6670
 
6671
+ // cli/installer/graderSmoke.ts
6672
+ function isPrimerAckVerdict(value) {
6673
+ return /<category>\s*primer_ack\s*<\/category>/i.test(value) || /<reason>\s*(?:batch )?primer received\s*<\/reason>/i.test(value);
6674
+ }
6675
+ function isValidRiskyEditSmokeVerdict(value) {
6676
+ return /<synkro-verdict(?:\s[^>]*)?>[\s\S]*<\/synkro-verdict>/i.test(value) && /<ok>\s*false\s*<\/ok>/i.test(value) && !isPrimerAckVerdict(value);
6677
+ }
6678
+ var init_graderSmoke = __esm({
6679
+ "cli/installer/graderSmoke.ts"() {
6680
+ "use strict";
6681
+ }
6682
+ });
6683
+
6515
6684
  // cli/commands/install.ts
6516
6685
  var install_exports = {};
6517
6686
  __export(install_exports, {
@@ -6527,9 +6696,9 @@ __export(install_exports, {
6527
6696
  syncSkillFiles: () => syncSkillFiles,
6528
6697
  writeHookScripts: () => writeHookScripts
6529
6698
  });
6530
- import { existsSync as existsSync21, mkdirSync as mkdirSync14, writeFileSync as writeFileSync15, chmodSync as chmodSync4, readFileSync as readFileSync19, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
6531
- import { homedir as homedir19 } from "os";
6532
- import { join as join17, isAbsolute, resolve as resolve4 } from "path";
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";
6533
6702
  import { execSync as execSync4, spawn as spawn4 } from "child_process";
6534
6703
  import { createInterface as createInterface2 } from "readline";
6535
6704
  import { createHash as createHash2 } from "crypto";
@@ -6625,7 +6794,7 @@ async function promptDeployLocation(current = "local") {
6625
6794
  `Where should Synkro run?
6626
6795
  local \u2014 a grading container on this machine (Docker)
6627
6796
  cloud \u2014 a private container hosted by Synkro
6628
- Both use your own Claude key. Choose [${current}] / ${other}: `,
6797
+ Each worker uses the account credentials you authorize. Choose [${current}] / ${other}: `,
6629
6798
  (answer) => {
6630
6799
  rl.close();
6631
6800
  const a = answer.trim().toLowerCase();
@@ -6656,38 +6825,38 @@ async function promptTranscriptSources(wantCC, wantCursor, wantCodex) {
6656
6825
  }
6657
6826
  }
6658
6827
  function ensureSynkroDir() {
6659
- mkdirSync14(SYNKRO_DIR10, { recursive: true });
6660
- mkdirSync14(HOOKS_DIR, { recursive: true });
6661
- mkdirSync14(BIN_DIR, { recursive: true });
6662
- mkdirSync14(OFFSETS_DIR, { recursive: true });
6663
- mkdirSync14(join17(SYNKRO_DIR10, "sessions"), { recursive: true });
6828
+ mkdirSync15(SYNKRO_DIR11, { recursive: true });
6829
+ mkdirSync15(HOOKS_DIR, { recursive: true });
6830
+ mkdirSync15(BIN_DIR, { recursive: true });
6831
+ mkdirSync15(OFFSETS_DIR, { recursive: true });
6832
+ mkdirSync15(join18(SYNKRO_DIR11, "sessions"), { recursive: true });
6664
6833
  }
6665
6834
  function writeHookScripts() {
6666
- const installExtractCorePath = join17(HOOKS_DIR, "installExtractCore.ts");
6667
- const bashScriptPath = join17(HOOKS_DIR, "cc-bash-judge.ts");
6668
- const skillJudgeScriptPath = join17(HOOKS_DIR, "cc-skill-judge.ts");
6669
- const cursorSkillJudgePath = join17(HOOKS_DIR, "cursor-skill-judge.ts");
6670
- const bashFollowupScriptPath = join17(HOOKS_DIR, "cc-bash-followup.ts");
6671
- const editPrecheckScriptPath = join17(HOOKS_DIR, "cc-edit-precheck.ts");
6672
- const cwePrecheckScriptPath = join17(HOOKS_DIR, "cc-cwe-precheck.ts");
6673
- const cvePrecheckScriptPath = join17(HOOKS_DIR, "cc-cve-precheck.ts");
6674
- const planJudgeScriptPath = join17(HOOKS_DIR, "cc-plan-judge.ts");
6675
- const agentJudgeScriptPath = join17(HOOKS_DIR, "cc-agent-judge.ts");
6676
- const stopSummaryScriptPath = join17(HOOKS_DIR, "cc-stop-summary.ts");
6677
- const sessionStartScriptPath = join17(HOOKS_DIR, "cc-session-start.ts");
6678
- const transcriptSyncScriptPath = join17(HOOKS_DIR, "cc-transcript-sync.ts");
6679
- const userPromptSubmitScriptPath = join17(HOOKS_DIR, "cc-user-prompt-submit.ts");
6680
- const promptRouteScriptPath = join17(HOOKS_DIR, "cc-prompt-route.ts");
6681
- const commonScriptPath = join17(HOOKS_DIR, "_synkro-common.ts");
6682
- const commonBashScriptPath = join17(HOOKS_DIR, "_synkro-common.sh");
6683
- const installScanScriptPath = join17(HOOKS_DIR, "cc-install-scan.ts");
6684
- const cursorBashJudgePath = join17(HOOKS_DIR, "cursor-bash-judge.ts");
6685
- const cursorEditCapturePath = join17(HOOKS_DIR, "cursor-edit-capture.ts");
6686
- const cursorAgentCapturePath = join17(HOOKS_DIR, "cursor-agent-capture.ts");
6687
- const mcpStdioProxyPath = join17(HOOKS_DIR, "mcp-stdio-proxy.ts");
6688
- const taskActivateIntentScriptPath = join17(HOOKS_DIR, "cc-task-activate-intent.ts");
6689
- const mcpGateScriptPath = join17(HOOKS_DIR, "cc-mcp-gate.ts");
6690
- const stubCommonPath = join17(HOOKS_DIR, "_synkro-stub-common.ts");
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");
6691
6860
  const stubFiles = [
6692
6861
  [stubCommonPath, STUB_COMMON_TS],
6693
6862
  [bashScriptPath, STUB_BASH_JUDGE_TS],
@@ -6712,14 +6881,14 @@ function writeHookScripts() {
6712
6881
  [cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS]
6713
6882
  ];
6714
6883
  for (const [p, content] of stubFiles) {
6715
- writeFileSync15(p, content, "utf-8");
6884
+ writeFileSync16(p, content, "utf-8");
6716
6885
  chmodSync4(p, 493);
6717
6886
  }
6718
- writeFileSync15(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
6887
+ writeFileSync16(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
6719
6888
  chmodSync4(mcpStdioProxyPath, 493);
6720
6889
  for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
6721
6890
  try {
6722
- unlinkSync7(join17(HOOKS_DIR, stale));
6891
+ unlinkSync7(join18(HOOKS_DIR, stale));
6723
6892
  } catch {
6724
6893
  }
6725
6894
  }
@@ -6755,11 +6924,11 @@ function shellQuoteSingle2(value) {
6755
6924
  }
6756
6925
  function resolveSynkroBundle() {
6757
6926
  const scriptPath = process.argv[1];
6758
- if (scriptPath && existsSync21(scriptPath)) return scriptPath;
6927
+ if (scriptPath && existsSync22(scriptPath)) return scriptPath;
6759
6928
  return null;
6760
6929
  }
6761
6930
  function writeConfigEnv(opts) {
6762
- const credsPath = join17(SYNKRO_DIR10, "credentials.json");
6931
+ const credsPath = join18(SYNKRO_DIR11, "credentials.json");
6763
6932
  const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
6764
6933
  const safeUserId = sanitizeConfigValue(opts.userId);
6765
6934
  const safeOrgId = sanitizeConfigValue(opts.orgId);
@@ -6775,7 +6944,7 @@ function writeConfigEnv(opts) {
6775
6944
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
6776
6945
  `SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
6777
6946
  `SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
6778
- `SYNKRO_VERSION=${shellQuoteSingle2("1.7.86")}`
6947
+ `SYNKRO_VERSION=${shellQuoteSingle2("1.7.88")}`
6779
6948
  ];
6780
6949
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
6781
6950
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
@@ -6795,12 +6964,12 @@ function writeConfigEnv(opts) {
6795
6964
  lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
6796
6965
  lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
6797
6966
  lines.push("");
6798
- writeFileSync15(CONFIG_PATH4, lines.join("\n"), "utf-8");
6967
+ writeFileSync16(CONFIG_PATH4, lines.join("\n"), "utf-8");
6799
6968
  chmodSync4(CONFIG_PATH4, 384);
6800
6969
  }
6801
6970
  function persistedTranscriptConsent(source) {
6802
6971
  try {
6803
- const env = readFileSync19(CONFIG_PATH4, "utf-8");
6972
+ const env = readFileSync20(CONFIG_PATH4, "utf-8");
6804
6973
  const specific = env.match(new RegExp(`^SYNKRO_TRANSCRIPT_CONSENT_${source}='(yes|no)'`, "m"));
6805
6974
  if (specific) return specific[1] === "yes";
6806
6975
  if (source !== "CODEX") {
@@ -6823,7 +6992,7 @@ async function getOrMintCloudToken(gatewayUrl) {
6823
6992
  assertGatewayAllowed(gatewayUrl);
6824
6993
  let stored = "";
6825
6994
  try {
6826
- stored = readFileSync19(CLOUD_JWT_PATH, "utf-8").trim();
6995
+ stored = readFileSync20(CLOUD_JWT_PATH, "utf-8").trim();
6827
6996
  } catch {
6828
6997
  }
6829
6998
  if (stored && !jwtExpired(stored)) return stored;
@@ -6839,25 +7008,53 @@ async function getOrMintCloudToken(gatewayUrl) {
6839
7008
  throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);
6840
7009
  }
6841
7010
  const { token } = await resp.json();
6842
- writeFileSync15(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
7011
+ writeFileSync16(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
6843
7012
  return token;
6844
7013
  }
6845
7014
  async function provisionCloudContainer(opts) {
6846
- let setupToken;
6847
- try {
6848
- console.log(" Authorize your Claude key for the hosted Synkro worker \u2014");
6849
- console.log(" a browser window will open for approval...\n");
6850
- setupToken = await captureClaudeSetupToken();
6851
- } catch (err) {
6852
- console.error(` \u2717 Could not capture Claude setup-token: ${err.message}`);
6853
- console.error(" Cloud needs a Claude setup-token. Run `claude setup-token` manually, then re-run install.");
6854
- process.exit(1);
7015
+ const sf = readFullSynkroFile();
7016
+ const envWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "", 10);
7017
+ const totalWorkers = Number.isFinite(envWorkers) && envWorkers > 0 ? envWorkers : 4;
7018
+ let claudeWorkers = 0;
7019
+ let cursorWorkers = 0;
7020
+ let codexWorkers = 0;
7021
+ if (sf && (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null)) {
7022
+ claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));
7023
+ cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));
7024
+ codexWorkers = Math.max(0, Math.floor(sf.workers.codex || 0));
7025
+ } else {
7026
+ const providers = [];
7027
+ if (sf?.grader.pool && sf.grader.pool !== "auto") providers.push(sf.grader.pool);
7028
+ else {
7029
+ if (opts.hasClaudeCode) providers.push("claude_code");
7030
+ if (opts.hasCursor) providers.push("cursor");
7031
+ if (opts.hasCodex) providers.push("codex");
7032
+ }
7033
+ ({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
7034
+ }
7035
+ if (claudeWorkers + cursorWorkers + codexWorkers === 0) codexWorkers = totalWorkers;
7036
+ const selectedKind = resolveConductorProvider(sf?.grader.pool || "auto", {
7037
+ claudeWorkers,
7038
+ cursorWorkers,
7039
+ codexWorkers
7040
+ });
7041
+ let setupToken = "";
7042
+ if (claudeWorkers > 0 || selectedKind === "claude_code") {
7043
+ try {
7044
+ console.log(" Authorize your Claude account for the hosted Claude worker \u2014");
7045
+ console.log(" a browser window will open for approval...\n");
7046
+ setupToken = await captureClaudeSetupToken();
7047
+ } catch (err) {
7048
+ console.error(` \u2717 Could not capture Claude setup-token: ${err.message}`);
7049
+ console.error(" Run `claude setup-token` manually, then re-run install.");
7050
+ process.exit(1);
7051
+ }
6855
7052
  }
6856
- console.log(" Validating Claude token...");
7053
+ if (setupToken) console.log(" Validating Claude token...");
6857
7054
  let validated = false;
6858
7055
  let authRejected = null;
6859
7056
  let lastErr = "";
6860
- for (let attempt = 1; attempt <= 2 && !validated; attempt++) {
7057
+ for (let attempt = 1; setupToken && attempt <= 2 && !validated; attempt++) {
6861
7058
  try {
6862
7059
  const out = execSync4('claude --print --output-format json "say ok"', {
6863
7060
  env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: setupToken },
@@ -6883,22 +7080,19 @@ async function provisionCloudContainer(opts) {
6883
7080
  }
6884
7081
  if (validated) {
6885
7082
  console.log(" \u2713 Claude token validated.\n");
6886
- } else {
7083
+ } else if (setupToken) {
6887
7084
  console.warn(` \u26A0 Couldn't confirm the token locally in time (${lastErr.slice(0, 50)}).`);
6888
7085
  console.warn(" Proceeding \u2014 the cloud smoke-grade below verifies it end-to-end in the container.\n");
6889
7086
  }
6890
7087
  const repo = detectGitRepo2() || void 0;
6891
- const sf = readFullSynkroFile();
6892
7088
  const harness = [];
6893
7089
  if (opts.hasClaudeCode) harness.push("claude-code");
6894
7090
  if (opts.hasCursor) harness.push("cursor");
6895
- const envWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "", 10);
6896
- const total = sf?.workers?.claude && sf.workers.claude > 0 ? sf.workers.claude : Number.isFinite(envWorkers) && envWorkers > 0 ? envWorkers : 4;
6897
- const cursorTotal = sf?.workers?.cursor && sf.workers.cursor > 0 ? sf.workers.cursor : 0;
7091
+ if (opts.hasCodex) harness.push("codex");
6898
7092
  let cursorApiKey = "";
6899
- if (cursorTotal > 0) {
7093
+ if (cursorWorkers > 0 || selectedKind === "cursor") {
6900
7094
  try {
6901
- cursorApiKey = readFileSync19(join17(SYNKRO_DIR10, "cursor-creds", "api-key"), "utf-8").trim();
7095
+ cursorApiKey = readFileSync20(join18(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
6902
7096
  } catch {
6903
7097
  }
6904
7098
  }
@@ -6908,6 +7102,13 @@ async function provisionCloudContainer(opts) {
6908
7102
  } catch (e) {
6909
7103
  console.warn(` (cloud token unavailable, using session token: ${e.message})`);
6910
7104
  }
7105
+ if (codexWorkers > 0 || selectedKind === "codex") {
7106
+ const codex = await setupCodexCloud(opts.gatewayUrl, opts.jwt, (message) => console.log(` ${message}`));
7107
+ if (!codex.ok) {
7108
+ console.error(` \u2717 ${codex.error || "Codex cloud authorization failed"}`);
7109
+ process.exit(1);
7110
+ }
7111
+ }
6911
7112
  try {
6912
7113
  const resp = await fetch(`${opts.gatewayUrl}/api/v1/cli/cloud-provision`, {
6913
7114
  method: "POST",
@@ -6917,8 +7118,10 @@ async function provisionCloudContainer(opts) {
6917
7118
  user_id: opts.userId,
6918
7119
  email: opts.email,
6919
7120
  harness,
6920
- claude_workers: total,
6921
- cursor_workers: cursorTotal,
7121
+ claude_workers: claudeWorkers,
7122
+ cursor_workers: cursorWorkers,
7123
+ codex_workers: codexWorkers,
7124
+ conductor_provider: selectedKind,
6922
7125
  cursor_api_key: cursorApiKey,
6923
7126
  // never logged; gateway stores it as the org secret
6924
7127
  connected_repo: repo,
@@ -6933,11 +7136,25 @@ async function provisionCloudContainer(opts) {
6933
7136
  }
6934
7137
  const out = await resp.json().catch(() => ({}));
6935
7138
  console.log(` \u2713 cloud container provisioned${out.container_id ? ` (${out.container_id})` : ""}`);
7139
+ const containerBase = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
7140
+ try {
7141
+ const recycle = await fetch(`${containerBase}/recycle`, {
7142
+ method: "POST",
7143
+ headers: { Authorization: `Bearer ${opts.jwt}` },
7144
+ signal: AbortSignal.timeout(3e4)
7145
+ });
7146
+ if (recycle.ok) console.log(" \u2713 cloud grader recycled onto the new pool");
7147
+ else console.warn(` \u26A0 cloud grader recycle returned HTTP ${recycle.status}; warmup will retry the current container`);
7148
+ } catch {
7149
+ console.warn(" \u26A0 cloud grader recycle was unreachable; warmup will retry");
7150
+ }
6936
7151
  console.log();
7152
+ return selectedKind;
6937
7153
  } catch (err) {
6938
7154
  console.error(` \u2717 Cloud provisioning failed: ${err.message}`);
6939
7155
  console.error(" Hooks + MCP are installed; re-run `synkro install` \u2192 cloud to retry.");
6940
7156
  console.log();
7157
+ throw err;
6941
7158
  } finally {
6942
7159
  setupToken = "";
6943
7160
  }
@@ -6978,17 +7195,21 @@ async function printWorkerDebug(base, jwt2) {
6978
7195
  console.warn(" (could not fetch worker debug)\n");
6979
7196
  }
6980
7197
  }
6981
- async function verifyCloudGrader(jwt2) {
7198
+ async function verifyCloudGrader(jwt2, requestedKind) {
6982
7199
  const base = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
6983
7200
  console.log(" Warming the cloud grader (booting container + checking workers)...");
6984
7201
  const deadline = Date.now() + 18e4;
6985
7202
  let healthy = 0;
7203
+ let agentKind = requestedKind || "claude_code";
6986
7204
  while (Date.now() < deadline) {
6987
7205
  try {
6988
7206
  const r = await fetch(`${base}/diag`, { headers: { Authorization: `Bearer ${jwt2}` }, signal: AbortSignal.timeout(6e4) });
6989
7207
  if (r.ok) {
6990
7208
  const j = await r.json();
6991
- healthy = j.claude_code?.healthy ?? 0;
7209
+ if (!requestedKind) {
7210
+ agentKind = ["codex", "cursor", "claude_code"].find((kind) => (j[kind]?.healthy ?? 0) > 0) || "claude_code";
7211
+ }
7212
+ healthy = j[agentKind]?.healthy ?? 0;
6992
7213
  if (healthy >= 1) break;
6993
7214
  }
6994
7215
  } catch {
@@ -7000,7 +7221,7 @@ async function verifyCloudGrader(jwt2) {
7000
7221
  console.warn(" Grading is NOT verified \u2014 check the container or re-run `synkro install`.\n");
7001
7222
  return false;
7002
7223
  }
7003
- console.log(` \u2713 ${healthy} Claude worker(s) healthy`);
7224
+ console.log(` \u2713 ${healthy} ${poolLabel(agentKind)} worker(s) healthy`);
7004
7225
  console.log(" Running smoke grade (a worker scores a known-risky edit; up to 120s)...");
7005
7226
  const started = Date.now();
7006
7227
  const ticker = setInterval(() => {
@@ -7016,7 +7237,7 @@ async function verifyCloudGrader(jwt2) {
7016
7237
  const r = await fetch(`${base}/grade/submit`, {
7017
7238
  method: "POST",
7018
7239
  headers: { Authorization: `Bearer ${jwt2}`, "Content-Type": "application/json" },
7019
- body: JSON.stringify({ role: "grade-edit", payload: probe, content: probe, agent_kind: "claude_code" }),
7240
+ body: JSON.stringify({ role: "grade-edit", payload: probe, content: probe, agent_kind: agentKind }),
7020
7241
  signal: AbortSignal.timeout(12e4)
7021
7242
  });
7022
7243
  stopTicker();
@@ -7026,11 +7247,12 @@ async function verifyCloudGrader(jwt2) {
7026
7247
  body = JSON.parse(raw);
7027
7248
  } catch {
7028
7249
  }
7029
- if (r.ok && typeof body.result === "string" && body.result.includes("<synkro-verdict>")) {
7250
+ if (r.ok && typeof body.result === "string" && isValidRiskyEditSmokeVerdict(body.result)) {
7030
7251
  console.log(" \u2713 smoke grade passed \u2014 cloud grading is live\n");
7031
7252
  return true;
7032
7253
  }
7033
- const detail = (body.error || raw || "").trim().slice(0, 300) || "(empty response)";
7254
+ const invalidVerdict = r.ok && typeof body.result === "string" ? `invalid smoke verdict: ${body.result}` : "";
7255
+ const detail = (body.error || invalidVerdict || raw || "").trim().slice(0, 300) || "(empty response)";
7034
7256
  console.warn(` \u2717 smoke grade FAILED (HTTP ${r.status}): ${detail}`);
7035
7257
  if (body.error_code) {
7036
7258
  console.warn(` \u25B8 ${body.error_code}: ${body.hint || ""}`);
@@ -7055,8 +7277,8 @@ async function verifyCloudGrader(jwt2) {
7055
7277
  }
7056
7278
  function readPersistedDeployLocation() {
7057
7279
  try {
7058
- if (existsSync21(CONFIG_PATH4)) {
7059
- const m = readFileSync19(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
7280
+ if (existsSync22(CONFIG_PATH4)) {
7281
+ const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
7060
7282
  if (m?.[1] === "cloud") return "cloud";
7061
7283
  }
7062
7284
  } catch {
@@ -7064,8 +7286,8 @@ function readPersistedDeployLocation() {
7064
7286
  return "local";
7065
7287
  }
7066
7288
  function updateConfigEnvLocation(location) {
7067
- if (!existsSync21(CONFIG_PATH4)) return;
7068
- let env = readFileSync19(CONFIG_PATH4, "utf-8");
7289
+ if (!existsSync22(CONFIG_PATH4)) return;
7290
+ let env = readFileSync20(CONFIG_PATH4, "utf-8");
7069
7291
  const set = (k, v) => {
7070
7292
  const re = new RegExp(`^${k}=.*$`, "m");
7071
7293
  const line = `${k}='${v}'`;
@@ -7073,14 +7295,14 @@ function updateConfigEnvLocation(location) {
7073
7295
  };
7074
7296
  set("SYNKRO_DEPLOY_LOCATION", location);
7075
7297
  set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
7076
- writeFileSync15(CONFIG_PATH4, env, "utf-8");
7298
+ writeFileSync16(CONFIG_PATH4, env, "utf-8");
7077
7299
  chmodSync4(CONFIG_PATH4, 384);
7078
7300
  }
7079
7301
  async function applyMcpConfig(opts) {
7080
7302
  if (!opts.hasClaudeCode && !opts.hasCursor && !opts.hasCodex) return;
7081
7303
  let mcpJwt2 = "";
7082
7304
  try {
7083
- mcpJwt2 = readFileSync19(join17(SYNKRO_DIR10, ".mcp-jwt"), "utf-8").trim();
7305
+ mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
7084
7306
  } catch {
7085
7307
  }
7086
7308
  if (!mcpJwt2) {
@@ -7172,10 +7394,10 @@ Deploy location: ${current || "(none)"} \u2192 ${desired}
7172
7394
  email = info.email;
7173
7395
  } catch {
7174
7396
  }
7175
- await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
7397
+ const agentKind = await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor, hasCodex });
7176
7398
  updateConfigEnvLocation("cloud");
7177
7399
  await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, hasCodex, local: false });
7178
- await verifyCloudGrader(token);
7400
+ await verifyCloudGrader(token, agentKind);
7179
7401
  console.log(" \u2713 grading + rules/MCP now served from Synkro Cloud\n");
7180
7402
  } else {
7181
7403
  updateConfigEnvLocation("local");
@@ -7210,8 +7432,8 @@ function resolveDeploymentMode() {
7210
7432
  const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
7211
7433
  if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
7212
7434
  try {
7213
- if (existsSync21(CONFIG_PATH4)) {
7214
- const m = readFileSync19(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
7435
+ if (existsSync22(CONFIG_PATH4)) {
7436
+ const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
7215
7437
  const val = m?.[1]?.toLowerCase();
7216
7438
  if (val === "bare-host" || val === "docker") return val;
7217
7439
  }
@@ -7238,16 +7460,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
7238
7460
  meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
7239
7461
  } catch {
7240
7462
  }
7241
- const claudeDir = join17(homedir19(), ".claude");
7463
+ const claudeDir = join18(homedir20(), ".claude");
7242
7464
  try {
7243
- const settings = JSON.parse(readFileSync19(join17(claudeDir, "settings.json"), "utf-8"));
7465
+ const settings = JSON.parse(readFileSync20(join18(claudeDir, "settings.json"), "utf-8"));
7244
7466
  const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
7245
7467
  if (plugins.length) meta.enabled_plugins = plugins;
7246
7468
  if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
7247
7469
  } catch {
7248
7470
  }
7249
7471
  try {
7250
- const mcpCache = JSON.parse(readFileSync19(join17(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
7472
+ const mcpCache = JSON.parse(readFileSync20(join18(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
7251
7473
  const mcpNames = Object.keys(mcpCache);
7252
7474
  if (mcpNames.length) meta.mcp_servers = mcpNames;
7253
7475
  } catch {
@@ -7259,10 +7481,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
7259
7481
  } catch {
7260
7482
  }
7261
7483
  try {
7262
- const sessionsDir = join17(claudeDir, "sessions");
7484
+ const sessionsDir = join18(claudeDir, "sessions");
7263
7485
  const files = readdirSync4(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
7264
7486
  for (const f of files) {
7265
- const s = JSON.parse(readFileSync19(join17(sessionsDir, f), "utf-8"));
7487
+ const s = JSON.parse(readFileSync20(join18(sessionsDir, f), "utf-8"));
7266
7488
  if (s.version) {
7267
7489
  meta.cc_version = meta.cc_version || s.version;
7268
7490
  break;
@@ -7464,7 +7686,7 @@ async function installCommand(opts = {}) {
7464
7686
  await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
7465
7687
  emit("install", {
7466
7688
  phase: "started",
7467
- cli_version_to: "1.7.86",
7689
+ cli_version_to: "1.7.88",
7468
7690
  agents_detected: agents.map((a) => a.kind),
7469
7691
  with_github: false,
7470
7692
  with_local_cc: false,
@@ -7476,9 +7698,9 @@ async function installCommand(opts = {}) {
7476
7698
  const scripts = writeHookScripts();
7477
7699
  console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
7478
7700
  for (const mode of ["edit", "bash"]) {
7479
- const pidFile = join17(SYNKRO_DIR10, "daemon", mode, "daemon.pid");
7701
+ const pidFile = join18(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
7480
7702
  try {
7481
- const pid = parseInt(readFileSync19(pidFile, "utf-8").trim(), 10);
7703
+ const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
7482
7704
  if (pid > 0) {
7483
7705
  process.kill(pid, "SIGTERM");
7484
7706
  console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
@@ -7596,7 +7818,7 @@ async function installCommand(opts = {}) {
7596
7818
  if (mintResp.ok) {
7597
7819
  const minted = await mintResp.json();
7598
7820
  mcpJwt2 = minted.token;
7599
- writeFileSync15(join17(SYNKRO_DIR10, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
7821
+ writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
7600
7822
  } else {
7601
7823
  console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
7602
7824
  }
@@ -7624,7 +7846,7 @@ async function installCommand(opts = {}) {
7624
7846
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7625
7847
  }
7626
7848
  const minted = await mintResp.json();
7627
- writeFileSync15(join17(SYNKRO_DIR10, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7849
+ writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7628
7850
  const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
7629
7851
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7630
7852
  console.log(` url: ${mcp.url}`);
@@ -7646,8 +7868,8 @@ async function installCommand(opts = {}) {
7646
7868
  if (hasCursor && !opts.noMcp) {
7647
7869
  try {
7648
7870
  if (useLocalMcp) {
7649
- const jwtPath = join17(SYNKRO_DIR10, ".mcp-jwt");
7650
- if (!existsSync21(jwtPath)) {
7871
+ const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
7872
+ if (!existsSync22(jwtPath)) {
7651
7873
  const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
7652
7874
  method: "POST",
7653
7875
  headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
@@ -7655,7 +7877,7 @@ async function installCommand(opts = {}) {
7655
7877
  });
7656
7878
  if (mintResp.ok) {
7657
7879
  const minted = await mintResp.json();
7658
- writeFileSync15(jwtPath, minted.token + "\n", { mode: 384 });
7880
+ writeFileSync16(jwtPath, minted.token + "\n", { mode: 384 });
7659
7881
  }
7660
7882
  }
7661
7883
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
@@ -7675,7 +7897,7 @@ async function installCommand(opts = {}) {
7675
7897
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7676
7898
  }
7677
7899
  const minted = await mintResp.json();
7678
- writeFileSync15(join17(SYNKRO_DIR10, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7900
+ writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7679
7901
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
7680
7902
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7681
7903
  console.log(` url: ${mcp.url}`);
@@ -7688,10 +7910,10 @@ async function installCommand(opts = {}) {
7688
7910
  }
7689
7911
  if (hasCodex && !opts.noMcp) {
7690
7912
  try {
7691
- const jwtPath = join17(SYNKRO_DIR10, ".mcp-jwt");
7913
+ const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
7692
7914
  let mcpJwt2 = "";
7693
7915
  try {
7694
- mcpJwt2 = readFileSync19(jwtPath, "utf-8").trim();
7916
+ mcpJwt2 = readFileSync20(jwtPath, "utf-8").trim();
7695
7917
  } catch {
7696
7918
  }
7697
7919
  if (!mcpJwt2) {
@@ -7709,7 +7931,7 @@ async function installCommand(opts = {}) {
7709
7931
  }
7710
7932
  const minted = await mintResp.json();
7711
7933
  mcpJwt2 = minted.token;
7712
- writeFileSync15(jwtPath, mcpJwt2 + "\n", { mode: 384 });
7934
+ writeFileSync16(jwtPath, mcpJwt2 + "\n", { mode: 384 });
7713
7935
  }
7714
7936
  const mcp = installCodexMcpConfig({
7715
7937
  gatewayUrl,
@@ -7756,7 +7978,7 @@ async function installCommand(opts = {}) {
7756
7978
  }
7757
7979
  const pool = sfp?.grader?.pool || "auto";
7758
7980
  if (pool === "cursor") return true;
7759
- if (pool === "claude_code") return false;
7981
+ if (pool === "claude_code" || pool === "codex") return false;
7760
7982
  return hasCursor;
7761
7983
  })();
7762
7984
  if (useLocalMcp) {
@@ -7776,14 +7998,17 @@ async function installCommand(opts = {}) {
7776
7998
  for (const w of sf?.warnings || []) console.warn(` \u26A0 ${w}`);
7777
7999
  let claudeWorkers;
7778
8000
  let cursorWorkers;
7779
- if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {
8001
+ let codexWorkers;
8002
+ if (sf && (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null)) {
7780
8003
  claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));
7781
8004
  cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));
7782
- if (claudeWorkers + cursorWorkers === 0) {
8005
+ codexWorkers = Math.max(0, Math.floor(sf.workers.codex || 0));
8006
+ if (claudeWorkers + cursorWorkers + codexWorkers === 0) {
7783
8007
  claudeWorkers = 0;
7784
- cursorWorkers = 8;
8008
+ cursorWorkers = 0;
8009
+ codexWorkers = 8;
7785
8010
  }
7786
- console.log(` synkro.toml: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor`);
8011
+ console.log(` synkro.toml: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
7787
8012
  } else {
7788
8013
  const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
7789
8014
  const synkroFilePool = sf?.grader?.pool || "auto";
@@ -7792,23 +8017,40 @@ async function installCommand(opts = {}) {
7792
8017
  providers = ["cursor"];
7793
8018
  } else if (synkroFilePool === "claude_code") {
7794
8019
  providers = ["claude_code"];
8020
+ } else if (synkroFilePool === "codex") {
8021
+ providers = ["codex"];
7795
8022
  } else {
7796
8023
  if (hasClaudeCode) providers.push("claude_code");
7797
8024
  if (hasCursor) providers.push("cursor");
8025
+ if (hasCodex) providers.push("codex");
7798
8026
  }
7799
- ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));
8027
+ ({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
7800
8028
  if (synkroFilePool !== "auto") console.log(` synkro.toml: grader pool set to ${poolLabel(synkroFilePool)}`);
7801
8029
  }
7802
- console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);
8030
+ const conductorProvider = resolveConductorProvider(sf?.grader.pool || "auto", {
8031
+ claudeWorkers,
8032
+ cursorWorkers,
8033
+ codexWorkers
8034
+ });
8035
+ let codexHomeDir;
8036
+ if (codexWorkers > 0 || conductorProvider === "codex") {
8037
+ const codexSetup = await setupCodexLocal((message) => console.log(` ${message}`));
8038
+ if (!codexSetup.ok || !codexSetup.home) {
8039
+ console.error(` \u2717 ${codexSetup.error || "Codex grader authorization failed"}`);
8040
+ process.exit(1);
8041
+ }
8042
+ codexHomeDir = codexSetup.home;
8043
+ }
8044
+ console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
7803
8045
  const connectedRepo = detectGitRepo2() || void 0;
7804
- const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });
8046
+ const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
7805
8047
  console.log(` \u2713 pulled ${image}`);
7806
8048
  console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);
7807
8049
  console.log(" waiting for container to be ready...");
7808
8050
  const ready = await waitForContainerReady(6e4);
7809
8051
  if (ready) {
7810
8052
  console.log(" \u2713 container ready");
7811
- const mcpJwt2 = readFileSync19(join17(SYNKRO_DIR10, ".mcp-jwt"), "utf-8").trim();
8053
+ const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
7812
8054
  try {
7813
8055
  const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
7814
8056
  method: "POST",
@@ -7841,8 +8083,8 @@ async function installCommand(opts = {}) {
7841
8083
  console.log();
7842
8084
  } else if (target === "cloud-container") {
7843
8085
  if (graderUsesCursor) await promptCursorApiKey(opts);
7844
- await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
7845
- cloudGradeOk = await verifyCloudGrader(token);
8086
+ const agentKind = await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor, hasCodex });
8087
+ cloudGradeOk = await verifyCloudGrader(token, agentKind);
7846
8088
  }
7847
8089
  if (transcriptConsent) {
7848
8090
  const repo = detectGitRepo2();
@@ -7851,7 +8093,7 @@ async function installCommand(opts = {}) {
7851
8093
  try {
7852
8094
  let mcpToken = "";
7853
8095
  try {
7854
- mcpToken = readFileSync19(join17(SYNKRO_DIR10, ".mcp-jwt"), "utf-8").trim();
8096
+ mcpToken = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
7855
8097
  } catch {
7856
8098
  }
7857
8099
  if (mcpToken) {
@@ -8019,8 +8261,8 @@ function writeSynkroFileIfMissing(opts) {
8019
8261
  try {
8020
8262
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8021
8263
  if (!root) return;
8022
- if (root === homedir19()) return;
8023
- const fp = join17(root, "synkro.toml");
8264
+ if (root === homedir20()) return;
8265
+ const fp = join18(root, "synkro.toml");
8024
8266
  let hasFile = false;
8025
8267
  try {
8026
8268
  hasFile = statSync2(fp).isFile();
@@ -8032,15 +8274,10 @@ function writeSynkroFileIfMissing(opts) {
8032
8274
  }
8033
8275
  let pool = "auto";
8034
8276
  const harness = [];
8035
- if (opts.hasClaudeCode) {
8036
- harness.push("claude-code");
8037
- if (!opts.hasCursor) pool = "claude";
8038
- }
8039
- if (opts.hasCursor) {
8040
- harness.push("cursor");
8041
- if (!opts.hasClaudeCode) pool = "cursor";
8042
- }
8277
+ if (opts.hasClaudeCode) harness.push("claude-code");
8278
+ if (opts.hasCursor) harness.push("cursor");
8043
8279
  if (opts.hasCodex) harness.push("codex");
8280
+ if (harness.length === 1) pool = harness[0] === "claude-code" ? "claude-code" : harness[0];
8044
8281
  const mode = opts.gradingMode === "byok" ? "byok" : "local";
8045
8282
  const toml = [
8046
8283
  "version = 1",
@@ -8057,7 +8294,7 @@ function writeSynkroFileIfMissing(opts) {
8057
8294
  "cve = true",
8058
8295
  ""
8059
8296
  ].join("\n");
8060
- writeFileSync15(fp, toml, "utf-8");
8297
+ writeFileSync16(fp, toml, "utf-8");
8061
8298
  console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);
8062
8299
  } catch {
8063
8300
  }
@@ -8065,12 +8302,12 @@ function writeSynkroFileIfMissing(opts) {
8065
8302
  function updateSynkroTomlLocation(location) {
8066
8303
  try {
8067
8304
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8068
- if (!root || root === homedir19()) return;
8069
- const fp = join17(root, "synkro.toml");
8305
+ if (!root || root === homedir20()) return;
8306
+ const fp = join18(root, "synkro.toml");
8070
8307
  let txt = "";
8071
8308
  try {
8072
8309
  if (!statSync2(fp).isFile()) return;
8073
- txt = readFileSync19(fp, "utf-8");
8310
+ txt = readFileSync20(fp, "utf-8");
8074
8311
  } catch {
8075
8312
  return;
8076
8313
  }
@@ -8078,7 +8315,7 @@ function updateSynkroTomlLocation(location) {
8078
8315
  if (!re.test(txt)) return;
8079
8316
  const next = txt.replace(re, `$1"${location}"`);
8080
8317
  if (next !== txt) {
8081
- writeFileSync15(fp, next, "utf-8");
8318
+ writeFileSync16(fp, next, "utf-8");
8082
8319
  console.log(` synkro.toml: [grader] location = "${location}"`);
8083
8320
  }
8084
8321
  } catch {
@@ -8088,9 +8325,9 @@ function readFullSynkroFile() {
8088
8325
  try {
8089
8326
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8090
8327
  if (!root) return null;
8091
- const fp = join17(root, "synkro.toml");
8092
- if (!existsSync21(fp)) return null;
8093
- const parsed = parseSynkroToml2(readFileSync19(fp, "utf-8"));
8328
+ const fp = join18(root, "synkro.toml");
8329
+ if (!existsSync22(fp)) return null;
8330
+ const parsed = parseSynkroToml2(readFileSync20(fp, "utf-8"));
8094
8331
  const valid = ["claude-code", "cursor", "codex"];
8095
8332
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
8096
8333
  const resolved = resolveGraderPool(parsed);
@@ -8103,7 +8340,8 @@ function readFullSynkroFile() {
8103
8340
  },
8104
8341
  workers: {
8105
8342
  ...resolved.claudeWorkers != null ? { claude: resolved.claudeWorkers } : {},
8106
- ...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {}
8343
+ ...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {},
8344
+ ...resolved.codexWorkers != null ? { codex: resolved.codexWorkers } : {}
8107
8345
  },
8108
8346
  warnings: resolved.warnings,
8109
8347
  ruleset: parsed.ruleset || "default",
@@ -8129,7 +8367,7 @@ function reconcileHarness() {
8129
8367
  console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
8130
8368
  const scripts = writeHookScripts();
8131
8369
  console.log("Wrote hook scripts to ~/.synkro/hooks/");
8132
- const ccSettings = join17(homedir19(), ".claude", "settings.json");
8370
+ const ccSettings = join18(homedir20(), ".claude", "settings.json");
8133
8371
  if (wantCC) {
8134
8372
  installCCHooks(ccSettings, {
8135
8373
  bashJudgeScriptPath: scripts.bashScript,
@@ -8152,7 +8390,7 @@ function reconcileHarness() {
8152
8390
  });
8153
8391
  console.log(" \u2713 Claude Code hooks registered");
8154
8392
  try {
8155
- const mcpJwt2 = readFileSync19(join17(SYNKRO_DIR10, ".mcp-jwt"), "utf-8").trim();
8393
+ const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8156
8394
  if (mcpJwt2) {
8157
8395
  installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt2, local: true });
8158
8396
  console.log(" \u2713 Claude Code MCP registered");
@@ -8164,7 +8402,7 @@ function reconcileHarness() {
8164
8402
  if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
8165
8403
  if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
8166
8404
  }
8167
- const cursorHooks = join17(homedir19(), ".cursor", "hooks.json");
8405
+ const cursorHooks = join18(homedir20(), ".cursor", "hooks.json");
8168
8406
  if (wantCursor) {
8169
8407
  installCursorHooks(cursorHooks, {
8170
8408
  bashJudgeScriptPath: scripts.cursorBashJudgeScript,
@@ -8195,7 +8433,7 @@ function reconcileHarness() {
8195
8433
  if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
8196
8434
  if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
8197
8435
  }
8198
- const codexHooks = join17(process.env.CODEX_HOME || join17(homedir19(), ".codex"), "hooks.json");
8436
+ const codexHooks = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "hooks.json");
8199
8437
  if (wantCodex) {
8200
8438
  installCodexHooks(codexHooks, {
8201
8439
  bashJudgeScriptPath: scripts.bashScript,
@@ -8226,10 +8464,14 @@ function reconcileHarness() {
8226
8464
  if (uninstallCodexHooks(codexHooks)) console.log(" \u2717 Codex hooks removed");
8227
8465
  if (uninstallCodexMcpConfig()) console.log(" \u2717 Codex MCP removed");
8228
8466
  }
8229
- if (sf.workers.claude != null || sf.workers.cursor != null) {
8467
+ if (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null) {
8230
8468
  const cw = Math.max(0, Math.floor(sf.workers.claude || 0));
8231
8469
  const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));
8232
- if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw };
8470
+ const codw = Math.max(0, Math.floor(sf.workers.codex || 0));
8471
+ if (cw + curw + codw > 0) {
8472
+ const counts2 = { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
8473
+ return { ...counts2, conductorProvider: resolveConductorProvider(sf.grader.pool, counts2) };
8474
+ }
8233
8475
  }
8234
8476
  const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
8235
8477
  const providers = [];
@@ -8237,12 +8479,16 @@ function reconcileHarness() {
8237
8479
  providers.push("cursor");
8238
8480
  } else if (sf.grader.pool === "claude_code") {
8239
8481
  providers.push("claude_code");
8482
+ } else if (sf.grader.pool === "codex") {
8483
+ providers.push("codex");
8240
8484
  } else {
8241
8485
  if (wantCC) providers.push("claude_code");
8242
8486
  if (wantCursor) providers.push("cursor");
8487
+ if (wantCodex) providers.push("codex");
8243
8488
  }
8244
8489
  if (providers.length === 0) providers.push("claude_code");
8245
- return splitWorkers(total, providers);
8490
+ const counts = splitWorkers(total, providers);
8491
+ return { ...counts, conductorProvider: resolveConductorProvider(sf.grader.pool, counts) };
8246
8492
  }
8247
8493
  async function ingestSkillTasks(tasks, mcpPort) {
8248
8494
  const summary = { ingested: 0, rules: 0, rejected: [] };
@@ -8279,7 +8525,7 @@ async function syncSkillFiles() {
8279
8525
  if (resolved.length === 0) return;
8280
8526
  const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
8281
8527
  const tasks = resolved.map((fp) => {
8282
- const content = readFileSync19(fp, "utf-8");
8528
+ const content = readFileSync20(fp, "utf-8");
8283
8529
  const source = `skill:${fp.split("/").pop()}`;
8284
8530
  if (!content.trim()) {
8285
8531
  console.log(` \u2298 skill ${source}: empty file, skipped`);
@@ -8300,15 +8546,15 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8300
8546
  const roots = [];
8301
8547
  const add = (p) => {
8302
8548
  try {
8303
- if (p && existsSync21(p) && statSync2(p).isDirectory()) roots.push(p);
8549
+ if (p && existsSync22(p) && statSync2(p).isDirectory()) roots.push(p);
8304
8550
  } catch {
8305
8551
  }
8306
8552
  };
8307
- add(join17(homedir19(), ".claude", "skills"));
8308
- add(join17(homedir19(), ".agents", "skills"));
8553
+ add(join18(homedir20(), ".claude", "skills"));
8554
+ add(join18(homedir20(), ".agents", "skills"));
8309
8555
  if (repoRoot2) {
8310
- add(join17(repoRoot2, ".claude", "skills"));
8311
- add(join17(repoRoot2, ".agents", "skills"));
8556
+ add(join18(repoRoot2, ".claude", "skills"));
8557
+ add(join18(repoRoot2, ".agents", "skills"));
8312
8558
  }
8313
8559
  const out = [];
8314
8560
  const seen = /* @__PURE__ */ new Set();
@@ -8319,7 +8565,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8319
8565
  try {
8320
8566
  const st = statSync2(file);
8321
8567
  if (!st.isFile() || st.size > 2e5) return;
8322
- content = readFileSync19(file, "utf-8");
8568
+ content = readFileSync20(file, "utf-8");
8323
8569
  } catch {
8324
8570
  return;
8325
8571
  }
@@ -8339,7 +8585,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8339
8585
  }
8340
8586
  for (const entry of entries) {
8341
8587
  if (entry.startsWith(".")) continue;
8342
- const full = join17(root, entry);
8588
+ const full = join18(root, entry);
8343
8589
  let st;
8344
8590
  try {
8345
8591
  st = statSync2(full);
@@ -8347,8 +8593,8 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8347
8593
  continue;
8348
8594
  }
8349
8595
  if (st.isDirectory()) {
8350
- const skillMd = join17(full, "SKILL.md");
8351
- if (existsSync21(skillMd)) consider(skillMd, entry);
8596
+ const skillMd = join18(full, "SKILL.md");
8597
+ if (existsSync22(skillMd)) consider(skillMd, entry);
8352
8598
  } else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
8353
8599
  consider(full, entry.replace(/\.mdx?$/i, ""));
8354
8600
  }
@@ -8397,7 +8643,7 @@ async function discoverAndIngestSkills() {
8397
8643
  if (sf?.skills?.length) {
8398
8644
  for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
8399
8645
  try {
8400
- excludeHashes.add(createHash2("sha256").update(readFileSync19(fp, "utf-8")).digest("hex"));
8646
+ excludeHashes.add(createHash2("sha256").update(readFileSync20(fp, "utf-8")).digest("hex"));
8401
8647
  } catch {
8402
8648
  }
8403
8649
  }
@@ -8428,13 +8674,13 @@ async function discoverAndIngestSkills() {
8428
8674
  const setHash = discoverySetHash(selectable);
8429
8675
  let prev = "";
8430
8676
  try {
8431
- prev = readFileSync19(SKILLS_DISCOVERED_PATH, "utf-8").trim();
8677
+ prev = readFileSync20(SKILLS_DISCOVERED_PATH, "utf-8").trim();
8432
8678
  } catch {
8433
8679
  }
8434
8680
  if (prev === setHash) return;
8435
8681
  const picks = await promptSkillDiscovery(found);
8436
8682
  try {
8437
- writeFileSync15(SKILLS_DISCOVERED_PATH, setHash);
8683
+ writeFileSync16(SKILLS_DISCOVERED_PATH, setHash);
8438
8684
  } catch {
8439
8685
  }
8440
8686
  if (picks.length === 0) {
@@ -8473,9 +8719,9 @@ function ensureReachabilityGitHook() {
8473
8719
  const root = run("git rev-parse --show-toplevel");
8474
8720
  if (!root) return null;
8475
8721
  let hooksDir = run("git config --get core.hooksPath");
8476
- hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join17(root, hooksDir) : join17(root, ".git", "hooks");
8477
- if (!existsSync21(hooksDir)) mkdirSync14(hooksDir, { recursive: true });
8478
- const hookPath = join17(hooksDir, "post-commit");
8722
+ hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join18(root, hooksDir) : join18(root, ".git", "hooks");
8723
+ if (!existsSync22(hooksDir)) mkdirSync15(hooksDir, { recursive: true });
8724
+ const hookPath = join18(hooksDir, "post-commit");
8479
8725
  const resolvedBin = resolveSynkroBinPath();
8480
8726
  const invoke = resolvedBin ? `"${resolvedBin}" reachability-scan --quiet` : "true";
8481
8727
  const START = "# >>> synkro reachability (managed) >>>";
@@ -8488,14 +8734,14 @@ function ensureReachabilityGitHook() {
8488
8734
  END
8489
8735
  ].join("\n");
8490
8736
  const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8491
- if (!existsSync21(hookPath)) {
8492
- writeFileSync15(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
8737
+ if (!existsSync22(hookPath)) {
8738
+ writeFileSync16(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
8493
8739
  return "installed";
8494
8740
  }
8495
- let cur = readFileSync19(hookPath, "utf-8");
8741
+ let cur = readFileSync20(hookPath, "utf-8");
8496
8742
  if (cur.includes(START)) {
8497
8743
  cur = cur.replace(new RegExp(esc(START) + "[\\s\\S]*?" + esc(END), "m"), block);
8498
- writeFileSync15(hookPath, cur);
8744
+ writeFileSync16(hookPath, cur);
8499
8745
  try {
8500
8746
  chmodSync4(hookPath, 493);
8501
8747
  } catch {
@@ -8503,7 +8749,7 @@ function ensureReachabilityGitHook() {
8503
8749
  return "updated";
8504
8750
  }
8505
8751
  const sep = cur.endsWith("\n") ? "" : "\n";
8506
- writeFileSync15(hookPath, cur + sep + "\n" + block + "\n");
8752
+ writeFileSync16(hookPath, cur + sep + "\n" + block + "\n");
8507
8753
  try {
8508
8754
  chmodSync4(hookPath, 493);
8509
8755
  } catch {
@@ -8531,17 +8777,17 @@ function detectGitRepo2() {
8531
8777
  function getClaudeProjectsFolder() {
8532
8778
  const cwd = process.cwd();
8533
8779
  const sanitized = "-" + cwd.replace(/\//g, "-");
8534
- const projectsDir = join17(homedir19(), ".claude", "projects", sanitized);
8535
- return existsSync21(projectsDir) ? projectsDir : null;
8780
+ const projectsDir = join18(homedir20(), ".claude", "projects", sanitized);
8781
+ return existsSync22(projectsDir) ? projectsDir : null;
8536
8782
  }
8537
8783
  function extractSessionInsights(projectsDir) {
8538
8784
  const insights = [];
8539
8785
  const files = readdirSync4(projectsDir).filter((f) => f.endsWith(".jsonl"));
8540
8786
  for (const file of files) {
8541
8787
  const sessionId = file.replace(".jsonl", "");
8542
- const filePath = join17(projectsDir, file);
8788
+ const filePath = join18(projectsDir, file);
8543
8789
  try {
8544
- const content = readFileSync19(filePath, "utf-8");
8790
+ const content = readFileSync20(filePath, "utf-8");
8545
8791
  const lines = content.split("\n").filter(Boolean);
8546
8792
  for (let i = 0; i < lines.length; i++) {
8547
8793
  try {
@@ -8617,17 +8863,17 @@ function extractTextContent(content) {
8617
8863
  return "";
8618
8864
  }
8619
8865
  function getCodexTranscriptFiles(repo) {
8620
- const sessionsDir = join17(process.env.CODEX_HOME || join17(homedir19(), ".codex"), "sessions");
8621
- if (!existsSync21(sessionsDir)) return [];
8866
+ const sessionsDir = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "sessions");
8867
+ if (!existsSync22(sessionsDir)) return [];
8622
8868
  let relative = [];
8623
8869
  try {
8624
8870
  relative = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
8625
8871
  } catch {
8626
8872
  return [];
8627
8873
  }
8628
- return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join17(sessionsDir, p)).filter((filePath) => {
8874
+ return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join18(sessionsDir, p)).filter((filePath) => {
8629
8875
  try {
8630
- const first = readFileSync19(filePath, "utf-8").split("\n", 1)[0];
8876
+ const first = readFileSync20(filePath, "utf-8").split("\n", 1)[0];
8631
8877
  const meta = JSON.parse(first);
8632
8878
  const cwd = typeof meta?.payload?.cwd === "string" ? resolve4(meta.payload.cwd) : "";
8633
8879
  const root = resolve4(repo);
@@ -8638,7 +8884,7 @@ function getCodexTranscriptFiles(repo) {
8638
8884
  });
8639
8885
  }
8640
8886
  function parseCodexTranscriptFile(filePath) {
8641
- const lines = readFileSync19(filePath, "utf-8").split("\n").filter(Boolean);
8887
+ const lines = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean);
8642
8888
  let sessionId = "";
8643
8889
  let model = "";
8644
8890
  const messages = [];
@@ -8693,7 +8939,7 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
8693
8939
  totalSessions++;
8694
8940
  totalMessages += result.ingested ?? messages.length;
8695
8941
  }
8696
- writeFileSync15(join17(OFFSETS_DIR, parsed.sessionId), String(readFileSync19(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
8942
+ writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
8697
8943
  } catch {
8698
8944
  }
8699
8945
  if ((i + 1) % 10 === 0 || i === files.length - 1) {
@@ -8707,14 +8953,14 @@ function cursorProjectSlug(workspaceRoot) {
8707
8953
  return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
8708
8954
  }
8709
8955
  function getCursorTranscriptsDir() {
8710
- const dir = join17(homedir19(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
8711
- return existsSync21(dir) ? dir : null;
8956
+ const dir = join18(homedir20(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
8957
+ return existsSync22(dir) ? dir : null;
8712
8958
  }
8713
8959
  function isSafeConvId(id) {
8714
8960
  return /^[A-Za-z0-9_-]+$/.test(id);
8715
8961
  }
8716
8962
  function parseCursorTranscriptFile(filePath) {
8717
- const content = readFileSync19(filePath, "utf-8");
8963
+ const content = readFileSync20(filePath, "utf-8");
8718
8964
  const lines = content.split("\n").filter(Boolean);
8719
8965
  const messages = [];
8720
8966
  for (let i = 0; i < lines.length; i++) {
@@ -8746,8 +8992,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8746
8992
  for (let i = 0; i < convDirs.length; i++) {
8747
8993
  const convId = convDirs[i];
8748
8994
  if (!isSafeConvId(convId)) continue;
8749
- const filePath = join17(dir, convId, `${convId}.jsonl`);
8750
- if (!existsSync21(filePath)) continue;
8995
+ const filePath = join18(dir, convId, `${convId}.jsonl`);
8996
+ if (!existsSync22(filePath)) continue;
8751
8997
  try {
8752
8998
  const all = parseCursorTranscriptFile(filePath);
8753
8999
  const messages = all.length > 500 ? all.slice(-500) : all;
@@ -8769,8 +9015,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8769
9015
  process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
8770
9016
  }
8771
9017
  try {
8772
- const lc = readFileSync19(filePath, "utf-8").split("\n").filter(Boolean).length;
8773
- writeFileSync15(join17(OFFSETS_DIR, convId), String(lc), "utf-8");
9018
+ const lc = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length;
9019
+ writeFileSync16(join18(OFFSETS_DIR, convId), String(lc), "utf-8");
8774
9020
  } catch {
8775
9021
  }
8776
9022
  }
@@ -8778,7 +9024,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8778
9024
  return { sessions: totalSessions, messages: totalMessages };
8779
9025
  }
8780
9026
  function parseTranscriptFile(filePath) {
8781
- const content = readFileSync19(filePath, "utf-8");
9027
+ const content = readFileSync20(filePath, "utf-8");
8782
9028
  const lines = content.split("\n").filter(Boolean);
8783
9029
  const messages = [];
8784
9030
  for (let i = 0; i < lines.length; i++) {
@@ -8826,7 +9072,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
8826
9072
  for (let i = 0; i < files.length; i++) {
8827
9073
  const file = files[i];
8828
9074
  const sessionId = file.replace(".jsonl", "");
8829
- const filePath = join17(projectsDir, file);
9075
+ const filePath = join18(projectsDir, file);
8830
9076
  try {
8831
9077
  const allMessages = parseTranscriptFile(filePath);
8832
9078
  const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
@@ -8848,9 +9094,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
8848
9094
  process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
8849
9095
  }
8850
9096
  try {
8851
- const content = readFileSync19(join17(projectsDir, file), "utf-8");
9097
+ const content = readFileSync20(join18(projectsDir, file), "utf-8");
8852
9098
  const lineCount = content.split("\n").filter(Boolean).length;
8853
- writeFileSync15(join17(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9099
+ writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
8854
9100
  } catch {
8855
9101
  }
8856
9102
  }
@@ -8871,7 +9117,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
8871
9117
  const sessions = [];
8872
9118
  for (const file of batch) {
8873
9119
  const sessionId = file.replace(".jsonl", "");
8874
- const filePath = join17(projectsDir, file);
9120
+ const filePath = join18(projectsDir, file);
8875
9121
  try {
8876
9122
  const allMessages = parseTranscriptFile(filePath);
8877
9123
  const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
@@ -8900,11 +9146,11 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
8900
9146
  }
8901
9147
  for (const file of batch) {
8902
9148
  const sessionId = file.replace(".jsonl", "");
8903
- const filePath = join17(projectsDir, file);
9149
+ const filePath = join18(projectsDir, file);
8904
9150
  try {
8905
- const content = readFileSync19(filePath, "utf-8");
9151
+ const content = readFileSync20(filePath, "utf-8");
8906
9152
  const lineCount = content.split("\n").filter(Boolean).length;
8907
- writeFileSync15(join17(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9153
+ writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
8908
9154
  } catch {
8909
9155
  }
8910
9156
  }
@@ -8944,7 +9190,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
8944
9190
  try {
8945
9191
  const parsed = parseCodexTranscriptFile(filePath);
8946
9192
  if (parsed.sessionId) {
8947
- writeFileSync15(join17(OFFSETS_DIR, parsed.sessionId), String(readFileSync19(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
9193
+ writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
8948
9194
  }
8949
9195
  } catch {
8950
9196
  }
@@ -8952,7 +9198,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
8952
9198
  }
8953
9199
  return { sessions: totalSessions, messages: totalMessages };
8954
9200
  }
8955
- var SYNKRO_DIR10, HOOKS_DIR, BIN_DIR, CONFIG_PATH4, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
9201
+ var SYNKRO_DIR11, HOOKS_DIR, BIN_DIR, CONFIG_PATH4, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
8956
9202
  var init_install = __esm({
8957
9203
  "cli/commands/install.ts"() {
8958
9204
  "use strict";
@@ -8972,11 +9218,13 @@ var init_install = __esm({
8972
9218
  init_claudeDesktopTap();
8973
9219
  init_dockerInstall();
8974
9220
  init_setupToken();
9221
+ init_codexCloudSetup();
8975
9222
  init_ptyShim();
8976
- SYNKRO_DIR10 = join17(homedir19(), ".synkro");
8977
- HOOKS_DIR = join17(SYNKRO_DIR10, "hooks");
8978
- BIN_DIR = join17(SYNKRO_DIR10, "bin");
8979
- CONFIG_PATH4 = join17(SYNKRO_DIR10, "config.env");
9223
+ 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");
8980
9228
  MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
8981
9229
  import { readFileSync } from 'node:fs';
8982
9230
  import { homedir } from 'node:os';
@@ -9087,23 +9335,23 @@ rl.on('line', async (line) => {
9087
9335
  }
9088
9336
  });
9089
9337
  `;
9090
- OFFSETS_DIR = join17(SYNKRO_DIR10, ".transcript-offsets");
9091
- CLOUD_JWT_PATH = join17(SYNKRO_DIR10, ".cloud-jwt");
9092
- SKILLS_DISCOVERED_PATH = join17(SYNKRO_DIR10, ".skills-discovered");
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");
9093
9341
  }
9094
9342
  });
9095
9343
 
9096
9344
  // cli/local-cc/install.ts
9097
- import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync16, readFileSync as readFileSync20, chmodSync as chmodSync5, copyFileSync as copyFileSync2, renameSync as renameSync7, unlinkSync as unlinkSync8, openSync as openSync2, fsyncSync, closeSync as closeSync2 } from "fs";
9098
- import { join as join18 } from "path";
9099
- import { homedir as homedir20 } from "os";
9100
- import { spawnSync as spawnSync6 } from "child_process";
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";
9348
+ import { spawnSync as spawnSync7 } from "child_process";
9101
9349
  function writePluginFiles() {
9102
9350
  for (const c of CHANNELS) {
9103
- mkdirSync15(c.sessionDir, { recursive: true });
9104
- mkdirSync15(c.pluginSettingsDir, { recursive: true });
9105
- writeFileSync16(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
9106
- writeFileSync16(
9351
+ mkdirSync16(c.sessionDir, { recursive: true });
9352
+ mkdirSync16(c.pluginSettingsDir, { recursive: true });
9353
+ writeFileSync17(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
9354
+ writeFileSync17(
9107
9355
  c.pluginSettingsPath,
9108
9356
  JSON.stringify({
9109
9357
  fastMode: true,
@@ -9118,13 +9366,13 @@ function writePluginFiles() {
9118
9366
  }, null, 2) + "\n",
9119
9367
  "utf-8"
9120
9368
  );
9121
- writeFileSync16(c.runScriptPath, c.runScriptSource, "utf-8");
9369
+ writeFileSync17(c.runScriptPath, c.runScriptSource, "utf-8");
9122
9370
  chmodSync5(c.runScriptPath, 493);
9123
9371
  }
9124
9372
  }
9125
9373
  function runBunInstall() {
9126
9374
  for (const c of CHANNELS) {
9127
- const r = spawnSync6("bun", ["install", "--silent"], {
9375
+ const r = spawnSync7("bun", ["install", "--silent"], {
9128
9376
  cwd: c.sessionDir,
9129
9377
  encoding: "utf-8",
9130
9378
  timeout: 12e4
@@ -9137,10 +9385,10 @@ function runBunInstall() {
9137
9385
  }
9138
9386
  }
9139
9387
  function safelyMutateClaudeJson(mutator) {
9140
- if (!existsSync22(CLAUDE_JSON_PATH)) {
9388
+ if (!existsSync23(CLAUDE_JSON_PATH)) {
9141
9389
  return;
9142
9390
  }
9143
- const originalText = readFileSync20(CLAUDE_JSON_PATH, "utf-8");
9391
+ const originalText = readFileSync21(CLAUDE_JSON_PATH, "utf-8");
9144
9392
  let parsed;
9145
9393
  try {
9146
9394
  parsed = JSON.parse(originalText);
@@ -9172,7 +9420,7 @@ function safelyMutateClaudeJson(mutator) {
9172
9420
  copyFileSync2(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
9173
9421
  const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
9174
9422
  try {
9175
- writeFileSync16(tmpPath, newText, "utf-8");
9423
+ writeFileSync17(tmpPath, newText, "utf-8");
9176
9424
  const fd = openSync2(tmpPath, "r");
9177
9425
  try {
9178
9426
  fsyncSync(fd);
@@ -9205,7 +9453,7 @@ function writeProjectMcpJson() {
9205
9453
  }
9206
9454
  }
9207
9455
  };
9208
- writeFileSync16(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
9456
+ writeFileSync17(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
9209
9457
  }
9210
9458
  }
9211
9459
  function patchClaudeJson() {
@@ -9240,15 +9488,15 @@ function patchClaudeJson() {
9240
9488
  });
9241
9489
  }
9242
9490
  function installLocalCC() {
9243
- let bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
9491
+ let bunCheck = spawnSync7("bun", ["--version"], { encoding: "utf-8" });
9244
9492
  if (bunCheck.status !== 0) {
9245
9493
  if (process.platform === "darwin") {
9246
9494
  console.log(" Installing bun via brew...");
9247
- const brewR = spawnSync6("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
9495
+ const brewR = spawnSync7("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
9248
9496
  if (brewR.status !== 0) {
9249
9497
  throw new LocalCCInstallError("bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash");
9250
9498
  }
9251
- bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
9499
+ bunCheck = spawnSync7("bun", ["--version"], { encoding: "utf-8" });
9252
9500
  if (bunCheck.status !== 0) {
9253
9501
  throw new LocalCCInstallError("bun installed but not found on PATH. Restart your terminal and re-run install.");
9254
9502
  }
@@ -9282,42 +9530,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
9282
9530
  var init_install2 = __esm({
9283
9531
  "cli/local-cc/install.ts"() {
9284
9532
  "use strict";
9285
- CLAUDE_JSON_BACKUP_PATH = join18(homedir20(), ".claude.json.synkro-bak");
9286
- SESSION_DIR = join18(homedir20(), ".synkro", "cc_sessions");
9287
- PLUGIN_PATH = join18(SESSION_DIR, "synkro-channel.ts");
9288
- PLUGIN_PKG_PATH = join18(SESSION_DIR, "package.json");
9289
- PLUGIN_SETTINGS_DIR = join18(SESSION_DIR, ".claude");
9290
- PLUGIN_SETTINGS_PATH = join18(PLUGIN_SETTINGS_DIR, "settings.json");
9291
- PROJECT_MCP_PATH = join18(SESSION_DIR, ".mcp.json");
9292
- CLAUDE_JSON_PATH = join18(homedir20(), ".claude.json");
9293
- RUN_SCRIPT_PATH = join18(SESSION_DIR, "run-claude.sh");
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");
9294
9542
  TMUX_SESSION_NAME = "synkro-local-cc";
9295
9543
  CHANNEL_1_PORT = 8941;
9296
- SESSION_DIR_2 = join18(homedir20(), ".synkro", "cc_sessions_2");
9297
- PLUGIN_PATH_2 = join18(SESSION_DIR_2, "synkro-channel.ts");
9298
- PLUGIN_PKG_PATH_2 = join18(SESSION_DIR_2, "package.json");
9299
- PLUGIN_SETTINGS_DIR_2 = join18(SESSION_DIR_2, ".claude");
9300
- PLUGIN_SETTINGS_PATH_2 = join18(PLUGIN_SETTINGS_DIR_2, "settings.json");
9301
- PROJECT_MCP_PATH_2 = join18(SESSION_DIR_2, ".mcp.json");
9302
- RUN_SCRIPT_PATH_2 = join18(SESSION_DIR_2, "run-claude.sh");
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");
9303
9551
  TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
9304
9552
  CHANNEL_2_PORT = 8951;
9305
- SESSION_DIR_3 = join18(homedir20(), ".synkro", "cc_sessions_3");
9306
- PLUGIN_PATH_3 = join18(SESSION_DIR_3, "synkro-channel.ts");
9307
- PLUGIN_PKG_PATH_3 = join18(SESSION_DIR_3, "package.json");
9308
- PLUGIN_SETTINGS_DIR_3 = join18(SESSION_DIR_3, ".claude");
9309
- PLUGIN_SETTINGS_PATH_3 = join18(PLUGIN_SETTINGS_DIR_3, "settings.json");
9310
- PROJECT_MCP_PATH_3 = join18(SESSION_DIR_3, ".mcp.json");
9311
- RUN_SCRIPT_PATH_3 = join18(SESSION_DIR_3, "run-claude.sh");
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");
9312
9560
  TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
9313
9561
  CHANNEL_3_PORT = 8942;
9314
- SESSION_DIR_4 = join18(homedir20(), ".synkro", "cc_sessions_4");
9315
- PLUGIN_PATH_4 = join18(SESSION_DIR_4, "synkro-channel.ts");
9316
- PLUGIN_PKG_PATH_4 = join18(SESSION_DIR_4, "package.json");
9317
- PLUGIN_SETTINGS_DIR_4 = join18(SESSION_DIR_4, ".claude");
9318
- PLUGIN_SETTINGS_PATH_4 = join18(PLUGIN_SETTINGS_DIR_4, "settings.json");
9319
- PROJECT_MCP_PATH_4 = join18(SESSION_DIR_4, ".mcp.json");
9320
- RUN_SCRIPT_PATH_4 = join18(SESSION_DIR_4, "run-claude.sh");
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");
9321
9569
  TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
9322
9570
  CHANNEL_4_PORT = 8952;
9323
9571
  RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
@@ -9591,10 +9839,10 @@ var disconnect_exports = {};
9591
9839
  __export(disconnect_exports, {
9592
9840
  disconnectCommand: () => disconnectCommand
9593
9841
  });
9594
- import { existsSync as existsSync23, rmSync as rmSync2, readdirSync as readdirSync5 } from "fs";
9595
- import { homedir as homedir21 } from "os";
9596
- import { join as join19 } from "path";
9597
- import { spawnSync as spawnSync7 } from "child_process";
9842
+ 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";
9845
+ import { spawnSync as spawnSync8 } from "child_process";
9598
9846
  import { createInterface as createInterface3 } from "readline";
9599
9847
  async function tearDownLocalCC() {
9600
9848
  const docker = dockerStatus();
@@ -9608,7 +9856,7 @@ async function tearDownLocalCC() {
9608
9856
  console.log("\u2713 removed synkro-server container");
9609
9857
  try {
9610
9858
  const image = imageTag();
9611
- const r = spawnSync7("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
9859
+ const r = spawnSync8("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
9612
9860
  console.log(r.status === 0 ? `\u2713 removed Docker image ${image}` : "\xB7 no Docker image to remove");
9613
9861
  } catch {
9614
9862
  }
@@ -9694,7 +9942,7 @@ async function disconnectCommand(args2 = [], opts = {}) {
9694
9942
  if (desktopMcpRemoved) removed.mcp = true;
9695
9943
  console.log(`${desktopMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? "removed from claude_desktop_config.json" : "no entry found"}`);
9696
9944
  }
9697
- if (existsSync23(SYNKRO_DIR11)) {
9945
+ if (existsSync24(SYNKRO_DIR12)) {
9698
9946
  if (purge2) {
9699
9947
  emit("uninstall", {
9700
9948
  flavor,
@@ -9703,41 +9951,41 @@ async function disconnectCommand(args2 = [], opts = {}) {
9703
9951
  duration_ms: Date.now() - startedAt
9704
9952
  });
9705
9953
  await flush({ force: true });
9706
- rmSync2(SYNKRO_DIR11, { recursive: true, force: true });
9954
+ rmSync3(SYNKRO_DIR12, { recursive: true, force: true });
9707
9955
  removed.config = true;
9708
- console.log(`\u2713 wiped ${SYNKRO_DIR11} entirely \u2014 including all scan data and backups`);
9956
+ console.log(`\u2713 wiped ${SYNKRO_DIR12} entirely \u2014 including all scan data and backups`);
9709
9957
  } else {
9710
9958
  const keep = /* @__PURE__ */ new Set([
9711
- join19(SYNKRO_DIR11, "pgdata"),
9712
- join19(SYNKRO_DIR11, "pgdata-backups"),
9713
- join19(SYNKRO_DIR11, ".transcript-offsets")
9959
+ join20(SYNKRO_DIR12, "pgdata"),
9960
+ join20(SYNKRO_DIR12, "pgdata-backups"),
9961
+ join20(SYNKRO_DIR12, ".transcript-offsets")
9714
9962
  ]);
9715
9963
  const preserved = [];
9716
- for (const entry of readdirSync5(SYNKRO_DIR11)) {
9717
- const full = join19(SYNKRO_DIR11, entry);
9964
+ for (const entry of readdirSync5(SYNKRO_DIR12)) {
9965
+ const full = join20(SYNKRO_DIR12, entry);
9718
9966
  if (keep.has(full)) {
9719
9967
  preserved.push(entry);
9720
9968
  continue;
9721
9969
  }
9722
- rmSync2(full, { recursive: true, force: true });
9970
+ rmSync3(full, { recursive: true, force: true });
9723
9971
  }
9724
9972
  removed.config = true;
9725
9973
  if (preserved.length > 0) {
9726
- console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR11} (kept your scan data: ${preserved.join(", ")})`);
9974
+ console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR12} (kept your scan data: ${preserved.join(", ")})`);
9727
9975
  console.log(" run `synkro uninstall --purge` to delete that too");
9728
9976
  } else {
9729
- console.log(`\u2713 removed ${SYNKRO_DIR11}`);
9977
+ console.log(`\u2713 removed ${SYNKRO_DIR12}`);
9730
9978
  }
9731
9979
  }
9732
9980
  } else {
9733
- console.log(`\xB7 ${SYNKRO_DIR11} already gone`);
9981
+ console.log(`\xB7 ${SYNKRO_DIR12} already gone`);
9734
9982
  }
9735
9983
  if (!purge2) {
9736
9984
  emit("uninstall", { flavor, purge: purge2, removed, duration_ms: Date.now() - startedAt });
9737
9985
  }
9738
9986
  console.log(purge2 ? "\nSynkro fully removed \u2014 this machine is clean, as if it was never installed." : "\nSynkro uninstalled. Your scan data is preserved.");
9739
9987
  }
9740
- var SYNKRO_DIR11;
9988
+ var SYNKRO_DIR12;
9741
9989
  var init_disconnect = __esm({
9742
9990
  "cli/commands/disconnect.ts"() {
9743
9991
  "use strict";
@@ -9752,14 +10000,14 @@ var init_disconnect = __esm({
9752
10000
  init_dockerInstall();
9753
10001
  init_macKeychain();
9754
10002
  init_telemetry();
9755
- SYNKRO_DIR11 = join19(homedir21(), ".synkro");
10003
+ SYNKRO_DIR12 = join20(homedir22(), ".synkro");
9756
10004
  }
9757
10005
  });
9758
10006
 
9759
10007
  // cli/local-cc/turnLog.ts
9760
- import { appendFileSync as appendFileSync3, existsSync as existsSync24, mkdirSync as mkdirSync16, openSync as openSync3, readFileSync as readFileSync21, readSync, closeSync as closeSync3, statSync as statSync3, watchFile, unwatchFile } from "fs";
9761
- import { dirname as dirname7, join as join20 } from "path";
9762
- import { homedir as homedir22 } from "os";
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";
9763
10011
  function truncate(s, max = PREVIEW_MAX) {
9764
10012
  if (s.length <= max) return s;
9765
10013
  return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
@@ -9779,7 +10027,7 @@ function extractSeverity(result) {
9779
10027
  }
9780
10028
  function appendTurn(args2) {
9781
10029
  try {
9782
- mkdirSync16(dirname7(TURN_LOG_PATH), { recursive: true });
10030
+ mkdirSync17(dirname7(TURN_LOG_PATH), { recursive: true });
9783
10031
  const entry = {
9784
10032
  ts: new Date(args2.startedAt).toISOString(),
9785
10033
  role: args2.role,
@@ -9795,11 +10043,11 @@ function appendTurn(args2) {
9795
10043
  }
9796
10044
  }
9797
10045
  function readRecentTurns(n = 20) {
9798
- if (!existsSync24(TURN_LOG_PATH)) return [];
10046
+ if (!existsSync25(TURN_LOG_PATH)) return [];
9799
10047
  try {
9800
10048
  const size = statSync3(TURN_LOG_PATH).size;
9801
10049
  if (size === 0) return [];
9802
- const text = readFileSync21(TURN_LOG_PATH, "utf-8");
10050
+ const text = readFileSync22(TURN_LOG_PATH, "utf-8");
9803
10051
  const lines = text.split("\n").filter(Boolean);
9804
10052
  const lastN = lines.slice(-n).reverse();
9805
10053
  return lastN.map((line) => {
@@ -9815,8 +10063,8 @@ function readRecentTurns(n = 20) {
9815
10063
  }
9816
10064
  function followTurns(onEntry) {
9817
10065
  try {
9818
- mkdirSync16(dirname7(TURN_LOG_PATH), { recursive: true });
9819
- if (!existsSync24(TURN_LOG_PATH)) {
10066
+ mkdirSync17(dirname7(TURN_LOG_PATH), { recursive: true });
10067
+ if (!existsSync25(TURN_LOG_PATH)) {
9820
10068
  appendFileSync3(TURN_LOG_PATH, "", "utf-8");
9821
10069
  }
9822
10070
  } catch {
@@ -9878,7 +10126,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
9878
10126
  var init_turnLog = __esm({
9879
10127
  "cli/local-cc/turnLog.ts"() {
9880
10128
  "use strict";
9881
- TURN_LOG_PATH = join20(homedir22(), ".synkro", "cc_sessions", "turns.log");
10129
+ TURN_LOG_PATH = join21(homedir23(), ".synkro", "cc_sessions", "turns.log");
9882
10130
  PREVIEW_MAX = 400;
9883
10131
  }
9884
10132
  });
@@ -10032,8 +10280,8 @@ __export(scanPr_exports, {
10032
10280
  scanPrCommand: () => scanPrCommand
10033
10281
  });
10034
10282
  import { execSync as execSync5, spawn as spawn5 } from "child_process";
10035
- import { readFileSync as readFileSync22, existsSync as existsSync25 } from "fs";
10036
- import { join as join21 } from "path";
10283
+ import { readFileSync as readFileSync23, existsSync as existsSync26 } from "fs";
10284
+ import { join as join22 } from "path";
10037
10285
  function parseMatchSpec(condition) {
10038
10286
  if (!condition.startsWith("match_spec:")) return null;
10039
10287
  try {
@@ -10512,10 +10760,10 @@ function shouldFail(findings, threshold) {
10512
10760
  return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
10513
10761
  }
10514
10762
  function readRepoDeps() {
10515
- const pkgPath = join21(process.cwd(), "package.json");
10516
- if (!existsSync25(pkgPath)) return {};
10763
+ const pkgPath = join22(process.cwd(), "package.json");
10764
+ if (!existsSync26(pkgPath)) return {};
10517
10765
  try {
10518
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
10766
+ const pkg = JSON.parse(readFileSync23(pkgPath, "utf-8"));
10519
10767
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
10520
10768
  } catch {
10521
10769
  return {};
@@ -10759,15 +11007,15 @@ var routeDecide_exports = {};
10759
11007
  __export(routeDecide_exports, {
10760
11008
  routeDecide: () => routeDecide
10761
11009
  });
10762
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "fs";
10763
- import { homedir as homedir23 } from "os";
10764
- import { join as join22 } from "path";
11010
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "fs";
11011
+ import { homedir as homedir24 } from "os";
11012
+ import { join as join23 } from "path";
10765
11013
  function safeSid(sid) {
10766
11014
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
10767
11015
  }
10768
11016
  function loadMcpJwt() {
10769
11017
  try {
10770
- return readFileSync23(join22(SYNKRO_DIR12, ".mcp-jwt"), "utf-8").trim();
11018
+ return readFileSync24(join23(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
10771
11019
  } catch {
10772
11020
  return "";
10773
11021
  }
@@ -10777,7 +11025,7 @@ async function routeDecide(sessionId) {
10777
11025
  const sid = safeSid(sessionId);
10778
11026
  let prompt = "";
10779
11027
  try {
10780
- const rec = JSON.parse(readFileSync23(join22(SESSIONS_DIR2, sid + ".json"), "utf-8"));
11028
+ const rec = JSON.parse(readFileSync24(join23(SESSIONS_DIR2, sid + ".json"), "utf-8"));
10781
11029
  prompt = String(rec.last_prompt || "").trim();
10782
11030
  } catch {
10783
11031
  return;
@@ -10800,29 +11048,29 @@ async function routeDecide(sessionId) {
10800
11048
  return;
10801
11049
  }
10802
11050
  if (!model || !VALID_MODELS.has(model)) return;
10803
- const lastFile = join22(PTY_DIR, "route-last-" + sid);
11051
+ const lastFile = join23(PTY_DIR, "route-last-" + sid);
10804
11052
  let last = "";
10805
11053
  try {
10806
- last = readFileSync23(lastFile, "utf-8").trim();
11054
+ last = readFileSync24(lastFile, "utf-8").trim();
10807
11055
  } catch {
10808
11056
  }
10809
11057
  if (model === last) return;
10810
11058
  try {
10811
- writeFileSync17(lastFile, model);
11059
+ writeFileSync18(lastFile, model);
10812
11060
  } catch {
10813
11061
  }
10814
11062
  try {
10815
- writeFileSync17(join22(PTY_DIR, "route-" + sid), model);
11063
+ writeFileSync18(join23(PTY_DIR, "route-" + sid), model);
10816
11064
  } catch {
10817
11065
  }
10818
11066
  }
10819
- var SYNKRO_DIR12, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
11067
+ var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
10820
11068
  var init_routeDecide = __esm({
10821
11069
  "cli/local-cc/routeDecide.ts"() {
10822
11070
  "use strict";
10823
- SYNKRO_DIR12 = join22(homedir23(), ".synkro");
10824
- PTY_DIR = join22(SYNKRO_DIR12, "pty");
10825
- SESSIONS_DIR2 = join22(PTY_DIR, "sessions");
11071
+ SYNKRO_DIR13 = join23(homedir24(), ".synkro");
11072
+ PTY_DIR = join23(SYNKRO_DIR13, "pty");
11073
+ SESSIONS_DIR2 = join23(PTY_DIR, "sessions");
10826
11074
  VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
10827
11075
  GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
10828
11076
  }
@@ -10833,10 +11081,10 @@ var routeOrchestrate_exports = {};
10833
11081
  __export(routeOrchestrate_exports, {
10834
11082
  routeAndResubmit: () => routeAndResubmit
10835
11083
  });
10836
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "fs";
10837
- import { homedir as homedir24 } from "os";
10838
- import { join as join23 } from "path";
10839
- import { spawnSync as spawnSync8 } from "child_process";
11084
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
11085
+ import { homedir as homedir25 } from "os";
11086
+ import { join as join24 } from "path";
11087
+ import { spawnSync as spawnSync9 } from "child_process";
10840
11088
  function safeSid2(sid) {
10841
11089
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
10842
11090
  }
@@ -10845,7 +11093,7 @@ function safeSession(s) {
10845
11093
  }
10846
11094
  function loadMcpJwt2() {
10847
11095
  try {
10848
- return readFileSync24(join23(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
11096
+ return readFileSync25(join24(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
10849
11097
  } catch {
10850
11098
  return "";
10851
11099
  }
@@ -10871,7 +11119,7 @@ async function classifyTask(prompt) {
10871
11119
  }
10872
11120
  function lastRoutedModel(sid) {
10873
11121
  try {
10874
- const v = readFileSync24(join23(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
11122
+ const v = readFileSync25(join24(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
10875
11123
  return VALID_MODELS2.has(v) ? v : "";
10876
11124
  } catch {
10877
11125
  return "";
@@ -10881,12 +11129,12 @@ function resolveSession(sid, tmuxSession) {
10881
11129
  const candidates = [];
10882
11130
  if (tmuxSession) candidates.push(tmuxSession);
10883
11131
  try {
10884
- const rec = JSON.parse(readFileSync24(join23(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
11132
+ const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
10885
11133
  if (rec.tmux_session) candidates.push(rec.tmux_session);
10886
11134
  } catch {
10887
11135
  }
10888
11136
  try {
10889
- candidates.push(readFileSync24(ACTIVE_SESSION_FILE2, "utf-8").trim());
11137
+ candidates.push(readFileSync25(ACTIVE_SESSION_FILE2, "utf-8").trim());
10890
11138
  } catch {
10891
11139
  }
10892
11140
  for (const c of candidates) if (c && safeSession(c)) return c;
@@ -10898,12 +11146,12 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
10898
11146
  const sid = safeSid2(sessionId);
10899
11147
  const session = resolveSession(sid, tmuxSession);
10900
11148
  if (!session) return;
10901
- if (spawnSync8("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return;
11149
+ if (spawnSync9("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return;
10902
11150
  const current = lastRoutedModel(sid);
10903
11151
  const picked = forceModel && VALID_MODELS2.has(forceModel) ? forceModel : await classifyTask(trimmed);
10904
11152
  const doSwap = !!picked && picked !== "keep" && picked !== current;
10905
- const sk = (...a) => spawnSync8("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
10906
- const capture = () => spawnSync8("tmux", ["capture-pane", "-t", session, "-p", "-S", "-25"], { encoding: "utf-8" }).stdout || "";
11153
+ const sk = (...a) => spawnSync9("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
11154
+ const capture = () => spawnSync9("tmux", ["capture-pane", "-t", session, "-p", "-S", "-25"], { encoding: "utf-8" }).stdout || "";
10907
11155
  await wait(400);
10908
11156
  if (doSwap) {
10909
11157
  sk("C-u");
@@ -10921,29 +11169,29 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
10921
11169
  }
10922
11170
  await wait(500);
10923
11171
  try {
10924
- writeFileSync18(join23(PTY_DIR2, "route-last-" + sid), picked);
11172
+ writeFileSync19(join24(PTY_DIR2, "route-last-" + sid), picked);
10925
11173
  } catch {
10926
11174
  }
10927
11175
  }
10928
11176
  try {
10929
- writeFileSync18(join23(PTY_DIR2, "route-guard-" + sid), "1");
11177
+ writeFileSync19(join24(PTY_DIR2, "route-guard-" + sid), "1");
10930
11178
  } catch {
10931
11179
  }
10932
11180
  sk("C-u");
10933
11181
  await wait(80);
10934
- spawnSync8("tmux", ["set-buffer", "-b", "synkro-route", trimmed], { encoding: "utf-8" });
10935
- spawnSync8("tmux", ["paste-buffer", "-t", session, "-b", "synkro-route", "-d"], { encoding: "utf-8" });
11182
+ spawnSync9("tmux", ["set-buffer", "-b", "synkro-route", trimmed], { encoding: "utf-8" });
11183
+ spawnSync9("tmux", ["paste-buffer", "-t", session, "-b", "synkro-route", "-d"], { encoding: "utf-8" });
10936
11184
  await wait(120);
10937
11185
  sk("Enter");
10938
11186
  }
10939
- var SYNKRO_DIR13, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2, GRADER_HOST_PORT2, wait;
11187
+ var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2, GRADER_HOST_PORT2, wait;
10940
11188
  var init_routeOrchestrate = __esm({
10941
11189
  "cli/local-cc/routeOrchestrate.ts"() {
10942
11190
  "use strict";
10943
- SYNKRO_DIR13 = join23(homedir24(), ".synkro");
10944
- PTY_DIR2 = join23(SYNKRO_DIR13, "pty");
10945
- SESSIONS_DIR3 = join23(PTY_DIR2, "sessions");
10946
- ACTIVE_SESSION_FILE2 = join23(PTY_DIR2, "active");
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");
10947
11195
  VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
10948
11196
  GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
10949
11197
  wait = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -10955,14 +11203,14 @@ var routingToggle_exports = {};
10955
11203
  __export(routingToggle_exports, {
10956
11204
  routingCommand: () => routingCommand
10957
11205
  });
10958
- import { writeFileSync as writeFileSync19, unlinkSync as unlinkSync9, existsSync as existsSync26, readdirSync as readdirSync6 } from "fs";
10959
- import { homedir as homedir25 } from "os";
10960
- import { join as join24 } from "path";
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";
10961
11209
  function safeSid3(sid) {
10962
11210
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
10963
11211
  }
10964
11212
  function markerFor(session) {
10965
- return session ? join24(PTY_DIR3, "routing-on-" + safeSid3(session)) : join24(PTY_DIR3, "routing-on");
11213
+ return session ? join25(PTY_DIR3, "routing-on-" + safeSid3(session)) : join25(PTY_DIR3, "routing-on");
10966
11214
  }
10967
11215
  function routingCommand(args2) {
10968
11216
  const sub = (args2[0] || "status").trim();
@@ -10971,7 +11219,7 @@ function routingCommand(args2) {
10971
11219
  if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
10972
11220
  if (sub === "on") {
10973
11221
  try {
10974
- writeFileSync19(markerFor(session), "1");
11222
+ writeFileSync20(markerFor(session), "1");
10975
11223
  } catch (e) {
10976
11224
  console.error("routing on failed:", String(e));
10977
11225
  return;
@@ -10983,7 +11231,7 @@ function routingCommand(args2) {
10983
11231
  if (sub === "off") {
10984
11232
  let removed = 0;
10985
11233
  if (session) {
10986
- if (existsSync26(markerFor(session))) {
11234
+ if (existsSync27(markerFor(session))) {
10987
11235
  try {
10988
11236
  unlinkSync9(markerFor(session));
10989
11237
  removed++;
@@ -10994,7 +11242,7 @@ function routingCommand(args2) {
10994
11242
  for (const f of safeList()) {
10995
11243
  if (f === "routing-on" || f.startsWith("routing-on-")) {
10996
11244
  try {
10997
- unlinkSync9(join24(PTY_DIR3, f));
11245
+ unlinkSync9(join25(PTY_DIR3, f));
10998
11246
  removed++;
10999
11247
  } catch {
11000
11248
  }
@@ -11026,24 +11274,24 @@ var PTY_DIR3;
11026
11274
  var init_routingToggle = __esm({
11027
11275
  "cli/local-cc/routingToggle.ts"() {
11028
11276
  "use strict";
11029
- PTY_DIR3 = join24(homedir25(), ".synkro", "pty");
11277
+ PTY_DIR3 = join25(homedir26(), ".synkro", "pty");
11030
11278
  }
11031
11279
  });
11032
11280
 
11033
11281
  // cli/local-cc/pueue.ts
11034
- import { execFileSync as execFileSync4, spawnSync as spawnSync9, spawn as spawn6 } from "child_process";
11035
- import { homedir as homedir26 } from "os";
11036
- import { join as join25 } from "path";
11282
+ 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";
11037
11285
  import { connect as connect2 } from "net";
11038
11286
  function pueueAvailable() {
11039
- const r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
11287
+ const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
11040
11288
  if (r.status !== 0) {
11041
11289
  throw new PueueError("pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.");
11042
11290
  }
11043
11291
  }
11044
11292
  function statusJson() {
11045
11293
  pueueAvailable();
11046
- const r = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8" });
11294
+ const r = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8" });
11047
11295
  if (r.status !== 0) {
11048
11296
  throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || "unknown error"} \u2014 is pueued running?`);
11049
11297
  }
@@ -11088,18 +11336,18 @@ function startTask(opts = {}) {
11088
11336
  let existing = findTask(ch);
11089
11337
  while (existing) {
11090
11338
  if (existing.status === "Running" || existing.status === "Queued") {
11091
- spawnSync9("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
11092
- spawnSync9("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
11339
+ spawnSync10("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
11340
+ spawnSync10("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
11093
11341
  for (let i = 0; i < 10; i++) {
11094
11342
  const check = findTask(ch);
11095
11343
  if (!check || check.id !== existing.id || check.status !== "Running" && check.status !== "Queued") break;
11096
- spawnSync9("sleep", ["0.5"], { encoding: "utf-8" });
11344
+ spawnSync10("sleep", ["0.5"], { encoding: "utf-8" });
11097
11345
  }
11098
11346
  }
11099
- spawnSync9("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
11347
+ spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
11100
11348
  existing = findTask(ch);
11101
11349
  }
11102
- const runScript = join25(cwd, "run-claude.sh");
11350
+ const runScript = join26(cwd, "run-claude.sh");
11103
11351
  const args2 = [
11104
11352
  "add",
11105
11353
  "--label",
@@ -11110,7 +11358,7 @@ function startTask(opts = {}) {
11110
11358
  "bash",
11111
11359
  runScript
11112
11360
  ];
11113
- const r = spawnSync9("pueue", args2, { encoding: "utf-8" });
11361
+ const r = spawnSync10("pueue", args2, { encoding: "utf-8" });
11114
11362
  if (r.status !== 0) {
11115
11363
  throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);
11116
11364
  }
@@ -11121,25 +11369,25 @@ function startTask(opts = {}) {
11121
11369
  return created;
11122
11370
  }
11123
11371
  function stopTask(channel = CHANNEL_PRIMARY) {
11124
- spawnSync9("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
11372
+ spawnSync10("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
11125
11373
  let t = findTask(channel);
11126
11374
  while (t) {
11127
11375
  if (t.status === "Running" || t.status === "Queued") {
11128
- spawnSync9("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
11376
+ spawnSync10("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
11129
11377
  for (let i = 0; i < 10; i++) {
11130
11378
  const check = findTask(channel);
11131
11379
  if (!check || check.id !== t.id || check.status !== "Running" && check.status !== "Queued") break;
11132
- spawnSync9("sleep", ["0.5"], { encoding: "utf-8" });
11380
+ spawnSync10("sleep", ["0.5"], { encoding: "utf-8" });
11133
11381
  }
11134
11382
  }
11135
- spawnSync9("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
11383
+ spawnSync10("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
11136
11384
  t = findTask(channel);
11137
11385
  }
11138
11386
  }
11139
11387
  function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
11140
11388
  const t = findTask(channel);
11141
11389
  if (!t) return `(no ${channel.taskLabel} task)`;
11142
- const r = spawnSync9("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
11390
+ const r = spawnSync10("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
11143
11391
  return r.stdout || r.stderr || "(no output)";
11144
11392
  }
11145
11393
  function ensureRunning(opts = {}) {
@@ -11164,8 +11412,8 @@ function probePort(host, port, timeoutMs = 500) {
11164
11412
  });
11165
11413
  }
11166
11414
  function tmuxDismissPrompts(tmuxSession = TMUX_SESSION) {
11167
- spawnSync9("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
11168
- spawnSync9("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
11415
+ spawnSync10("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
11416
+ spawnSync10("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
11169
11417
  }
11170
11418
  async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tmuxSession = TMUX_SESSION) {
11171
11419
  const deadline = Date.now() + timeoutMs;
@@ -11177,46 +11425,46 @@ async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tm
11177
11425
  return probePort(host, port);
11178
11426
  }
11179
11427
  function brewInstall(pkg) {
11180
- const brew = spawnSync9("brew", ["--version"], { encoding: "utf-8" });
11428
+ const brew = spawnSync10("brew", ["--version"], { encoding: "utf-8" });
11181
11429
  if (brew.status !== 0) return false;
11182
11430
  console.log(` Installing ${pkg} via brew...`);
11183
- const r = spawnSync9("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
11431
+ const r = spawnSync10("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
11184
11432
  return r.status === 0;
11185
11433
  }
11186
11434
  function assertPueueInstalled() {
11187
- let r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
11435
+ let r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
11188
11436
  if (r.status !== 0) {
11189
11437
  if (process.platform === "darwin" && brewInstall("pueue")) {
11190
- r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
11438
+ r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
11191
11439
  if (r.status !== 0) throw new PueueError("pueue install succeeded but binary not found on PATH.");
11192
11440
  } else {
11193
11441
  throw new PueueError("pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue");
11194
11442
  }
11195
11443
  }
11196
- const status = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
11444
+ const status = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
11197
11445
  if (status.status !== 0) {
11198
11446
  console.log(" Starting pueued daemon...");
11199
11447
  const child = spawn6("pueued", ["-d"], { stdio: "ignore", detached: true });
11200
11448
  child.unref();
11201
- spawnSync9("sleep", ["1"]);
11202
- const retry = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
11449
+ spawnSync10("sleep", ["1"]);
11450
+ const retry = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
11203
11451
  if (retry.status !== 0) {
11204
11452
  throw new PueueError("pueue daemon not reachable after starting pueued. Check `pueued` manually.");
11205
11453
  }
11206
11454
  }
11207
- spawnSync9("pueue", ["parallel", "2"], { encoding: "utf-8" });
11455
+ spawnSync10("pueue", ["parallel", "2"], { encoding: "utf-8" });
11208
11456
  }
11209
11457
  function assertClaudeInstalled() {
11210
- const r = spawnSync9("claude", ["--version"], { encoding: "utf-8" });
11458
+ const r = spawnSync10("claude", ["--version"], { encoding: "utf-8" });
11211
11459
  if (r.status !== 0) {
11212
11460
  throw new PueueError("claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code");
11213
11461
  }
11214
11462
  }
11215
11463
  function assertTmuxInstalled() {
11216
- let r = spawnSync9("tmux", ["-V"], { encoding: "utf-8" });
11464
+ let r = spawnSync10("tmux", ["-V"], { encoding: "utf-8" });
11217
11465
  if (r.status !== 0) {
11218
11466
  if (process.platform === "darwin" && brewInstall("tmux")) {
11219
- r = spawnSync9("tmux", ["-V"], { encoding: "utf-8" });
11467
+ r = spawnSync10("tmux", ["-V"], { encoding: "utf-8" });
11220
11468
  if (r.status !== 0) throw new PueueError("tmux install succeeded but binary not found on PATH.");
11221
11469
  } else {
11222
11470
  throw new PueueError("tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)");
@@ -11229,12 +11477,12 @@ var init_pueue = __esm({
11229
11477
  "use strict";
11230
11478
  TASK_LABEL = "synkro-local-cc";
11231
11479
  TMUX_SESSION = "synkro-local-cc";
11232
- SESSION_DIR2 = join25(homedir26(), ".synkro", "cc_sessions");
11480
+ SESSION_DIR2 = join26(homedir27(), ".synkro", "cc_sessions");
11233
11481
  TASK_LABEL_2 = "synkro-local-cc-2";
11234
11482
  TMUX_SESSION_2 = "synkro-local-cc-2";
11235
- SESSION_DIR_22 = join25(homedir26(), ".synkro", "cc_sessions_2");
11236
- SESSION_DIR_32 = join25(homedir26(), ".synkro", "cc_sessions_3");
11237
- SESSION_DIR_42 = join25(homedir26(), ".synkro", "cc_sessions_4");
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");
11238
11486
  PueueError = class extends Error {
11239
11487
  constructor(message, cause) {
11240
11488
  super(message);
@@ -11249,13 +11497,13 @@ var init_pueue = __esm({
11249
11497
  });
11250
11498
 
11251
11499
  // cli/local-cc/settings.ts
11252
- import { existsSync as existsSync27, readFileSync as readFileSync25 } from "fs";
11253
- import { homedir as homedir27 } from "os";
11254
- import { join as join26 } from "path";
11500
+ import { existsSync as existsSync28, readFileSync as readFileSync26 } from "fs";
11501
+ import { homedir as homedir28 } from "os";
11502
+ import { join as join27 } from "path";
11255
11503
  function isLocalCCEnabled() {
11256
- if (!existsSync27(CONFIG_PATH5)) return false;
11504
+ if (!existsSync28(CONFIG_PATH5)) return false;
11257
11505
  try {
11258
- const content = readFileSync25(CONFIG_PATH5, "utf-8");
11506
+ const content = readFileSync26(CONFIG_PATH5, "utf-8");
11259
11507
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
11260
11508
  return match?.[1] === "yes";
11261
11509
  } catch {
@@ -11266,7 +11514,7 @@ var CONFIG_PATH5;
11266
11514
  var init_settings = __esm({
11267
11515
  "cli/local-cc/settings.ts"() {
11268
11516
  "use strict";
11269
- CONFIG_PATH5 = join26(homedir27(), ".synkro", "config.env");
11517
+ CONFIG_PATH5 = join27(homedir28(), ".synkro", "config.env");
11270
11518
  }
11271
11519
  });
11272
11520
 
@@ -11275,11 +11523,11 @@ var localCc_exports = {};
11275
11523
  __export(localCc_exports, {
11276
11524
  localCcCommand: () => localCcCommand
11277
11525
  });
11278
- import { spawnSync as spawnSync10 } from "child_process";
11279
- import { homedir as homedir28 } from "os";
11280
- import { join as join27 } from "path";
11526
+ import { spawnSync as spawnSync11 } from "child_process";
11527
+ import { homedir as homedir29 } from "os";
11528
+ import { join as join28 } from "path";
11281
11529
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
11282
- import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
11530
+ import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
11283
11531
  function deploymentMode() {
11284
11532
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
11285
11533
  if (env === "docker") return "docker";
@@ -11385,15 +11633,15 @@ TROUBLESHOOTING
11385
11633
  `);
11386
11634
  }
11387
11635
  function readGatewayUrl() {
11388
- if (existsSync28(CONFIG_PATH6)) {
11389
- const m = readFileSync26(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
11636
+ if (existsSync29(CONFIG_PATH6)) {
11637
+ const m = readFileSync27(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
11390
11638
  if (m) return m[1];
11391
11639
  }
11392
11640
  return "https://api.synkro.sh";
11393
11641
  }
11394
11642
  function updateLocalInferenceFlag(enabled) {
11395
- if (!existsSync28(CONFIG_PATH6)) return;
11396
- let content = readFileSync26(CONFIG_PATH6, "utf-8");
11643
+ if (!existsSync29(CONFIG_PATH6)) return;
11644
+ let content = readFileSync27(CONFIG_PATH6, "utf-8");
11397
11645
  const flag = enabled ? "yes" : "no";
11398
11646
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
11399
11647
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -11402,7 +11650,7 @@ function updateLocalInferenceFlag(enabled) {
11402
11650
  SYNKRO_LOCAL_INFERENCE='${flag}'
11403
11651
  `;
11404
11652
  }
11405
- writeFileSync20(CONFIG_PATH6, content, "utf-8");
11653
+ writeFileSync21(CONFIG_PATH6, content, "utf-8");
11406
11654
  }
11407
11655
  async function setServerGradingProvider(provider) {
11408
11656
  await ensureValidToken();
@@ -11456,7 +11704,7 @@ async function cmdStatus() {
11456
11704
  }
11457
11705
  const ch1Up = await isChannelAvailable();
11458
11706
  console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? "reachable" : "unreachable"}`);
11459
- const tmux1 = spawnSync10("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
11707
+ const tmux1 = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
11460
11708
  console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? "live" : "absent"}`);
11461
11709
  const t2 = findTask(CHANNEL_SECONDARY);
11462
11710
  if (!t2) {
@@ -11466,7 +11714,7 @@ async function cmdStatus() {
11466
11714
  }
11467
11715
  const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);
11468
11716
  console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? "reachable" : "unreachable"}`);
11469
- const tmux2 = spawnSync10("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
11717
+ const tmux2 = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
11470
11718
  console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? "live" : "absent"}`);
11471
11719
  }
11472
11720
  async function cmdEnable() {
@@ -11523,9 +11771,9 @@ async function warmChannels(ready1, ready2) {
11523
11771
  async function cmdStart(rest = []) {
11524
11772
  if (inDockerMode()) {
11525
11773
  if (rest.length > 0) {
11526
- const { claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest);
11527
- console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor)...`);
11528
- await dockerUpdate({ claudeWorkers, cursorWorkers });
11774
+ const { claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest);
11775
+ console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)...`);
11776
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider });
11529
11777
  const ready3 = await waitForContainerReady(6e4);
11530
11778
  console.log(ready3 ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
11531
11779
  return;
@@ -11571,18 +11819,20 @@ async function cmdRestart(rest = []) {
11571
11819
  const { explicit } = resolveWorkerConfig(rest);
11572
11820
  let claudeWorkers;
11573
11821
  let cursorWorkers;
11822
+ let codexWorkers;
11823
+ let conductorProvider;
11574
11824
  if (explicit) {
11575
- ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));
11825
+ ({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest));
11576
11826
  } else {
11577
11827
  const reconciled = reconcileHarness();
11578
11828
  if (reconciled) {
11579
- ({ claudeWorkers, cursorWorkers } = reconciled);
11829
+ ({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = reconciled);
11580
11830
  } else {
11581
- ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));
11831
+ ({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest));
11582
11832
  }
11583
11833
  }
11584
- console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor, pulling latest image)...`);
11585
- await dockerUpdate({ claudeWorkers, cursorWorkers });
11834
+ console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex, pulling latest image)...`);
11835
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider });
11586
11836
  const ready = await waitForContainerReady(6e4);
11587
11837
  console.log(ready ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
11588
11838
  if (ready) {
@@ -11672,7 +11922,7 @@ function cmdLogs(rest) {
11672
11922
  }
11673
11923
  return "200";
11674
11924
  })();
11675
- spawnSync10("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
11925
+ spawnSync11("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
11676
11926
  return;
11677
11927
  }
11678
11928
  for (const arg of rest) {
@@ -11720,7 +11970,7 @@ function cmdLogs(rest) {
11720
11970
  function cmdAttach(rest) {
11721
11971
  assertTmuxInstalled();
11722
11972
  const readonly = rest.some((a) => a === "--readonly" || a === "-r");
11723
- const has = spawnSync10("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
11973
+ const has = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
11724
11974
  if (has.status !== 0) {
11725
11975
  console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);
11726
11976
  process.exit(1);
@@ -11733,7 +11983,7 @@ function cmdAttach(rest) {
11733
11983
  console.log("Detach with Ctrl-B then D. (Do not press Ctrl-C \u2014 that would interrupt claude.)");
11734
11984
  console.log();
11735
11985
  const args2 = readonly ? ["attach-session", "-r", "-t", TMUX_SESSION_NAME] : ["attach-session", "-t", TMUX_SESSION_NAME];
11736
- const r = spawnSync10("tmux", args2, { stdio: "inherit" });
11986
+ const r = spawnSync11("tmux", args2, { stdio: "inherit" });
11737
11987
  process.exit(r.status ?? 0);
11738
11988
  }
11739
11989
  async function cmdTest() {
@@ -11834,8 +12084,8 @@ var init_localCc = __esm({
11834
12084
  init_install();
11835
12085
  init_client2();
11836
12086
  init_stub();
11837
- SYNKRO_CONFIG_PATH = join27(homedir28(), ".synkro", "config.env");
11838
- CONFIG_PATH6 = join27(homedir28(), ".synkro", "config.env");
12087
+ SYNKRO_CONFIG_PATH = join28(homedir29(), ".synkro", "config.env");
12088
+ CONFIG_PATH6 = join28(homedir29(), ".synkro", "config.env");
11839
12089
  }
11840
12090
  });
11841
12091
 
@@ -11844,14 +12094,14 @@ var import_exports = {};
11844
12094
  __export(import_exports, {
11845
12095
  importCommand: () => importCommand
11846
12096
  });
11847
- import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync7 } from "fs";
11848
- import { homedir as homedir29 } from "os";
11849
- import { join as join28 } from "path";
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";
11850
12100
  import { execSync as execSync6 } from "child_process";
11851
12101
  import { createInterface as createInterface4 } from "readline";
11852
12102
  function readMcpJwt() {
11853
12103
  try {
11854
- return readFileSync27(join28(homedir29(), ".synkro", ".mcp-jwt"), "utf-8").trim();
12104
+ return readFileSync28(join29(homedir30(), ".synkro", ".mcp-jwt"), "utf-8").trim();
11855
12105
  } catch {
11856
12106
  return "";
11857
12107
  }
@@ -11859,7 +12109,7 @@ function readMcpJwt() {
11859
12109
  function readConfigEnv2() {
11860
12110
  const out = {};
11861
12111
  try {
11862
- for (const line of readFileSync27(CONFIG_PATH7, "utf-8").split("\n")) {
12112
+ for (const line of readFileSync28(CONFIG_PATH7, "utf-8").split("\n")) {
11863
12113
  const t = line.trim();
11864
12114
  if (!t || t.startsWith("#")) continue;
11865
12115
  const eq = t.indexOf("=");
@@ -11871,8 +12121,8 @@ function readConfigEnv2() {
11871
12121
  }
11872
12122
  function projectsFolder() {
11873
12123
  const sanitized = process.cwd().replace(/\//g, "-");
11874
- const dir = join28(homedir29(), ".claude", "projects", sanitized);
11875
- return existsSync29(dir) ? dir : null;
12124
+ const dir = join29(homedir30(), ".claude", "projects", sanitized);
12125
+ return existsSync30(dir) ? dir : null;
11876
12126
  }
11877
12127
  function repoName() {
11878
12128
  try {
@@ -11911,7 +12161,7 @@ function extractToolResultText(content, e) {
11911
12161
  return t;
11912
12162
  }
11913
12163
  function parseSession(filePath, sessionId) {
11914
- const lines = readFileSync27(filePath, "utf-8").split("\n").filter(Boolean);
12164
+ const lines = readFileSync28(filePath, "utf-8").split("\n").filter(Boolean);
11915
12165
  const messages = [];
11916
12166
  const actions = [];
11917
12167
  let step = 0;
@@ -11991,7 +12241,7 @@ async function importCommand() {
11991
12241
  return;
11992
12242
  }
11993
12243
  }
11994
- const sessions = files.map((f) => parseSession(join28(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
12244
+ const sessions = files.map((f) => parseSession(join29(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
11995
12245
  const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
11996
12246
  let ok = 0, fail = 0;
11997
12247
  if (isCloud) {
@@ -12066,7 +12316,7 @@ var init_import = __esm({
12066
12316
  "cli/commands/import.ts"() {
12067
12317
  "use strict";
12068
12318
  init_stub();
12069
- CONFIG_PATH7 = join28(homedir29(), ".synkro", "config.env");
12319
+ CONFIG_PATH7 = join29(homedir30(), ".synkro", "config.env");
12070
12320
  }
12071
12321
  });
12072
12322
 
@@ -12108,10 +12358,10 @@ var init_packVerify = __esm({
12108
12358
  });
12109
12359
 
12110
12360
  // cli/installer/lockfile.ts
12111
- import { existsSync as existsSync30, readFileSync as readFileSync28, writeFileSync as writeFileSync21 } from "fs";
12112
- import { join as join29 } from "path";
12361
+ import { existsSync as existsSync31, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "fs";
12362
+ import { join as join30 } from "path";
12113
12363
  function lockPath(repoRoot2) {
12114
- return join29(repoRoot2, LOCK_FILE);
12364
+ return join30(repoRoot2, LOCK_FILE);
12115
12365
  }
12116
12366
  function writeLockfile(repoRoot2, entries) {
12117
12367
  const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
@@ -12129,7 +12379,7 @@ function writeLockfile(repoRoot2, entries) {
12129
12379
  ""
12130
12380
  ])
12131
12381
  ].join("\n");
12132
- writeFileSync21(lockPath(repoRoot2), body, "utf-8");
12382
+ writeFileSync22(lockPath(repoRoot2), body, "utf-8");
12133
12383
  }
12134
12384
  var LOCK_FILE;
12135
12385
  var init_lockfile = __esm({
@@ -12144,9 +12394,9 @@ var sync_exports = {};
12144
12394
  __export(sync_exports, {
12145
12395
  syncCommand: () => syncCommand
12146
12396
  });
12147
- import { existsSync as existsSync31, mkdirSync as mkdirSync17, readdirSync as readdirSync8, rmSync as rmSync3, writeFileSync as writeFileSync22 } from "fs";
12148
- import { homedir as homedir30 } from "os";
12149
- import { join as join30 } from "path";
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";
12150
12400
  function cacheKey(ref, version) {
12151
12401
  return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
12152
12402
  }
@@ -12177,8 +12427,8 @@ async function syncCommand(_args = []) {
12177
12427
  }
12178
12428
  const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
12179
12429
  const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
12180
- const cacheDir = join30(homedir30(), ".synkro", "cache", "packs");
12181
- if (!cloud) mkdirSync17(cacheDir, { recursive: true });
12430
+ const cacheDir = join31(homedir31(), ".synkro", "cache", "packs");
12431
+ if (!cloud) mkdirSync18(cacheDir, { recursive: true });
12182
12432
  console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
12183
12433
  const lock = [];
12184
12434
  const keptCacheFiles = /* @__PURE__ */ new Set();
@@ -12206,7 +12456,7 @@ async function syncCommand(_args = []) {
12206
12456
  if (!cloud) {
12207
12457
  const fname = cacheKey(ref, data.version);
12208
12458
  keptCacheFiles.add(fname);
12209
- writeFileSync22(join30(cacheDir, fname), JSON.stringify({
12459
+ writeFileSync23(join31(cacheDir, fname), JSON.stringify({
12210
12460
  ref,
12211
12461
  version: data.version,
12212
12462
  digest: data.digest,
@@ -12218,11 +12468,11 @@ async function syncCommand(_args = []) {
12218
12468
  const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
12219
12469
  console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
12220
12470
  }
12221
- if (!cloud && existsSync31(cacheDir)) {
12471
+ if (!cloud && existsSync32(cacheDir)) {
12222
12472
  for (const f of readdirSync8(cacheDir)) {
12223
12473
  if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
12224
12474
  try {
12225
- rmSync3(join30(cacheDir, f));
12475
+ rmSync4(join31(cacheDir, f));
12226
12476
  } catch {
12227
12477
  }
12228
12478
  }
@@ -12251,13 +12501,13 @@ var whoami_exports = {};
12251
12501
  __export(whoami_exports, {
12252
12502
  whoamiCommand: () => whoamiCommand
12253
12503
  });
12254
- import { readFileSync as readFileSync29, existsSync as existsSync32 } from "fs";
12255
- import { join as join31 } from "path";
12256
- import { homedir as homedir31 } from "os";
12504
+ import { readFileSync as readFileSync30, existsSync as existsSync33 } from "fs";
12505
+ import { join as join32 } from "path";
12506
+ import { homedir as homedir32 } from "os";
12257
12507
  function readConfigEnv3() {
12258
- if (!existsSync32(CONFIG_PATH8)) return {};
12508
+ if (!existsSync33(CONFIG_PATH8)) return {};
12259
12509
  const out = {};
12260
- for (const line of readFileSync29(CONFIG_PATH8, "utf-8").split("\n")) {
12510
+ for (const line of readFileSync30(CONFIG_PATH8, "utf-8").split("\n")) {
12261
12511
  const t = line.trim();
12262
12512
  if (!t || t.startsWith("#")) continue;
12263
12513
  const eq = t.indexOf("=");
@@ -12267,8 +12517,8 @@ function readConfigEnv3() {
12267
12517
  }
12268
12518
  function jwtStatus() {
12269
12519
  try {
12270
- if (!existsSync32(JWT_PATH2)) return { status: "none" };
12271
- const jwt2 = readFileSync29(JWT_PATH2, "utf-8").trim();
12520
+ if (!existsSync33(JWT_PATH2)) return { status: "none" };
12521
+ const jwt2 = readFileSync30(JWT_PATH2, "utf-8").trim();
12272
12522
  if (!jwt2) return { status: "none" };
12273
12523
  const payload = jwt2.split(".")[1];
12274
12524
  if (!payload) return { status: "valid" };
@@ -12326,13 +12576,13 @@ async function whoamiCommand(args2 = []) {
12326
12576
  console.log("synkro identity");
12327
12577
  for (const [k, v] of rows) console.log(` ${k.padEnd(width)} ${v}`);
12328
12578
  }
12329
- var SYNKRO_DIR14, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
12579
+ var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
12330
12580
  var init_whoami = __esm({
12331
12581
  "cli/commands/whoami.ts"() {
12332
12582
  "use strict";
12333
- SYNKRO_DIR14 = join31(homedir31(), ".synkro");
12334
- CONFIG_PATH8 = join31(SYNKRO_DIR14, "config.env");
12335
- JWT_PATH2 = join31(SYNKRO_DIR14, ".mcp-jwt");
12583
+ SYNKRO_DIR15 = join32(homedir32(), ".synkro");
12584
+ CONFIG_PATH8 = join32(SYNKRO_DIR15, "config.env");
12585
+ JWT_PATH2 = join32(SYNKRO_DIR15, ".mcp-jwt");
12336
12586
  GRADING_LABEL = {
12337
12587
  local: "on-device worker pool",
12338
12588
  cloud: "Synkro Cloud worker pool",
@@ -12377,12 +12627,12 @@ __export(linear_exports, {
12377
12627
  formatLinks: () => formatLinks,
12378
12628
  linearCommand: () => linearCommand
12379
12629
  });
12380
- import { readFileSync as readFileSync30 } from "fs";
12381
- import { homedir as homedir32 } from "os";
12382
- import { join as join32 } from "path";
12630
+ import { readFileSync as readFileSync31 } from "fs";
12631
+ import { homedir as homedir33 } from "os";
12632
+ import { join as join33 } from "path";
12383
12633
  function mcpJwt() {
12384
12634
  try {
12385
- return readFileSync30(join32(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
12635
+ return readFileSync31(join33(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
12386
12636
  } catch {
12387
12637
  return "";
12388
12638
  }
@@ -12417,11 +12667,11 @@ async function linearCommand(_args = []) {
12417
12667
  }
12418
12668
  console.log(formatLinks(links));
12419
12669
  }
12420
- var SYNKRO_DIR15, PORT2, BASE;
12670
+ var SYNKRO_DIR16, PORT2, BASE;
12421
12671
  var init_linear = __esm({
12422
12672
  "cli/commands/linear.ts"() {
12423
12673
  "use strict";
12424
- SYNKRO_DIR15 = join32(homedir32(), ".synkro");
12674
+ SYNKRO_DIR16 = join33(homedir33(), ".synkro");
12425
12675
  PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
12426
12676
  BASE = `http://127.0.0.1:${PORT2}`;
12427
12677
  }
@@ -12429,7 +12679,7 @@ var init_linear = __esm({
12429
12679
 
12430
12680
  // cli/scanning/cveReachability.ts
12431
12681
  import { parse } from "@babel/parser";
12432
- import { readFileSync as readFileSync31 } from "fs";
12682
+ import { readFileSync as readFileSync32 } from "fs";
12433
12683
  function walk(node, visit) {
12434
12684
  if (!node || typeof node.type !== "string") return;
12435
12685
  visit(node);
@@ -12570,10 +12820,10 @@ var init_cveReachability = __esm({
12570
12820
  });
12571
12821
 
12572
12822
  // cli/reachability/reachabilityScan.ts
12573
- import { spawnSync as spawnSync11, execFileSync as execFileSync5 } from "child_process";
12574
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync23, existsSync as existsSync33, readdirSync as readdirSync9 } from "fs";
12575
- import { join as join33 } from "path";
12576
- import { homedir as homedir33 } from "os";
12823
+ 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";
12577
12827
  import { createRequire } from "module";
12578
12828
  function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12579
12829
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
@@ -12590,7 +12840,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12590
12840
  }
12591
12841
  for (const e of ents) {
12592
12842
  if (files.length >= maxFiles) break;
12593
- const full = join33(dir, e.name);
12843
+ const full = join34(dir, e.name);
12594
12844
  if (e.isDirectory()) {
12595
12845
  if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
12596
12846
  continue;
@@ -12598,7 +12848,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12598
12848
  if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
12599
12849
  const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
12600
12850
  try {
12601
- const content = readFileSync32(full, "utf8");
12851
+ const content = readFileSync33(full, "utf8");
12602
12852
  if (content.length <= maxBytes) files.push({ path: rel, content });
12603
12853
  } catch {
12604
12854
  }
@@ -12617,12 +12867,12 @@ function cleanVersion(spec) {
12617
12867
  function gatherManifestVersions(repoRoot2) {
12618
12868
  const out = {};
12619
12869
  const dirs = [repoRoot2];
12620
- const pkgsDir = join33(repoRoot2, "packages");
12621
- if (existsSync33(pkgsDir)) {
12870
+ const pkgsDir = join34(repoRoot2, "packages");
12871
+ if (existsSync34(pkgsDir)) {
12622
12872
  try {
12623
12873
  for (const d of readdirSync9(pkgsDir)) {
12624
- const pd = join33(pkgsDir, d);
12625
- if (existsSync33(join33(pd, "package.json"))) dirs.push(pd);
12874
+ const pd = join34(pkgsDir, d);
12875
+ if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
12626
12876
  }
12627
12877
  } catch {
12628
12878
  }
@@ -12631,7 +12881,7 @@ function gatherManifestVersions(repoRoot2) {
12631
12881
  for (const dir of dirs) {
12632
12882
  let pkg;
12633
12883
  try {
12634
- pkg = JSON.parse(readFileSync32(join33(dir, "package.json"), "utf8"));
12884
+ pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
12635
12885
  } catch {
12636
12886
  continue;
12637
12887
  }
@@ -12651,28 +12901,28 @@ function findJelly(repoRoot2) {
12651
12901
  try {
12652
12902
  const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
12653
12903
  const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
12654
- const pkg = JSON.parse(readFileSync32(pkgJson, "utf8"));
12904
+ const pkg = JSON.parse(readFileSync33(pkgJson, "utf8"));
12655
12905
  const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
12656
12906
  if (bin) {
12657
- const p = join33(dir, bin);
12658
- if (existsSync33(p)) return p;
12907
+ const p = join34(dir, bin);
12908
+ if (existsSync34(p)) return p;
12659
12909
  }
12660
12910
  } catch {
12661
12911
  }
12662
12912
  for (const base of [repoRoot2, process.cwd()]) {
12663
- const b = join33(base, "node_modules", ".bin", "jelly");
12664
- if (existsSync33(b)) return b;
12913
+ const b = join34(base, "node_modules", ".bin", "jelly");
12914
+ if (existsSync34(b)) return b;
12665
12915
  }
12666
12916
  return null;
12667
12917
  }
12668
12918
  function findEntries(repoRoot2) {
12669
12919
  const dirs = [repoRoot2];
12670
- const pkgsDir = join33(repoRoot2, "packages");
12671
- if (existsSync33(pkgsDir)) {
12920
+ const pkgsDir = join34(repoRoot2, "packages");
12921
+ if (existsSync34(pkgsDir)) {
12672
12922
  try {
12673
12923
  for (const d of readdirSync9(pkgsDir)) {
12674
- const pd = join33(pkgsDir, d);
12675
- if (existsSync33(join33(pd, "package.json"))) dirs.push(pd);
12924
+ const pd = join34(pkgsDir, d);
12925
+ if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
12676
12926
  }
12677
12927
  } catch {
12678
12928
  }
@@ -12680,12 +12930,12 @@ function findEntries(repoRoot2) {
12680
12930
  const entries = [];
12681
12931
  for (const dir of dirs) {
12682
12932
  try {
12683
- const pkg = JSON.parse(readFileSync32(join33(dir, "package.json"), "utf8"));
12933
+ const pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
12684
12934
  const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
12685
12935
  for (const c of cands) {
12686
12936
  if (typeof c !== "string") continue;
12687
- const f = join33(dir, c);
12688
- if (existsSync33(f)) {
12937
+ const f = join34(dir, c);
12938
+ if (existsSync34(f)) {
12689
12939
  entries.push(f);
12690
12940
  break;
12691
12941
  }
@@ -12718,9 +12968,9 @@ function parseApiUsage(log) {
12718
12968
  }
12719
12969
  function runReachabilityScan(repoRoot2, opts = {}) {
12720
12970
  const commit = currentCommit(repoRoot2);
12721
- if (!opts.force && commit && existsSync33(REACHABILITY_PATH)) {
12971
+ if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
12722
12972
  try {
12723
- const prev = JSON.parse(readFileSync32(REACHABILITY_PATH, "utf8"));
12973
+ const prev = JSON.parse(readFileSync33(REACHABILITY_PATH, "utf8"));
12724
12974
  if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
12725
12975
  } catch {
12726
12976
  }
@@ -12771,7 +13021,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
12771
13021
  if (jelly) {
12772
13022
  const entries = findEntries(repoRoot2);
12773
13023
  if (entries.length > 0) {
12774
- const r = spawnSync11(
13024
+ const r = spawnSync12(
12775
13025
  process.execPath,
12776
13026
  [jelly, "-b", repoRoot2, "--api-usage", ...entries],
12777
13027
  { encoding: "utf8", timeout: opts.timeoutMs ?? 18e4, maxBuffer: 2e8 }
@@ -12809,7 +13059,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
12809
13059
  if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
12810
13060
  const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
12811
13061
  try {
12812
- writeFileSync23(REACHABILITY_PATH, JSON.stringify(file, null, 2));
13062
+ writeFileSync24(REACHABILITY_PATH, JSON.stringify(file, null, 2));
12813
13063
  } catch (e) {
12814
13064
  return { ok: false, reason: "write failed: " + String(e.message || e) };
12815
13065
  }
@@ -12821,7 +13071,7 @@ var init_reachabilityScan = __esm({
12821
13071
  "use strict";
12822
13072
  init_cveReachability();
12823
13073
  require2 = createRequire(import.meta.url);
12824
- REACHABILITY_PATH = join33(homedir33(), ".synkro", "reachability.json");
13074
+ REACHABILITY_PATH = join34(homedir34(), ".synkro", "reachability.json");
12825
13075
  }
12826
13076
  });
12827
13077
 
@@ -12830,15 +13080,15 @@ var reachabilityScan_exports = {};
12830
13080
  __export(reachabilityScan_exports, {
12831
13081
  reachabilityScanCommand: () => reachabilityScanCommand
12832
13082
  });
12833
- import { readFileSync as readFileSync33, existsSync as existsSync34 } from "fs";
12834
- import { join as join34 } from "path";
12835
- import { homedir as homedir34 } from "os";
13083
+ import { readFileSync as readFileSync34, existsSync as existsSync35 } from "fs";
13084
+ import { join as join35 } from "path";
13085
+ import { homedir as homedir35 } from "os";
12836
13086
  import { execFileSync as execFileSync6 } from "child_process";
12837
13087
  function readConfigEnv4() {
12838
- const p = join34(SYNKRO_DIR16, "config.env");
12839
- if (!existsSync34(p)) return {};
13088
+ const p = join35(SYNKRO_DIR17, "config.env");
13089
+ if (!existsSync35(p)) return {};
12840
13090
  const out = {};
12841
- for (const line of readFileSync33(p, "utf-8").split("\n")) {
13091
+ for (const line of readFileSync34(p, "utf-8").split("\n")) {
12842
13092
  const t = line.trim();
12843
13093
  if (!t || t.startsWith("#")) continue;
12844
13094
  const eq = t.indexOf("=");
@@ -12870,11 +13120,11 @@ async function pushToCloud(cfg, repo) {
12870
13120
  while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
12871
13121
  let jwt2 = "";
12872
13122
  try {
12873
- jwt2 = readFileSync33(join34(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
13123
+ jwt2 = readFileSync34(join35(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
12874
13124
  } catch {
12875
13125
  }
12876
- if (!jwt2 || !existsSync34(REACHABILITY_PATH)) return;
12877
- const body = readFileSync33(REACHABILITY_PATH, "utf-8");
13126
+ if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
13127
+ const body = readFileSync34(REACHABILITY_PATH, "utf-8");
12878
13128
  try {
12879
13129
  const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
12880
13130
  method: "POST",
@@ -12901,12 +13151,12 @@ async function reachabilityScanCommand(args2 = []) {
12901
13151
  const isCloud = cfg.SYNKRO_DEPLOY_LOCATION === "cloud" || cfg.SYNKRO_STORAGE_MODE === "cloud";
12902
13152
  if (isCloud) await pushToCloud(cfg, cfg.SYNKRO_CONNECTED_REPO || repoSlug(root));
12903
13153
  }
12904
- var SYNKRO_DIR16;
13154
+ var SYNKRO_DIR17;
12905
13155
  var init_reachabilityScan2 = __esm({
12906
13156
  "cli/commands/reachabilityScan.ts"() {
12907
13157
  "use strict";
12908
13158
  init_reachabilityScan();
12909
- SYNKRO_DIR16 = join34(homedir34(), ".synkro");
13159
+ SYNKRO_DIR17 = join35(homedir35(), ".synkro");
12910
13160
  }
12911
13161
  });
12912
13162
 
@@ -12935,9 +13185,9 @@ async function startCommand(rest = []) {
12935
13185
  assertDockerAvailable();
12936
13186
  const cfg = resolveWorkerConfig(rest);
12937
13187
  if (cfg.explicit) {
12938
- console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)
13188
+ console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor + ${cfg.codexWorkers} codex)
12939
13189
  `);
12940
- await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });
13190
+ await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, conductorProvider: cfg.conductorProvider, connectedRepo: resolveConnectedRepo() });
12941
13191
  const ready = await waitForContainerReady(6e4);
12942
13192
  if (!ready) {
12943
13193
  console.error("\n\u26A0 container did not pass /healthz within 60s");
@@ -12964,10 +13214,12 @@ async function updateCommand() {
12964
13214
  }
12965
13215
  const claudeWorkers = cfg.claudeWorkers ?? 8;
12966
13216
  const cursorWorkers = cfg.cursorWorkers ?? 0;
13217
+ const codexWorkers = cfg.codexWorkers ?? 0;
13218
+ const conductorProvider = cfg.conductorProvider;
12967
13219
  console.log("Synkro: updating to the latest container image");
12968
- console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor worker(s)
13220
+ console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex worker(s)
12969
13221
  `);
12970
- await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });
13222
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
12971
13223
  const ready = await waitForContainerReady(9e4);
12972
13224
  if (!ready) {
12973
13225
  console.error("\n\u26A0 container did not pass its health check within 90s \u2014 check: docker logs synkro-server");
@@ -12991,16 +13243,20 @@ async function restartCommand(rest = []) {
12991
13243
  const cfg = resolveWorkerConfig(rest);
12992
13244
  let claudeWorkers = cfg.claudeWorkers;
12993
13245
  let cursorWorkers = cfg.cursorWorkers;
13246
+ let codexWorkers = cfg.codexWorkers;
13247
+ let conductorProvider = cfg.conductorProvider;
12994
13248
  if (!cfg.explicit) {
12995
13249
  const reconciled = reconcileHarness();
12996
13250
  if (reconciled) {
12997
13251
  claudeWorkers = reconciled.claudeWorkers;
12998
13252
  cursorWorkers = reconciled.cursorWorkers;
13253
+ codexWorkers = reconciled.codexWorkers;
13254
+ conductorProvider = reconciled.conductorProvider;
12999
13255
  }
13000
13256
  }
13001
- console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor)
13257
+ console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)
13002
13258
  `);
13003
- await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });
13259
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
13004
13260
  const ready = await waitForContainerReady(6e4);
13005
13261
  if (!ready) {
13006
13262
  console.error("\n\u26A0 container did not pass /healthz within 60s");
@@ -13028,13 +13284,13 @@ var config_exports = {};
13028
13284
  __export(config_exports, {
13029
13285
  configCommand: () => configCommand
13030
13286
  });
13031
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync24, existsSync as existsSync35 } from "fs";
13032
- import { join as join35 } from "path";
13033
- import { homedir as homedir35 } from "os";
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";
13034
13290
  function readConfigEnv5() {
13035
- if (!existsSync35(CONFIG_PATH9)) return {};
13291
+ if (!existsSync36(CONFIG_PATH9)) return {};
13036
13292
  const out = {};
13037
- for (const line of readFileSync34(CONFIG_PATH9, "utf-8").split("\n")) {
13293
+ for (const line of readFileSync35(CONFIG_PATH9, "utf-8").split("\n")) {
13038
13294
  const t = line.trim();
13039
13295
  if (!t || t.startsWith("#")) continue;
13040
13296
  const eq = t.indexOf("=");
@@ -13043,11 +13299,11 @@ function readConfigEnv5() {
13043
13299
  return out;
13044
13300
  }
13045
13301
  function updateConfigValue(key, value) {
13046
- if (!existsSync35(CONFIG_PATH9)) {
13302
+ if (!existsSync36(CONFIG_PATH9)) {
13047
13303
  console.error("No config found. Run `synkro install` first.");
13048
13304
  process.exit(1);
13049
13305
  }
13050
- const lines = readFileSync34(CONFIG_PATH9, "utf-8").split("\n");
13306
+ const lines = readFileSync35(CONFIG_PATH9, "utf-8").split("\n");
13051
13307
  const pattern = new RegExp(`^${key}=`);
13052
13308
  let found = false;
13053
13309
  const updated = lines.map((line) => {
@@ -13058,7 +13314,7 @@ function updateConfigValue(key, value) {
13058
13314
  return line;
13059
13315
  });
13060
13316
  if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
13061
- writeFileSync24(CONFIG_PATH9, updated.join("\n"), "utf-8");
13317
+ writeFileSync25(CONFIG_PATH9, updated.join("\n"), "utf-8");
13062
13318
  }
13063
13319
  function resolveInferenceMode(cfg) {
13064
13320
  if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
@@ -13210,14 +13466,14 @@ To change:`);
13210
13466
  }
13211
13467
  if (inferenceValue !== "cloud") await reconcileContainer();
13212
13468
  }
13213
- var SYNKRO_DIR17, CONFIG_PATH9;
13469
+ var SYNKRO_DIR18, CONFIG_PATH9;
13214
13470
  var init_config = __esm({
13215
13471
  "cli/commands/config.ts"() {
13216
13472
  "use strict";
13217
13473
  init_stub();
13218
13474
  init_optout();
13219
- SYNKRO_DIR17 = join35(homedir35(), ".synkro");
13220
- CONFIG_PATH9 = join35(SYNKRO_DIR17, "config.env");
13475
+ SYNKRO_DIR18 = join36(homedir36(), ".synkro");
13476
+ CONFIG_PATH9 = join36(SYNKRO_DIR18, "config.env");
13221
13477
  }
13222
13478
  });
13223
13479
 
@@ -13406,14 +13662,14 @@ Usage:
13406
13662
  });
13407
13663
 
13408
13664
  // cli/bootstrap.js
13409
- import { readFileSync as readFileSync35, existsSync as existsSync36 } from "fs";
13665
+ import { readFileSync as readFileSync36, existsSync as existsSync37 } from "fs";
13410
13666
  import { resolve as resolve5 } from "path";
13411
13667
  var envCandidates = [
13412
13668
  resolve5(process.env.HOME ?? "", ".synkro", "config.env")
13413
13669
  ];
13414
13670
  for (const envPath of envCandidates) {
13415
- if (!existsSync36(envPath)) continue;
13416
- const envContent = readFileSync35(envPath, "utf-8");
13671
+ if (!existsSync37(envPath)) continue;
13672
+ const envContent = readFileSync36(envPath, "utf-8");
13417
13673
  for (const line of envContent.split("\n")) {
13418
13674
  const trimmed = line.trim();
13419
13675
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -13430,7 +13686,7 @@ var subArgs = args.slice(1);
13430
13686
  var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
13431
13687
  var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
13432
13688
  function printVersion() {
13433
- console.log("1.7.86");
13689
+ console.log("1.7.88");
13434
13690
  }
13435
13691
  function printHelp2() {
13436
13692
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents