@synkro-sh/cli 1.7.86 → 1.7.87

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.87";
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;
@@ -5490,19 +5490,24 @@ import { join as join14 } from "path";
5490
5490
  import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
5491
5491
  function splitWorkers(total, providers) {
5492
5492
  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 };
5493
+ const selected = ["claude_code", "cursor", "codex"].filter((provider) => providers.includes(provider));
5494
+ const active = selected.length > 0 ? selected : ["claude_code"];
5495
+ const base = Math.floor(t / active.length);
5496
+ let remainder = t % active.length;
5497
+ const counts = { claudeWorkers: 0, cursorWorkers: 0, codexWorkers: 0 };
5498
+ for (const provider of active) {
5499
+ const count = base + (remainder-- > 0 ? 1 : 0);
5500
+ if (provider === "claude_code") counts.claudeWorkers = count;
5501
+ else if (provider === "cursor") counts.cursorWorkers = count;
5502
+ else counts.codexWorkers = count;
5503
+ }
5504
+ return counts;
5501
5505
  }
5502
5506
  function normalizeProvider(p) {
5503
5507
  const v = p.trim().toLowerCase();
5504
5508
  if (v === "claude" || v === "claude-code" || v === "claude_code" || v === "cc") return "claude_code";
5505
5509
  if (v === "cursor") return "cursor";
5510
+ if (v === "codex" || v === "openai-codex" || v === "openai_codex") return "codex";
5506
5511
  return null;
5507
5512
  }
5508
5513
  function resolveGraderPool(parsed) {
@@ -5516,8 +5521,9 @@ function resolveGraderPool(parsed) {
5516
5521
  const np = normalizeProvider(k);
5517
5522
  if (np === "claude_code") counts.claude_code = Math.max(0, Math.floor(v));
5518
5523
  else if (np === "cursor") counts.cursor = Math.max(0, Math.floor(v));
5524
+ else if (np === "codex") counts.codex = Math.max(0, Math.floor(v));
5519
5525
  else if (flagUnknown) {
5520
- warnings.push(`synkro.toml: ${where}.${k} is not a known provider (use claude-code or cursor) \u2014 ignored`);
5526
+ warnings.push(`synkro.toml: ${where}.${k} is not a known provider (use claude-code, cursor, or codex) \u2014 ignored`);
5521
5527
  }
5522
5528
  }
5523
5529
  };
@@ -5528,10 +5534,10 @@ function resolveGraderPool(parsed) {
5528
5534
  if (rawPool != null && rawPool !== "auto") {
5529
5535
  const np = normalizeProvider(String(rawPool));
5530
5536
  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`);
5537
+ else warnings.push(`synkro.toml: grader.pool="${rawPool}" is not recognized (use auto | claude-code | cursor | codex) \u2014 falling back to auto`);
5532
5538
  }
5533
- if (counts.claude_code != null || counts.cursor != null) {
5534
- return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, pool, warnings };
5539
+ if (counts.claude_code != null || counts.cursor != null || counts.codex != null) {
5540
+ return { claudeWorkers: counts.claude_code, cursorWorkers: counts.cursor, codexWorkers: counts.codex, pool, warnings };
5535
5541
  }
5536
5542
  return { pool, warnings };
5537
5543
  }
@@ -5624,15 +5630,18 @@ function resolveWorkerConfig(rest) {
5624
5630
  if (provs.length === 0) {
5625
5631
  const sc = readSynkroFileConfig();
5626
5632
  for (const w of sc.warnings) console.warn(` \u26A0 ${w}`);
5627
- if (sc.claudeWorkers != null || sc.cursorWorkers != null) {
5633
+ if (sc.claudeWorkers != null || sc.cursorWorkers != null || sc.codexWorkers != null) {
5628
5634
  const cw = sc.claudeWorkers || 0;
5629
5635
  const curw = sc.cursorWorkers || 0;
5630
- if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw, explicit };
5636
+ const codw = sc.codexWorkers || 0;
5637
+ if (cw + curw + codw > 0) return { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw, explicit };
5631
5638
  }
5632
5639
  if (sc.pool === "cursor") {
5633
5640
  provs = ["cursor"];
5634
5641
  } else if (sc.pool === "claude_code") {
5635
5642
  provs = ["claude_code"];
5643
+ } else if (sc.pool === "codex") {
5644
+ provs = ["codex"];
5636
5645
  } else {
5637
5646
  provs = detectAgents().map((a) => a.kind);
5638
5647
  if (provs.length === 0) provs = ["claude_code"];
@@ -5710,7 +5719,14 @@ async function dockerInstall(opts = {}) {
5710
5719
  const image = imageTag();
5711
5720
  const claudeWorkers = opts.claudeWorkers ?? opts.workersPerPool ?? 8;
5712
5721
  const cursorWorkers = opts.cursorWorkers ?? 0;
5713
- const totalWorkers = claudeWorkers + cursorWorkers;
5722
+ const codexWorkers = opts.codexWorkers ?? 0;
5723
+ const totalWorkers = claudeWorkers + cursorWorkers + codexWorkers;
5724
+ const codexHomeDir = opts.codexHomeDir ?? join14(SYNKRO_DIR7, "codex-local-session");
5725
+ if (codexWorkers > 0 && !existsSync19(join14(codexHomeDir, "auth.json"))) {
5726
+ throw new DockerInstallError(
5727
+ "Codex grader credentials are missing. Re-run `synkro install` to authorize an isolated Codex session."
5728
+ );
5729
+ }
5714
5730
  mkdirSync12(PGDATA_PATH, { recursive: true });
5715
5731
  mkdirSync12(BACKUP_DIR, { recursive: true });
5716
5732
  mkdirSync12(CLAUDE_HOST_STATE_DIR, { recursive: true });
@@ -5806,12 +5822,15 @@ async function dockerInstall(opts = {}) {
5806
5822
  // Cursor creds — mounted RW so the in-container refresher can rotate the
5807
5823
  // access token in place. Only mounted when the install includes Cursor.
5808
5824
  ...cursorWorkers > 0 ? ["-v", `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`] : [],
5825
+ ...codexWorkers > 0 ? ["-v", `${codexHomeDir}:/home/synkro/.codex:rw`] : [],
5809
5826
  "-e",
5810
5827
  `WORKERS_PER_POOL=${totalWorkers}`,
5811
5828
  "-e",
5812
5829
  `CLAUDE_WORKERS=${claudeWorkers}`,
5813
5830
  "-e",
5814
5831
  `CURSOR_WORKERS=${cursorWorkers}`,
5832
+ "-e",
5833
+ `CODEX_WORKERS=${codexWorkers}`,
5815
5834
  // Pass through the batch-size lever if the operator set it. Defaults
5816
5835
  // inside the container to 5; clamped to [1, 20] by synkro-server.ts.
5817
5836
  ...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
@@ -5929,6 +5948,7 @@ function readContainerConfig() {
5929
5948
  return {
5930
5949
  claudeWorkers: num(get("CLAUDE_WORKERS")),
5931
5950
  cursorWorkers: num(get("CURSOR_WORKERS")),
5951
+ codexWorkers: num(get("CODEX_WORKERS")),
5932
5952
  connectedRepo: get("SYNKRO_CONNECTED_REPO") || void 0
5933
5953
  };
5934
5954
  }
@@ -6165,6 +6185,119 @@ var init_setupToken = __esm({
6165
6185
  }
6166
6186
  });
6167
6187
 
6188
+ // cli/local-cc/codexCloudSetup.ts
6189
+ import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
6190
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
6191
+ import { homedir as homedir18 } from "os";
6192
+ import { join as join16 } from "path";
6193
+ function findCodexBinary() {
6194
+ if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
6195
+ const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
6196
+ const p = (r.stdout || "").trim().split("\n")[0];
6197
+ return p || null;
6198
+ }
6199
+ function runCodexLogin(codexBin, codexHome) {
6200
+ mkdirSync13(codexHome, { recursive: true, mode: 448 });
6201
+ writeFileSync14(join16(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
6202
+ return new Promise((resolve6, reject) => {
6203
+ const proc = nodeSpawn2(codexBin, ["login"], {
6204
+ stdio: "inherit",
6205
+ env: { ...process.env, CODEX_HOME: codexHome }
6206
+ });
6207
+ proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
6208
+ proc.on("close", (code) => code === 0 ? resolve6() : reject(new Error(`codex login exited with code ${code}`)));
6209
+ });
6210
+ }
6211
+ async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
6212
+ try {
6213
+ const status = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/status`, {
6214
+ headers: { Authorization: `Bearer ${bearerToken}` }
6215
+ });
6216
+ const body = await status.json().catch(() => ({}));
6217
+ if (status.ok && body.connected) {
6218
+ onStatus?.("\u2713 Existing Codex cloud session is connected.");
6219
+ return { ok: true };
6220
+ }
6221
+ } catch {
6222
+ }
6223
+ const codexBin = findCodexBinary();
6224
+ if (!codexBin) {
6225
+ 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.)" };
6226
+ }
6227
+ onStatus?.("Opening your browser to authorize your Codex subscription for Synkro Cloud\u2026");
6228
+ try {
6229
+ await runCodexLogin(codexBin, CODEX_CLOUD_HOME);
6230
+ } catch (e) {
6231
+ return { ok: false, error: `Codex login failed: ${e.message}` };
6232
+ }
6233
+ const authPath = join16(CODEX_CLOUD_HOME, "auth.json");
6234
+ if (!existsSync20(authPath)) {
6235
+ return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
6236
+ }
6237
+ let auth;
6238
+ try {
6239
+ auth = JSON.parse(readFileSync18(authPath, "utf-8"));
6240
+ } catch (e) {
6241
+ return { ok: false, error: `could not read codex auth.json: ${e.message}` };
6242
+ }
6243
+ if (!auth || typeof auth !== "object" || !auth.tokens?.refresh_token) {
6244
+ return { ok: false, error: "codex auth.json has no refresh token \u2014 cloud refresh would fail. Re-run login." };
6245
+ }
6246
+ onStatus?.("Connecting your Codex session to Synkro Cloud\u2026");
6247
+ let resp;
6248
+ try {
6249
+ resp = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/seed`, {
6250
+ method: "POST",
6251
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
6252
+ body: JSON.stringify({ auth })
6253
+ });
6254
+ } catch (e) {
6255
+ return { ok: false, error: `failed to reach Synkro API: ${e.message}` };
6256
+ }
6257
+ if (!resp.ok) {
6258
+ const detail = await resp.text().catch(() => "");
6259
+ return { ok: false, error: `seed rejected (${resp.status}): ${detail.slice(0, 200)}` };
6260
+ }
6261
+ try {
6262
+ rmSync(CODEX_CLOUD_HOME, { recursive: true, force: true });
6263
+ } catch {
6264
+ }
6265
+ onStatus?.("\u2713 Codex subscription connected to Synkro Cloud.");
6266
+ return { ok: true };
6267
+ }
6268
+ async function setupCodexLocal(onStatus) {
6269
+ const codexBin = findCodexBinary();
6270
+ if (!codexBin) {
6271
+ return { ok: false, error: "Codex CLI not found on PATH. Install Codex, then re-run `synkro install`." };
6272
+ }
6273
+ const authPath = join16(CODEX_LOCAL_HOME, "auth.json");
6274
+ if (!existsSync20(authPath)) {
6275
+ onStatus?.("Opening your browser to authorize an isolated Codex session for the local grader\u2026");
6276
+ try {
6277
+ await runCodexLogin(codexBin, CODEX_LOCAL_HOME);
6278
+ } catch (e) {
6279
+ return { ok: false, error: `Codex login failed: ${e.message}` };
6280
+ }
6281
+ }
6282
+ try {
6283
+ const auth = JSON.parse(readFileSync18(authPath, "utf-8"));
6284
+ if (!auth.tokens?.refresh_token) throw new Error("auth.json has no refresh token");
6285
+ } catch (e) {
6286
+ return { ok: false, error: `Codex local grader auth is invalid: ${e.message}` };
6287
+ }
6288
+ onStatus?.("\u2713 Codex session ready for the local grader.");
6289
+ return { ok: true, home: CODEX_LOCAL_HOME };
6290
+ }
6291
+ var SYNKRO_DIR9, CODEX_CLOUD_HOME, CODEX_LOCAL_HOME;
6292
+ var init_codexCloudSetup = __esm({
6293
+ "cli/local-cc/codexCloudSetup.ts"() {
6294
+ "use strict";
6295
+ SYNKRO_DIR9 = join16(homedir18(), ".synkro");
6296
+ CODEX_CLOUD_HOME = join16(SYNKRO_DIR9, "codex-cloud-session");
6297
+ CODEX_LOCAL_HOME = join16(SYNKRO_DIR9, "codex-local-session");
6298
+ }
6299
+ });
6300
+
6168
6301
  // cli/local-cc/ptyShim.ts
6169
6302
  var ptyShim_exports = {};
6170
6303
  __export(ptyShim_exports, {
@@ -6181,26 +6314,26 @@ __export(ptyShim_exports, {
6181
6314
  uninstallPtyShim: () => uninstallPtyShim
6182
6315
  });
6183
6316
  import {
6184
- existsSync as existsSync20,
6185
- mkdirSync as mkdirSync13,
6186
- writeFileSync as writeFileSync14,
6317
+ existsSync as existsSync21,
6318
+ mkdirSync as mkdirSync14,
6319
+ writeFileSync as writeFileSync15,
6187
6320
  chmodSync as chmodSync3,
6188
- readFileSync as readFileSync18,
6189
- rmSync,
6321
+ readFileSync as readFileSync19,
6322
+ rmSync as rmSync2,
6190
6323
  realpathSync,
6191
6324
  symlinkSync,
6192
6325
  lstatSync,
6193
6326
  readdirSync as readdirSync3
6194
6327
  } 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";
6328
+ import { homedir as homedir19 } from "os";
6329
+ import { join as join17 } from "path";
6330
+ import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
6198
6331
  function rcFiles() {
6199
- const h = homedir18();
6200
- return [join16(h, ".zshrc"), join16(h, ".bashrc"), join16(h, ".bash_profile")];
6332
+ const h = homedir19();
6333
+ return [join17(h, ".zshrc"), join17(h, ".bashrc"), join17(h, ".bash_profile")];
6201
6334
  }
6202
6335
  function resolveRealClaude() {
6203
- const r = spawnSync4("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6336
+ const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6204
6337
  const p = (r.stdout || "").trim();
6205
6338
  if (p) {
6206
6339
  try {
@@ -6209,8 +6342,8 @@ function resolveRealClaude() {
6209
6342
  return p;
6210
6343
  }
6211
6344
  }
6212
- for (const c of [join16(homedir18(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
6213
- if (existsSync20(c)) {
6345
+ for (const c of [join17(homedir19(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
6346
+ if (existsSync21(c)) {
6214
6347
  try {
6215
6348
  return realpathSync(c);
6216
6349
  } catch {
@@ -6221,11 +6354,11 @@ function resolveRealClaude() {
6221
6354
  return "claude";
6222
6355
  }
6223
6356
  function findClaudeLink() {
6224
- const r = spawnSync4("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6357
+ const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6225
6358
  let linkPath = (r.stdout || "").trim();
6226
6359
  if (!linkPath) {
6227
- const c = join16(homedir18(), ".local", "bin", "claude");
6228
- if (existsSync20(c)) linkPath = c;
6360
+ const c = join17(homedir19(), ".local", "bin", "claude");
6361
+ if (existsSync21(c)) linkPath = c;
6229
6362
  else return null;
6230
6363
  }
6231
6364
  if (linkPath === SHIM_PATH) return null;
@@ -6238,7 +6371,7 @@ function findClaudeLink() {
6238
6371
  }
6239
6372
  function isOurShim(path) {
6240
6373
  try {
6241
- return readFileSync18(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
6374
+ return readFileSync19(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
6242
6375
  } catch {
6243
6376
  return false;
6244
6377
  }
@@ -6251,7 +6384,7 @@ function shadowClaude() {
6251
6384
  }
6252
6385
  const { linkPath, realTarget } = found;
6253
6386
  if (isOurShim(linkPath)) {
6254
- console.log(` \xB7 ${linkPath.replace(homedir18(), "~")} already shadowed`);
6387
+ console.log(` \xB7 ${linkPath.replace(homedir19(), "~")} already shadowed`);
6255
6388
  return;
6256
6389
  }
6257
6390
  let wasSymlink = false;
@@ -6261,14 +6394,14 @@ function shadowClaude() {
6261
6394
  }
6262
6395
  const state = { linkPath, realTarget, wasSymlink };
6263
6396
  try {
6264
- writeFileSync14(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
6397
+ writeFileSync15(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
6265
6398
  } catch {
6266
6399
  }
6267
6400
  try {
6268
- rmSync(linkPath, { force: true });
6269
- writeFileSync14(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
6401
+ rmSync2(linkPath, { force: true });
6402
+ writeFileSync15(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
6270
6403
  chmodSync3(linkPath, 493);
6271
- console.log(` \u2713 shadowed ${linkPath.replace(homedir18(), "~")} \u2192 shim (real: ${realTarget.replace(homedir18(), "~")})`);
6404
+ console.log(` \u2713 shadowed ${linkPath.replace(homedir19(), "~")} \u2192 shim (real: ${realTarget.replace(homedir19(), "~")})`);
6272
6405
  } catch (e) {
6273
6406
  console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
6274
6407
  }
@@ -6276,16 +6409,16 @@ function shadowClaude() {
6276
6409
  function unshadowClaude() {
6277
6410
  let state;
6278
6411
  try {
6279
- state = JSON.parse(readFileSync18(SHADOW_STATE_FILE, "utf-8"));
6412
+ state = JSON.parse(readFileSync19(SHADOW_STATE_FILE, "utf-8"));
6280
6413
  } catch {
6281
6414
  return;
6282
6415
  }
6283
6416
  if (!state?.linkPath) return;
6284
6417
  try {
6285
- if (existsSync20(state.linkPath) && !isOurShim(state.linkPath)) return;
6286
- rmSync(state.linkPath, { force: true });
6418
+ if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
6419
+ rmSync2(state.linkPath, { force: true });
6287
6420
  symlinkSync(state.realTarget, state.linkPath);
6288
- console.log(`\u2713 restored ${state.linkPath.replace(homedir18(), "~")} \u2192 ${state.realTarget.replace(homedir18(), "~")}`);
6421
+ console.log(`\u2713 restored ${state.linkPath.replace(homedir19(), "~")} \u2192 ${state.realTarget.replace(homedir19(), "~")}`);
6289
6422
  } catch (e) {
6290
6423
  console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
6291
6424
  }
@@ -6293,16 +6426,16 @@ function unshadowClaude() {
6293
6426
  function addPathBlock() {
6294
6427
  const touched = [];
6295
6428
  for (const rc of rcFiles()) {
6296
- if (!existsSync20(rc) && rc.endsWith(".bash_profile")) continue;
6429
+ if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
6297
6430
  let body = "";
6298
6431
  try {
6299
- body = readFileSync18(rc, "utf-8");
6432
+ body = readFileSync19(rc, "utf-8");
6300
6433
  } catch {
6301
6434
  }
6302
6435
  const cleaned = stripBlock(body);
6303
6436
  const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
6304
6437
  try {
6305
- writeFileSync14(rc, next, "utf-8");
6438
+ writeFileSync15(rc, next, "utf-8");
6306
6439
  touched.push(rc);
6307
6440
  } catch {
6308
6441
  }
@@ -6318,15 +6451,15 @@ function escapeRe(s) {
6318
6451
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6319
6452
  }
6320
6453
  function installPtyShim() {
6321
- mkdirSync13(SHIM_BIN_DIR, { recursive: true });
6322
- mkdirSync13(PTY_STATE_DIR, { recursive: true });
6454
+ mkdirSync14(SHIM_BIN_DIR, { recursive: true });
6455
+ mkdirSync14(PTY_STATE_DIR, { recursive: true });
6323
6456
  const real = resolveRealClaude();
6324
- writeFileSync14(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
6457
+ writeFileSync15(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
6325
6458
  chmodSync3(SHIM_PATH, 493);
6326
6459
  const touched = addPathBlock();
6327
6460
  console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
6328
6461
  shadowClaude();
6329
- if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir18(), "~")).join(", ")}`);
6462
+ if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir19(), "~")).join(", ")}`);
6330
6463
  }
6331
6464
  function uninstallPtyShim() {
6332
6465
  try {
@@ -6334,30 +6467,30 @@ function uninstallPtyShim() {
6334
6467
  } catch {
6335
6468
  }
6336
6469
  try {
6337
- const ls = spawnSync4("tmux", ["ls", "-F", "#{session_name}"], { encoding: "utf-8" });
6470
+ const ls = spawnSync5("tmux", ["ls", "-F", "#{session_name}"], { encoding: "utf-8" });
6338
6471
  for (const s of (ls.stdout || "").split("\n")) {
6339
- if (s.startsWith(SHIM_SESSION_PREFIX)) spawnSync4("tmux", ["kill-session", "-t", `=${s}`], { encoding: "utf-8" });
6472
+ if (s.startsWith(SHIM_SESSION_PREFIX)) spawnSync5("tmux", ["kill-session", "-t", `=${s}`], { encoding: "utf-8" });
6340
6473
  }
6341
6474
  } catch {
6342
6475
  }
6343
6476
  const cleaned = [];
6344
6477
  for (const rc of rcFiles()) {
6345
- if (!existsSync20(rc)) continue;
6478
+ if (!existsSync21(rc)) continue;
6346
6479
  try {
6347
- const body = readFileSync18(rc, "utf-8");
6480
+ const body = readFileSync19(rc, "utf-8");
6348
6481
  if (body.includes(RC_BEGIN)) {
6349
- writeFileSync14(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
6350
- cleaned.push(rc.replace(homedir18(), "~"));
6482
+ writeFileSync15(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
6483
+ cleaned.push(rc.replace(homedir19(), "~"));
6351
6484
  }
6352
6485
  } catch {
6353
6486
  }
6354
6487
  }
6355
6488
  try {
6356
- rmSync(SHIM_BIN_DIR, { recursive: true, force: true });
6489
+ rmSync2(SHIM_BIN_DIR, { recursive: true, force: true });
6357
6490
  } catch {
6358
6491
  }
6359
6492
  try {
6360
- rmSync(PTY_STATE_DIR, { recursive: true, force: true });
6493
+ rmSync2(PTY_STATE_DIR, { recursive: true, force: true });
6361
6494
  } catch {
6362
6495
  }
6363
6496
  console.log(`\u2713 removed pty routing shim${cleaned.length ? ` (cleaned PATH in: ${cleaned.join(", ")})` : ""}`);
@@ -6379,12 +6512,12 @@ function listSessions(opts = {}) {
6379
6512
  const out = [];
6380
6513
  for (const f of files) {
6381
6514
  try {
6382
- const r = JSON.parse(readFileSync18(join16(SESSIONS_DIR, f), "utf-8"));
6515
+ const r = JSON.parse(readFileSync19(join17(SESSIONS_DIR, f), "utf-8"));
6383
6516
  if (!r || !r.session_id) continue;
6384
6517
  if (liveOnly) {
6385
6518
  const s = r.tmux_session;
6386
6519
  if (!s || !isOurSession(s)) continue;
6387
- if (spawnSync4("tmux", ["has-session", "-t", `=${s}`], { encoding: "utf-8" }).status !== 0) continue;
6520
+ if (spawnSync5("tmux", ["has-session", "-t", `=${s}`], { encoding: "utf-8" }).status !== 0) continue;
6388
6521
  }
6389
6522
  out.push(r);
6390
6523
  } catch {
@@ -6398,7 +6531,7 @@ function currentTmuxSession() {
6398
6531
  try {
6399
6532
  const pane = process.env.TMUX_PANE || "";
6400
6533
  const args2 = pane ? ["display-message", "-p", "-t", pane, "#{session_name}"] : ["display-message", "-p", "#{session_name}"];
6401
- const r = spawnSync4("tmux", args2, { encoding: "utf-8" });
6534
+ const r = spawnSync5("tmux", args2, { encoding: "utf-8" });
6402
6535
  if (r.status === 0) return (r.stdout || "").trim();
6403
6536
  } catch {
6404
6537
  }
@@ -6408,7 +6541,7 @@ function resolveTargetSession(override) {
6408
6541
  const own = currentTmuxSession();
6409
6542
  const fromFile = (() => {
6410
6543
  try {
6411
- return readFileSync18(ACTIVE_SESSION_FILE, "utf-8").trim();
6544
+ return readFileSync19(ACTIVE_SESSION_FILE, "utf-8").trim();
6412
6545
  } catch {
6413
6546
  return "";
6414
6547
  }
@@ -6429,14 +6562,14 @@ function injectModel(model, sessionOverride) {
6429
6562
  console.error(`synkro route: refusing suspicious session token: ${session}`);
6430
6563
  return false;
6431
6564
  }
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" });
6565
+ if (spawnSync5("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return false;
6566
+ const sendKeys = (...a) => spawnSync5("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
6434
6567
  sendKeys("Escape");
6435
6568
  sendKeys("-l", `/model ${model}`);
6436
6569
  sendKeys("Enter");
6437
- const pidFile = join16(PTY_STATE_DIR, `poll-${session}.pid`);
6570
+ const pidFile = join17(PTY_STATE_DIR, `poll-${session}.pid`);
6438
6571
  try {
6439
- mkdirSync13(PTY_STATE_DIR, { recursive: true });
6572
+ mkdirSync14(PTY_STATE_DIR, { recursive: true });
6440
6573
  } catch {
6441
6574
  }
6442
6575
  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 +6577,17 @@ function injectModel(model, sessionOverride) {
6444
6577
  child.unref();
6445
6578
  return true;
6446
6579
  }
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;
6580
+ 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
6581
  var init_ptyShim = __esm({
6449
6582
  "cli/local-cc/ptyShim.ts"() {
6450
6583
  "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");
6584
+ SYNKRO_DIR10 = join17(homedir19(), ".synkro");
6585
+ SHIM_BIN_DIR = join17(SYNKRO_DIR10, "bin");
6586
+ SHIM_PATH = join17(SHIM_BIN_DIR, "claude");
6587
+ PTY_STATE_DIR = join17(SYNKRO_DIR10, "pty");
6588
+ ACTIVE_SESSION_FILE = join17(PTY_STATE_DIR, "active");
6589
+ SHADOW_STATE_FILE = join17(PTY_STATE_DIR, "shadow.json");
6590
+ SESSIONS_DIR = join17(PTY_STATE_DIR, "sessions");
6458
6591
  SHIM_SESSION_PREFIX = "synkro-cc-";
6459
6592
  RC_BEGIN = "# >>> synkro pty shim (managed \u2014 do not edit) >>>";
6460
6593
  RC_END = "# <<< synkro pty shim <<<";
@@ -6527,9 +6660,9 @@ __export(install_exports, {
6527
6660
  syncSkillFiles: () => syncSkillFiles,
6528
6661
  writeHookScripts: () => writeHookScripts
6529
6662
  });
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";
6663
+ 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";
6664
+ import { homedir as homedir20 } from "os";
6665
+ import { join as join18, isAbsolute, resolve as resolve4 } from "path";
6533
6666
  import { execSync as execSync4, spawn as spawn4 } from "child_process";
6534
6667
  import { createInterface as createInterface2 } from "readline";
6535
6668
  import { createHash as createHash2 } from "crypto";
@@ -6625,7 +6758,7 @@ async function promptDeployLocation(current = "local") {
6625
6758
  `Where should Synkro run?
6626
6759
  local \u2014 a grading container on this machine (Docker)
6627
6760
  cloud \u2014 a private container hosted by Synkro
6628
- Both use your own Claude key. Choose [${current}] / ${other}: `,
6761
+ Each worker uses the account credentials you authorize. Choose [${current}] / ${other}: `,
6629
6762
  (answer) => {
6630
6763
  rl.close();
6631
6764
  const a = answer.trim().toLowerCase();
@@ -6656,38 +6789,38 @@ async function promptTranscriptSources(wantCC, wantCursor, wantCodex) {
6656
6789
  }
6657
6790
  }
6658
6791
  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 });
6792
+ mkdirSync15(SYNKRO_DIR11, { recursive: true });
6793
+ mkdirSync15(HOOKS_DIR, { recursive: true });
6794
+ mkdirSync15(BIN_DIR, { recursive: true });
6795
+ mkdirSync15(OFFSETS_DIR, { recursive: true });
6796
+ mkdirSync15(join18(SYNKRO_DIR11, "sessions"), { recursive: true });
6664
6797
  }
6665
6798
  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");
6799
+ const installExtractCorePath = join18(HOOKS_DIR, "installExtractCore.ts");
6800
+ const bashScriptPath = join18(HOOKS_DIR, "cc-bash-judge.ts");
6801
+ const skillJudgeScriptPath = join18(HOOKS_DIR, "cc-skill-judge.ts");
6802
+ const cursorSkillJudgePath = join18(HOOKS_DIR, "cursor-skill-judge.ts");
6803
+ const bashFollowupScriptPath = join18(HOOKS_DIR, "cc-bash-followup.ts");
6804
+ const editPrecheckScriptPath = join18(HOOKS_DIR, "cc-edit-precheck.ts");
6805
+ const cwePrecheckScriptPath = join18(HOOKS_DIR, "cc-cwe-precheck.ts");
6806
+ const cvePrecheckScriptPath = join18(HOOKS_DIR, "cc-cve-precheck.ts");
6807
+ const planJudgeScriptPath = join18(HOOKS_DIR, "cc-plan-judge.ts");
6808
+ const agentJudgeScriptPath = join18(HOOKS_DIR, "cc-agent-judge.ts");
6809
+ const stopSummaryScriptPath = join18(HOOKS_DIR, "cc-stop-summary.ts");
6810
+ const sessionStartScriptPath = join18(HOOKS_DIR, "cc-session-start.ts");
6811
+ const transcriptSyncScriptPath = join18(HOOKS_DIR, "cc-transcript-sync.ts");
6812
+ const userPromptSubmitScriptPath = join18(HOOKS_DIR, "cc-user-prompt-submit.ts");
6813
+ const promptRouteScriptPath = join18(HOOKS_DIR, "cc-prompt-route.ts");
6814
+ const commonScriptPath = join18(HOOKS_DIR, "_synkro-common.ts");
6815
+ const commonBashScriptPath = join18(HOOKS_DIR, "_synkro-common.sh");
6816
+ const installScanScriptPath = join18(HOOKS_DIR, "cc-install-scan.ts");
6817
+ const cursorBashJudgePath = join18(HOOKS_DIR, "cursor-bash-judge.ts");
6818
+ const cursorEditCapturePath = join18(HOOKS_DIR, "cursor-edit-capture.ts");
6819
+ const cursorAgentCapturePath = join18(HOOKS_DIR, "cursor-agent-capture.ts");
6820
+ const mcpStdioProxyPath = join18(HOOKS_DIR, "mcp-stdio-proxy.ts");
6821
+ const taskActivateIntentScriptPath = join18(HOOKS_DIR, "cc-task-activate-intent.ts");
6822
+ const mcpGateScriptPath = join18(HOOKS_DIR, "cc-mcp-gate.ts");
6823
+ const stubCommonPath = join18(HOOKS_DIR, "_synkro-stub-common.ts");
6691
6824
  const stubFiles = [
6692
6825
  [stubCommonPath, STUB_COMMON_TS],
6693
6826
  [bashScriptPath, STUB_BASH_JUDGE_TS],
@@ -6712,14 +6845,14 @@ function writeHookScripts() {
6712
6845
  [cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS]
6713
6846
  ];
6714
6847
  for (const [p, content] of stubFiles) {
6715
- writeFileSync15(p, content, "utf-8");
6848
+ writeFileSync16(p, content, "utf-8");
6716
6849
  chmodSync4(p, 493);
6717
6850
  }
6718
- writeFileSync15(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
6851
+ writeFileSync16(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
6719
6852
  chmodSync4(mcpStdioProxyPath, 493);
6720
6853
  for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
6721
6854
  try {
6722
- unlinkSync7(join17(HOOKS_DIR, stale));
6855
+ unlinkSync7(join18(HOOKS_DIR, stale));
6723
6856
  } catch {
6724
6857
  }
6725
6858
  }
@@ -6755,11 +6888,11 @@ function shellQuoteSingle2(value) {
6755
6888
  }
6756
6889
  function resolveSynkroBundle() {
6757
6890
  const scriptPath = process.argv[1];
6758
- if (scriptPath && existsSync21(scriptPath)) return scriptPath;
6891
+ if (scriptPath && existsSync22(scriptPath)) return scriptPath;
6759
6892
  return null;
6760
6893
  }
6761
6894
  function writeConfigEnv(opts) {
6762
- const credsPath = join17(SYNKRO_DIR10, "credentials.json");
6895
+ const credsPath = join18(SYNKRO_DIR11, "credentials.json");
6763
6896
  const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
6764
6897
  const safeUserId = sanitizeConfigValue(opts.userId);
6765
6898
  const safeOrgId = sanitizeConfigValue(opts.orgId);
@@ -6775,7 +6908,7 @@ function writeConfigEnv(opts) {
6775
6908
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
6776
6909
  `SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
6777
6910
  `SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
6778
- `SYNKRO_VERSION=${shellQuoteSingle2("1.7.86")}`
6911
+ `SYNKRO_VERSION=${shellQuoteSingle2("1.7.87")}`
6779
6912
  ];
6780
6913
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
6781
6914
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
@@ -6795,12 +6928,12 @@ function writeConfigEnv(opts) {
6795
6928
  lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
6796
6929
  lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
6797
6930
  lines.push("");
6798
- writeFileSync15(CONFIG_PATH4, lines.join("\n"), "utf-8");
6931
+ writeFileSync16(CONFIG_PATH4, lines.join("\n"), "utf-8");
6799
6932
  chmodSync4(CONFIG_PATH4, 384);
6800
6933
  }
6801
6934
  function persistedTranscriptConsent(source) {
6802
6935
  try {
6803
- const env = readFileSync19(CONFIG_PATH4, "utf-8");
6936
+ const env = readFileSync20(CONFIG_PATH4, "utf-8");
6804
6937
  const specific = env.match(new RegExp(`^SYNKRO_TRANSCRIPT_CONSENT_${source}='(yes|no)'`, "m"));
6805
6938
  if (specific) return specific[1] === "yes";
6806
6939
  if (source !== "CODEX") {
@@ -6823,7 +6956,7 @@ async function getOrMintCloudToken(gatewayUrl) {
6823
6956
  assertGatewayAllowed(gatewayUrl);
6824
6957
  let stored = "";
6825
6958
  try {
6826
- stored = readFileSync19(CLOUD_JWT_PATH, "utf-8").trim();
6959
+ stored = readFileSync20(CLOUD_JWT_PATH, "utf-8").trim();
6827
6960
  } catch {
6828
6961
  }
6829
6962
  if (stored && !jwtExpired(stored)) return stored;
@@ -6839,25 +6972,49 @@ async function getOrMintCloudToken(gatewayUrl) {
6839
6972
  throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);
6840
6973
  }
6841
6974
  const { token } = await resp.json();
6842
- writeFileSync15(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
6975
+ writeFileSync16(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
6843
6976
  return token;
6844
6977
  }
6845
6978
  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);
6979
+ const sf = readFullSynkroFile();
6980
+ const envWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "", 10);
6981
+ const totalWorkers = Number.isFinite(envWorkers) && envWorkers > 0 ? envWorkers : 4;
6982
+ let claudeWorkers = 0;
6983
+ let cursorWorkers = 0;
6984
+ let codexWorkers = 0;
6985
+ if (sf && (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null)) {
6986
+ claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));
6987
+ cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));
6988
+ codexWorkers = Math.max(0, Math.floor(sf.workers.codex || 0));
6989
+ } else {
6990
+ const providers = [];
6991
+ if (sf?.grader.pool && sf.grader.pool !== "auto") providers.push(sf.grader.pool);
6992
+ else {
6993
+ if (opts.hasClaudeCode) providers.push("claude_code");
6994
+ if (opts.hasCursor) providers.push("cursor");
6995
+ if (opts.hasCodex) providers.push("codex");
6996
+ }
6997
+ ({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
6998
+ }
6999
+ if (claudeWorkers + cursorWorkers + codexWorkers === 0) codexWorkers = totalWorkers;
7000
+ const selectedKind = codexWorkers > 0 && claudeWorkers === 0 && cursorWorkers === 0 ? "codex" : cursorWorkers > 0 && claudeWorkers === 0 ? "cursor" : "claude_code";
7001
+ let setupToken = "";
7002
+ if (claudeWorkers > 0) {
7003
+ try {
7004
+ console.log(" Authorize your Claude account for the hosted Claude worker \u2014");
7005
+ console.log(" a browser window will open for approval...\n");
7006
+ setupToken = await captureClaudeSetupToken();
7007
+ } catch (err) {
7008
+ console.error(` \u2717 Could not capture Claude setup-token: ${err.message}`);
7009
+ console.error(" Run `claude setup-token` manually, then re-run install.");
7010
+ process.exit(1);
7011
+ }
6855
7012
  }
6856
- console.log(" Validating Claude token...");
7013
+ if (setupToken) console.log(" Validating Claude token...");
6857
7014
  let validated = false;
6858
7015
  let authRejected = null;
6859
7016
  let lastErr = "";
6860
- for (let attempt = 1; attempt <= 2 && !validated; attempt++) {
7017
+ for (let attempt = 1; setupToken && attempt <= 2 && !validated; attempt++) {
6861
7018
  try {
6862
7019
  const out = execSync4('claude --print --output-format json "say ok"', {
6863
7020
  env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: setupToken },
@@ -6883,22 +7040,19 @@ async function provisionCloudContainer(opts) {
6883
7040
  }
6884
7041
  if (validated) {
6885
7042
  console.log(" \u2713 Claude token validated.\n");
6886
- } else {
7043
+ } else if (setupToken) {
6887
7044
  console.warn(` \u26A0 Couldn't confirm the token locally in time (${lastErr.slice(0, 50)}).`);
6888
7045
  console.warn(" Proceeding \u2014 the cloud smoke-grade below verifies it end-to-end in the container.\n");
6889
7046
  }
6890
7047
  const repo = detectGitRepo2() || void 0;
6891
- const sf = readFullSynkroFile();
6892
7048
  const harness = [];
6893
7049
  if (opts.hasClaudeCode) harness.push("claude-code");
6894
7050
  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;
7051
+ if (opts.hasCodex) harness.push("codex");
6898
7052
  let cursorApiKey = "";
6899
- if (cursorTotal > 0) {
7053
+ if (cursorWorkers > 0) {
6900
7054
  try {
6901
- cursorApiKey = readFileSync19(join17(SYNKRO_DIR10, "cursor-creds", "api-key"), "utf-8").trim();
7055
+ cursorApiKey = readFileSync20(join18(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
6902
7056
  } catch {
6903
7057
  }
6904
7058
  }
@@ -6908,6 +7062,13 @@ async function provisionCloudContainer(opts) {
6908
7062
  } catch (e) {
6909
7063
  console.warn(` (cloud token unavailable, using session token: ${e.message})`);
6910
7064
  }
7065
+ if (codexWorkers > 0) {
7066
+ const codex = await setupCodexCloud(opts.gatewayUrl, opts.jwt, (message) => console.log(` ${message}`));
7067
+ if (!codex.ok) {
7068
+ console.error(` \u2717 ${codex.error || "Codex cloud authorization failed"}`);
7069
+ process.exit(1);
7070
+ }
7071
+ }
6911
7072
  try {
6912
7073
  const resp = await fetch(`${opts.gatewayUrl}/api/v1/cli/cloud-provision`, {
6913
7074
  method: "POST",
@@ -6917,8 +7078,9 @@ async function provisionCloudContainer(opts) {
6917
7078
  user_id: opts.userId,
6918
7079
  email: opts.email,
6919
7080
  harness,
6920
- claude_workers: total,
6921
- cursor_workers: cursorTotal,
7081
+ claude_workers: claudeWorkers,
7082
+ cursor_workers: cursorWorkers,
7083
+ codex_workers: codexWorkers,
6922
7084
  cursor_api_key: cursorApiKey,
6923
7085
  // never logged; gateway stores it as the org secret
6924
7086
  connected_repo: repo,
@@ -6933,11 +7095,25 @@ async function provisionCloudContainer(opts) {
6933
7095
  }
6934
7096
  const out = await resp.json().catch(() => ({}));
6935
7097
  console.log(` \u2713 cloud container provisioned${out.container_id ? ` (${out.container_id})` : ""}`);
7098
+ const containerBase = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
7099
+ try {
7100
+ const recycle = await fetch(`${containerBase}/recycle`, {
7101
+ method: "POST",
7102
+ headers: { Authorization: `Bearer ${opts.jwt}` },
7103
+ signal: AbortSignal.timeout(3e4)
7104
+ });
7105
+ if (recycle.ok) console.log(" \u2713 cloud grader recycled onto the new pool");
7106
+ else console.warn(` \u26A0 cloud grader recycle returned HTTP ${recycle.status}; warmup will retry the current container`);
7107
+ } catch {
7108
+ console.warn(" \u26A0 cloud grader recycle was unreachable; warmup will retry");
7109
+ }
6936
7110
  console.log();
7111
+ return selectedKind;
6937
7112
  } catch (err) {
6938
7113
  console.error(` \u2717 Cloud provisioning failed: ${err.message}`);
6939
7114
  console.error(" Hooks + MCP are installed; re-run `synkro install` \u2192 cloud to retry.");
6940
7115
  console.log();
7116
+ throw err;
6941
7117
  } finally {
6942
7118
  setupToken = "";
6943
7119
  }
@@ -6978,17 +7154,21 @@ async function printWorkerDebug(base, jwt2) {
6978
7154
  console.warn(" (could not fetch worker debug)\n");
6979
7155
  }
6980
7156
  }
6981
- async function verifyCloudGrader(jwt2) {
7157
+ async function verifyCloudGrader(jwt2, requestedKind) {
6982
7158
  const base = (process.env.SYNKRO_CONTAINERS_URL || "https://api.synkro.sh").replace(/\/$/, "");
6983
7159
  console.log(" Warming the cloud grader (booting container + checking workers)...");
6984
7160
  const deadline = Date.now() + 18e4;
6985
7161
  let healthy = 0;
7162
+ let agentKind = requestedKind || "claude_code";
6986
7163
  while (Date.now() < deadline) {
6987
7164
  try {
6988
7165
  const r = await fetch(`${base}/diag`, { headers: { Authorization: `Bearer ${jwt2}` }, signal: AbortSignal.timeout(6e4) });
6989
7166
  if (r.ok) {
6990
7167
  const j = await r.json();
6991
- healthy = j.claude_code?.healthy ?? 0;
7168
+ if (!requestedKind) {
7169
+ agentKind = ["codex", "cursor", "claude_code"].find((kind) => (j[kind]?.healthy ?? 0) > 0) || "claude_code";
7170
+ }
7171
+ healthy = j[agentKind]?.healthy ?? 0;
6992
7172
  if (healthy >= 1) break;
6993
7173
  }
6994
7174
  } catch {
@@ -7000,7 +7180,7 @@ async function verifyCloudGrader(jwt2) {
7000
7180
  console.warn(" Grading is NOT verified \u2014 check the container or re-run `synkro install`.\n");
7001
7181
  return false;
7002
7182
  }
7003
- console.log(` \u2713 ${healthy} Claude worker(s) healthy`);
7183
+ console.log(` \u2713 ${healthy} ${poolLabel(agentKind)} worker(s) healthy`);
7004
7184
  console.log(" Running smoke grade (a worker scores a known-risky edit; up to 120s)...");
7005
7185
  const started = Date.now();
7006
7186
  const ticker = setInterval(() => {
@@ -7016,7 +7196,7 @@ async function verifyCloudGrader(jwt2) {
7016
7196
  const r = await fetch(`${base}/grade/submit`, {
7017
7197
  method: "POST",
7018
7198
  headers: { Authorization: `Bearer ${jwt2}`, "Content-Type": "application/json" },
7019
- body: JSON.stringify({ role: "grade-edit", payload: probe, content: probe, agent_kind: "claude_code" }),
7199
+ body: JSON.stringify({ role: "grade-edit", payload: probe, content: probe, agent_kind: agentKind }),
7020
7200
  signal: AbortSignal.timeout(12e4)
7021
7201
  });
7022
7202
  stopTicker();
@@ -7055,8 +7235,8 @@ async function verifyCloudGrader(jwt2) {
7055
7235
  }
7056
7236
  function readPersistedDeployLocation() {
7057
7237
  try {
7058
- if (existsSync21(CONFIG_PATH4)) {
7059
- const m = readFileSync19(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
7238
+ if (existsSync22(CONFIG_PATH4)) {
7239
+ const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
7060
7240
  if (m?.[1] === "cloud") return "cloud";
7061
7241
  }
7062
7242
  } catch {
@@ -7064,8 +7244,8 @@ function readPersistedDeployLocation() {
7064
7244
  return "local";
7065
7245
  }
7066
7246
  function updateConfigEnvLocation(location) {
7067
- if (!existsSync21(CONFIG_PATH4)) return;
7068
- let env = readFileSync19(CONFIG_PATH4, "utf-8");
7247
+ if (!existsSync22(CONFIG_PATH4)) return;
7248
+ let env = readFileSync20(CONFIG_PATH4, "utf-8");
7069
7249
  const set = (k, v) => {
7070
7250
  const re = new RegExp(`^${k}=.*$`, "m");
7071
7251
  const line = `${k}='${v}'`;
@@ -7073,14 +7253,14 @@ function updateConfigEnvLocation(location) {
7073
7253
  };
7074
7254
  set("SYNKRO_DEPLOY_LOCATION", location);
7075
7255
  set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
7076
- writeFileSync15(CONFIG_PATH4, env, "utf-8");
7256
+ writeFileSync16(CONFIG_PATH4, env, "utf-8");
7077
7257
  chmodSync4(CONFIG_PATH4, 384);
7078
7258
  }
7079
7259
  async function applyMcpConfig(opts) {
7080
7260
  if (!opts.hasClaudeCode && !opts.hasCursor && !opts.hasCodex) return;
7081
7261
  let mcpJwt2 = "";
7082
7262
  try {
7083
- mcpJwt2 = readFileSync19(join17(SYNKRO_DIR10, ".mcp-jwt"), "utf-8").trim();
7263
+ mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
7084
7264
  } catch {
7085
7265
  }
7086
7266
  if (!mcpJwt2) {
@@ -7172,10 +7352,10 @@ Deploy location: ${current || "(none)"} \u2192 ${desired}
7172
7352
  email = info.email;
7173
7353
  } catch {
7174
7354
  }
7175
- await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
7355
+ const agentKind = await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor, hasCodex });
7176
7356
  updateConfigEnvLocation("cloud");
7177
7357
  await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, hasCodex, local: false });
7178
- await verifyCloudGrader(token);
7358
+ await verifyCloudGrader(token, agentKind);
7179
7359
  console.log(" \u2713 grading + rules/MCP now served from Synkro Cloud\n");
7180
7360
  } else {
7181
7361
  updateConfigEnvLocation("local");
@@ -7210,8 +7390,8 @@ function resolveDeploymentMode() {
7210
7390
  const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
7211
7391
  if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
7212
7392
  try {
7213
- if (existsSync21(CONFIG_PATH4)) {
7214
- const m = readFileSync19(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
7393
+ if (existsSync22(CONFIG_PATH4)) {
7394
+ const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
7215
7395
  const val = m?.[1]?.toLowerCase();
7216
7396
  if (val === "bare-host" || val === "docker") return val;
7217
7397
  }
@@ -7238,16 +7418,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
7238
7418
  meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
7239
7419
  } catch {
7240
7420
  }
7241
- const claudeDir = join17(homedir19(), ".claude");
7421
+ const claudeDir = join18(homedir20(), ".claude");
7242
7422
  try {
7243
- const settings = JSON.parse(readFileSync19(join17(claudeDir, "settings.json"), "utf-8"));
7423
+ const settings = JSON.parse(readFileSync20(join18(claudeDir, "settings.json"), "utf-8"));
7244
7424
  const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
7245
7425
  if (plugins.length) meta.enabled_plugins = plugins;
7246
7426
  if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
7247
7427
  } catch {
7248
7428
  }
7249
7429
  try {
7250
- const mcpCache = JSON.parse(readFileSync19(join17(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
7430
+ const mcpCache = JSON.parse(readFileSync20(join18(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
7251
7431
  const mcpNames = Object.keys(mcpCache);
7252
7432
  if (mcpNames.length) meta.mcp_servers = mcpNames;
7253
7433
  } catch {
@@ -7259,10 +7439,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
7259
7439
  } catch {
7260
7440
  }
7261
7441
  try {
7262
- const sessionsDir = join17(claudeDir, "sessions");
7442
+ const sessionsDir = join18(claudeDir, "sessions");
7263
7443
  const files = readdirSync4(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
7264
7444
  for (const f of files) {
7265
- const s = JSON.parse(readFileSync19(join17(sessionsDir, f), "utf-8"));
7445
+ const s = JSON.parse(readFileSync20(join18(sessionsDir, f), "utf-8"));
7266
7446
  if (s.version) {
7267
7447
  meta.cc_version = meta.cc_version || s.version;
7268
7448
  break;
@@ -7464,7 +7644,7 @@ async function installCommand(opts = {}) {
7464
7644
  await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
7465
7645
  emit("install", {
7466
7646
  phase: "started",
7467
- cli_version_to: "1.7.86",
7647
+ cli_version_to: "1.7.87",
7468
7648
  agents_detected: agents.map((a) => a.kind),
7469
7649
  with_github: false,
7470
7650
  with_local_cc: false,
@@ -7476,9 +7656,9 @@ async function installCommand(opts = {}) {
7476
7656
  const scripts = writeHookScripts();
7477
7657
  console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
7478
7658
  for (const mode of ["edit", "bash"]) {
7479
- const pidFile = join17(SYNKRO_DIR10, "daemon", mode, "daemon.pid");
7659
+ const pidFile = join18(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
7480
7660
  try {
7481
- const pid = parseInt(readFileSync19(pidFile, "utf-8").trim(), 10);
7661
+ const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
7482
7662
  if (pid > 0) {
7483
7663
  process.kill(pid, "SIGTERM");
7484
7664
  console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
@@ -7596,7 +7776,7 @@ async function installCommand(opts = {}) {
7596
7776
  if (mintResp.ok) {
7597
7777
  const minted = await mintResp.json();
7598
7778
  mcpJwt2 = minted.token;
7599
- writeFileSync15(join17(SYNKRO_DIR10, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
7779
+ writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
7600
7780
  } else {
7601
7781
  console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
7602
7782
  }
@@ -7624,7 +7804,7 @@ async function installCommand(opts = {}) {
7624
7804
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7625
7805
  }
7626
7806
  const minted = await mintResp.json();
7627
- writeFileSync15(join17(SYNKRO_DIR10, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7807
+ writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7628
7808
  const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
7629
7809
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7630
7810
  console.log(` url: ${mcp.url}`);
@@ -7646,8 +7826,8 @@ async function installCommand(opts = {}) {
7646
7826
  if (hasCursor && !opts.noMcp) {
7647
7827
  try {
7648
7828
  if (useLocalMcp) {
7649
- const jwtPath = join17(SYNKRO_DIR10, ".mcp-jwt");
7650
- if (!existsSync21(jwtPath)) {
7829
+ const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
7830
+ if (!existsSync22(jwtPath)) {
7651
7831
  const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
7652
7832
  method: "POST",
7653
7833
  headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
@@ -7655,7 +7835,7 @@ async function installCommand(opts = {}) {
7655
7835
  });
7656
7836
  if (mintResp.ok) {
7657
7837
  const minted = await mintResp.json();
7658
- writeFileSync15(jwtPath, minted.token + "\n", { mode: 384 });
7838
+ writeFileSync16(jwtPath, minted.token + "\n", { mode: 384 });
7659
7839
  }
7660
7840
  }
7661
7841
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
@@ -7675,7 +7855,7 @@ async function installCommand(opts = {}) {
7675
7855
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7676
7856
  }
7677
7857
  const minted = await mintResp.json();
7678
- writeFileSync15(join17(SYNKRO_DIR10, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7858
+ writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7679
7859
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
7680
7860
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7681
7861
  console.log(` url: ${mcp.url}`);
@@ -7688,10 +7868,10 @@ async function installCommand(opts = {}) {
7688
7868
  }
7689
7869
  if (hasCodex && !opts.noMcp) {
7690
7870
  try {
7691
- const jwtPath = join17(SYNKRO_DIR10, ".mcp-jwt");
7871
+ const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
7692
7872
  let mcpJwt2 = "";
7693
7873
  try {
7694
- mcpJwt2 = readFileSync19(jwtPath, "utf-8").trim();
7874
+ mcpJwt2 = readFileSync20(jwtPath, "utf-8").trim();
7695
7875
  } catch {
7696
7876
  }
7697
7877
  if (!mcpJwt2) {
@@ -7709,7 +7889,7 @@ async function installCommand(opts = {}) {
7709
7889
  }
7710
7890
  const minted = await mintResp.json();
7711
7891
  mcpJwt2 = minted.token;
7712
- writeFileSync15(jwtPath, mcpJwt2 + "\n", { mode: 384 });
7892
+ writeFileSync16(jwtPath, mcpJwt2 + "\n", { mode: 384 });
7713
7893
  }
7714
7894
  const mcp = installCodexMcpConfig({
7715
7895
  gatewayUrl,
@@ -7756,7 +7936,7 @@ async function installCommand(opts = {}) {
7756
7936
  }
7757
7937
  const pool = sfp?.grader?.pool || "auto";
7758
7938
  if (pool === "cursor") return true;
7759
- if (pool === "claude_code") return false;
7939
+ if (pool === "claude_code" || pool === "codex") return false;
7760
7940
  return hasCursor;
7761
7941
  })();
7762
7942
  if (useLocalMcp) {
@@ -7776,14 +7956,17 @@ async function installCommand(opts = {}) {
7776
7956
  for (const w of sf?.warnings || []) console.warn(` \u26A0 ${w}`);
7777
7957
  let claudeWorkers;
7778
7958
  let cursorWorkers;
7779
- if (sf && (sf.workers.claude != null || sf.workers.cursor != null)) {
7959
+ let codexWorkers;
7960
+ if (sf && (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null)) {
7780
7961
  claudeWorkers = Math.max(0, Math.floor(sf.workers.claude || 0));
7781
7962
  cursorWorkers = Math.max(0, Math.floor(sf.workers.cursor || 0));
7782
- if (claudeWorkers + cursorWorkers === 0) {
7963
+ codexWorkers = Math.max(0, Math.floor(sf.workers.codex || 0));
7964
+ if (claudeWorkers + cursorWorkers + codexWorkers === 0) {
7783
7965
  claudeWorkers = 0;
7784
- cursorWorkers = 8;
7966
+ cursorWorkers = 0;
7967
+ codexWorkers = 8;
7785
7968
  }
7786
- console.log(` synkro.toml: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor`);
7969
+ console.log(` synkro.toml: explicit workers \u2014 ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
7787
7970
  } else {
7788
7971
  const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
7789
7972
  const synkroFilePool = sf?.grader?.pool || "auto";
@@ -7792,23 +7975,35 @@ async function installCommand(opts = {}) {
7792
7975
  providers = ["cursor"];
7793
7976
  } else if (synkroFilePool === "claude_code") {
7794
7977
  providers = ["claude_code"];
7978
+ } else if (synkroFilePool === "codex") {
7979
+ providers = ["codex"];
7795
7980
  } else {
7796
7981
  if (hasClaudeCode) providers.push("claude_code");
7797
7982
  if (hasCursor) providers.push("cursor");
7983
+ if (hasCodex) providers.push("codex");
7798
7984
  }
7799
- ({ claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers));
7985
+ ({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
7800
7986
  if (synkroFilePool !== "auto") console.log(` synkro.toml: grader pool set to ${poolLabel(synkroFilePool)}`);
7801
7987
  }
7802
- console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);
7988
+ let codexHomeDir;
7989
+ if (codexWorkers > 0) {
7990
+ const codexSetup = await setupCodexLocal((message) => console.log(` ${message}`));
7991
+ if (!codexSetup.ok || !codexSetup.home) {
7992
+ console.error(` \u2717 ${codexSetup.error || "Codex grader authorization failed"}`);
7993
+ process.exit(1);
7994
+ }
7995
+ codexHomeDir = codexSetup.home;
7996
+ }
7997
+ console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
7803
7998
  const connectedRepo = detectGitRepo2() || void 0;
7804
- const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });
7999
+ const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, codexHomeDir, connectedRepo });
7805
8000
  console.log(` \u2713 pulled ${image}`);
7806
8001
  console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);
7807
8002
  console.log(" waiting for container to be ready...");
7808
8003
  const ready = await waitForContainerReady(6e4);
7809
8004
  if (ready) {
7810
8005
  console.log(" \u2713 container ready");
7811
- const mcpJwt2 = readFileSync19(join17(SYNKRO_DIR10, ".mcp-jwt"), "utf-8").trim();
8006
+ const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
7812
8007
  try {
7813
8008
  const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
7814
8009
  method: "POST",
@@ -7841,8 +8036,8 @@ async function installCommand(opts = {}) {
7841
8036
  console.log();
7842
8037
  } else if (target === "cloud-container") {
7843
8038
  if (graderUsesCursor) await promptCursorApiKey(opts);
7844
- await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
7845
- cloudGradeOk = await verifyCloudGrader(token);
8039
+ const agentKind = await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor, hasCodex });
8040
+ cloudGradeOk = await verifyCloudGrader(token, agentKind);
7846
8041
  }
7847
8042
  if (transcriptConsent) {
7848
8043
  const repo = detectGitRepo2();
@@ -7851,7 +8046,7 @@ async function installCommand(opts = {}) {
7851
8046
  try {
7852
8047
  let mcpToken = "";
7853
8048
  try {
7854
- mcpToken = readFileSync19(join17(SYNKRO_DIR10, ".mcp-jwt"), "utf-8").trim();
8049
+ mcpToken = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
7855
8050
  } catch {
7856
8051
  }
7857
8052
  if (mcpToken) {
@@ -8019,8 +8214,8 @@ function writeSynkroFileIfMissing(opts) {
8019
8214
  try {
8020
8215
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8021
8216
  if (!root) return;
8022
- if (root === homedir19()) return;
8023
- const fp = join17(root, "synkro.toml");
8217
+ if (root === homedir20()) return;
8218
+ const fp = join18(root, "synkro.toml");
8024
8219
  let hasFile = false;
8025
8220
  try {
8026
8221
  hasFile = statSync2(fp).isFile();
@@ -8032,15 +8227,10 @@ function writeSynkroFileIfMissing(opts) {
8032
8227
  }
8033
8228
  let pool = "auto";
8034
8229
  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
- }
8230
+ if (opts.hasClaudeCode) harness.push("claude-code");
8231
+ if (opts.hasCursor) harness.push("cursor");
8043
8232
  if (opts.hasCodex) harness.push("codex");
8233
+ if (harness.length === 1) pool = harness[0] === "claude-code" ? "claude-code" : harness[0];
8044
8234
  const mode = opts.gradingMode === "byok" ? "byok" : "local";
8045
8235
  const toml = [
8046
8236
  "version = 1",
@@ -8057,7 +8247,7 @@ function writeSynkroFileIfMissing(opts) {
8057
8247
  "cve = true",
8058
8248
  ""
8059
8249
  ].join("\n");
8060
- writeFileSync15(fp, toml, "utf-8");
8250
+ writeFileSync16(fp, toml, "utf-8");
8061
8251
  console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);
8062
8252
  } catch {
8063
8253
  }
@@ -8065,12 +8255,12 @@ function writeSynkroFileIfMissing(opts) {
8065
8255
  function updateSynkroTomlLocation(location) {
8066
8256
  try {
8067
8257
  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");
8258
+ if (!root || root === homedir20()) return;
8259
+ const fp = join18(root, "synkro.toml");
8070
8260
  let txt = "";
8071
8261
  try {
8072
8262
  if (!statSync2(fp).isFile()) return;
8073
- txt = readFileSync19(fp, "utf-8");
8263
+ txt = readFileSync20(fp, "utf-8");
8074
8264
  } catch {
8075
8265
  return;
8076
8266
  }
@@ -8078,7 +8268,7 @@ function updateSynkroTomlLocation(location) {
8078
8268
  if (!re.test(txt)) return;
8079
8269
  const next = txt.replace(re, `$1"${location}"`);
8080
8270
  if (next !== txt) {
8081
- writeFileSync15(fp, next, "utf-8");
8271
+ writeFileSync16(fp, next, "utf-8");
8082
8272
  console.log(` synkro.toml: [grader] location = "${location}"`);
8083
8273
  }
8084
8274
  } catch {
@@ -8088,9 +8278,9 @@ function readFullSynkroFile() {
8088
8278
  try {
8089
8279
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8090
8280
  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"));
8281
+ const fp = join18(root, "synkro.toml");
8282
+ if (!existsSync22(fp)) return null;
8283
+ const parsed = parseSynkroToml2(readFileSync20(fp, "utf-8"));
8094
8284
  const valid = ["claude-code", "cursor", "codex"];
8095
8285
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
8096
8286
  const resolved = resolveGraderPool(parsed);
@@ -8103,7 +8293,8 @@ function readFullSynkroFile() {
8103
8293
  },
8104
8294
  workers: {
8105
8295
  ...resolved.claudeWorkers != null ? { claude: resolved.claudeWorkers } : {},
8106
- ...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {}
8296
+ ...resolved.cursorWorkers != null ? { cursor: resolved.cursorWorkers } : {},
8297
+ ...resolved.codexWorkers != null ? { codex: resolved.codexWorkers } : {}
8107
8298
  },
8108
8299
  warnings: resolved.warnings,
8109
8300
  ruleset: parsed.ruleset || "default",
@@ -8129,7 +8320,7 @@ function reconcileHarness() {
8129
8320
  console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
8130
8321
  const scripts = writeHookScripts();
8131
8322
  console.log("Wrote hook scripts to ~/.synkro/hooks/");
8132
- const ccSettings = join17(homedir19(), ".claude", "settings.json");
8323
+ const ccSettings = join18(homedir20(), ".claude", "settings.json");
8133
8324
  if (wantCC) {
8134
8325
  installCCHooks(ccSettings, {
8135
8326
  bashJudgeScriptPath: scripts.bashScript,
@@ -8152,7 +8343,7 @@ function reconcileHarness() {
8152
8343
  });
8153
8344
  console.log(" \u2713 Claude Code hooks registered");
8154
8345
  try {
8155
- const mcpJwt2 = readFileSync19(join17(SYNKRO_DIR10, ".mcp-jwt"), "utf-8").trim();
8346
+ const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8156
8347
  if (mcpJwt2) {
8157
8348
  installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt2, local: true });
8158
8349
  console.log(" \u2713 Claude Code MCP registered");
@@ -8164,7 +8355,7 @@ function reconcileHarness() {
8164
8355
  if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
8165
8356
  if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
8166
8357
  }
8167
- const cursorHooks = join17(homedir19(), ".cursor", "hooks.json");
8358
+ const cursorHooks = join18(homedir20(), ".cursor", "hooks.json");
8168
8359
  if (wantCursor) {
8169
8360
  installCursorHooks(cursorHooks, {
8170
8361
  bashJudgeScriptPath: scripts.cursorBashJudgeScript,
@@ -8195,7 +8386,7 @@ function reconcileHarness() {
8195
8386
  if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
8196
8387
  if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
8197
8388
  }
8198
- const codexHooks = join17(process.env.CODEX_HOME || join17(homedir19(), ".codex"), "hooks.json");
8389
+ const codexHooks = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "hooks.json");
8199
8390
  if (wantCodex) {
8200
8391
  installCodexHooks(codexHooks, {
8201
8392
  bashJudgeScriptPath: scripts.bashScript,
@@ -8226,10 +8417,11 @@ function reconcileHarness() {
8226
8417
  if (uninstallCodexHooks(codexHooks)) console.log(" \u2717 Codex hooks removed");
8227
8418
  if (uninstallCodexMcpConfig()) console.log(" \u2717 Codex MCP removed");
8228
8419
  }
8229
- if (sf.workers.claude != null || sf.workers.cursor != null) {
8420
+ if (sf.workers.claude != null || sf.workers.cursor != null || sf.workers.codex != null) {
8230
8421
  const cw = Math.max(0, Math.floor(sf.workers.claude || 0));
8231
8422
  const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));
8232
- if (cw + curw > 0) return { claudeWorkers: cw, cursorWorkers: curw };
8423
+ const codw = Math.max(0, Math.floor(sf.workers.codex || 0));
8424
+ if (cw + curw + codw > 0) return { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
8233
8425
  }
8234
8426
  const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
8235
8427
  const providers = [];
@@ -8237,9 +8429,12 @@ function reconcileHarness() {
8237
8429
  providers.push("cursor");
8238
8430
  } else if (sf.grader.pool === "claude_code") {
8239
8431
  providers.push("claude_code");
8432
+ } else if (sf.grader.pool === "codex") {
8433
+ providers.push("codex");
8240
8434
  } else {
8241
8435
  if (wantCC) providers.push("claude_code");
8242
8436
  if (wantCursor) providers.push("cursor");
8437
+ if (wantCodex) providers.push("codex");
8243
8438
  }
8244
8439
  if (providers.length === 0) providers.push("claude_code");
8245
8440
  return splitWorkers(total, providers);
@@ -8279,7 +8474,7 @@ async function syncSkillFiles() {
8279
8474
  if (resolved.length === 0) return;
8280
8475
  const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
8281
8476
  const tasks = resolved.map((fp) => {
8282
- const content = readFileSync19(fp, "utf-8");
8477
+ const content = readFileSync20(fp, "utf-8");
8283
8478
  const source = `skill:${fp.split("/").pop()}`;
8284
8479
  if (!content.trim()) {
8285
8480
  console.log(` \u2298 skill ${source}: empty file, skipped`);
@@ -8300,15 +8495,15 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8300
8495
  const roots = [];
8301
8496
  const add = (p) => {
8302
8497
  try {
8303
- if (p && existsSync21(p) && statSync2(p).isDirectory()) roots.push(p);
8498
+ if (p && existsSync22(p) && statSync2(p).isDirectory()) roots.push(p);
8304
8499
  } catch {
8305
8500
  }
8306
8501
  };
8307
- add(join17(homedir19(), ".claude", "skills"));
8308
- add(join17(homedir19(), ".agents", "skills"));
8502
+ add(join18(homedir20(), ".claude", "skills"));
8503
+ add(join18(homedir20(), ".agents", "skills"));
8309
8504
  if (repoRoot2) {
8310
- add(join17(repoRoot2, ".claude", "skills"));
8311
- add(join17(repoRoot2, ".agents", "skills"));
8505
+ add(join18(repoRoot2, ".claude", "skills"));
8506
+ add(join18(repoRoot2, ".agents", "skills"));
8312
8507
  }
8313
8508
  const out = [];
8314
8509
  const seen = /* @__PURE__ */ new Set();
@@ -8319,7 +8514,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8319
8514
  try {
8320
8515
  const st = statSync2(file);
8321
8516
  if (!st.isFile() || st.size > 2e5) return;
8322
- content = readFileSync19(file, "utf-8");
8517
+ content = readFileSync20(file, "utf-8");
8323
8518
  } catch {
8324
8519
  return;
8325
8520
  }
@@ -8339,7 +8534,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8339
8534
  }
8340
8535
  for (const entry of entries) {
8341
8536
  if (entry.startsWith(".")) continue;
8342
- const full = join17(root, entry);
8537
+ const full = join18(root, entry);
8343
8538
  let st;
8344
8539
  try {
8345
8540
  st = statSync2(full);
@@ -8347,8 +8542,8 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8347
8542
  continue;
8348
8543
  }
8349
8544
  if (st.isDirectory()) {
8350
- const skillMd = join17(full, "SKILL.md");
8351
- if (existsSync21(skillMd)) consider(skillMd, entry);
8545
+ const skillMd = join18(full, "SKILL.md");
8546
+ if (existsSync22(skillMd)) consider(skillMd, entry);
8352
8547
  } else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
8353
8548
  consider(full, entry.replace(/\.mdx?$/i, ""));
8354
8549
  }
@@ -8397,7 +8592,7 @@ async function discoverAndIngestSkills() {
8397
8592
  if (sf?.skills?.length) {
8398
8593
  for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
8399
8594
  try {
8400
- excludeHashes.add(createHash2("sha256").update(readFileSync19(fp, "utf-8")).digest("hex"));
8595
+ excludeHashes.add(createHash2("sha256").update(readFileSync20(fp, "utf-8")).digest("hex"));
8401
8596
  } catch {
8402
8597
  }
8403
8598
  }
@@ -8428,13 +8623,13 @@ async function discoverAndIngestSkills() {
8428
8623
  const setHash = discoverySetHash(selectable);
8429
8624
  let prev = "";
8430
8625
  try {
8431
- prev = readFileSync19(SKILLS_DISCOVERED_PATH, "utf-8").trim();
8626
+ prev = readFileSync20(SKILLS_DISCOVERED_PATH, "utf-8").trim();
8432
8627
  } catch {
8433
8628
  }
8434
8629
  if (prev === setHash) return;
8435
8630
  const picks = await promptSkillDiscovery(found);
8436
8631
  try {
8437
- writeFileSync15(SKILLS_DISCOVERED_PATH, setHash);
8632
+ writeFileSync16(SKILLS_DISCOVERED_PATH, setHash);
8438
8633
  } catch {
8439
8634
  }
8440
8635
  if (picks.length === 0) {
@@ -8473,9 +8668,9 @@ function ensureReachabilityGitHook() {
8473
8668
  const root = run("git rev-parse --show-toplevel");
8474
8669
  if (!root) return null;
8475
8670
  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");
8671
+ hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join18(root, hooksDir) : join18(root, ".git", "hooks");
8672
+ if (!existsSync22(hooksDir)) mkdirSync15(hooksDir, { recursive: true });
8673
+ const hookPath = join18(hooksDir, "post-commit");
8479
8674
  const resolvedBin = resolveSynkroBinPath();
8480
8675
  const invoke = resolvedBin ? `"${resolvedBin}" reachability-scan --quiet` : "true";
8481
8676
  const START = "# >>> synkro reachability (managed) >>>";
@@ -8488,14 +8683,14 @@ function ensureReachabilityGitHook() {
8488
8683
  END
8489
8684
  ].join("\n");
8490
8685
  const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8491
- if (!existsSync21(hookPath)) {
8492
- writeFileSync15(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
8686
+ if (!existsSync22(hookPath)) {
8687
+ writeFileSync16(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
8493
8688
  return "installed";
8494
8689
  }
8495
- let cur = readFileSync19(hookPath, "utf-8");
8690
+ let cur = readFileSync20(hookPath, "utf-8");
8496
8691
  if (cur.includes(START)) {
8497
8692
  cur = cur.replace(new RegExp(esc(START) + "[\\s\\S]*?" + esc(END), "m"), block);
8498
- writeFileSync15(hookPath, cur);
8693
+ writeFileSync16(hookPath, cur);
8499
8694
  try {
8500
8695
  chmodSync4(hookPath, 493);
8501
8696
  } catch {
@@ -8503,7 +8698,7 @@ function ensureReachabilityGitHook() {
8503
8698
  return "updated";
8504
8699
  }
8505
8700
  const sep = cur.endsWith("\n") ? "" : "\n";
8506
- writeFileSync15(hookPath, cur + sep + "\n" + block + "\n");
8701
+ writeFileSync16(hookPath, cur + sep + "\n" + block + "\n");
8507
8702
  try {
8508
8703
  chmodSync4(hookPath, 493);
8509
8704
  } catch {
@@ -8531,17 +8726,17 @@ function detectGitRepo2() {
8531
8726
  function getClaudeProjectsFolder() {
8532
8727
  const cwd = process.cwd();
8533
8728
  const sanitized = "-" + cwd.replace(/\//g, "-");
8534
- const projectsDir = join17(homedir19(), ".claude", "projects", sanitized);
8535
- return existsSync21(projectsDir) ? projectsDir : null;
8729
+ const projectsDir = join18(homedir20(), ".claude", "projects", sanitized);
8730
+ return existsSync22(projectsDir) ? projectsDir : null;
8536
8731
  }
8537
8732
  function extractSessionInsights(projectsDir) {
8538
8733
  const insights = [];
8539
8734
  const files = readdirSync4(projectsDir).filter((f) => f.endsWith(".jsonl"));
8540
8735
  for (const file of files) {
8541
8736
  const sessionId = file.replace(".jsonl", "");
8542
- const filePath = join17(projectsDir, file);
8737
+ const filePath = join18(projectsDir, file);
8543
8738
  try {
8544
- const content = readFileSync19(filePath, "utf-8");
8739
+ const content = readFileSync20(filePath, "utf-8");
8545
8740
  const lines = content.split("\n").filter(Boolean);
8546
8741
  for (let i = 0; i < lines.length; i++) {
8547
8742
  try {
@@ -8617,17 +8812,17 @@ function extractTextContent(content) {
8617
8812
  return "";
8618
8813
  }
8619
8814
  function getCodexTranscriptFiles(repo) {
8620
- const sessionsDir = join17(process.env.CODEX_HOME || join17(homedir19(), ".codex"), "sessions");
8621
- if (!existsSync21(sessionsDir)) return [];
8815
+ const sessionsDir = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "sessions");
8816
+ if (!existsSync22(sessionsDir)) return [];
8622
8817
  let relative = [];
8623
8818
  try {
8624
8819
  relative = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
8625
8820
  } catch {
8626
8821
  return [];
8627
8822
  }
8628
- return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join17(sessionsDir, p)).filter((filePath) => {
8823
+ return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join18(sessionsDir, p)).filter((filePath) => {
8629
8824
  try {
8630
- const first = readFileSync19(filePath, "utf-8").split("\n", 1)[0];
8825
+ const first = readFileSync20(filePath, "utf-8").split("\n", 1)[0];
8631
8826
  const meta = JSON.parse(first);
8632
8827
  const cwd = typeof meta?.payload?.cwd === "string" ? resolve4(meta.payload.cwd) : "";
8633
8828
  const root = resolve4(repo);
@@ -8638,7 +8833,7 @@ function getCodexTranscriptFiles(repo) {
8638
8833
  });
8639
8834
  }
8640
8835
  function parseCodexTranscriptFile(filePath) {
8641
- const lines = readFileSync19(filePath, "utf-8").split("\n").filter(Boolean);
8836
+ const lines = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean);
8642
8837
  let sessionId = "";
8643
8838
  let model = "";
8644
8839
  const messages = [];
@@ -8693,7 +8888,7 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
8693
8888
  totalSessions++;
8694
8889
  totalMessages += result.ingested ?? messages.length;
8695
8890
  }
8696
- writeFileSync15(join17(OFFSETS_DIR, parsed.sessionId), String(readFileSync19(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
8891
+ writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
8697
8892
  } catch {
8698
8893
  }
8699
8894
  if ((i + 1) % 10 === 0 || i === files.length - 1) {
@@ -8707,14 +8902,14 @@ function cursorProjectSlug(workspaceRoot) {
8707
8902
  return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
8708
8903
  }
8709
8904
  function getCursorTranscriptsDir() {
8710
- const dir = join17(homedir19(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
8711
- return existsSync21(dir) ? dir : null;
8905
+ const dir = join18(homedir20(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
8906
+ return existsSync22(dir) ? dir : null;
8712
8907
  }
8713
8908
  function isSafeConvId(id) {
8714
8909
  return /^[A-Za-z0-9_-]+$/.test(id);
8715
8910
  }
8716
8911
  function parseCursorTranscriptFile(filePath) {
8717
- const content = readFileSync19(filePath, "utf-8");
8912
+ const content = readFileSync20(filePath, "utf-8");
8718
8913
  const lines = content.split("\n").filter(Boolean);
8719
8914
  const messages = [];
8720
8915
  for (let i = 0; i < lines.length; i++) {
@@ -8746,8 +8941,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8746
8941
  for (let i = 0; i < convDirs.length; i++) {
8747
8942
  const convId = convDirs[i];
8748
8943
  if (!isSafeConvId(convId)) continue;
8749
- const filePath = join17(dir, convId, `${convId}.jsonl`);
8750
- if (!existsSync21(filePath)) continue;
8944
+ const filePath = join18(dir, convId, `${convId}.jsonl`);
8945
+ if (!existsSync22(filePath)) continue;
8751
8946
  try {
8752
8947
  const all = parseCursorTranscriptFile(filePath);
8753
8948
  const messages = all.length > 500 ? all.slice(-500) : all;
@@ -8769,8 +8964,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8769
8964
  process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
8770
8965
  }
8771
8966
  try {
8772
- const lc = readFileSync19(filePath, "utf-8").split("\n").filter(Boolean).length;
8773
- writeFileSync15(join17(OFFSETS_DIR, convId), String(lc), "utf-8");
8967
+ const lc = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length;
8968
+ writeFileSync16(join18(OFFSETS_DIR, convId), String(lc), "utf-8");
8774
8969
  } catch {
8775
8970
  }
8776
8971
  }
@@ -8778,7 +8973,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8778
8973
  return { sessions: totalSessions, messages: totalMessages };
8779
8974
  }
8780
8975
  function parseTranscriptFile(filePath) {
8781
- const content = readFileSync19(filePath, "utf-8");
8976
+ const content = readFileSync20(filePath, "utf-8");
8782
8977
  const lines = content.split("\n").filter(Boolean);
8783
8978
  const messages = [];
8784
8979
  for (let i = 0; i < lines.length; i++) {
@@ -8826,7 +9021,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
8826
9021
  for (let i = 0; i < files.length; i++) {
8827
9022
  const file = files[i];
8828
9023
  const sessionId = file.replace(".jsonl", "");
8829
- const filePath = join17(projectsDir, file);
9024
+ const filePath = join18(projectsDir, file);
8830
9025
  try {
8831
9026
  const allMessages = parseTranscriptFile(filePath);
8832
9027
  const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
@@ -8848,9 +9043,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
8848
9043
  process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
8849
9044
  }
8850
9045
  try {
8851
- const content = readFileSync19(join17(projectsDir, file), "utf-8");
9046
+ const content = readFileSync20(join18(projectsDir, file), "utf-8");
8852
9047
  const lineCount = content.split("\n").filter(Boolean).length;
8853
- writeFileSync15(join17(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9048
+ writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
8854
9049
  } catch {
8855
9050
  }
8856
9051
  }
@@ -8871,7 +9066,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
8871
9066
  const sessions = [];
8872
9067
  for (const file of batch) {
8873
9068
  const sessionId = file.replace(".jsonl", "");
8874
- const filePath = join17(projectsDir, file);
9069
+ const filePath = join18(projectsDir, file);
8875
9070
  try {
8876
9071
  const allMessages = parseTranscriptFile(filePath);
8877
9072
  const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
@@ -8900,11 +9095,11 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
8900
9095
  }
8901
9096
  for (const file of batch) {
8902
9097
  const sessionId = file.replace(".jsonl", "");
8903
- const filePath = join17(projectsDir, file);
9098
+ const filePath = join18(projectsDir, file);
8904
9099
  try {
8905
- const content = readFileSync19(filePath, "utf-8");
9100
+ const content = readFileSync20(filePath, "utf-8");
8906
9101
  const lineCount = content.split("\n").filter(Boolean).length;
8907
- writeFileSync15(join17(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9102
+ writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
8908
9103
  } catch {
8909
9104
  }
8910
9105
  }
@@ -8944,7 +9139,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
8944
9139
  try {
8945
9140
  const parsed = parseCodexTranscriptFile(filePath);
8946
9141
  if (parsed.sessionId) {
8947
- writeFileSync15(join17(OFFSETS_DIR, parsed.sessionId), String(readFileSync19(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
9142
+ writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
8948
9143
  }
8949
9144
  } catch {
8950
9145
  }
@@ -8952,7 +9147,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
8952
9147
  }
8953
9148
  return { sessions: totalSessions, messages: totalMessages };
8954
9149
  }
8955
- var SYNKRO_DIR10, HOOKS_DIR, BIN_DIR, CONFIG_PATH4, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
9150
+ var SYNKRO_DIR11, HOOKS_DIR, BIN_DIR, CONFIG_PATH4, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
8956
9151
  var init_install = __esm({
8957
9152
  "cli/commands/install.ts"() {
8958
9153
  "use strict";
@@ -8972,11 +9167,12 @@ var init_install = __esm({
8972
9167
  init_claudeDesktopTap();
8973
9168
  init_dockerInstall();
8974
9169
  init_setupToken();
9170
+ init_codexCloudSetup();
8975
9171
  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");
9172
+ SYNKRO_DIR11 = join18(homedir20(), ".synkro");
9173
+ HOOKS_DIR = join18(SYNKRO_DIR11, "hooks");
9174
+ BIN_DIR = join18(SYNKRO_DIR11, "bin");
9175
+ CONFIG_PATH4 = join18(SYNKRO_DIR11, "config.env");
8980
9176
  MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
8981
9177
  import { readFileSync } from 'node:fs';
8982
9178
  import { homedir } from 'node:os';
@@ -9087,23 +9283,23 @@ rl.on('line', async (line) => {
9087
9283
  }
9088
9284
  });
9089
9285
  `;
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");
9286
+ OFFSETS_DIR = join18(SYNKRO_DIR11, ".transcript-offsets");
9287
+ CLOUD_JWT_PATH = join18(SYNKRO_DIR11, ".cloud-jwt");
9288
+ SKILLS_DISCOVERED_PATH = join18(SYNKRO_DIR11, ".skills-discovered");
9093
9289
  }
9094
9290
  });
9095
9291
 
9096
9292
  // 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";
9293
+ 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";
9294
+ import { join as join19 } from "path";
9295
+ import { homedir as homedir21 } from "os";
9296
+ import { spawnSync as spawnSync7 } from "child_process";
9101
9297
  function writePluginFiles() {
9102
9298
  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(
9299
+ mkdirSync16(c.sessionDir, { recursive: true });
9300
+ mkdirSync16(c.pluginSettingsDir, { recursive: true });
9301
+ writeFileSync17(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
9302
+ writeFileSync17(
9107
9303
  c.pluginSettingsPath,
9108
9304
  JSON.stringify({
9109
9305
  fastMode: true,
@@ -9118,13 +9314,13 @@ function writePluginFiles() {
9118
9314
  }, null, 2) + "\n",
9119
9315
  "utf-8"
9120
9316
  );
9121
- writeFileSync16(c.runScriptPath, c.runScriptSource, "utf-8");
9317
+ writeFileSync17(c.runScriptPath, c.runScriptSource, "utf-8");
9122
9318
  chmodSync5(c.runScriptPath, 493);
9123
9319
  }
9124
9320
  }
9125
9321
  function runBunInstall() {
9126
9322
  for (const c of CHANNELS) {
9127
- const r = spawnSync6("bun", ["install", "--silent"], {
9323
+ const r = spawnSync7("bun", ["install", "--silent"], {
9128
9324
  cwd: c.sessionDir,
9129
9325
  encoding: "utf-8",
9130
9326
  timeout: 12e4
@@ -9137,10 +9333,10 @@ function runBunInstall() {
9137
9333
  }
9138
9334
  }
9139
9335
  function safelyMutateClaudeJson(mutator) {
9140
- if (!existsSync22(CLAUDE_JSON_PATH)) {
9336
+ if (!existsSync23(CLAUDE_JSON_PATH)) {
9141
9337
  return;
9142
9338
  }
9143
- const originalText = readFileSync20(CLAUDE_JSON_PATH, "utf-8");
9339
+ const originalText = readFileSync21(CLAUDE_JSON_PATH, "utf-8");
9144
9340
  let parsed;
9145
9341
  try {
9146
9342
  parsed = JSON.parse(originalText);
@@ -9172,7 +9368,7 @@ function safelyMutateClaudeJson(mutator) {
9172
9368
  copyFileSync2(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
9173
9369
  const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
9174
9370
  try {
9175
- writeFileSync16(tmpPath, newText, "utf-8");
9371
+ writeFileSync17(tmpPath, newText, "utf-8");
9176
9372
  const fd = openSync2(tmpPath, "r");
9177
9373
  try {
9178
9374
  fsyncSync(fd);
@@ -9205,7 +9401,7 @@ function writeProjectMcpJson() {
9205
9401
  }
9206
9402
  }
9207
9403
  };
9208
- writeFileSync16(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
9404
+ writeFileSync17(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
9209
9405
  }
9210
9406
  }
9211
9407
  function patchClaudeJson() {
@@ -9240,15 +9436,15 @@ function patchClaudeJson() {
9240
9436
  });
9241
9437
  }
9242
9438
  function installLocalCC() {
9243
- let bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
9439
+ let bunCheck = spawnSync7("bun", ["--version"], { encoding: "utf-8" });
9244
9440
  if (bunCheck.status !== 0) {
9245
9441
  if (process.platform === "darwin") {
9246
9442
  console.log(" Installing bun via brew...");
9247
- const brewR = spawnSync6("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
9443
+ const brewR = spawnSync7("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
9248
9444
  if (brewR.status !== 0) {
9249
9445
  throw new LocalCCInstallError("bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash");
9250
9446
  }
9251
- bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
9447
+ bunCheck = spawnSync7("bun", ["--version"], { encoding: "utf-8" });
9252
9448
  if (bunCheck.status !== 0) {
9253
9449
  throw new LocalCCInstallError("bun installed but not found on PATH. Restart your terminal and re-run install.");
9254
9450
  }
@@ -9282,42 +9478,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
9282
9478
  var init_install2 = __esm({
9283
9479
  "cli/local-cc/install.ts"() {
9284
9480
  "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");
9481
+ CLAUDE_JSON_BACKUP_PATH = join19(homedir21(), ".claude.json.synkro-bak");
9482
+ SESSION_DIR = join19(homedir21(), ".synkro", "cc_sessions");
9483
+ PLUGIN_PATH = join19(SESSION_DIR, "synkro-channel.ts");
9484
+ PLUGIN_PKG_PATH = join19(SESSION_DIR, "package.json");
9485
+ PLUGIN_SETTINGS_DIR = join19(SESSION_DIR, ".claude");
9486
+ PLUGIN_SETTINGS_PATH = join19(PLUGIN_SETTINGS_DIR, "settings.json");
9487
+ PROJECT_MCP_PATH = join19(SESSION_DIR, ".mcp.json");
9488
+ CLAUDE_JSON_PATH = join19(homedir21(), ".claude.json");
9489
+ RUN_SCRIPT_PATH = join19(SESSION_DIR, "run-claude.sh");
9294
9490
  TMUX_SESSION_NAME = "synkro-local-cc";
9295
9491
  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");
9492
+ SESSION_DIR_2 = join19(homedir21(), ".synkro", "cc_sessions_2");
9493
+ PLUGIN_PATH_2 = join19(SESSION_DIR_2, "synkro-channel.ts");
9494
+ PLUGIN_PKG_PATH_2 = join19(SESSION_DIR_2, "package.json");
9495
+ PLUGIN_SETTINGS_DIR_2 = join19(SESSION_DIR_2, ".claude");
9496
+ PLUGIN_SETTINGS_PATH_2 = join19(PLUGIN_SETTINGS_DIR_2, "settings.json");
9497
+ PROJECT_MCP_PATH_2 = join19(SESSION_DIR_2, ".mcp.json");
9498
+ RUN_SCRIPT_PATH_2 = join19(SESSION_DIR_2, "run-claude.sh");
9303
9499
  TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
9304
9500
  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");
9501
+ SESSION_DIR_3 = join19(homedir21(), ".synkro", "cc_sessions_3");
9502
+ PLUGIN_PATH_3 = join19(SESSION_DIR_3, "synkro-channel.ts");
9503
+ PLUGIN_PKG_PATH_3 = join19(SESSION_DIR_3, "package.json");
9504
+ PLUGIN_SETTINGS_DIR_3 = join19(SESSION_DIR_3, ".claude");
9505
+ PLUGIN_SETTINGS_PATH_3 = join19(PLUGIN_SETTINGS_DIR_3, "settings.json");
9506
+ PROJECT_MCP_PATH_3 = join19(SESSION_DIR_3, ".mcp.json");
9507
+ RUN_SCRIPT_PATH_3 = join19(SESSION_DIR_3, "run-claude.sh");
9312
9508
  TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
9313
9509
  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");
9510
+ SESSION_DIR_4 = join19(homedir21(), ".synkro", "cc_sessions_4");
9511
+ PLUGIN_PATH_4 = join19(SESSION_DIR_4, "synkro-channel.ts");
9512
+ PLUGIN_PKG_PATH_4 = join19(SESSION_DIR_4, "package.json");
9513
+ PLUGIN_SETTINGS_DIR_4 = join19(SESSION_DIR_4, ".claude");
9514
+ PLUGIN_SETTINGS_PATH_4 = join19(PLUGIN_SETTINGS_DIR_4, "settings.json");
9515
+ PROJECT_MCP_PATH_4 = join19(SESSION_DIR_4, ".mcp.json");
9516
+ RUN_SCRIPT_PATH_4 = join19(SESSION_DIR_4, "run-claude.sh");
9321
9517
  TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
9322
9518
  CHANNEL_4_PORT = 8952;
9323
9519
  RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
@@ -9591,10 +9787,10 @@ var disconnect_exports = {};
9591
9787
  __export(disconnect_exports, {
9592
9788
  disconnectCommand: () => disconnectCommand
9593
9789
  });
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";
9790
+ import { existsSync as existsSync24, rmSync as rmSync3, readdirSync as readdirSync5 } from "fs";
9791
+ import { homedir as homedir22 } from "os";
9792
+ import { join as join20 } from "path";
9793
+ import { spawnSync as spawnSync8 } from "child_process";
9598
9794
  import { createInterface as createInterface3 } from "readline";
9599
9795
  async function tearDownLocalCC() {
9600
9796
  const docker = dockerStatus();
@@ -9608,7 +9804,7 @@ async function tearDownLocalCC() {
9608
9804
  console.log("\u2713 removed synkro-server container");
9609
9805
  try {
9610
9806
  const image = imageTag();
9611
- const r = spawnSync7("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
9807
+ const r = spawnSync8("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
9612
9808
  console.log(r.status === 0 ? `\u2713 removed Docker image ${image}` : "\xB7 no Docker image to remove");
9613
9809
  } catch {
9614
9810
  }
@@ -9694,7 +9890,7 @@ async function disconnectCommand(args2 = [], opts = {}) {
9694
9890
  if (desktopMcpRemoved) removed.mcp = true;
9695
9891
  console.log(`${desktopMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? "removed from claude_desktop_config.json" : "no entry found"}`);
9696
9892
  }
9697
- if (existsSync23(SYNKRO_DIR11)) {
9893
+ if (existsSync24(SYNKRO_DIR12)) {
9698
9894
  if (purge2) {
9699
9895
  emit("uninstall", {
9700
9896
  flavor,
@@ -9703,41 +9899,41 @@ async function disconnectCommand(args2 = [], opts = {}) {
9703
9899
  duration_ms: Date.now() - startedAt
9704
9900
  });
9705
9901
  await flush({ force: true });
9706
- rmSync2(SYNKRO_DIR11, { recursive: true, force: true });
9902
+ rmSync3(SYNKRO_DIR12, { recursive: true, force: true });
9707
9903
  removed.config = true;
9708
- console.log(`\u2713 wiped ${SYNKRO_DIR11} entirely \u2014 including all scan data and backups`);
9904
+ console.log(`\u2713 wiped ${SYNKRO_DIR12} entirely \u2014 including all scan data and backups`);
9709
9905
  } else {
9710
9906
  const keep = /* @__PURE__ */ new Set([
9711
- join19(SYNKRO_DIR11, "pgdata"),
9712
- join19(SYNKRO_DIR11, "pgdata-backups"),
9713
- join19(SYNKRO_DIR11, ".transcript-offsets")
9907
+ join20(SYNKRO_DIR12, "pgdata"),
9908
+ join20(SYNKRO_DIR12, "pgdata-backups"),
9909
+ join20(SYNKRO_DIR12, ".transcript-offsets")
9714
9910
  ]);
9715
9911
  const preserved = [];
9716
- for (const entry of readdirSync5(SYNKRO_DIR11)) {
9717
- const full = join19(SYNKRO_DIR11, entry);
9912
+ for (const entry of readdirSync5(SYNKRO_DIR12)) {
9913
+ const full = join20(SYNKRO_DIR12, entry);
9718
9914
  if (keep.has(full)) {
9719
9915
  preserved.push(entry);
9720
9916
  continue;
9721
9917
  }
9722
- rmSync2(full, { recursive: true, force: true });
9918
+ rmSync3(full, { recursive: true, force: true });
9723
9919
  }
9724
9920
  removed.config = true;
9725
9921
  if (preserved.length > 0) {
9726
- console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR11} (kept your scan data: ${preserved.join(", ")})`);
9922
+ console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR12} (kept your scan data: ${preserved.join(", ")})`);
9727
9923
  console.log(" run `synkro uninstall --purge` to delete that too");
9728
9924
  } else {
9729
- console.log(`\u2713 removed ${SYNKRO_DIR11}`);
9925
+ console.log(`\u2713 removed ${SYNKRO_DIR12}`);
9730
9926
  }
9731
9927
  }
9732
9928
  } else {
9733
- console.log(`\xB7 ${SYNKRO_DIR11} already gone`);
9929
+ console.log(`\xB7 ${SYNKRO_DIR12} already gone`);
9734
9930
  }
9735
9931
  if (!purge2) {
9736
9932
  emit("uninstall", { flavor, purge: purge2, removed, duration_ms: Date.now() - startedAt });
9737
9933
  }
9738
9934
  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
9935
  }
9740
- var SYNKRO_DIR11;
9936
+ var SYNKRO_DIR12;
9741
9937
  var init_disconnect = __esm({
9742
9938
  "cli/commands/disconnect.ts"() {
9743
9939
  "use strict";
@@ -9752,14 +9948,14 @@ var init_disconnect = __esm({
9752
9948
  init_dockerInstall();
9753
9949
  init_macKeychain();
9754
9950
  init_telemetry();
9755
- SYNKRO_DIR11 = join19(homedir21(), ".synkro");
9951
+ SYNKRO_DIR12 = join20(homedir22(), ".synkro");
9756
9952
  }
9757
9953
  });
9758
9954
 
9759
9955
  // 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";
9956
+ 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";
9957
+ import { dirname as dirname7, join as join21 } from "path";
9958
+ import { homedir as homedir23 } from "os";
9763
9959
  function truncate(s, max = PREVIEW_MAX) {
9764
9960
  if (s.length <= max) return s;
9765
9961
  return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
@@ -9779,7 +9975,7 @@ function extractSeverity(result) {
9779
9975
  }
9780
9976
  function appendTurn(args2) {
9781
9977
  try {
9782
- mkdirSync16(dirname7(TURN_LOG_PATH), { recursive: true });
9978
+ mkdirSync17(dirname7(TURN_LOG_PATH), { recursive: true });
9783
9979
  const entry = {
9784
9980
  ts: new Date(args2.startedAt).toISOString(),
9785
9981
  role: args2.role,
@@ -9795,11 +9991,11 @@ function appendTurn(args2) {
9795
9991
  }
9796
9992
  }
9797
9993
  function readRecentTurns(n = 20) {
9798
- if (!existsSync24(TURN_LOG_PATH)) return [];
9994
+ if (!existsSync25(TURN_LOG_PATH)) return [];
9799
9995
  try {
9800
9996
  const size = statSync3(TURN_LOG_PATH).size;
9801
9997
  if (size === 0) return [];
9802
- const text = readFileSync21(TURN_LOG_PATH, "utf-8");
9998
+ const text = readFileSync22(TURN_LOG_PATH, "utf-8");
9803
9999
  const lines = text.split("\n").filter(Boolean);
9804
10000
  const lastN = lines.slice(-n).reverse();
9805
10001
  return lastN.map((line) => {
@@ -9815,8 +10011,8 @@ function readRecentTurns(n = 20) {
9815
10011
  }
9816
10012
  function followTurns(onEntry) {
9817
10013
  try {
9818
- mkdirSync16(dirname7(TURN_LOG_PATH), { recursive: true });
9819
- if (!existsSync24(TURN_LOG_PATH)) {
10014
+ mkdirSync17(dirname7(TURN_LOG_PATH), { recursive: true });
10015
+ if (!existsSync25(TURN_LOG_PATH)) {
9820
10016
  appendFileSync3(TURN_LOG_PATH, "", "utf-8");
9821
10017
  }
9822
10018
  } catch {
@@ -9878,7 +10074,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
9878
10074
  var init_turnLog = __esm({
9879
10075
  "cli/local-cc/turnLog.ts"() {
9880
10076
  "use strict";
9881
- TURN_LOG_PATH = join20(homedir22(), ".synkro", "cc_sessions", "turns.log");
10077
+ TURN_LOG_PATH = join21(homedir23(), ".synkro", "cc_sessions", "turns.log");
9882
10078
  PREVIEW_MAX = 400;
9883
10079
  }
9884
10080
  });
@@ -10032,8 +10228,8 @@ __export(scanPr_exports, {
10032
10228
  scanPrCommand: () => scanPrCommand
10033
10229
  });
10034
10230
  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";
10231
+ import { readFileSync as readFileSync23, existsSync as existsSync26 } from "fs";
10232
+ import { join as join22 } from "path";
10037
10233
  function parseMatchSpec(condition) {
10038
10234
  if (!condition.startsWith("match_spec:")) return null;
10039
10235
  try {
@@ -10512,10 +10708,10 @@ function shouldFail(findings, threshold) {
10512
10708
  return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
10513
10709
  }
10514
10710
  function readRepoDeps() {
10515
- const pkgPath = join21(process.cwd(), "package.json");
10516
- if (!existsSync25(pkgPath)) return {};
10711
+ const pkgPath = join22(process.cwd(), "package.json");
10712
+ if (!existsSync26(pkgPath)) return {};
10517
10713
  try {
10518
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf-8"));
10714
+ const pkg = JSON.parse(readFileSync23(pkgPath, "utf-8"));
10519
10715
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
10520
10716
  } catch {
10521
10717
  return {};
@@ -10759,15 +10955,15 @@ var routeDecide_exports = {};
10759
10955
  __export(routeDecide_exports, {
10760
10956
  routeDecide: () => routeDecide
10761
10957
  });
10762
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "fs";
10763
- import { homedir as homedir23 } from "os";
10764
- import { join as join22 } from "path";
10958
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "fs";
10959
+ import { homedir as homedir24 } from "os";
10960
+ import { join as join23 } from "path";
10765
10961
  function safeSid(sid) {
10766
10962
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
10767
10963
  }
10768
10964
  function loadMcpJwt() {
10769
10965
  try {
10770
- return readFileSync23(join22(SYNKRO_DIR12, ".mcp-jwt"), "utf-8").trim();
10966
+ return readFileSync24(join23(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
10771
10967
  } catch {
10772
10968
  return "";
10773
10969
  }
@@ -10777,7 +10973,7 @@ async function routeDecide(sessionId) {
10777
10973
  const sid = safeSid(sessionId);
10778
10974
  let prompt = "";
10779
10975
  try {
10780
- const rec = JSON.parse(readFileSync23(join22(SESSIONS_DIR2, sid + ".json"), "utf-8"));
10976
+ const rec = JSON.parse(readFileSync24(join23(SESSIONS_DIR2, sid + ".json"), "utf-8"));
10781
10977
  prompt = String(rec.last_prompt || "").trim();
10782
10978
  } catch {
10783
10979
  return;
@@ -10800,29 +10996,29 @@ async function routeDecide(sessionId) {
10800
10996
  return;
10801
10997
  }
10802
10998
  if (!model || !VALID_MODELS.has(model)) return;
10803
- const lastFile = join22(PTY_DIR, "route-last-" + sid);
10999
+ const lastFile = join23(PTY_DIR, "route-last-" + sid);
10804
11000
  let last = "";
10805
11001
  try {
10806
- last = readFileSync23(lastFile, "utf-8").trim();
11002
+ last = readFileSync24(lastFile, "utf-8").trim();
10807
11003
  } catch {
10808
11004
  }
10809
11005
  if (model === last) return;
10810
11006
  try {
10811
- writeFileSync17(lastFile, model);
11007
+ writeFileSync18(lastFile, model);
10812
11008
  } catch {
10813
11009
  }
10814
11010
  try {
10815
- writeFileSync17(join22(PTY_DIR, "route-" + sid), model);
11011
+ writeFileSync18(join23(PTY_DIR, "route-" + sid), model);
10816
11012
  } catch {
10817
11013
  }
10818
11014
  }
10819
- var SYNKRO_DIR12, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
11015
+ var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
10820
11016
  var init_routeDecide = __esm({
10821
11017
  "cli/local-cc/routeDecide.ts"() {
10822
11018
  "use strict";
10823
- SYNKRO_DIR12 = join22(homedir23(), ".synkro");
10824
- PTY_DIR = join22(SYNKRO_DIR12, "pty");
10825
- SESSIONS_DIR2 = join22(PTY_DIR, "sessions");
11019
+ SYNKRO_DIR13 = join23(homedir24(), ".synkro");
11020
+ PTY_DIR = join23(SYNKRO_DIR13, "pty");
11021
+ SESSIONS_DIR2 = join23(PTY_DIR, "sessions");
10826
11022
  VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
10827
11023
  GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
10828
11024
  }
@@ -10833,10 +11029,10 @@ var routeOrchestrate_exports = {};
10833
11029
  __export(routeOrchestrate_exports, {
10834
11030
  routeAndResubmit: () => routeAndResubmit
10835
11031
  });
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";
11032
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
11033
+ import { homedir as homedir25 } from "os";
11034
+ import { join as join24 } from "path";
11035
+ import { spawnSync as spawnSync9 } from "child_process";
10840
11036
  function safeSid2(sid) {
10841
11037
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
10842
11038
  }
@@ -10845,7 +11041,7 @@ function safeSession(s) {
10845
11041
  }
10846
11042
  function loadMcpJwt2() {
10847
11043
  try {
10848
- return readFileSync24(join23(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
11044
+ return readFileSync25(join24(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
10849
11045
  } catch {
10850
11046
  return "";
10851
11047
  }
@@ -10871,7 +11067,7 @@ async function classifyTask(prompt) {
10871
11067
  }
10872
11068
  function lastRoutedModel(sid) {
10873
11069
  try {
10874
- const v = readFileSync24(join23(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
11070
+ const v = readFileSync25(join24(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
10875
11071
  return VALID_MODELS2.has(v) ? v : "";
10876
11072
  } catch {
10877
11073
  return "";
@@ -10881,12 +11077,12 @@ function resolveSession(sid, tmuxSession) {
10881
11077
  const candidates = [];
10882
11078
  if (tmuxSession) candidates.push(tmuxSession);
10883
11079
  try {
10884
- const rec = JSON.parse(readFileSync24(join23(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
11080
+ const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
10885
11081
  if (rec.tmux_session) candidates.push(rec.tmux_session);
10886
11082
  } catch {
10887
11083
  }
10888
11084
  try {
10889
- candidates.push(readFileSync24(ACTIVE_SESSION_FILE2, "utf-8").trim());
11085
+ candidates.push(readFileSync25(ACTIVE_SESSION_FILE2, "utf-8").trim());
10890
11086
  } catch {
10891
11087
  }
10892
11088
  for (const c of candidates) if (c && safeSession(c)) return c;
@@ -10898,12 +11094,12 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
10898
11094
  const sid = safeSid2(sessionId);
10899
11095
  const session = resolveSession(sid, tmuxSession);
10900
11096
  if (!session) return;
10901
- if (spawnSync8("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return;
11097
+ if (spawnSync9("tmux", ["has-session", "-t", `=${session}`], { encoding: "utf-8" }).status !== 0) return;
10902
11098
  const current = lastRoutedModel(sid);
10903
11099
  const picked = forceModel && VALID_MODELS2.has(forceModel) ? forceModel : await classifyTask(trimmed);
10904
11100
  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 || "";
11101
+ const sk = (...a) => spawnSync9("tmux", ["send-keys", "-t", session, ...a], { encoding: "utf-8" });
11102
+ const capture = () => spawnSync9("tmux", ["capture-pane", "-t", session, "-p", "-S", "-25"], { encoding: "utf-8" }).stdout || "";
10907
11103
  await wait(400);
10908
11104
  if (doSwap) {
10909
11105
  sk("C-u");
@@ -10921,29 +11117,29 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
10921
11117
  }
10922
11118
  await wait(500);
10923
11119
  try {
10924
- writeFileSync18(join23(PTY_DIR2, "route-last-" + sid), picked);
11120
+ writeFileSync19(join24(PTY_DIR2, "route-last-" + sid), picked);
10925
11121
  } catch {
10926
11122
  }
10927
11123
  }
10928
11124
  try {
10929
- writeFileSync18(join23(PTY_DIR2, "route-guard-" + sid), "1");
11125
+ writeFileSync19(join24(PTY_DIR2, "route-guard-" + sid), "1");
10930
11126
  } catch {
10931
11127
  }
10932
11128
  sk("C-u");
10933
11129
  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" });
11130
+ spawnSync9("tmux", ["set-buffer", "-b", "synkro-route", trimmed], { encoding: "utf-8" });
11131
+ spawnSync9("tmux", ["paste-buffer", "-t", session, "-b", "synkro-route", "-d"], { encoding: "utf-8" });
10936
11132
  await wait(120);
10937
11133
  sk("Enter");
10938
11134
  }
10939
- var SYNKRO_DIR13, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2, GRADER_HOST_PORT2, wait;
11135
+ var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2, GRADER_HOST_PORT2, wait;
10940
11136
  var init_routeOrchestrate = __esm({
10941
11137
  "cli/local-cc/routeOrchestrate.ts"() {
10942
11138
  "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");
11139
+ SYNKRO_DIR14 = join24(homedir25(), ".synkro");
11140
+ PTY_DIR2 = join24(SYNKRO_DIR14, "pty");
11141
+ SESSIONS_DIR3 = join24(PTY_DIR2, "sessions");
11142
+ ACTIVE_SESSION_FILE2 = join24(PTY_DIR2, "active");
10947
11143
  VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
10948
11144
  GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
10949
11145
  wait = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -10955,14 +11151,14 @@ var routingToggle_exports = {};
10955
11151
  __export(routingToggle_exports, {
10956
11152
  routingCommand: () => routingCommand
10957
11153
  });
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";
11154
+ import { writeFileSync as writeFileSync20, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
11155
+ import { homedir as homedir26 } from "os";
11156
+ import { join as join25 } from "path";
10961
11157
  function safeSid3(sid) {
10962
11158
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
10963
11159
  }
10964
11160
  function markerFor(session) {
10965
- return session ? join24(PTY_DIR3, "routing-on-" + safeSid3(session)) : join24(PTY_DIR3, "routing-on");
11161
+ return session ? join25(PTY_DIR3, "routing-on-" + safeSid3(session)) : join25(PTY_DIR3, "routing-on");
10966
11162
  }
10967
11163
  function routingCommand(args2) {
10968
11164
  const sub = (args2[0] || "status").trim();
@@ -10971,7 +11167,7 @@ function routingCommand(args2) {
10971
11167
  if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
10972
11168
  if (sub === "on") {
10973
11169
  try {
10974
- writeFileSync19(markerFor(session), "1");
11170
+ writeFileSync20(markerFor(session), "1");
10975
11171
  } catch (e) {
10976
11172
  console.error("routing on failed:", String(e));
10977
11173
  return;
@@ -10983,7 +11179,7 @@ function routingCommand(args2) {
10983
11179
  if (sub === "off") {
10984
11180
  let removed = 0;
10985
11181
  if (session) {
10986
- if (existsSync26(markerFor(session))) {
11182
+ if (existsSync27(markerFor(session))) {
10987
11183
  try {
10988
11184
  unlinkSync9(markerFor(session));
10989
11185
  removed++;
@@ -10994,7 +11190,7 @@ function routingCommand(args2) {
10994
11190
  for (const f of safeList()) {
10995
11191
  if (f === "routing-on" || f.startsWith("routing-on-")) {
10996
11192
  try {
10997
- unlinkSync9(join24(PTY_DIR3, f));
11193
+ unlinkSync9(join25(PTY_DIR3, f));
10998
11194
  removed++;
10999
11195
  } catch {
11000
11196
  }
@@ -11026,24 +11222,24 @@ var PTY_DIR3;
11026
11222
  var init_routingToggle = __esm({
11027
11223
  "cli/local-cc/routingToggle.ts"() {
11028
11224
  "use strict";
11029
- PTY_DIR3 = join24(homedir25(), ".synkro", "pty");
11225
+ PTY_DIR3 = join25(homedir26(), ".synkro", "pty");
11030
11226
  }
11031
11227
  });
11032
11228
 
11033
11229
  // 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";
11230
+ import { execFileSync as execFileSync4, spawnSync as spawnSync10, spawn as spawn6 } from "child_process";
11231
+ import { homedir as homedir27 } from "os";
11232
+ import { join as join26 } from "path";
11037
11233
  import { connect as connect2 } from "net";
11038
11234
  function pueueAvailable() {
11039
- const r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
11235
+ const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
11040
11236
  if (r.status !== 0) {
11041
11237
  throw new PueueError("pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.");
11042
11238
  }
11043
11239
  }
11044
11240
  function statusJson() {
11045
11241
  pueueAvailable();
11046
- const r = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8" });
11242
+ const r = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8" });
11047
11243
  if (r.status !== 0) {
11048
11244
  throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || "unknown error"} \u2014 is pueued running?`);
11049
11245
  }
@@ -11088,18 +11284,18 @@ function startTask(opts = {}) {
11088
11284
  let existing = findTask(ch);
11089
11285
  while (existing) {
11090
11286
  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" });
11287
+ spawnSync10("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
11288
+ spawnSync10("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
11093
11289
  for (let i = 0; i < 10; i++) {
11094
11290
  const check = findTask(ch);
11095
11291
  if (!check || check.id !== existing.id || check.status !== "Running" && check.status !== "Queued") break;
11096
- spawnSync9("sleep", ["0.5"], { encoding: "utf-8" });
11292
+ spawnSync10("sleep", ["0.5"], { encoding: "utf-8" });
11097
11293
  }
11098
11294
  }
11099
- spawnSync9("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
11295
+ spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
11100
11296
  existing = findTask(ch);
11101
11297
  }
11102
- const runScript = join25(cwd, "run-claude.sh");
11298
+ const runScript = join26(cwd, "run-claude.sh");
11103
11299
  const args2 = [
11104
11300
  "add",
11105
11301
  "--label",
@@ -11110,7 +11306,7 @@ function startTask(opts = {}) {
11110
11306
  "bash",
11111
11307
  runScript
11112
11308
  ];
11113
- const r = spawnSync9("pueue", args2, { encoding: "utf-8" });
11309
+ const r = spawnSync10("pueue", args2, { encoding: "utf-8" });
11114
11310
  if (r.status !== 0) {
11115
11311
  throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);
11116
11312
  }
@@ -11121,25 +11317,25 @@ function startTask(opts = {}) {
11121
11317
  return created;
11122
11318
  }
11123
11319
  function stopTask(channel = CHANNEL_PRIMARY) {
11124
- spawnSync9("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
11320
+ spawnSync10("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
11125
11321
  let t = findTask(channel);
11126
11322
  while (t) {
11127
11323
  if (t.status === "Running" || t.status === "Queued") {
11128
- spawnSync9("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
11324
+ spawnSync10("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
11129
11325
  for (let i = 0; i < 10; i++) {
11130
11326
  const check = findTask(channel);
11131
11327
  if (!check || check.id !== t.id || check.status !== "Running" && check.status !== "Queued") break;
11132
- spawnSync9("sleep", ["0.5"], { encoding: "utf-8" });
11328
+ spawnSync10("sleep", ["0.5"], { encoding: "utf-8" });
11133
11329
  }
11134
11330
  }
11135
- spawnSync9("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
11331
+ spawnSync10("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
11136
11332
  t = findTask(channel);
11137
11333
  }
11138
11334
  }
11139
11335
  function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
11140
11336
  const t = findTask(channel);
11141
11337
  if (!t) return `(no ${channel.taskLabel} task)`;
11142
- const r = spawnSync9("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
11338
+ const r = spawnSync10("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
11143
11339
  return r.stdout || r.stderr || "(no output)";
11144
11340
  }
11145
11341
  function ensureRunning(opts = {}) {
@@ -11164,8 +11360,8 @@ function probePort(host, port, timeoutMs = 500) {
11164
11360
  });
11165
11361
  }
11166
11362
  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" });
11363
+ spawnSync10("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
11364
+ spawnSync10("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
11169
11365
  }
11170
11366
  async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tmuxSession = TMUX_SESSION) {
11171
11367
  const deadline = Date.now() + timeoutMs;
@@ -11177,46 +11373,46 @@ async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tm
11177
11373
  return probePort(host, port);
11178
11374
  }
11179
11375
  function brewInstall(pkg) {
11180
- const brew = spawnSync9("brew", ["--version"], { encoding: "utf-8" });
11376
+ const brew = spawnSync10("brew", ["--version"], { encoding: "utf-8" });
11181
11377
  if (brew.status !== 0) return false;
11182
11378
  console.log(` Installing ${pkg} via brew...`);
11183
- const r = spawnSync9("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
11379
+ const r = spawnSync10("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
11184
11380
  return r.status === 0;
11185
11381
  }
11186
11382
  function assertPueueInstalled() {
11187
- let r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
11383
+ let r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
11188
11384
  if (r.status !== 0) {
11189
11385
  if (process.platform === "darwin" && brewInstall("pueue")) {
11190
- r = spawnSync9("pueue", ["--version"], { encoding: "utf-8" });
11386
+ r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
11191
11387
  if (r.status !== 0) throw new PueueError("pueue install succeeded but binary not found on PATH.");
11192
11388
  } else {
11193
11389
  throw new PueueError("pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue");
11194
11390
  }
11195
11391
  }
11196
- const status = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
11392
+ const status = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
11197
11393
  if (status.status !== 0) {
11198
11394
  console.log(" Starting pueued daemon...");
11199
11395
  const child = spawn6("pueued", ["-d"], { stdio: "ignore", detached: true });
11200
11396
  child.unref();
11201
- spawnSync9("sleep", ["1"]);
11202
- const retry = spawnSync9("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
11397
+ spawnSync10("sleep", ["1"]);
11398
+ const retry = spawnSync10("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
11203
11399
  if (retry.status !== 0) {
11204
11400
  throw new PueueError("pueue daemon not reachable after starting pueued. Check `pueued` manually.");
11205
11401
  }
11206
11402
  }
11207
- spawnSync9("pueue", ["parallel", "2"], { encoding: "utf-8" });
11403
+ spawnSync10("pueue", ["parallel", "2"], { encoding: "utf-8" });
11208
11404
  }
11209
11405
  function assertClaudeInstalled() {
11210
- const r = spawnSync9("claude", ["--version"], { encoding: "utf-8" });
11406
+ const r = spawnSync10("claude", ["--version"], { encoding: "utf-8" });
11211
11407
  if (r.status !== 0) {
11212
11408
  throw new PueueError("claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code");
11213
11409
  }
11214
11410
  }
11215
11411
  function assertTmuxInstalled() {
11216
- let r = spawnSync9("tmux", ["-V"], { encoding: "utf-8" });
11412
+ let r = spawnSync10("tmux", ["-V"], { encoding: "utf-8" });
11217
11413
  if (r.status !== 0) {
11218
11414
  if (process.platform === "darwin" && brewInstall("tmux")) {
11219
- r = spawnSync9("tmux", ["-V"], { encoding: "utf-8" });
11415
+ r = spawnSync10("tmux", ["-V"], { encoding: "utf-8" });
11220
11416
  if (r.status !== 0) throw new PueueError("tmux install succeeded but binary not found on PATH.");
11221
11417
  } else {
11222
11418
  throw new PueueError("tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)");
@@ -11229,12 +11425,12 @@ var init_pueue = __esm({
11229
11425
  "use strict";
11230
11426
  TASK_LABEL = "synkro-local-cc";
11231
11427
  TMUX_SESSION = "synkro-local-cc";
11232
- SESSION_DIR2 = join25(homedir26(), ".synkro", "cc_sessions");
11428
+ SESSION_DIR2 = join26(homedir27(), ".synkro", "cc_sessions");
11233
11429
  TASK_LABEL_2 = "synkro-local-cc-2";
11234
11430
  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");
11431
+ SESSION_DIR_22 = join26(homedir27(), ".synkro", "cc_sessions_2");
11432
+ SESSION_DIR_32 = join26(homedir27(), ".synkro", "cc_sessions_3");
11433
+ SESSION_DIR_42 = join26(homedir27(), ".synkro", "cc_sessions_4");
11238
11434
  PueueError = class extends Error {
11239
11435
  constructor(message, cause) {
11240
11436
  super(message);
@@ -11249,13 +11445,13 @@ var init_pueue = __esm({
11249
11445
  });
11250
11446
 
11251
11447
  // 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";
11448
+ import { existsSync as existsSync28, readFileSync as readFileSync26 } from "fs";
11449
+ import { homedir as homedir28 } from "os";
11450
+ import { join as join27 } from "path";
11255
11451
  function isLocalCCEnabled() {
11256
- if (!existsSync27(CONFIG_PATH5)) return false;
11452
+ if (!existsSync28(CONFIG_PATH5)) return false;
11257
11453
  try {
11258
- const content = readFileSync25(CONFIG_PATH5, "utf-8");
11454
+ const content = readFileSync26(CONFIG_PATH5, "utf-8");
11259
11455
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
11260
11456
  return match?.[1] === "yes";
11261
11457
  } catch {
@@ -11266,7 +11462,7 @@ var CONFIG_PATH5;
11266
11462
  var init_settings = __esm({
11267
11463
  "cli/local-cc/settings.ts"() {
11268
11464
  "use strict";
11269
- CONFIG_PATH5 = join26(homedir27(), ".synkro", "config.env");
11465
+ CONFIG_PATH5 = join27(homedir28(), ".synkro", "config.env");
11270
11466
  }
11271
11467
  });
11272
11468
 
@@ -11275,11 +11471,11 @@ var localCc_exports = {};
11275
11471
  __export(localCc_exports, {
11276
11472
  localCcCommand: () => localCcCommand
11277
11473
  });
11278
- import { spawnSync as spawnSync10 } from "child_process";
11279
- import { homedir as homedir28 } from "os";
11280
- import { join as join27 } from "path";
11474
+ import { spawnSync as spawnSync11 } from "child_process";
11475
+ import { homedir as homedir29 } from "os";
11476
+ import { join as join28 } from "path";
11281
11477
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
11282
- import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
11478
+ import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
11283
11479
  function deploymentMode() {
11284
11480
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
11285
11481
  if (env === "docker") return "docker";
@@ -11385,15 +11581,15 @@ TROUBLESHOOTING
11385
11581
  `);
11386
11582
  }
11387
11583
  function readGatewayUrl() {
11388
- if (existsSync28(CONFIG_PATH6)) {
11389
- const m = readFileSync26(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
11584
+ if (existsSync29(CONFIG_PATH6)) {
11585
+ const m = readFileSync27(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
11390
11586
  if (m) return m[1];
11391
11587
  }
11392
11588
  return "https://api.synkro.sh";
11393
11589
  }
11394
11590
  function updateLocalInferenceFlag(enabled) {
11395
- if (!existsSync28(CONFIG_PATH6)) return;
11396
- let content = readFileSync26(CONFIG_PATH6, "utf-8");
11591
+ if (!existsSync29(CONFIG_PATH6)) return;
11592
+ let content = readFileSync27(CONFIG_PATH6, "utf-8");
11397
11593
  const flag = enabled ? "yes" : "no";
11398
11594
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
11399
11595
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -11402,7 +11598,7 @@ function updateLocalInferenceFlag(enabled) {
11402
11598
  SYNKRO_LOCAL_INFERENCE='${flag}'
11403
11599
  `;
11404
11600
  }
11405
- writeFileSync20(CONFIG_PATH6, content, "utf-8");
11601
+ writeFileSync21(CONFIG_PATH6, content, "utf-8");
11406
11602
  }
11407
11603
  async function setServerGradingProvider(provider) {
11408
11604
  await ensureValidToken();
@@ -11456,7 +11652,7 @@ async function cmdStatus() {
11456
11652
  }
11457
11653
  const ch1Up = await isChannelAvailable();
11458
11654
  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" });
11655
+ const tmux1 = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
11460
11656
  console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? "live" : "absent"}`);
11461
11657
  const t2 = findTask(CHANNEL_SECONDARY);
11462
11658
  if (!t2) {
@@ -11466,7 +11662,7 @@ async function cmdStatus() {
11466
11662
  }
11467
11663
  const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);
11468
11664
  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" });
11665
+ const tmux2 = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
11470
11666
  console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? "live" : "absent"}`);
11471
11667
  }
11472
11668
  async function cmdEnable() {
@@ -11523,9 +11719,9 @@ async function warmChannels(ready1, ready2) {
11523
11719
  async function cmdStart(rest = []) {
11524
11720
  if (inDockerMode()) {
11525
11721
  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 });
11722
+ const { claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest);
11723
+ console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)...`);
11724
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers });
11529
11725
  const ready3 = await waitForContainerReady(6e4);
11530
11726
  console.log(ready3 ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
11531
11727
  return;
@@ -11571,18 +11767,19 @@ async function cmdRestart(rest = []) {
11571
11767
  const { explicit } = resolveWorkerConfig(rest);
11572
11768
  let claudeWorkers;
11573
11769
  let cursorWorkers;
11770
+ let codexWorkers;
11574
11771
  if (explicit) {
11575
- ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));
11772
+ ({ claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest));
11576
11773
  } else {
11577
11774
  const reconciled = reconcileHarness();
11578
11775
  if (reconciled) {
11579
- ({ claudeWorkers, cursorWorkers } = reconciled);
11776
+ ({ claudeWorkers, cursorWorkers, codexWorkers } = reconciled);
11580
11777
  } else {
11581
- ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));
11778
+ ({ claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest));
11582
11779
  }
11583
11780
  }
11584
- console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor, pulling latest image)...`);
11585
- await dockerUpdate({ claudeWorkers, cursorWorkers });
11781
+ console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex, pulling latest image)...`);
11782
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers });
11586
11783
  const ready = await waitForContainerReady(6e4);
11587
11784
  console.log(ready ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
11588
11785
  if (ready) {
@@ -11672,7 +11869,7 @@ function cmdLogs(rest) {
11672
11869
  }
11673
11870
  return "200";
11674
11871
  })();
11675
- spawnSync10("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
11872
+ spawnSync11("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
11676
11873
  return;
11677
11874
  }
11678
11875
  for (const arg of rest) {
@@ -11720,7 +11917,7 @@ function cmdLogs(rest) {
11720
11917
  function cmdAttach(rest) {
11721
11918
  assertTmuxInstalled();
11722
11919
  const readonly = rest.some((a) => a === "--readonly" || a === "-r");
11723
- const has = spawnSync10("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
11920
+ const has = spawnSync11("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
11724
11921
  if (has.status !== 0) {
11725
11922
  console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);
11726
11923
  process.exit(1);
@@ -11733,7 +11930,7 @@ function cmdAttach(rest) {
11733
11930
  console.log("Detach with Ctrl-B then D. (Do not press Ctrl-C \u2014 that would interrupt claude.)");
11734
11931
  console.log();
11735
11932
  const args2 = readonly ? ["attach-session", "-r", "-t", TMUX_SESSION_NAME] : ["attach-session", "-t", TMUX_SESSION_NAME];
11736
- const r = spawnSync10("tmux", args2, { stdio: "inherit" });
11933
+ const r = spawnSync11("tmux", args2, { stdio: "inherit" });
11737
11934
  process.exit(r.status ?? 0);
11738
11935
  }
11739
11936
  async function cmdTest() {
@@ -11834,8 +12031,8 @@ var init_localCc = __esm({
11834
12031
  init_install();
11835
12032
  init_client2();
11836
12033
  init_stub();
11837
- SYNKRO_CONFIG_PATH = join27(homedir28(), ".synkro", "config.env");
11838
- CONFIG_PATH6 = join27(homedir28(), ".synkro", "config.env");
12034
+ SYNKRO_CONFIG_PATH = join28(homedir29(), ".synkro", "config.env");
12035
+ CONFIG_PATH6 = join28(homedir29(), ".synkro", "config.env");
11839
12036
  }
11840
12037
  });
11841
12038
 
@@ -11844,14 +12041,14 @@ var import_exports = {};
11844
12041
  __export(import_exports, {
11845
12042
  importCommand: () => importCommand
11846
12043
  });
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";
12044
+ import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync7 } from "fs";
12045
+ import { homedir as homedir30 } from "os";
12046
+ import { join as join29 } from "path";
11850
12047
  import { execSync as execSync6 } from "child_process";
11851
12048
  import { createInterface as createInterface4 } from "readline";
11852
12049
  function readMcpJwt() {
11853
12050
  try {
11854
- return readFileSync27(join28(homedir29(), ".synkro", ".mcp-jwt"), "utf-8").trim();
12051
+ return readFileSync28(join29(homedir30(), ".synkro", ".mcp-jwt"), "utf-8").trim();
11855
12052
  } catch {
11856
12053
  return "";
11857
12054
  }
@@ -11859,7 +12056,7 @@ function readMcpJwt() {
11859
12056
  function readConfigEnv2() {
11860
12057
  const out = {};
11861
12058
  try {
11862
- for (const line of readFileSync27(CONFIG_PATH7, "utf-8").split("\n")) {
12059
+ for (const line of readFileSync28(CONFIG_PATH7, "utf-8").split("\n")) {
11863
12060
  const t = line.trim();
11864
12061
  if (!t || t.startsWith("#")) continue;
11865
12062
  const eq = t.indexOf("=");
@@ -11871,8 +12068,8 @@ function readConfigEnv2() {
11871
12068
  }
11872
12069
  function projectsFolder() {
11873
12070
  const sanitized = process.cwd().replace(/\//g, "-");
11874
- const dir = join28(homedir29(), ".claude", "projects", sanitized);
11875
- return existsSync29(dir) ? dir : null;
12071
+ const dir = join29(homedir30(), ".claude", "projects", sanitized);
12072
+ return existsSync30(dir) ? dir : null;
11876
12073
  }
11877
12074
  function repoName() {
11878
12075
  try {
@@ -11911,7 +12108,7 @@ function extractToolResultText(content, e) {
11911
12108
  return t;
11912
12109
  }
11913
12110
  function parseSession(filePath, sessionId) {
11914
- const lines = readFileSync27(filePath, "utf-8").split("\n").filter(Boolean);
12111
+ const lines = readFileSync28(filePath, "utf-8").split("\n").filter(Boolean);
11915
12112
  const messages = [];
11916
12113
  const actions = [];
11917
12114
  let step = 0;
@@ -11991,7 +12188,7 @@ async function importCommand() {
11991
12188
  return;
11992
12189
  }
11993
12190
  }
11994
- const sessions = files.map((f) => parseSession(join28(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
12191
+ const sessions = files.map((f) => parseSession(join29(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
11995
12192
  const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
11996
12193
  let ok = 0, fail = 0;
11997
12194
  if (isCloud) {
@@ -12066,7 +12263,7 @@ var init_import = __esm({
12066
12263
  "cli/commands/import.ts"() {
12067
12264
  "use strict";
12068
12265
  init_stub();
12069
- CONFIG_PATH7 = join28(homedir29(), ".synkro", "config.env");
12266
+ CONFIG_PATH7 = join29(homedir30(), ".synkro", "config.env");
12070
12267
  }
12071
12268
  });
12072
12269
 
@@ -12108,10 +12305,10 @@ var init_packVerify = __esm({
12108
12305
  });
12109
12306
 
12110
12307
  // cli/installer/lockfile.ts
12111
- import { existsSync as existsSync30, readFileSync as readFileSync28, writeFileSync as writeFileSync21 } from "fs";
12112
- import { join as join29 } from "path";
12308
+ import { existsSync as existsSync31, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "fs";
12309
+ import { join as join30 } from "path";
12113
12310
  function lockPath(repoRoot2) {
12114
- return join29(repoRoot2, LOCK_FILE);
12311
+ return join30(repoRoot2, LOCK_FILE);
12115
12312
  }
12116
12313
  function writeLockfile(repoRoot2, entries) {
12117
12314
  const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
@@ -12129,7 +12326,7 @@ function writeLockfile(repoRoot2, entries) {
12129
12326
  ""
12130
12327
  ])
12131
12328
  ].join("\n");
12132
- writeFileSync21(lockPath(repoRoot2), body, "utf-8");
12329
+ writeFileSync22(lockPath(repoRoot2), body, "utf-8");
12133
12330
  }
12134
12331
  var LOCK_FILE;
12135
12332
  var init_lockfile = __esm({
@@ -12144,9 +12341,9 @@ var sync_exports = {};
12144
12341
  __export(sync_exports, {
12145
12342
  syncCommand: () => syncCommand
12146
12343
  });
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";
12344
+ import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync23 } from "fs";
12345
+ import { homedir as homedir31 } from "os";
12346
+ import { join as join31 } from "path";
12150
12347
  function cacheKey(ref, version) {
12151
12348
  return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
12152
12349
  }
@@ -12177,8 +12374,8 @@ async function syncCommand(_args = []) {
12177
12374
  }
12178
12375
  const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
12179
12376
  const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
12180
- const cacheDir = join30(homedir30(), ".synkro", "cache", "packs");
12181
- if (!cloud) mkdirSync17(cacheDir, { recursive: true });
12377
+ const cacheDir = join31(homedir31(), ".synkro", "cache", "packs");
12378
+ if (!cloud) mkdirSync18(cacheDir, { recursive: true });
12182
12379
  console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
12183
12380
  const lock = [];
12184
12381
  const keptCacheFiles = /* @__PURE__ */ new Set();
@@ -12206,7 +12403,7 @@ async function syncCommand(_args = []) {
12206
12403
  if (!cloud) {
12207
12404
  const fname = cacheKey(ref, data.version);
12208
12405
  keptCacheFiles.add(fname);
12209
- writeFileSync22(join30(cacheDir, fname), JSON.stringify({
12406
+ writeFileSync23(join31(cacheDir, fname), JSON.stringify({
12210
12407
  ref,
12211
12408
  version: data.version,
12212
12409
  digest: data.digest,
@@ -12218,11 +12415,11 @@ async function syncCommand(_args = []) {
12218
12415
  const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
12219
12416
  console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
12220
12417
  }
12221
- if (!cloud && existsSync31(cacheDir)) {
12418
+ if (!cloud && existsSync32(cacheDir)) {
12222
12419
  for (const f of readdirSync8(cacheDir)) {
12223
12420
  if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
12224
12421
  try {
12225
- rmSync3(join30(cacheDir, f));
12422
+ rmSync4(join31(cacheDir, f));
12226
12423
  } catch {
12227
12424
  }
12228
12425
  }
@@ -12251,13 +12448,13 @@ var whoami_exports = {};
12251
12448
  __export(whoami_exports, {
12252
12449
  whoamiCommand: () => whoamiCommand
12253
12450
  });
12254
- import { readFileSync as readFileSync29, existsSync as existsSync32 } from "fs";
12255
- import { join as join31 } from "path";
12256
- import { homedir as homedir31 } from "os";
12451
+ import { readFileSync as readFileSync30, existsSync as existsSync33 } from "fs";
12452
+ import { join as join32 } from "path";
12453
+ import { homedir as homedir32 } from "os";
12257
12454
  function readConfigEnv3() {
12258
- if (!existsSync32(CONFIG_PATH8)) return {};
12455
+ if (!existsSync33(CONFIG_PATH8)) return {};
12259
12456
  const out = {};
12260
- for (const line of readFileSync29(CONFIG_PATH8, "utf-8").split("\n")) {
12457
+ for (const line of readFileSync30(CONFIG_PATH8, "utf-8").split("\n")) {
12261
12458
  const t = line.trim();
12262
12459
  if (!t || t.startsWith("#")) continue;
12263
12460
  const eq = t.indexOf("=");
@@ -12267,8 +12464,8 @@ function readConfigEnv3() {
12267
12464
  }
12268
12465
  function jwtStatus() {
12269
12466
  try {
12270
- if (!existsSync32(JWT_PATH2)) return { status: "none" };
12271
- const jwt2 = readFileSync29(JWT_PATH2, "utf-8").trim();
12467
+ if (!existsSync33(JWT_PATH2)) return { status: "none" };
12468
+ const jwt2 = readFileSync30(JWT_PATH2, "utf-8").trim();
12272
12469
  if (!jwt2) return { status: "none" };
12273
12470
  const payload = jwt2.split(".")[1];
12274
12471
  if (!payload) return { status: "valid" };
@@ -12326,13 +12523,13 @@ async function whoamiCommand(args2 = []) {
12326
12523
  console.log("synkro identity");
12327
12524
  for (const [k, v] of rows) console.log(` ${k.padEnd(width)} ${v}`);
12328
12525
  }
12329
- var SYNKRO_DIR14, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
12526
+ var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
12330
12527
  var init_whoami = __esm({
12331
12528
  "cli/commands/whoami.ts"() {
12332
12529
  "use strict";
12333
- SYNKRO_DIR14 = join31(homedir31(), ".synkro");
12334
- CONFIG_PATH8 = join31(SYNKRO_DIR14, "config.env");
12335
- JWT_PATH2 = join31(SYNKRO_DIR14, ".mcp-jwt");
12530
+ SYNKRO_DIR15 = join32(homedir32(), ".synkro");
12531
+ CONFIG_PATH8 = join32(SYNKRO_DIR15, "config.env");
12532
+ JWT_PATH2 = join32(SYNKRO_DIR15, ".mcp-jwt");
12336
12533
  GRADING_LABEL = {
12337
12534
  local: "on-device worker pool",
12338
12535
  cloud: "Synkro Cloud worker pool",
@@ -12377,12 +12574,12 @@ __export(linear_exports, {
12377
12574
  formatLinks: () => formatLinks,
12378
12575
  linearCommand: () => linearCommand
12379
12576
  });
12380
- import { readFileSync as readFileSync30 } from "fs";
12381
- import { homedir as homedir32 } from "os";
12382
- import { join as join32 } from "path";
12577
+ import { readFileSync as readFileSync31 } from "fs";
12578
+ import { homedir as homedir33 } from "os";
12579
+ import { join as join33 } from "path";
12383
12580
  function mcpJwt() {
12384
12581
  try {
12385
- return readFileSync30(join32(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
12582
+ return readFileSync31(join33(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
12386
12583
  } catch {
12387
12584
  return "";
12388
12585
  }
@@ -12417,11 +12614,11 @@ async function linearCommand(_args = []) {
12417
12614
  }
12418
12615
  console.log(formatLinks(links));
12419
12616
  }
12420
- var SYNKRO_DIR15, PORT2, BASE;
12617
+ var SYNKRO_DIR16, PORT2, BASE;
12421
12618
  var init_linear = __esm({
12422
12619
  "cli/commands/linear.ts"() {
12423
12620
  "use strict";
12424
- SYNKRO_DIR15 = join32(homedir32(), ".synkro");
12621
+ SYNKRO_DIR16 = join33(homedir33(), ".synkro");
12425
12622
  PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
12426
12623
  BASE = `http://127.0.0.1:${PORT2}`;
12427
12624
  }
@@ -12429,7 +12626,7 @@ var init_linear = __esm({
12429
12626
 
12430
12627
  // cli/scanning/cveReachability.ts
12431
12628
  import { parse } from "@babel/parser";
12432
- import { readFileSync as readFileSync31 } from "fs";
12629
+ import { readFileSync as readFileSync32 } from "fs";
12433
12630
  function walk(node, visit) {
12434
12631
  if (!node || typeof node.type !== "string") return;
12435
12632
  visit(node);
@@ -12570,10 +12767,10 @@ var init_cveReachability = __esm({
12570
12767
  });
12571
12768
 
12572
12769
  // 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";
12770
+ import { spawnSync as spawnSync12, execFileSync as execFileSync5 } from "child_process";
12771
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync24, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
12772
+ import { join as join34 } from "path";
12773
+ import { homedir as homedir34 } from "os";
12577
12774
  import { createRequire } from "module";
12578
12775
  function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12579
12776
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
@@ -12590,7 +12787,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12590
12787
  }
12591
12788
  for (const e of ents) {
12592
12789
  if (files.length >= maxFiles) break;
12593
- const full = join33(dir, e.name);
12790
+ const full = join34(dir, e.name);
12594
12791
  if (e.isDirectory()) {
12595
12792
  if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
12596
12793
  continue;
@@ -12598,7 +12795,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12598
12795
  if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
12599
12796
  const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
12600
12797
  try {
12601
- const content = readFileSync32(full, "utf8");
12798
+ const content = readFileSync33(full, "utf8");
12602
12799
  if (content.length <= maxBytes) files.push({ path: rel, content });
12603
12800
  } catch {
12604
12801
  }
@@ -12617,12 +12814,12 @@ function cleanVersion(spec) {
12617
12814
  function gatherManifestVersions(repoRoot2) {
12618
12815
  const out = {};
12619
12816
  const dirs = [repoRoot2];
12620
- const pkgsDir = join33(repoRoot2, "packages");
12621
- if (existsSync33(pkgsDir)) {
12817
+ const pkgsDir = join34(repoRoot2, "packages");
12818
+ if (existsSync34(pkgsDir)) {
12622
12819
  try {
12623
12820
  for (const d of readdirSync9(pkgsDir)) {
12624
- const pd = join33(pkgsDir, d);
12625
- if (existsSync33(join33(pd, "package.json"))) dirs.push(pd);
12821
+ const pd = join34(pkgsDir, d);
12822
+ if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
12626
12823
  }
12627
12824
  } catch {
12628
12825
  }
@@ -12631,7 +12828,7 @@ function gatherManifestVersions(repoRoot2) {
12631
12828
  for (const dir of dirs) {
12632
12829
  let pkg;
12633
12830
  try {
12634
- pkg = JSON.parse(readFileSync32(join33(dir, "package.json"), "utf8"));
12831
+ pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
12635
12832
  } catch {
12636
12833
  continue;
12637
12834
  }
@@ -12651,28 +12848,28 @@ function findJelly(repoRoot2) {
12651
12848
  try {
12652
12849
  const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
12653
12850
  const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
12654
- const pkg = JSON.parse(readFileSync32(pkgJson, "utf8"));
12851
+ const pkg = JSON.parse(readFileSync33(pkgJson, "utf8"));
12655
12852
  const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
12656
12853
  if (bin) {
12657
- const p = join33(dir, bin);
12658
- if (existsSync33(p)) return p;
12854
+ const p = join34(dir, bin);
12855
+ if (existsSync34(p)) return p;
12659
12856
  }
12660
12857
  } catch {
12661
12858
  }
12662
12859
  for (const base of [repoRoot2, process.cwd()]) {
12663
- const b = join33(base, "node_modules", ".bin", "jelly");
12664
- if (existsSync33(b)) return b;
12860
+ const b = join34(base, "node_modules", ".bin", "jelly");
12861
+ if (existsSync34(b)) return b;
12665
12862
  }
12666
12863
  return null;
12667
12864
  }
12668
12865
  function findEntries(repoRoot2) {
12669
12866
  const dirs = [repoRoot2];
12670
- const pkgsDir = join33(repoRoot2, "packages");
12671
- if (existsSync33(pkgsDir)) {
12867
+ const pkgsDir = join34(repoRoot2, "packages");
12868
+ if (existsSync34(pkgsDir)) {
12672
12869
  try {
12673
12870
  for (const d of readdirSync9(pkgsDir)) {
12674
- const pd = join33(pkgsDir, d);
12675
- if (existsSync33(join33(pd, "package.json"))) dirs.push(pd);
12871
+ const pd = join34(pkgsDir, d);
12872
+ if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
12676
12873
  }
12677
12874
  } catch {
12678
12875
  }
@@ -12680,12 +12877,12 @@ function findEntries(repoRoot2) {
12680
12877
  const entries = [];
12681
12878
  for (const dir of dirs) {
12682
12879
  try {
12683
- const pkg = JSON.parse(readFileSync32(join33(dir, "package.json"), "utf8"));
12880
+ const pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
12684
12881
  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
12882
  for (const c of cands) {
12686
12883
  if (typeof c !== "string") continue;
12687
- const f = join33(dir, c);
12688
- if (existsSync33(f)) {
12884
+ const f = join34(dir, c);
12885
+ if (existsSync34(f)) {
12689
12886
  entries.push(f);
12690
12887
  break;
12691
12888
  }
@@ -12718,9 +12915,9 @@ function parseApiUsage(log) {
12718
12915
  }
12719
12916
  function runReachabilityScan(repoRoot2, opts = {}) {
12720
12917
  const commit = currentCommit(repoRoot2);
12721
- if (!opts.force && commit && existsSync33(REACHABILITY_PATH)) {
12918
+ if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
12722
12919
  try {
12723
- const prev = JSON.parse(readFileSync32(REACHABILITY_PATH, "utf8"));
12920
+ const prev = JSON.parse(readFileSync33(REACHABILITY_PATH, "utf8"));
12724
12921
  if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
12725
12922
  } catch {
12726
12923
  }
@@ -12771,7 +12968,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
12771
12968
  if (jelly) {
12772
12969
  const entries = findEntries(repoRoot2);
12773
12970
  if (entries.length > 0) {
12774
- const r = spawnSync11(
12971
+ const r = spawnSync12(
12775
12972
  process.execPath,
12776
12973
  [jelly, "-b", repoRoot2, "--api-usage", ...entries],
12777
12974
  { encoding: "utf8", timeout: opts.timeoutMs ?? 18e4, maxBuffer: 2e8 }
@@ -12809,7 +13006,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
12809
13006
  if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
12810
13007
  const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
12811
13008
  try {
12812
- writeFileSync23(REACHABILITY_PATH, JSON.stringify(file, null, 2));
13009
+ writeFileSync24(REACHABILITY_PATH, JSON.stringify(file, null, 2));
12813
13010
  } catch (e) {
12814
13011
  return { ok: false, reason: "write failed: " + String(e.message || e) };
12815
13012
  }
@@ -12821,7 +13018,7 @@ var init_reachabilityScan = __esm({
12821
13018
  "use strict";
12822
13019
  init_cveReachability();
12823
13020
  require2 = createRequire(import.meta.url);
12824
- REACHABILITY_PATH = join33(homedir33(), ".synkro", "reachability.json");
13021
+ REACHABILITY_PATH = join34(homedir34(), ".synkro", "reachability.json");
12825
13022
  }
12826
13023
  });
12827
13024
 
@@ -12830,15 +13027,15 @@ var reachabilityScan_exports = {};
12830
13027
  __export(reachabilityScan_exports, {
12831
13028
  reachabilityScanCommand: () => reachabilityScanCommand
12832
13029
  });
12833
- import { readFileSync as readFileSync33, existsSync as existsSync34 } from "fs";
12834
- import { join as join34 } from "path";
12835
- import { homedir as homedir34 } from "os";
13030
+ import { readFileSync as readFileSync34, existsSync as existsSync35 } from "fs";
13031
+ import { join as join35 } from "path";
13032
+ import { homedir as homedir35 } from "os";
12836
13033
  import { execFileSync as execFileSync6 } from "child_process";
12837
13034
  function readConfigEnv4() {
12838
- const p = join34(SYNKRO_DIR16, "config.env");
12839
- if (!existsSync34(p)) return {};
13035
+ const p = join35(SYNKRO_DIR17, "config.env");
13036
+ if (!existsSync35(p)) return {};
12840
13037
  const out = {};
12841
- for (const line of readFileSync33(p, "utf-8").split("\n")) {
13038
+ for (const line of readFileSync34(p, "utf-8").split("\n")) {
12842
13039
  const t = line.trim();
12843
13040
  if (!t || t.startsWith("#")) continue;
12844
13041
  const eq = t.indexOf("=");
@@ -12870,11 +13067,11 @@ async function pushToCloud(cfg, repo) {
12870
13067
  while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
12871
13068
  let jwt2 = "";
12872
13069
  try {
12873
- jwt2 = readFileSync33(join34(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
13070
+ jwt2 = readFileSync34(join35(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
12874
13071
  } catch {
12875
13072
  }
12876
- if (!jwt2 || !existsSync34(REACHABILITY_PATH)) return;
12877
- const body = readFileSync33(REACHABILITY_PATH, "utf-8");
13073
+ if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
13074
+ const body = readFileSync34(REACHABILITY_PATH, "utf-8");
12878
13075
  try {
12879
13076
  const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
12880
13077
  method: "POST",
@@ -12901,12 +13098,12 @@ async function reachabilityScanCommand(args2 = []) {
12901
13098
  const isCloud = cfg.SYNKRO_DEPLOY_LOCATION === "cloud" || cfg.SYNKRO_STORAGE_MODE === "cloud";
12902
13099
  if (isCloud) await pushToCloud(cfg, cfg.SYNKRO_CONNECTED_REPO || repoSlug(root));
12903
13100
  }
12904
- var SYNKRO_DIR16;
13101
+ var SYNKRO_DIR17;
12905
13102
  var init_reachabilityScan2 = __esm({
12906
13103
  "cli/commands/reachabilityScan.ts"() {
12907
13104
  "use strict";
12908
13105
  init_reachabilityScan();
12909
- SYNKRO_DIR16 = join34(homedir34(), ".synkro");
13106
+ SYNKRO_DIR17 = join35(homedir35(), ".synkro");
12910
13107
  }
12911
13108
  });
12912
13109
 
@@ -12935,9 +13132,9 @@ async function startCommand(rest = []) {
12935
13132
  assertDockerAvailable();
12936
13133
  const cfg = resolveWorkerConfig(rest);
12937
13134
  if (cfg.explicit) {
12938
- console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)
13135
+ console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor + ${cfg.codexWorkers} codex)
12939
13136
  `);
12940
- await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });
13137
+ await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, connectedRepo: resolveConnectedRepo() });
12941
13138
  const ready = await waitForContainerReady(6e4);
12942
13139
  if (!ready) {
12943
13140
  console.error("\n\u26A0 container did not pass /healthz within 60s");
@@ -12964,10 +13161,11 @@ async function updateCommand() {
12964
13161
  }
12965
13162
  const claudeWorkers = cfg.claudeWorkers ?? 8;
12966
13163
  const cursorWorkers = cfg.cursorWorkers ?? 0;
13164
+ const codexWorkers = cfg.codexWorkers ?? 0;
12967
13165
  console.log("Synkro: updating to the latest container image");
12968
- console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor worker(s)
13166
+ console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex worker(s)
12969
13167
  `);
12970
- await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });
13168
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, connectedRepo: resolveConnectedRepo() });
12971
13169
  const ready = await waitForContainerReady(9e4);
12972
13170
  if (!ready) {
12973
13171
  console.error("\n\u26A0 container did not pass its health check within 90s \u2014 check: docker logs synkro-server");
@@ -12991,16 +13189,18 @@ async function restartCommand(rest = []) {
12991
13189
  const cfg = resolveWorkerConfig(rest);
12992
13190
  let claudeWorkers = cfg.claudeWorkers;
12993
13191
  let cursorWorkers = cfg.cursorWorkers;
13192
+ let codexWorkers = cfg.codexWorkers;
12994
13193
  if (!cfg.explicit) {
12995
13194
  const reconciled = reconcileHarness();
12996
13195
  if (reconciled) {
12997
13196
  claudeWorkers = reconciled.claudeWorkers;
12998
13197
  cursorWorkers = reconciled.cursorWorkers;
13198
+ codexWorkers = reconciled.codexWorkers;
12999
13199
  }
13000
13200
  }
13001
- console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor)
13201
+ console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)
13002
13202
  `);
13003
- await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });
13203
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, connectedRepo: resolveConnectedRepo() });
13004
13204
  const ready = await waitForContainerReady(6e4);
13005
13205
  if (!ready) {
13006
13206
  console.error("\n\u26A0 container did not pass /healthz within 60s");
@@ -13028,13 +13228,13 @@ var config_exports = {};
13028
13228
  __export(config_exports, {
13029
13229
  configCommand: () => configCommand
13030
13230
  });
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";
13231
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync25, existsSync as existsSync36 } from "fs";
13232
+ import { join as join36 } from "path";
13233
+ import { homedir as homedir36 } from "os";
13034
13234
  function readConfigEnv5() {
13035
- if (!existsSync35(CONFIG_PATH9)) return {};
13235
+ if (!existsSync36(CONFIG_PATH9)) return {};
13036
13236
  const out = {};
13037
- for (const line of readFileSync34(CONFIG_PATH9, "utf-8").split("\n")) {
13237
+ for (const line of readFileSync35(CONFIG_PATH9, "utf-8").split("\n")) {
13038
13238
  const t = line.trim();
13039
13239
  if (!t || t.startsWith("#")) continue;
13040
13240
  const eq = t.indexOf("=");
@@ -13043,11 +13243,11 @@ function readConfigEnv5() {
13043
13243
  return out;
13044
13244
  }
13045
13245
  function updateConfigValue(key, value) {
13046
- if (!existsSync35(CONFIG_PATH9)) {
13246
+ if (!existsSync36(CONFIG_PATH9)) {
13047
13247
  console.error("No config found. Run `synkro install` first.");
13048
13248
  process.exit(1);
13049
13249
  }
13050
- const lines = readFileSync34(CONFIG_PATH9, "utf-8").split("\n");
13250
+ const lines = readFileSync35(CONFIG_PATH9, "utf-8").split("\n");
13051
13251
  const pattern = new RegExp(`^${key}=`);
13052
13252
  let found = false;
13053
13253
  const updated = lines.map((line) => {
@@ -13058,7 +13258,7 @@ function updateConfigValue(key, value) {
13058
13258
  return line;
13059
13259
  });
13060
13260
  if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
13061
- writeFileSync24(CONFIG_PATH9, updated.join("\n"), "utf-8");
13261
+ writeFileSync25(CONFIG_PATH9, updated.join("\n"), "utf-8");
13062
13262
  }
13063
13263
  function resolveInferenceMode(cfg) {
13064
13264
  if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
@@ -13210,14 +13410,14 @@ To change:`);
13210
13410
  }
13211
13411
  if (inferenceValue !== "cloud") await reconcileContainer();
13212
13412
  }
13213
- var SYNKRO_DIR17, CONFIG_PATH9;
13413
+ var SYNKRO_DIR18, CONFIG_PATH9;
13214
13414
  var init_config = __esm({
13215
13415
  "cli/commands/config.ts"() {
13216
13416
  "use strict";
13217
13417
  init_stub();
13218
13418
  init_optout();
13219
- SYNKRO_DIR17 = join35(homedir35(), ".synkro");
13220
- CONFIG_PATH9 = join35(SYNKRO_DIR17, "config.env");
13419
+ SYNKRO_DIR18 = join36(homedir36(), ".synkro");
13420
+ CONFIG_PATH9 = join36(SYNKRO_DIR18, "config.env");
13221
13421
  }
13222
13422
  });
13223
13423
 
@@ -13406,14 +13606,14 @@ Usage:
13406
13606
  });
13407
13607
 
13408
13608
  // cli/bootstrap.js
13409
- import { readFileSync as readFileSync35, existsSync as existsSync36 } from "fs";
13609
+ import { readFileSync as readFileSync36, existsSync as existsSync37 } from "fs";
13410
13610
  import { resolve as resolve5 } from "path";
13411
13611
  var envCandidates = [
13412
13612
  resolve5(process.env.HOME ?? "", ".synkro", "config.env")
13413
13613
  ];
13414
13614
  for (const envPath of envCandidates) {
13415
- if (!existsSync36(envPath)) continue;
13416
- const envContent = readFileSync35(envPath, "utf-8");
13615
+ if (!existsSync37(envPath)) continue;
13616
+ const envContent = readFileSync36(envPath, "utf-8");
13417
13617
  for (const line of envContent.split("\n")) {
13418
13618
  const trimmed = line.trim();
13419
13619
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -13430,7 +13630,7 @@ var subArgs = args.slice(1);
13430
13630
  var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
13431
13631
  var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
13432
13632
  function printVersion() {
13433
- console.log("1.7.86");
13633
+ console.log("1.7.87");
13434
13634
  }
13435
13635
  function printHelp2() {
13436
13636
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents