@synkro-sh/cli 1.6.48 → 1.6.50

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
@@ -8451,18 +8451,35 @@ async function promptStorageMode() {
8451
8451
  );
8452
8452
  });
8453
8453
  }
8454
- async function promptTranscriptConsent() {
8454
+ async function promptTranscriptSources(wantCC, wantCursor) {
8455
+ if (!wantCC && !wantCursor) return { cc: false, cursor: false };
8455
8456
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
8456
- return new Promise((resolve4) => {
8457
- rl.question(
8458
- "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) ",
8459
- (answer) => {
8460
- rl.close();
8461
- const trimmed = answer.trim().toLowerCase();
8462
- resolve4(trimmed === "" || trimmed === "y" || trimmed === "yes");
8463
- }
8457
+ const ask2 = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim().toLowerCase())));
8458
+ try {
8459
+ if (wantCC && wantCursor) {
8460
+ console.log("Import and embed past session history? This indexes sessions so Ask Synkro");
8461
+ console.log("can answer questions about your patterns and the dashboard shows full history.");
8462
+ console.log(" 1. Cursor only");
8463
+ console.log(" 2. Claude Code only");
8464
+ console.log(" 3. Both");
8465
+ console.log(" n. Skip");
8466
+ const a2 = await ask2("Choose [1/2/3/n] (default: 3): ");
8467
+ if (a2 === "n" || a2 === "no") return { cc: false, cursor: false };
8468
+ if (a2 === "1") return { cc: false, cursor: true };
8469
+ if (a2 === "2") return { cc: true, cursor: false };
8470
+ return { cc: true, cursor: true };
8471
+ }
8472
+ const which2 = wantCursor ? "Cursor" : "Claude Code";
8473
+ const a = await ask2(
8474
+ `Import and embed your ${which2} session history?
8475
+ This indexes past sessions so Ask Synkro can answer questions about your
8476
+ coding patterns and the dashboard shows full history. (Y/n) `
8464
8477
  );
8465
- });
8478
+ const yes = a === "" || a === "y" || a === "yes";
8479
+ return { cc: wantCC && yes, cursor: wantCursor && yes };
8480
+ } finally {
8481
+ rl.close();
8482
+ }
8466
8483
  }
8467
8484
  function ensureSynkroDir() {
8468
8485
  mkdirSync8(SYNKRO_DIR4, { recursive: true });
@@ -8630,7 +8647,7 @@ function writeConfigEnv(opts) {
8630
8647
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
8631
8648
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
8632
8649
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
8633
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.48")}`
8650
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.50")}`
8634
8651
  ];
8635
8652
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
8636
8653
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -8813,7 +8830,8 @@ async function installCommand(opts = {}) {
8813
8830
  let agents;
8814
8831
  let gradingMode;
8815
8832
  let storageMode;
8816
- let transcriptConsent = true;
8833
+ let transcriptCC = false;
8834
+ let transcriptCursor = false;
8817
8835
  if (existingSynkro) {
8818
8836
  const wantCC = existingSynkro.harness.includes("claude-code");
8819
8837
  const wantCursor = existingSynkro.harness.includes("cursor");
@@ -8853,15 +8871,26 @@ async function installCommand(opts = {}) {
8853
8871
  console.log(" BYOK grading uses your own provider key \u2014 register one in the");
8854
8872
  console.log(" dashboard under Settings \u2192 Provider Keys if you have not already.\n");
8855
8873
  }
8856
- if (process.stdin.isTTY) {
8857
- transcriptConsent = await promptTranscriptConsent();
8858
- if (transcriptConsent) {
8859
- console.log(" \u2713 Session import enabled\n");
8860
- } else {
8861
- console.log(" \u2717 Session import skipped\n");
8862
- }
8863
- }
8864
8874
  }
8875
+ {
8876
+ const wantCC = agents.some((a) => a.kind === "claude_code");
8877
+ const wantCursor = agents.some((a) => a.kind === "cursor");
8878
+ if ((wantCC || wantCursor) && process.stdin.isTTY) {
8879
+ const src = await promptTranscriptSources(wantCC, wantCursor);
8880
+ transcriptCC = src.cc;
8881
+ transcriptCursor = src.cursor;
8882
+ const chosen = [src.cc && "Claude Code", src.cursor && "Cursor"].filter(Boolean).join(" + ");
8883
+ console.log(chosen ? ` \u2713 Session import: ${chosen}
8884
+ ` : " \u2717 Session import skipped\n");
8885
+ } else if ((wantCC || wantCursor) && process.env.SYNKRO_IMPORT_TRANSCRIPTS === "yes") {
8886
+ transcriptCC = wantCC;
8887
+ transcriptCursor = wantCursor;
8888
+ console.log(" \u2713 Session import: SYNKRO_IMPORT_TRANSCRIPTS=yes\n");
8889
+ } else if (!process.stdin.isTTY) {
8890
+ console.log(" \u2717 Session import skipped (non-interactive; set SYNKRO_IMPORT_TRANSCRIPTS=yes to opt in)\n");
8891
+ }
8892
+ }
8893
+ const transcriptConsent = transcriptCC || transcriptCursor;
8865
8894
  ensureSynkroDir();
8866
8895
  const hookMode = opts.hookMode || resolvePersistedHookMode();
8867
8896
  const scripts = writeHookScripts(hookMode);
@@ -9128,7 +9157,7 @@ async function installCommand(opts = {}) {
9128
9157
  }
9129
9158
  console.log();
9130
9159
  }
9131
- if (transcriptConsent && hasClaudeCode) {
9160
+ if (transcriptConsent) {
9132
9161
  const repo = detectGitRepo2();
9133
9162
  if (repo) {
9134
9163
  if (storageMode === "local") {
@@ -9140,21 +9169,34 @@ async function installCommand(opts = {}) {
9140
9169
  }
9141
9170
  if (mcpToken) {
9142
9171
  const mcpPort = parseInt(process.env.SYNKRO_MCP_PORT || "18931", 10);
9143
- const result = await syncTranscriptsLocal(mcpPort, mcpToken, repo);
9144
- if (result.messages > 0) {
9145
- console.log(` \u2713 Imported ${result.sessions} sessions (${result.messages} messages) into local store.`);
9172
+ let imported = 0;
9173
+ if (transcriptCC && hasClaudeCode) {
9174
+ const r = await syncTranscriptsLocal(mcpPort, mcpToken, repo);
9175
+ if (r.messages > 0) {
9176
+ console.log(` \u2713 Imported ${r.sessions} Claude Code sessions (${r.messages} messages) into local store.`);
9177
+ imported += r.messages;
9178
+ }
9179
+ }
9180
+ if (transcriptCursor && hasCursor) {
9181
+ const r = await syncCursorTranscriptsLocal(mcpPort, mcpToken, repo);
9182
+ if (r.messages > 0) {
9183
+ console.log(` \u2713 Imported ${r.sessions} Cursor sessions (${r.messages} messages) into local store.`);
9184
+ imported += r.messages;
9185
+ }
9186
+ }
9187
+ if (imported > 0) {
9146
9188
  console.log(" Embeddings generated. Analyzing for rule suggestions...");
9147
9189
  try {
9148
9190
  const suggestResp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/suggest-rules`, {
9149
9191
  method: "POST",
9150
- headers: { "Content-Type": "application/json" },
9192
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
9151
9193
  signal: AbortSignal.timeout(9e4)
9152
9194
  });
9153
9195
  if (suggestResp.ok) {
9154
9196
  const suggestResult = await suggestResp.json();
9155
9197
  if (suggestResult.suggested && suggestResult.suggested > 0) {
9156
9198
  console.log(` \u2713 Generated ${suggestResult.suggested} rule suggestions from your session history.`);
9157
- console.log(' Ask Claude: "show me suggested rules" to review them.\n');
9199
+ console.log(' Ask Synkro: "show me suggested rules" to review them.\n');
9158
9200
  } else {
9159
9201
  console.log(" No rule suggestions generated yet \u2014 more session data needed.\n");
9160
9202
  }
@@ -9171,24 +9213,29 @@ async function installCommand(opts = {}) {
9171
9213
  `);
9172
9214
  }
9173
9215
  } else {
9174
- try {
9175
- const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);
9176
- if (ingested > 0) {
9177
- console.log(` \u2713 Indexed ${ingested} session insights from Claude Code history.`);
9178
- }
9179
- } catch (err) {
9180
- console.warn(` \u26A0 Session indexing skipped: ${err.message}
9216
+ if (transcriptCC && hasClaudeCode) {
9217
+ try {
9218
+ const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);
9219
+ if (ingested > 0) {
9220
+ console.log(` \u2713 Indexed ${ingested} session insights from Claude Code history.`);
9221
+ }
9222
+ } catch (err) {
9223
+ console.warn(` \u26A0 Session indexing skipped: ${err.message}
9181
9224
  `);
9182
- }
9183
- try {
9184
- const result = await syncTranscriptsBulk(gatewayUrl, token, repo);
9185
- if (result.messages > 0) {
9186
- console.log(` \u2713 Synced ${result.sessions} sessions (${result.messages} messages) to cloud.`);
9187
- console.log(" Embeddings use your configured inference provider.\n");
9188
9225
  }
9189
- } catch (err) {
9190
- console.warn(` \u26A0 Transcript sync skipped: ${err.message}
9226
+ try {
9227
+ const result = await syncTranscriptsBulk(gatewayUrl, token, repo);
9228
+ if (result.messages > 0) {
9229
+ console.log(` \u2713 Synced ${result.sessions} sessions (${result.messages} messages) to cloud.`);
9230
+ console.log(" Embeddings use your configured inference provider.\n");
9231
+ }
9232
+ } catch (err) {
9233
+ console.warn(` \u26A0 Transcript sync skipped: ${err.message}
9191
9234
  `);
9235
+ }
9236
+ }
9237
+ if (transcriptCursor && hasCursor) {
9238
+ console.log(" \u2139 Cursor transcript import requires local storage mode \u2014 skipped under cloud storage.\n");
9192
9239
  }
9193
9240
  }
9194
9241
  }
@@ -9552,6 +9599,80 @@ function extractTextContent(content) {
9552
9599
  }
9553
9600
  return "";
9554
9601
  }
9602
+ function cursorProjectSlug(workspaceRoot) {
9603
+ return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9604
+ }
9605
+ function getCursorTranscriptsDir() {
9606
+ const dir = join8(homedir8(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
9607
+ return existsSync10(dir) ? dir : null;
9608
+ }
9609
+ function isSafeConvId(id) {
9610
+ return /^[A-Za-z0-9_-]+$/.test(id);
9611
+ }
9612
+ function parseCursorTranscriptFile(filePath) {
9613
+ const content = readFileSync8(filePath, "utf-8");
9614
+ const lines = content.split("\n").filter(Boolean);
9615
+ const messages = [];
9616
+ for (let i = 0; i < lines.length; i++) {
9617
+ try {
9618
+ const entry = JSON.parse(lines[i]);
9619
+ const role = entry.role || entry.message?.role;
9620
+ if (role !== "user" && role !== "assistant") continue;
9621
+ const text = extractTextContent(entry.message?.content ?? entry.content);
9622
+ if (!text) continue;
9623
+ messages.push({ message_index: i, type: role, content: text });
9624
+ } catch {
9625
+ }
9626
+ }
9627
+ return messages;
9628
+ }
9629
+ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
9630
+ const dir = getCursorTranscriptsDir();
9631
+ if (!dir) return { sessions: 0, messages: 0 };
9632
+ let convDirs = [];
9633
+ try {
9634
+ convDirs = readdirSync3(dir, { withFileTypes: true }).filter((d) => d.isDirectory() && isSafeConvId(d.name)).map((d) => d.name);
9635
+ } catch {
9636
+ return { sessions: 0, messages: 0 };
9637
+ }
9638
+ if (convDirs.length === 0) return { sessions: 0, messages: 0 };
9639
+ console.log(` Found ${convDirs.length} Cursor session transcripts, importing + embedding...`);
9640
+ let totalSessions = 0;
9641
+ let totalMessages = 0;
9642
+ for (let i = 0; i < convDirs.length; i++) {
9643
+ const convId = convDirs[i];
9644
+ if (!isSafeConvId(convId)) continue;
9645
+ const filePath = join8(dir, convId, `${convId}.jsonl`);
9646
+ if (!existsSync10(filePath)) continue;
9647
+ try {
9648
+ const all = parseCursorTranscriptFile(filePath);
9649
+ const messages = all.length > 500 ? all.slice(-500) : all;
9650
+ if (messages.length === 0) continue;
9651
+ const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {
9652
+ method: "POST",
9653
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
9654
+ body: JSON.stringify({ session_id: convId, repo, messages }),
9655
+ signal: AbortSignal.timeout(15e3)
9656
+ });
9657
+ if (resp.ok) {
9658
+ const result = await resp.json();
9659
+ totalSessions++;
9660
+ totalMessages += result.ingested ?? messages.length;
9661
+ }
9662
+ } catch {
9663
+ }
9664
+ if ((i + 1) % 10 === 0 || i === convDirs.length - 1) {
9665
+ process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
9666
+ }
9667
+ try {
9668
+ const lc = readFileSync8(filePath, "utf-8").split("\n").filter(Boolean).length;
9669
+ writeFileSync7(join8(OFFSETS_DIR, convId), String(lc), "utf-8");
9670
+ } catch {
9671
+ }
9672
+ }
9673
+ if (totalSessions > 0) process.stdout.write("\n");
9674
+ return { sessions: totalSessions, messages: totalMessages };
9675
+ }
9555
9676
  function parseTranscriptFile(filePath) {
9556
9677
  const content = readFileSync8(filePath, "utf-8");
9557
9678
  const lines = content.split("\n").filter(Boolean);
@@ -11718,7 +11839,7 @@ var args = process.argv.slice(2);
11718
11839
  var cmd = args[0] || "";
11719
11840
  var subArgs = args.slice(1);
11720
11841
  function printVersion() {
11721
- console.log("1.6.48");
11842
+ console.log("1.6.50");
11722
11843
  }
11723
11844
  function printHelp2() {
11724
11845
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents