@synkro-sh/cli 1.6.47 → 1.6.49
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 +165 -46
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -8330,14 +8330,14 @@ import { join as join8 } from "path";
|
|
|
8330
8330
|
import { execSync as execSync6 } from "child_process";
|
|
8331
8331
|
import { createInterface as createInterface3 } from "readline";
|
|
8332
8332
|
function resolvePersistedHookMode() {
|
|
8333
|
-
if (process.env.SYNKRO_HOOK_MODE === "stub") return "stub";
|
|
8334
8333
|
if (process.env.SYNKRO_HOOK_MODE === "full") return "full";
|
|
8334
|
+
if (process.env.SYNKRO_HOOK_MODE === "stub") return "stub";
|
|
8335
8335
|
try {
|
|
8336
8336
|
const env = readFileSync8(CONFIG_PATH2, "utf-8");
|
|
8337
|
-
if (/^SYNKRO_HOOK_MODE=['"]?
|
|
8337
|
+
if (/^SYNKRO_HOOK_MODE=['"]?full['"]?\s*$/m.test(env)) return "full";
|
|
8338
8338
|
} catch {
|
|
8339
8339
|
}
|
|
8340
|
-
return "
|
|
8340
|
+
return "stub";
|
|
8341
8341
|
}
|
|
8342
8342
|
function sanitizeGatewayCandidate(raw) {
|
|
8343
8343
|
if (!raw) return void 0;
|
|
@@ -8354,11 +8354,12 @@ function parseArgs(argv) {
|
|
|
8354
8354
|
else if (a === "--force" || a === "-f") opts.force = true;
|
|
8355
8355
|
else if (a === "--link-repo") opts.linkRepo = true;
|
|
8356
8356
|
else if (a === "--stub" || a === "--mode=stub") opts.hookMode = "stub";
|
|
8357
|
-
else if (a === "--mode=full") opts.hookMode = "full";
|
|
8357
|
+
else if (a === "--legacy" || a === "--mode=full") opts.hookMode = "full";
|
|
8358
8358
|
}
|
|
8359
8359
|
const modeIdx = argv.indexOf("--mode");
|
|
8360
8360
|
if (modeIdx >= 0 && argv[modeIdx + 1] === "stub") opts.hookMode = "stub";
|
|
8361
8361
|
if (modeIdx >= 0 && argv[modeIdx + 1] === "full") opts.hookMode = "full";
|
|
8362
|
+
if (!opts.hookMode && process.env.SYNKRO_HOOK_MODE === "full") opts.hookMode = "full";
|
|
8362
8363
|
if (!opts.hookMode && process.env.SYNKRO_HOOK_MODE === "stub") opts.hookMode = "stub";
|
|
8363
8364
|
if (!opts.gatewayUrl) {
|
|
8364
8365
|
const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);
|
|
@@ -8450,18 +8451,35 @@ async function promptStorageMode() {
|
|
|
8450
8451
|
);
|
|
8451
8452
|
});
|
|
8452
8453
|
}
|
|
8453
|
-
async function
|
|
8454
|
+
async function promptTranscriptSources(wantCC, wantCursor) {
|
|
8455
|
+
if (!wantCC && !wantCursor) return { cc: false, cursor: false };
|
|
8454
8456
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
(
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
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) `
|
|
8463
8477
|
);
|
|
8464
|
-
|
|
8478
|
+
const yes = a === "" || a === "y" || a === "yes";
|
|
8479
|
+
return { cc: wantCC && yes, cursor: wantCursor && yes };
|
|
8480
|
+
} finally {
|
|
8481
|
+
rl.close();
|
|
8482
|
+
}
|
|
8465
8483
|
}
|
|
8466
8484
|
function ensureSynkroDir() {
|
|
8467
8485
|
mkdirSync8(SYNKRO_DIR4, { recursive: true });
|
|
@@ -8470,7 +8488,7 @@ function ensureSynkroDir() {
|
|
|
8470
8488
|
mkdirSync8(OFFSETS_DIR, { recursive: true });
|
|
8471
8489
|
mkdirSync8(join8(SYNKRO_DIR4, "sessions"), { recursive: true });
|
|
8472
8490
|
}
|
|
8473
|
-
function writeHookScripts(mode = "
|
|
8491
|
+
function writeHookScripts(mode = "stub") {
|
|
8474
8492
|
const installExtractCorePath = join8(HOOKS_DIR, "installExtractCore.ts");
|
|
8475
8493
|
const bashScriptPath = join8(HOOKS_DIR, "cc-bash-judge.ts");
|
|
8476
8494
|
const bashFollowupScriptPath = join8(HOOKS_DIR, "cc-bash-followup.ts");
|
|
@@ -8629,7 +8647,7 @@ function writeConfigEnv(opts) {
|
|
|
8629
8647
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
8630
8648
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
8631
8649
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
8632
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
8650
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.49")}`
|
|
8633
8651
|
];
|
|
8634
8652
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
8635
8653
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -8643,7 +8661,7 @@ function writeConfigEnv(opts) {
|
|
|
8643
8661
|
lines.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle(safeMode)}`);
|
|
8644
8662
|
lines.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.gradingMode ?? "local", 16))}`);
|
|
8645
8663
|
lines.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.storageMode ?? "local", 16))}`);
|
|
8646
|
-
lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.hookMode ?? "
|
|
8664
|
+
lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
|
|
8647
8665
|
lines.push("");
|
|
8648
8666
|
writeFileSync7(CONFIG_PATH2, lines.join("\n"), "utf-8");
|
|
8649
8667
|
chmodSync2(CONFIG_PATH2, 384);
|
|
@@ -8812,7 +8830,8 @@ async function installCommand(opts = {}) {
|
|
|
8812
8830
|
let agents;
|
|
8813
8831
|
let gradingMode;
|
|
8814
8832
|
let storageMode;
|
|
8815
|
-
let
|
|
8833
|
+
let transcriptCC = true;
|
|
8834
|
+
let transcriptCursor = true;
|
|
8816
8835
|
if (existingSynkro) {
|
|
8817
8836
|
const wantCC = existingSynkro.harness.includes("claude-code");
|
|
8818
8837
|
const wantCursor = existingSynkro.harness.includes("cursor");
|
|
@@ -8822,6 +8841,8 @@ async function installCommand(opts = {}) {
|
|
|
8822
8841
|
if (agents.length === 0 && detected.length > 0) agents = detected;
|
|
8823
8842
|
gradingMode = existingSynkro.grader.mode === "byok" ? "byok" : "local";
|
|
8824
8843
|
storageMode = "local";
|
|
8844
|
+
transcriptCC = agents.some((a) => a.kind === "claude_code");
|
|
8845
|
+
transcriptCursor = agents.some((a) => a.kind === "cursor");
|
|
8825
8846
|
console.log(`Using .synkro config:`);
|
|
8826
8847
|
console.log(` harness: ${existingSynkro.harness.join(", ")}`);
|
|
8827
8848
|
console.log(` grading: ${gradingMode} pool: ${existingSynkro.grader.pool}`);
|
|
@@ -8852,15 +8873,21 @@ async function installCommand(opts = {}) {
|
|
|
8852
8873
|
console.log(" BYOK grading uses your own provider key \u2014 register one in the");
|
|
8853
8874
|
console.log(" dashboard under Settings \u2192 Provider Keys if you have not already.\n");
|
|
8854
8875
|
}
|
|
8876
|
+
const wantCC = agents.some((a) => a.kind === "claude_code");
|
|
8877
|
+
const wantCursor = agents.some((a) => a.kind === "cursor");
|
|
8855
8878
|
if (process.stdin.isTTY) {
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
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 {
|
|
8886
|
+
transcriptCC = wantCC;
|
|
8887
|
+
transcriptCursor = wantCursor;
|
|
8862
8888
|
}
|
|
8863
8889
|
}
|
|
8890
|
+
const transcriptConsent = transcriptCC || transcriptCursor;
|
|
8864
8891
|
ensureSynkroDir();
|
|
8865
8892
|
const hookMode = opts.hookMode || resolvePersistedHookMode();
|
|
8866
8893
|
const scripts = writeHookScripts(hookMode);
|
|
@@ -9127,7 +9154,7 @@ async function installCommand(opts = {}) {
|
|
|
9127
9154
|
}
|
|
9128
9155
|
console.log();
|
|
9129
9156
|
}
|
|
9130
|
-
if (transcriptConsent
|
|
9157
|
+
if (transcriptConsent) {
|
|
9131
9158
|
const repo = detectGitRepo2();
|
|
9132
9159
|
if (repo) {
|
|
9133
9160
|
if (storageMode === "local") {
|
|
@@ -9139,21 +9166,34 @@ async function installCommand(opts = {}) {
|
|
|
9139
9166
|
}
|
|
9140
9167
|
if (mcpToken) {
|
|
9141
9168
|
const mcpPort = parseInt(process.env.SYNKRO_MCP_PORT || "18931", 10);
|
|
9142
|
-
|
|
9143
|
-
if (
|
|
9144
|
-
|
|
9169
|
+
let imported = 0;
|
|
9170
|
+
if (transcriptCC && hasClaudeCode) {
|
|
9171
|
+
const r = await syncTranscriptsLocal(mcpPort, mcpToken, repo);
|
|
9172
|
+
if (r.messages > 0) {
|
|
9173
|
+
console.log(` \u2713 Imported ${r.sessions} Claude Code sessions (${r.messages} messages) into local store.`);
|
|
9174
|
+
imported += r.messages;
|
|
9175
|
+
}
|
|
9176
|
+
}
|
|
9177
|
+
if (transcriptCursor && hasCursor) {
|
|
9178
|
+
const r = await syncCursorTranscriptsLocal(mcpPort, mcpToken, repo);
|
|
9179
|
+
if (r.messages > 0) {
|
|
9180
|
+
console.log(` \u2713 Imported ${r.sessions} Cursor sessions (${r.messages} messages) into local store.`);
|
|
9181
|
+
imported += r.messages;
|
|
9182
|
+
}
|
|
9183
|
+
}
|
|
9184
|
+
if (imported > 0) {
|
|
9145
9185
|
console.log(" Embeddings generated. Analyzing for rule suggestions...");
|
|
9146
9186
|
try {
|
|
9147
9187
|
const suggestResp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/suggest-rules`, {
|
|
9148
9188
|
method: "POST",
|
|
9149
|
-
headers: { "Content-Type": "application/json" },
|
|
9189
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
|
|
9150
9190
|
signal: AbortSignal.timeout(9e4)
|
|
9151
9191
|
});
|
|
9152
9192
|
if (suggestResp.ok) {
|
|
9153
9193
|
const suggestResult = await suggestResp.json();
|
|
9154
9194
|
if (suggestResult.suggested && suggestResult.suggested > 0) {
|
|
9155
9195
|
console.log(` \u2713 Generated ${suggestResult.suggested} rule suggestions from your session history.`);
|
|
9156
|
-
console.log(' Ask
|
|
9196
|
+
console.log(' Ask Synkro: "show me suggested rules" to review them.\n');
|
|
9157
9197
|
} else {
|
|
9158
9198
|
console.log(" No rule suggestions generated yet \u2014 more session data needed.\n");
|
|
9159
9199
|
}
|
|
@@ -9170,24 +9210,29 @@ async function installCommand(opts = {}) {
|
|
|
9170
9210
|
`);
|
|
9171
9211
|
}
|
|
9172
9212
|
} else {
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9213
|
+
if (transcriptCC && hasClaudeCode) {
|
|
9214
|
+
try {
|
|
9215
|
+
const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);
|
|
9216
|
+
if (ingested > 0) {
|
|
9217
|
+
console.log(` \u2713 Indexed ${ingested} session insights from Claude Code history.`);
|
|
9218
|
+
}
|
|
9219
|
+
} catch (err) {
|
|
9220
|
+
console.warn(` \u26A0 Session indexing skipped: ${err.message}
|
|
9180
9221
|
`);
|
|
9181
|
-
}
|
|
9182
|
-
try {
|
|
9183
|
-
const result = await syncTranscriptsBulk(gatewayUrl, token, repo);
|
|
9184
|
-
if (result.messages > 0) {
|
|
9185
|
-
console.log(` \u2713 Synced ${result.sessions} sessions (${result.messages} messages) to cloud.`);
|
|
9186
|
-
console.log(" Embeddings use your configured inference provider.\n");
|
|
9187
9222
|
}
|
|
9188
|
-
|
|
9189
|
-
|
|
9223
|
+
try {
|
|
9224
|
+
const result = await syncTranscriptsBulk(gatewayUrl, token, repo);
|
|
9225
|
+
if (result.messages > 0) {
|
|
9226
|
+
console.log(` \u2713 Synced ${result.sessions} sessions (${result.messages} messages) to cloud.`);
|
|
9227
|
+
console.log(" Embeddings use your configured inference provider.\n");
|
|
9228
|
+
}
|
|
9229
|
+
} catch (err) {
|
|
9230
|
+
console.warn(` \u26A0 Transcript sync skipped: ${err.message}
|
|
9190
9231
|
`);
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9234
|
+
if (transcriptCursor && hasCursor) {
|
|
9235
|
+
console.log(" \u2139 Cursor transcript import requires local storage mode \u2014 skipped under cloud storage.\n");
|
|
9191
9236
|
}
|
|
9192
9237
|
}
|
|
9193
9238
|
}
|
|
@@ -9551,6 +9596,80 @@ function extractTextContent(content) {
|
|
|
9551
9596
|
}
|
|
9552
9597
|
return "";
|
|
9553
9598
|
}
|
|
9599
|
+
function cursorProjectSlug(workspaceRoot) {
|
|
9600
|
+
return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9601
|
+
}
|
|
9602
|
+
function getCursorTranscriptsDir() {
|
|
9603
|
+
const dir = join8(homedir8(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
|
|
9604
|
+
return existsSync10(dir) ? dir : null;
|
|
9605
|
+
}
|
|
9606
|
+
function isSafeConvId(id) {
|
|
9607
|
+
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
9608
|
+
}
|
|
9609
|
+
function parseCursorTranscriptFile(filePath) {
|
|
9610
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
9611
|
+
const lines = content.split("\n").filter(Boolean);
|
|
9612
|
+
const messages = [];
|
|
9613
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9614
|
+
try {
|
|
9615
|
+
const entry = JSON.parse(lines[i]);
|
|
9616
|
+
const role = entry.role || entry.message?.role;
|
|
9617
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
9618
|
+
const text = extractTextContent(entry.message?.content ?? entry.content);
|
|
9619
|
+
if (!text) continue;
|
|
9620
|
+
messages.push({ message_index: i, type: role, content: text });
|
|
9621
|
+
} catch {
|
|
9622
|
+
}
|
|
9623
|
+
}
|
|
9624
|
+
return messages;
|
|
9625
|
+
}
|
|
9626
|
+
async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
9627
|
+
const dir = getCursorTranscriptsDir();
|
|
9628
|
+
if (!dir) return { sessions: 0, messages: 0 };
|
|
9629
|
+
let convDirs = [];
|
|
9630
|
+
try {
|
|
9631
|
+
convDirs = readdirSync3(dir, { withFileTypes: true }).filter((d) => d.isDirectory() && isSafeConvId(d.name)).map((d) => d.name);
|
|
9632
|
+
} catch {
|
|
9633
|
+
return { sessions: 0, messages: 0 };
|
|
9634
|
+
}
|
|
9635
|
+
if (convDirs.length === 0) return { sessions: 0, messages: 0 };
|
|
9636
|
+
console.log(` Found ${convDirs.length} Cursor session transcripts, importing + embedding...`);
|
|
9637
|
+
let totalSessions = 0;
|
|
9638
|
+
let totalMessages = 0;
|
|
9639
|
+
for (let i = 0; i < convDirs.length; i++) {
|
|
9640
|
+
const convId = convDirs[i];
|
|
9641
|
+
if (!isSafeConvId(convId)) continue;
|
|
9642
|
+
const filePath = join8(dir, convId, `${convId}.jsonl`);
|
|
9643
|
+
if (!existsSync10(filePath)) continue;
|
|
9644
|
+
try {
|
|
9645
|
+
const all = parseCursorTranscriptFile(filePath);
|
|
9646
|
+
const messages = all.length > 500 ? all.slice(-500) : all;
|
|
9647
|
+
if (messages.length === 0) continue;
|
|
9648
|
+
const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {
|
|
9649
|
+
method: "POST",
|
|
9650
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
|
|
9651
|
+
body: JSON.stringify({ session_id: convId, repo, messages }),
|
|
9652
|
+
signal: AbortSignal.timeout(15e3)
|
|
9653
|
+
});
|
|
9654
|
+
if (resp.ok) {
|
|
9655
|
+
const result = await resp.json();
|
|
9656
|
+
totalSessions++;
|
|
9657
|
+
totalMessages += result.ingested ?? messages.length;
|
|
9658
|
+
}
|
|
9659
|
+
} catch {
|
|
9660
|
+
}
|
|
9661
|
+
if ((i + 1) % 10 === 0 || i === convDirs.length - 1) {
|
|
9662
|
+
process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
|
|
9663
|
+
}
|
|
9664
|
+
try {
|
|
9665
|
+
const lc = readFileSync8(filePath, "utf-8").split("\n").filter(Boolean).length;
|
|
9666
|
+
writeFileSync7(join8(OFFSETS_DIR, convId), String(lc), "utf-8");
|
|
9667
|
+
} catch {
|
|
9668
|
+
}
|
|
9669
|
+
}
|
|
9670
|
+
if (totalSessions > 0) process.stdout.write("\n");
|
|
9671
|
+
return { sessions: totalSessions, messages: totalMessages };
|
|
9672
|
+
}
|
|
9554
9673
|
function parseTranscriptFile(filePath) {
|
|
9555
9674
|
const content = readFileSync8(filePath, "utf-8");
|
|
9556
9675
|
const lines = content.split("\n").filter(Boolean);
|
|
@@ -11717,7 +11836,7 @@ var args = process.argv.slice(2);
|
|
|
11717
11836
|
var cmd = args[0] || "";
|
|
11718
11837
|
var subArgs = args.slice(1);
|
|
11719
11838
|
function printVersion() {
|
|
11720
|
-
console.log("1.6.
|
|
11839
|
+
console.log("1.6.49");
|
|
11721
11840
|
}
|
|
11722
11841
|
function printHelp2() {
|
|
11723
11842
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|