@synkro-sh/cli 1.6.32 → 1.6.34

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
@@ -601,6 +601,58 @@ var init_mcpConfig = __esm({
601
601
  }
602
602
  });
603
603
 
604
+ // cli/installer/skillParser.ts
605
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
606
+ import { resolve as resolve2, basename } from "path";
607
+ function parseSection(heading, body) {
608
+ const lines = body.split("\n");
609
+ const meta = {};
610
+ const textLines = [];
611
+ for (const line of lines) {
612
+ const m = line.match(/^(mode|severity|category)\s*:\s*(.+)/i);
613
+ if (m && META_KEYS.has(m[1].toLowerCase())) {
614
+ meta[m[1].toLowerCase()] = m[2].trim();
615
+ } else if (line.trim()) {
616
+ textLines.push(line.trim());
617
+ }
618
+ }
619
+ const description = textLines.join(" ").trim();
620
+ const text = description ? `${heading}: ${description}` : heading;
621
+ return {
622
+ text,
623
+ mode: meta.mode || "ask",
624
+ severity: meta.severity || "medium",
625
+ category: meta.category || "custom"
626
+ };
627
+ }
628
+ function parseSkillFile(filePath) {
629
+ if (!existsSync4(filePath)) return null;
630
+ const content = readFileSync4(filePath, "utf-8");
631
+ const source = `skill:${basename(filePath)}`;
632
+ const sections = content.split(/^## /m).slice(1);
633
+ if (sections.length === 0) return null;
634
+ const rules = [];
635
+ for (const section of sections) {
636
+ const newlineIdx = section.indexOf("\n");
637
+ if (newlineIdx === -1) continue;
638
+ const heading = section.slice(0, newlineIdx).trim();
639
+ const body = section.slice(newlineIdx + 1);
640
+ if (!heading) continue;
641
+ rules.push(parseSection(heading, body));
642
+ }
643
+ return rules.length > 0 ? { source, rules } : null;
644
+ }
645
+ function resolveSkillPaths(skills, repoRoot) {
646
+ return skills.filter((s) => s.endsWith(".md")).map((s) => resolve2(repoRoot, s)).filter((p) => existsSync4(p));
647
+ }
648
+ var META_KEYS;
649
+ var init_skillParser = __esm({
650
+ "cli/installer/skillParser.ts"() {
651
+ "use strict";
652
+ META_KEYS = /* @__PURE__ */ new Set(["mode", "severity", "category"]);
653
+ }
654
+ });
655
+
604
656
  // cli/installer/hookScripts.ts
605
657
  var SYNKRO_COMMON_SCRIPT;
606
658
  var init_hookScripts = __esm({
@@ -1144,15 +1196,19 @@ export function findGitRoot(cwd: string): string {
1144
1196
 
1145
1197
  export interface SynkroFileConfig {
1146
1198
  version: number;
1199
+ harness: ('claude-code' | 'cursor')[];
1147
1200
  grader: { pool: 'auto' | 'claude' | 'cursor'; mode?: string };
1148
1201
  ruleset: string;
1202
+ skills: string[];
1149
1203
  scanning: { cwe: boolean; cve: boolean };
1150
1204
  }
1151
1205
 
1152
1206
  const SYNKRO_FILE_DEFAULTS: SynkroFileConfig = {
1153
1207
  version: 1,
1208
+ harness: ['claude-code', 'cursor'],
1154
1209
  grader: { pool: 'auto' },
1155
1210
  ruleset: 'default',
1211
+ skills: [],
1156
1212
  scanning: { cwe: true, cve: true },
1157
1213
  };
1158
1214
 
@@ -1166,13 +1222,19 @@ export function loadSynkroFile(cwd?: string): SynkroFileConfig {
1166
1222
  try {
1167
1223
  if (!existsSync(fp)) { _synkroFileCache = SYNKRO_FILE_DEFAULTS; return _synkroFileCache; }
1168
1224
  const parsed = JSON.parse(readFileSync(fp, 'utf-8'));
1225
+ const validHarness = ['claude-code', 'cursor'] as const;
1226
+ const harness = Array.isArray(parsed.harness)
1227
+ ? parsed.harness.filter((h: string) => validHarness.includes(h as any))
1228
+ : ['claude-code', 'cursor'];
1169
1229
  _synkroFileCache = {
1170
1230
  version: parsed.version || 1,
1231
+ harness: harness.length > 0 ? harness : ['claude-code', 'cursor'],
1171
1232
  grader: {
1172
1233
  pool: ['auto', 'claude', 'cursor'].includes(parsed.grader?.pool) ? parsed.grader.pool : 'auto',
1173
1234
  mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : undefined,
1174
1235
  },
1175
1236
  ruleset: typeof parsed.ruleset === 'string' ? parsed.ruleset : 'default',
1237
+ skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string') : [],
1176
1238
  scanning: {
1177
1239
  cwe: parsed.scanning?.cwe !== false,
1178
1240
  cve: parsed.scanning?.cve !== false,
@@ -5939,7 +6001,7 @@ __export(stub_exports, {
5939
6001
  saveCredentials: () => saveCredentials
5940
6002
  });
5941
6003
  import { createServer } from "http";
5942
- import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
6004
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
5943
6005
  import { homedir as homedir4, platform } from "os";
5944
6006
  import { join as join3, dirname as dirname4 } from "path";
5945
6007
  import { execFile } from "child_process";
@@ -5970,17 +6032,17 @@ function openBrowser(url) {
5970
6032
  }
5971
6033
  function saveCredentials(data) {
5972
6034
  const dir = dirname4(AUTH_FILE);
5973
- if (!existsSync4(dir)) {
6035
+ if (!existsSync5(dir)) {
5974
6036
  mkdirSync4(dir, { recursive: true, mode: 448 });
5975
6037
  }
5976
6038
  writeFileSync4(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
5977
6039
  }
5978
6040
  function loadCredentials() {
5979
- if (!existsSync4(AUTH_FILE)) {
6041
+ if (!existsSync5(AUTH_FILE)) {
5980
6042
  return null;
5981
6043
  }
5982
6044
  try {
5983
- const content = readFileSync4(AUTH_FILE, "utf8");
6045
+ const content = readFileSync5(AUTH_FILE, "utf8");
5984
6046
  return JSON.parse(content);
5985
6047
  } catch (error) {
5986
6048
  return null;
@@ -5993,7 +6055,7 @@ function createCallbackServer() {
5993
6055
  "Access-Control-Allow-Headers": "Content-Type",
5994
6056
  "Vary": "Origin"
5995
6057
  };
5996
- return new Promise((resolve3, reject) => {
6058
+ return new Promise((resolve4, reject) => {
5997
6059
  const server = createServer((req, res) => {
5998
6060
  if (req.method === "OPTIONS") {
5999
6061
  const origin = req.headers.origin;
@@ -6082,7 +6144,7 @@ function createCallbackServer() {
6082
6144
  res.end(JSON.stringify({ ok: true }));
6083
6145
  setTimeout(() => {
6084
6146
  server.close();
6085
- resolve3(authData);
6147
+ resolve4(authData);
6086
6148
  }, 200);
6087
6149
  });
6088
6150
  req.on("error", (e) => {
@@ -6235,7 +6297,7 @@ async function ensureValidToken() {
6235
6297
  return true;
6236
6298
  }
6237
6299
  function clearCredentials() {
6238
- if (existsSync4(AUTH_FILE)) {
6300
+ if (existsSync5(AUTH_FILE)) {
6239
6301
  unlinkSync2(AUTH_FILE);
6240
6302
  }
6241
6303
  }
@@ -6415,7 +6477,7 @@ jobs:
6415
6477
  });
6416
6478
 
6417
6479
  // cli/installer/githubSetup.ts
6418
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
6480
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
6419
6481
  import { execSync as execSync2 } from "child_process";
6420
6482
  import { join as join4 } from "path";
6421
6483
  function ghSecretSet(token, owner, repo, name, value) {
@@ -6472,7 +6534,7 @@ function writeWorkflowFile(repoRootPath) {
6472
6534
  function findGitRoot(startCwd) {
6473
6535
  let cur = startCwd;
6474
6536
  while (cur && cur !== "/") {
6475
- if (existsSync5(join4(cur, ".git"))) return cur;
6537
+ if (existsSync6(join4(cur, ".git"))) return cur;
6476
6538
  const parent = join4(cur, "..");
6477
6539
  if (parent === cur) break;
6478
6540
  cur = parent;
@@ -6538,10 +6600,10 @@ function detectSubdirRepos() {
6538
6600
  }
6539
6601
  }
6540
6602
  function ask(rl, question) {
6541
- return new Promise((resolve3) => rl.question(question, resolve3));
6603
+ return new Promise((resolve4) => rl.question(question, resolve4));
6542
6604
  }
6543
6605
  function waitForGithubToken() {
6544
- return new Promise((resolve3, reject) => {
6606
+ return new Promise((resolve4, reject) => {
6545
6607
  const server = createServer2((req, res) => {
6546
6608
  if (req.method === "OPTIONS") {
6547
6609
  res.writeHead(204, {
@@ -6578,7 +6640,7 @@ function waitForGithubToken() {
6578
6640
  });
6579
6641
  res.end(JSON.stringify({ ok: true }));
6580
6642
  setTimeout(() => server.close(), 200);
6581
- resolve3(parsed.github_token);
6643
+ resolve4(parsed.github_token);
6582
6644
  } catch {
6583
6645
  res.writeHead(400, { "Content-Type": "application/json" });
6584
6646
  res.end(JSON.stringify({ error: "invalid json" }));
@@ -6838,7 +6900,7 @@ __export(macKeychain_exports, {
6838
6900
  writeCursorApiKey: () => writeCursorApiKey,
6839
6901
  writeRefreshAgent: () => writeRefreshAgent
6840
6902
  });
6841
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, chmodSync, readFileSync as readFileSync5, statSync } from "fs";
6903
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, chmodSync, readFileSync as readFileSync6, statSync } from "fs";
6842
6904
  import { homedir as homedir5, platform as platform2 } from "os";
6843
6905
  import { join as join5 } from "path";
6844
6906
  import { spawnSync } from "child_process";
@@ -6866,7 +6928,7 @@ function exportKeychainCreds() {
6866
6928
  }
6867
6929
  function cursorApiKeyConfigured() {
6868
6930
  try {
6869
- return existsSync6(CURSOR_API_KEY_FILE) && readFileSync5(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
6931
+ return existsSync7(CURSOR_API_KEY_FILE) && readFileSync6(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
6870
6932
  } catch {
6871
6933
  return false;
6872
6934
  }
@@ -6882,7 +6944,7 @@ function writeCursorApiKey(key) {
6882
6944
  async function validateCursorApiKey() {
6883
6945
  let key;
6884
6946
  try {
6885
- key = readFileSync5(CURSOR_API_KEY_FILE, "utf-8").trim();
6947
+ key = readFileSync6(CURSOR_API_KEY_FILE, "utf-8").trim();
6886
6948
  } catch {
6887
6949
  return null;
6888
6950
  }
@@ -6901,7 +6963,7 @@ async function validateCursorApiKey() {
6901
6963
  }
6902
6964
  }
6903
6965
  function credsAreStale() {
6904
- if (!existsSync6(CLAUDE_CREDS_FILE)) return true;
6966
+ if (!existsSync7(CLAUDE_CREDS_FILE)) return true;
6905
6967
  try {
6906
6968
  const ageMs = Date.now() - statSync(CLAUDE_CREDS_FILE).mtimeMs;
6907
6969
  return ageMs > REFRESH_INTERVAL_SECONDS * 1e3;
@@ -6964,7 +7026,7 @@ function uninstallRefreshAgent() {
6964
7026
  timeout: 5e3
6965
7027
  });
6966
7028
  try {
6967
- if (existsSync6(LAUNCHD_PLIST)) {
7029
+ if (existsSync7(LAUNCHD_PLIST)) {
6968
7030
  __require("fs").unlinkSync(LAUNCHD_PLIST);
6969
7031
  }
6970
7032
  } catch {
@@ -6976,7 +7038,7 @@ function refreshCreds() {
6976
7038
  }
6977
7039
  function readExportedCreds() {
6978
7040
  try {
6979
- return readFileSync5(CLAUDE_CREDS_FILE, "utf-8");
7041
+ return readFileSync6(CLAUDE_CREDS_FILE, "utf-8");
6980
7042
  } catch {
6981
7043
  return null;
6982
7044
  }
@@ -7025,7 +7087,7 @@ __export(dockerInstall_exports, {
7025
7087
  splitWorkers: () => splitWorkers,
7026
7088
  waitForContainerReady: () => waitForContainerReady
7027
7089
  });
7028
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
7090
+ import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
7029
7091
  import { homedir as homedir6 } from "os";
7030
7092
  import { join as join6 } from "path";
7031
7093
  import { execSync as execSync4, spawnSync as spawnSync2 } from "child_process";
@@ -7051,8 +7113,8 @@ function readSynkroFilePool() {
7051
7113
  const root = execSync4("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
7052
7114
  if (!root) return "auto";
7053
7115
  const fp = join6(root, ".synkro");
7054
- if (!existsSync7(fp)) return "auto";
7055
- const parsed = JSON.parse(readFileSync6(fp, "utf-8"));
7116
+ if (!existsSync8(fp)) return "auto";
7117
+ const parsed = JSON.parse(readFileSync7(fp, "utf-8"));
7056
7118
  const pool = parsed?.grader?.pool;
7057
7119
  if (pool === "cursor" || pool === "claude") return pool;
7058
7120
  } catch {
@@ -7179,10 +7241,10 @@ async function dockerInstall(opts = {}) {
7179
7241
  mkdirSync7(BACKUP_DIR, { recursive: true });
7180
7242
  mkdirSync7(CLAUDE_HOST_STATE_DIR, { recursive: true });
7181
7243
  const hostClaudeJson = join6(homedir6(), ".claude.json");
7182
- if (existsSync7(hostClaudeJson)) {
7244
+ if (existsSync8(hostClaudeJson)) {
7183
7245
  copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);
7184
7246
  }
7185
- if (!existsSync7(MCP_JWT_PATH)) {
7247
+ if (!existsSync8(MCP_JWT_PATH)) {
7186
7248
  throw new DockerInstallError(
7187
7249
  `MCP JWT missing at ${MCP_JWT_PATH}. The installer should mint this before calling dockerInstall.`
7188
7250
  );
@@ -7417,7 +7479,7 @@ async function dockerSafeStart() {
7417
7479
  return { ok: false, pgdataState: "no_container", error: "No synkro-server container found. Run `synkro install` first." };
7418
7480
  }
7419
7481
  const pgCheck = checkPgdata();
7420
- if (existsSync7(PGDATA_PATH) && readdirSync2(PGDATA_PATH).length > 0) {
7482
+ if (existsSync8(PGDATA_PATH) && readdirSync2(PGDATA_PATH).length > 0) {
7421
7483
  if (pgCheck.healthy) {
7422
7484
  console.log(` pgdata: existing data found \u2014 ${pgCheck.details}`);
7423
7485
  } else {
@@ -7457,7 +7519,7 @@ async function dockerSafeRestart() {
7457
7519
  return { ok: startResult.ok, stop: stopResult, start: startResult };
7458
7520
  }
7459
7521
  function checkPgdata() {
7460
- if (!existsSync7(PGDATA_PATH)) return { healthy: false, details: "pgdata directory does not exist" };
7522
+ if (!existsSync8(PGDATA_PATH)) return { healthy: false, details: "pgdata directory does not exist" };
7461
7523
  const entries = readdirSync2(PGDATA_PATH);
7462
7524
  if (entries.length === 0) return { healthy: true, details: "empty (fresh start)" };
7463
7525
  const hasPidFile = entries.includes("postmaster.pid");
@@ -7506,14 +7568,14 @@ __export(setupGithub_exports, {
7506
7568
  import { createInterface as createInterface2 } from "readline/promises";
7507
7569
  import { stdin as input, stdout as output } from "process";
7508
7570
  import { execSync as execSync5, spawn as nodeSpawn } from "child_process";
7509
- import { existsSync as existsSync8, readFileSync as readFileSync7, unlinkSync as unlinkSync3 } from "fs";
7571
+ import { existsSync as existsSync9, readFileSync as readFileSync8, unlinkSync as unlinkSync3 } from "fs";
7510
7572
  import { homedir as homedir7, platform as platform3 } from "os";
7511
7573
  import { join as join7 } from "path";
7512
7574
  import { execFile as execFile2 } from "child_process";
7513
7575
  function readConfig() {
7514
- if (!existsSync8(CONFIG_PATH)) return {};
7576
+ if (!existsSync9(CONFIG_PATH)) return {};
7515
7577
  const out = {};
7516
- for (const line of readFileSync7(CONFIG_PATH, "utf-8").split("\n")) {
7578
+ for (const line of readFileSync8(CONFIG_PATH, "utf-8").split("\n")) {
7517
7579
  const t = line.trim();
7518
7580
  if (!t || t.startsWith("#")) continue;
7519
7581
  const eq = t.indexOf("=");
@@ -7526,7 +7588,7 @@ async function prompt(rl, q, opts = {}) {
7526
7588
  process.stdout.write(q);
7527
7589
  const wasRaw = process.stdin.isRaw;
7528
7590
  if (process.stdin.setRawMode) process.stdin.setRawMode(true);
7529
- return await new Promise((resolve3) => {
7591
+ return await new Promise((resolve4) => {
7530
7592
  let chunk = "";
7531
7593
  const onData = (data) => {
7532
7594
  const s = data.toString("utf-8");
@@ -7534,7 +7596,7 @@ async function prompt(rl, q, opts = {}) {
7534
7596
  process.stdin.removeListener("data", onData);
7535
7597
  if (process.stdin.setRawMode) process.stdin.setRawMode(wasRaw ?? false);
7536
7598
  process.stdout.write("\n");
7537
- resolve3(chunk);
7599
+ resolve4(chunk);
7538
7600
  return;
7539
7601
  }
7540
7602
  if (s === "") process.exit(130);
@@ -7575,7 +7637,7 @@ function sleep(ms) {
7575
7637
  }
7576
7638
  function captureClaudeSetupToken() {
7577
7639
  const tmpFile = join7(SYNKRO_DIR3, `token-capture-${Date.now()}.raw`);
7578
- return new Promise((resolve3, reject) => {
7640
+ return new Promise((resolve4, reject) => {
7579
7641
  const proc = nodeSpawn("script", ["-q", tmpFile, "claude", "setup-token"], {
7580
7642
  stdio: "inherit"
7581
7643
  });
@@ -7583,7 +7645,7 @@ function captureClaudeSetupToken() {
7583
7645
  proc.on("close", (code) => {
7584
7646
  let raw = "";
7585
7647
  try {
7586
- raw = readFileSync7(tmpFile, "utf-8");
7648
+ raw = readFileSync8(tmpFile, "utf-8");
7587
7649
  } catch (e) {
7588
7650
  reject(new Error(`Could not read script output file: ${e.message}`));
7589
7651
  return;
@@ -7605,7 +7667,7 @@ function captureClaudeSetupToken() {
7605
7667
  reject(new Error(`Could not find token in claude setup-token output (file=${raw.length}b, yellow=${yellow.length}b)`));
7606
7668
  return;
7607
7669
  }
7608
- resolve3(token[0]);
7670
+ resolve4(token[0]);
7609
7671
  });
7610
7672
  });
7611
7673
  }
@@ -7865,9 +7927,12 @@ var install_exports = {};
7865
7927
  __export(install_exports, {
7866
7928
  detectGitRepo: () => detectGitRepo2,
7867
7929
  installCommand: () => installCommand,
7868
- parseArgs: () => parseArgs
7930
+ parseArgs: () => parseArgs,
7931
+ reconcileHarness: () => reconcileHarness,
7932
+ syncSkillFiles: () => syncSkillFiles,
7933
+ writeHookScripts: () => writeHookScripts
7869
7934
  });
7870
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync8, readdirSync as readdirSync3 } from "fs";
7935
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync9, readdirSync as readdirSync3 } from "fs";
7871
7936
  import { homedir as homedir8 } from "os";
7872
7937
  import { join as join8 } from "path";
7873
7938
  import { execSync as execSync6, spawnSync as spawnSync3 } from "child_process";
@@ -7899,20 +7964,20 @@ async function promptAgentSelection(detected) {
7899
7964
  detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
7900
7965
  console.log(` ${detected.length + 1}. Both / all (default)`);
7901
7966
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7902
- const ask2 = () => new Promise((resolve3) => {
7967
+ const ask2 = () => new Promise((resolve4) => {
7903
7968
  rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
7904
7969
  const t = answer.trim().toLowerCase();
7905
7970
  if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
7906
7971
  rl.close();
7907
- return resolve3(detected);
7972
+ return resolve4(detected);
7908
7973
  }
7909
7974
  const n = parseInt(t, 10);
7910
7975
  if (Number.isInteger(n) && n >= 1 && n <= detected.length) {
7911
7976
  rl.close();
7912
- return resolve3([detected[n - 1]]);
7977
+ return resolve4([detected[n - 1]]);
7913
7978
  }
7914
7979
  console.log("Invalid choice. Try again.");
7915
- resolve3(ask2());
7980
+ resolve4(ask2());
7916
7981
  });
7917
7982
  });
7918
7983
  return ask2();
@@ -7935,12 +8000,12 @@ async function promptCursorApiKey(opts) {
7935
8000
  return;
7936
8001
  }
7937
8002
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7938
- const key = await new Promise((resolve3) => {
8003
+ const key = await new Promise((resolve4) => {
7939
8004
  rl.question(
7940
8005
  "Cursor grading needs a Cursor API key (cursor.com \u2192 Settings \u2192 API Keys).\nPaste it now, or press Enter to skip (Cursor workers stay idle until set): ",
7941
8006
  (answer) => {
7942
8007
  rl.close();
7943
- resolve3(answer.trim());
8008
+ resolve4(answer.trim());
7944
8009
  }
7945
8010
  );
7946
8011
  });
@@ -7954,12 +8019,12 @@ async function promptCursorApiKey(opts) {
7954
8019
  async function promptGradingMode() {
7955
8020
  if (!process.stdin.isTTY) return "local";
7956
8021
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7957
- return new Promise((resolve3) => {
8022
+ return new Promise((resolve4) => {
7958
8023
  rl.question(
7959
8024
  "Where should grading run?\n local \u2014 on this machine, via the Synkro container (default)\n byok \u2014 via an LLM API using your own provider key\nChoose [local] / byok: ",
7960
8025
  (answer) => {
7961
8026
  rl.close();
7962
- resolve3(answer.trim().toLowerCase() === "byok" ? "byok" : "local");
8027
+ resolve4(answer.trim().toLowerCase() === "byok" ? "byok" : "local");
7963
8028
  }
7964
8029
  );
7965
8030
  });
@@ -7967,25 +8032,25 @@ async function promptGradingMode() {
7967
8032
  async function promptStorageMode() {
7968
8033
  if (!process.stdin.isTTY) return "local";
7969
8034
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7970
- return new Promise((resolve3) => {
8035
+ return new Promise((resolve4) => {
7971
8036
  rl.question(
7972
8037
  "Where should telemetry be stored?\n local \u2014 on this machine only (default)\n cloud \u2014 sent to Synkro cloud\nChoose [local] / cloud: ",
7973
8038
  (answer) => {
7974
8039
  rl.close();
7975
- resolve3(answer.trim().toLowerCase() === "cloud" ? "cloud" : "local");
8040
+ resolve4(answer.trim().toLowerCase() === "cloud" ? "cloud" : "local");
7976
8041
  }
7977
8042
  );
7978
8043
  });
7979
8044
  }
7980
8045
  async function promptTranscriptConsent() {
7981
8046
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7982
- return new Promise((resolve3) => {
8047
+ return new Promise((resolve4) => {
7983
8048
  rl.question(
7984
8049
  "Import and embed your Claude Code session history?\nThis indexes past sessions so Ask Synkro can answer questions\nabout your coding patterns and the dashboard shows full history. (Y/n) ",
7985
8050
  (answer) => {
7986
8051
  rl.close();
7987
8052
  const trimmed = answer.trim().toLowerCase();
7988
- resolve3(trimmed === "" || trimmed === "y" || trimmed === "yes");
8053
+ resolve4(trimmed === "" || trimmed === "y" || trimmed === "yes");
7989
8054
  }
7990
8055
  );
7991
8056
  });
@@ -8093,7 +8158,7 @@ function shellQuoteSingle(value) {
8093
8158
  }
8094
8159
  function resolveSynkroBundle() {
8095
8160
  const scriptPath = process.argv[1];
8096
- if (scriptPath && existsSync9(scriptPath)) return scriptPath;
8161
+ if (scriptPath && existsSync10(scriptPath)) return scriptPath;
8097
8162
  return null;
8098
8163
  }
8099
8164
  function writeConfigEnv(opts) {
@@ -8113,7 +8178,7 @@ function writeConfigEnv(opts) {
8113
8178
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
8114
8179
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
8115
8180
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
8116
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.32")}`
8181
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.34")}`
8117
8182
  ];
8118
8183
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
8119
8184
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -8135,8 +8200,8 @@ function resolveDeploymentMode() {
8135
8200
  const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
8136
8201
  if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
8137
8202
  try {
8138
- if (existsSync9(CONFIG_PATH2)) {
8139
- const m = readFileSync8(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
8203
+ if (existsSync10(CONFIG_PATH2)) {
8204
+ const m = readFileSync9(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
8140
8205
  const val = m?.[1]?.toLowerCase();
8141
8206
  if (val === "bare-host" || val === "docker") return val;
8142
8207
  }
@@ -8165,14 +8230,14 @@ function collectLocalMetadata(includeClaudeCode = true) {
8165
8230
  }
8166
8231
  const claudeDir = join8(homedir8(), ".claude");
8167
8232
  try {
8168
- const settings = JSON.parse(readFileSync8(join8(claudeDir, "settings.json"), "utf-8"));
8233
+ const settings = JSON.parse(readFileSync9(join8(claudeDir, "settings.json"), "utf-8"));
8169
8234
  const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
8170
8235
  if (plugins.length) meta.enabled_plugins = plugins;
8171
8236
  if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
8172
8237
  } catch {
8173
8238
  }
8174
8239
  try {
8175
- const mcpCache = JSON.parse(readFileSync8(join8(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
8240
+ const mcpCache = JSON.parse(readFileSync9(join8(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
8176
8241
  const mcpNames = Object.keys(mcpCache);
8177
8242
  if (mcpNames.length) meta.mcp_servers = mcpNames;
8178
8243
  } catch {
@@ -8187,7 +8252,7 @@ function collectLocalMetadata(includeClaudeCode = true) {
8187
8252
  const sessionsDir = join8(claudeDir, "sessions");
8188
8253
  const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
8189
8254
  for (const f of files) {
8190
- const s = JSON.parse(readFileSync8(join8(sessionsDir, f), "utf-8"));
8255
+ const s = JSON.parse(readFileSync9(join8(sessionsDir, f), "utf-8"));
8191
8256
  if (s.version) {
8192
8257
  meta.cc_version = meta.cc_version || s.version;
8193
8258
  break;
@@ -8319,7 +8384,7 @@ async function installCommand(opts = {}) {
8319
8384
  for (const mode of ["edit", "bash"]) {
8320
8385
  const pidFile = join8(SYNKRO_DIR4, "daemon", mode, "daemon.pid");
8321
8386
  try {
8322
- const pid = parseInt(readFileSync8(pidFile, "utf-8").trim(), 10);
8387
+ const pid = parseInt(readFileSync9(pidFile, "utf-8").trim(), 10);
8323
8388
  if (pid > 0) {
8324
8389
  process.kill(pid, "SIGTERM");
8325
8390
  console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
@@ -8453,7 +8518,7 @@ async function installCommand(opts = {}) {
8453
8518
  try {
8454
8519
  if (useLocalMcp) {
8455
8520
  const jwtPath = join8(SYNKRO_DIR4, ".mcp-jwt");
8456
- if (!existsSync9(jwtPath)) {
8521
+ if (!existsSync10(jwtPath)) {
8457
8522
  const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
8458
8523
  method: "POST",
8459
8524
  headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
@@ -8543,7 +8608,7 @@ async function installCommand(opts = {}) {
8543
8608
  const ready = await waitForContainerReady(6e4);
8544
8609
  if (ready) {
8545
8610
  console.log(" \u2713 container ready");
8546
- const mcpJwt = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8611
+ const mcpJwt = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8547
8612
  try {
8548
8613
  const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
8549
8614
  method: "POST",
@@ -8560,6 +8625,7 @@ async function installCommand(opts = {}) {
8560
8625
  } catch {
8561
8626
  console.warn(" \u26A0 ingest endpoint unreachable \u2014 telemetry spool may not drain.");
8562
8627
  }
8628
+ await syncSkillFiles();
8563
8629
  } else {
8564
8630
  console.error(" \u2717 container did not become healthy within 60s");
8565
8631
  console.error(" Run `docker logs synkro-server` to debug.");
@@ -8574,7 +8640,7 @@ async function installCommand(opts = {}) {
8574
8640
  try {
8575
8641
  let mcpToken = "";
8576
8642
  try {
8577
- mcpToken = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8643
+ mcpToken = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8578
8644
  } catch {
8579
8645
  }
8580
8646
  if (mcpToken) {
@@ -8643,15 +8709,23 @@ function writeSynkroFileIfMissing(opts) {
8643
8709
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8644
8710
  if (!root) return;
8645
8711
  const fp = join8(root, ".synkro");
8646
- if (existsSync9(fp)) {
8712
+ if (existsSync10(fp)) {
8647
8713
  console.log(` .synkro: ${fp} (existing, respected)`);
8648
8714
  return;
8649
8715
  }
8650
8716
  let pool = "auto";
8651
- if (opts.hasClaudeCode && !opts.hasCursor) pool = "claude";
8652
- else if (opts.hasCursor && !opts.hasClaudeCode) pool = "cursor";
8717
+ const harness = [];
8718
+ if (opts.hasClaudeCode) {
8719
+ harness.push("claude-code");
8720
+ if (!opts.hasCursor) pool = "claude";
8721
+ }
8722
+ if (opts.hasCursor) {
8723
+ harness.push("cursor");
8724
+ if (!opts.hasClaudeCode) pool = "cursor";
8725
+ }
8653
8726
  const config = {
8654
8727
  version: 1,
8728
+ harness,
8655
8729
  grader: {
8656
8730
  pool,
8657
8731
  mode: opts.gradingMode === "byok" ? "byok" : "local"
@@ -8669,14 +8743,146 @@ function readSynkroFilePool2() {
8669
8743
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8670
8744
  if (!root) return "auto";
8671
8745
  const fp = join8(root, ".synkro");
8672
- if (!existsSync9(fp)) return "auto";
8673
- const parsed = JSON.parse(readFileSync8(fp, "utf-8"));
8746
+ if (!existsSync10(fp)) return "auto";
8747
+ const parsed = JSON.parse(readFileSync9(fp, "utf-8"));
8674
8748
  const pool = parsed?.grader?.pool;
8675
8749
  if (pool === "cursor" || pool === "claude") return pool;
8676
8750
  } catch {
8677
8751
  }
8678
8752
  return "auto";
8679
8753
  }
8754
+ function readFullSynkroFile() {
8755
+ try {
8756
+ const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8757
+ if (!root) return null;
8758
+ const fp = join8(root, ".synkro");
8759
+ if (!existsSync10(fp)) return null;
8760
+ const parsed = JSON.parse(readFileSync9(fp, "utf-8"));
8761
+ const valid = ["claude-code", "cursor"];
8762
+ const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
8763
+ return {
8764
+ harness: harness.length > 0 ? harness : ["claude-code", "cursor"],
8765
+ grader: {
8766
+ pool: ["auto", "claude", "cursor"].includes(parsed.grader?.pool) ? parsed.grader.pool : "auto",
8767
+ mode: ["local", "byok"].includes(parsed.grader?.mode) ? parsed.grader.mode : "local"
8768
+ },
8769
+ ruleset: parsed.ruleset || "default",
8770
+ skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && s.endsWith(".md")) : [],
8771
+ scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },
8772
+ _repoRoot: root
8773
+ };
8774
+ } catch {
8775
+ return null;
8776
+ }
8777
+ }
8778
+ function reconcileHarness() {
8779
+ const sf = readFullSynkroFile();
8780
+ if (!sf) {
8781
+ console.log("No .synkro file found in repo root \u2014 skipping harness reconciliation.");
8782
+ return null;
8783
+ }
8784
+ const wantCC = sf.harness.includes("claude-code");
8785
+ const wantCursor = sf.harness.includes("cursor");
8786
+ console.log(`.synkro: harness=[${sf.harness.join(", ")}] pool=${sf.grader.pool} mode=${sf.grader.mode}`);
8787
+ const scripts = writeHookScripts();
8788
+ console.log("Wrote hook scripts to ~/.synkro/hooks/");
8789
+ const ccSettings = join8(homedir8(), ".claude", "settings.json");
8790
+ if (wantCC) {
8791
+ installCCHooks(ccSettings, {
8792
+ bashJudgeScriptPath: scripts.bashScript,
8793
+ bashFollowupScriptPath: scripts.bashFollowupScript,
8794
+ editPrecheckScriptPath: scripts.editPrecheckScript,
8795
+ cwePrecheckScriptPath: scripts.cwePrecheckScript,
8796
+ cvePrecheckScriptPath: scripts.cvePrecheckScript,
8797
+ planJudgeScriptPath: scripts.planJudgeScript,
8798
+ agentJudgeScriptPath: scripts.agentJudgeScript,
8799
+ stopSummaryScriptPath: scripts.stopSummaryScript,
8800
+ sessionStartScriptPath: scripts.sessionStartScript,
8801
+ transcriptSyncScriptPath: scripts.transcriptSyncScript,
8802
+ userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
8803
+ installScanScriptPath: scripts.installScanScript
8804
+ });
8805
+ console.log(" \u2713 Claude Code hooks registered");
8806
+ try {
8807
+ const mcpJwt = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8808
+ if (mcpJwt) {
8809
+ installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt, local: true });
8810
+ console.log(" \u2713 Claude Code MCP registered");
8811
+ }
8812
+ } catch {
8813
+ }
8814
+ } else {
8815
+ if (uninstallCCHooks(ccSettings)) console.log(" \u2717 Claude Code hooks removed");
8816
+ if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
8817
+ }
8818
+ const cursorHooks = join8(homedir8(), ".cursor", "hooks.json");
8819
+ if (wantCursor) {
8820
+ installCursorHooks(cursorHooks, {
8821
+ bashJudgeScriptPath: scripts.cursorBashJudgeScript,
8822
+ editCaptureScriptPath: scripts.cursorEditCaptureScript,
8823
+ agentCaptureScriptPath: scripts.cursorAgentCaptureScript,
8824
+ bashFollowupScriptPath: scripts.bashFollowupScript,
8825
+ editPrecheckScriptPath: scripts.editPrecheckScript,
8826
+ cwePrecheckScriptPath: scripts.cwePrecheckScript,
8827
+ cvePrecheckScriptPath: scripts.cvePrecheckScript,
8828
+ planJudgeScriptPath: scripts.planJudgeScript,
8829
+ agentJudgeScriptPath: scripts.agentJudgeScript,
8830
+ stopSummaryScriptPath: scripts.stopSummaryScript,
8831
+ sessionStartScriptPath: scripts.sessionStartScript,
8832
+ userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
8833
+ transcriptSyncScriptPath: scripts.transcriptSyncScript,
8834
+ installScanScriptPath: scripts.installScanScript
8835
+ });
8836
+ console.log(" \u2713 Cursor hooks registered");
8837
+ try {
8838
+ installCursorMcpConfig({ gatewayUrl: "", bearerToken: "", local: true });
8839
+ console.log(" \u2713 Cursor MCP registered");
8840
+ } catch {
8841
+ }
8842
+ } else {
8843
+ if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
8844
+ if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
8845
+ }
8846
+ const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
8847
+ const providers = [];
8848
+ if (sf.grader.pool === "cursor") {
8849
+ providers.push("cursor");
8850
+ } else if (sf.grader.pool === "claude") {
8851
+ providers.push("claude_code");
8852
+ } else {
8853
+ if (wantCC) providers.push("claude_code");
8854
+ if (wantCursor) providers.push("cursor");
8855
+ }
8856
+ if (providers.length === 0) providers.push("claude_code");
8857
+ return splitWorkers(total, providers);
8858
+ }
8859
+ async function syncSkillFiles() {
8860
+ const sf = readFullSynkroFile();
8861
+ if (!sf || sf.skills.length === 0) return;
8862
+ const resolved = resolveSkillPaths(sf.skills, sf._repoRoot);
8863
+ if (resolved.length === 0) return;
8864
+ const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
8865
+ for (const fp of resolved) {
8866
+ const skill = parseSkillFile(fp);
8867
+ if (!skill || skill.rules.length === 0) continue;
8868
+ try {
8869
+ const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {
8870
+ method: "POST",
8871
+ headers: { "Content-Type": "application/json" },
8872
+ body: JSON.stringify(skill),
8873
+ signal: AbortSignal.timeout(1e4)
8874
+ });
8875
+ if (resp.ok) {
8876
+ const result = await resp.json();
8877
+ console.log(` \u2713 skill ${skill.source}: ${result.created} rules synced (${result.removed} removed)`);
8878
+ } else {
8879
+ console.warn(` \u26A0 skill ${skill.source}: sync failed (${resp.status})`);
8880
+ }
8881
+ } catch (e) {
8882
+ console.warn(` \u26A0 skill ${skill.source}: ${e.message}`);
8883
+ }
8884
+ }
8885
+ }
8680
8886
  function detectGitRepo2() {
8681
8887
  const run = (cmd2) => {
8682
8888
  try {
@@ -8696,7 +8902,7 @@ function getClaudeProjectsFolder() {
8696
8902
  const cwd = process.cwd();
8697
8903
  const sanitized = "-" + cwd.replace(/\//g, "-");
8698
8904
  const projectsDir = join8(homedir8(), ".claude", "projects", sanitized);
8699
- return existsSync9(projectsDir) ? projectsDir : null;
8905
+ return existsSync10(projectsDir) ? projectsDir : null;
8700
8906
  }
8701
8907
  function extractSessionInsights(projectsDir) {
8702
8908
  const insights = [];
@@ -8705,7 +8911,7 @@ function extractSessionInsights(projectsDir) {
8705
8911
  const sessionId = file.replace(".jsonl", "");
8706
8912
  const filePath = join8(projectsDir, file);
8707
8913
  try {
8708
- const content = readFileSync8(filePath, "utf-8");
8914
+ const content = readFileSync9(filePath, "utf-8");
8709
8915
  const lines = content.split("\n").filter(Boolean);
8710
8916
  for (let i = 0; i < lines.length; i++) {
8711
8917
  try {
@@ -8781,7 +8987,7 @@ function extractTextContent(content) {
8781
8987
  return "";
8782
8988
  }
8783
8989
  function parseTranscriptFile(filePath) {
8784
- const content = readFileSync8(filePath, "utf-8");
8990
+ const content = readFileSync9(filePath, "utf-8");
8785
8991
  const lines = content.split("\n").filter(Boolean);
8786
8992
  const messages = [];
8787
8993
  for (let i = 0; i < lines.length; i++) {
@@ -8851,7 +9057,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
8851
9057
  process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
8852
9058
  }
8853
9059
  try {
8854
- const content = readFileSync8(join8(projectsDir, file), "utf-8");
9060
+ const content = readFileSync9(join8(projectsDir, file), "utf-8");
8855
9061
  const lineCount = content.split("\n").filter(Boolean).length;
8856
9062
  writeFileSync7(join8(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
8857
9063
  } catch {
@@ -8905,7 +9111,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
8905
9111
  const sessionId = file.replace(".jsonl", "");
8906
9112
  const filePath = join8(projectsDir, file);
8907
9113
  try {
8908
- const content = readFileSync8(filePath, "utf-8");
9114
+ const content = readFileSync9(filePath, "utf-8");
8909
9115
  const lineCount = content.split("\n").filter(Boolean).length;
8910
9116
  writeFileSync7(join8(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
8911
9117
  } catch {
@@ -8922,6 +9128,7 @@ var init_install = __esm({
8922
9128
  init_ccHookConfig();
8923
9129
  init_cursorHookConfig();
8924
9130
  init_mcpConfig();
9131
+ init_skillParser();
8925
9132
  init_hookScripts();
8926
9133
  init_hookScriptsTs();
8927
9134
  init_stub();
@@ -8984,7 +9191,7 @@ rl.on('line', async (line) => {
8984
9191
  });
8985
9192
 
8986
9193
  // cli/local-cc/install.ts
8987
- import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync9, chmodSync as chmodSync3, copyFileSync as copyFileSync2, renameSync as renameSync4, unlinkSync as unlinkSync4, openSync, fsyncSync, closeSync } from "fs";
9194
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync10, chmodSync as chmodSync3, copyFileSync as copyFileSync2, renameSync as renameSync4, unlinkSync as unlinkSync4, openSync, fsyncSync, closeSync } from "fs";
8988
9195
  import { join as join9 } from "path";
8989
9196
  import { homedir as homedir9 } from "os";
8990
9197
  import { spawnSync as spawnSync4 } from "child_process";
@@ -9020,10 +9227,10 @@ function runBunInstall() {
9020
9227
  }
9021
9228
  }
9022
9229
  function safelyMutateClaudeJson(mutator) {
9023
- if (!existsSync10(CLAUDE_JSON_PATH)) {
9230
+ if (!existsSync11(CLAUDE_JSON_PATH)) {
9024
9231
  return;
9025
9232
  }
9026
- const originalText = readFileSync9(CLAUDE_JSON_PATH, "utf-8");
9233
+ const originalText = readFileSync10(CLAUDE_JSON_PATH, "utf-8");
9027
9234
  let parsed;
9028
9235
  try {
9029
9236
  parsed = JSON.parse(originalText);
@@ -9474,7 +9681,7 @@ var disconnect_exports = {};
9474
9681
  __export(disconnect_exports, {
9475
9682
  disconnectCommand: () => disconnectCommand
9476
9683
  });
9477
- import { existsSync as existsSync11, rmSync, readdirSync as readdirSync4 } from "fs";
9684
+ import { existsSync as existsSync12, rmSync, readdirSync as readdirSync4 } from "fs";
9478
9685
  import { homedir as homedir10 } from "os";
9479
9686
  import { join as join10 } from "path";
9480
9687
  import { spawnSync as spawnSync5 } from "child_process";
@@ -9515,10 +9722,10 @@ function confirmPurge() {
9515
9722
  return Promise.resolve(false);
9516
9723
  }
9517
9724
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
9518
- return new Promise((resolve3) => {
9725
+ return new Promise((resolve4) => {
9519
9726
  rl.question(" Type 'yes' to wipe everything (anything else cancels): ", (answer) => {
9520
9727
  rl.close();
9521
- resolve3(answer.trim().toLowerCase() === "yes");
9728
+ resolve4(answer.trim().toLowerCase() === "yes");
9522
9729
  });
9523
9730
  });
9524
9731
  }
@@ -9550,7 +9757,7 @@ async function disconnectCommand(args2 = []) {
9550
9757
  const cursorMcpRemoved = uninstallCursorMcpConfig();
9551
9758
  console.log(`${cursorMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Cursor): ${cursorMcpRemoved ? "removed from ~/.cursor/mcp.json" : "no entry found"}`);
9552
9759
  }
9553
- if (existsSync11(SYNKRO_DIR5)) {
9760
+ if (existsSync12(SYNKRO_DIR5)) {
9554
9761
  if (purge) {
9555
9762
  rmSync(SYNKRO_DIR5, { recursive: true, force: true });
9556
9763
  console.log(`\u2713 wiped ${SYNKRO_DIR5} entirely \u2014 including all scan data and backups`);
@@ -9593,7 +9800,7 @@ var init_disconnect = __esm({
9593
9800
  });
9594
9801
 
9595
9802
  // cli/local-cc/turnLog.ts
9596
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync10, readSync, closeSync as closeSync2, statSync as statSync2, watchFile, unwatchFile } from "fs";
9803
+ import { appendFileSync, existsSync as existsSync13, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync11, readSync, closeSync as closeSync2, statSync as statSync2, watchFile, unwatchFile } from "fs";
9597
9804
  import { dirname as dirname6, join as join11 } from "path";
9598
9805
  import { homedir as homedir11 } from "os";
9599
9806
  function truncate(s, max = PREVIEW_MAX) {
@@ -9631,11 +9838,11 @@ function appendTurn(args2) {
9631
9838
  }
9632
9839
  }
9633
9840
  function readRecentTurns(n = 20) {
9634
- if (!existsSync12(TURN_LOG_PATH)) return [];
9841
+ if (!existsSync13(TURN_LOG_PATH)) return [];
9635
9842
  try {
9636
9843
  const size = statSync2(TURN_LOG_PATH).size;
9637
9844
  if (size === 0) return [];
9638
- const text = readFileSync10(TURN_LOG_PATH, "utf-8");
9845
+ const text = readFileSync11(TURN_LOG_PATH, "utf-8");
9639
9846
  const lines = text.split("\n").filter(Boolean);
9640
9847
  const lastN = lines.slice(-n).reverse();
9641
9848
  return lastN.map((line) => {
@@ -9652,7 +9859,7 @@ function readRecentTurns(n = 20) {
9652
9859
  function followTurns(onEntry) {
9653
9860
  try {
9654
9861
  mkdirSync10(dirname6(TURN_LOG_PATH), { recursive: true });
9655
- if (!existsSync12(TURN_LOG_PATH)) {
9862
+ if (!existsSync13(TURN_LOG_PATH)) {
9656
9863
  appendFileSync(TURN_LOG_PATH, "", "utf-8");
9657
9864
  }
9658
9865
  } catch {
@@ -9728,7 +9935,7 @@ async function submitToChannel(role, payload, opts = {}) {
9728
9935
  const port = opts.port ?? CHANNEL_PORT;
9729
9936
  const startedAt = Date.now();
9730
9937
  try {
9731
- const result = await new Promise((resolve3, reject) => {
9938
+ const result = await new Promise((resolve4, reject) => {
9732
9939
  const req = httpRequest({
9733
9940
  host: CHANNEL_HOST,
9734
9941
  port,
@@ -9754,7 +9961,7 @@ async function submitToChannel(role, payload, opts = {}) {
9754
9961
  reject(new LocalCCError(parsed.error));
9755
9962
  return;
9756
9963
  }
9757
- resolve3(String(parsed.result ?? ""));
9964
+ resolve4(String(parsed.result ?? ""));
9758
9965
  } catch (err) {
9759
9966
  reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
9760
9967
  }
@@ -9780,14 +9987,14 @@ async function submitToChannel(role, payload, opts = {}) {
9780
9987
  }
9781
9988
  }
9782
9989
  function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500) {
9783
- return new Promise((resolve3) => {
9990
+ return new Promise((resolve4) => {
9784
9991
  const sock = connect(port, CHANNEL_HOST);
9785
9992
  const done = (ok) => {
9786
9993
  try {
9787
9994
  sock.destroy();
9788
9995
  } catch {
9789
9996
  }
9790
- resolve3(ok);
9997
+ resolve4(ok);
9791
9998
  };
9792
9999
  sock.once("connect", () => done(true));
9793
10000
  sock.once("error", () => done(false));
@@ -9819,10 +10026,10 @@ __export(grade_exports, {
9819
10026
  gradeCommand: () => gradeCommand
9820
10027
  });
9821
10028
  async function readStdin() {
9822
- return new Promise((resolve3, reject) => {
10029
+ return new Promise((resolve4, reject) => {
9823
10030
  const chunks = [];
9824
10031
  process.stdin.on("data", (c) => chunks.push(c));
9825
- process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
10032
+ process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
9826
10033
  process.stdin.on("error", reject);
9827
10034
  });
9828
10035
  }
@@ -9981,14 +10188,14 @@ function ensureRunning(opts = {}) {
9981
10188
  return startTask(opts);
9982
10189
  }
9983
10190
  function probePort(host, port, timeoutMs = 500) {
9984
- return new Promise((resolve3) => {
10191
+ return new Promise((resolve4) => {
9985
10192
  const sock = connect2(port, host);
9986
10193
  const done = (ok) => {
9987
10194
  try {
9988
10195
  sock.destroy();
9989
10196
  } catch {
9990
10197
  }
9991
- resolve3(ok);
10198
+ resolve4(ok);
9992
10199
  };
9993
10200
  sock.once("connect", () => done(true));
9994
10201
  sock.once("error", () => done(false));
@@ -10081,13 +10288,13 @@ var init_pueue = __esm({
10081
10288
  });
10082
10289
 
10083
10290
  // cli/local-cc/settings.ts
10084
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
10291
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
10085
10292
  import { homedir as homedir13 } from "os";
10086
10293
  import { join as join13 } from "path";
10087
10294
  function isLocalCCEnabled() {
10088
- if (!existsSync13(CONFIG_PATH3)) return false;
10295
+ if (!existsSync14(CONFIG_PATH3)) return false;
10089
10296
  try {
10090
- const content = readFileSync11(CONFIG_PATH3, "utf-8");
10297
+ const content = readFileSync12(CONFIG_PATH3, "utf-8");
10091
10298
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
10092
10299
  return match?.[1] === "yes";
10093
10300
  } catch {
@@ -10111,7 +10318,7 @@ import { spawnSync as spawnSync7 } from "child_process";
10111
10318
  import { homedir as homedir14 } from "os";
10112
10319
  import { join as join14 } from "path";
10113
10320
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
10114
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
10321
+ import { existsSync as existsSync15, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
10115
10322
  function deploymentMode() {
10116
10323
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
10117
10324
  if (env === "docker") return "docker";
@@ -10217,15 +10424,15 @@ TROUBLESHOOTING
10217
10424
  `);
10218
10425
  }
10219
10426
  function readGatewayUrl() {
10220
- if (existsSync14(CONFIG_PATH4)) {
10221
- const m = readFileSync12(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
10427
+ if (existsSync15(CONFIG_PATH4)) {
10428
+ const m = readFileSync13(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
10222
10429
  if (m) return m[1];
10223
10430
  }
10224
10431
  return "https://api.synkro.sh";
10225
10432
  }
10226
10433
  function updateLocalInferenceFlag(enabled) {
10227
- if (!existsSync14(CONFIG_PATH4)) return;
10228
- let content = readFileSync12(CONFIG_PATH4, "utf-8");
10434
+ if (!existsSync15(CONFIG_PATH4)) return;
10435
+ let content = readFileSync13(CONFIG_PATH4, "utf-8");
10229
10436
  const flag = enabled ? "yes" : "no";
10230
10437
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
10231
10438
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -10400,11 +10607,24 @@ function cmdStop() {
10400
10607
  }
10401
10608
  async function cmdRestart(rest = []) {
10402
10609
  if (inDockerMode()) {
10403
- const { claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest);
10610
+ const { explicit } = resolveWorkerConfig(rest);
10611
+ let claudeWorkers;
10612
+ let cursorWorkers;
10613
+ if (explicit) {
10614
+ ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));
10615
+ } else {
10616
+ const reconciled = reconcileHarness();
10617
+ if (reconciled) {
10618
+ ({ claudeWorkers, cursorWorkers } = reconciled);
10619
+ } else {
10620
+ ({ claudeWorkers, cursorWorkers } = resolveWorkerConfig(rest));
10621
+ }
10622
+ }
10404
10623
  console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor, pulling latest image)...`);
10405
10624
  await dockerUpdate({ claudeWorkers, cursorWorkers });
10406
10625
  const ready = await waitForContainerReady(6e4);
10407
10626
  console.log(ready ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
10627
+ if (ready) await syncSkillFiles();
10408
10628
  return;
10409
10629
  }
10410
10630
  stopTask(CHANNEL_PRIMARY);
@@ -10519,7 +10739,7 @@ function cmdLogs(rest) {
10519
10739
  if (!raw) console.log(" " + colorize("(use --raw / -r to see full payloads, --live / -f to follow)", 90));
10520
10740
  return;
10521
10741
  }
10522
- return new Promise((resolve3) => {
10742
+ return new Promise((resolve4) => {
10523
10743
  console.log(" " + colorize("\u2014 following new turns (Ctrl-C to exit) \u2014", 90));
10524
10744
  const stop = followTurns((t) => {
10525
10745
  console.log(" " + formatTurn(t, raw));
@@ -10527,7 +10747,7 @@ function cmdLogs(rest) {
10527
10747
  const onSigint = () => {
10528
10748
  stop();
10529
10749
  process.removeListener("SIGINT", onSigint);
10530
- resolve3();
10750
+ resolve4();
10531
10751
  };
10532
10752
  process.on("SIGINT", onSigint);
10533
10753
  });
@@ -10644,6 +10864,7 @@ var init_localCc = __esm({
10644
10864
  init_settings();
10645
10865
  init_macKeychain();
10646
10866
  init_dockerInstall();
10867
+ init_install();
10647
10868
  init_client();
10648
10869
  init_stub();
10649
10870
  SYNKRO_CONFIG_PATH = join14(homedir14(), ".synkro", "config.env");
@@ -10754,13 +10975,13 @@ var config_exports = {};
10754
10975
  __export(config_exports, {
10755
10976
  configCommand: () => configCommand
10756
10977
  });
10757
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync15 } from "fs";
10978
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync16 } from "fs";
10758
10979
  import { join as join15 } from "path";
10759
10980
  import { homedir as homedir15 } from "os";
10760
10981
  function readConfigEnv() {
10761
- if (!existsSync15(CONFIG_PATH5)) return {};
10982
+ if (!existsSync16(CONFIG_PATH5)) return {};
10762
10983
  const out = {};
10763
- for (const line of readFileSync13(CONFIG_PATH5, "utf-8").split("\n")) {
10984
+ for (const line of readFileSync14(CONFIG_PATH5, "utf-8").split("\n")) {
10764
10985
  const t = line.trim();
10765
10986
  if (!t || t.startsWith("#")) continue;
10766
10987
  const eq = t.indexOf("=");
@@ -10769,11 +10990,11 @@ function readConfigEnv() {
10769
10990
  return out;
10770
10991
  }
10771
10992
  function updateConfigValue(key, value) {
10772
- if (!existsSync15(CONFIG_PATH5)) {
10993
+ if (!existsSync16(CONFIG_PATH5)) {
10773
10994
  console.error("No config found. Run `synkro install` first.");
10774
10995
  process.exit(1);
10775
10996
  }
10776
- const lines = readFileSync13(CONFIG_PATH5, "utf-8").split("\n");
10997
+ const lines = readFileSync14(CONFIG_PATH5, "utf-8").split("\n");
10777
10998
  const pattern = new RegExp(`^${key}=`);
10778
10999
  let found = false;
10779
11000
  const updated = lines.map((line) => {
@@ -10900,14 +11121,14 @@ var init_config = __esm({
10900
11121
  });
10901
11122
 
10902
11123
  // cli/bootstrap.js
10903
- import { readFileSync as readFileSync14, existsSync as existsSync16 } from "fs";
10904
- import { resolve as resolve2 } from "path";
11124
+ import { readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
11125
+ import { resolve as resolve3 } from "path";
10905
11126
  var envCandidates = [
10906
- resolve2(process.env.HOME ?? "", ".synkro", "config.env")
11127
+ resolve3(process.env.HOME ?? "", ".synkro", "config.env")
10907
11128
  ];
10908
11129
  for (const envPath of envCandidates) {
10909
- if (!existsSync16(envPath)) continue;
10910
- const envContent = readFileSync14(envPath, "utf-8");
11130
+ if (!existsSync17(envPath)) continue;
11131
+ const envContent = readFileSync15(envPath, "utf-8");
10911
11132
  for (const line of envContent.split("\n")) {
10912
11133
  const trimmed = line.trim();
10913
11134
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -10922,7 +11143,7 @@ var args = process.argv.slice(2);
10922
11143
  var cmd = args[0] || "";
10923
11144
  var subArgs = args.slice(1);
10924
11145
  function printVersion() {
10925
- console.log("1.6.32");
11146
+ console.log("1.6.34");
10926
11147
  }
10927
11148
  function printHelp2() {
10928
11149
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents