@synkro-sh/cli 1.7.18 → 1.7.27

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
@@ -269,6 +269,10 @@ function installCCHooks(settingsPath, config) {
269
269
  ],
270
270
  [SYNKRO_MARKER]: true
271
271
  });
272
+ if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {
273
+ delete settings.env.ANTHROPIC_BASE_URL;
274
+ if (Object.keys(settings.env).length === 0) delete settings.env;
275
+ }
272
276
  writeSettingsAtomic(settingsPath, settings);
273
277
  }
274
278
  function uninstallCCHooks(settingsPath) {
@@ -287,14 +291,19 @@ function uninstallCCHooks(settingsPath) {
287
291
  if (Object.keys(settings.hooks).length === 0) {
288
292
  delete settings.hooks;
289
293
  }
294
+ if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {
295
+ delete settings.env.ANTHROPIC_BASE_URL;
296
+ if (Object.keys(settings.env).length === 0) delete settings.env;
297
+ }
290
298
  writeSettingsAtomic(settingsPath, settings);
291
299
  return true;
292
300
  }
293
- var SYNKRO_MARKER;
301
+ var SYNKRO_MARKER, USAGE_PROXY_URL;
294
302
  var init_ccHookConfig = __esm({
295
303
  "cli/installer/ccHookConfig.ts"() {
296
304
  "use strict";
297
305
  SYNKRO_MARKER = "__synkro_managed__";
306
+ USAGE_PROXY_URL = `http://127.0.0.1:${process.env.SYNKRO_HOST_USAGE_PROXY_PORT || "18927"}`;
298
307
  }
299
308
  });
300
309
 
@@ -7133,6 +7142,15 @@ async function main() {
7133
7142
  const usage = aggregateUsage(transcriptPath, { modelFallback });
7134
7143
  emitUsageTick({ sessionId, usage, hookType: 'prompt_submit', gitRepo, modelFallback });
7135
7144
  }
7145
+ // Nudge the local container to refresh account-wide coding-agent usage OUT OF BAND.
7146
+ // The endpoint returns 202 immediately and does its own Anthropic ping, so this
7147
+ // never gates the prompt; a stopped container just fails the fetch fast (caught).
7148
+ try {
7149
+ const usagePort = process.env.SYNKRO_MCP_HOST_PORT || '18931';
7150
+ await fetch('http://127.0.0.1:' + usagePort + '/api/local/usage-poll', {
7151
+ method: 'POST', signal: AbortSignal.timeout(1200),
7152
+ }).catch(() => {});
7153
+ } catch {}
7136
7154
  outputEmpty();
7137
7155
  } catch {
7138
7156
  outputEmpty();
@@ -10550,13 +10568,14 @@ async function dockerInstall(opts = {}) {
10550
10568
  }
10551
10569
  mkdirSync8(CURSOR_CREDS_DIR, { recursive: true });
10552
10570
  if (needsKeychainBridge()) {
10553
- if (claudeWorkers > 0) {
10554
- const path = exportKeychainCreds();
10555
- if (!path) {
10571
+ const claudeCredsPath = exportKeychainCreds();
10572
+ if (!claudeCredsPath) {
10573
+ if (claudeWorkers > 0) {
10556
10574
  throw new DockerInstallError(
10557
10575
  "Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container."
10558
10576
  );
10559
10577
  }
10578
+ console.warn(" \u26A0 Claude Code keychain entry not found \u2014 live usage telemetry stays off until you sign in to Claude Code (grading is unaffected).");
10560
10579
  }
10561
10580
  if (cursorWorkers > 0 && !cursorApiKeyConfigured()) {
10562
10581
  console.warn(" \u26A0 No Cursor API key found \u2014 Cursor grader workers will be idle.");
@@ -10979,6 +10998,87 @@ var init_setupToken = __esm({
10979
10998
  }
10980
10999
  });
10981
11000
 
11001
+ // cli/local-cc/codexCloudSetup.ts
11002
+ var codexCloudSetup_exports = {};
11003
+ __export(codexCloudSetup_exports, {
11004
+ setupCodexCloud: () => setupCodexCloud
11005
+ });
11006
+ import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
11007
+ import { readFileSync as readFileSync9, mkdirSync as mkdirSync9, rmSync, existsSync as existsSync10 } from "fs";
11008
+ import { homedir as homedir9 } from "os";
11009
+ import { join as join9 } from "path";
11010
+ function findCodexBinary() {
11011
+ if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
11012
+ const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
11013
+ const p = (r.stdout || "").trim().split("\n")[0];
11014
+ return p || null;
11015
+ }
11016
+ function runCodexLogin(codexBin) {
11017
+ mkdirSync9(CODEX_CLOUD_HOME, { recursive: true, mode: 448 });
11018
+ return new Promise((resolve4, reject) => {
11019
+ const proc = nodeSpawn2(codexBin, ["login"], {
11020
+ stdio: "inherit",
11021
+ env: { ...process.env, CODEX_HOME: CODEX_CLOUD_HOME }
11022
+ });
11023
+ proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
11024
+ proc.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`codex login exited with code ${code}`)));
11025
+ });
11026
+ }
11027
+ async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
11028
+ const codexBin = findCodexBinary();
11029
+ if (!codexBin) {
11030
+ 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.)" };
11031
+ }
11032
+ onStatus?.("Opening your browser to authorize your Codex subscription for Synkro Cloud\u2026");
11033
+ try {
11034
+ await runCodexLogin(codexBin);
11035
+ } catch (e) {
11036
+ return { ok: false, error: `Codex login failed: ${e.message}` };
11037
+ }
11038
+ const authPath = join9(CODEX_CLOUD_HOME, "auth.json");
11039
+ if (!existsSync10(authPath)) {
11040
+ return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
11041
+ }
11042
+ let auth;
11043
+ try {
11044
+ auth = JSON.parse(readFileSync9(authPath, "utf-8"));
11045
+ } catch (e) {
11046
+ return { ok: false, error: `could not read codex auth.json: ${e.message}` };
11047
+ }
11048
+ if (!auth || typeof auth !== "object" || !auth.tokens?.refresh_token) {
11049
+ return { ok: false, error: "codex auth.json has no refresh token \u2014 cloud refresh would fail. Re-run login." };
11050
+ }
11051
+ onStatus?.("Connecting your Codex session to Synkro Cloud\u2026");
11052
+ let resp;
11053
+ try {
11054
+ resp = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/seed`, {
11055
+ method: "POST",
11056
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
11057
+ body: JSON.stringify({ auth })
11058
+ });
11059
+ } catch (e) {
11060
+ return { ok: false, error: `failed to reach Synkro API: ${e.message}` };
11061
+ }
11062
+ if (!resp.ok) {
11063
+ const detail = await resp.text().catch(() => "");
11064
+ return { ok: false, error: `seed rejected (${resp.status}): ${detail.slice(0, 200)}` };
11065
+ }
11066
+ try {
11067
+ rmSync(CODEX_CLOUD_HOME, { recursive: true, force: true });
11068
+ } catch {
11069
+ }
11070
+ onStatus?.("\u2713 Codex subscription connected to Synkro Cloud.");
11071
+ return { ok: true };
11072
+ }
11073
+ var SYNKRO_DIR4, CODEX_CLOUD_HOME;
11074
+ var init_codexCloudSetup = __esm({
11075
+ "cli/local-cc/codexCloudSetup.ts"() {
11076
+ "use strict";
11077
+ SYNKRO_DIR4 = join9(homedir9(), ".synkro");
11078
+ CODEX_CLOUD_HOME = join9(SYNKRO_DIR4, "codex-cloud-session");
11079
+ }
11080
+ });
11081
+
10982
11082
  // cli/commands/setupGithub.ts
10983
11083
  var setupGithub_exports = {};
10984
11084
  __export(setupGithub_exports, {
@@ -10988,14 +11088,14 @@ __export(setupGithub_exports, {
10988
11088
  import { createInterface as createInterface2 } from "readline/promises";
10989
11089
  import { stdin as input, stdout as output } from "process";
10990
11090
  import { execSync as execSync5 } from "child_process";
10991
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
10992
- import { homedir as homedir9, platform as platform4 } from "os";
10993
- import { join as join9 } from "path";
11091
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
11092
+ import { homedir as homedir10, platform as platform4 } from "os";
11093
+ import { join as join10 } from "path";
10994
11094
  import { execFile as execFile2 } from "child_process";
10995
11095
  function readConfig() {
10996
- if (!existsSync10(CONFIG_PATH)) return {};
11096
+ if (!existsSync11(CONFIG_PATH)) return {};
10997
11097
  const out = {};
10998
- for (const line of readFileSync9(CONFIG_PATH, "utf-8").split("\n")) {
11098
+ for (const line of readFileSync10(CONFIG_PATH, "utf-8").split("\n")) {
10999
11099
  const t = line.trim();
11000
11100
  if (!t || t.startsWith("#")) continue;
11001
11101
  const eq = t.indexOf("=");
@@ -11295,15 +11395,15 @@ Will push secrets to ${selected.length} repo(s):`);
11295
11395
  console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);
11296
11396
  console.log("Open a PR on any selected repo to trigger your first Synkro scan.");
11297
11397
  }
11298
- var SYNKRO_DIR4, CONFIG_PATH;
11398
+ var SYNKRO_DIR5, CONFIG_PATH;
11299
11399
  var init_setupGithub = __esm({
11300
11400
  "cli/commands/setupGithub.ts"() {
11301
11401
  "use strict";
11302
11402
  init_githubSetup();
11303
11403
  init_stub();
11304
11404
  init_setupToken();
11305
- SYNKRO_DIR4 = join9(homedir9(), ".synkro");
11306
- CONFIG_PATH = join9(SYNKRO_DIR4, "config.env");
11405
+ SYNKRO_DIR5 = join10(homedir10(), ".synkro");
11406
+ CONFIG_PATH = join10(SYNKRO_DIR5, "config.env");
11307
11407
  }
11308
11408
  });
11309
11409
 
@@ -11322,9 +11422,9 @@ __export(install_exports, {
11322
11422
  syncSkillFiles: () => syncSkillFiles,
11323
11423
  writeHookScripts: () => writeHookScripts
11324
11424
  });
11325
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, chmodSync as chmodSync2, readFileSync as readFileSync10, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync } from "fs";
11326
- import { homedir as homedir10 } from "os";
11327
- import { join as join10 } from "path";
11425
+ import { existsSync as existsSync12, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, chmodSync as chmodSync2, readFileSync as readFileSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync } from "fs";
11426
+ import { homedir as homedir11 } from "os";
11427
+ import { join as join11 } from "path";
11328
11428
  import { execSync as execSync6 } from "child_process";
11329
11429
  import { createInterface as createInterface3 } from "readline";
11330
11430
  import { createHash } from "crypto";
@@ -11332,7 +11432,7 @@ function resolvePersistedHookMode() {
11332
11432
  if (process.env.SYNKRO_HOOK_MODE === "full") return "full";
11333
11433
  if (process.env.SYNKRO_HOOK_MODE === "stub") return "stub";
11334
11434
  try {
11335
- const env = readFileSync10(CONFIG_PATH2, "utf-8");
11435
+ const env = readFileSync11(CONFIG_PATH2, "utf-8");
11336
11436
  if (/^SYNKRO_HOOK_MODE=['"]?full['"]?\s*$/m.test(env)) return "full";
11337
11437
  } catch {
11338
11438
  }
@@ -11468,37 +11568,51 @@ coding patterns and the dashboard shows full history. (Y/n) `
11468
11568
  rl.close();
11469
11569
  }
11470
11570
  }
11571
+ async function promptCodexCloudSetup() {
11572
+ if (!process.stdin.isTTY) return false;
11573
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
11574
+ return new Promise((resolve4) => {
11575
+ rl.question(
11576
+ "Connect a Codex (ChatGPT subscription) session for cloud grading? This opens a\nbrowser to authorize; your subscription is stored securely and refreshed for you. (y/N) ",
11577
+ (answer) => {
11578
+ rl.close();
11579
+ const a = answer.trim().toLowerCase();
11580
+ resolve4(a === "y" || a === "yes");
11581
+ }
11582
+ );
11583
+ });
11584
+ }
11471
11585
  function ensureSynkroDir() {
11472
- mkdirSync9(SYNKRO_DIR5, { recursive: true });
11473
- mkdirSync9(HOOKS_DIR, { recursive: true });
11474
- mkdirSync9(BIN_DIR, { recursive: true });
11475
- mkdirSync9(OFFSETS_DIR, { recursive: true });
11476
- mkdirSync9(join10(SYNKRO_DIR5, "sessions"), { recursive: true });
11586
+ mkdirSync10(SYNKRO_DIR6, { recursive: true });
11587
+ mkdirSync10(HOOKS_DIR, { recursive: true });
11588
+ mkdirSync10(BIN_DIR, { recursive: true });
11589
+ mkdirSync10(OFFSETS_DIR, { recursive: true });
11590
+ mkdirSync10(join11(SYNKRO_DIR6, "sessions"), { recursive: true });
11477
11591
  }
11478
11592
  function writeHookScripts(mode = "stub") {
11479
- const installExtractCorePath = join10(HOOKS_DIR, "installExtractCore.ts");
11480
- const bashScriptPath = join10(HOOKS_DIR, "cc-bash-judge.ts");
11481
- const bashFollowupScriptPath = join10(HOOKS_DIR, "cc-bash-followup.ts");
11482
- const editPrecheckScriptPath = join10(HOOKS_DIR, "cc-edit-precheck.ts");
11483
- const cwePrecheckScriptPath = join10(HOOKS_DIR, "cc-cwe-precheck.ts");
11484
- const cvePrecheckScriptPath = join10(HOOKS_DIR, "cc-cve-precheck.ts");
11485
- const planJudgeScriptPath = join10(HOOKS_DIR, "cc-plan-judge.ts");
11486
- const agentJudgeScriptPath = join10(HOOKS_DIR, "cc-agent-judge.ts");
11487
- const stopSummaryScriptPath = join10(HOOKS_DIR, "cc-stop-summary.ts");
11488
- const sessionStartScriptPath = join10(HOOKS_DIR, "cc-session-start.ts");
11489
- const transcriptSyncScriptPath = join10(HOOKS_DIR, "cc-transcript-sync.ts");
11490
- const userPromptSubmitScriptPath = join10(HOOKS_DIR, "cc-user-prompt-submit.ts");
11491
- const commonScriptPath = join10(HOOKS_DIR, "_synkro-common.ts");
11492
- const commonBashScriptPath = join10(HOOKS_DIR, "_synkro-common.sh");
11493
- const installScanScriptPath = join10(HOOKS_DIR, "cc-install-scan.ts");
11494
- const cursorBashJudgePath = join10(HOOKS_DIR, "cursor-bash-judge.ts");
11495
- const cursorEditCapturePath = join10(HOOKS_DIR, "cursor-edit-capture.ts");
11496
- const cursorAgentCapturePath = join10(HOOKS_DIR, "cursor-agent-capture.ts");
11497
- const mcpStdioProxyPath = join10(HOOKS_DIR, "mcp-stdio-proxy.ts");
11498
- const taskActivateIntentScriptPath = join10(HOOKS_DIR, "cc-task-activate-intent.ts");
11499
- const mcpGateScriptPath = join10(HOOKS_DIR, "cc-mcp-gate.ts");
11593
+ const installExtractCorePath = join11(HOOKS_DIR, "installExtractCore.ts");
11594
+ const bashScriptPath = join11(HOOKS_DIR, "cc-bash-judge.ts");
11595
+ const bashFollowupScriptPath = join11(HOOKS_DIR, "cc-bash-followup.ts");
11596
+ const editPrecheckScriptPath = join11(HOOKS_DIR, "cc-edit-precheck.ts");
11597
+ const cwePrecheckScriptPath = join11(HOOKS_DIR, "cc-cwe-precheck.ts");
11598
+ const cvePrecheckScriptPath = join11(HOOKS_DIR, "cc-cve-precheck.ts");
11599
+ const planJudgeScriptPath = join11(HOOKS_DIR, "cc-plan-judge.ts");
11600
+ const agentJudgeScriptPath = join11(HOOKS_DIR, "cc-agent-judge.ts");
11601
+ const stopSummaryScriptPath = join11(HOOKS_DIR, "cc-stop-summary.ts");
11602
+ const sessionStartScriptPath = join11(HOOKS_DIR, "cc-session-start.ts");
11603
+ const transcriptSyncScriptPath = join11(HOOKS_DIR, "cc-transcript-sync.ts");
11604
+ const userPromptSubmitScriptPath = join11(HOOKS_DIR, "cc-user-prompt-submit.ts");
11605
+ const commonScriptPath = join11(HOOKS_DIR, "_synkro-common.ts");
11606
+ const commonBashScriptPath = join11(HOOKS_DIR, "_synkro-common.sh");
11607
+ const installScanScriptPath = join11(HOOKS_DIR, "cc-install-scan.ts");
11608
+ const cursorBashJudgePath = join11(HOOKS_DIR, "cursor-bash-judge.ts");
11609
+ const cursorEditCapturePath = join11(HOOKS_DIR, "cursor-edit-capture.ts");
11610
+ const cursorAgentCapturePath = join11(HOOKS_DIR, "cursor-agent-capture.ts");
11611
+ const mcpStdioProxyPath = join11(HOOKS_DIR, "mcp-stdio-proxy.ts");
11612
+ const taskActivateIntentScriptPath = join11(HOOKS_DIR, "cc-task-activate-intent.ts");
11613
+ const mcpGateScriptPath = join11(HOOKS_DIR, "cc-mcp-gate.ts");
11500
11614
  if (mode === "stub") {
11501
- const stubCommonPath2 = join10(HOOKS_DIR, "_synkro-stub-common.ts");
11615
+ const stubCommonPath2 = join11(HOOKS_DIR, "_synkro-stub-common.ts");
11502
11616
  const stubFiles = [
11503
11617
  [stubCommonPath2, STUB_COMMON_TS],
11504
11618
  [bashScriptPath, STUB_BASH_JUDGE_TS],
@@ -11527,7 +11641,7 @@ function writeHookScripts(mode = "stub") {
11527
11641
  chmodSync2(mcpStdioProxyPath, 493);
11528
11642
  for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
11529
11643
  try {
11530
- unlinkSync4(join10(HOOKS_DIR, stale));
11644
+ unlinkSync4(join11(HOOKS_DIR, stale));
11531
11645
  } catch {
11532
11646
  }
11533
11647
  }
@@ -11571,7 +11685,7 @@ function writeHookScripts(mode = "stub") {
11571
11685
  writeFileSync8(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
11572
11686
  writeFileSync8(installExtractCorePath, "/**\n * Deterministic install-command extraction \u2014 no LLM, no network.\n * Shared by the API pkg-scan route and the hook scripts (copied to ~/.synkro/hooks/).\n */\nimport { parse as shellParse } from 'shell-quote';\n\nexport interface DeterministicPkgRequest {\n name: string;\n version: string;\n ecosystem: string;\n}\n\ninterface RawInstall {\n ecosystem: string;\n name: string;\n versionSpec: string | null;\n source: 'registry' | 'git' | 'local' | 'url' | 'unknown';\n}\n\nconst SEPARATOR_OPS = new Set(['&&', '||', ';', '|', '&', '\\n']);\n\n/** Split a shell command into command segments (each an argv string array). */\nexport function segmentCommand(command: string): string[][] {\n let tokens: unknown[];\n try {\n tokens = shellParse(command) as unknown[];\n } catch {\n return [command.split(/\\s+/).filter(Boolean)];\n }\n const segments: string[][] = [];\n let current: string[] = [];\n for (const tok of tokens) {\n if (typeof tok === 'string') {\n current.push(tok);\n continue;\n }\n if (tok && typeof tok === 'object') {\n const op = (tok as { op?: string }).op;\n const pattern = (tok as { pattern?: string }).pattern;\n if (op && SEPARATOR_OPS.has(op)) {\n if (current.length) segments.push(current);\n current = [];\n } else if (typeof pattern === 'string') {\n current.push(pattern);\n }\n }\n }\n if (current.length) segments.push(current);\n return segments;\n}\n\nconst PM_TABLE: Record<string, { subs: Set<string>; ecosystem: string }> = {\n npm: { subs: new Set(['install', 'i', 'add', 'ci']), ecosystem: 'npm' },\n pnpm: { subs: new Set(['add', 'install', 'i', 'dlx']), ecosystem: 'npm' },\n yarn: { subs: new Set(['add', 'install']), ecosystem: 'npm' },\n bun: { subs: new Set(['add', 'install', 'i']), ecosystem: 'npm' },\n pip: { subs: new Set(['install']), ecosystem: 'PyPI' },\n pip3: { subs: new Set(['install']), ecosystem: 'PyPI' },\n cargo: { subs: new Set(['add', 'install']), ecosystem: 'crates.io' },\n go: { subs: new Set(['get', 'install']), ecosystem: 'Go' },\n gem: { subs: new Set(['install']), ecosystem: 'RubyGems' },\n composer: { subs: new Set(['require']), ecosystem: 'Packagist' },\n};\nconst SYSTEM_PMS = new Set(['apt', 'apt-get', 'apk', 'brew', 'dnf', 'yum', 'pacman']);\nconst SYSTEM_SUBS = new Set(['install', 'add']);\n\nconst WRAPPERS = new Set(['sudo', 'doas', 'command', 'env', 'xargs', 'nice', 'time']);\nconst VALUE_FLAGS = new Set([\n '--filter', '-F', '-C', '--dir', '--prefix', '--registry', '--tag', '--features',\n '-v', '--version', '--index-url', '--extra-index-url', '--target', '-t',\n]);\n\nfunction basename(p: string): string {\n const i = p.lastIndexOf('/');\n return i >= 0 ? p.slice(i + 1) : p;\n}\n\nfunction stripPrefixes(argv: string[]): string[] {\n let i = 0;\n while (i < argv.length) {\n const t = argv[i];\n if (WRAPPERS.has(basename(t).toLowerCase())) { i++; continue; }\n if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(t)) { i++; continue; }\n break;\n }\n return argv.slice(i);\n}\n\nfunction looksLikePath(tok: string): boolean {\n return tok === '.' || tok === '..' || /^\\.{0,2}\\//.test(tok) || tok.startsWith('~/') || tok.startsWith('file:');\n}\n\n/** Shell redirect fragments (e.g. `2>&1` \u2192 argv `2`, `1`) \u2014 not package names. */\nfunction isRedirectFragment(tok: string): boolean {\n if (/^\\d+$/.test(tok)) return true;\n if (/^[<>]|[<>]$/.test(tok)) return true;\n if (tok === '&' || tok === '|') return true;\n if (/^\\d*[<>]/.test(tok)) return true;\n return false;\n}\n\nfunction parsePackageToken(tok: string, ecosystem: string): RawInstall | null {\n if (/^(https?:)?\\/\\//.test(tok) || tok.startsWith('git+') || tok.startsWith('git:')) {\n return { ecosystem, name: tok, versionSpec: null, source: tok.includes('git') ? 'git' : 'url' };\n }\n if (looksLikePath(tok)) {\n return { ecosystem, name: basename(tok.replace(/\\/+$/, '')) || tok, versionSpec: null, source: 'local' };\n }\n if (ecosystem === 'PyPI') {\n const noExtras = tok.replace(/\\[[^\\]]*\\]/g, '');\n const m = noExtras.match(/^([A-Za-z0-9_.-]+)\\s*([=~!<>].*)?$/);\n if (!m) return null;\n return { ecosystem, name: m[1], versionSpec: m[2] ? m[2].trim() : null, source: 'registry' };\n }\n if (ecosystem === 'Packagist') {\n // composer uses vendor/package:version-constraint (e.g. monolog/monolog:1.0.0).\n // Split on the first ':' after the vendor/ slash; never on the '/'.\n const ci = tok.indexOf(':');\n if (ci > 0) return { ecosystem, name: tok.slice(0, ci), versionSpec: tok.slice(ci + 1) || null, source: 'registry' };\n return { ecosystem, name: tok, versionSpec: null, source: 'registry' };\n }\n const at = tok.lastIndexOf('@');\n if (at > 0) {\n return { ecosystem, name: tok.slice(0, at), versionSpec: tok.slice(at + 1) || null, source: 'registry' };\n }\n return { ecosystem, name: tok, versionSpec: null, source: 'registry' };\n}\n\n/** Deterministic extraction for a single command segment. */\nexport function extractSegment(rawArgv: string[]): RawInstall[] {\n let argv = stripPrefixes(rawArgv);\n if (argv.length < 2) return [];\n let bin = basename(argv[0]).toLowerCase();\n\n if (bin === 'uv' && argv[1] === 'pip') { argv = argv.slice(1); bin = 'pip'; }\n if ((bin === 'python' || bin === 'python3') && argv.includes('-m')) {\n const mi = argv.indexOf('-m');\n if (argv[mi + 1] === 'pip') { argv = ['pip', ...argv.slice(mi + 2)]; bin = 'pip'; }\n }\n\n const isSystem = SYSTEM_PMS.has(bin);\n const entry = PM_TABLE[bin];\n if (!entry && !isSystem) return [];\n const ecosystem = entry ? entry.ecosystem : 'system';\n const subs = entry ? entry.subs : SYSTEM_SUBS;\n\n let subIdx = -1;\n for (let i = 1; i < argv.length; i++) {\n if (subs.has(argv[i].toLowerCase())) { subIdx = i; break; }\n }\n if (subIdx === -1) return [];\n\n const installs: RawInstall[] = [];\n // gem pins the version with a separate `-v`/`--version` flag rather than an\n // inline spec; capture it and apply to the package(s) in this segment.\n let flagVersion: string | null = null;\n for (let i = subIdx + 1; i < argv.length; i++) {\n const tok = argv[i];\n if (isRedirectFragment(tok)) break;\n if (tok.startsWith('-')) {\n const lower = tok.toLowerCase();\n if (ecosystem === 'RubyGems' && (lower === '-v' || lower === '--version')) {\n flagVersion = (argv[i + 1] || '').replace(/^[v=\\s]+/, '') || null;\n i++;\n continue;\n }\n if (VALUE_FLAGS.has(tok)) i++;\n continue;\n }\n const parsed = parsePackageToken(tok, ecosystem);\n if (parsed) installs.push(parsed);\n }\n if (flagVersion) {\n for (const ins of installs) {\n if (ins.source === 'registry' && !ins.versionSpec) ins.versionSpec = flagVersion;\n }\n }\n return installs;\n}\n\nconst ECO_TO_OSV: Record<string, string | null> = {\n npm: 'npm',\n pypi: 'PyPI', PyPI: 'PyPI',\n cargo: 'crates.io', 'crates.io': 'crates.io',\n go: 'Go', Go: 'Go',\n rubygems: 'RubyGems', RubyGems: 'RubyGems',\n packagist: 'Packagist', Packagist: 'Packagist',\n maven: 'Maven', Maven: 'Maven',\n nuget: 'NuGet', NuGet: 'NuGet',\n apt: null, brew: null, system: null, other: null,\n};\n\nfunction normalizeName(name: string, osvEco: string): string {\n const n = name.trim();\n if (osvEco === 'npm') return n.toLowerCase();\n if (osvEco === 'PyPI') return n.toLowerCase().replace(/[-_.]+/g, '-');\n return n;\n}\n\nfunction concretePin(spec: string | null): string | null {\n if (!spec) return null;\n const c = spec.trim().replace(/^[v=\\s]+/, '');\n if (c.toLowerCase() === 'latest' || c === '') return null;\n if (/[\\^~><|*\\sx]/i.test(c)) return null;\n return /^\\d[\\w.\\-+]*$/.test(c) ? c : null;\n}\n\nconst PKG_JSON_DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];\n\nfunction safeParseObject(text: string): Record<string, any> | null {\n try {\n const v = JSON.parse(text);\n return v && typeof v === 'object' && !Array.isArray(v) ? v : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Diff two package.json contents and return the registry packages that are\n * newly added or whose version spec changed in the new content. The caller\n * scans these against the vuln DB before letting the edit land \u2014 so a bare\n * `pnpm install` afterwards has nothing left to vet. Non-registry sources\n * (file:, link:, workspace:, git, http, relative paths) are skipped.\n */\nexport function extractPackageJsonDelta(oldText: string, newText: string): DeterministicPkgRequest[] {\n const newJson = safeParseObject(newText);\n if (!newJson) return [];\n const oldJson = safeParseObject(oldText) || {};\n\n const out = new Map<string, DeterministicPkgRequest>();\n for (const field of PKG_JSON_DEP_FIELDS) {\n const oldDeps = (oldJson[field] && typeof oldJson[field] === 'object') ? oldJson[field] : {};\n const newDeps = (newJson[field] && typeof newJson[field] === 'object') ? newJson[field] : {};\n for (const [rawName, version] of Object.entries(newDeps)) {\n if (typeof version !== 'string') continue;\n if (oldDeps[rawName] === version) continue;\n const spec = version.trim();\n if (\n spec.startsWith('file:') || spec.startsWith('link:') ||\n spec.startsWith('http') || spec.startsWith('git') ||\n spec.startsWith('workspace:') || spec.startsWith('catalog:') ||\n spec.startsWith('npm:') ||\n spec.startsWith('./') || spec.startsWith('../') ||\n spec === '' || spec === '*'\n ) continue;\n const name = rawName.toLowerCase();\n out.set(name, {\n name,\n version: concretePin(spec) ?? '*',\n ecosystem: 'npm',\n });\n }\n }\n return [...out.values()];\n}\n\n/**\n * Parse registry installs from a shell command without LLM/network.\n * Unpinned versions use '*' so OSV scans the full advisory history.\n */\nexport function extractDeterministicPkgRequests(command: string): DeterministicPkgRequest[] {\n const merged = new Map<string, DeterministicPkgRequest>();\n for (const r of segmentCommand(command).flatMap(extractSegment)) {\n if (r.source !== 'registry') continue;\n const osvEco = ECO_TO_OSV[r.ecosystem] ?? ECO_TO_OSV[r.ecosystem.toLowerCase()] ?? null;\n if (!osvEco) continue;\n const name = normalizeName(r.name, osvEco);\n if (!name) continue;\n const key = osvEco + '|' + name.toLowerCase();\n const version = concretePin(r.versionSpec) ?? '*';\n const prev = merged.get(key);\n if (!prev || (prev.version === '*' && version !== '*')) {\n merged.set(key, { name, version, ecosystem: osvEco });\n }\n }\n return [...merged.values()];\n}\n", "utf-8");
11573
11687
  writeFileSync8(taskActivateIntentScriptPath, STUB_TASK_ACTIVATE_INTENT_TS, "utf-8");
11574
- const stubCommonPath = join10(HOOKS_DIR, "_synkro-stub-common.ts");
11688
+ const stubCommonPath = join11(HOOKS_DIR, "_synkro-stub-common.ts");
11575
11689
  writeFileSync8(stubCommonPath, STUB_COMMON_TS, "utf-8");
11576
11690
  chmodSync2(stubCommonPath, 493);
11577
11691
  writeFileSync8(mcpGateScriptPath, STUB_MCP_GATE_TS, "utf-8");
@@ -11625,11 +11739,11 @@ function shellQuoteSingle(value) {
11625
11739
  }
11626
11740
  function resolveSynkroBundle() {
11627
11741
  const scriptPath = process.argv[1];
11628
- if (scriptPath && existsSync11(scriptPath)) return scriptPath;
11742
+ if (scriptPath && existsSync12(scriptPath)) return scriptPath;
11629
11743
  return null;
11630
11744
  }
11631
11745
  function writeConfigEnv(opts) {
11632
- const credsPath = join10(SYNKRO_DIR5, "credentials.json");
11746
+ const credsPath = join11(SYNKRO_DIR6, "credentials.json");
11633
11747
  const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
11634
11748
  const safeUserId = sanitizeConfigValue(opts.userId);
11635
11749
  const safeOrgId = sanitizeConfigValue(opts.orgId);
@@ -11645,7 +11759,7 @@ function writeConfigEnv(opts) {
11645
11759
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
11646
11760
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
11647
11761
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
11648
- `SYNKRO_VERSION=${shellQuoteSingle("1.7.18")}`
11762
+ `SYNKRO_VERSION=${shellQuoteSingle("1.7.27")}`
11649
11763
  ];
11650
11764
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
11651
11765
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -11678,7 +11792,7 @@ async function getOrMintCloudToken(gatewayUrl) {
11678
11792
  assertGatewayAllowed(gatewayUrl);
11679
11793
  let stored = "";
11680
11794
  try {
11681
- stored = readFileSync10(CLOUD_JWT_PATH, "utf-8").trim();
11795
+ stored = readFileSync11(CLOUD_JWT_PATH, "utf-8").trim();
11682
11796
  } catch {
11683
11797
  }
11684
11798
  if (stored && !jwtExpired(stored)) return stored;
@@ -11737,7 +11851,7 @@ async function provisionCloudContainer(opts) {
11737
11851
  let cursorApiKey = "";
11738
11852
  if (cursorTotal > 0) {
11739
11853
  try {
11740
- cursorApiKey = readFileSync10(join10(SYNKRO_DIR5, "cursor-creds", "api-key"), "utf-8").trim();
11854
+ cursorApiKey = readFileSync11(join11(SYNKRO_DIR6, "cursor-creds", "api-key"), "utf-8").trim();
11741
11855
  } catch {
11742
11856
  }
11743
11857
  }
@@ -11894,8 +12008,8 @@ async function verifyCloudGrader(jwt2) {
11894
12008
  }
11895
12009
  function readPersistedDeployLocation() {
11896
12010
  try {
11897
- if (existsSync11(CONFIG_PATH2)) {
11898
- const m = readFileSync10(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
12011
+ if (existsSync12(CONFIG_PATH2)) {
12012
+ const m = readFileSync11(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
11899
12013
  if (m?.[1] === "cloud") return "cloud";
11900
12014
  }
11901
12015
  } catch {
@@ -11903,8 +12017,8 @@ function readPersistedDeployLocation() {
11903
12017
  return "local";
11904
12018
  }
11905
12019
  function updateConfigEnvLocation(location) {
11906
- if (!existsSync11(CONFIG_PATH2)) return;
11907
- let env = readFileSync10(CONFIG_PATH2, "utf-8");
12020
+ if (!existsSync12(CONFIG_PATH2)) return;
12021
+ let env = readFileSync11(CONFIG_PATH2, "utf-8");
11908
12022
  const set = (k, v) => {
11909
12023
  const re = new RegExp(`^${k}=.*$`, "m");
11910
12024
  const line = `${k}='${v}'`;
@@ -11919,7 +12033,7 @@ async function applyMcpConfig(opts) {
11919
12033
  if (!opts.hasClaudeCode && !opts.hasCursor) return;
11920
12034
  let mcpJwt = "";
11921
12035
  try {
11922
- mcpJwt = readFileSync10(join10(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
12036
+ mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
11923
12037
  } catch {
11924
12038
  }
11925
12039
  if (!mcpJwt) {
@@ -12037,8 +12151,8 @@ function resolveDeploymentMode() {
12037
12151
  const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
12038
12152
  if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
12039
12153
  try {
12040
- if (existsSync11(CONFIG_PATH2)) {
12041
- const m = readFileSync10(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
12154
+ if (existsSync12(CONFIG_PATH2)) {
12155
+ const m = readFileSync11(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
12042
12156
  const val = m?.[1]?.toLowerCase();
12043
12157
  if (val === "bare-host" || val === "docker") return val;
12044
12158
  }
@@ -12065,16 +12179,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
12065
12179
  meta.cc_version = execSync6("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
12066
12180
  } catch {
12067
12181
  }
12068
- const claudeDir = join10(homedir10(), ".claude");
12182
+ const claudeDir = join11(homedir11(), ".claude");
12069
12183
  try {
12070
- const settings = JSON.parse(readFileSync10(join10(claudeDir, "settings.json"), "utf-8"));
12184
+ const settings = JSON.parse(readFileSync11(join11(claudeDir, "settings.json"), "utf-8"));
12071
12185
  const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
12072
12186
  if (plugins.length) meta.enabled_plugins = plugins;
12073
12187
  if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
12074
12188
  } catch {
12075
12189
  }
12076
12190
  try {
12077
- const mcpCache = JSON.parse(readFileSync10(join10(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
12191
+ const mcpCache = JSON.parse(readFileSync11(join11(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
12078
12192
  const mcpNames = Object.keys(mcpCache);
12079
12193
  if (mcpNames.length) meta.mcp_servers = mcpNames;
12080
12194
  } catch {
@@ -12086,10 +12200,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
12086
12200
  } catch {
12087
12201
  }
12088
12202
  try {
12089
- const sessionsDir = join10(claudeDir, "sessions");
12203
+ const sessionsDir = join11(claudeDir, "sessions");
12090
12204
  const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
12091
12205
  for (const f of files) {
12092
- const s = JSON.parse(readFileSync10(join10(sessionsDir, f), "utf-8"));
12206
+ const s = JSON.parse(readFileSync11(join11(sessionsDir, f), "utf-8"));
12093
12207
  if (s.version) {
12094
12208
  meta.cc_version = meta.cc_version || s.version;
12095
12209
  break;
@@ -12277,9 +12391,9 @@ async function installCommand(opts = {}) {
12277
12391
  const scripts = writeHookScripts(hookMode);
12278
12392
  console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
12279
12393
  for (const mode of ["edit", "bash"]) {
12280
- const pidFile = join10(SYNKRO_DIR5, "daemon", mode, "daemon.pid");
12394
+ const pidFile = join11(SYNKRO_DIR6, "daemon", mode, "daemon.pid");
12281
12395
  try {
12282
- const pid = parseInt(readFileSync10(pidFile, "utf-8").trim(), 10);
12396
+ const pid = parseInt(readFileSync11(pidFile, "utf-8").trim(), 10);
12283
12397
  if (pid > 0) {
12284
12398
  process.kill(pid, "SIGTERM");
12285
12399
  console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
@@ -12362,7 +12476,7 @@ async function installCommand(opts = {}) {
12362
12476
  if (mintResp.ok) {
12363
12477
  const minted = await mintResp.json();
12364
12478
  mcpJwt = minted.token;
12365
- writeFileSync8(join10(SYNKRO_DIR5, ".mcp-jwt"), mcpJwt + "\n", { mode: 384 });
12479
+ writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), mcpJwt + "\n", { mode: 384 });
12366
12480
  } else {
12367
12481
  console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
12368
12482
  }
@@ -12390,7 +12504,7 @@ async function installCommand(opts = {}) {
12390
12504
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
12391
12505
  }
12392
12506
  const minted = await mintResp.json();
12393
- writeFileSync8(join10(SYNKRO_DIR5, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
12507
+ writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
12394
12508
  const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
12395
12509
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
12396
12510
  console.log(` url: ${mcp.url}`);
@@ -12407,8 +12521,8 @@ async function installCommand(opts = {}) {
12407
12521
  if (hasCursor && !opts.noMcp) {
12408
12522
  try {
12409
12523
  if (useLocalMcp) {
12410
- const jwtPath = join10(SYNKRO_DIR5, ".mcp-jwt");
12411
- if (!existsSync11(jwtPath)) {
12524
+ const jwtPath = join11(SYNKRO_DIR6, ".mcp-jwt");
12525
+ if (!existsSync12(jwtPath)) {
12412
12526
  const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
12413
12527
  method: "POST",
12414
12528
  headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
@@ -12436,7 +12550,7 @@ async function installCommand(opts = {}) {
12436
12550
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
12437
12551
  }
12438
12552
  const minted = await mintResp.json();
12439
- writeFileSync8(join10(SYNKRO_DIR5, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
12553
+ writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
12440
12554
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
12441
12555
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
12442
12556
  console.log(` url: ${mcp.url}`);
@@ -12518,7 +12632,7 @@ async function installCommand(opts = {}) {
12518
12632
  const ready = await waitForContainerReady(6e4);
12519
12633
  if (ready) {
12520
12634
  console.log(" \u2713 container ready");
12521
- const mcpJwt = readFileSync10(join10(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
12635
+ const mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
12522
12636
  try {
12523
12637
  const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
12524
12638
  method: "POST",
@@ -12553,6 +12667,11 @@ async function installCommand(opts = {}) {
12553
12667
  if (hasCursor) await promptCursorApiKey(opts);
12554
12668
  await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
12555
12669
  cloudGradeOk = await verifyCloudGrader(token);
12670
+ if (await promptCodexCloudSetup()) {
12671
+ const { setupCodexCloud: setupCodexCloud2 } = await Promise.resolve().then(() => (init_codexCloudSetup(), codexCloudSetup_exports));
12672
+ const res = await setupCodexCloud2(gatewayUrl, token, (m) => console.log(` ${m}`));
12673
+ if (!res.ok) console.warn(` \u26A0 Codex cloud setup skipped: ${res.error}`);
12674
+ }
12556
12675
  }
12557
12676
  if (transcriptConsent) {
12558
12677
  const repo = detectGitRepo2();
@@ -12561,7 +12680,7 @@ async function installCommand(opts = {}) {
12561
12680
  try {
12562
12681
  let mcpToken = "";
12563
12682
  try {
12564
- mcpToken = readFileSync10(join10(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
12683
+ mcpToken = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
12565
12684
  } catch {
12566
12685
  }
12567
12686
  if (mcpToken) {
@@ -12697,8 +12816,8 @@ function writeSynkroFileIfMissing(opts) {
12697
12816
  try {
12698
12817
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
12699
12818
  if (!root) return;
12700
- if (root === homedir10()) return;
12701
- const fp = join10(root, "synkro.toml");
12819
+ if (root === homedir11()) return;
12820
+ const fp = join11(root, "synkro.toml");
12702
12821
  let hasFile = false;
12703
12822
  try {
12704
12823
  hasFile = statSync(fp).isFile();
@@ -12743,9 +12862,9 @@ function readFullSynkroFile() {
12743
12862
  try {
12744
12863
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
12745
12864
  if (!root) return null;
12746
- const fp = join10(root, "synkro.toml");
12747
- if (!existsSync11(fp)) return null;
12748
- const parsed = parseSynkroToml2(readFileSync10(fp, "utf-8"));
12865
+ const fp = join11(root, "synkro.toml");
12866
+ if (!existsSync12(fp)) return null;
12867
+ const parsed = parseSynkroToml2(readFileSync11(fp, "utf-8"));
12749
12868
  const valid = ["claude-code", "cursor"];
12750
12869
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
12751
12870
  const resolved = resolveGraderPool(parsed);
@@ -12783,7 +12902,7 @@ function reconcileHarness() {
12783
12902
  console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
12784
12903
  const scripts = writeHookScripts(resolvePersistedHookMode());
12785
12904
  console.log("Wrote hook scripts to ~/.synkro/hooks/");
12786
- const ccSettings = join10(homedir10(), ".claude", "settings.json");
12905
+ const ccSettings = join11(homedir11(), ".claude", "settings.json");
12787
12906
  if (wantCC) {
12788
12907
  installCCHooks(ccSettings, {
12789
12908
  bashJudgeScriptPath: scripts.bashScript,
@@ -12803,7 +12922,7 @@ function reconcileHarness() {
12803
12922
  });
12804
12923
  console.log(" \u2713 Claude Code hooks registered");
12805
12924
  try {
12806
- const mcpJwt = readFileSync10(join10(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
12925
+ const mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
12807
12926
  if (mcpJwt) {
12808
12927
  installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt, local: true });
12809
12928
  console.log(" \u2713 Claude Code MCP registered");
@@ -12815,7 +12934,7 @@ function reconcileHarness() {
12815
12934
  if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
12816
12935
  if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
12817
12936
  }
12818
- const cursorHooks = join10(homedir10(), ".cursor", "hooks.json");
12937
+ const cursorHooks = join11(homedir11(), ".cursor", "hooks.json");
12819
12938
  if (wantCursor) {
12820
12939
  installCursorHooks(cursorHooks, {
12821
12940
  bashJudgeScriptPath: scripts.cursorBashJudgeScript,
@@ -12897,7 +13016,7 @@ async function syncSkillFiles() {
12897
13016
  if (resolved.length === 0) return;
12898
13017
  const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
12899
13018
  const tasks = resolved.map((fp) => {
12900
- const content = readFileSync10(fp, "utf-8");
13019
+ const content = readFileSync11(fp, "utf-8");
12901
13020
  const source = `skill:${fp.split("/").pop()}`;
12902
13021
  if (!content.trim()) {
12903
13022
  console.log(` \u2298 skill ${source}: empty file, skipped`);
@@ -12915,15 +13034,15 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
12915
13034
  const roots = [];
12916
13035
  const add = (p) => {
12917
13036
  try {
12918
- if (p && existsSync11(p) && statSync(p).isDirectory()) roots.push(p);
13037
+ if (p && existsSync12(p) && statSync(p).isDirectory()) roots.push(p);
12919
13038
  } catch {
12920
13039
  }
12921
13040
  };
12922
- add(join10(homedir10(), ".claude", "skills"));
12923
- add(join10(homedir10(), ".agents", "skills"));
13041
+ add(join11(homedir11(), ".claude", "skills"));
13042
+ add(join11(homedir11(), ".agents", "skills"));
12924
13043
  if (repoRoot) {
12925
- add(join10(repoRoot, ".claude", "skills"));
12926
- add(join10(repoRoot, ".agents", "skills"));
13044
+ add(join11(repoRoot, ".claude", "skills"));
13045
+ add(join11(repoRoot, ".agents", "skills"));
12927
13046
  }
12928
13047
  const out = [];
12929
13048
  const seen = /* @__PURE__ */ new Set();
@@ -12934,7 +13053,7 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
12934
13053
  try {
12935
13054
  const st = statSync(file);
12936
13055
  if (!st.isFile() || st.size > 2e5) return;
12937
- content = readFileSync10(file, "utf-8");
13056
+ content = readFileSync11(file, "utf-8");
12938
13057
  } catch {
12939
13058
  return;
12940
13059
  }
@@ -12953,7 +13072,7 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
12953
13072
  }
12954
13073
  for (const entry of entries) {
12955
13074
  if (entry.startsWith(".")) continue;
12956
- const full = join10(root, entry);
13075
+ const full = join11(root, entry);
12957
13076
  let st;
12958
13077
  try {
12959
13078
  st = statSync(full);
@@ -12961,8 +13080,8 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
12961
13080
  continue;
12962
13081
  }
12963
13082
  if (st.isDirectory()) {
12964
- const skillMd = join10(full, "SKILL.md");
12965
- if (existsSync11(skillMd)) consider(skillMd, entry);
13083
+ const skillMd = join11(full, "SKILL.md");
13084
+ if (existsSync12(skillMd)) consider(skillMd, entry);
12966
13085
  } else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
12967
13086
  consider(full, entry.replace(/\.mdx?$/i, ""));
12968
13087
  }
@@ -13000,7 +13119,7 @@ async function discoverAndIngestSkills() {
13000
13119
  if (sf?.skills?.length) {
13001
13120
  for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
13002
13121
  try {
13003
- excludeHashes.add(createHash("sha256").update(readFileSync10(fp, "utf-8")).digest("hex"));
13122
+ excludeHashes.add(createHash("sha256").update(readFileSync11(fp, "utf-8")).digest("hex"));
13004
13123
  } catch {
13005
13124
  }
13006
13125
  }
@@ -13010,7 +13129,7 @@ async function discoverAndIngestSkills() {
13010
13129
  const setHash = discoverySetHash(found);
13011
13130
  let prev = "";
13012
13131
  try {
13013
- prev = readFileSync10(SKILLS_DISCOVERED_PATH, "utf-8").trim();
13132
+ prev = readFileSync11(SKILLS_DISCOVERED_PATH, "utf-8").trim();
13014
13133
  } catch {
13015
13134
  }
13016
13135
  if (prev === setHash) return;
@@ -13051,17 +13170,17 @@ function detectGitRepo2() {
13051
13170
  function getClaudeProjectsFolder() {
13052
13171
  const cwd = process.cwd();
13053
13172
  const sanitized = "-" + cwd.replace(/\//g, "-");
13054
- const projectsDir = join10(homedir10(), ".claude", "projects", sanitized);
13055
- return existsSync11(projectsDir) ? projectsDir : null;
13173
+ const projectsDir = join11(homedir11(), ".claude", "projects", sanitized);
13174
+ return existsSync12(projectsDir) ? projectsDir : null;
13056
13175
  }
13057
13176
  function extractSessionInsights(projectsDir) {
13058
13177
  const insights = [];
13059
13178
  const files = readdirSync3(projectsDir).filter((f) => f.endsWith(".jsonl"));
13060
13179
  for (const file of files) {
13061
13180
  const sessionId = file.replace(".jsonl", "");
13062
- const filePath = join10(projectsDir, file);
13181
+ const filePath = join11(projectsDir, file);
13063
13182
  try {
13064
- const content = readFileSync10(filePath, "utf-8");
13183
+ const content = readFileSync11(filePath, "utf-8");
13065
13184
  const lines = content.split("\n").filter(Boolean);
13066
13185
  for (let i = 0; i < lines.length; i++) {
13067
13186
  try {
@@ -13140,14 +13259,14 @@ function cursorProjectSlug(workspaceRoot) {
13140
13259
  return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
13141
13260
  }
13142
13261
  function getCursorTranscriptsDir() {
13143
- const dir = join10(homedir10(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
13144
- return existsSync11(dir) ? dir : null;
13262
+ const dir = join11(homedir11(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
13263
+ return existsSync12(dir) ? dir : null;
13145
13264
  }
13146
13265
  function isSafeConvId(id) {
13147
13266
  return /^[A-Za-z0-9_-]+$/.test(id);
13148
13267
  }
13149
13268
  function parseCursorTranscriptFile(filePath) {
13150
- const content = readFileSync10(filePath, "utf-8");
13269
+ const content = readFileSync11(filePath, "utf-8");
13151
13270
  const lines = content.split("\n").filter(Boolean);
13152
13271
  const messages = [];
13153
13272
  for (let i = 0; i < lines.length; i++) {
@@ -13179,8 +13298,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
13179
13298
  for (let i = 0; i < convDirs.length; i++) {
13180
13299
  const convId = convDirs[i];
13181
13300
  if (!isSafeConvId(convId)) continue;
13182
- const filePath = join10(dir, convId, `${convId}.jsonl`);
13183
- if (!existsSync11(filePath)) continue;
13301
+ const filePath = join11(dir, convId, `${convId}.jsonl`);
13302
+ if (!existsSync12(filePath)) continue;
13184
13303
  try {
13185
13304
  const all = parseCursorTranscriptFile(filePath);
13186
13305
  const messages = all.length > 500 ? all.slice(-500) : all;
@@ -13202,8 +13321,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
13202
13321
  process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
13203
13322
  }
13204
13323
  try {
13205
- const lc = readFileSync10(filePath, "utf-8").split("\n").filter(Boolean).length;
13206
- writeFileSync8(join10(OFFSETS_DIR, convId), String(lc), "utf-8");
13324
+ const lc = readFileSync11(filePath, "utf-8").split("\n").filter(Boolean).length;
13325
+ writeFileSync8(join11(OFFSETS_DIR, convId), String(lc), "utf-8");
13207
13326
  } catch {
13208
13327
  }
13209
13328
  }
@@ -13211,7 +13330,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
13211
13330
  return { sessions: totalSessions, messages: totalMessages };
13212
13331
  }
13213
13332
  function parseTranscriptFile(filePath) {
13214
- const content = readFileSync10(filePath, "utf-8");
13333
+ const content = readFileSync11(filePath, "utf-8");
13215
13334
  const lines = content.split("\n").filter(Boolean);
13216
13335
  const messages = [];
13217
13336
  for (let i = 0; i < lines.length; i++) {
@@ -13259,7 +13378,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
13259
13378
  for (let i = 0; i < files.length; i++) {
13260
13379
  const file = files[i];
13261
13380
  const sessionId = file.replace(".jsonl", "");
13262
- const filePath = join10(projectsDir, file);
13381
+ const filePath = join11(projectsDir, file);
13263
13382
  try {
13264
13383
  const allMessages = parseTranscriptFile(filePath);
13265
13384
  const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
@@ -13281,9 +13400,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
13281
13400
  process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
13282
13401
  }
13283
13402
  try {
13284
- const content = readFileSync10(join10(projectsDir, file), "utf-8");
13403
+ const content = readFileSync11(join11(projectsDir, file), "utf-8");
13285
13404
  const lineCount = content.split("\n").filter(Boolean).length;
13286
- writeFileSync8(join10(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
13405
+ writeFileSync8(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
13287
13406
  } catch {
13288
13407
  }
13289
13408
  }
@@ -13304,7 +13423,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
13304
13423
  const sessions = [];
13305
13424
  for (const file of batch) {
13306
13425
  const sessionId = file.replace(".jsonl", "");
13307
- const filePath = join10(projectsDir, file);
13426
+ const filePath = join11(projectsDir, file);
13308
13427
  try {
13309
13428
  const allMessages = parseTranscriptFile(filePath);
13310
13429
  const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
@@ -13333,18 +13452,18 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
13333
13452
  }
13334
13453
  for (const file of batch) {
13335
13454
  const sessionId = file.replace(".jsonl", "");
13336
- const filePath = join10(projectsDir, file);
13455
+ const filePath = join11(projectsDir, file);
13337
13456
  try {
13338
- const content = readFileSync10(filePath, "utf-8");
13457
+ const content = readFileSync11(filePath, "utf-8");
13339
13458
  const lineCount = content.split("\n").filter(Boolean).length;
13340
- writeFileSync8(join10(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
13459
+ writeFileSync8(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
13341
13460
  } catch {
13342
13461
  }
13343
13462
  }
13344
13463
  }
13345
13464
  return { sessions: totalSessions, messages: totalMessages };
13346
13465
  }
13347
- var SYNKRO_DIR5, HOOKS_DIR, BIN_DIR, CONFIG_PATH2, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
13466
+ var SYNKRO_DIR6, HOOKS_DIR, BIN_DIR, CONFIG_PATH2, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
13348
13467
  var init_install = __esm({
13349
13468
  "cli/commands/install.ts"() {
13350
13469
  "use strict";
@@ -13363,10 +13482,10 @@ var init_install = __esm({
13363
13482
  init_claudeDesktopTap();
13364
13483
  init_dockerInstall();
13365
13484
  init_setupToken();
13366
- SYNKRO_DIR5 = join10(homedir10(), ".synkro");
13367
- HOOKS_DIR = join10(SYNKRO_DIR5, "hooks");
13368
- BIN_DIR = join10(SYNKRO_DIR5, "bin");
13369
- CONFIG_PATH2 = join10(SYNKRO_DIR5, "config.env");
13485
+ SYNKRO_DIR6 = join11(homedir11(), ".synkro");
13486
+ HOOKS_DIR = join11(SYNKRO_DIR6, "hooks");
13487
+ BIN_DIR = join11(SYNKRO_DIR6, "bin");
13488
+ CONFIG_PATH2 = join11(SYNKRO_DIR6, "config.env");
13370
13489
  MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
13371
13490
  import { readFileSync } from 'node:fs';
13372
13491
  import { homedir } from 'node:os';
@@ -13477,21 +13596,21 @@ rl.on('line', async (line) => {
13477
13596
  }
13478
13597
  });
13479
13598
  `;
13480
- OFFSETS_DIR = join10(SYNKRO_DIR5, ".transcript-offsets");
13481
- CLOUD_JWT_PATH = join10(SYNKRO_DIR5, ".cloud-jwt");
13482
- SKILLS_DISCOVERED_PATH = join10(SYNKRO_DIR5, ".skills-discovered");
13599
+ OFFSETS_DIR = join11(SYNKRO_DIR6, ".transcript-offsets");
13600
+ CLOUD_JWT_PATH = join11(SYNKRO_DIR6, ".cloud-jwt");
13601
+ SKILLS_DISCOVERED_PATH = join11(SYNKRO_DIR6, ".skills-discovered");
13483
13602
  }
13484
13603
  });
13485
13604
 
13486
13605
  // cli/local-cc/install.ts
13487
- import { existsSync as existsSync12, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9, readFileSync as readFileSync11, chmodSync as chmodSync3, copyFileSync as copyFileSync2, renameSync as renameSync4, unlinkSync as unlinkSync5, openSync, fsyncSync, closeSync } from "fs";
13488
- import { join as join11 } from "path";
13489
- import { homedir as homedir11 } from "os";
13490
- import { spawnSync as spawnSync5 } from "child_process";
13606
+ import { existsSync as existsSync13, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, readFileSync as readFileSync12, chmodSync as chmodSync3, copyFileSync as copyFileSync2, renameSync as renameSync4, unlinkSync as unlinkSync5, openSync, fsyncSync, closeSync } from "fs";
13607
+ import { join as join12 } from "path";
13608
+ import { homedir as homedir12 } from "os";
13609
+ import { spawnSync as spawnSync6 } from "child_process";
13491
13610
  function writePluginFiles() {
13492
13611
  for (const c of CHANNELS) {
13493
- mkdirSync10(c.sessionDir, { recursive: true });
13494
- mkdirSync10(c.pluginSettingsDir, { recursive: true });
13612
+ mkdirSync11(c.sessionDir, { recursive: true });
13613
+ mkdirSync11(c.pluginSettingsDir, { recursive: true });
13495
13614
  writeFileSync9(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
13496
13615
  writeFileSync9(
13497
13616
  c.pluginSettingsPath,
@@ -13514,7 +13633,7 @@ function writePluginFiles() {
13514
13633
  }
13515
13634
  function runBunInstall() {
13516
13635
  for (const c of CHANNELS) {
13517
- const r = spawnSync5("bun", ["install", "--silent"], {
13636
+ const r = spawnSync6("bun", ["install", "--silent"], {
13518
13637
  cwd: c.sessionDir,
13519
13638
  encoding: "utf-8",
13520
13639
  timeout: 12e4
@@ -13527,10 +13646,10 @@ function runBunInstall() {
13527
13646
  }
13528
13647
  }
13529
13648
  function safelyMutateClaudeJson(mutator) {
13530
- if (!existsSync12(CLAUDE_JSON_PATH)) {
13649
+ if (!existsSync13(CLAUDE_JSON_PATH)) {
13531
13650
  return;
13532
13651
  }
13533
- const originalText = readFileSync11(CLAUDE_JSON_PATH, "utf-8");
13652
+ const originalText = readFileSync12(CLAUDE_JSON_PATH, "utf-8");
13534
13653
  let parsed;
13535
13654
  try {
13536
13655
  parsed = JSON.parse(originalText);
@@ -13630,15 +13749,15 @@ function patchClaudeJson() {
13630
13749
  });
13631
13750
  }
13632
13751
  function installLocalCC() {
13633
- let bunCheck = spawnSync5("bun", ["--version"], { encoding: "utf-8" });
13752
+ let bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
13634
13753
  if (bunCheck.status !== 0) {
13635
13754
  if (process.platform === "darwin") {
13636
13755
  console.log(" Installing bun via brew...");
13637
- const brewR = spawnSync5("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
13756
+ const brewR = spawnSync6("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
13638
13757
  if (brewR.status !== 0) {
13639
13758
  throw new LocalCCInstallError("bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash");
13640
13759
  }
13641
- bunCheck = spawnSync5("bun", ["--version"], { encoding: "utf-8" });
13760
+ bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
13642
13761
  if (bunCheck.status !== 0) {
13643
13762
  throw new LocalCCInstallError("bun installed but not found on PATH. Restart your terminal and re-run install.");
13644
13763
  }
@@ -13672,42 +13791,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
13672
13791
  var init_install2 = __esm({
13673
13792
  "cli/local-cc/install.ts"() {
13674
13793
  "use strict";
13675
- CLAUDE_JSON_BACKUP_PATH = join11(homedir11(), ".claude.json.synkro-bak");
13676
- SESSION_DIR = join11(homedir11(), ".synkro", "cc_sessions");
13677
- PLUGIN_PATH = join11(SESSION_DIR, "synkro-channel.ts");
13678
- PLUGIN_PKG_PATH = join11(SESSION_DIR, "package.json");
13679
- PLUGIN_SETTINGS_DIR = join11(SESSION_DIR, ".claude");
13680
- PLUGIN_SETTINGS_PATH = join11(PLUGIN_SETTINGS_DIR, "settings.json");
13681
- PROJECT_MCP_PATH = join11(SESSION_DIR, ".mcp.json");
13682
- CLAUDE_JSON_PATH = join11(homedir11(), ".claude.json");
13683
- RUN_SCRIPT_PATH = join11(SESSION_DIR, "run-claude.sh");
13794
+ CLAUDE_JSON_BACKUP_PATH = join12(homedir12(), ".claude.json.synkro-bak");
13795
+ SESSION_DIR = join12(homedir12(), ".synkro", "cc_sessions");
13796
+ PLUGIN_PATH = join12(SESSION_DIR, "synkro-channel.ts");
13797
+ PLUGIN_PKG_PATH = join12(SESSION_DIR, "package.json");
13798
+ PLUGIN_SETTINGS_DIR = join12(SESSION_DIR, ".claude");
13799
+ PLUGIN_SETTINGS_PATH = join12(PLUGIN_SETTINGS_DIR, "settings.json");
13800
+ PROJECT_MCP_PATH = join12(SESSION_DIR, ".mcp.json");
13801
+ CLAUDE_JSON_PATH = join12(homedir12(), ".claude.json");
13802
+ RUN_SCRIPT_PATH = join12(SESSION_DIR, "run-claude.sh");
13684
13803
  TMUX_SESSION_NAME = "synkro-local-cc";
13685
13804
  CHANNEL_1_PORT = 8941;
13686
- SESSION_DIR_2 = join11(homedir11(), ".synkro", "cc_sessions_2");
13687
- PLUGIN_PATH_2 = join11(SESSION_DIR_2, "synkro-channel.ts");
13688
- PLUGIN_PKG_PATH_2 = join11(SESSION_DIR_2, "package.json");
13689
- PLUGIN_SETTINGS_DIR_2 = join11(SESSION_DIR_2, ".claude");
13690
- PLUGIN_SETTINGS_PATH_2 = join11(PLUGIN_SETTINGS_DIR_2, "settings.json");
13691
- PROJECT_MCP_PATH_2 = join11(SESSION_DIR_2, ".mcp.json");
13692
- RUN_SCRIPT_PATH_2 = join11(SESSION_DIR_2, "run-claude.sh");
13805
+ SESSION_DIR_2 = join12(homedir12(), ".synkro", "cc_sessions_2");
13806
+ PLUGIN_PATH_2 = join12(SESSION_DIR_2, "synkro-channel.ts");
13807
+ PLUGIN_PKG_PATH_2 = join12(SESSION_DIR_2, "package.json");
13808
+ PLUGIN_SETTINGS_DIR_2 = join12(SESSION_DIR_2, ".claude");
13809
+ PLUGIN_SETTINGS_PATH_2 = join12(PLUGIN_SETTINGS_DIR_2, "settings.json");
13810
+ PROJECT_MCP_PATH_2 = join12(SESSION_DIR_2, ".mcp.json");
13811
+ RUN_SCRIPT_PATH_2 = join12(SESSION_DIR_2, "run-claude.sh");
13693
13812
  TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
13694
13813
  CHANNEL_2_PORT = 8951;
13695
- SESSION_DIR_3 = join11(homedir11(), ".synkro", "cc_sessions_3");
13696
- PLUGIN_PATH_3 = join11(SESSION_DIR_3, "synkro-channel.ts");
13697
- PLUGIN_PKG_PATH_3 = join11(SESSION_DIR_3, "package.json");
13698
- PLUGIN_SETTINGS_DIR_3 = join11(SESSION_DIR_3, ".claude");
13699
- PLUGIN_SETTINGS_PATH_3 = join11(PLUGIN_SETTINGS_DIR_3, "settings.json");
13700
- PROJECT_MCP_PATH_3 = join11(SESSION_DIR_3, ".mcp.json");
13701
- RUN_SCRIPT_PATH_3 = join11(SESSION_DIR_3, "run-claude.sh");
13814
+ SESSION_DIR_3 = join12(homedir12(), ".synkro", "cc_sessions_3");
13815
+ PLUGIN_PATH_3 = join12(SESSION_DIR_3, "synkro-channel.ts");
13816
+ PLUGIN_PKG_PATH_3 = join12(SESSION_DIR_3, "package.json");
13817
+ PLUGIN_SETTINGS_DIR_3 = join12(SESSION_DIR_3, ".claude");
13818
+ PLUGIN_SETTINGS_PATH_3 = join12(PLUGIN_SETTINGS_DIR_3, "settings.json");
13819
+ PROJECT_MCP_PATH_3 = join12(SESSION_DIR_3, ".mcp.json");
13820
+ RUN_SCRIPT_PATH_3 = join12(SESSION_DIR_3, "run-claude.sh");
13702
13821
  TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
13703
13822
  CHANNEL_3_PORT = 8942;
13704
- SESSION_DIR_4 = join11(homedir11(), ".synkro", "cc_sessions_4");
13705
- PLUGIN_PATH_4 = join11(SESSION_DIR_4, "synkro-channel.ts");
13706
- PLUGIN_PKG_PATH_4 = join11(SESSION_DIR_4, "package.json");
13707
- PLUGIN_SETTINGS_DIR_4 = join11(SESSION_DIR_4, ".claude");
13708
- PLUGIN_SETTINGS_PATH_4 = join11(PLUGIN_SETTINGS_DIR_4, "settings.json");
13709
- PROJECT_MCP_PATH_4 = join11(SESSION_DIR_4, ".mcp.json");
13710
- RUN_SCRIPT_PATH_4 = join11(SESSION_DIR_4, "run-claude.sh");
13823
+ SESSION_DIR_4 = join12(homedir12(), ".synkro", "cc_sessions_4");
13824
+ PLUGIN_PATH_4 = join12(SESSION_DIR_4, "synkro-channel.ts");
13825
+ PLUGIN_PKG_PATH_4 = join12(SESSION_DIR_4, "package.json");
13826
+ PLUGIN_SETTINGS_DIR_4 = join12(SESSION_DIR_4, ".claude");
13827
+ PLUGIN_SETTINGS_PATH_4 = join12(PLUGIN_SETTINGS_DIR_4, "settings.json");
13828
+ PROJECT_MCP_PATH_4 = join12(SESSION_DIR_4, ".mcp.json");
13829
+ RUN_SCRIPT_PATH_4 = join12(SESSION_DIR_4, "run-claude.sh");
13711
13830
  TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
13712
13831
  CHANNEL_4_PORT = 8952;
13713
13832
  RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
@@ -13981,10 +14100,10 @@ var disconnect_exports = {};
13981
14100
  __export(disconnect_exports, {
13982
14101
  disconnectCommand: () => disconnectCommand
13983
14102
  });
13984
- import { existsSync as existsSync13, rmSync, readdirSync as readdirSync4 } from "fs";
13985
- import { homedir as homedir12 } from "os";
13986
- import { join as join12 } from "path";
13987
- import { spawnSync as spawnSync6 } from "child_process";
14103
+ import { existsSync as existsSync14, rmSync as rmSync2, readdirSync as readdirSync4 } from "fs";
14104
+ import { homedir as homedir13 } from "os";
14105
+ import { join as join13 } from "path";
14106
+ import { spawnSync as spawnSync7 } from "child_process";
13988
14107
  import { createInterface as createInterface4 } from "readline";
13989
14108
  async function tearDownLocalCC() {
13990
14109
  const docker = dockerStatus();
@@ -13998,7 +14117,7 @@ async function tearDownLocalCC() {
13998
14117
  console.log("\u2713 removed synkro-server container");
13999
14118
  try {
14000
14119
  const image = imageTag();
14001
- const r = spawnSync6("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
14120
+ const r = spawnSync7("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
14002
14121
  console.log(r.status === 0 ? `\u2713 removed Docker image ${image}` : "\xB7 no Docker image to remove");
14003
14122
  } catch {
14004
14123
  }
@@ -14061,34 +14180,34 @@ async function disconnectCommand(args2 = []) {
14061
14180
  const desktopMcpRemoved = uninstallClaudeDesktopMcpConfig();
14062
14181
  console.log(`${desktopMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? "removed from claude_desktop_config.json" : "no entry found"}`);
14063
14182
  }
14064
- if (existsSync13(SYNKRO_DIR6)) {
14183
+ if (existsSync14(SYNKRO_DIR7)) {
14065
14184
  if (purge) {
14066
- rmSync(SYNKRO_DIR6, { recursive: true, force: true });
14067
- console.log(`\u2713 wiped ${SYNKRO_DIR6} entirely \u2014 including all scan data and backups`);
14185
+ rmSync2(SYNKRO_DIR7, { recursive: true, force: true });
14186
+ console.log(`\u2713 wiped ${SYNKRO_DIR7} entirely \u2014 including all scan data and backups`);
14068
14187
  } else {
14069
- const keep = /* @__PURE__ */ new Set([join12(SYNKRO_DIR6, "pgdata"), join12(SYNKRO_DIR6, "pgdata-backups")]);
14188
+ const keep = /* @__PURE__ */ new Set([join13(SYNKRO_DIR7, "pgdata"), join13(SYNKRO_DIR7, "pgdata-backups")]);
14070
14189
  const preserved = [];
14071
- for (const entry of readdirSync4(SYNKRO_DIR6)) {
14072
- const full = join12(SYNKRO_DIR6, entry);
14190
+ for (const entry of readdirSync4(SYNKRO_DIR7)) {
14191
+ const full = join13(SYNKRO_DIR7, entry);
14073
14192
  if (keep.has(full)) {
14074
14193
  preserved.push(entry);
14075
14194
  continue;
14076
14195
  }
14077
- rmSync(full, { recursive: true, force: true });
14196
+ rmSync2(full, { recursive: true, force: true });
14078
14197
  }
14079
14198
  if (preserved.length > 0) {
14080
- console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR6} (kept your scan data: ${preserved.join(", ")})`);
14199
+ console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR7} (kept your scan data: ${preserved.join(", ")})`);
14081
14200
  console.log(" run `synkro uninstall --purge` to delete that too");
14082
14201
  } else {
14083
- console.log(`\u2713 removed ${SYNKRO_DIR6}`);
14202
+ console.log(`\u2713 removed ${SYNKRO_DIR7}`);
14084
14203
  }
14085
14204
  }
14086
14205
  } else {
14087
- console.log(`\xB7 ${SYNKRO_DIR6} already gone`);
14206
+ console.log(`\xB7 ${SYNKRO_DIR7} already gone`);
14088
14207
  }
14089
14208
  console.log(purge ? "\nSynkro fully removed \u2014 this machine is clean, as if it was never installed." : "\nSynkro uninstalled. Your scan data is preserved.");
14090
14209
  }
14091
- var SYNKRO_DIR6;
14210
+ var SYNKRO_DIR7;
14092
14211
  var init_disconnect = __esm({
14093
14212
  "cli/commands/disconnect.ts"() {
14094
14213
  "use strict";
@@ -14099,14 +14218,14 @@ var init_disconnect = __esm({
14099
14218
  init_install2();
14100
14219
  init_dockerInstall();
14101
14220
  init_macKeychain();
14102
- SYNKRO_DIR6 = join12(homedir12(), ".synkro");
14221
+ SYNKRO_DIR7 = join13(homedir13(), ".synkro");
14103
14222
  }
14104
14223
  });
14105
14224
 
14106
14225
  // cli/local-cc/turnLog.ts
14107
- import { appendFileSync, existsSync as existsSync14, mkdirSync as mkdirSync11, openSync as openSync2, readFileSync as readFileSync12, readSync, closeSync as closeSync2, statSync as statSync2, watchFile, unwatchFile } from "fs";
14108
- import { dirname as dirname6, join as join13 } from "path";
14109
- import { homedir as homedir13 } from "os";
14226
+ import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync12, openSync as openSync2, readFileSync as readFileSync13, readSync, closeSync as closeSync2, statSync as statSync2, watchFile, unwatchFile } from "fs";
14227
+ import { dirname as dirname6, join as join14 } from "path";
14228
+ import { homedir as homedir14 } from "os";
14110
14229
  function truncate(s, max = PREVIEW_MAX) {
14111
14230
  if (s.length <= max) return s;
14112
14231
  return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
@@ -14126,7 +14245,7 @@ function extractSeverity(result) {
14126
14245
  }
14127
14246
  function appendTurn(args2) {
14128
14247
  try {
14129
- mkdirSync11(dirname6(TURN_LOG_PATH), { recursive: true });
14248
+ mkdirSync12(dirname6(TURN_LOG_PATH), { recursive: true });
14130
14249
  const entry = {
14131
14250
  ts: new Date(args2.startedAt).toISOString(),
14132
14251
  role: args2.role,
@@ -14142,11 +14261,11 @@ function appendTurn(args2) {
14142
14261
  }
14143
14262
  }
14144
14263
  function readRecentTurns(n = 20) {
14145
- if (!existsSync14(TURN_LOG_PATH)) return [];
14264
+ if (!existsSync15(TURN_LOG_PATH)) return [];
14146
14265
  try {
14147
14266
  const size = statSync2(TURN_LOG_PATH).size;
14148
14267
  if (size === 0) return [];
14149
- const text = readFileSync12(TURN_LOG_PATH, "utf-8");
14268
+ const text = readFileSync13(TURN_LOG_PATH, "utf-8");
14150
14269
  const lines = text.split("\n").filter(Boolean);
14151
14270
  const lastN = lines.slice(-n).reverse();
14152
14271
  return lastN.map((line) => {
@@ -14162,8 +14281,8 @@ function readRecentTurns(n = 20) {
14162
14281
  }
14163
14282
  function followTurns(onEntry) {
14164
14283
  try {
14165
- mkdirSync11(dirname6(TURN_LOG_PATH), { recursive: true });
14166
- if (!existsSync14(TURN_LOG_PATH)) {
14284
+ mkdirSync12(dirname6(TURN_LOG_PATH), { recursive: true });
14285
+ if (!existsSync15(TURN_LOG_PATH)) {
14167
14286
  appendFileSync(TURN_LOG_PATH, "", "utf-8");
14168
14287
  }
14169
14288
  } catch {
@@ -14225,7 +14344,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
14225
14344
  var init_turnLog = __esm({
14226
14345
  "cli/local-cc/turnLog.ts"() {
14227
14346
  "use strict";
14228
- TURN_LOG_PATH = join13(homedir13(), ".synkro", "cc_sessions", "turns.log");
14347
+ TURN_LOG_PATH = join14(homedir14(), ".synkro", "cc_sessions", "turns.log");
14229
14348
  PREVIEW_MAX = 400;
14230
14349
  }
14231
14350
  });
@@ -14374,19 +14493,19 @@ var init_grade = __esm({
14374
14493
  });
14375
14494
 
14376
14495
  // cli/local-cc/pueue.ts
14377
- import { execFileSync as execFileSync3, spawnSync as spawnSync7, spawn as spawn2 } from "child_process";
14378
- import { homedir as homedir14 } from "os";
14379
- import { join as join14 } from "path";
14496
+ import { execFileSync as execFileSync3, spawnSync as spawnSync8, spawn as spawn2 } from "child_process";
14497
+ import { homedir as homedir15 } from "os";
14498
+ import { join as join15 } from "path";
14380
14499
  import { connect as connect2 } from "net";
14381
14500
  function pueueAvailable() {
14382
- const r = spawnSync7("pueue", ["--version"], { encoding: "utf-8" });
14501
+ const r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
14383
14502
  if (r.status !== 0) {
14384
14503
  throw new PueueError("pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.");
14385
14504
  }
14386
14505
  }
14387
14506
  function statusJson() {
14388
14507
  pueueAvailable();
14389
- const r = spawnSync7("pueue", ["status", "--json"], { encoding: "utf-8" });
14508
+ const r = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8" });
14390
14509
  if (r.status !== 0) {
14391
14510
  throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || "unknown error"} \u2014 is pueued running?`);
14392
14511
  }
@@ -14431,18 +14550,18 @@ function startTask(opts = {}) {
14431
14550
  let existing = findTask(ch);
14432
14551
  while (existing) {
14433
14552
  if (existing.status === "Running" || existing.status === "Queued") {
14434
- spawnSync7("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
14435
- spawnSync7("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
14553
+ spawnSync8("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
14554
+ spawnSync8("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
14436
14555
  for (let i = 0; i < 10; i++) {
14437
14556
  const check = findTask(ch);
14438
14557
  if (!check || check.id !== existing.id || check.status !== "Running" && check.status !== "Queued") break;
14439
- spawnSync7("sleep", ["0.5"], { encoding: "utf-8" });
14558
+ spawnSync8("sleep", ["0.5"], { encoding: "utf-8" });
14440
14559
  }
14441
14560
  }
14442
- spawnSync7("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
14561
+ spawnSync8("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
14443
14562
  existing = findTask(ch);
14444
14563
  }
14445
- const runScript = join14(cwd, "run-claude.sh");
14564
+ const runScript = join15(cwd, "run-claude.sh");
14446
14565
  const args2 = [
14447
14566
  "add",
14448
14567
  "--label",
@@ -14453,7 +14572,7 @@ function startTask(opts = {}) {
14453
14572
  "bash",
14454
14573
  runScript
14455
14574
  ];
14456
- const r = spawnSync7("pueue", args2, { encoding: "utf-8" });
14575
+ const r = spawnSync8("pueue", args2, { encoding: "utf-8" });
14457
14576
  if (r.status !== 0) {
14458
14577
  throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);
14459
14578
  }
@@ -14464,25 +14583,25 @@ function startTask(opts = {}) {
14464
14583
  return created;
14465
14584
  }
14466
14585
  function stopTask(channel = CHANNEL_PRIMARY) {
14467
- spawnSync7("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
14586
+ spawnSync8("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
14468
14587
  let t = findTask(channel);
14469
14588
  while (t) {
14470
14589
  if (t.status === "Running" || t.status === "Queued") {
14471
- spawnSync7("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
14590
+ spawnSync8("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
14472
14591
  for (let i = 0; i < 10; i++) {
14473
14592
  const check = findTask(channel);
14474
14593
  if (!check || check.id !== t.id || check.status !== "Running" && check.status !== "Queued") break;
14475
- spawnSync7("sleep", ["0.5"], { encoding: "utf-8" });
14594
+ spawnSync8("sleep", ["0.5"], { encoding: "utf-8" });
14476
14595
  }
14477
14596
  }
14478
- spawnSync7("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
14597
+ spawnSync8("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
14479
14598
  t = findTask(channel);
14480
14599
  }
14481
14600
  }
14482
14601
  function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
14483
14602
  const t = findTask(channel);
14484
14603
  if (!t) return `(no ${channel.taskLabel} task)`;
14485
- const r = spawnSync7("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
14604
+ const r = spawnSync8("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
14486
14605
  return r.stdout || r.stderr || "(no output)";
14487
14606
  }
14488
14607
  function ensureRunning(opts = {}) {
@@ -14507,8 +14626,8 @@ function probePort(host, port, timeoutMs = 500) {
14507
14626
  });
14508
14627
  }
14509
14628
  function tmuxDismissPrompts(tmuxSession = TMUX_SESSION) {
14510
- spawnSync7("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
14511
- spawnSync7("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
14629
+ spawnSync8("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
14630
+ spawnSync8("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
14512
14631
  }
14513
14632
  async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tmuxSession = TMUX_SESSION) {
14514
14633
  const deadline = Date.now() + timeoutMs;
@@ -14520,46 +14639,46 @@ async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tm
14520
14639
  return probePort(host, port);
14521
14640
  }
14522
14641
  function brewInstall(pkg) {
14523
- const brew = spawnSync7("brew", ["--version"], { encoding: "utf-8" });
14642
+ const brew = spawnSync8("brew", ["--version"], { encoding: "utf-8" });
14524
14643
  if (brew.status !== 0) return false;
14525
14644
  console.log(` Installing ${pkg} via brew...`);
14526
- const r = spawnSync7("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
14645
+ const r = spawnSync8("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
14527
14646
  return r.status === 0;
14528
14647
  }
14529
14648
  function assertPueueInstalled() {
14530
- let r = spawnSync7("pueue", ["--version"], { encoding: "utf-8" });
14649
+ let r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
14531
14650
  if (r.status !== 0) {
14532
14651
  if (process.platform === "darwin" && brewInstall("pueue")) {
14533
- r = spawnSync7("pueue", ["--version"], { encoding: "utf-8" });
14652
+ r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
14534
14653
  if (r.status !== 0) throw new PueueError("pueue install succeeded but binary not found on PATH.");
14535
14654
  } else {
14536
14655
  throw new PueueError("pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue");
14537
14656
  }
14538
14657
  }
14539
- const status = spawnSync7("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
14658
+ const status = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
14540
14659
  if (status.status !== 0) {
14541
14660
  console.log(" Starting pueued daemon...");
14542
14661
  const child = spawn2("pueued", ["-d"], { stdio: "ignore", detached: true });
14543
14662
  child.unref();
14544
- spawnSync7("sleep", ["1"]);
14545
- const retry = spawnSync7("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
14663
+ spawnSync8("sleep", ["1"]);
14664
+ const retry = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
14546
14665
  if (retry.status !== 0) {
14547
14666
  throw new PueueError("pueue daemon not reachable after starting pueued. Check `pueued` manually.");
14548
14667
  }
14549
14668
  }
14550
- spawnSync7("pueue", ["parallel", "2"], { encoding: "utf-8" });
14669
+ spawnSync8("pueue", ["parallel", "2"], { encoding: "utf-8" });
14551
14670
  }
14552
14671
  function assertClaudeInstalled() {
14553
- const r = spawnSync7("claude", ["--version"], { encoding: "utf-8" });
14672
+ const r = spawnSync8("claude", ["--version"], { encoding: "utf-8" });
14554
14673
  if (r.status !== 0) {
14555
14674
  throw new PueueError("claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code");
14556
14675
  }
14557
14676
  }
14558
14677
  function assertTmuxInstalled() {
14559
- let r = spawnSync7("tmux", ["-V"], { encoding: "utf-8" });
14678
+ let r = spawnSync8("tmux", ["-V"], { encoding: "utf-8" });
14560
14679
  if (r.status !== 0) {
14561
14680
  if (process.platform === "darwin" && brewInstall("tmux")) {
14562
- r = spawnSync7("tmux", ["-V"], { encoding: "utf-8" });
14681
+ r = spawnSync8("tmux", ["-V"], { encoding: "utf-8" });
14563
14682
  if (r.status !== 0) throw new PueueError("tmux install succeeded but binary not found on PATH.");
14564
14683
  } else {
14565
14684
  throw new PueueError("tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)");
@@ -14572,12 +14691,12 @@ var init_pueue = __esm({
14572
14691
  "use strict";
14573
14692
  TASK_LABEL = "synkro-local-cc";
14574
14693
  TMUX_SESSION = "synkro-local-cc";
14575
- SESSION_DIR2 = join14(homedir14(), ".synkro", "cc_sessions");
14694
+ SESSION_DIR2 = join15(homedir15(), ".synkro", "cc_sessions");
14576
14695
  TASK_LABEL_2 = "synkro-local-cc-2";
14577
14696
  TMUX_SESSION_2 = "synkro-local-cc-2";
14578
- SESSION_DIR_22 = join14(homedir14(), ".synkro", "cc_sessions_2");
14579
- SESSION_DIR_32 = join14(homedir14(), ".synkro", "cc_sessions_3");
14580
- SESSION_DIR_42 = join14(homedir14(), ".synkro", "cc_sessions_4");
14697
+ SESSION_DIR_22 = join15(homedir15(), ".synkro", "cc_sessions_2");
14698
+ SESSION_DIR_32 = join15(homedir15(), ".synkro", "cc_sessions_3");
14699
+ SESSION_DIR_42 = join15(homedir15(), ".synkro", "cc_sessions_4");
14581
14700
  PueueError = class extends Error {
14582
14701
  constructor(message, cause) {
14583
14702
  super(message);
@@ -14592,13 +14711,13 @@ var init_pueue = __esm({
14592
14711
  });
14593
14712
 
14594
14713
  // cli/local-cc/settings.ts
14595
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
14596
- import { homedir as homedir15 } from "os";
14597
- import { join as join15 } from "path";
14714
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
14715
+ import { homedir as homedir16 } from "os";
14716
+ import { join as join16 } from "path";
14598
14717
  function isLocalCCEnabled() {
14599
- if (!existsSync15(CONFIG_PATH3)) return false;
14718
+ if (!existsSync16(CONFIG_PATH3)) return false;
14600
14719
  try {
14601
- const content = readFileSync13(CONFIG_PATH3, "utf-8");
14720
+ const content = readFileSync14(CONFIG_PATH3, "utf-8");
14602
14721
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
14603
14722
  return match?.[1] === "yes";
14604
14723
  } catch {
@@ -14609,7 +14728,7 @@ var CONFIG_PATH3;
14609
14728
  var init_settings = __esm({
14610
14729
  "cli/local-cc/settings.ts"() {
14611
14730
  "use strict";
14612
- CONFIG_PATH3 = join15(homedir15(), ".synkro", "config.env");
14731
+ CONFIG_PATH3 = join16(homedir16(), ".synkro", "config.env");
14613
14732
  }
14614
14733
  });
14615
14734
 
@@ -14618,11 +14737,11 @@ var localCc_exports = {};
14618
14737
  __export(localCc_exports, {
14619
14738
  localCcCommand: () => localCcCommand
14620
14739
  });
14621
- import { spawnSync as spawnSync8 } from "child_process";
14622
- import { homedir as homedir16 } from "os";
14623
- import { join as join16 } from "path";
14740
+ import { spawnSync as spawnSync9 } from "child_process";
14741
+ import { homedir as homedir17 } from "os";
14742
+ import { join as join17 } from "path";
14624
14743
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
14625
- import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
14744
+ import { existsSync as existsSync17, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
14626
14745
  function deploymentMode() {
14627
14746
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
14628
14747
  if (env === "docker") return "docker";
@@ -14728,15 +14847,15 @@ TROUBLESHOOTING
14728
14847
  `);
14729
14848
  }
14730
14849
  function readGatewayUrl() {
14731
- if (existsSync16(CONFIG_PATH4)) {
14732
- const m = readFileSync14(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
14850
+ if (existsSync17(CONFIG_PATH4)) {
14851
+ const m = readFileSync15(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
14733
14852
  if (m) return m[1];
14734
14853
  }
14735
14854
  return "https://api.synkro.sh";
14736
14855
  }
14737
14856
  function updateLocalInferenceFlag(enabled) {
14738
- if (!existsSync16(CONFIG_PATH4)) return;
14739
- let content = readFileSync14(CONFIG_PATH4, "utf-8");
14857
+ if (!existsSync17(CONFIG_PATH4)) return;
14858
+ let content = readFileSync15(CONFIG_PATH4, "utf-8");
14740
14859
  const flag = enabled ? "yes" : "no";
14741
14860
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
14742
14861
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -14799,7 +14918,7 @@ async function cmdStatus() {
14799
14918
  }
14800
14919
  const ch1Up = await isChannelAvailable();
14801
14920
  console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? "reachable" : "unreachable"}`);
14802
- const tmux1 = spawnSync8("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
14921
+ const tmux1 = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
14803
14922
  console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? "live" : "absent"}`);
14804
14923
  const t2 = findTask(CHANNEL_SECONDARY);
14805
14924
  if (!t2) {
@@ -14809,7 +14928,7 @@ async function cmdStatus() {
14809
14928
  }
14810
14929
  const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);
14811
14930
  console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? "reachable" : "unreachable"}`);
14812
- const tmux2 = spawnSync8("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
14931
+ const tmux2 = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
14813
14932
  console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? "live" : "absent"}`);
14814
14933
  }
14815
14934
  async function cmdEnable() {
@@ -15015,7 +15134,7 @@ function cmdLogs(rest) {
15015
15134
  }
15016
15135
  return "200";
15017
15136
  })();
15018
- spawnSync8("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
15137
+ spawnSync9("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
15019
15138
  return;
15020
15139
  }
15021
15140
  for (const arg of rest) {
@@ -15063,7 +15182,7 @@ function cmdLogs(rest) {
15063
15182
  function cmdAttach(rest) {
15064
15183
  assertTmuxInstalled();
15065
15184
  const readonly = rest.some((a) => a === "--readonly" || a === "-r");
15066
- const has = spawnSync8("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
15185
+ const has = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
15067
15186
  if (has.status !== 0) {
15068
15187
  console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);
15069
15188
  process.exit(1);
@@ -15076,7 +15195,7 @@ function cmdAttach(rest) {
15076
15195
  console.log("Detach with Ctrl-B then D. (Do not press Ctrl-C \u2014 that would interrupt claude.)");
15077
15196
  console.log();
15078
15197
  const args2 = readonly ? ["attach-session", "-r", "-t", TMUX_SESSION_NAME] : ["attach-session", "-t", TMUX_SESSION_NAME];
15079
- const r = spawnSync8("tmux", args2, { stdio: "inherit" });
15198
+ const r = spawnSync9("tmux", args2, { stdio: "inherit" });
15080
15199
  process.exit(r.status ?? 0);
15081
15200
  }
15082
15201
  async function cmdTest() {
@@ -15177,8 +15296,8 @@ var init_localCc = __esm({
15177
15296
  init_install();
15178
15297
  init_client();
15179
15298
  init_stub();
15180
- SYNKRO_CONFIG_PATH = join16(homedir16(), ".synkro", "config.env");
15181
- CONFIG_PATH4 = join16(homedir16(), ".synkro", "config.env");
15299
+ SYNKRO_CONFIG_PATH = join17(homedir17(), ".synkro", "config.env");
15300
+ CONFIG_PATH4 = join17(homedir17(), ".synkro", "config.env");
15182
15301
  }
15183
15302
  });
15184
15303
 
@@ -15187,15 +15306,15 @@ var import_exports = {};
15187
15306
  __export(import_exports, {
15188
15307
  importCommand: () => importCommand
15189
15308
  });
15190
- import { existsSync as existsSync17, readFileSync as readFileSync15, readdirSync as readdirSync5 } from "fs";
15191
- import { homedir as homedir17 } from "os";
15192
- import { join as join17 } from "path";
15309
+ import { existsSync as existsSync18, readFileSync as readFileSync16, readdirSync as readdirSync5 } from "fs";
15310
+ import { homedir as homedir18 } from "os";
15311
+ import { join as join18 } from "path";
15193
15312
  import { execSync as execSync7 } from "child_process";
15194
15313
  import { createInterface as createInterface5 } from "readline";
15195
15314
  function readConfigEnv() {
15196
15315
  const out = {};
15197
15316
  try {
15198
- for (const line of readFileSync15(CONFIG_PATH5, "utf-8").split("\n")) {
15317
+ for (const line of readFileSync16(CONFIG_PATH5, "utf-8").split("\n")) {
15199
15318
  const t = line.trim();
15200
15319
  if (!t || t.startsWith("#")) continue;
15201
15320
  const eq = t.indexOf("=");
@@ -15207,8 +15326,8 @@ function readConfigEnv() {
15207
15326
  }
15208
15327
  function projectsFolder() {
15209
15328
  const sanitized = process.cwd().replace(/\//g, "-");
15210
- const dir = join17(homedir17(), ".claude", "projects", sanitized);
15211
- return existsSync17(dir) ? dir : null;
15329
+ const dir = join18(homedir18(), ".claude", "projects", sanitized);
15330
+ return existsSync18(dir) ? dir : null;
15212
15331
  }
15213
15332
  function repoName() {
15214
15333
  try {
@@ -15231,7 +15350,7 @@ function extractText(content) {
15231
15350
  return "";
15232
15351
  }
15233
15352
  function parseSession(filePath, sessionId) {
15234
- const lines = readFileSync15(filePath, "utf-8").split("\n").filter(Boolean);
15353
+ const lines = readFileSync16(filePath, "utf-8").split("\n").filter(Boolean);
15235
15354
  const messages = [];
15236
15355
  const actions = [];
15237
15356
  let step = 0;
@@ -15304,7 +15423,7 @@ async function importCommand() {
15304
15423
  return;
15305
15424
  }
15306
15425
  }
15307
- const sessions = files.map((f) => parseSession(join17(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
15426
+ const sessions = files.map((f) => parseSession(join18(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
15308
15427
  const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
15309
15428
  let ok = 0, fail = 0;
15310
15429
  if (isCloud) {
@@ -15364,7 +15483,7 @@ var init_import = __esm({
15364
15483
  "cli/commands/import.ts"() {
15365
15484
  "use strict";
15366
15485
  init_stub();
15367
- CONFIG_PATH5 = join17(homedir17(), ".synkro", "config.env");
15486
+ CONFIG_PATH5 = join18(homedir18(), ".synkro", "config.env");
15368
15487
  }
15369
15488
  });
15370
15489
 
@@ -15406,10 +15525,10 @@ var init_packVerify = __esm({
15406
15525
  });
15407
15526
 
15408
15527
  // cli/installer/lockfile.ts
15409
- import { existsSync as existsSync18, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
15410
- import { join as join18 } from "path";
15528
+ import { existsSync as existsSync19, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
15529
+ import { join as join19 } from "path";
15411
15530
  function lockPath(repoRoot) {
15412
- return join18(repoRoot, LOCK_FILE);
15531
+ return join19(repoRoot, LOCK_FILE);
15413
15532
  }
15414
15533
  function writeLockfile(repoRoot, entries) {
15415
15534
  const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
@@ -15442,9 +15561,9 @@ var sync_exports = {};
15442
15561
  __export(sync_exports, {
15443
15562
  syncCommand: () => syncCommand
15444
15563
  });
15445
- import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync12 } from "fs";
15446
- import { homedir as homedir18 } from "os";
15447
- import { join as join19 } from "path";
15564
+ import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync6, rmSync as rmSync3, writeFileSync as writeFileSync12 } from "fs";
15565
+ import { homedir as homedir19 } from "os";
15566
+ import { join as join20 } from "path";
15448
15567
  function cacheKey(ref, version) {
15449
15568
  return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
15450
15569
  }
@@ -15475,8 +15594,8 @@ async function syncCommand(_args = []) {
15475
15594
  }
15476
15595
  const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
15477
15596
  const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
15478
- const cacheDir = join19(homedir18(), ".synkro", "cache", "packs");
15479
- if (!cloud) mkdirSync12(cacheDir, { recursive: true });
15597
+ const cacheDir = join20(homedir19(), ".synkro", "cache", "packs");
15598
+ if (!cloud) mkdirSync13(cacheDir, { recursive: true });
15480
15599
  console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
15481
15600
  const lock = [];
15482
15601
  const keptCacheFiles = /* @__PURE__ */ new Set();
@@ -15504,7 +15623,7 @@ async function syncCommand(_args = []) {
15504
15623
  if (!cloud) {
15505
15624
  const fname = cacheKey(ref, data.version);
15506
15625
  keptCacheFiles.add(fname);
15507
- writeFileSync12(join19(cacheDir, fname), JSON.stringify({
15626
+ writeFileSync12(join20(cacheDir, fname), JSON.stringify({
15508
15627
  ref,
15509
15628
  version: data.version,
15510
15629
  digest: data.digest,
@@ -15516,11 +15635,11 @@ async function syncCommand(_args = []) {
15516
15635
  const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
15517
15636
  console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
15518
15637
  }
15519
- if (!cloud && existsSync19(cacheDir)) {
15638
+ if (!cloud && existsSync20(cacheDir)) {
15520
15639
  for (const f of readdirSync6(cacheDir)) {
15521
15640
  if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
15522
15641
  try {
15523
- rmSync2(join19(cacheDir, f));
15642
+ rmSync3(join20(cacheDir, f));
15524
15643
  } catch {
15525
15644
  }
15526
15645
  }
@@ -15656,13 +15775,13 @@ var config_exports = {};
15656
15775
  __export(config_exports, {
15657
15776
  configCommand: () => configCommand
15658
15777
  });
15659
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync20 } from "fs";
15660
- import { join as join20 } from "path";
15661
- import { homedir as homedir19 } from "os";
15778
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync21 } from "fs";
15779
+ import { join as join21 } from "path";
15780
+ import { homedir as homedir20 } from "os";
15662
15781
  function readConfigEnv2() {
15663
- if (!existsSync20(CONFIG_PATH6)) return {};
15782
+ if (!existsSync21(CONFIG_PATH6)) return {};
15664
15783
  const out = {};
15665
- for (const line of readFileSync17(CONFIG_PATH6, "utf-8").split("\n")) {
15784
+ for (const line of readFileSync18(CONFIG_PATH6, "utf-8").split("\n")) {
15666
15785
  const t = line.trim();
15667
15786
  if (!t || t.startsWith("#")) continue;
15668
15787
  const eq = t.indexOf("=");
@@ -15671,11 +15790,11 @@ function readConfigEnv2() {
15671
15790
  return out;
15672
15791
  }
15673
15792
  function updateConfigValue(key, value) {
15674
- if (!existsSync20(CONFIG_PATH6)) {
15793
+ if (!existsSync21(CONFIG_PATH6)) {
15675
15794
  console.error("No config found. Run `synkro install` first.");
15676
15795
  process.exit(1);
15677
15796
  }
15678
- const lines = readFileSync17(CONFIG_PATH6, "utf-8").split("\n");
15797
+ const lines = readFileSync18(CONFIG_PATH6, "utf-8").split("\n");
15679
15798
  const pattern = new RegExp(`^${key}=`);
15680
15799
  let found = false;
15681
15800
  const updated = lines.map((line) => {
@@ -15813,25 +15932,25 @@ To change:`);
15813
15932
  }
15814
15933
  if (inferenceValue !== "cloud") await reconcileContainer();
15815
15934
  }
15816
- var SYNKRO_DIR7, CONFIG_PATH6;
15935
+ var SYNKRO_DIR8, CONFIG_PATH6;
15817
15936
  var init_config = __esm({
15818
15937
  "cli/commands/config.ts"() {
15819
15938
  "use strict";
15820
15939
  init_stub();
15821
- SYNKRO_DIR7 = join20(homedir19(), ".synkro");
15822
- CONFIG_PATH6 = join20(SYNKRO_DIR7, "config.env");
15940
+ SYNKRO_DIR8 = join21(homedir20(), ".synkro");
15941
+ CONFIG_PATH6 = join21(SYNKRO_DIR8, "config.env");
15823
15942
  }
15824
15943
  });
15825
15944
 
15826
15945
  // cli/bootstrap.js
15827
- import { readFileSync as readFileSync18, existsSync as existsSync21 } from "fs";
15946
+ import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
15828
15947
  import { resolve as resolve3 } from "path";
15829
15948
  var envCandidates = [
15830
15949
  resolve3(process.env.HOME ?? "", ".synkro", "config.env")
15831
15950
  ];
15832
15951
  for (const envPath of envCandidates) {
15833
- if (!existsSync21(envPath)) continue;
15834
- const envContent = readFileSync18(envPath, "utf-8");
15952
+ if (!existsSync22(envPath)) continue;
15953
+ const envContent = readFileSync19(envPath, "utf-8");
15835
15954
  for (const line of envContent.split("\n")) {
15836
15955
  const trimmed = line.trim();
15837
15956
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -15846,7 +15965,7 @@ var args = process.argv.slice(2);
15846
15965
  var cmd = args[0] || "";
15847
15966
  var subArgs = args.slice(1);
15848
15967
  function printVersion() {
15849
- console.log("1.7.18");
15968
+ console.log("1.7.27");
15850
15969
  }
15851
15970
  function printHelp2() {
15852
15971
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents