@synkro-sh/cli 1.6.33 → 1.6.35

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({
@@ -1147,6 +1199,7 @@ export interface SynkroFileConfig {
1147
1199
  harness: ('claude-code' | 'cursor')[];
1148
1200
  grader: { pool: 'auto' | 'claude' | 'cursor'; mode?: string };
1149
1201
  ruleset: string;
1202
+ skills: string[];
1150
1203
  scanning: { cwe: boolean; cve: boolean };
1151
1204
  }
1152
1205
 
@@ -1155,9 +1208,58 @@ const SYNKRO_FILE_DEFAULTS: SynkroFileConfig = {
1155
1208
  harness: ['claude-code', 'cursor'],
1156
1209
  grader: { pool: 'auto' },
1157
1210
  ruleset: 'default',
1211
+ skills: [],
1158
1212
  scanning: { cwe: true, cve: true },
1159
1213
  };
1160
1214
 
1215
+ function parseSynkroYaml(raw: string): Record<string, any> {
1216
+ const result: Record<string, any> = {};
1217
+ const lines = raw.split('\\n');
1218
+ let currentKey = '';
1219
+ let currentObj: Record<string, any> | null = null;
1220
+ let currentArr: string[] | null = null;
1221
+
1222
+ for (const line of lines) {
1223
+ if (!line.trim() || line.trim().startsWith('#')) continue;
1224
+
1225
+ if (line.match(/^\\S/) && line.includes(':')) {
1226
+ if (currentObj && currentKey) result[currentKey] = currentObj;
1227
+ if (currentArr && currentKey) result[currentKey] = currentArr;
1228
+ currentObj = null;
1229
+ currentArr = null;
1230
+
1231
+ const colonIdx = line.indexOf(':');
1232
+ const key = line.slice(0, colonIdx).trim();
1233
+ const val = line.slice(colonIdx + 1).trim();
1234
+ currentKey = key;
1235
+
1236
+ if (val) {
1237
+ if (val === '[]') result[key] = [];
1238
+ else if (val === 'true') result[key] = true;
1239
+ else if (val === 'false') result[key] = false;
1240
+ else if (/^\\d+$/.test(val)) result[key] = parseInt(val, 10);
1241
+ else result[key] = val;
1242
+ currentKey = '';
1243
+ }
1244
+ } else if (line.match(/^ - /)) {
1245
+ if (!currentArr) currentArr = [];
1246
+ currentArr.push(line.replace(/^ - /, '').trim());
1247
+ } else if (line.match(/^ \\S/) && line.includes(':')) {
1248
+ if (!currentObj) currentObj = {};
1249
+ const colonIdx = line.indexOf(':');
1250
+ const k = line.slice(0, colonIdx).trim();
1251
+ const v = line.slice(colonIdx + 1).trim();
1252
+ if (v === 'true') currentObj[k] = true;
1253
+ else if (v === 'false') currentObj[k] = false;
1254
+ else if (/^\\d+$/.test(v)) currentObj[k] = parseInt(v, 10);
1255
+ else currentObj[k] = v;
1256
+ }
1257
+ }
1258
+ if (currentObj && currentKey) result[currentKey] = currentObj;
1259
+ if (currentArr && currentKey) result[currentKey] = currentArr;
1260
+ return result;
1261
+ }
1262
+
1161
1263
  let _synkroFileCache: SynkroFileConfig | undefined;
1162
1264
 
1163
1265
  export function loadSynkroFile(cwd?: string): SynkroFileConfig {
@@ -1167,7 +1269,8 @@ export function loadSynkroFile(cwd?: string): SynkroFileConfig {
1167
1269
  const fp = root + '/.synkro';
1168
1270
  try {
1169
1271
  if (!existsSync(fp)) { _synkroFileCache = SYNKRO_FILE_DEFAULTS; return _synkroFileCache; }
1170
- const parsed = JSON.parse(readFileSync(fp, 'utf-8'));
1272
+ const raw = readFileSync(fp, 'utf-8');
1273
+ const parsed = raw.trimStart().startsWith('{') ? JSON.parse(raw) : parseSynkroYaml(raw);
1171
1274
  const validHarness = ['claude-code', 'cursor'] as const;
1172
1275
  const harness = Array.isArray(parsed.harness)
1173
1276
  ? parsed.harness.filter((h: string) => validHarness.includes(h as any))
@@ -1180,6 +1283,7 @@ export function loadSynkroFile(cwd?: string): SynkroFileConfig {
1180
1283
  mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : undefined,
1181
1284
  },
1182
1285
  ruleset: typeof parsed.ruleset === 'string' ? parsed.ruleset : 'default',
1286
+ skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s: unknown) => typeof s === 'string') : [],
1183
1287
  scanning: {
1184
1288
  cwe: parsed.scanning?.cwe !== false,
1185
1289
  cve: parsed.scanning?.cve !== false,
@@ -5946,7 +6050,7 @@ __export(stub_exports, {
5946
6050
  saveCredentials: () => saveCredentials
5947
6051
  });
5948
6052
  import { createServer } from "http";
5949
- import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
6053
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
5950
6054
  import { homedir as homedir4, platform } from "os";
5951
6055
  import { join as join3, dirname as dirname4 } from "path";
5952
6056
  import { execFile } from "child_process";
@@ -5977,17 +6081,17 @@ function openBrowser(url) {
5977
6081
  }
5978
6082
  function saveCredentials(data) {
5979
6083
  const dir = dirname4(AUTH_FILE);
5980
- if (!existsSync4(dir)) {
6084
+ if (!existsSync5(dir)) {
5981
6085
  mkdirSync4(dir, { recursive: true, mode: 448 });
5982
6086
  }
5983
6087
  writeFileSync4(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
5984
6088
  }
5985
6089
  function loadCredentials() {
5986
- if (!existsSync4(AUTH_FILE)) {
6090
+ if (!existsSync5(AUTH_FILE)) {
5987
6091
  return null;
5988
6092
  }
5989
6093
  try {
5990
- const content = readFileSync4(AUTH_FILE, "utf8");
6094
+ const content = readFileSync5(AUTH_FILE, "utf8");
5991
6095
  return JSON.parse(content);
5992
6096
  } catch (error) {
5993
6097
  return null;
@@ -6000,7 +6104,7 @@ function createCallbackServer() {
6000
6104
  "Access-Control-Allow-Headers": "Content-Type",
6001
6105
  "Vary": "Origin"
6002
6106
  };
6003
- return new Promise((resolve3, reject) => {
6107
+ return new Promise((resolve4, reject) => {
6004
6108
  const server = createServer((req, res) => {
6005
6109
  if (req.method === "OPTIONS") {
6006
6110
  const origin = req.headers.origin;
@@ -6089,7 +6193,7 @@ function createCallbackServer() {
6089
6193
  res.end(JSON.stringify({ ok: true }));
6090
6194
  setTimeout(() => {
6091
6195
  server.close();
6092
- resolve3(authData);
6196
+ resolve4(authData);
6093
6197
  }, 200);
6094
6198
  });
6095
6199
  req.on("error", (e) => {
@@ -6242,7 +6346,7 @@ async function ensureValidToken() {
6242
6346
  return true;
6243
6347
  }
6244
6348
  function clearCredentials() {
6245
- if (existsSync4(AUTH_FILE)) {
6349
+ if (existsSync5(AUTH_FILE)) {
6246
6350
  unlinkSync2(AUTH_FILE);
6247
6351
  }
6248
6352
  }
@@ -6422,7 +6526,7 @@ jobs:
6422
6526
  });
6423
6527
 
6424
6528
  // cli/installer/githubSetup.ts
6425
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
6529
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
6426
6530
  import { execSync as execSync2 } from "child_process";
6427
6531
  import { join as join4 } from "path";
6428
6532
  function ghSecretSet(token, owner, repo, name, value) {
@@ -6479,7 +6583,7 @@ function writeWorkflowFile(repoRootPath) {
6479
6583
  function findGitRoot(startCwd) {
6480
6584
  let cur = startCwd;
6481
6585
  while (cur && cur !== "/") {
6482
- if (existsSync5(join4(cur, ".git"))) return cur;
6586
+ if (existsSync6(join4(cur, ".git"))) return cur;
6483
6587
  const parent = join4(cur, "..");
6484
6588
  if (parent === cur) break;
6485
6589
  cur = parent;
@@ -6545,10 +6649,10 @@ function detectSubdirRepos() {
6545
6649
  }
6546
6650
  }
6547
6651
  function ask(rl, question) {
6548
- return new Promise((resolve3) => rl.question(question, resolve3));
6652
+ return new Promise((resolve4) => rl.question(question, resolve4));
6549
6653
  }
6550
6654
  function waitForGithubToken() {
6551
- return new Promise((resolve3, reject) => {
6655
+ return new Promise((resolve4, reject) => {
6552
6656
  const server = createServer2((req, res) => {
6553
6657
  if (req.method === "OPTIONS") {
6554
6658
  res.writeHead(204, {
@@ -6585,7 +6689,7 @@ function waitForGithubToken() {
6585
6689
  });
6586
6690
  res.end(JSON.stringify({ ok: true }));
6587
6691
  setTimeout(() => server.close(), 200);
6588
- resolve3(parsed.github_token);
6692
+ resolve4(parsed.github_token);
6589
6693
  } catch {
6590
6694
  res.writeHead(400, { "Content-Type": "application/json" });
6591
6695
  res.end(JSON.stringify({ error: "invalid json" }));
@@ -6845,7 +6949,7 @@ __export(macKeychain_exports, {
6845
6949
  writeCursorApiKey: () => writeCursorApiKey,
6846
6950
  writeRefreshAgent: () => writeRefreshAgent
6847
6951
  });
6848
- import { existsSync as existsSync6, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, chmodSync, readFileSync as readFileSync5, statSync } from "fs";
6952
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, chmodSync, readFileSync as readFileSync6, statSync } from "fs";
6849
6953
  import { homedir as homedir5, platform as platform2 } from "os";
6850
6954
  import { join as join5 } from "path";
6851
6955
  import { spawnSync } from "child_process";
@@ -6873,7 +6977,7 @@ function exportKeychainCreds() {
6873
6977
  }
6874
6978
  function cursorApiKeyConfigured() {
6875
6979
  try {
6876
- return existsSync6(CURSOR_API_KEY_FILE) && readFileSync5(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
6980
+ return existsSync7(CURSOR_API_KEY_FILE) && readFileSync6(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
6877
6981
  } catch {
6878
6982
  return false;
6879
6983
  }
@@ -6889,7 +6993,7 @@ function writeCursorApiKey(key) {
6889
6993
  async function validateCursorApiKey() {
6890
6994
  let key;
6891
6995
  try {
6892
- key = readFileSync5(CURSOR_API_KEY_FILE, "utf-8").trim();
6996
+ key = readFileSync6(CURSOR_API_KEY_FILE, "utf-8").trim();
6893
6997
  } catch {
6894
6998
  return null;
6895
6999
  }
@@ -6908,7 +7012,7 @@ async function validateCursorApiKey() {
6908
7012
  }
6909
7013
  }
6910
7014
  function credsAreStale() {
6911
- if (!existsSync6(CLAUDE_CREDS_FILE)) return true;
7015
+ if (!existsSync7(CLAUDE_CREDS_FILE)) return true;
6912
7016
  try {
6913
7017
  const ageMs = Date.now() - statSync(CLAUDE_CREDS_FILE).mtimeMs;
6914
7018
  return ageMs > REFRESH_INTERVAL_SECONDS * 1e3;
@@ -6971,7 +7075,7 @@ function uninstallRefreshAgent() {
6971
7075
  timeout: 5e3
6972
7076
  });
6973
7077
  try {
6974
- if (existsSync6(LAUNCHD_PLIST)) {
7078
+ if (existsSync7(LAUNCHD_PLIST)) {
6975
7079
  __require("fs").unlinkSync(LAUNCHD_PLIST);
6976
7080
  }
6977
7081
  } catch {
@@ -6983,7 +7087,7 @@ function refreshCreds() {
6983
7087
  }
6984
7088
  function readExportedCreds() {
6985
7089
  try {
6986
- return readFileSync5(CLAUDE_CREDS_FILE, "utf-8");
7090
+ return readFileSync6(CLAUDE_CREDS_FILE, "utf-8");
6987
7091
  } catch {
6988
7092
  return null;
6989
7093
  }
@@ -7032,7 +7136,7 @@ __export(dockerInstall_exports, {
7032
7136
  splitWorkers: () => splitWorkers,
7033
7137
  waitForContainerReady: () => waitForContainerReady
7034
7138
  });
7035
- import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
7139
+ import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "fs";
7036
7140
  import { homedir as homedir6 } from "os";
7037
7141
  import { join as join6 } from "path";
7038
7142
  import { execSync as execSync4, spawnSync as spawnSync2 } from "child_process";
@@ -7053,13 +7157,57 @@ function normalizeProvider(p) {
7053
7157
  if (v === "cursor") return "cursor";
7054
7158
  return null;
7055
7159
  }
7160
+ function parseSynkroYaml(raw) {
7161
+ const result = {};
7162
+ const lines = raw.split("\n");
7163
+ let currentKey = "";
7164
+ let currentObj = null;
7165
+ let currentArr = null;
7166
+ for (const line of lines) {
7167
+ if (!line.trim() || line.trim().startsWith("#")) continue;
7168
+ if (/^\S/.test(line) && line.includes(":")) {
7169
+ if (currentObj && currentKey) result[currentKey] = currentObj;
7170
+ if (currentArr && currentKey) result[currentKey] = currentArr;
7171
+ currentObj = null;
7172
+ currentArr = null;
7173
+ const ci = line.indexOf(":");
7174
+ const key = line.slice(0, ci).trim();
7175
+ const val = line.slice(ci + 1).trim();
7176
+ currentKey = key;
7177
+ if (val) {
7178
+ if (val === "[]") result[key] = [];
7179
+ else if (val === "true") result[key] = true;
7180
+ else if (val === "false") result[key] = false;
7181
+ else if (/^\d+$/.test(val)) result[key] = parseInt(val, 10);
7182
+ else result[key] = val;
7183
+ currentKey = "";
7184
+ }
7185
+ } else if (/^ - /.test(line)) {
7186
+ if (!currentArr) currentArr = [];
7187
+ currentArr.push(line.replace(/^ - /, "").trim());
7188
+ } else if (/^ \S/.test(line) && line.includes(":")) {
7189
+ if (!currentObj) currentObj = {};
7190
+ const ci = line.indexOf(":");
7191
+ const k = line.slice(0, ci).trim();
7192
+ const v = line.slice(ci + 1).trim();
7193
+ if (v === "true") currentObj[k] = true;
7194
+ else if (v === "false") currentObj[k] = false;
7195
+ else if (/^\d+$/.test(v)) currentObj[k] = parseInt(v, 10);
7196
+ else currentObj[k] = v;
7197
+ }
7198
+ }
7199
+ if (currentObj && currentKey) result[currentKey] = currentObj;
7200
+ if (currentArr && currentKey) result[currentKey] = currentArr;
7201
+ return result;
7202
+ }
7056
7203
  function readSynkroFilePool() {
7057
7204
  try {
7058
7205
  const root = execSync4("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
7059
7206
  if (!root) return "auto";
7060
7207
  const fp = join6(root, ".synkro");
7061
- if (!existsSync7(fp)) return "auto";
7062
- const parsed = JSON.parse(readFileSync6(fp, "utf-8"));
7208
+ if (!existsSync8(fp)) return "auto";
7209
+ const raw = readFileSync7(fp, "utf-8");
7210
+ const parsed = raw.trimStart().startsWith("{") ? JSON.parse(raw) : parseSynkroYaml(raw);
7063
7211
  const pool = parsed?.grader?.pool;
7064
7212
  if (pool === "cursor" || pool === "claude") return pool;
7065
7213
  } catch {
@@ -7186,10 +7334,10 @@ async function dockerInstall(opts = {}) {
7186
7334
  mkdirSync7(BACKUP_DIR, { recursive: true });
7187
7335
  mkdirSync7(CLAUDE_HOST_STATE_DIR, { recursive: true });
7188
7336
  const hostClaudeJson = join6(homedir6(), ".claude.json");
7189
- if (existsSync7(hostClaudeJson)) {
7337
+ if (existsSync8(hostClaudeJson)) {
7190
7338
  copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);
7191
7339
  }
7192
- if (!existsSync7(MCP_JWT_PATH)) {
7340
+ if (!existsSync8(MCP_JWT_PATH)) {
7193
7341
  throw new DockerInstallError(
7194
7342
  `MCP JWT missing at ${MCP_JWT_PATH}. The installer should mint this before calling dockerInstall.`
7195
7343
  );
@@ -7424,7 +7572,7 @@ async function dockerSafeStart() {
7424
7572
  return { ok: false, pgdataState: "no_container", error: "No synkro-server container found. Run `synkro install` first." };
7425
7573
  }
7426
7574
  const pgCheck = checkPgdata();
7427
- if (existsSync7(PGDATA_PATH) && readdirSync2(PGDATA_PATH).length > 0) {
7575
+ if (existsSync8(PGDATA_PATH) && readdirSync2(PGDATA_PATH).length > 0) {
7428
7576
  if (pgCheck.healthy) {
7429
7577
  console.log(` pgdata: existing data found \u2014 ${pgCheck.details}`);
7430
7578
  } else {
@@ -7464,7 +7612,7 @@ async function dockerSafeRestart() {
7464
7612
  return { ok: startResult.ok, stop: stopResult, start: startResult };
7465
7613
  }
7466
7614
  function checkPgdata() {
7467
- if (!existsSync7(PGDATA_PATH)) return { healthy: false, details: "pgdata directory does not exist" };
7615
+ if (!existsSync8(PGDATA_PATH)) return { healthy: false, details: "pgdata directory does not exist" };
7468
7616
  const entries = readdirSync2(PGDATA_PATH);
7469
7617
  if (entries.length === 0) return { healthy: true, details: "empty (fresh start)" };
7470
7618
  const hasPidFile = entries.includes("postmaster.pid");
@@ -7513,14 +7661,14 @@ __export(setupGithub_exports, {
7513
7661
  import { createInterface as createInterface2 } from "readline/promises";
7514
7662
  import { stdin as input, stdout as output } from "process";
7515
7663
  import { execSync as execSync5, spawn as nodeSpawn } from "child_process";
7516
- import { existsSync as existsSync8, readFileSync as readFileSync7, unlinkSync as unlinkSync3 } from "fs";
7664
+ import { existsSync as existsSync9, readFileSync as readFileSync8, unlinkSync as unlinkSync3 } from "fs";
7517
7665
  import { homedir as homedir7, platform as platform3 } from "os";
7518
7666
  import { join as join7 } from "path";
7519
7667
  import { execFile as execFile2 } from "child_process";
7520
7668
  function readConfig() {
7521
- if (!existsSync8(CONFIG_PATH)) return {};
7669
+ if (!existsSync9(CONFIG_PATH)) return {};
7522
7670
  const out = {};
7523
- for (const line of readFileSync7(CONFIG_PATH, "utf-8").split("\n")) {
7671
+ for (const line of readFileSync8(CONFIG_PATH, "utf-8").split("\n")) {
7524
7672
  const t = line.trim();
7525
7673
  if (!t || t.startsWith("#")) continue;
7526
7674
  const eq = t.indexOf("=");
@@ -7533,7 +7681,7 @@ async function prompt(rl, q, opts = {}) {
7533
7681
  process.stdout.write(q);
7534
7682
  const wasRaw = process.stdin.isRaw;
7535
7683
  if (process.stdin.setRawMode) process.stdin.setRawMode(true);
7536
- return await new Promise((resolve3) => {
7684
+ return await new Promise((resolve4) => {
7537
7685
  let chunk = "";
7538
7686
  const onData = (data) => {
7539
7687
  const s = data.toString("utf-8");
@@ -7541,7 +7689,7 @@ async function prompt(rl, q, opts = {}) {
7541
7689
  process.stdin.removeListener("data", onData);
7542
7690
  if (process.stdin.setRawMode) process.stdin.setRawMode(wasRaw ?? false);
7543
7691
  process.stdout.write("\n");
7544
- resolve3(chunk);
7692
+ resolve4(chunk);
7545
7693
  return;
7546
7694
  }
7547
7695
  if (s === "") process.exit(130);
@@ -7582,7 +7730,7 @@ function sleep(ms) {
7582
7730
  }
7583
7731
  function captureClaudeSetupToken() {
7584
7732
  const tmpFile = join7(SYNKRO_DIR3, `token-capture-${Date.now()}.raw`);
7585
- return new Promise((resolve3, reject) => {
7733
+ return new Promise((resolve4, reject) => {
7586
7734
  const proc = nodeSpawn("script", ["-q", tmpFile, "claude", "setup-token"], {
7587
7735
  stdio: "inherit"
7588
7736
  });
@@ -7590,7 +7738,7 @@ function captureClaudeSetupToken() {
7590
7738
  proc.on("close", (code) => {
7591
7739
  let raw = "";
7592
7740
  try {
7593
- raw = readFileSync7(tmpFile, "utf-8");
7741
+ raw = readFileSync8(tmpFile, "utf-8");
7594
7742
  } catch (e) {
7595
7743
  reject(new Error(`Could not read script output file: ${e.message}`));
7596
7744
  return;
@@ -7612,7 +7760,7 @@ function captureClaudeSetupToken() {
7612
7760
  reject(new Error(`Could not find token in claude setup-token output (file=${raw.length}b, yellow=${yellow.length}b)`));
7613
7761
  return;
7614
7762
  }
7615
- resolve3(token[0]);
7763
+ resolve4(token[0]);
7616
7764
  });
7617
7765
  });
7618
7766
  }
@@ -7874,9 +8022,10 @@ __export(install_exports, {
7874
8022
  installCommand: () => installCommand,
7875
8023
  parseArgs: () => parseArgs,
7876
8024
  reconcileHarness: () => reconcileHarness,
8025
+ syncSkillFiles: () => syncSkillFiles,
7877
8026
  writeHookScripts: () => writeHookScripts
7878
8027
  });
7879
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync8, readdirSync as readdirSync3 } from "fs";
8028
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync9, readdirSync as readdirSync3 } from "fs";
7880
8029
  import { homedir as homedir8 } from "os";
7881
8030
  import { join as join8 } from "path";
7882
8031
  import { execSync as execSync6, spawnSync as spawnSync3 } from "child_process";
@@ -7908,20 +8057,20 @@ async function promptAgentSelection(detected) {
7908
8057
  detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
7909
8058
  console.log(` ${detected.length + 1}. Both / all (default)`);
7910
8059
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7911
- const ask2 = () => new Promise((resolve3) => {
8060
+ const ask2 = () => new Promise((resolve4) => {
7912
8061
  rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
7913
8062
  const t = answer.trim().toLowerCase();
7914
8063
  if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
7915
8064
  rl.close();
7916
- return resolve3(detected);
8065
+ return resolve4(detected);
7917
8066
  }
7918
8067
  const n = parseInt(t, 10);
7919
8068
  if (Number.isInteger(n) && n >= 1 && n <= detected.length) {
7920
8069
  rl.close();
7921
- return resolve3([detected[n - 1]]);
8070
+ return resolve4([detected[n - 1]]);
7922
8071
  }
7923
8072
  console.log("Invalid choice. Try again.");
7924
- resolve3(ask2());
8073
+ resolve4(ask2());
7925
8074
  });
7926
8075
  });
7927
8076
  return ask2();
@@ -7944,12 +8093,12 @@ async function promptCursorApiKey(opts) {
7944
8093
  return;
7945
8094
  }
7946
8095
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7947
- const key = await new Promise((resolve3) => {
8096
+ const key = await new Promise((resolve4) => {
7948
8097
  rl.question(
7949
8098
  "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): ",
7950
8099
  (answer) => {
7951
8100
  rl.close();
7952
- resolve3(answer.trim());
8101
+ resolve4(answer.trim());
7953
8102
  }
7954
8103
  );
7955
8104
  });
@@ -7963,12 +8112,12 @@ async function promptCursorApiKey(opts) {
7963
8112
  async function promptGradingMode() {
7964
8113
  if (!process.stdin.isTTY) return "local";
7965
8114
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7966
- return new Promise((resolve3) => {
8115
+ return new Promise((resolve4) => {
7967
8116
  rl.question(
7968
8117
  "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: ",
7969
8118
  (answer) => {
7970
8119
  rl.close();
7971
- resolve3(answer.trim().toLowerCase() === "byok" ? "byok" : "local");
8120
+ resolve4(answer.trim().toLowerCase() === "byok" ? "byok" : "local");
7972
8121
  }
7973
8122
  );
7974
8123
  });
@@ -7976,25 +8125,25 @@ async function promptGradingMode() {
7976
8125
  async function promptStorageMode() {
7977
8126
  if (!process.stdin.isTTY) return "local";
7978
8127
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7979
- return new Promise((resolve3) => {
8128
+ return new Promise((resolve4) => {
7980
8129
  rl.question(
7981
8130
  "Where should telemetry be stored?\n local \u2014 on this machine only (default)\n cloud \u2014 sent to Synkro cloud\nChoose [local] / cloud: ",
7982
8131
  (answer) => {
7983
8132
  rl.close();
7984
- resolve3(answer.trim().toLowerCase() === "cloud" ? "cloud" : "local");
8133
+ resolve4(answer.trim().toLowerCase() === "cloud" ? "cloud" : "local");
7985
8134
  }
7986
8135
  );
7987
8136
  });
7988
8137
  }
7989
8138
  async function promptTranscriptConsent() {
7990
8139
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
7991
- return new Promise((resolve3) => {
8140
+ return new Promise((resolve4) => {
7992
8141
  rl.question(
7993
8142
  "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) ",
7994
8143
  (answer) => {
7995
8144
  rl.close();
7996
8145
  const trimmed = answer.trim().toLowerCase();
7997
- resolve3(trimmed === "" || trimmed === "y" || trimmed === "yes");
8146
+ resolve4(trimmed === "" || trimmed === "y" || trimmed === "yes");
7998
8147
  }
7999
8148
  );
8000
8149
  });
@@ -8102,7 +8251,7 @@ function shellQuoteSingle(value) {
8102
8251
  }
8103
8252
  function resolveSynkroBundle() {
8104
8253
  const scriptPath = process.argv[1];
8105
- if (scriptPath && existsSync9(scriptPath)) return scriptPath;
8254
+ if (scriptPath && existsSync10(scriptPath)) return scriptPath;
8106
8255
  return null;
8107
8256
  }
8108
8257
  function writeConfigEnv(opts) {
@@ -8122,7 +8271,7 @@ function writeConfigEnv(opts) {
8122
8271
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
8123
8272
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
8124
8273
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
8125
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.33")}`
8274
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.35")}`
8126
8275
  ];
8127
8276
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
8128
8277
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -8144,8 +8293,8 @@ function resolveDeploymentMode() {
8144
8293
  const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
8145
8294
  if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
8146
8295
  try {
8147
- if (existsSync9(CONFIG_PATH2)) {
8148
- const m = readFileSync8(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
8296
+ if (existsSync10(CONFIG_PATH2)) {
8297
+ const m = readFileSync9(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
8149
8298
  const val = m?.[1]?.toLowerCase();
8150
8299
  if (val === "bare-host" || val === "docker") return val;
8151
8300
  }
@@ -8174,14 +8323,14 @@ function collectLocalMetadata(includeClaudeCode = true) {
8174
8323
  }
8175
8324
  const claudeDir = join8(homedir8(), ".claude");
8176
8325
  try {
8177
- const settings = JSON.parse(readFileSync8(join8(claudeDir, "settings.json"), "utf-8"));
8326
+ const settings = JSON.parse(readFileSync9(join8(claudeDir, "settings.json"), "utf-8"));
8178
8327
  const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
8179
8328
  if (plugins.length) meta.enabled_plugins = plugins;
8180
8329
  if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
8181
8330
  } catch {
8182
8331
  }
8183
8332
  try {
8184
- const mcpCache = JSON.parse(readFileSync8(join8(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
8333
+ const mcpCache = JSON.parse(readFileSync9(join8(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
8185
8334
  const mcpNames = Object.keys(mcpCache);
8186
8335
  if (mcpNames.length) meta.mcp_servers = mcpNames;
8187
8336
  } catch {
@@ -8196,7 +8345,7 @@ function collectLocalMetadata(includeClaudeCode = true) {
8196
8345
  const sessionsDir = join8(claudeDir, "sessions");
8197
8346
  const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
8198
8347
  for (const f of files) {
8199
- const s = JSON.parse(readFileSync8(join8(sessionsDir, f), "utf-8"));
8348
+ const s = JSON.parse(readFileSync9(join8(sessionsDir, f), "utf-8"));
8200
8349
  if (s.version) {
8201
8350
  meta.cc_version = meta.cc_version || s.version;
8202
8351
  break;
@@ -8328,7 +8477,7 @@ async function installCommand(opts = {}) {
8328
8477
  for (const mode of ["edit", "bash"]) {
8329
8478
  const pidFile = join8(SYNKRO_DIR4, "daemon", mode, "daemon.pid");
8330
8479
  try {
8331
- const pid = parseInt(readFileSync8(pidFile, "utf-8").trim(), 10);
8480
+ const pid = parseInt(readFileSync9(pidFile, "utf-8").trim(), 10);
8332
8481
  if (pid > 0) {
8333
8482
  process.kill(pid, "SIGTERM");
8334
8483
  console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
@@ -8462,7 +8611,7 @@ async function installCommand(opts = {}) {
8462
8611
  try {
8463
8612
  if (useLocalMcp) {
8464
8613
  const jwtPath = join8(SYNKRO_DIR4, ".mcp-jwt");
8465
- if (!existsSync9(jwtPath)) {
8614
+ if (!existsSync10(jwtPath)) {
8466
8615
  const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
8467
8616
  method: "POST",
8468
8617
  headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
@@ -8552,7 +8701,7 @@ async function installCommand(opts = {}) {
8552
8701
  const ready = await waitForContainerReady(6e4);
8553
8702
  if (ready) {
8554
8703
  console.log(" \u2713 container ready");
8555
- const mcpJwt = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8704
+ const mcpJwt = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8556
8705
  try {
8557
8706
  const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
8558
8707
  method: "POST",
@@ -8569,6 +8718,7 @@ async function installCommand(opts = {}) {
8569
8718
  } catch {
8570
8719
  console.warn(" \u26A0 ingest endpoint unreachable \u2014 telemetry spool may not drain.");
8571
8720
  }
8721
+ await syncSkillFiles();
8572
8722
  } else {
8573
8723
  console.error(" \u2717 container did not become healthy within 60s");
8574
8724
  console.error(" Run `docker logs synkro-server` to debug.");
@@ -8583,7 +8733,7 @@ async function installCommand(opts = {}) {
8583
8733
  try {
8584
8734
  let mcpToken = "";
8585
8735
  try {
8586
- mcpToken = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8736
+ mcpToken = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8587
8737
  } catch {
8588
8738
  }
8589
8739
  if (mcpToken) {
@@ -8647,12 +8797,58 @@ async function installCommand(opts = {}) {
8647
8797
  }
8648
8798
  console.log("\u2713 Synkro installed.");
8649
8799
  }
8800
+ function parseSynkroYaml2(raw) {
8801
+ const result = {};
8802
+ const lines = raw.split("\n");
8803
+ let currentKey = "";
8804
+ let currentObj = null;
8805
+ let currentArr = null;
8806
+ for (const line of lines) {
8807
+ if (!line.trim() || line.trim().startsWith("#")) continue;
8808
+ if (/^\S/.test(line) && line.includes(":")) {
8809
+ if (currentObj && currentKey) result[currentKey] = currentObj;
8810
+ if (currentArr && currentKey) result[currentKey] = currentArr;
8811
+ currentObj = null;
8812
+ currentArr = null;
8813
+ const colonIdx = line.indexOf(":");
8814
+ const key = line.slice(0, colonIdx).trim();
8815
+ const val = line.slice(colonIdx + 1).trim();
8816
+ currentKey = key;
8817
+ if (val) {
8818
+ if (val === "[]") result[key] = [];
8819
+ else if (val === "true") result[key] = true;
8820
+ else if (val === "false") result[key] = false;
8821
+ else if (/^\d+$/.test(val)) result[key] = parseInt(val, 10);
8822
+ else result[key] = val;
8823
+ currentKey = "";
8824
+ }
8825
+ } else if (/^ - /.test(line)) {
8826
+ if (!currentArr) currentArr = [];
8827
+ currentArr.push(line.replace(/^ - /, "").trim());
8828
+ } else if (/^ \S/.test(line) && line.includes(":")) {
8829
+ if (!currentObj) currentObj = {};
8830
+ const colonIdx = line.indexOf(":");
8831
+ const k = line.slice(0, colonIdx).trim();
8832
+ const v = line.slice(colonIdx + 1).trim();
8833
+ if (v === "true") currentObj[k] = true;
8834
+ else if (v === "false") currentObj[k] = false;
8835
+ else if (/^\d+$/.test(v)) currentObj[k] = parseInt(v, 10);
8836
+ else currentObj[k] = v;
8837
+ }
8838
+ }
8839
+ if (currentObj && currentKey) result[currentKey] = currentObj;
8840
+ if (currentArr && currentKey) result[currentKey] = currentArr;
8841
+ return result;
8842
+ }
8843
+ function parseSynkroFileRaw(content) {
8844
+ return content.trimStart().startsWith("{") ? JSON.parse(content) : parseSynkroYaml2(content);
8845
+ }
8650
8846
  function writeSynkroFileIfMissing(opts) {
8651
8847
  try {
8652
8848
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8653
8849
  if (!root) return;
8654
8850
  const fp = join8(root, ".synkro");
8655
- if (existsSync9(fp)) {
8851
+ if (existsSync10(fp)) {
8656
8852
  console.log(` .synkro: ${fp} (existing, respected)`);
8657
8853
  return;
8658
8854
  }
@@ -8666,18 +8862,28 @@ function writeSynkroFileIfMissing(opts) {
8666
8862
  harness.push("cursor");
8667
8863
  if (!opts.hasClaudeCode) pool = "cursor";
8668
8864
  }
8669
- const config = {
8670
- version: 1,
8671
- harness,
8672
- grader: {
8673
- pool,
8674
- mode: opts.gradingMode === "byok" ? "byok" : "local"
8675
- },
8676
- ruleset: "default",
8677
- scanning: { cwe: true, cve: true }
8678
- };
8679
- writeFileSync7(fp, JSON.stringify(config, null, 2) + "\n", "utf-8");
8680
- console.log(` .synkro: wrote ${fp} (pool=${pool}, mode=${config.grader.mode})`);
8865
+ const mode = opts.gradingMode === "byok" ? "byok" : "local";
8866
+ const yaml = [
8867
+ "version: 1",
8868
+ "",
8869
+ "harness:",
8870
+ ...harness.map((h) => ` - ${h}`),
8871
+ "",
8872
+ "grader:",
8873
+ ` pool: ${pool}`,
8874
+ ` mode: ${mode}`,
8875
+ "",
8876
+ "ruleset: default",
8877
+ "",
8878
+ "skills: []",
8879
+ "",
8880
+ "scanning:",
8881
+ " cwe: true",
8882
+ " cve: true",
8883
+ ""
8884
+ ].join("\n");
8885
+ writeFileSync7(fp, yaml, "utf-8");
8886
+ console.log(` .synkro: wrote ${fp} (pool=${pool}, mode=${mode})`);
8681
8887
  } catch {
8682
8888
  }
8683
8889
  }
@@ -8686,8 +8892,8 @@ function readSynkroFilePool2() {
8686
8892
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8687
8893
  if (!root) return "auto";
8688
8894
  const fp = join8(root, ".synkro");
8689
- if (!existsSync9(fp)) return "auto";
8690
- const parsed = JSON.parse(readFileSync8(fp, "utf-8"));
8895
+ if (!existsSync10(fp)) return "auto";
8896
+ const parsed = parseSynkroFileRaw(readFileSync9(fp, "utf-8"));
8691
8897
  const pool = parsed?.grader?.pool;
8692
8898
  if (pool === "cursor" || pool === "claude") return pool;
8693
8899
  } catch {
@@ -8699,8 +8905,8 @@ function readFullSynkroFile() {
8699
8905
  const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8700
8906
  if (!root) return null;
8701
8907
  const fp = join8(root, ".synkro");
8702
- if (!existsSync9(fp)) return null;
8703
- const parsed = JSON.parse(readFileSync8(fp, "utf-8"));
8908
+ if (!existsSync10(fp)) return null;
8909
+ const parsed = parseSynkroFileRaw(readFileSync9(fp, "utf-8"));
8704
8910
  const valid = ["claude-code", "cursor"];
8705
8911
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
8706
8912
  return {
@@ -8710,7 +8916,9 @@ function readFullSynkroFile() {
8710
8916
  mode: ["local", "byok"].includes(parsed.grader?.mode) ? parsed.grader.mode : "local"
8711
8917
  },
8712
8918
  ruleset: parsed.ruleset || "default",
8713
- scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false }
8919
+ skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && s.endsWith(".md")) : [],
8920
+ scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },
8921
+ _repoRoot: root
8714
8922
  };
8715
8923
  } catch {
8716
8924
  return null;
@@ -8745,7 +8953,7 @@ function reconcileHarness() {
8745
8953
  });
8746
8954
  console.log(" \u2713 Claude Code hooks registered");
8747
8955
  try {
8748
- const mcpJwt = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8956
+ const mcpJwt = readFileSync9(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
8749
8957
  if (mcpJwt) {
8750
8958
  installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt, local: true });
8751
8959
  console.log(" \u2713 Claude Code MCP registered");
@@ -8797,6 +9005,33 @@ function reconcileHarness() {
8797
9005
  if (providers.length === 0) providers.push("claude_code");
8798
9006
  return splitWorkers(total, providers);
8799
9007
  }
9008
+ async function syncSkillFiles() {
9009
+ const sf = readFullSynkroFile();
9010
+ if (!sf || sf.skills.length === 0) return;
9011
+ const resolved = resolveSkillPaths(sf.skills, sf._repoRoot);
9012
+ if (resolved.length === 0) return;
9013
+ const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
9014
+ for (const fp of resolved) {
9015
+ const skill = parseSkillFile(fp);
9016
+ if (!skill || skill.rules.length === 0) continue;
9017
+ try {
9018
+ const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {
9019
+ method: "POST",
9020
+ headers: { "Content-Type": "application/json" },
9021
+ body: JSON.stringify(skill),
9022
+ signal: AbortSignal.timeout(1e4)
9023
+ });
9024
+ if (resp.ok) {
9025
+ const result = await resp.json();
9026
+ console.log(` \u2713 skill ${skill.source}: ${result.created} rules synced (${result.removed} removed)`);
9027
+ } else {
9028
+ console.warn(` \u26A0 skill ${skill.source}: sync failed (${resp.status})`);
9029
+ }
9030
+ } catch (e) {
9031
+ console.warn(` \u26A0 skill ${skill.source}: ${e.message}`);
9032
+ }
9033
+ }
9034
+ }
8800
9035
  function detectGitRepo2() {
8801
9036
  const run = (cmd2) => {
8802
9037
  try {
@@ -8816,7 +9051,7 @@ function getClaudeProjectsFolder() {
8816
9051
  const cwd = process.cwd();
8817
9052
  const sanitized = "-" + cwd.replace(/\//g, "-");
8818
9053
  const projectsDir = join8(homedir8(), ".claude", "projects", sanitized);
8819
- return existsSync9(projectsDir) ? projectsDir : null;
9054
+ return existsSync10(projectsDir) ? projectsDir : null;
8820
9055
  }
8821
9056
  function extractSessionInsights(projectsDir) {
8822
9057
  const insights = [];
@@ -8825,7 +9060,7 @@ function extractSessionInsights(projectsDir) {
8825
9060
  const sessionId = file.replace(".jsonl", "");
8826
9061
  const filePath = join8(projectsDir, file);
8827
9062
  try {
8828
- const content = readFileSync8(filePath, "utf-8");
9063
+ const content = readFileSync9(filePath, "utf-8");
8829
9064
  const lines = content.split("\n").filter(Boolean);
8830
9065
  for (let i = 0; i < lines.length; i++) {
8831
9066
  try {
@@ -8901,7 +9136,7 @@ function extractTextContent(content) {
8901
9136
  return "";
8902
9137
  }
8903
9138
  function parseTranscriptFile(filePath) {
8904
- const content = readFileSync8(filePath, "utf-8");
9139
+ const content = readFileSync9(filePath, "utf-8");
8905
9140
  const lines = content.split("\n").filter(Boolean);
8906
9141
  const messages = [];
8907
9142
  for (let i = 0; i < lines.length; i++) {
@@ -8971,7 +9206,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
8971
9206
  process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
8972
9207
  }
8973
9208
  try {
8974
- const content = readFileSync8(join8(projectsDir, file), "utf-8");
9209
+ const content = readFileSync9(join8(projectsDir, file), "utf-8");
8975
9210
  const lineCount = content.split("\n").filter(Boolean).length;
8976
9211
  writeFileSync7(join8(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
8977
9212
  } catch {
@@ -9025,7 +9260,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
9025
9260
  const sessionId = file.replace(".jsonl", "");
9026
9261
  const filePath = join8(projectsDir, file);
9027
9262
  try {
9028
- const content = readFileSync8(filePath, "utf-8");
9263
+ const content = readFileSync9(filePath, "utf-8");
9029
9264
  const lineCount = content.split("\n").filter(Boolean).length;
9030
9265
  writeFileSync7(join8(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9031
9266
  } catch {
@@ -9042,6 +9277,7 @@ var init_install = __esm({
9042
9277
  init_ccHookConfig();
9043
9278
  init_cursorHookConfig();
9044
9279
  init_mcpConfig();
9280
+ init_skillParser();
9045
9281
  init_hookScripts();
9046
9282
  init_hookScriptsTs();
9047
9283
  init_stub();
@@ -9104,7 +9340,7 @@ rl.on('line', async (line) => {
9104
9340
  });
9105
9341
 
9106
9342
  // cli/local-cc/install.ts
9107
- 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";
9343
+ 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";
9108
9344
  import { join as join9 } from "path";
9109
9345
  import { homedir as homedir9 } from "os";
9110
9346
  import { spawnSync as spawnSync4 } from "child_process";
@@ -9140,10 +9376,10 @@ function runBunInstall() {
9140
9376
  }
9141
9377
  }
9142
9378
  function safelyMutateClaudeJson(mutator) {
9143
- if (!existsSync10(CLAUDE_JSON_PATH)) {
9379
+ if (!existsSync11(CLAUDE_JSON_PATH)) {
9144
9380
  return;
9145
9381
  }
9146
- const originalText = readFileSync9(CLAUDE_JSON_PATH, "utf-8");
9382
+ const originalText = readFileSync10(CLAUDE_JSON_PATH, "utf-8");
9147
9383
  let parsed;
9148
9384
  try {
9149
9385
  parsed = JSON.parse(originalText);
@@ -9594,7 +9830,7 @@ var disconnect_exports = {};
9594
9830
  __export(disconnect_exports, {
9595
9831
  disconnectCommand: () => disconnectCommand
9596
9832
  });
9597
- import { existsSync as existsSync11, rmSync, readdirSync as readdirSync4 } from "fs";
9833
+ import { existsSync as existsSync12, rmSync, readdirSync as readdirSync4 } from "fs";
9598
9834
  import { homedir as homedir10 } from "os";
9599
9835
  import { join as join10 } from "path";
9600
9836
  import { spawnSync as spawnSync5 } from "child_process";
@@ -9635,10 +9871,10 @@ function confirmPurge() {
9635
9871
  return Promise.resolve(false);
9636
9872
  }
9637
9873
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
9638
- return new Promise((resolve3) => {
9874
+ return new Promise((resolve4) => {
9639
9875
  rl.question(" Type 'yes' to wipe everything (anything else cancels): ", (answer) => {
9640
9876
  rl.close();
9641
- resolve3(answer.trim().toLowerCase() === "yes");
9877
+ resolve4(answer.trim().toLowerCase() === "yes");
9642
9878
  });
9643
9879
  });
9644
9880
  }
@@ -9670,7 +9906,7 @@ async function disconnectCommand(args2 = []) {
9670
9906
  const cursorMcpRemoved = uninstallCursorMcpConfig();
9671
9907
  console.log(`${cursorMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Cursor): ${cursorMcpRemoved ? "removed from ~/.cursor/mcp.json" : "no entry found"}`);
9672
9908
  }
9673
- if (existsSync11(SYNKRO_DIR5)) {
9909
+ if (existsSync12(SYNKRO_DIR5)) {
9674
9910
  if (purge) {
9675
9911
  rmSync(SYNKRO_DIR5, { recursive: true, force: true });
9676
9912
  console.log(`\u2713 wiped ${SYNKRO_DIR5} entirely \u2014 including all scan data and backups`);
@@ -9713,7 +9949,7 @@ var init_disconnect = __esm({
9713
9949
  });
9714
9950
 
9715
9951
  // cli/local-cc/turnLog.ts
9716
- 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";
9952
+ 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";
9717
9953
  import { dirname as dirname6, join as join11 } from "path";
9718
9954
  import { homedir as homedir11 } from "os";
9719
9955
  function truncate(s, max = PREVIEW_MAX) {
@@ -9751,11 +9987,11 @@ function appendTurn(args2) {
9751
9987
  }
9752
9988
  }
9753
9989
  function readRecentTurns(n = 20) {
9754
- if (!existsSync12(TURN_LOG_PATH)) return [];
9990
+ if (!existsSync13(TURN_LOG_PATH)) return [];
9755
9991
  try {
9756
9992
  const size = statSync2(TURN_LOG_PATH).size;
9757
9993
  if (size === 0) return [];
9758
- const text = readFileSync10(TURN_LOG_PATH, "utf-8");
9994
+ const text = readFileSync11(TURN_LOG_PATH, "utf-8");
9759
9995
  const lines = text.split("\n").filter(Boolean);
9760
9996
  const lastN = lines.slice(-n).reverse();
9761
9997
  return lastN.map((line) => {
@@ -9772,7 +10008,7 @@ function readRecentTurns(n = 20) {
9772
10008
  function followTurns(onEntry) {
9773
10009
  try {
9774
10010
  mkdirSync10(dirname6(TURN_LOG_PATH), { recursive: true });
9775
- if (!existsSync12(TURN_LOG_PATH)) {
10011
+ if (!existsSync13(TURN_LOG_PATH)) {
9776
10012
  appendFileSync(TURN_LOG_PATH, "", "utf-8");
9777
10013
  }
9778
10014
  } catch {
@@ -9848,7 +10084,7 @@ async function submitToChannel(role, payload, opts = {}) {
9848
10084
  const port = opts.port ?? CHANNEL_PORT;
9849
10085
  const startedAt = Date.now();
9850
10086
  try {
9851
- const result = await new Promise((resolve3, reject) => {
10087
+ const result = await new Promise((resolve4, reject) => {
9852
10088
  const req = httpRequest({
9853
10089
  host: CHANNEL_HOST,
9854
10090
  port,
@@ -9874,7 +10110,7 @@ async function submitToChannel(role, payload, opts = {}) {
9874
10110
  reject(new LocalCCError(parsed.error));
9875
10111
  return;
9876
10112
  }
9877
- resolve3(String(parsed.result ?? ""));
10113
+ resolve4(String(parsed.result ?? ""));
9878
10114
  } catch (err) {
9879
10115
  reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
9880
10116
  }
@@ -9900,14 +10136,14 @@ async function submitToChannel(role, payload, opts = {}) {
9900
10136
  }
9901
10137
  }
9902
10138
  function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500) {
9903
- return new Promise((resolve3) => {
10139
+ return new Promise((resolve4) => {
9904
10140
  const sock = connect(port, CHANNEL_HOST);
9905
10141
  const done = (ok) => {
9906
10142
  try {
9907
10143
  sock.destroy();
9908
10144
  } catch {
9909
10145
  }
9910
- resolve3(ok);
10146
+ resolve4(ok);
9911
10147
  };
9912
10148
  sock.once("connect", () => done(true));
9913
10149
  sock.once("error", () => done(false));
@@ -9939,10 +10175,10 @@ __export(grade_exports, {
9939
10175
  gradeCommand: () => gradeCommand
9940
10176
  });
9941
10177
  async function readStdin() {
9942
- return new Promise((resolve3, reject) => {
10178
+ return new Promise((resolve4, reject) => {
9943
10179
  const chunks = [];
9944
10180
  process.stdin.on("data", (c) => chunks.push(c));
9945
- process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
10181
+ process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
9946
10182
  process.stdin.on("error", reject);
9947
10183
  });
9948
10184
  }
@@ -10101,14 +10337,14 @@ function ensureRunning(opts = {}) {
10101
10337
  return startTask(opts);
10102
10338
  }
10103
10339
  function probePort(host, port, timeoutMs = 500) {
10104
- return new Promise((resolve3) => {
10340
+ return new Promise((resolve4) => {
10105
10341
  const sock = connect2(port, host);
10106
10342
  const done = (ok) => {
10107
10343
  try {
10108
10344
  sock.destroy();
10109
10345
  } catch {
10110
10346
  }
10111
- resolve3(ok);
10347
+ resolve4(ok);
10112
10348
  };
10113
10349
  sock.once("connect", () => done(true));
10114
10350
  sock.once("error", () => done(false));
@@ -10201,13 +10437,13 @@ var init_pueue = __esm({
10201
10437
  });
10202
10438
 
10203
10439
  // cli/local-cc/settings.ts
10204
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
10440
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
10205
10441
  import { homedir as homedir13 } from "os";
10206
10442
  import { join as join13 } from "path";
10207
10443
  function isLocalCCEnabled() {
10208
- if (!existsSync13(CONFIG_PATH3)) return false;
10444
+ if (!existsSync14(CONFIG_PATH3)) return false;
10209
10445
  try {
10210
- const content = readFileSync11(CONFIG_PATH3, "utf-8");
10446
+ const content = readFileSync12(CONFIG_PATH3, "utf-8");
10211
10447
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
10212
10448
  return match?.[1] === "yes";
10213
10449
  } catch {
@@ -10231,7 +10467,7 @@ import { spawnSync as spawnSync7 } from "child_process";
10231
10467
  import { homedir as homedir14 } from "os";
10232
10468
  import { join as join14 } from "path";
10233
10469
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
10234
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
10470
+ import { existsSync as existsSync15, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
10235
10471
  function deploymentMode() {
10236
10472
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
10237
10473
  if (env === "docker") return "docker";
@@ -10337,15 +10573,15 @@ TROUBLESHOOTING
10337
10573
  `);
10338
10574
  }
10339
10575
  function readGatewayUrl() {
10340
- if (existsSync14(CONFIG_PATH4)) {
10341
- const m = readFileSync12(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
10576
+ if (existsSync15(CONFIG_PATH4)) {
10577
+ const m = readFileSync13(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
10342
10578
  if (m) return m[1];
10343
10579
  }
10344
10580
  return "https://api.synkro.sh";
10345
10581
  }
10346
10582
  function updateLocalInferenceFlag(enabled) {
10347
- if (!existsSync14(CONFIG_PATH4)) return;
10348
- let content = readFileSync12(CONFIG_PATH4, "utf-8");
10583
+ if (!existsSync15(CONFIG_PATH4)) return;
10584
+ let content = readFileSync13(CONFIG_PATH4, "utf-8");
10349
10585
  const flag = enabled ? "yes" : "no";
10350
10586
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
10351
10587
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -10537,6 +10773,7 @@ async function cmdRestart(rest = []) {
10537
10773
  await dockerUpdate({ claudeWorkers, cursorWorkers });
10538
10774
  const ready = await waitForContainerReady(6e4);
10539
10775
  console.log(ready ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
10776
+ if (ready) await syncSkillFiles();
10540
10777
  return;
10541
10778
  }
10542
10779
  stopTask(CHANNEL_PRIMARY);
@@ -10651,7 +10888,7 @@ function cmdLogs(rest) {
10651
10888
  if (!raw) console.log(" " + colorize("(use --raw / -r to see full payloads, --live / -f to follow)", 90));
10652
10889
  return;
10653
10890
  }
10654
- return new Promise((resolve3) => {
10891
+ return new Promise((resolve4) => {
10655
10892
  console.log(" " + colorize("\u2014 following new turns (Ctrl-C to exit) \u2014", 90));
10656
10893
  const stop = followTurns((t) => {
10657
10894
  console.log(" " + formatTurn(t, raw));
@@ -10659,7 +10896,7 @@ function cmdLogs(rest) {
10659
10896
  const onSigint = () => {
10660
10897
  stop();
10661
10898
  process.removeListener("SIGINT", onSigint);
10662
- resolve3();
10899
+ resolve4();
10663
10900
  };
10664
10901
  process.on("SIGINT", onSigint);
10665
10902
  });
@@ -10887,13 +11124,13 @@ var config_exports = {};
10887
11124
  __export(config_exports, {
10888
11125
  configCommand: () => configCommand
10889
11126
  });
10890
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync15 } from "fs";
11127
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync16 } from "fs";
10891
11128
  import { join as join15 } from "path";
10892
11129
  import { homedir as homedir15 } from "os";
10893
11130
  function readConfigEnv() {
10894
- if (!existsSync15(CONFIG_PATH5)) return {};
11131
+ if (!existsSync16(CONFIG_PATH5)) return {};
10895
11132
  const out = {};
10896
- for (const line of readFileSync13(CONFIG_PATH5, "utf-8").split("\n")) {
11133
+ for (const line of readFileSync14(CONFIG_PATH5, "utf-8").split("\n")) {
10897
11134
  const t = line.trim();
10898
11135
  if (!t || t.startsWith("#")) continue;
10899
11136
  const eq = t.indexOf("=");
@@ -10902,11 +11139,11 @@ function readConfigEnv() {
10902
11139
  return out;
10903
11140
  }
10904
11141
  function updateConfigValue(key, value) {
10905
- if (!existsSync15(CONFIG_PATH5)) {
11142
+ if (!existsSync16(CONFIG_PATH5)) {
10906
11143
  console.error("No config found. Run `synkro install` first.");
10907
11144
  process.exit(1);
10908
11145
  }
10909
- const lines = readFileSync13(CONFIG_PATH5, "utf-8").split("\n");
11146
+ const lines = readFileSync14(CONFIG_PATH5, "utf-8").split("\n");
10910
11147
  const pattern = new RegExp(`^${key}=`);
10911
11148
  let found = false;
10912
11149
  const updated = lines.map((line) => {
@@ -11033,14 +11270,14 @@ var init_config = __esm({
11033
11270
  });
11034
11271
 
11035
11272
  // cli/bootstrap.js
11036
- import { readFileSync as readFileSync14, existsSync as existsSync16 } from "fs";
11037
- import { resolve as resolve2 } from "path";
11273
+ import { readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
11274
+ import { resolve as resolve3 } from "path";
11038
11275
  var envCandidates = [
11039
- resolve2(process.env.HOME ?? "", ".synkro", "config.env")
11276
+ resolve3(process.env.HOME ?? "", ".synkro", "config.env")
11040
11277
  ];
11041
11278
  for (const envPath of envCandidates) {
11042
- if (!existsSync16(envPath)) continue;
11043
- const envContent = readFileSync14(envPath, "utf-8");
11279
+ if (!existsSync17(envPath)) continue;
11280
+ const envContent = readFileSync15(envPath, "utf-8");
11044
11281
  for (const line of envContent.split("\n")) {
11045
11282
  const trimmed = line.trim();
11046
11283
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -11055,7 +11292,7 @@ var args = process.argv.slice(2);
11055
11292
  var cmd = args[0] || "";
11056
11293
  var subArgs = args.slice(1);
11057
11294
  function printVersion() {
11058
- console.log("1.6.33");
11295
+ console.log("1.6.35");
11059
11296
  }
11060
11297
  function printHelp2() {
11061
11298
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents