@synkro-sh/cli 1.4.7 → 1.4.9
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 +705 -186
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -63,6 +63,18 @@ function detectAgents() {
|
|
|
63
63
|
version: codexBinary ? getVersion("codex") : void 0
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
+
const cursorBinary = which("cursor");
|
|
67
|
+
const cursorConfigDir = join(home, ".cursor");
|
|
68
|
+
if (cursorBinary || existsSync(cursorConfigDir)) {
|
|
69
|
+
agents.push({
|
|
70
|
+
kind: "cursor",
|
|
71
|
+
name: "Cursor",
|
|
72
|
+
binaryPath: cursorBinary,
|
|
73
|
+
configDir: cursorConfigDir,
|
|
74
|
+
settingsPath: join(cursorConfigDir, "hooks.json"),
|
|
75
|
+
version: cursorBinary ? getVersion("cursor") : void 0
|
|
76
|
+
});
|
|
77
|
+
}
|
|
66
78
|
return agents;
|
|
67
79
|
}
|
|
68
80
|
var init_agentDetect = __esm({
|
|
@@ -239,48 +251,156 @@ var init_ccHookConfig = __esm({
|
|
|
239
251
|
}
|
|
240
252
|
});
|
|
241
253
|
|
|
242
|
-
// cli/installer/
|
|
254
|
+
// cli/installer/cursorHookConfig.ts
|
|
243
255
|
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync as renameSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
256
|
+
import { dirname as dirname2 } from "path";
|
|
257
|
+
function readHooksFile(path) {
|
|
258
|
+
if (!existsSync3(path)) return { version: 1, hooks: {} };
|
|
259
|
+
try {
|
|
260
|
+
const raw = readFileSync2(path, "utf-8");
|
|
261
|
+
return JSON.parse(raw);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
throw new Error(`Failed to parse ${path}: ${err.message}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function writeHooksFileAtomic(path, data) {
|
|
267
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
268
|
+
const tmpPath = `${path}.synkro.tmp`;
|
|
269
|
+
writeFileSync2(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
270
|
+
renameSync2(tmpPath, path);
|
|
271
|
+
}
|
|
272
|
+
function isSynkroEntry2(entry) {
|
|
273
|
+
if (entry?.[SYNKRO_MARKER2]) return true;
|
|
274
|
+
return typeof entry?.command === "string" && entry.command.includes("/.synkro/hooks/");
|
|
275
|
+
}
|
|
276
|
+
function removeSynkroEntries2(hooks, event) {
|
|
277
|
+
if (!hooks) return;
|
|
278
|
+
const arr = hooks[event];
|
|
279
|
+
if (!Array.isArray(arr)) return;
|
|
280
|
+
hooks[event] = arr.filter((entry) => !isSynkroEntry2(entry));
|
|
281
|
+
}
|
|
282
|
+
function installCursorHooks(hooksJsonPath, config) {
|
|
283
|
+
const file = readHooksFile(hooksJsonPath);
|
|
284
|
+
file.version = file.version ?? 1;
|
|
285
|
+
file.hooks = file.hooks ?? {};
|
|
286
|
+
const events = ["beforeShellExecution", "preToolUse", "afterFileEdit", "postToolUse"];
|
|
287
|
+
for (const evt of events) {
|
|
288
|
+
removeSynkroEntries2(file.hooks, evt);
|
|
289
|
+
}
|
|
290
|
+
file.hooks.beforeShellExecution = file.hooks.beforeShellExecution ?? [];
|
|
291
|
+
file.hooks.beforeShellExecution.push({
|
|
292
|
+
command: config.bashJudgeScriptPath,
|
|
293
|
+
timeout: 10,
|
|
294
|
+
failClosed: true,
|
|
295
|
+
[SYNKRO_MARKER2]: true
|
|
296
|
+
});
|
|
297
|
+
file.hooks.preToolUse = file.hooks.preToolUse ?? [];
|
|
298
|
+
file.hooks.preToolUse.push({
|
|
299
|
+
command: config.editPrecheckScriptPath,
|
|
300
|
+
timeout: 15,
|
|
301
|
+
[SYNKRO_MARKER2]: true
|
|
302
|
+
});
|
|
303
|
+
file.hooks.afterFileEdit = file.hooks.afterFileEdit ?? [];
|
|
304
|
+
file.hooks.afterFileEdit.push({
|
|
305
|
+
command: config.editCaptureScriptPath,
|
|
306
|
+
timeout: 15,
|
|
307
|
+
[SYNKRO_MARKER2]: true
|
|
308
|
+
});
|
|
309
|
+
file.hooks.postToolUse = file.hooks.postToolUse ?? [];
|
|
310
|
+
file.hooks.postToolUse.push({
|
|
311
|
+
command: config.bashFollowupScriptPath,
|
|
312
|
+
timeout: 10,
|
|
313
|
+
[SYNKRO_MARKER2]: true
|
|
314
|
+
});
|
|
315
|
+
writeHooksFileAtomic(hooksJsonPath, file);
|
|
316
|
+
}
|
|
317
|
+
function uninstallCursorHooks(hooksJsonPath) {
|
|
318
|
+
if (!existsSync3(hooksJsonPath)) return false;
|
|
319
|
+
const file = readHooksFile(hooksJsonPath);
|
|
320
|
+
if (!file.hooks) return false;
|
|
321
|
+
const events = ["beforeShellExecution", "preToolUse", "afterFileEdit", "postToolUse"];
|
|
322
|
+
for (const evt of events) {
|
|
323
|
+
removeSynkroEntries2(file.hooks, evt);
|
|
324
|
+
}
|
|
325
|
+
for (const evt of events) {
|
|
326
|
+
if (Array.isArray(file.hooks[evt]) && file.hooks[evt].length === 0) {
|
|
327
|
+
delete file.hooks[evt];
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (Object.keys(file.hooks).length === 0) {
|
|
331
|
+
delete file.hooks;
|
|
332
|
+
}
|
|
333
|
+
writeHooksFileAtomic(hooksJsonPath, file);
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
function inspectCursorHooks(hooksJsonPath) {
|
|
337
|
+
if (!existsSync3(hooksJsonPath)) {
|
|
338
|
+
return { installed: false, beforeShellExecution: false, preToolUse: false, afterFileEdit: false, postToolUse: false };
|
|
339
|
+
}
|
|
340
|
+
const file = readHooksFile(hooksJsonPath);
|
|
341
|
+
const h = file.hooks ?? {};
|
|
342
|
+
const beforeShellExecution = (h.beforeShellExecution ?? []).some((e) => isSynkroEntry2(e));
|
|
343
|
+
const preToolUse = (h.preToolUse ?? []).some((e) => isSynkroEntry2(e));
|
|
344
|
+
const afterFileEdit = (h.afterFileEdit ?? []).some((e) => isSynkroEntry2(e));
|
|
345
|
+
const postToolUse = (h.postToolUse ?? []).some((e) => isSynkroEntry2(e));
|
|
346
|
+
return {
|
|
347
|
+
installed: beforeShellExecution || preToolUse || afterFileEdit || postToolUse,
|
|
348
|
+
beforeShellExecution,
|
|
349
|
+
preToolUse,
|
|
350
|
+
afterFileEdit,
|
|
351
|
+
postToolUse
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
var SYNKRO_MARKER2;
|
|
355
|
+
var init_cursorHookConfig = __esm({
|
|
356
|
+
"cli/installer/cursorHookConfig.ts"() {
|
|
357
|
+
"use strict";
|
|
358
|
+
SYNKRO_MARKER2 = "__synkro_managed__";
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// cli/installer/mcpConfig.ts
|
|
363
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, renameSync as renameSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
244
364
|
import { homedir as homedir2 } from "os";
|
|
245
|
-
import { dirname as
|
|
365
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
246
366
|
function readClaudeJson() {
|
|
247
|
-
if (!
|
|
367
|
+
if (!existsSync4(CC_CONFIG_PATH)) return {};
|
|
248
368
|
try {
|
|
249
|
-
const raw =
|
|
369
|
+
const raw = readFileSync3(CC_CONFIG_PATH, "utf-8");
|
|
250
370
|
return JSON.parse(raw);
|
|
251
371
|
} catch (err) {
|
|
252
372
|
throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${err.message}`);
|
|
253
373
|
}
|
|
254
374
|
}
|
|
255
375
|
function writeClaudeJsonAtomic(config) {
|
|
256
|
-
|
|
376
|
+
mkdirSync3(dirname3(CC_CONFIG_PATH), { recursive: true });
|
|
257
377
|
const tmpPath = `${CC_CONFIG_PATH}.synkro.tmp`;
|
|
258
|
-
|
|
259
|
-
|
|
378
|
+
writeFileSync3(tmpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
379
|
+
renameSync3(tmpPath, CC_CONFIG_PATH);
|
|
260
380
|
}
|
|
261
381
|
function installMcpConfig(opts) {
|
|
262
382
|
const config = readClaudeJson();
|
|
263
383
|
config.mcpServers = config.mcpServers ?? {};
|
|
264
384
|
for (const [name, entry] of Object.entries(config.mcpServers)) {
|
|
265
|
-
if (entry?.[
|
|
385
|
+
if (entry?.[SYNKRO_MARKER3] === true) delete config.mcpServers[name];
|
|
266
386
|
}
|
|
267
387
|
const url = `${opts.gatewayUrl.replace(/\/$/, "")}/api/v1/mcp/guardrails`;
|
|
268
388
|
config.mcpServers[SYNKRO_SERVER_NAME] = {
|
|
269
389
|
type: "http",
|
|
270
390
|
url,
|
|
271
391
|
headers: { Authorization: `Bearer ${opts.bearerToken}` },
|
|
272
|
-
[
|
|
392
|
+
[SYNKRO_MARKER3]: true
|
|
273
393
|
};
|
|
274
394
|
writeClaudeJsonAtomic(config);
|
|
275
395
|
return { path: CC_CONFIG_PATH, url };
|
|
276
396
|
}
|
|
277
397
|
function uninstallMcpConfig() {
|
|
278
|
-
if (!
|
|
398
|
+
if (!existsSync4(CC_CONFIG_PATH)) return false;
|
|
279
399
|
const config = readClaudeJson();
|
|
280
400
|
if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;
|
|
281
401
|
let removed = false;
|
|
282
402
|
for (const [name, entry] of Object.entries(config.mcpServers)) {
|
|
283
|
-
if (entry?.[
|
|
403
|
+
if (entry?.[SYNKRO_MARKER3] === true) {
|
|
284
404
|
delete config.mcpServers[name];
|
|
285
405
|
removed = true;
|
|
286
406
|
}
|
|
@@ -291,28 +411,28 @@ function uninstallMcpConfig() {
|
|
|
291
411
|
return true;
|
|
292
412
|
}
|
|
293
413
|
function inspectMcpConfig() {
|
|
294
|
-
if (!
|
|
414
|
+
if (!existsSync4(CC_CONFIG_PATH)) {
|
|
295
415
|
return { installed: false, configPath: CC_CONFIG_PATH };
|
|
296
416
|
}
|
|
297
417
|
const config = readClaudeJson();
|
|
298
418
|
const entry = config.mcpServers?.[SYNKRO_SERVER_NAME];
|
|
299
|
-
if (!entry || entry[
|
|
419
|
+
if (!entry || entry[SYNKRO_MARKER3] !== true) {
|
|
300
420
|
return { installed: false, configPath: CC_CONFIG_PATH };
|
|
301
421
|
}
|
|
302
422
|
return { installed: true, configPath: CC_CONFIG_PATH, url: entry.url };
|
|
303
423
|
}
|
|
304
|
-
var
|
|
424
|
+
var SYNKRO_MARKER3, SYNKRO_SERVER_NAME, CC_CONFIG_PATH;
|
|
305
425
|
var init_mcpConfig = __esm({
|
|
306
426
|
"cli/installer/mcpConfig.ts"() {
|
|
307
427
|
"use strict";
|
|
308
|
-
|
|
428
|
+
SYNKRO_MARKER3 = "__synkro_managed__";
|
|
309
429
|
SYNKRO_SERVER_NAME = "synkro-guardrails";
|
|
310
430
|
CC_CONFIG_PATH = join2(homedir2(), ".claude.json");
|
|
311
431
|
}
|
|
312
432
|
});
|
|
313
433
|
|
|
314
434
|
// cli/installer/hookScripts.ts
|
|
315
|
-
var CC_BASH_JUDGE_SCRIPT, CC_EDIT_PRECHECK_SCRIPT, CC_EDIT_CAPTURE_SCRIPT, CC_STOP_SUMMARY_SCRIPT, CC_SESSION_START_SCRIPT, CC_BASH_FOLLOWUP_SCRIPT, CC_TRANSCRIPT_SYNC_SCRIPT;
|
|
435
|
+
var CC_BASH_JUDGE_SCRIPT, CC_EDIT_PRECHECK_SCRIPT, CC_EDIT_CAPTURE_SCRIPT, CC_STOP_SUMMARY_SCRIPT, CC_SESSION_START_SCRIPT, CC_BASH_FOLLOWUP_SCRIPT, CC_TRANSCRIPT_SYNC_SCRIPT, SYNKRO_COMMON_SCRIPT, CURSOR_BASH_JUDGE_SCRIPT, CURSOR_EDIT_PRECHECK_SCRIPT, CURSOR_EDIT_CAPTURE_SCRIPT, CURSOR_BASH_FOLLOWUP_SCRIPT;
|
|
316
436
|
var init_hookScripts = __esm({
|
|
317
437
|
"cli/installer/hookScripts.ts"() {
|
|
318
438
|
"use strict";
|
|
@@ -589,25 +709,12 @@ ensure_fresh_jwt() {
|
|
|
589
709
|
|
|
590
710
|
ensure_fresh_jwt
|
|
591
711
|
|
|
592
|
-
# Resolve tier
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
SYNKRO_CAPTURE_DEPTH=""
|
|
597
|
-
if find "$TIER_CACHE_FILE" -mmin -60 2>/dev/null | grep -q .; then
|
|
598
|
-
SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null)
|
|
599
|
-
fi
|
|
600
|
-
if [ -z "$SYNKRO_INFERENCE_TIER" ] || [ -z "$SYNKRO_CAPTURE_DEPTH" ]; then
|
|
601
|
-
ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null || echo "")
|
|
602
|
-
if [ -n "$ME_RESP" ]; then
|
|
603
|
-
SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
|
|
604
|
-
[ -n "$SYNKRO_INFERENCE_TIER" ] && printf '%s' "$SYNKRO_INFERENCE_TIER" > "$TIER_CACHE_FILE" 2>/dev/null || true
|
|
605
|
-
SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
|
|
606
|
-
[ -n "$SYNKRO_CAPTURE_DEPTH" ] && printf '%s' "$SYNKRO_CAPTURE_DEPTH" > "$CD_CACHE_FILE" 2>/dev/null || true
|
|
607
|
-
fi
|
|
608
|
-
fi
|
|
712
|
+
# Resolve tier + capture_depth live from /cli/me every call. Default to local_only (fail-safe).
|
|
713
|
+
ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null || echo "")
|
|
714
|
+
SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
|
|
715
|
+
SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
|
|
609
716
|
SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
|
|
610
|
-
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-
|
|
717
|
+
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-local_only}"
|
|
611
718
|
|
|
612
719
|
USE_LOCAL=false
|
|
613
720
|
if command -v claude >/dev/null 2>&1; then
|
|
@@ -1110,25 +1217,12 @@ ensure_fresh_jwt() {
|
|
|
1110
1217
|
ensure_fresh_jwt
|
|
1111
1218
|
|
|
1112
1219
|
|
|
1113
|
-
# Resolve tier
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
SYNKRO_CAPTURE_DEPTH=""
|
|
1118
|
-
if find "$TIER_CACHE_FILE" -mmin -60 2>/dev/null | grep -q .; then
|
|
1119
|
-
SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null)
|
|
1120
|
-
fi
|
|
1121
|
-
if [ -z "$SYNKRO_INFERENCE_TIER" ] || [ -z "$SYNKRO_CAPTURE_DEPTH" ]; then
|
|
1122
|
-
ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null || echo "")
|
|
1123
|
-
if [ -n "$ME_RESP" ]; then
|
|
1124
|
-
SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
|
|
1125
|
-
[ -n "$SYNKRO_INFERENCE_TIER" ] && printf '%s' "$SYNKRO_INFERENCE_TIER" > "$TIER_CACHE_FILE" 2>/dev/null || true
|
|
1126
|
-
SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
|
|
1127
|
-
[ -n "$SYNKRO_CAPTURE_DEPTH" ] && printf '%s' "$SYNKRO_CAPTURE_DEPTH" > "$CD_CACHE_FILE" 2>/dev/null || true
|
|
1128
|
-
fi
|
|
1129
|
-
fi
|
|
1220
|
+
# Resolve tier + capture_depth live from /cli/me every call. Default to local_only (fail-safe).
|
|
1221
|
+
ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null || echo "")
|
|
1222
|
+
SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
|
|
1223
|
+
SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
|
|
1130
1224
|
SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
|
|
1131
|
-
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-
|
|
1225
|
+
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-local_only}"
|
|
1132
1226
|
|
|
1133
1227
|
if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; }; then
|
|
1134
1228
|
# \u2500\u2500\u2500 LOCAL PATH: channel-grader first, then \`claude --print\` cold fallback. \u2500\u2500\u2500
|
|
@@ -1567,26 +1661,12 @@ if [ -n "$SESSION_ID" ] && [ -n "$TOOL_USE_ID" ]; then
|
|
|
1567
1661
|
) &
|
|
1568
1662
|
fi
|
|
1569
1663
|
|
|
1570
|
-
# Resolve tier + capture_depth
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
SYNKRO_CAPTURE_DEPTH=""
|
|
1575
|
-
if find "$TIER_CACHE_FILE" -mmin -60 2>/dev/null | grep -q .; then
|
|
1576
|
-
SYNKRO_INFERENCE_TIER=$(cat "$TIER_CACHE_FILE" 2>/dev/null || true)
|
|
1577
|
-
SYNKRO_CAPTURE_DEPTH=$(cat "$CD_CACHE_FILE" 2>/dev/null || true)
|
|
1578
|
-
fi
|
|
1579
|
-
if [ -z "$SYNKRO_INFERENCE_TIER" ] || [ -z "$SYNKRO_CAPTURE_DEPTH" ]; then
|
|
1580
|
-
ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 2 2>/dev/null || echo "")
|
|
1581
|
-
if [ -n "$ME_RESP" ]; then
|
|
1582
|
-
SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null || true)
|
|
1583
|
-
[ -n "$SYNKRO_INFERENCE_TIER" ] && printf '%s' "$SYNKRO_INFERENCE_TIER" > "$TIER_CACHE_FILE" 2>/dev/null || true
|
|
1584
|
-
SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null || true)
|
|
1585
|
-
[ -n "$SYNKRO_CAPTURE_DEPTH" ] && printf '%s' "$SYNKRO_CAPTURE_DEPTH" > "$CD_CACHE_FILE" 2>/dev/null || true
|
|
1586
|
-
fi
|
|
1587
|
-
fi
|
|
1664
|
+
# Resolve tier + capture_depth live from /cli/me every call. Default to local_only (fail-safe).
|
|
1665
|
+
ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null || echo "")
|
|
1666
|
+
SYNKRO_INFERENCE_TIER=$(echo "$ME_RESP" | jq -r '.tier // empty' 2>/dev/null)
|
|
1667
|
+
SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
|
|
1588
1668
|
SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
|
|
1589
|
-
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-
|
|
1669
|
+
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-local_only}"
|
|
1590
1670
|
|
|
1591
1671
|
if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; }; then
|
|
1592
1672
|
# \u2500\u2500\u2500 LOCAL PATH: channel-grader first, then \`claude --print\` cold fallback. \u2500\u2500\u2500
|
|
@@ -1647,8 +1727,10 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
|
|
|
1647
1727
|
# Wait for CVE scan
|
|
1648
1728
|
wait $CVE_PID 2>/dev/null
|
|
1649
1729
|
CVE_TEXT=""
|
|
1730
|
+
CVE_FINDINGS_JSON="[]"
|
|
1650
1731
|
if [ -s "$CVE_RESULT_FILE" ]; then
|
|
1651
1732
|
CVE_TEXT=$(jq -r '.summary // empty' "$CVE_RESULT_FILE" 2>/dev/null || echo "")
|
|
1733
|
+
CVE_FINDINGS_JSON=$(jq -c '[.findings[]? | {package: .package, version: .version, cve: .id, severity: .severity, score: .score}]' "$CVE_RESULT_FILE" 2>/dev/null || echo "[]")
|
|
1652
1734
|
fi
|
|
1653
1735
|
|
|
1654
1736
|
# Wrapper extraction (greedy \u2014 tolerates nested XML tags).
|
|
@@ -1766,6 +1848,7 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
|
1766
1848
|
--arg session_id "$SESSION_ID" \\
|
|
1767
1849
|
--arg mech_cat "$MECH_CAT" \\
|
|
1768
1850
|
--arg biz_cat "$BIZ_CAT" \\
|
|
1851
|
+
--argjson cve_findings "\${CVE_FINDINGS_JSON:-[]}" \\
|
|
1769
1852
|
'{
|
|
1770
1853
|
event_id: $event_id, timestamp: $timestamp, hook_type: $hook_type,
|
|
1771
1854
|
verdict: $verdict, severity: $severity, risk_level: $risk_level,
|
|
@@ -1773,7 +1856,8 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
|
1773
1856
|
} + (if $repo != "" then {repo: $repo} else {} end)
|
|
1774
1857
|
+ (if $session_id != "" then {session_id: $session_id} else {} end)
|
|
1775
1858
|
+ (if $mech_cat != "" then {mechanism_category: $mech_cat} else {} end)
|
|
1776
|
-
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)
|
|
1859
|
+
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)
|
|
1860
|
+
+ (if ($cve_findings | length) > 0 then {cve_findings: $cve_findings} else {} end)')
|
|
1777
1861
|
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
1778
1862
|
-H "Content-Type: application/json" \\
|
|
1779
1863
|
-H "Authorization: Bearer $JWT" \\
|
|
@@ -2067,14 +2151,15 @@ CREDS_PATH="\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}"
|
|
|
2067
2151
|
if [ ! -f "$CREDS_PATH" ]; then echo '{}'; exit 0; fi
|
|
2068
2152
|
if [ "\${SYNKRO_TRANSCRIPT_CONSENT:-yes}" = "no" ]; then echo '{}'; exit 0; fi
|
|
2069
2153
|
|
|
2070
|
-
# Hard-skip in local_only privacy mode \u2014 conversation content must never leave the device.
|
|
2071
|
-
CD_CACHE_FILE="$HOME/.synkro/.tier-cache-\${SYNKRO_USER_ID:-default}.cd"
|
|
2072
|
-
SYNKRO_CAPTURE_DEPTH=$(cat "$CD_CACHE_FILE" 2>/dev/null || echo "full")
|
|
2073
|
-
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then echo '{}'; exit 0; fi
|
|
2074
|
-
|
|
2075
2154
|
JWT=$(jq -r '.access_token // empty' "$CREDS_PATH" 2>/dev/null)
|
|
2076
2155
|
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2077
2156
|
|
|
2157
|
+
# Hard-skip in local_only privacy mode \u2014 conversation content must never leave the device.
|
|
2158
|
+
ME_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/me" -H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null || echo "")
|
|
2159
|
+
SYNKRO_CAPTURE_DEPTH=$(echo "$ME_RESP" | jq -r '.capture_depth // empty' 2>/dev/null)
|
|
2160
|
+
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-local_only}"
|
|
2161
|
+
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then echo '{}'; exit 0; fi
|
|
2162
|
+
|
|
2078
2163
|
PAYLOAD=$(cat)
|
|
2079
2164
|
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
|
|
2080
2165
|
TRANSCRIPT_PATH=$(echo "$PAYLOAD" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
@@ -2182,6 +2267,386 @@ disown 2>/dev/null || true
|
|
|
2182
2267
|
# Update offset
|
|
2183
2268
|
printf '%s' "$TOTAL_LINES" > "$OFFSET_FILE" 2>/dev/null || true
|
|
2184
2269
|
|
|
2270
|
+
echo '{}'
|
|
2271
|
+
exit 0
|
|
2272
|
+
`;
|
|
2273
|
+
SYNKRO_COMMON_SCRIPT = `#!/bin/bash
|
|
2274
|
+
# Shared Synkro hook utilities \u2014 sourced by IDE-specific adapter scripts.
|
|
2275
|
+
# Provides: auth, JWT refresh, config loading, API helpers, git detection.
|
|
2276
|
+
|
|
2277
|
+
synkro_log() { echo "[synkro] $1" >&2; }
|
|
2278
|
+
|
|
2279
|
+
synkro_channel_up() {
|
|
2280
|
+
(exec 3<>/dev/tcp/127.0.0.1/\${SYNKRO_CHANNEL_PORT:-8929}) 2>/dev/null && exec 3<&- 3>&-
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
# Load config
|
|
2284
|
+
_SYNKRO_CONFIG="$HOME/.synkro/config.env"
|
|
2285
|
+
if [ -f "$_SYNKRO_CONFIG" ]; then
|
|
2286
|
+
set -a
|
|
2287
|
+
# shellcheck disable=SC1090
|
|
2288
|
+
. "$_SYNKRO_CONFIG"
|
|
2289
|
+
set +a
|
|
2290
|
+
fi
|
|
2291
|
+
|
|
2292
|
+
GATEWAY_URL="\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}"
|
|
2293
|
+
CREDS_PATH="\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}"
|
|
2294
|
+
|
|
2295
|
+
synkro_load_jwt() {
|
|
2296
|
+
if [ ! -f "$CREDS_PATH" ]; then
|
|
2297
|
+
echo ""
|
|
2298
|
+
return 1
|
|
2299
|
+
fi
|
|
2300
|
+
jq -r '.access_token // empty' "$CREDS_PATH" 2>/dev/null
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
synkro_refresh_jwt() {
|
|
2304
|
+
local refresh_token
|
|
2305
|
+
refresh_token=$(jq -r '.refresh_token // empty' "$CREDS_PATH" 2>/dev/null)
|
|
2306
|
+
if [ -z "$refresh_token" ]; then return 1; fi
|
|
2307
|
+
local refresh_body
|
|
2308
|
+
refresh_body=$(jq -n --arg rt "$refresh_token" '{refresh_token:$rt}')
|
|
2309
|
+
local refresh_resp
|
|
2310
|
+
refresh_resp=$(curl -sS -X POST "\${GATEWAY_URL}/api/auth/refresh" \\
|
|
2311
|
+
-H "Content-Type: application/json" \\
|
|
2312
|
+
-d "$refresh_body" \\
|
|
2313
|
+
--max-time 4 2>/dev/null)
|
|
2314
|
+
local new_access
|
|
2315
|
+
new_access=$(echo "$refresh_resp" | jq -r '.access_token // empty' 2>/dev/null)
|
|
2316
|
+
if [ -z "$new_access" ]; then return 1; fi
|
|
2317
|
+
local new_refresh
|
|
2318
|
+
new_refresh=$(echo "$refresh_resp" | jq -r '.refresh_token // empty' 2>/dev/null)
|
|
2319
|
+
if [ -z "$new_refresh" ]; then new_refresh="$refresh_token"; fi
|
|
2320
|
+
local tmp="\${CREDS_PATH}.synkro.tmp"
|
|
2321
|
+
jq --arg at "$new_access" --arg rt "$new_refresh" \\
|
|
2322
|
+
'. + {access_token: $at, refresh_token: $rt}' \\
|
|
2323
|
+
"$CREDS_PATH" > "$tmp" 2>/dev/null && mv "$tmp" "$CREDS_PATH"
|
|
2324
|
+
JWT="$new_access"
|
|
2325
|
+
return 0
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
synkro_ensure_fresh_jwt() {
|
|
2329
|
+
[ -z "$JWT" ] && return 1
|
|
2330
|
+
local payload exp now remaining
|
|
2331
|
+
payload=$(printf '%s' "$JWT" | cut -d. -f2)
|
|
2332
|
+
case $((\${#payload} % 4)) in
|
|
2333
|
+
2) payload="\${payload}==" ;;
|
|
2334
|
+
3) payload="\${payload}=" ;;
|
|
2335
|
+
esac
|
|
2336
|
+
exp=$(printf '%s' "$payload" | tr '_-' '/+' | base64 -D 2>/dev/null | jq -r '.exp // 0' 2>/dev/null)
|
|
2337
|
+
now=$(date -u +%s)
|
|
2338
|
+
remaining=$((exp - now))
|
|
2339
|
+
if [ "$remaining" -lt 60 ]; then
|
|
2340
|
+
synkro_refresh_jwt
|
|
2341
|
+
fi
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
synkro_detect_repo() {
|
|
2345
|
+
local cwd="\${1:-.}"
|
|
2346
|
+
if command -v git >/dev/null 2>&1; then
|
|
2347
|
+
local remote
|
|
2348
|
+
remote=$(git -C "$cwd" remote get-url origin 2>/dev/null || true)
|
|
2349
|
+
if [ -n "$remote" ]; then
|
|
2350
|
+
echo "$remote" | sed -E 's|^git@[^:]+:||; s|^https?://[^/]+/||; s|\\.git$||'
|
|
2351
|
+
return
|
|
2352
|
+
fi
|
|
2353
|
+
fi
|
|
2354
|
+
echo ""
|
|
2355
|
+
}
|
|
2356
|
+
`;
|
|
2357
|
+
CURSOR_BASH_JUDGE_SCRIPT = `#!/bin/bash
|
|
2358
|
+
# Synkro beforeShellExecution hook for Cursor.
|
|
2359
|
+
# Reads Cursor's stdin payload, judges via Synkro gateway, returns Cursor-format verdict.
|
|
2360
|
+
|
|
2361
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2362
|
+
# shellcheck disable=SC1091
|
|
2363
|
+
. "$SCRIPT_DIR/_synkro-common.sh"
|
|
2364
|
+
|
|
2365
|
+
JWT=$(synkro_load_jwt)
|
|
2366
|
+
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2367
|
+
synkro_ensure_fresh_jwt
|
|
2368
|
+
|
|
2369
|
+
PAYLOAD=$(cat)
|
|
2370
|
+
if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
|
|
2371
|
+
|
|
2372
|
+
COMMAND=$(echo "$PAYLOAD" | jq -r '.command // empty' 2>/dev/null)
|
|
2373
|
+
if [ -z "$COMMAND" ]; then echo '{}'; exit 0; fi
|
|
2374
|
+
|
|
2375
|
+
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
|
|
2376
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.conversation_id // empty' 2>/dev/null)
|
|
2377
|
+
GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
|
|
2378
|
+
|
|
2379
|
+
CMD_SHORT=$(printf '%s' "$COMMAND" | head -c 80)
|
|
2380
|
+
synkro_log "bashGuard checking: $CMD_SHORT"
|
|
2381
|
+
|
|
2382
|
+
TOOL_INPUT=$(jq -n --arg cmd "$COMMAND" '{command: $cmd}')
|
|
2383
|
+
|
|
2384
|
+
BODY=$(jq -n \\
|
|
2385
|
+
--argjson tool_input "$TOOL_INPUT" \\
|
|
2386
|
+
--arg session_id "$SESSION_ID" \\
|
|
2387
|
+
--arg cwd "$CWD" \\
|
|
2388
|
+
--arg repo "$GIT_REPO" \\
|
|
2389
|
+
'{
|
|
2390
|
+
kind: "bash_judge",
|
|
2391
|
+
tool_input: $tool_input,
|
|
2392
|
+
user_intent: null,
|
|
2393
|
+
recent_user_messages: [],
|
|
2394
|
+
recent_messages: [],
|
|
2395
|
+
recent_actions: [],
|
|
2396
|
+
session_id: (if ($session_id | length) > 0 then $session_id else null end),
|
|
2397
|
+
cwd: (if ($cwd | length) > 0 then $cwd else null end),
|
|
2398
|
+
repo: (if ($repo | length) > 0 then $repo else null end),
|
|
2399
|
+
ide: "cursor"
|
|
2400
|
+
}')
|
|
2401
|
+
|
|
2402
|
+
VERDICT=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/judge" \\
|
|
2403
|
+
-H "Content-Type: application/json" \\
|
|
2404
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2405
|
+
-d "$BODY" \\
|
|
2406
|
+
--max-time 6 2>/dev/null || echo "")
|
|
2407
|
+
|
|
2408
|
+
if echo "$VERDICT" | grep -qE '"detail":"Token has expired|"detail":"Invalid or expired token'; then
|
|
2409
|
+
if synkro_refresh_jwt; then
|
|
2410
|
+
VERDICT=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/judge" \\
|
|
2411
|
+
-H "Content-Type: application/json" \\
|
|
2412
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2413
|
+
-d "$BODY" \\
|
|
2414
|
+
--max-time 6 2>/dev/null || echo "")
|
|
2415
|
+
fi
|
|
2416
|
+
fi
|
|
2417
|
+
|
|
2418
|
+
if [ -z "$VERDICT" ]; then
|
|
2419
|
+
synkro_log "bashGuard $CMD_SHORT \u2192 error (timeout)"
|
|
2420
|
+
echo '{}'
|
|
2421
|
+
exit 0
|
|
2422
|
+
fi
|
|
2423
|
+
|
|
2424
|
+
SEVERITY=$(echo "$VERDICT" | jq -r '.severity // "audit"' 2>/dev/null)
|
|
2425
|
+
REASONING=$(echo "$VERDICT" | jq -r '.reasoning // ""' 2>/dev/null)
|
|
2426
|
+
ALTERNATIVE=$(echo "$VERDICT" | jq -r '.alternative // ""' 2>/dev/null)
|
|
2427
|
+
CATEGORY=$(echo "$VERDICT" | jq -r '.category // ""' 2>/dev/null)
|
|
2428
|
+
VERDICT_KIND=$(echo "$VERDICT" | jq -r '.verdict // "warn"' 2>/dev/null)
|
|
2429
|
+
|
|
2430
|
+
case "$SEVERITY" in
|
|
2431
|
+
block|audit) ;;
|
|
2432
|
+
low|medium|high|critical)
|
|
2433
|
+
if [ "$VERDICT_KIND" = "allow" ]; then SEVERITY="audit"; else SEVERITY="block"; fi
|
|
2434
|
+
;;
|
|
2435
|
+
*)
|
|
2436
|
+
if [ "$VERDICT_KIND" = "allow" ]; then SEVERITY="audit"; else SEVERITY="block"; fi
|
|
2437
|
+
;;
|
|
2438
|
+
esac
|
|
2439
|
+
|
|
2440
|
+
ALT_SUFFIX=""
|
|
2441
|
+
if [ -n "$ALTERNATIVE" ] && [ "$ALTERNATIVE" != "null" ]; then
|
|
2442
|
+
ALT_SUFFIX=" Suggested: \${ALTERNATIVE}"
|
|
2443
|
+
fi
|
|
2444
|
+
|
|
2445
|
+
case "$SEVERITY" in
|
|
2446
|
+
block)
|
|
2447
|
+
synkro_log "bashGuard $CMD_SHORT \u2192 BLOCK: $REASONING"
|
|
2448
|
+
jq -n \\
|
|
2449
|
+
--arg user "Synkro safety judge blocked this command: \${REASONING}\${ALT_SUFFIX}" \\
|
|
2450
|
+
--arg agent "Synkro safety judge (severity: \${SEVERITY}, category: \${CATEGORY}). Reasoning: \${REASONING}.\${ALT_SUFFIX}" \\
|
|
2451
|
+
'{permission: "deny", user_message: $user, agent_message: $agent}'
|
|
2452
|
+
;;
|
|
2453
|
+
audit)
|
|
2454
|
+
synkro_log "bashGuard $CMD_SHORT \u2192 pass (\${CATEGORY})"
|
|
2455
|
+
echo '{}'
|
|
2456
|
+
;;
|
|
2457
|
+
*)
|
|
2458
|
+
synkro_log "bashGuard $CMD_SHORT \u2192 BLOCK (unexpected severity)"
|
|
2459
|
+
jq -n \\
|
|
2460
|
+
--arg user "Synkro safety judge blocked this command (unexpected severity)." \\
|
|
2461
|
+
'{permission: "deny", user_message: $user}'
|
|
2462
|
+
;;
|
|
2463
|
+
esac
|
|
2464
|
+
`;
|
|
2465
|
+
CURSOR_EDIT_PRECHECK_SCRIPT = `#!/bin/bash
|
|
2466
|
+
# Synkro preToolUse hook for Cursor \u2014 pre-check edits against org rules.
|
|
2467
|
+
# Only acts on edit-like tool names; passes through everything else.
|
|
2468
|
+
|
|
2469
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2470
|
+
# shellcheck disable=SC1091
|
|
2471
|
+
. "$SCRIPT_DIR/_synkro-common.sh"
|
|
2472
|
+
|
|
2473
|
+
JWT=$(synkro_load_jwt)
|
|
2474
|
+
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2475
|
+
synkro_ensure_fresh_jwt
|
|
2476
|
+
|
|
2477
|
+
PAYLOAD=$(cat)
|
|
2478
|
+
if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
|
|
2479
|
+
|
|
2480
|
+
TOOL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
2481
|
+
|
|
2482
|
+
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
|
|
2483
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.conversation_id // empty' 2>/dev/null)
|
|
2484
|
+
GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
|
|
2485
|
+
|
|
2486
|
+
FILE_PATH=$(echo "$PAYLOAD" | jq -r '.tool_input.file_path // .tool_input.path // .tool_input.target_file // empty' 2>/dev/null)
|
|
2487
|
+
CONTENT=$(echo "$PAYLOAD" | jq -r '.tool_input.content // .tool_input.new_string // .tool_input.code_edit // empty' 2>/dev/null)
|
|
2488
|
+
|
|
2489
|
+
# Skip non-edit tools \u2014 if there's no file path in tool_input, this isn't a file edit
|
|
2490
|
+
if [ -z "$FILE_PATH" ]; then echo '{}'; exit 0; fi
|
|
2491
|
+
if [ -z "$FILE_PATH" ]; then echo '{}'; exit 0; fi
|
|
2492
|
+
|
|
2493
|
+
BASENAME=$(basename "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")
|
|
2494
|
+
synkro_log "editGuard checking: $BASENAME"
|
|
2495
|
+
|
|
2496
|
+
BODY=$(jq -n \\
|
|
2497
|
+
--arg file_path "$FILE_PATH" \\
|
|
2498
|
+
--arg content "$CONTENT" \\
|
|
2499
|
+
--arg session_id "$SESSION_ID" \\
|
|
2500
|
+
--arg cwd "$CWD" \\
|
|
2501
|
+
--arg repo "$GIT_REPO" \\
|
|
2502
|
+
'{
|
|
2503
|
+
file_path: $file_path,
|
|
2504
|
+
content: $content,
|
|
2505
|
+
session_id: (if ($session_id | length) > 0 then $session_id else null end),
|
|
2506
|
+
cwd: (if ($cwd | length) > 0 then $cwd else null end),
|
|
2507
|
+
repo: (if ($repo | length) > 0 then $repo else null end),
|
|
2508
|
+
ide: "cursor"
|
|
2509
|
+
}')
|
|
2510
|
+
|
|
2511
|
+
RESP=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit" \\
|
|
2512
|
+
-H "Content-Type: application/json" \\
|
|
2513
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2514
|
+
-d "$BODY" \\
|
|
2515
|
+
--max-time 8 2>/dev/null || echo "")
|
|
2516
|
+
|
|
2517
|
+
if [ -z "$RESP" ]; then
|
|
2518
|
+
synkro_log "editGuard $BASENAME \u2192 error (timeout)"
|
|
2519
|
+
echo '{}'
|
|
2520
|
+
exit 0
|
|
2521
|
+
fi
|
|
2522
|
+
|
|
2523
|
+
DECISION=$(echo "$RESP" | jq -r '.hookSpecificOutput.permissionDecision // "allow"' 2>/dev/null)
|
|
2524
|
+
case "$DECISION" in
|
|
2525
|
+
deny|ask)
|
|
2526
|
+
REASON=$(echo "$RESP" | jq -r '.hookSpecificOutput.permissionDecisionReason // "Blocked by Synkro"' 2>/dev/null)
|
|
2527
|
+
synkro_log "editGuard $BASENAME \u2192 BLOCK: $REASON"
|
|
2528
|
+
jq -n --arg user "$REASON" --arg agent "$REASON" \\
|
|
2529
|
+
'{permission: "deny", user_message: $user, agent_message: $agent}'
|
|
2530
|
+
;;
|
|
2531
|
+
*)
|
|
2532
|
+
synkro_log "editGuard $BASENAME \u2192 pass"
|
|
2533
|
+
echo '{}'
|
|
2534
|
+
;;
|
|
2535
|
+
esac
|
|
2536
|
+
`;
|
|
2537
|
+
CURSOR_EDIT_CAPTURE_SCRIPT = `#!/bin/bash
|
|
2538
|
+
# Synkro afterFileEdit hook for Cursor \u2014 fire-and-forget telemetry + CVE scan.
|
|
2539
|
+
# Cannot block (Cursor afterFileEdit is observational only).
|
|
2540
|
+
|
|
2541
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2542
|
+
# shellcheck disable=SC1091
|
|
2543
|
+
. "$SCRIPT_DIR/_synkro-common.sh"
|
|
2544
|
+
|
|
2545
|
+
JWT=$(synkro_load_jwt)
|
|
2546
|
+
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2547
|
+
|
|
2548
|
+
PAYLOAD=$(cat)
|
|
2549
|
+
if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
|
|
2550
|
+
|
|
2551
|
+
FILE_PATH=$(echo "$PAYLOAD" | jq -r '.file_path // empty' 2>/dev/null)
|
|
2552
|
+
if [ -z "$FILE_PATH" ]; then echo '{}'; exit 0; fi
|
|
2553
|
+
|
|
2554
|
+
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // .workspace_roots[0] // empty' 2>/dev/null)
|
|
2555
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.conversation_id // empty' 2>/dev/null)
|
|
2556
|
+
GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
|
|
2557
|
+
BASENAME=$(basename "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")
|
|
2558
|
+
|
|
2559
|
+
# Read full file content for edit scan
|
|
2560
|
+
FULL_CONTENT=""
|
|
2561
|
+
FULL_PATH=""
|
|
2562
|
+
if [ -n "$CWD" ]; then
|
|
2563
|
+
FULL_PATH="$CWD/$FILE_PATH"
|
|
2564
|
+
else
|
|
2565
|
+
FULL_PATH="$FILE_PATH"
|
|
2566
|
+
fi
|
|
2567
|
+
if [ -f "$FULL_PATH" ]; then
|
|
2568
|
+
FULL_CONTENT=$(head -c 50000 "$FULL_PATH" 2>/dev/null || true)
|
|
2569
|
+
fi
|
|
2570
|
+
|
|
2571
|
+
# Extract deps from nearest package.json
|
|
2572
|
+
DEPS_JSON="{}"
|
|
2573
|
+
_PKG_DIR="\${CWD:-.}"
|
|
2574
|
+
while [ "$_PKG_DIR" != "/" ]; do
|
|
2575
|
+
if [ -f "$_PKG_DIR/package.json" ]; then
|
|
2576
|
+
DEPS_JSON=$(jq -c '(.dependencies // {}) + (.devDependencies // {})' "$_PKG_DIR/package.json" 2>/dev/null || echo "{}")
|
|
2577
|
+
break
|
|
2578
|
+
fi
|
|
2579
|
+
_PKG_DIR=$(dirname "$_PKG_DIR")
|
|
2580
|
+
done
|
|
2581
|
+
|
|
2582
|
+
synkro_log "editScan $BASENAME"
|
|
2583
|
+
|
|
2584
|
+
# Fire-and-forget: edit scan + CVE scan in background
|
|
2585
|
+
(
|
|
2586
|
+
BODY=$(jq -n \\
|
|
2587
|
+
--arg file_path "$FILE_PATH" \\
|
|
2588
|
+
--arg content "$FULL_CONTENT" \\
|
|
2589
|
+
--arg session_id "$SESSION_ID" \\
|
|
2590
|
+
--arg cwd "$CWD" \\
|
|
2591
|
+
--arg repo "$GIT_REPO" \\
|
|
2592
|
+
--argjson deps "$DEPS_JSON" \\
|
|
2593
|
+
'{
|
|
2594
|
+
file_path: $file_path,
|
|
2595
|
+
content: $content,
|
|
2596
|
+
dependencies: $deps,
|
|
2597
|
+
session_id: (if ($session_id | length) > 0 then $session_id else null end),
|
|
2598
|
+
cwd: (if ($cwd | length) > 0 then $cwd else null end),
|
|
2599
|
+
repo: (if ($repo | length) > 0 then $repo else null end),
|
|
2600
|
+
ide: "cursor"
|
|
2601
|
+
}')
|
|
2602
|
+
|
|
2603
|
+
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/edit-scan" \\
|
|
2604
|
+
-H "Content-Type: application/json" \\
|
|
2605
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2606
|
+
-d "$BODY" \\
|
|
2607
|
+
--max-time 10 >/dev/null 2>&1 || true
|
|
2608
|
+
) &
|
|
2609
|
+
disown 2>/dev/null || true
|
|
2610
|
+
|
|
2611
|
+
echo '{}'
|
|
2612
|
+
exit 0
|
|
2613
|
+
`;
|
|
2614
|
+
CURSOR_BASH_FOLLOWUP_SCRIPT = `#!/bin/bash
|
|
2615
|
+
# Synkro postToolUse hook for Cursor \u2014 fire-and-forget follow-up telemetry.
|
|
2616
|
+
# Marks bash judgments as "allowed" after successful execution.
|
|
2617
|
+
|
|
2618
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2619
|
+
# shellcheck disable=SC1091
|
|
2620
|
+
. "$SCRIPT_DIR/_synkro-common.sh"
|
|
2621
|
+
|
|
2622
|
+
JWT=$(synkro_load_jwt)
|
|
2623
|
+
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2624
|
+
|
|
2625
|
+
PAYLOAD=$(cat)
|
|
2626
|
+
if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
|
|
2627
|
+
|
|
2628
|
+
TOOL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
2629
|
+
case "$TOOL_NAME" in
|
|
2630
|
+
Shell|Bash|terminal|run_terminal_cmd|execute_command) ;;
|
|
2631
|
+
*) echo '{}'; exit 0 ;;
|
|
2632
|
+
esac
|
|
2633
|
+
|
|
2634
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.conversation_id // empty' 2>/dev/null)
|
|
2635
|
+
TOOL_USE_ID=$(echo "$PAYLOAD" | jq -r '.tool_use_id // empty' 2>/dev/null)
|
|
2636
|
+
|
|
2637
|
+
if [ -n "$SESSION_ID" ] && [ -n "$TOOL_USE_ID" ]; then
|
|
2638
|
+
(
|
|
2639
|
+
BODY=$(jq -n --arg sid "$SESSION_ID" --arg tid "$TOOL_USE_ID" \\
|
|
2640
|
+
'{session_id: $sid, tool_use_id: $tid, decision: "allow"}')
|
|
2641
|
+
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/bash-followup" \\
|
|
2642
|
+
-H "Content-Type: application/json" \\
|
|
2643
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2644
|
+
-d "$BODY" \\
|
|
2645
|
+
--max-time 3 >/dev/null 2>&1 || true
|
|
2646
|
+
) &
|
|
2647
|
+
disown 2>/dev/null || true
|
|
2648
|
+
fi
|
|
2649
|
+
|
|
2185
2650
|
echo '{}'
|
|
2186
2651
|
exit 0
|
|
2187
2652
|
`;
|
|
@@ -2190,9 +2655,9 @@ exit 0
|
|
|
2190
2655
|
|
|
2191
2656
|
// cli/auth/stub.ts
|
|
2192
2657
|
import { createServer } from "http";
|
|
2193
|
-
import { writeFileSync as
|
|
2658
|
+
import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
2194
2659
|
import { homedir as homedir3, platform } from "os";
|
|
2195
|
-
import { join as join3, dirname as
|
|
2660
|
+
import { join as join3, dirname as dirname4 } from "path";
|
|
2196
2661
|
import { execFile } from "child_process";
|
|
2197
2662
|
import jwt from "jsonwebtoken";
|
|
2198
2663
|
function openBrowser(url) {
|
|
@@ -2220,18 +2685,18 @@ function openBrowser(url) {
|
|
|
2220
2685
|
});
|
|
2221
2686
|
}
|
|
2222
2687
|
function saveCredentials(data) {
|
|
2223
|
-
const dir =
|
|
2224
|
-
if (!
|
|
2225
|
-
|
|
2688
|
+
const dir = dirname4(AUTH_FILE);
|
|
2689
|
+
if (!existsSync5(dir)) {
|
|
2690
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
2226
2691
|
}
|
|
2227
|
-
|
|
2692
|
+
writeFileSync4(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
|
|
2228
2693
|
}
|
|
2229
2694
|
function loadCredentials() {
|
|
2230
|
-
if (!
|
|
2695
|
+
if (!existsSync5(AUTH_FILE)) {
|
|
2231
2696
|
return null;
|
|
2232
2697
|
}
|
|
2233
2698
|
try {
|
|
2234
|
-
const content =
|
|
2699
|
+
const content = readFileSync4(AUTH_FILE, "utf8");
|
|
2235
2700
|
return JSON.parse(content);
|
|
2236
2701
|
} catch (error) {
|
|
2237
2702
|
return null;
|
|
@@ -2475,7 +2940,7 @@ async function ensureValidToken() {
|
|
|
2475
2940
|
return true;
|
|
2476
2941
|
}
|
|
2477
2942
|
function clearCredentials() {
|
|
2478
|
-
if (
|
|
2943
|
+
if (existsSync5(AUTH_FILE)) {
|
|
2479
2944
|
unlinkSync2(AUTH_FILE);
|
|
2480
2945
|
}
|
|
2481
2946
|
}
|
|
@@ -2649,7 +3114,7 @@ jobs:
|
|
|
2649
3114
|
});
|
|
2650
3115
|
|
|
2651
3116
|
// cli/installer/githubSetup.ts
|
|
2652
|
-
import { existsSync as
|
|
3117
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
2653
3118
|
import { execSync as execSync2 } from "child_process";
|
|
2654
3119
|
import { join as join4 } from "path";
|
|
2655
3120
|
function ghSecretSet(token, owner, repo, name, value) {
|
|
@@ -2698,15 +3163,15 @@ async function pushSecretsToRepo(opts, owner, repo, secrets) {
|
|
|
2698
3163
|
}
|
|
2699
3164
|
function writeWorkflowFile(repoRootPath) {
|
|
2700
3165
|
const workflowDir = join4(repoRootPath, ".github", "workflows");
|
|
2701
|
-
|
|
3166
|
+
mkdirSync5(workflowDir, { recursive: true });
|
|
2702
3167
|
const workflowFile = join4(workflowDir, "synkro.yml");
|
|
2703
|
-
|
|
3168
|
+
writeFileSync5(workflowFile, SYNKRO_WORKFLOW_YAML, "utf-8");
|
|
2704
3169
|
return workflowFile;
|
|
2705
3170
|
}
|
|
2706
3171
|
function findGitRoot(startCwd) {
|
|
2707
3172
|
let cur = startCwd;
|
|
2708
3173
|
while (cur && cur !== "/") {
|
|
2709
|
-
if (
|
|
3174
|
+
if (existsSync6(join4(cur, ".git"))) return cur;
|
|
2710
3175
|
const parent = join4(cur, "..");
|
|
2711
3176
|
if (parent === cur) break;
|
|
2712
3177
|
cur = parent;
|
|
@@ -2949,14 +3414,14 @@ __export(setupGithub_exports, {
|
|
|
2949
3414
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
2950
3415
|
import { stdin as input, stdout as output } from "process";
|
|
2951
3416
|
import { execSync as execSync4, spawn as nodeSpawn } from "child_process";
|
|
2952
|
-
import { existsSync as
|
|
3417
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
2953
3418
|
import { homedir as homedir4, platform as platform2 } from "os";
|
|
2954
3419
|
import { join as join5 } from "path";
|
|
2955
3420
|
import { execFile as execFile2 } from "child_process";
|
|
2956
3421
|
function readConfig() {
|
|
2957
|
-
if (!
|
|
3422
|
+
if (!existsSync7(CONFIG_PATH)) return {};
|
|
2958
3423
|
const out = {};
|
|
2959
|
-
for (const line of
|
|
3424
|
+
for (const line of readFileSync5(CONFIG_PATH, "utf-8").split("\n")) {
|
|
2960
3425
|
const t = line.trim();
|
|
2961
3426
|
if (!t || t.startsWith("#")) continue;
|
|
2962
3427
|
const eq = t.indexOf("=");
|
|
@@ -3026,7 +3491,7 @@ function captureClaudeSetupToken() {
|
|
|
3026
3491
|
proc.on("close", (code) => {
|
|
3027
3492
|
let raw = "";
|
|
3028
3493
|
try {
|
|
3029
|
-
raw =
|
|
3494
|
+
raw = readFileSync5(tmpFile, "utf-8");
|
|
3030
3495
|
} catch (e) {
|
|
3031
3496
|
reject(new Error(`Could not read script output file: ${e.message}`));
|
|
3032
3497
|
return;
|
|
@@ -3304,20 +3769,20 @@ var init_setupGithub = __esm({
|
|
|
3304
3769
|
});
|
|
3305
3770
|
|
|
3306
3771
|
// cli/installer/promptFetcher.ts
|
|
3307
|
-
import { existsSync as
|
|
3772
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
3308
3773
|
import { homedir as homedir5 } from "os";
|
|
3309
3774
|
import { join as join6 } from "path";
|
|
3310
3775
|
function readCache() {
|
|
3311
|
-
if (!
|
|
3776
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
3312
3777
|
try {
|
|
3313
|
-
return JSON.parse(
|
|
3778
|
+
return JSON.parse(readFileSync6(CACHE_PATH, "utf-8"));
|
|
3314
3779
|
} catch {
|
|
3315
3780
|
return null;
|
|
3316
3781
|
}
|
|
3317
3782
|
}
|
|
3318
3783
|
function writeCache(entry) {
|
|
3319
|
-
|
|
3320
|
-
|
|
3784
|
+
mkdirSync6(join6(homedir5(), ".synkro", "prompts"), { recursive: true });
|
|
3785
|
+
writeFileSync6(CACHE_PATH, JSON.stringify(entry, null, 2), "utf-8");
|
|
3321
3786
|
}
|
|
3322
3787
|
function isCacheFresh(cache) {
|
|
3323
3788
|
const ageMs = Date.now() - cache.fetched_at;
|
|
@@ -3371,13 +3836,13 @@ var init_promptFetcher = __esm({
|
|
|
3371
3836
|
});
|
|
3372
3837
|
|
|
3373
3838
|
// cli/local-cc/settings.ts
|
|
3374
|
-
import { existsSync as
|
|
3839
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
3375
3840
|
import { homedir as homedir6 } from "os";
|
|
3376
3841
|
import { join as join7 } from "path";
|
|
3377
3842
|
function isLocalCCEnabled() {
|
|
3378
|
-
if (!
|
|
3843
|
+
if (!existsSync9(CONFIG_PATH2)) return false;
|
|
3379
3844
|
try {
|
|
3380
|
-
const content =
|
|
3845
|
+
const content = readFileSync7(CONFIG_PATH2, "utf-8");
|
|
3381
3846
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
3382
3847
|
return match?.[1] === "yes";
|
|
3383
3848
|
} catch {
|
|
@@ -3542,17 +4007,17 @@ await mcp.connect(new StdioServerTransport());
|
|
|
3542
4007
|
});
|
|
3543
4008
|
|
|
3544
4009
|
// cli/local-cc/install.ts
|
|
3545
|
-
import { existsSync as
|
|
4010
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, readFileSync as readFileSync8, chmodSync, copyFileSync, renameSync as renameSync4, unlinkSync as unlinkSync4, openSync, fsyncSync, closeSync } from "fs";
|
|
3546
4011
|
import { join as join8 } from "path";
|
|
3547
4012
|
import { homedir as homedir7 } from "os";
|
|
3548
4013
|
import { spawnSync } from "child_process";
|
|
3549
4014
|
function writePluginFiles() {
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
4015
|
+
mkdirSync7(SESSION_DIR, { recursive: true });
|
|
4016
|
+
mkdirSync7(PLUGIN_SETTINGS_DIR, { recursive: true });
|
|
4017
|
+
writeFileSync7(PLUGIN_PATH, CHANNEL_PLUGIN_SOURCE, "utf-8");
|
|
3553
4018
|
chmodSync(PLUGIN_PATH, 493);
|
|
3554
|
-
|
|
3555
|
-
|
|
4019
|
+
writeFileSync7(PLUGIN_PKG_PATH, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
4020
|
+
writeFileSync7(
|
|
3556
4021
|
PLUGIN_SETTINGS_PATH,
|
|
3557
4022
|
JSON.stringify({
|
|
3558
4023
|
fastMode: true,
|
|
@@ -3564,7 +4029,7 @@ function writePluginFiles() {
|
|
|
3564
4029
|
}, null, 2) + "\n",
|
|
3565
4030
|
"utf-8"
|
|
3566
4031
|
);
|
|
3567
|
-
|
|
4032
|
+
writeFileSync7(RUN_SCRIPT_PATH, RUN_SCRIPT_SOURCE, "utf-8");
|
|
3568
4033
|
chmodSync(RUN_SCRIPT_PATH, 493);
|
|
3569
4034
|
}
|
|
3570
4035
|
function runBunInstall() {
|
|
@@ -3580,10 +4045,10 @@ function runBunInstall() {
|
|
|
3580
4045
|
}
|
|
3581
4046
|
}
|
|
3582
4047
|
function safelyMutateClaudeJson(mutator) {
|
|
3583
|
-
if (!
|
|
4048
|
+
if (!existsSync10(CLAUDE_JSON_PATH)) {
|
|
3584
4049
|
return;
|
|
3585
4050
|
}
|
|
3586
|
-
const originalText =
|
|
4051
|
+
const originalText = readFileSync8(CLAUDE_JSON_PATH, "utf-8");
|
|
3587
4052
|
let parsed;
|
|
3588
4053
|
try {
|
|
3589
4054
|
parsed = JSON.parse(originalText);
|
|
@@ -3615,14 +4080,14 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
3615
4080
|
copyFileSync(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
|
|
3616
4081
|
const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
|
|
3617
4082
|
try {
|
|
3618
|
-
|
|
4083
|
+
writeFileSync7(tmpPath, newText, "utf-8");
|
|
3619
4084
|
const fd = openSync(tmpPath, "r");
|
|
3620
4085
|
try {
|
|
3621
4086
|
fsyncSync(fd);
|
|
3622
4087
|
} finally {
|
|
3623
4088
|
closeSync(fd);
|
|
3624
4089
|
}
|
|
3625
|
-
|
|
4090
|
+
renameSync4(tmpPath, CLAUDE_JSON_PATH);
|
|
3626
4091
|
} catch (err) {
|
|
3627
4092
|
try {
|
|
3628
4093
|
unlinkSync4(tmpPath);
|
|
@@ -3647,7 +4112,7 @@ function writeProjectMcpJson() {
|
|
|
3647
4112
|
}
|
|
3648
4113
|
}
|
|
3649
4114
|
};
|
|
3650
|
-
|
|
4115
|
+
writeFileSync7(PROJECT_MCP_PATH, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
|
|
3651
4116
|
}
|
|
3652
4117
|
function patchClaudeJson() {
|
|
3653
4118
|
safelyMutateClaudeJson((parsed) => {
|
|
@@ -3967,16 +4432,16 @@ var init_pueue = __esm({
|
|
|
3967
4432
|
});
|
|
3968
4433
|
|
|
3969
4434
|
// cli/local-cc/prompts.ts
|
|
3970
|
-
import { existsSync as
|
|
4435
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
3971
4436
|
import { homedir as homedir9 } from "os";
|
|
3972
4437
|
import { join as join10 } from "path";
|
|
3973
4438
|
function loadCachedPrompts() {
|
|
3974
4439
|
if (_cached) return _cached;
|
|
3975
|
-
if (!
|
|
4440
|
+
if (!existsSync11(CACHE_PATH2)) {
|
|
3976
4441
|
throw new Error("Prompts cache not found. Run `synkro install` or `synkro update` first.");
|
|
3977
4442
|
}
|
|
3978
4443
|
try {
|
|
3979
|
-
_cached = JSON.parse(
|
|
4444
|
+
_cached = JSON.parse(readFileSync9(CACHE_PATH2, "utf-8"));
|
|
3980
4445
|
return _cached;
|
|
3981
4446
|
} catch {
|
|
3982
4447
|
throw new Error("Prompts cache is corrupted. Run `synkro update` to refresh.");
|
|
@@ -4017,8 +4482,8 @@ Any text output is silently discarded. Only the reply tool call is captured.`;
|
|
|
4017
4482
|
});
|
|
4018
4483
|
|
|
4019
4484
|
// cli/local-cc/turnLog.ts
|
|
4020
|
-
import { appendFileSync, existsSync as
|
|
4021
|
-
import { dirname as
|
|
4485
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync8, openSync as openSync2, readFileSync as readFileSync10, readSync, closeSync as closeSync2, statSync, watchFile, unwatchFile } from "fs";
|
|
4486
|
+
import { dirname as dirname5, join as join11 } from "path";
|
|
4022
4487
|
import { homedir as homedir10 } from "os";
|
|
4023
4488
|
function truncate(s, max = PREVIEW_MAX) {
|
|
4024
4489
|
if (s.length <= max) return s;
|
|
@@ -4039,7 +4504,7 @@ function extractSeverity(result) {
|
|
|
4039
4504
|
}
|
|
4040
4505
|
function appendTurn(args2) {
|
|
4041
4506
|
try {
|
|
4042
|
-
|
|
4507
|
+
mkdirSync8(dirname5(TURN_LOG_PATH), { recursive: true });
|
|
4043
4508
|
const entry = {
|
|
4044
4509
|
ts: new Date(args2.startedAt).toISOString(),
|
|
4045
4510
|
role: args2.role,
|
|
@@ -4055,11 +4520,11 @@ function appendTurn(args2) {
|
|
|
4055
4520
|
}
|
|
4056
4521
|
}
|
|
4057
4522
|
function readRecentTurns(n = 20) {
|
|
4058
|
-
if (!
|
|
4523
|
+
if (!existsSync12(TURN_LOG_PATH)) return [];
|
|
4059
4524
|
try {
|
|
4060
4525
|
const size = statSync(TURN_LOG_PATH).size;
|
|
4061
4526
|
if (size === 0) return [];
|
|
4062
|
-
const text =
|
|
4527
|
+
const text = readFileSync10(TURN_LOG_PATH, "utf-8");
|
|
4063
4528
|
const lines = text.split("\n").filter(Boolean);
|
|
4064
4529
|
const lastN = lines.slice(-n).reverse();
|
|
4065
4530
|
return lastN.map((line) => {
|
|
@@ -4075,8 +4540,8 @@ function readRecentTurns(n = 20) {
|
|
|
4075
4540
|
}
|
|
4076
4541
|
function followTurns(onEntry) {
|
|
4077
4542
|
try {
|
|
4078
|
-
|
|
4079
|
-
if (!
|
|
4543
|
+
mkdirSync8(dirname5(TURN_LOG_PATH), { recursive: true });
|
|
4544
|
+
if (!existsSync12(TURN_LOG_PATH)) {
|
|
4080
4545
|
appendFileSync(TURN_LOG_PATH, "", "utf-8");
|
|
4081
4546
|
}
|
|
4082
4547
|
} catch {
|
|
@@ -4244,7 +4709,7 @@ __export(install_exports, {
|
|
|
4244
4709
|
installCommand: () => installCommand,
|
|
4245
4710
|
parseArgs: () => parseArgs
|
|
4246
4711
|
});
|
|
4247
|
-
import { existsSync as
|
|
4712
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, chmodSync as chmodSync2, readFileSync as readFileSync11, readdirSync } from "fs";
|
|
4248
4713
|
import { homedir as homedir11 } from "os";
|
|
4249
4714
|
import { join as join12 } from "path";
|
|
4250
4715
|
import { execSync as execSync5 } from "child_process";
|
|
@@ -4283,10 +4748,10 @@ async function promptTranscriptConsent() {
|
|
|
4283
4748
|
});
|
|
4284
4749
|
}
|
|
4285
4750
|
function ensureSynkroDir() {
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4751
|
+
mkdirSync9(SYNKRO_DIR2, { recursive: true });
|
|
4752
|
+
mkdirSync9(HOOKS_DIR, { recursive: true });
|
|
4753
|
+
mkdirSync9(BIN_DIR, { recursive: true });
|
|
4754
|
+
mkdirSync9(OFFSETS_DIR, { recursive: true });
|
|
4290
4755
|
}
|
|
4291
4756
|
function writeHookScripts() {
|
|
4292
4757
|
const bashScriptPath = join12(HOOKS_DIR, "cc-bash-judge.sh");
|
|
@@ -4296,13 +4761,23 @@ function writeHookScripts() {
|
|
|
4296
4761
|
const stopSummaryScriptPath = join12(HOOKS_DIR, "cc-stop-summary.sh");
|
|
4297
4762
|
const sessionStartScriptPath = join12(HOOKS_DIR, "cc-session-start.sh");
|
|
4298
4763
|
const transcriptSyncScriptPath = join12(HOOKS_DIR, "cc-transcript-sync.sh");
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4764
|
+
const commonScriptPath = join12(HOOKS_DIR, "_synkro-common.sh");
|
|
4765
|
+
const cursorBashJudgePath = join12(HOOKS_DIR, "cursor-bash-judge.sh");
|
|
4766
|
+
const cursorEditPrecheckPath = join12(HOOKS_DIR, "cursor-edit-precheck.sh");
|
|
4767
|
+
const cursorEditCapturePath = join12(HOOKS_DIR, "cursor-edit-capture.sh");
|
|
4768
|
+
const cursorBashFollowupPath = join12(HOOKS_DIR, "cursor-bash-followup.sh");
|
|
4769
|
+
writeFileSync8(bashScriptPath, CC_BASH_JUDGE_SCRIPT, "utf-8");
|
|
4770
|
+
writeFileSync8(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, "utf-8");
|
|
4771
|
+
writeFileSync8(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, "utf-8");
|
|
4772
|
+
writeFileSync8(editPrecheckScriptPath, CC_EDIT_PRECHECK_SCRIPT, "utf-8");
|
|
4773
|
+
writeFileSync8(stopSummaryScriptPath, CC_STOP_SUMMARY_SCRIPT, "utf-8");
|
|
4774
|
+
writeFileSync8(sessionStartScriptPath, CC_SESSION_START_SCRIPT, "utf-8");
|
|
4775
|
+
writeFileSync8(transcriptSyncScriptPath, CC_TRANSCRIPT_SYNC_SCRIPT, "utf-8");
|
|
4776
|
+
writeFileSync8(commonScriptPath, SYNKRO_COMMON_SCRIPT, "utf-8");
|
|
4777
|
+
writeFileSync8(cursorBashJudgePath, CURSOR_BASH_JUDGE_SCRIPT, "utf-8");
|
|
4778
|
+
writeFileSync8(cursorEditPrecheckPath, CURSOR_EDIT_PRECHECK_SCRIPT, "utf-8");
|
|
4779
|
+
writeFileSync8(cursorEditCapturePath, CURSOR_EDIT_CAPTURE_SCRIPT, "utf-8");
|
|
4780
|
+
writeFileSync8(cursorBashFollowupPath, CURSOR_BASH_FOLLOWUP_SCRIPT, "utf-8");
|
|
4306
4781
|
chmodSync2(bashScriptPath, 493);
|
|
4307
4782
|
chmodSync2(bashFollowupScriptPath, 493);
|
|
4308
4783
|
chmodSync2(editCaptureScriptPath, 493);
|
|
@@ -4310,6 +4785,11 @@ function writeHookScripts() {
|
|
|
4310
4785
|
chmodSync2(stopSummaryScriptPath, 493);
|
|
4311
4786
|
chmodSync2(sessionStartScriptPath, 493);
|
|
4312
4787
|
chmodSync2(transcriptSyncScriptPath, 493);
|
|
4788
|
+
chmodSync2(commonScriptPath, 493);
|
|
4789
|
+
chmodSync2(cursorBashJudgePath, 493);
|
|
4790
|
+
chmodSync2(cursorEditPrecheckPath, 493);
|
|
4791
|
+
chmodSync2(cursorEditCapturePath, 493);
|
|
4792
|
+
chmodSync2(cursorBashFollowupPath, 493);
|
|
4313
4793
|
return {
|
|
4314
4794
|
bashScript: bashScriptPath,
|
|
4315
4795
|
bashFollowupScript: bashFollowupScriptPath,
|
|
@@ -4317,7 +4797,11 @@ function writeHookScripts() {
|
|
|
4317
4797
|
editPrecheckScript: editPrecheckScriptPath,
|
|
4318
4798
|
stopSummaryScript: stopSummaryScriptPath,
|
|
4319
4799
|
sessionStartScript: sessionStartScriptPath,
|
|
4320
|
-
transcriptSyncScript: transcriptSyncScriptPath
|
|
4800
|
+
transcriptSyncScript: transcriptSyncScriptPath,
|
|
4801
|
+
cursorBashJudgeScript: cursorBashJudgePath,
|
|
4802
|
+
cursorEditPrecheckScript: cursorEditPrecheckPath,
|
|
4803
|
+
cursorEditCaptureScript: cursorEditCapturePath,
|
|
4804
|
+
cursorBashFollowupScript: cursorBashFollowupPath
|
|
4321
4805
|
};
|
|
4322
4806
|
}
|
|
4323
4807
|
function sanitizeConfigValue(raw, maxLen = 256) {
|
|
@@ -4329,7 +4813,7 @@ function shellQuoteSingle(value) {
|
|
|
4329
4813
|
}
|
|
4330
4814
|
function resolveSynkroBundle() {
|
|
4331
4815
|
const scriptPath = process.argv[1];
|
|
4332
|
-
if (scriptPath &&
|
|
4816
|
+
if (scriptPath && existsSync13(scriptPath)) return scriptPath;
|
|
4333
4817
|
return null;
|
|
4334
4818
|
}
|
|
4335
4819
|
function writeConfigEnv(opts) {
|
|
@@ -4349,7 +4833,7 @@ function writeConfigEnv(opts) {
|
|
|
4349
4833
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
4350
4834
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
4351
4835
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
4352
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.4.
|
|
4836
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.4.9")}`
|
|
4353
4837
|
];
|
|
4354
4838
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
4355
4839
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -4360,12 +4844,12 @@ function writeConfigEnv(opts) {
|
|
|
4360
4844
|
}
|
|
4361
4845
|
lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle(opts.localInference ? "yes" : "no")}`);
|
|
4362
4846
|
lines.push("");
|
|
4363
|
-
|
|
4847
|
+
writeFileSync8(CONFIG_PATH3, lines.join("\n"), "utf-8");
|
|
4364
4848
|
chmodSync2(CONFIG_PATH3, 384);
|
|
4365
4849
|
}
|
|
4366
4850
|
function updateLocalInferenceFlag(enabled) {
|
|
4367
|
-
if (!
|
|
4368
|
-
let content =
|
|
4851
|
+
if (!existsSync13(CONFIG_PATH3)) return;
|
|
4852
|
+
let content = readFileSync11(CONFIG_PATH3, "utf-8");
|
|
4369
4853
|
const flag = enabled ? "yes" : "no";
|
|
4370
4854
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
4371
4855
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -4374,7 +4858,7 @@ function updateLocalInferenceFlag(enabled) {
|
|
|
4374
4858
|
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
4375
4859
|
`;
|
|
4376
4860
|
}
|
|
4377
|
-
|
|
4861
|
+
writeFileSync8(CONFIG_PATH3, content, "utf-8");
|
|
4378
4862
|
}
|
|
4379
4863
|
function collectLocalMetadata() {
|
|
4380
4864
|
const meta = { platform: process.platform };
|
|
@@ -4396,14 +4880,14 @@ function collectLocalMetadata() {
|
|
|
4396
4880
|
}
|
|
4397
4881
|
const claudeDir = join12(homedir11(), ".claude");
|
|
4398
4882
|
try {
|
|
4399
|
-
const settings = JSON.parse(
|
|
4883
|
+
const settings = JSON.parse(readFileSync11(join12(claudeDir, "settings.json"), "utf-8"));
|
|
4400
4884
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
4401
4885
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
4402
4886
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
4403
4887
|
} catch {
|
|
4404
4888
|
}
|
|
4405
4889
|
try {
|
|
4406
|
-
const mcpCache = JSON.parse(
|
|
4890
|
+
const mcpCache = JSON.parse(readFileSync11(join12(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
4407
4891
|
const mcpNames = Object.keys(mcpCache);
|
|
4408
4892
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
4409
4893
|
} catch {
|
|
@@ -4418,7 +4902,7 @@ function collectLocalMetadata() {
|
|
|
4418
4902
|
const sessionsDir = join12(claudeDir, "sessions");
|
|
4419
4903
|
const files = readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
4420
4904
|
for (const f of files) {
|
|
4421
|
-
const s = JSON.parse(
|
|
4905
|
+
const s = JSON.parse(readFileSync11(join12(sessionsDir, f), "utf-8"));
|
|
4422
4906
|
if (s.version) {
|
|
4423
4907
|
meta.cc_version = meta.cc_version || s.version;
|
|
4424
4908
|
break;
|
|
@@ -4481,12 +4965,12 @@ function isAlreadyInstalled() {
|
|
|
4481
4965
|
join12(HOOKS_DIR, "cc-stop-summary.sh"),
|
|
4482
4966
|
join12(HOOKS_DIR, "cc-session-start.sh")
|
|
4483
4967
|
];
|
|
4484
|
-
if (!requiredScripts.every((p) =>
|
|
4485
|
-
if (!
|
|
4968
|
+
if (!requiredScripts.every((p) => existsSync13(p))) return false;
|
|
4969
|
+
if (!existsSync13(CONFIG_PATH3)) return false;
|
|
4486
4970
|
const settingsPath = join12(homedir11(), ".claude", "settings.json");
|
|
4487
|
-
if (!
|
|
4971
|
+
if (!existsSync13(settingsPath)) return false;
|
|
4488
4972
|
try {
|
|
4489
|
-
const settings = JSON.parse(
|
|
4973
|
+
const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
4490
4974
|
const hooks = settings?.hooks;
|
|
4491
4975
|
if (!hooks || typeof hooks !== "object") return false;
|
|
4492
4976
|
const hasManaged = (kind) => Array.isArray(hooks[kind]) && hooks[kind].some((entry) => entry?.__synkro_managed__ === true);
|
|
@@ -4619,7 +5103,7 @@ async function installCommand(opts = {}) {
|
|
|
4619
5103
|
for (const mode of ["edit", "bash"]) {
|
|
4620
5104
|
const pidFile = join12(SYNKRO_DIR2, "daemon", mode, "daemon.pid");
|
|
4621
5105
|
try {
|
|
4622
|
-
const pid = parseInt(
|
|
5106
|
+
const pid = parseInt(readFileSync11(pidFile, "utf-8").trim(), 10);
|
|
4623
5107
|
if (pid > 0) {
|
|
4624
5108
|
process.kill(pid, "SIGTERM");
|
|
4625
5109
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -4637,6 +5121,7 @@ async function installCommand(opts = {}) {
|
|
|
4637
5121
|
}
|
|
4638
5122
|
}
|
|
4639
5123
|
let hasClaudeCode = false;
|
|
5124
|
+
let hasCursor = false;
|
|
4640
5125
|
for (const agent of agents) {
|
|
4641
5126
|
if (agent.kind === "claude_code") {
|
|
4642
5127
|
hasClaudeCode = true;
|
|
@@ -4651,6 +5136,15 @@ async function installCommand(opts = {}) {
|
|
|
4651
5136
|
skipTranscriptSync: !transcriptConsent
|
|
4652
5137
|
});
|
|
4653
5138
|
console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
|
|
5139
|
+
} else if (agent.kind === "cursor") {
|
|
5140
|
+
hasCursor = true;
|
|
5141
|
+
installCursorHooks(agent.settingsPath, {
|
|
5142
|
+
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
5143
|
+
editPrecheckScriptPath: scripts.cursorEditPrecheckScript,
|
|
5144
|
+
editCaptureScriptPath: scripts.cursorEditCaptureScript,
|
|
5145
|
+
bashFollowupScriptPath: scripts.cursorBashFollowupScript
|
|
5146
|
+
});
|
|
5147
|
+
console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
|
|
4654
5148
|
}
|
|
4655
5149
|
}
|
|
4656
5150
|
console.log();
|
|
@@ -4773,7 +5267,7 @@ function getClaudeProjectsFolder() {
|
|
|
4773
5267
|
const cwd = process.cwd();
|
|
4774
5268
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
4775
5269
|
const projectsDir = join12(homedir11(), ".claude", "projects", sanitized);
|
|
4776
|
-
return
|
|
5270
|
+
return existsSync13(projectsDir) ? projectsDir : null;
|
|
4777
5271
|
}
|
|
4778
5272
|
function extractSessionInsights(projectsDir) {
|
|
4779
5273
|
const insights = [];
|
|
@@ -4782,7 +5276,7 @@ function extractSessionInsights(projectsDir) {
|
|
|
4782
5276
|
const sessionId = file.replace(".jsonl", "");
|
|
4783
5277
|
const filePath = join12(projectsDir, file);
|
|
4784
5278
|
try {
|
|
4785
|
-
const content =
|
|
5279
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
4786
5280
|
const lines = content.split("\n").filter(Boolean);
|
|
4787
5281
|
for (let i = 0; i < lines.length; i++) {
|
|
4788
5282
|
try {
|
|
@@ -4858,7 +5352,7 @@ function extractTextContent(content) {
|
|
|
4858
5352
|
return "";
|
|
4859
5353
|
}
|
|
4860
5354
|
function parseTranscriptFile(filePath) {
|
|
4861
|
-
const content =
|
|
5355
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
4862
5356
|
const lines = content.split("\n").filter(Boolean);
|
|
4863
5357
|
const messages = [];
|
|
4864
5358
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -4940,9 +5434,9 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
4940
5434
|
const sessionId = file.replace(".jsonl", "");
|
|
4941
5435
|
const filePath = join12(projectsDir, file);
|
|
4942
5436
|
try {
|
|
4943
|
-
const content =
|
|
5437
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
4944
5438
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
4945
|
-
|
|
5439
|
+
writeFileSync8(join12(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
4946
5440
|
} catch {
|
|
4947
5441
|
}
|
|
4948
5442
|
}
|
|
@@ -4955,6 +5449,7 @@ var init_install2 = __esm({
|
|
|
4955
5449
|
"use strict";
|
|
4956
5450
|
init_agentDetect();
|
|
4957
5451
|
init_ccHookConfig();
|
|
5452
|
+
init_cursorHookConfig();
|
|
4958
5453
|
init_mcpConfig();
|
|
4959
5454
|
init_hookScripts();
|
|
4960
5455
|
init_stub();
|
|
@@ -5046,13 +5541,13 @@ var status_exports = {};
|
|
|
5046
5541
|
__export(status_exports, {
|
|
5047
5542
|
statusCommand: () => statusCommand
|
|
5048
5543
|
});
|
|
5049
|
-
import { existsSync as
|
|
5544
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
5050
5545
|
import { homedir as homedir12 } from "os";
|
|
5051
5546
|
import { join as join13 } from "path";
|
|
5052
5547
|
function readConfigEnv() {
|
|
5053
|
-
if (!
|
|
5548
|
+
if (!existsSync14(CONFIG_PATH4)) return {};
|
|
5054
5549
|
const out = {};
|
|
5055
|
-
const raw =
|
|
5550
|
+
const raw = readFileSync12(CONFIG_PATH4, "utf-8");
|
|
5056
5551
|
for (const line of raw.split("\n")) {
|
|
5057
5552
|
const trimmed = line.trim();
|
|
5058
5553
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -5121,6 +5616,15 @@ async function statusCommand() {
|
|
|
5121
5616
|
console.log(` \u2022 SessionEnd summary: ${hooks.sessionEnd ? "\u2713" : "\u2717"}`);
|
|
5122
5617
|
console.log(` \u2022 SessionStart: ${hooks.sessionStart ? "\u2713" : "\u2717"}`);
|
|
5123
5618
|
}
|
|
5619
|
+
} else if (a.kind === "cursor") {
|
|
5620
|
+
const hooks = inspectCursorHooks(a.settingsPath);
|
|
5621
|
+
console.log(` hooks installed: ${hooks.installed ? "\u2713" : "\u2717"}`);
|
|
5622
|
+
if (hooks.installed) {
|
|
5623
|
+
console.log(` \u2022 beforeShellExecution: ${hooks.beforeShellExecution ? "\u2713" : "\u2717"}`);
|
|
5624
|
+
console.log(` \u2022 preToolUse: ${hooks.preToolUse ? "\u2713" : "\u2717"}`);
|
|
5625
|
+
console.log(` \u2022 afterFileEdit: ${hooks.afterFileEdit ? "\u2713" : "\u2717"}`);
|
|
5626
|
+
console.log(` \u2022 postToolUse: ${hooks.postToolUse ? "\u2713" : "\u2717"}`);
|
|
5627
|
+
}
|
|
5124
5628
|
}
|
|
5125
5629
|
}
|
|
5126
5630
|
}
|
|
@@ -5131,13 +5635,23 @@ async function statusCommand() {
|
|
|
5131
5635
|
const editCaptureScript = join13(SYNKRO_DIR3, "hooks", "cc-edit-capture.sh");
|
|
5132
5636
|
const stopSummaryScript = join13(SYNKRO_DIR3, "hooks", "cc-stop-summary.sh");
|
|
5133
5637
|
const sessionStartScript = join13(SYNKRO_DIR3, "hooks", "cc-session-start.sh");
|
|
5638
|
+
const cursorBashJudgeScript = join13(SYNKRO_DIR3, "hooks", "cursor-bash-judge.sh");
|
|
5639
|
+
const cursorEditPrecheckScript = join13(SYNKRO_DIR3, "hooks", "cursor-edit-precheck.sh");
|
|
5640
|
+
const cursorEditCaptureScript = join13(SYNKRO_DIR3, "hooks", "cursor-edit-capture.sh");
|
|
5641
|
+
const cursorBashFollowupScript = join13(SYNKRO_DIR3, "hooks", "cursor-bash-followup.sh");
|
|
5642
|
+
const commonScript = join13(SYNKRO_DIR3, "hooks", "_synkro-common.sh");
|
|
5134
5643
|
console.log("Hook scripts:");
|
|
5135
|
-
console.log(` ${
|
|
5136
|
-
console.log(` ${
|
|
5137
|
-
console.log(` ${
|
|
5138
|
-
console.log(` ${
|
|
5139
|
-
console.log(` ${
|
|
5140
|
-
console.log(` ${
|
|
5644
|
+
console.log(` ${existsSync14(bashScript) ? "\u2713" : "\u2717"} ${bashScript}`);
|
|
5645
|
+
console.log(` ${existsSync14(bashFollowupScript) ? "\u2713" : "\u2717"} ${bashFollowupScript}`);
|
|
5646
|
+
console.log(` ${existsSync14(editPrecheckScript) ? "\u2713" : "\u2717"} ${editPrecheckScript}`);
|
|
5647
|
+
console.log(` ${existsSync14(editCaptureScript) ? "\u2713" : "\u2717"} ${editCaptureScript}`);
|
|
5648
|
+
console.log(` ${existsSync14(stopSummaryScript) ? "\u2713" : "\u2717"} ${stopSummaryScript}`);
|
|
5649
|
+
console.log(` ${existsSync14(sessionStartScript) ? "\u2713" : "\u2717"} ${sessionStartScript}`);
|
|
5650
|
+
console.log(` ${existsSync14(commonScript) ? "\u2713" : "\u2717"} ${commonScript}`);
|
|
5651
|
+
console.log(` ${existsSync14(cursorBashJudgeScript) ? "\u2713" : "\u2717"} ${cursorBashJudgeScript}`);
|
|
5652
|
+
console.log(` ${existsSync14(cursorEditPrecheckScript) ? "\u2713" : "\u2717"} ${cursorEditPrecheckScript}`);
|
|
5653
|
+
console.log(` ${existsSync14(cursorEditCaptureScript) ? "\u2713" : "\u2717"} ${cursorEditCaptureScript}`);
|
|
5654
|
+
console.log(` ${existsSync14(cursorBashFollowupScript) ? "\u2713" : "\u2717"} ${cursorBashFollowupScript}`);
|
|
5141
5655
|
console.log();
|
|
5142
5656
|
const mcp = inspectMcpConfig();
|
|
5143
5657
|
console.log("Guardrails MCP server (Claude Code):");
|
|
@@ -5156,6 +5670,7 @@ var init_status = __esm({
|
|
|
5156
5670
|
init_stub();
|
|
5157
5671
|
init_agentDetect();
|
|
5158
5672
|
init_ccHookConfig();
|
|
5673
|
+
init_cursorHookConfig();
|
|
5159
5674
|
init_mcpConfig();
|
|
5160
5675
|
SYNKRO_DIR3 = join13(homedir12(), ".synkro");
|
|
5161
5676
|
CONFIG_PATH4 = join13(SYNKRO_DIR3, "config.env");
|
|
@@ -5247,13 +5762,13 @@ var config_exports = {};
|
|
|
5247
5762
|
__export(config_exports, {
|
|
5248
5763
|
configCommand: () => configCommand
|
|
5249
5764
|
});
|
|
5250
|
-
import { readFileSync as
|
|
5765
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync15 } from "fs";
|
|
5251
5766
|
import { join as join14 } from "path";
|
|
5252
5767
|
import { homedir as homedir13 } from "os";
|
|
5253
5768
|
function readConfigEnv2() {
|
|
5254
|
-
if (!
|
|
5769
|
+
if (!existsSync15(CONFIG_PATH5)) return {};
|
|
5255
5770
|
const out = {};
|
|
5256
|
-
for (const line of
|
|
5771
|
+
for (const line of readFileSync13(CONFIG_PATH5, "utf-8").split("\n")) {
|
|
5257
5772
|
const t = line.trim();
|
|
5258
5773
|
if (!t || t.startsWith("#")) continue;
|
|
5259
5774
|
const eq = t.indexOf("=");
|
|
@@ -5262,11 +5777,11 @@ function readConfigEnv2() {
|
|
|
5262
5777
|
return out;
|
|
5263
5778
|
}
|
|
5264
5779
|
function updateConfigValue(key, value) {
|
|
5265
|
-
if (!
|
|
5780
|
+
if (!existsSync15(CONFIG_PATH5)) {
|
|
5266
5781
|
console.error("No config found. Run `synkro install` first.");
|
|
5267
5782
|
process.exit(1);
|
|
5268
5783
|
}
|
|
5269
|
-
const lines =
|
|
5784
|
+
const lines = readFileSync13(CONFIG_PATH5, "utf-8").split("\n");
|
|
5270
5785
|
const pattern = new RegExp(`^${key}=`);
|
|
5271
5786
|
let found = false;
|
|
5272
5787
|
const updated = lines.map((line) => {
|
|
@@ -5277,7 +5792,7 @@ function updateConfigValue(key, value) {
|
|
|
5277
5792
|
return line;
|
|
5278
5793
|
});
|
|
5279
5794
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
5280
|
-
|
|
5795
|
+
writeFileSync9(CONFIG_PATH5, updated.join("\n"), "utf-8");
|
|
5281
5796
|
}
|
|
5282
5797
|
async function configCommand(args2) {
|
|
5283
5798
|
if (args2.length === 0) {
|
|
@@ -5344,7 +5859,7 @@ __export(scanPr_exports, {
|
|
|
5344
5859
|
scanPrCommand: () => scanPrCommand
|
|
5345
5860
|
});
|
|
5346
5861
|
import { execSync as execSync6, spawn as spawn2 } from "child_process";
|
|
5347
|
-
import { readFileSync as
|
|
5862
|
+
import { readFileSync as readFileSync14, existsSync as existsSync16 } from "fs";
|
|
5348
5863
|
import { join as join15 } from "path";
|
|
5349
5864
|
function parseMatchSpec(condition) {
|
|
5350
5865
|
if (!condition.startsWith("match_spec:")) return null;
|
|
@@ -5825,9 +6340,9 @@ function shouldFail(findings, threshold) {
|
|
|
5825
6340
|
}
|
|
5826
6341
|
function readRepoDeps() {
|
|
5827
6342
|
const pkgPath = join15(process.cwd(), "package.json");
|
|
5828
|
-
if (!
|
|
6343
|
+
if (!existsSync16(pkgPath)) return {};
|
|
5829
6344
|
try {
|
|
5830
|
-
const pkg = JSON.parse(
|
|
6345
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
5831
6346
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5832
6347
|
} catch {
|
|
5833
6348
|
return {};
|
|
@@ -6089,7 +6604,7 @@ var disconnect_exports = {};
|
|
|
6089
6604
|
__export(disconnect_exports, {
|
|
6090
6605
|
disconnectCommand: () => disconnectCommand
|
|
6091
6606
|
});
|
|
6092
|
-
import { existsSync as
|
|
6607
|
+
import { existsSync as existsSync17, rmSync } from "fs";
|
|
6093
6608
|
import { homedir as homedir14 } from "os";
|
|
6094
6609
|
import { join as join16 } from "path";
|
|
6095
6610
|
function tearDownLocalCC() {
|
|
@@ -6114,6 +6629,9 @@ function disconnectCommand(args2 = []) {
|
|
|
6114
6629
|
sawClaudeCode = true;
|
|
6115
6630
|
const removed = uninstallCCHooks(agent.settingsPath);
|
|
6116
6631
|
console.log(`${removed ? "\u2713" : "\xB7"} ${agent.name}: ${removed ? "removed Synkro hook entries" : "no Synkro hooks found"}`);
|
|
6632
|
+
} else if (agent.kind === "cursor") {
|
|
6633
|
+
const removed = uninstallCursorHooks(agent.settingsPath);
|
|
6634
|
+
console.log(`${removed ? "\u2713" : "\xB7"} ${agent.name}: ${removed ? "removed Synkro hook entries" : "no Synkro hooks found"}`);
|
|
6117
6635
|
}
|
|
6118
6636
|
}
|
|
6119
6637
|
if (sawClaudeCode) {
|
|
@@ -6121,13 +6639,13 @@ function disconnectCommand(args2 = []) {
|
|
|
6121
6639
|
console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
|
|
6122
6640
|
}
|
|
6123
6641
|
if (purge) {
|
|
6124
|
-
if (
|
|
6642
|
+
if (existsSync17(SYNKRO_DIR5)) {
|
|
6125
6643
|
rmSync(SYNKRO_DIR5, { recursive: true, force: true });
|
|
6126
6644
|
console.log(`\u2713 Removed ${SYNKRO_DIR5}`);
|
|
6127
6645
|
} else {
|
|
6128
6646
|
console.log(`\xB7 ${SYNKRO_DIR5} already gone, nothing to remove`);
|
|
6129
6647
|
}
|
|
6130
|
-
} else if (
|
|
6648
|
+
} else if (existsSync17(SYNKRO_DIR5)) {
|
|
6131
6649
|
console.log(`Config preserved at ${SYNKRO_DIR5}. Run with --purge to remove.`);
|
|
6132
6650
|
}
|
|
6133
6651
|
console.log("\nSynkro disconnected.");
|
|
@@ -6138,6 +6656,7 @@ var init_disconnect = __esm({
|
|
|
6138
6656
|
"use strict";
|
|
6139
6657
|
init_agentDetect();
|
|
6140
6658
|
init_ccHookConfig();
|
|
6659
|
+
init_cursorHookConfig();
|
|
6141
6660
|
init_mcpConfig();
|
|
6142
6661
|
init_pueue();
|
|
6143
6662
|
init_install();
|
|
@@ -6190,7 +6709,7 @@ __export(localCc_exports, {
|
|
|
6190
6709
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
6191
6710
|
import { homedir as homedir15 } from "os";
|
|
6192
6711
|
import { join as join17 } from "path";
|
|
6193
|
-
import { existsSync as
|
|
6712
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
6194
6713
|
function printHelp() {
|
|
6195
6714
|
console.log(`synkro local-cc \u2014 manage the local Claude Code inference session
|
|
6196
6715
|
|
|
@@ -6280,15 +6799,15 @@ TROUBLESHOOTING
|
|
|
6280
6799
|
`);
|
|
6281
6800
|
}
|
|
6282
6801
|
function readGatewayUrl() {
|
|
6283
|
-
if (
|
|
6284
|
-
const m =
|
|
6802
|
+
if (existsSync18(CONFIG_PATH6)) {
|
|
6803
|
+
const m = readFileSync15(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
6285
6804
|
if (m) return m[1];
|
|
6286
6805
|
}
|
|
6287
6806
|
return "https://api.synkro.sh";
|
|
6288
6807
|
}
|
|
6289
6808
|
function updateLocalInferenceFlag2(enabled) {
|
|
6290
|
-
if (!
|
|
6291
|
-
let content =
|
|
6809
|
+
if (!existsSync18(CONFIG_PATH6)) return;
|
|
6810
|
+
let content = readFileSync15(CONFIG_PATH6, "utf-8");
|
|
6292
6811
|
const flag = enabled ? "yes" : "no";
|
|
6293
6812
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
6294
6813
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -6297,7 +6816,7 @@ function updateLocalInferenceFlag2(enabled) {
|
|
|
6297
6816
|
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
6298
6817
|
`;
|
|
6299
6818
|
}
|
|
6300
|
-
|
|
6819
|
+
writeFileSync10(CONFIG_PATH6, content, "utf-8");
|
|
6301
6820
|
}
|
|
6302
6821
|
async function setServerGradingProvider(provider) {
|
|
6303
6822
|
await ensureValidToken();
|
|
@@ -6624,15 +7143,15 @@ var init_grade = __esm({
|
|
|
6624
7143
|
});
|
|
6625
7144
|
|
|
6626
7145
|
// cli/bootstrap.js
|
|
6627
|
-
import { readFileSync as
|
|
7146
|
+
import { readFileSync as readFileSync16, existsSync as existsSync19 } from "fs";
|
|
6628
7147
|
import { resolve } from "path";
|
|
6629
7148
|
var envCandidates = [
|
|
6630
7149
|
resolve(process.cwd(), ".env"),
|
|
6631
7150
|
resolve(process.env.HOME ?? "", ".synkro", "config.env")
|
|
6632
7151
|
];
|
|
6633
7152
|
for (const envPath of envCandidates) {
|
|
6634
|
-
if (!
|
|
6635
|
-
const envContent =
|
|
7153
|
+
if (!existsSync19(envPath)) continue;
|
|
7154
|
+
const envContent = readFileSync16(envPath, "utf-8");
|
|
6636
7155
|
for (const line of envContent.split("\n")) {
|
|
6637
7156
|
const trimmed = line.trim();
|
|
6638
7157
|
if (!trimmed || trimmed.startsWith("#")) continue;
|