@synkro-sh/cli 1.4.6 → 1.4.8
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 +694 -127
- 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";
|
|
@@ -1647,8 +1767,10 @@ if synkro_channel_up || { [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v cl
|
|
|
1647
1767
|
# Wait for CVE scan
|
|
1648
1768
|
wait $CVE_PID 2>/dev/null
|
|
1649
1769
|
CVE_TEXT=""
|
|
1770
|
+
CVE_FINDINGS_JSON="[]"
|
|
1650
1771
|
if [ -s "$CVE_RESULT_FILE" ]; then
|
|
1651
1772
|
CVE_TEXT=$(jq -r '.summary // empty' "$CVE_RESULT_FILE" 2>/dev/null || echo "")
|
|
1773
|
+
CVE_FINDINGS_JSON=$(jq -c '[.findings[]? | {package: .package, version: .version, cve: .id, severity: .severity, score: .score}]' "$CVE_RESULT_FILE" 2>/dev/null || echo "[]")
|
|
1652
1774
|
fi
|
|
1653
1775
|
|
|
1654
1776
|
# Wrapper extraction (greedy \u2014 tolerates nested XML tags).
|
|
@@ -1766,6 +1888,7 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
|
1766
1888
|
--arg session_id "$SESSION_ID" \\
|
|
1767
1889
|
--arg mech_cat "$MECH_CAT" \\
|
|
1768
1890
|
--arg biz_cat "$BIZ_CAT" \\
|
|
1891
|
+
--argjson cve_findings "\${CVE_FINDINGS_JSON:-[]}" \\
|
|
1769
1892
|
'{
|
|
1770
1893
|
event_id: $event_id, timestamp: $timestamp, hook_type: $hook_type,
|
|
1771
1894
|
verdict: $verdict, severity: $severity, risk_level: $risk_level,
|
|
@@ -1773,7 +1896,8 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
|
1773
1896
|
} + (if $repo != "" then {repo: $repo} else {} end)
|
|
1774
1897
|
+ (if $session_id != "" then {session_id: $session_id} else {} end)
|
|
1775
1898
|
+ (if $mech_cat != "" then {mechanism_category: $mech_cat} else {} end)
|
|
1776
|
-
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)
|
|
1899
|
+
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)
|
|
1900
|
+
+ (if ($cve_findings | length) > 0 then {cve_findings: $cve_findings} else {} end)')
|
|
1777
1901
|
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
1778
1902
|
-H "Content-Type: application/json" \\
|
|
1779
1903
|
-H "Authorization: Bearer $JWT" \\
|
|
@@ -2182,6 +2306,386 @@ disown 2>/dev/null || true
|
|
|
2182
2306
|
# Update offset
|
|
2183
2307
|
printf '%s' "$TOTAL_LINES" > "$OFFSET_FILE" 2>/dev/null || true
|
|
2184
2308
|
|
|
2309
|
+
echo '{}'
|
|
2310
|
+
exit 0
|
|
2311
|
+
`;
|
|
2312
|
+
SYNKRO_COMMON_SCRIPT = `#!/bin/bash
|
|
2313
|
+
# Shared Synkro hook utilities \u2014 sourced by IDE-specific adapter scripts.
|
|
2314
|
+
# Provides: auth, JWT refresh, config loading, API helpers, git detection.
|
|
2315
|
+
|
|
2316
|
+
synkro_log() { echo "[synkro] $1" >&2; }
|
|
2317
|
+
|
|
2318
|
+
synkro_channel_up() {
|
|
2319
|
+
(exec 3<>/dev/tcp/127.0.0.1/\${SYNKRO_CHANNEL_PORT:-8929}) 2>/dev/null && exec 3<&- 3>&-
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
# Load config
|
|
2323
|
+
_SYNKRO_CONFIG="$HOME/.synkro/config.env"
|
|
2324
|
+
if [ -f "$_SYNKRO_CONFIG" ]; then
|
|
2325
|
+
set -a
|
|
2326
|
+
# shellcheck disable=SC1090
|
|
2327
|
+
. "$_SYNKRO_CONFIG"
|
|
2328
|
+
set +a
|
|
2329
|
+
fi
|
|
2330
|
+
|
|
2331
|
+
GATEWAY_URL="\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}"
|
|
2332
|
+
CREDS_PATH="\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}"
|
|
2333
|
+
|
|
2334
|
+
synkro_load_jwt() {
|
|
2335
|
+
if [ ! -f "$CREDS_PATH" ]; then
|
|
2336
|
+
echo ""
|
|
2337
|
+
return 1
|
|
2338
|
+
fi
|
|
2339
|
+
jq -r '.access_token // empty' "$CREDS_PATH" 2>/dev/null
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
synkro_refresh_jwt() {
|
|
2343
|
+
local refresh_token
|
|
2344
|
+
refresh_token=$(jq -r '.refresh_token // empty' "$CREDS_PATH" 2>/dev/null)
|
|
2345
|
+
if [ -z "$refresh_token" ]; then return 1; fi
|
|
2346
|
+
local refresh_body
|
|
2347
|
+
refresh_body=$(jq -n --arg rt "$refresh_token" '{refresh_token:$rt}')
|
|
2348
|
+
local refresh_resp
|
|
2349
|
+
refresh_resp=$(curl -sS -X POST "\${GATEWAY_URL}/api/auth/refresh" \\
|
|
2350
|
+
-H "Content-Type: application/json" \\
|
|
2351
|
+
-d "$refresh_body" \\
|
|
2352
|
+
--max-time 4 2>/dev/null)
|
|
2353
|
+
local new_access
|
|
2354
|
+
new_access=$(echo "$refresh_resp" | jq -r '.access_token // empty' 2>/dev/null)
|
|
2355
|
+
if [ -z "$new_access" ]; then return 1; fi
|
|
2356
|
+
local new_refresh
|
|
2357
|
+
new_refresh=$(echo "$refresh_resp" | jq -r '.refresh_token // empty' 2>/dev/null)
|
|
2358
|
+
if [ -z "$new_refresh" ]; then new_refresh="$refresh_token"; fi
|
|
2359
|
+
local tmp="\${CREDS_PATH}.synkro.tmp"
|
|
2360
|
+
jq --arg at "$new_access" --arg rt "$new_refresh" \\
|
|
2361
|
+
'. + {access_token: $at, refresh_token: $rt}' \\
|
|
2362
|
+
"$CREDS_PATH" > "$tmp" 2>/dev/null && mv "$tmp" "$CREDS_PATH"
|
|
2363
|
+
JWT="$new_access"
|
|
2364
|
+
return 0
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
synkro_ensure_fresh_jwt() {
|
|
2368
|
+
[ -z "$JWT" ] && return 1
|
|
2369
|
+
local payload exp now remaining
|
|
2370
|
+
payload=$(printf '%s' "$JWT" | cut -d. -f2)
|
|
2371
|
+
case $((\${#payload} % 4)) in
|
|
2372
|
+
2) payload="\${payload}==" ;;
|
|
2373
|
+
3) payload="\${payload}=" ;;
|
|
2374
|
+
esac
|
|
2375
|
+
exp=$(printf '%s' "$payload" | tr '_-' '/+' | base64 -D 2>/dev/null | jq -r '.exp // 0' 2>/dev/null)
|
|
2376
|
+
now=$(date -u +%s)
|
|
2377
|
+
remaining=$((exp - now))
|
|
2378
|
+
if [ "$remaining" -lt 60 ]; then
|
|
2379
|
+
synkro_refresh_jwt
|
|
2380
|
+
fi
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
synkro_detect_repo() {
|
|
2384
|
+
local cwd="\${1:-.}"
|
|
2385
|
+
if command -v git >/dev/null 2>&1; then
|
|
2386
|
+
local remote
|
|
2387
|
+
remote=$(git -C "$cwd" remote get-url origin 2>/dev/null || true)
|
|
2388
|
+
if [ -n "$remote" ]; then
|
|
2389
|
+
echo "$remote" | sed -E 's|^git@[^:]+:||; s|^https?://[^/]+/||; s|\\.git$||'
|
|
2390
|
+
return
|
|
2391
|
+
fi
|
|
2392
|
+
fi
|
|
2393
|
+
echo ""
|
|
2394
|
+
}
|
|
2395
|
+
`;
|
|
2396
|
+
CURSOR_BASH_JUDGE_SCRIPT = `#!/bin/bash
|
|
2397
|
+
# Synkro beforeShellExecution hook for Cursor.
|
|
2398
|
+
# Reads Cursor's stdin payload, judges via Synkro gateway, returns Cursor-format verdict.
|
|
2399
|
+
|
|
2400
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2401
|
+
# shellcheck disable=SC1091
|
|
2402
|
+
. "$SCRIPT_DIR/_synkro-common.sh"
|
|
2403
|
+
|
|
2404
|
+
JWT=$(synkro_load_jwt)
|
|
2405
|
+
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2406
|
+
synkro_ensure_fresh_jwt
|
|
2407
|
+
|
|
2408
|
+
PAYLOAD=$(cat)
|
|
2409
|
+
if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
|
|
2410
|
+
|
|
2411
|
+
COMMAND=$(echo "$PAYLOAD" | jq -r '.command // empty' 2>/dev/null)
|
|
2412
|
+
if [ -z "$COMMAND" ]; then echo '{}'; exit 0; fi
|
|
2413
|
+
|
|
2414
|
+
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
|
|
2415
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.conversation_id // empty' 2>/dev/null)
|
|
2416
|
+
GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
|
|
2417
|
+
|
|
2418
|
+
CMD_SHORT=$(printf '%s' "$COMMAND" | head -c 80)
|
|
2419
|
+
synkro_log "bashGuard checking: $CMD_SHORT"
|
|
2420
|
+
|
|
2421
|
+
TOOL_INPUT=$(jq -n --arg cmd "$COMMAND" '{command: $cmd}')
|
|
2422
|
+
|
|
2423
|
+
BODY=$(jq -n \\
|
|
2424
|
+
--argjson tool_input "$TOOL_INPUT" \\
|
|
2425
|
+
--arg session_id "$SESSION_ID" \\
|
|
2426
|
+
--arg cwd "$CWD" \\
|
|
2427
|
+
--arg repo "$GIT_REPO" \\
|
|
2428
|
+
'{
|
|
2429
|
+
kind: "bash_judge",
|
|
2430
|
+
tool_input: $tool_input,
|
|
2431
|
+
user_intent: null,
|
|
2432
|
+
recent_user_messages: [],
|
|
2433
|
+
recent_messages: [],
|
|
2434
|
+
recent_actions: [],
|
|
2435
|
+
session_id: (if ($session_id | length) > 0 then $session_id else null end),
|
|
2436
|
+
cwd: (if ($cwd | length) > 0 then $cwd else null end),
|
|
2437
|
+
repo: (if ($repo | length) > 0 then $repo else null end),
|
|
2438
|
+
ide: "cursor"
|
|
2439
|
+
}')
|
|
2440
|
+
|
|
2441
|
+
VERDICT=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/judge" \\
|
|
2442
|
+
-H "Content-Type: application/json" \\
|
|
2443
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2444
|
+
-d "$BODY" \\
|
|
2445
|
+
--max-time 6 2>/dev/null || echo "")
|
|
2446
|
+
|
|
2447
|
+
if echo "$VERDICT" | grep -qE '"detail":"Token has expired|"detail":"Invalid or expired token'; then
|
|
2448
|
+
if synkro_refresh_jwt; then
|
|
2449
|
+
VERDICT=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/judge" \\
|
|
2450
|
+
-H "Content-Type: application/json" \\
|
|
2451
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2452
|
+
-d "$BODY" \\
|
|
2453
|
+
--max-time 6 2>/dev/null || echo "")
|
|
2454
|
+
fi
|
|
2455
|
+
fi
|
|
2456
|
+
|
|
2457
|
+
if [ -z "$VERDICT" ]; then
|
|
2458
|
+
synkro_log "bashGuard $CMD_SHORT \u2192 error (timeout)"
|
|
2459
|
+
echo '{}'
|
|
2460
|
+
exit 0
|
|
2461
|
+
fi
|
|
2462
|
+
|
|
2463
|
+
SEVERITY=$(echo "$VERDICT" | jq -r '.severity // "audit"' 2>/dev/null)
|
|
2464
|
+
REASONING=$(echo "$VERDICT" | jq -r '.reasoning // ""' 2>/dev/null)
|
|
2465
|
+
ALTERNATIVE=$(echo "$VERDICT" | jq -r '.alternative // ""' 2>/dev/null)
|
|
2466
|
+
CATEGORY=$(echo "$VERDICT" | jq -r '.category // ""' 2>/dev/null)
|
|
2467
|
+
VERDICT_KIND=$(echo "$VERDICT" | jq -r '.verdict // "warn"' 2>/dev/null)
|
|
2468
|
+
|
|
2469
|
+
case "$SEVERITY" in
|
|
2470
|
+
block|audit) ;;
|
|
2471
|
+
low|medium|high|critical)
|
|
2472
|
+
if [ "$VERDICT_KIND" = "allow" ]; then SEVERITY="audit"; else SEVERITY="block"; fi
|
|
2473
|
+
;;
|
|
2474
|
+
*)
|
|
2475
|
+
if [ "$VERDICT_KIND" = "allow" ]; then SEVERITY="audit"; else SEVERITY="block"; fi
|
|
2476
|
+
;;
|
|
2477
|
+
esac
|
|
2478
|
+
|
|
2479
|
+
ALT_SUFFIX=""
|
|
2480
|
+
if [ -n "$ALTERNATIVE" ] && [ "$ALTERNATIVE" != "null" ]; then
|
|
2481
|
+
ALT_SUFFIX=" Suggested: \${ALTERNATIVE}"
|
|
2482
|
+
fi
|
|
2483
|
+
|
|
2484
|
+
case "$SEVERITY" in
|
|
2485
|
+
block)
|
|
2486
|
+
synkro_log "bashGuard $CMD_SHORT \u2192 BLOCK: $REASONING"
|
|
2487
|
+
jq -n \\
|
|
2488
|
+
--arg user "Synkro safety judge blocked this command: \${REASONING}\${ALT_SUFFIX}" \\
|
|
2489
|
+
--arg agent "Synkro safety judge (severity: \${SEVERITY}, category: \${CATEGORY}). Reasoning: \${REASONING}.\${ALT_SUFFIX}" \\
|
|
2490
|
+
'{permission: "deny", user_message: $user, agent_message: $agent}'
|
|
2491
|
+
;;
|
|
2492
|
+
audit)
|
|
2493
|
+
synkro_log "bashGuard $CMD_SHORT \u2192 pass (\${CATEGORY})"
|
|
2494
|
+
echo '{}'
|
|
2495
|
+
;;
|
|
2496
|
+
*)
|
|
2497
|
+
synkro_log "bashGuard $CMD_SHORT \u2192 BLOCK (unexpected severity)"
|
|
2498
|
+
jq -n \\
|
|
2499
|
+
--arg user "Synkro safety judge blocked this command (unexpected severity)." \\
|
|
2500
|
+
'{permission: "deny", user_message: $user}'
|
|
2501
|
+
;;
|
|
2502
|
+
esac
|
|
2503
|
+
`;
|
|
2504
|
+
CURSOR_EDIT_PRECHECK_SCRIPT = `#!/bin/bash
|
|
2505
|
+
# Synkro preToolUse hook for Cursor \u2014 pre-check edits against org rules.
|
|
2506
|
+
# Only acts on edit-like tool names; passes through everything else.
|
|
2507
|
+
|
|
2508
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2509
|
+
# shellcheck disable=SC1091
|
|
2510
|
+
. "$SCRIPT_DIR/_synkro-common.sh"
|
|
2511
|
+
|
|
2512
|
+
JWT=$(synkro_load_jwt)
|
|
2513
|
+
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2514
|
+
synkro_ensure_fresh_jwt
|
|
2515
|
+
|
|
2516
|
+
PAYLOAD=$(cat)
|
|
2517
|
+
if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
|
|
2518
|
+
|
|
2519
|
+
TOOL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
2520
|
+
|
|
2521
|
+
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
|
|
2522
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.conversation_id // empty' 2>/dev/null)
|
|
2523
|
+
GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
|
|
2524
|
+
|
|
2525
|
+
FILE_PATH=$(echo "$PAYLOAD" | jq -r '.tool_input.file_path // .tool_input.path // .tool_input.target_file // empty' 2>/dev/null)
|
|
2526
|
+
CONTENT=$(echo "$PAYLOAD" | jq -r '.tool_input.content // .tool_input.new_string // .tool_input.code_edit // empty' 2>/dev/null)
|
|
2527
|
+
|
|
2528
|
+
# Skip non-edit tools \u2014 if there's no file path in tool_input, this isn't a file edit
|
|
2529
|
+
if [ -z "$FILE_PATH" ]; then echo '{}'; exit 0; fi
|
|
2530
|
+
if [ -z "$FILE_PATH" ]; then echo '{}'; exit 0; fi
|
|
2531
|
+
|
|
2532
|
+
BASENAME=$(basename "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")
|
|
2533
|
+
synkro_log "editGuard checking: $BASENAME"
|
|
2534
|
+
|
|
2535
|
+
BODY=$(jq -n \\
|
|
2536
|
+
--arg file_path "$FILE_PATH" \\
|
|
2537
|
+
--arg content "$CONTENT" \\
|
|
2538
|
+
--arg session_id "$SESSION_ID" \\
|
|
2539
|
+
--arg cwd "$CWD" \\
|
|
2540
|
+
--arg repo "$GIT_REPO" \\
|
|
2541
|
+
'{
|
|
2542
|
+
file_path: $file_path,
|
|
2543
|
+
content: $content,
|
|
2544
|
+
session_id: (if ($session_id | length) > 0 then $session_id else null end),
|
|
2545
|
+
cwd: (if ($cwd | length) > 0 then $cwd else null end),
|
|
2546
|
+
repo: (if ($repo | length) > 0 then $repo else null end),
|
|
2547
|
+
ide: "cursor"
|
|
2548
|
+
}')
|
|
2549
|
+
|
|
2550
|
+
RESP=$(curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit" \\
|
|
2551
|
+
-H "Content-Type: application/json" \\
|
|
2552
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2553
|
+
-d "$BODY" \\
|
|
2554
|
+
--max-time 8 2>/dev/null || echo "")
|
|
2555
|
+
|
|
2556
|
+
if [ -z "$RESP" ]; then
|
|
2557
|
+
synkro_log "editGuard $BASENAME \u2192 error (timeout)"
|
|
2558
|
+
echo '{}'
|
|
2559
|
+
exit 0
|
|
2560
|
+
fi
|
|
2561
|
+
|
|
2562
|
+
DECISION=$(echo "$RESP" | jq -r '.hookSpecificOutput.permissionDecision // "allow"' 2>/dev/null)
|
|
2563
|
+
case "$DECISION" in
|
|
2564
|
+
deny|ask)
|
|
2565
|
+
REASON=$(echo "$RESP" | jq -r '.hookSpecificOutput.permissionDecisionReason // "Blocked by Synkro"' 2>/dev/null)
|
|
2566
|
+
synkro_log "editGuard $BASENAME \u2192 BLOCK: $REASON"
|
|
2567
|
+
jq -n --arg user "$REASON" --arg agent "$REASON" \\
|
|
2568
|
+
'{permission: "deny", user_message: $user, agent_message: $agent}'
|
|
2569
|
+
;;
|
|
2570
|
+
*)
|
|
2571
|
+
synkro_log "editGuard $BASENAME \u2192 pass"
|
|
2572
|
+
echo '{}'
|
|
2573
|
+
;;
|
|
2574
|
+
esac
|
|
2575
|
+
`;
|
|
2576
|
+
CURSOR_EDIT_CAPTURE_SCRIPT = `#!/bin/bash
|
|
2577
|
+
# Synkro afterFileEdit hook for Cursor \u2014 fire-and-forget telemetry + CVE scan.
|
|
2578
|
+
# Cannot block (Cursor afterFileEdit is observational only).
|
|
2579
|
+
|
|
2580
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2581
|
+
# shellcheck disable=SC1091
|
|
2582
|
+
. "$SCRIPT_DIR/_synkro-common.sh"
|
|
2583
|
+
|
|
2584
|
+
JWT=$(synkro_load_jwt)
|
|
2585
|
+
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2586
|
+
|
|
2587
|
+
PAYLOAD=$(cat)
|
|
2588
|
+
if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
|
|
2589
|
+
|
|
2590
|
+
FILE_PATH=$(echo "$PAYLOAD" | jq -r '.file_path // empty' 2>/dev/null)
|
|
2591
|
+
if [ -z "$FILE_PATH" ]; then echo '{}'; exit 0; fi
|
|
2592
|
+
|
|
2593
|
+
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // .workspace_roots[0] // empty' 2>/dev/null)
|
|
2594
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.conversation_id // empty' 2>/dev/null)
|
|
2595
|
+
GIT_REPO=$(synkro_detect_repo "\${CWD:-.}")
|
|
2596
|
+
BASENAME=$(basename "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")
|
|
2597
|
+
|
|
2598
|
+
# Read full file content for edit scan
|
|
2599
|
+
FULL_CONTENT=""
|
|
2600
|
+
FULL_PATH=""
|
|
2601
|
+
if [ -n "$CWD" ]; then
|
|
2602
|
+
FULL_PATH="$CWD/$FILE_PATH"
|
|
2603
|
+
else
|
|
2604
|
+
FULL_PATH="$FILE_PATH"
|
|
2605
|
+
fi
|
|
2606
|
+
if [ -f "$FULL_PATH" ]; then
|
|
2607
|
+
FULL_CONTENT=$(head -c 50000 "$FULL_PATH" 2>/dev/null || true)
|
|
2608
|
+
fi
|
|
2609
|
+
|
|
2610
|
+
# Extract deps from nearest package.json
|
|
2611
|
+
DEPS_JSON="{}"
|
|
2612
|
+
_PKG_DIR="\${CWD:-.}"
|
|
2613
|
+
while [ "$_PKG_DIR" != "/" ]; do
|
|
2614
|
+
if [ -f "$_PKG_DIR/package.json" ]; then
|
|
2615
|
+
DEPS_JSON=$(jq -c '(.dependencies // {}) + (.devDependencies // {})' "$_PKG_DIR/package.json" 2>/dev/null || echo "{}")
|
|
2616
|
+
break
|
|
2617
|
+
fi
|
|
2618
|
+
_PKG_DIR=$(dirname "$_PKG_DIR")
|
|
2619
|
+
done
|
|
2620
|
+
|
|
2621
|
+
synkro_log "editScan $BASENAME"
|
|
2622
|
+
|
|
2623
|
+
# Fire-and-forget: edit scan + CVE scan in background
|
|
2624
|
+
(
|
|
2625
|
+
BODY=$(jq -n \\
|
|
2626
|
+
--arg file_path "$FILE_PATH" \\
|
|
2627
|
+
--arg content "$FULL_CONTENT" \\
|
|
2628
|
+
--arg session_id "$SESSION_ID" \\
|
|
2629
|
+
--arg cwd "$CWD" \\
|
|
2630
|
+
--arg repo "$GIT_REPO" \\
|
|
2631
|
+
--argjson deps "$DEPS_JSON" \\
|
|
2632
|
+
'{
|
|
2633
|
+
file_path: $file_path,
|
|
2634
|
+
content: $content,
|
|
2635
|
+
dependencies: $deps,
|
|
2636
|
+
session_id: (if ($session_id | length) > 0 then $session_id else null end),
|
|
2637
|
+
cwd: (if ($cwd | length) > 0 then $cwd else null end),
|
|
2638
|
+
repo: (if ($repo | length) > 0 then $repo else null end),
|
|
2639
|
+
ide: "cursor"
|
|
2640
|
+
}')
|
|
2641
|
+
|
|
2642
|
+
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/edit-scan" \\
|
|
2643
|
+
-H "Content-Type: application/json" \\
|
|
2644
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2645
|
+
-d "$BODY" \\
|
|
2646
|
+
--max-time 10 >/dev/null 2>&1 || true
|
|
2647
|
+
) &
|
|
2648
|
+
disown 2>/dev/null || true
|
|
2649
|
+
|
|
2650
|
+
echo '{}'
|
|
2651
|
+
exit 0
|
|
2652
|
+
`;
|
|
2653
|
+
CURSOR_BASH_FOLLOWUP_SCRIPT = `#!/bin/bash
|
|
2654
|
+
# Synkro postToolUse hook for Cursor \u2014 fire-and-forget follow-up telemetry.
|
|
2655
|
+
# Marks bash judgments as "allowed" after successful execution.
|
|
2656
|
+
|
|
2657
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
2658
|
+
# shellcheck disable=SC1091
|
|
2659
|
+
. "$SCRIPT_DIR/_synkro-common.sh"
|
|
2660
|
+
|
|
2661
|
+
JWT=$(synkro_load_jwt)
|
|
2662
|
+
if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
|
|
2663
|
+
|
|
2664
|
+
PAYLOAD=$(cat)
|
|
2665
|
+
if [ -z "$PAYLOAD" ]; then echo '{}'; exit 0; fi
|
|
2666
|
+
|
|
2667
|
+
TOOL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
2668
|
+
case "$TOOL_NAME" in
|
|
2669
|
+
Shell|Bash|terminal|run_terminal_cmd|execute_command) ;;
|
|
2670
|
+
*) echo '{}'; exit 0 ;;
|
|
2671
|
+
esac
|
|
2672
|
+
|
|
2673
|
+
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.conversation_id // empty' 2>/dev/null)
|
|
2674
|
+
TOOL_USE_ID=$(echo "$PAYLOAD" | jq -r '.tool_use_id // empty' 2>/dev/null)
|
|
2675
|
+
|
|
2676
|
+
if [ -n "$SESSION_ID" ] && [ -n "$TOOL_USE_ID" ]; then
|
|
2677
|
+
(
|
|
2678
|
+
BODY=$(jq -n --arg sid "$SESSION_ID" --arg tid "$TOOL_USE_ID" \\
|
|
2679
|
+
'{session_id: $sid, tool_use_id: $tid, decision: "allow"}')
|
|
2680
|
+
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/bash-followup" \\
|
|
2681
|
+
-H "Content-Type: application/json" \\
|
|
2682
|
+
-H "Authorization: Bearer $JWT" \\
|
|
2683
|
+
-d "$BODY" \\
|
|
2684
|
+
--max-time 3 >/dev/null 2>&1 || true
|
|
2685
|
+
) &
|
|
2686
|
+
disown 2>/dev/null || true
|
|
2687
|
+
fi
|
|
2688
|
+
|
|
2185
2689
|
echo '{}'
|
|
2186
2690
|
exit 0
|
|
2187
2691
|
`;
|
|
@@ -2190,9 +2694,9 @@ exit 0
|
|
|
2190
2694
|
|
|
2191
2695
|
// cli/auth/stub.ts
|
|
2192
2696
|
import { createServer } from "http";
|
|
2193
|
-
import { writeFileSync as
|
|
2697
|
+
import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
2194
2698
|
import { homedir as homedir3, platform } from "os";
|
|
2195
|
-
import { join as join3, dirname as
|
|
2699
|
+
import { join as join3, dirname as dirname4 } from "path";
|
|
2196
2700
|
import { execFile } from "child_process";
|
|
2197
2701
|
import jwt from "jsonwebtoken";
|
|
2198
2702
|
function openBrowser(url) {
|
|
@@ -2220,18 +2724,18 @@ function openBrowser(url) {
|
|
|
2220
2724
|
});
|
|
2221
2725
|
}
|
|
2222
2726
|
function saveCredentials(data) {
|
|
2223
|
-
const dir =
|
|
2224
|
-
if (!
|
|
2225
|
-
|
|
2727
|
+
const dir = dirname4(AUTH_FILE);
|
|
2728
|
+
if (!existsSync5(dir)) {
|
|
2729
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
2226
2730
|
}
|
|
2227
|
-
|
|
2731
|
+
writeFileSync4(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
|
|
2228
2732
|
}
|
|
2229
2733
|
function loadCredentials() {
|
|
2230
|
-
if (!
|
|
2734
|
+
if (!existsSync5(AUTH_FILE)) {
|
|
2231
2735
|
return null;
|
|
2232
2736
|
}
|
|
2233
2737
|
try {
|
|
2234
|
-
const content =
|
|
2738
|
+
const content = readFileSync4(AUTH_FILE, "utf8");
|
|
2235
2739
|
return JSON.parse(content);
|
|
2236
2740
|
} catch (error) {
|
|
2237
2741
|
return null;
|
|
@@ -2475,7 +2979,7 @@ async function ensureValidToken() {
|
|
|
2475
2979
|
return true;
|
|
2476
2980
|
}
|
|
2477
2981
|
function clearCredentials() {
|
|
2478
|
-
if (
|
|
2982
|
+
if (existsSync5(AUTH_FILE)) {
|
|
2479
2983
|
unlinkSync2(AUTH_FILE);
|
|
2480
2984
|
}
|
|
2481
2985
|
}
|
|
@@ -2649,7 +3153,7 @@ jobs:
|
|
|
2649
3153
|
});
|
|
2650
3154
|
|
|
2651
3155
|
// cli/installer/githubSetup.ts
|
|
2652
|
-
import { existsSync as
|
|
3156
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
2653
3157
|
import { execSync as execSync2 } from "child_process";
|
|
2654
3158
|
import { join as join4 } from "path";
|
|
2655
3159
|
function ghSecretSet(token, owner, repo, name, value) {
|
|
@@ -2698,15 +3202,15 @@ async function pushSecretsToRepo(opts, owner, repo, secrets) {
|
|
|
2698
3202
|
}
|
|
2699
3203
|
function writeWorkflowFile(repoRootPath) {
|
|
2700
3204
|
const workflowDir = join4(repoRootPath, ".github", "workflows");
|
|
2701
|
-
|
|
3205
|
+
mkdirSync5(workflowDir, { recursive: true });
|
|
2702
3206
|
const workflowFile = join4(workflowDir, "synkro.yml");
|
|
2703
|
-
|
|
3207
|
+
writeFileSync5(workflowFile, SYNKRO_WORKFLOW_YAML, "utf-8");
|
|
2704
3208
|
return workflowFile;
|
|
2705
3209
|
}
|
|
2706
3210
|
function findGitRoot(startCwd) {
|
|
2707
3211
|
let cur = startCwd;
|
|
2708
3212
|
while (cur && cur !== "/") {
|
|
2709
|
-
if (
|
|
3213
|
+
if (existsSync6(join4(cur, ".git"))) return cur;
|
|
2710
3214
|
const parent = join4(cur, "..");
|
|
2711
3215
|
if (parent === cur) break;
|
|
2712
3216
|
cur = parent;
|
|
@@ -2949,14 +3453,14 @@ __export(setupGithub_exports, {
|
|
|
2949
3453
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
2950
3454
|
import { stdin as input, stdout as output } from "process";
|
|
2951
3455
|
import { execSync as execSync4, spawn as nodeSpawn } from "child_process";
|
|
2952
|
-
import { existsSync as
|
|
3456
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
2953
3457
|
import { homedir as homedir4, platform as platform2 } from "os";
|
|
2954
3458
|
import { join as join5 } from "path";
|
|
2955
3459
|
import { execFile as execFile2 } from "child_process";
|
|
2956
3460
|
function readConfig() {
|
|
2957
|
-
if (!
|
|
3461
|
+
if (!existsSync7(CONFIG_PATH)) return {};
|
|
2958
3462
|
const out = {};
|
|
2959
|
-
for (const line of
|
|
3463
|
+
for (const line of readFileSync5(CONFIG_PATH, "utf-8").split("\n")) {
|
|
2960
3464
|
const t = line.trim();
|
|
2961
3465
|
if (!t || t.startsWith("#")) continue;
|
|
2962
3466
|
const eq = t.indexOf("=");
|
|
@@ -3026,7 +3530,7 @@ function captureClaudeSetupToken() {
|
|
|
3026
3530
|
proc.on("close", (code) => {
|
|
3027
3531
|
let raw = "";
|
|
3028
3532
|
try {
|
|
3029
|
-
raw =
|
|
3533
|
+
raw = readFileSync5(tmpFile, "utf-8");
|
|
3030
3534
|
} catch (e) {
|
|
3031
3535
|
reject(new Error(`Could not read script output file: ${e.message}`));
|
|
3032
3536
|
return;
|
|
@@ -3304,20 +3808,20 @@ var init_setupGithub = __esm({
|
|
|
3304
3808
|
});
|
|
3305
3809
|
|
|
3306
3810
|
// cli/installer/promptFetcher.ts
|
|
3307
|
-
import { existsSync as
|
|
3811
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
3308
3812
|
import { homedir as homedir5 } from "os";
|
|
3309
3813
|
import { join as join6 } from "path";
|
|
3310
3814
|
function readCache() {
|
|
3311
|
-
if (!
|
|
3815
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
3312
3816
|
try {
|
|
3313
|
-
return JSON.parse(
|
|
3817
|
+
return JSON.parse(readFileSync6(CACHE_PATH, "utf-8"));
|
|
3314
3818
|
} catch {
|
|
3315
3819
|
return null;
|
|
3316
3820
|
}
|
|
3317
3821
|
}
|
|
3318
3822
|
function writeCache(entry) {
|
|
3319
|
-
|
|
3320
|
-
|
|
3823
|
+
mkdirSync6(join6(homedir5(), ".synkro", "prompts"), { recursive: true });
|
|
3824
|
+
writeFileSync6(CACHE_PATH, JSON.stringify(entry, null, 2), "utf-8");
|
|
3321
3825
|
}
|
|
3322
3826
|
function isCacheFresh(cache) {
|
|
3323
3827
|
const ageMs = Date.now() - cache.fetched_at;
|
|
@@ -3371,13 +3875,13 @@ var init_promptFetcher = __esm({
|
|
|
3371
3875
|
});
|
|
3372
3876
|
|
|
3373
3877
|
// cli/local-cc/settings.ts
|
|
3374
|
-
import { existsSync as
|
|
3878
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
3375
3879
|
import { homedir as homedir6 } from "os";
|
|
3376
3880
|
import { join as join7 } from "path";
|
|
3377
3881
|
function isLocalCCEnabled() {
|
|
3378
|
-
if (!
|
|
3882
|
+
if (!existsSync9(CONFIG_PATH2)) return false;
|
|
3379
3883
|
try {
|
|
3380
|
-
const content =
|
|
3884
|
+
const content = readFileSync7(CONFIG_PATH2, "utf-8");
|
|
3381
3885
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
3382
3886
|
return match?.[1] === "yes";
|
|
3383
3887
|
} catch {
|
|
@@ -3542,17 +4046,17 @@ await mcp.connect(new StdioServerTransport());
|
|
|
3542
4046
|
});
|
|
3543
4047
|
|
|
3544
4048
|
// cli/local-cc/install.ts
|
|
3545
|
-
import { existsSync as
|
|
4049
|
+
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
4050
|
import { join as join8 } from "path";
|
|
3547
4051
|
import { homedir as homedir7 } from "os";
|
|
3548
4052
|
import { spawnSync } from "child_process";
|
|
3549
4053
|
function writePluginFiles() {
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
4054
|
+
mkdirSync7(SESSION_DIR, { recursive: true });
|
|
4055
|
+
mkdirSync7(PLUGIN_SETTINGS_DIR, { recursive: true });
|
|
4056
|
+
writeFileSync7(PLUGIN_PATH, CHANNEL_PLUGIN_SOURCE, "utf-8");
|
|
3553
4057
|
chmodSync(PLUGIN_PATH, 493);
|
|
3554
|
-
|
|
3555
|
-
|
|
4058
|
+
writeFileSync7(PLUGIN_PKG_PATH, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
4059
|
+
writeFileSync7(
|
|
3556
4060
|
PLUGIN_SETTINGS_PATH,
|
|
3557
4061
|
JSON.stringify({
|
|
3558
4062
|
fastMode: true,
|
|
@@ -3564,7 +4068,7 @@ function writePluginFiles() {
|
|
|
3564
4068
|
}, null, 2) + "\n",
|
|
3565
4069
|
"utf-8"
|
|
3566
4070
|
);
|
|
3567
|
-
|
|
4071
|
+
writeFileSync7(RUN_SCRIPT_PATH, RUN_SCRIPT_SOURCE, "utf-8");
|
|
3568
4072
|
chmodSync(RUN_SCRIPT_PATH, 493);
|
|
3569
4073
|
}
|
|
3570
4074
|
function runBunInstall() {
|
|
@@ -3580,10 +4084,10 @@ function runBunInstall() {
|
|
|
3580
4084
|
}
|
|
3581
4085
|
}
|
|
3582
4086
|
function safelyMutateClaudeJson(mutator) {
|
|
3583
|
-
if (!
|
|
4087
|
+
if (!existsSync10(CLAUDE_JSON_PATH)) {
|
|
3584
4088
|
return;
|
|
3585
4089
|
}
|
|
3586
|
-
const originalText =
|
|
4090
|
+
const originalText = readFileSync8(CLAUDE_JSON_PATH, "utf-8");
|
|
3587
4091
|
let parsed;
|
|
3588
4092
|
try {
|
|
3589
4093
|
parsed = JSON.parse(originalText);
|
|
@@ -3615,14 +4119,14 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
3615
4119
|
copyFileSync(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
|
|
3616
4120
|
const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
|
|
3617
4121
|
try {
|
|
3618
|
-
|
|
4122
|
+
writeFileSync7(tmpPath, newText, "utf-8");
|
|
3619
4123
|
const fd = openSync(tmpPath, "r");
|
|
3620
4124
|
try {
|
|
3621
4125
|
fsyncSync(fd);
|
|
3622
4126
|
} finally {
|
|
3623
4127
|
closeSync(fd);
|
|
3624
4128
|
}
|
|
3625
|
-
|
|
4129
|
+
renameSync4(tmpPath, CLAUDE_JSON_PATH);
|
|
3626
4130
|
} catch (err) {
|
|
3627
4131
|
try {
|
|
3628
4132
|
unlinkSync4(tmpPath);
|
|
@@ -3647,7 +4151,7 @@ function writeProjectMcpJson() {
|
|
|
3647
4151
|
}
|
|
3648
4152
|
}
|
|
3649
4153
|
};
|
|
3650
|
-
|
|
4154
|
+
writeFileSync7(PROJECT_MCP_PATH, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
|
|
3651
4155
|
}
|
|
3652
4156
|
function patchClaudeJson() {
|
|
3653
4157
|
safelyMutateClaudeJson((parsed) => {
|
|
@@ -3967,16 +4471,16 @@ var init_pueue = __esm({
|
|
|
3967
4471
|
});
|
|
3968
4472
|
|
|
3969
4473
|
// cli/local-cc/prompts.ts
|
|
3970
|
-
import { existsSync as
|
|
4474
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
3971
4475
|
import { homedir as homedir9 } from "os";
|
|
3972
4476
|
import { join as join10 } from "path";
|
|
3973
4477
|
function loadCachedPrompts() {
|
|
3974
4478
|
if (_cached) return _cached;
|
|
3975
|
-
if (!
|
|
4479
|
+
if (!existsSync11(CACHE_PATH2)) {
|
|
3976
4480
|
throw new Error("Prompts cache not found. Run `synkro install` or `synkro update` first.");
|
|
3977
4481
|
}
|
|
3978
4482
|
try {
|
|
3979
|
-
_cached = JSON.parse(
|
|
4483
|
+
_cached = JSON.parse(readFileSync9(CACHE_PATH2, "utf-8"));
|
|
3980
4484
|
return _cached;
|
|
3981
4485
|
} catch {
|
|
3982
4486
|
throw new Error("Prompts cache is corrupted. Run `synkro update` to refresh.");
|
|
@@ -3993,23 +4497,32 @@ function getPrimer(role) {
|
|
|
3993
4497
|
function buildChannelContent(role, payload) {
|
|
3994
4498
|
return `${getPrimer(role)}
|
|
3995
4499
|
|
|
4500
|
+
${CHANNEL_REPLY_INSTRUCTIONS}
|
|
4501
|
+
|
|
3996
4502
|
---
|
|
3997
4503
|
PAYLOAD (the input to evaluate):
|
|
3998
4504
|
|
|
3999
4505
|
${payload}`;
|
|
4000
4506
|
}
|
|
4001
|
-
var CACHE_PATH2, _cached;
|
|
4507
|
+
var CACHE_PATH2, _cached, CHANNEL_REPLY_INSTRUCTIONS;
|
|
4002
4508
|
var init_prompts = __esm({
|
|
4003
4509
|
"cli/local-cc/prompts.ts"() {
|
|
4004
4510
|
"use strict";
|
|
4005
4511
|
CACHE_PATH2 = join10(homedir9(), ".synkro", "prompts", "judge-prompts.json");
|
|
4006
4512
|
_cached = null;
|
|
4513
|
+
CHANNEL_REPLY_INSTRUCTIONS = `
|
|
4514
|
+
DELIVERY METHOD \u2014 MANDATORY, OVERRIDES ALL OTHER OUTPUT RULES:
|
|
4515
|
+
You are running inside a Synkro MCP channel. Do NOT output your verdict as text.
|
|
4516
|
+
Instead, after generating your verdict, call the \`reply\` tool EXACTLY ONCE with:
|
|
4517
|
+
- req_id: the req_id from this channel event's meta
|
|
4518
|
+
- result: your complete verdict block as a string (the <synkro-verdict>\u2026</synkro-verdict> XML)
|
|
4519
|
+
Any text output is silently discarded. Only the reply tool call is captured.`;
|
|
4007
4520
|
}
|
|
4008
4521
|
});
|
|
4009
4522
|
|
|
4010
4523
|
// cli/local-cc/turnLog.ts
|
|
4011
|
-
import { appendFileSync, existsSync as
|
|
4012
|
-
import { dirname as
|
|
4524
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync8, openSync as openSync2, readFileSync as readFileSync10, readSync, closeSync as closeSync2, statSync, watchFile, unwatchFile } from "fs";
|
|
4525
|
+
import { dirname as dirname5, join as join11 } from "path";
|
|
4013
4526
|
import { homedir as homedir10 } from "os";
|
|
4014
4527
|
function truncate(s, max = PREVIEW_MAX) {
|
|
4015
4528
|
if (s.length <= max) return s;
|
|
@@ -4030,7 +4543,7 @@ function extractSeverity(result) {
|
|
|
4030
4543
|
}
|
|
4031
4544
|
function appendTurn(args2) {
|
|
4032
4545
|
try {
|
|
4033
|
-
|
|
4546
|
+
mkdirSync8(dirname5(TURN_LOG_PATH), { recursive: true });
|
|
4034
4547
|
const entry = {
|
|
4035
4548
|
ts: new Date(args2.startedAt).toISOString(),
|
|
4036
4549
|
role: args2.role,
|
|
@@ -4046,11 +4559,11 @@ function appendTurn(args2) {
|
|
|
4046
4559
|
}
|
|
4047
4560
|
}
|
|
4048
4561
|
function readRecentTurns(n = 20) {
|
|
4049
|
-
if (!
|
|
4562
|
+
if (!existsSync12(TURN_LOG_PATH)) return [];
|
|
4050
4563
|
try {
|
|
4051
4564
|
const size = statSync(TURN_LOG_PATH).size;
|
|
4052
4565
|
if (size === 0) return [];
|
|
4053
|
-
const text =
|
|
4566
|
+
const text = readFileSync10(TURN_LOG_PATH, "utf-8");
|
|
4054
4567
|
const lines = text.split("\n").filter(Boolean);
|
|
4055
4568
|
const lastN = lines.slice(-n).reverse();
|
|
4056
4569
|
return lastN.map((line) => {
|
|
@@ -4066,8 +4579,8 @@ function readRecentTurns(n = 20) {
|
|
|
4066
4579
|
}
|
|
4067
4580
|
function followTurns(onEntry) {
|
|
4068
4581
|
try {
|
|
4069
|
-
|
|
4070
|
-
if (!
|
|
4582
|
+
mkdirSync8(dirname5(TURN_LOG_PATH), { recursive: true });
|
|
4583
|
+
if (!existsSync12(TURN_LOG_PATH)) {
|
|
4071
4584
|
appendFileSync(TURN_LOG_PATH, "", "utf-8");
|
|
4072
4585
|
}
|
|
4073
4586
|
} catch {
|
|
@@ -4235,7 +4748,7 @@ __export(install_exports, {
|
|
|
4235
4748
|
installCommand: () => installCommand,
|
|
4236
4749
|
parseArgs: () => parseArgs
|
|
4237
4750
|
});
|
|
4238
|
-
import { existsSync as
|
|
4751
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, chmodSync as chmodSync2, readFileSync as readFileSync11, readdirSync } from "fs";
|
|
4239
4752
|
import { homedir as homedir11 } from "os";
|
|
4240
4753
|
import { join as join12 } from "path";
|
|
4241
4754
|
import { execSync as execSync5 } from "child_process";
|
|
@@ -4274,10 +4787,10 @@ async function promptTranscriptConsent() {
|
|
|
4274
4787
|
});
|
|
4275
4788
|
}
|
|
4276
4789
|
function ensureSynkroDir() {
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4790
|
+
mkdirSync9(SYNKRO_DIR2, { recursive: true });
|
|
4791
|
+
mkdirSync9(HOOKS_DIR, { recursive: true });
|
|
4792
|
+
mkdirSync9(BIN_DIR, { recursive: true });
|
|
4793
|
+
mkdirSync9(OFFSETS_DIR, { recursive: true });
|
|
4281
4794
|
}
|
|
4282
4795
|
function writeHookScripts() {
|
|
4283
4796
|
const bashScriptPath = join12(HOOKS_DIR, "cc-bash-judge.sh");
|
|
@@ -4287,13 +4800,23 @@ function writeHookScripts() {
|
|
|
4287
4800
|
const stopSummaryScriptPath = join12(HOOKS_DIR, "cc-stop-summary.sh");
|
|
4288
4801
|
const sessionStartScriptPath = join12(HOOKS_DIR, "cc-session-start.sh");
|
|
4289
4802
|
const transcriptSyncScriptPath = join12(HOOKS_DIR, "cc-transcript-sync.sh");
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4803
|
+
const commonScriptPath = join12(HOOKS_DIR, "_synkro-common.sh");
|
|
4804
|
+
const cursorBashJudgePath = join12(HOOKS_DIR, "cursor-bash-judge.sh");
|
|
4805
|
+
const cursorEditPrecheckPath = join12(HOOKS_DIR, "cursor-edit-precheck.sh");
|
|
4806
|
+
const cursorEditCapturePath = join12(HOOKS_DIR, "cursor-edit-capture.sh");
|
|
4807
|
+
const cursorBashFollowupPath = join12(HOOKS_DIR, "cursor-bash-followup.sh");
|
|
4808
|
+
writeFileSync8(bashScriptPath, CC_BASH_JUDGE_SCRIPT, "utf-8");
|
|
4809
|
+
writeFileSync8(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, "utf-8");
|
|
4810
|
+
writeFileSync8(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, "utf-8");
|
|
4811
|
+
writeFileSync8(editPrecheckScriptPath, CC_EDIT_PRECHECK_SCRIPT, "utf-8");
|
|
4812
|
+
writeFileSync8(stopSummaryScriptPath, CC_STOP_SUMMARY_SCRIPT, "utf-8");
|
|
4813
|
+
writeFileSync8(sessionStartScriptPath, CC_SESSION_START_SCRIPT, "utf-8");
|
|
4814
|
+
writeFileSync8(transcriptSyncScriptPath, CC_TRANSCRIPT_SYNC_SCRIPT, "utf-8");
|
|
4815
|
+
writeFileSync8(commonScriptPath, SYNKRO_COMMON_SCRIPT, "utf-8");
|
|
4816
|
+
writeFileSync8(cursorBashJudgePath, CURSOR_BASH_JUDGE_SCRIPT, "utf-8");
|
|
4817
|
+
writeFileSync8(cursorEditPrecheckPath, CURSOR_EDIT_PRECHECK_SCRIPT, "utf-8");
|
|
4818
|
+
writeFileSync8(cursorEditCapturePath, CURSOR_EDIT_CAPTURE_SCRIPT, "utf-8");
|
|
4819
|
+
writeFileSync8(cursorBashFollowupPath, CURSOR_BASH_FOLLOWUP_SCRIPT, "utf-8");
|
|
4297
4820
|
chmodSync2(bashScriptPath, 493);
|
|
4298
4821
|
chmodSync2(bashFollowupScriptPath, 493);
|
|
4299
4822
|
chmodSync2(editCaptureScriptPath, 493);
|
|
@@ -4301,6 +4824,11 @@ function writeHookScripts() {
|
|
|
4301
4824
|
chmodSync2(stopSummaryScriptPath, 493);
|
|
4302
4825
|
chmodSync2(sessionStartScriptPath, 493);
|
|
4303
4826
|
chmodSync2(transcriptSyncScriptPath, 493);
|
|
4827
|
+
chmodSync2(commonScriptPath, 493);
|
|
4828
|
+
chmodSync2(cursorBashJudgePath, 493);
|
|
4829
|
+
chmodSync2(cursorEditPrecheckPath, 493);
|
|
4830
|
+
chmodSync2(cursorEditCapturePath, 493);
|
|
4831
|
+
chmodSync2(cursorBashFollowupPath, 493);
|
|
4304
4832
|
return {
|
|
4305
4833
|
bashScript: bashScriptPath,
|
|
4306
4834
|
bashFollowupScript: bashFollowupScriptPath,
|
|
@@ -4308,7 +4836,11 @@ function writeHookScripts() {
|
|
|
4308
4836
|
editPrecheckScript: editPrecheckScriptPath,
|
|
4309
4837
|
stopSummaryScript: stopSummaryScriptPath,
|
|
4310
4838
|
sessionStartScript: sessionStartScriptPath,
|
|
4311
|
-
transcriptSyncScript: transcriptSyncScriptPath
|
|
4839
|
+
transcriptSyncScript: transcriptSyncScriptPath,
|
|
4840
|
+
cursorBashJudgeScript: cursorBashJudgePath,
|
|
4841
|
+
cursorEditPrecheckScript: cursorEditPrecheckPath,
|
|
4842
|
+
cursorEditCaptureScript: cursorEditCapturePath,
|
|
4843
|
+
cursorBashFollowupScript: cursorBashFollowupPath
|
|
4312
4844
|
};
|
|
4313
4845
|
}
|
|
4314
4846
|
function sanitizeConfigValue(raw, maxLen = 256) {
|
|
@@ -4320,7 +4852,7 @@ function shellQuoteSingle(value) {
|
|
|
4320
4852
|
}
|
|
4321
4853
|
function resolveSynkroBundle() {
|
|
4322
4854
|
const scriptPath = process.argv[1];
|
|
4323
|
-
if (scriptPath &&
|
|
4855
|
+
if (scriptPath && existsSync13(scriptPath)) return scriptPath;
|
|
4324
4856
|
return null;
|
|
4325
4857
|
}
|
|
4326
4858
|
function writeConfigEnv(opts) {
|
|
@@ -4340,7 +4872,7 @@ function writeConfigEnv(opts) {
|
|
|
4340
4872
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
4341
4873
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
4342
4874
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
4343
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.4.
|
|
4875
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.4.8")}`
|
|
4344
4876
|
];
|
|
4345
4877
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
4346
4878
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -4351,12 +4883,12 @@ function writeConfigEnv(opts) {
|
|
|
4351
4883
|
}
|
|
4352
4884
|
lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle(opts.localInference ? "yes" : "no")}`);
|
|
4353
4885
|
lines.push("");
|
|
4354
|
-
|
|
4886
|
+
writeFileSync8(CONFIG_PATH3, lines.join("\n"), "utf-8");
|
|
4355
4887
|
chmodSync2(CONFIG_PATH3, 384);
|
|
4356
4888
|
}
|
|
4357
4889
|
function updateLocalInferenceFlag(enabled) {
|
|
4358
|
-
if (!
|
|
4359
|
-
let content =
|
|
4890
|
+
if (!existsSync13(CONFIG_PATH3)) return;
|
|
4891
|
+
let content = readFileSync11(CONFIG_PATH3, "utf-8");
|
|
4360
4892
|
const flag = enabled ? "yes" : "no";
|
|
4361
4893
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
4362
4894
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -4365,7 +4897,7 @@ function updateLocalInferenceFlag(enabled) {
|
|
|
4365
4897
|
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
4366
4898
|
`;
|
|
4367
4899
|
}
|
|
4368
|
-
|
|
4900
|
+
writeFileSync8(CONFIG_PATH3, content, "utf-8");
|
|
4369
4901
|
}
|
|
4370
4902
|
function collectLocalMetadata() {
|
|
4371
4903
|
const meta = { platform: process.platform };
|
|
@@ -4387,14 +4919,14 @@ function collectLocalMetadata() {
|
|
|
4387
4919
|
}
|
|
4388
4920
|
const claudeDir = join12(homedir11(), ".claude");
|
|
4389
4921
|
try {
|
|
4390
|
-
const settings = JSON.parse(
|
|
4922
|
+
const settings = JSON.parse(readFileSync11(join12(claudeDir, "settings.json"), "utf-8"));
|
|
4391
4923
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
4392
4924
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
4393
4925
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
4394
4926
|
} catch {
|
|
4395
4927
|
}
|
|
4396
4928
|
try {
|
|
4397
|
-
const mcpCache = JSON.parse(
|
|
4929
|
+
const mcpCache = JSON.parse(readFileSync11(join12(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
4398
4930
|
const mcpNames = Object.keys(mcpCache);
|
|
4399
4931
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
4400
4932
|
} catch {
|
|
@@ -4409,7 +4941,7 @@ function collectLocalMetadata() {
|
|
|
4409
4941
|
const sessionsDir = join12(claudeDir, "sessions");
|
|
4410
4942
|
const files = readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
4411
4943
|
for (const f of files) {
|
|
4412
|
-
const s = JSON.parse(
|
|
4944
|
+
const s = JSON.parse(readFileSync11(join12(sessionsDir, f), "utf-8"));
|
|
4413
4945
|
if (s.version) {
|
|
4414
4946
|
meta.cc_version = meta.cc_version || s.version;
|
|
4415
4947
|
break;
|
|
@@ -4472,12 +5004,12 @@ function isAlreadyInstalled() {
|
|
|
4472
5004
|
join12(HOOKS_DIR, "cc-stop-summary.sh"),
|
|
4473
5005
|
join12(HOOKS_DIR, "cc-session-start.sh")
|
|
4474
5006
|
];
|
|
4475
|
-
if (!requiredScripts.every((p) =>
|
|
4476
|
-
if (!
|
|
5007
|
+
if (!requiredScripts.every((p) => existsSync13(p))) return false;
|
|
5008
|
+
if (!existsSync13(CONFIG_PATH3)) return false;
|
|
4477
5009
|
const settingsPath = join12(homedir11(), ".claude", "settings.json");
|
|
4478
|
-
if (!
|
|
5010
|
+
if (!existsSync13(settingsPath)) return false;
|
|
4479
5011
|
try {
|
|
4480
|
-
const settings = JSON.parse(
|
|
5012
|
+
const settings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
4481
5013
|
const hooks = settings?.hooks;
|
|
4482
5014
|
if (!hooks || typeof hooks !== "object") return false;
|
|
4483
5015
|
const hasManaged = (kind) => Array.isArray(hooks[kind]) && hooks[kind].some((entry) => entry?.__synkro_managed__ === true);
|
|
@@ -4610,7 +5142,7 @@ async function installCommand(opts = {}) {
|
|
|
4610
5142
|
for (const mode of ["edit", "bash"]) {
|
|
4611
5143
|
const pidFile = join12(SYNKRO_DIR2, "daemon", mode, "daemon.pid");
|
|
4612
5144
|
try {
|
|
4613
|
-
const pid = parseInt(
|
|
5145
|
+
const pid = parseInt(readFileSync11(pidFile, "utf-8").trim(), 10);
|
|
4614
5146
|
if (pid > 0) {
|
|
4615
5147
|
process.kill(pid, "SIGTERM");
|
|
4616
5148
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -4628,6 +5160,7 @@ async function installCommand(opts = {}) {
|
|
|
4628
5160
|
}
|
|
4629
5161
|
}
|
|
4630
5162
|
let hasClaudeCode = false;
|
|
5163
|
+
let hasCursor = false;
|
|
4631
5164
|
for (const agent of agents) {
|
|
4632
5165
|
if (agent.kind === "claude_code") {
|
|
4633
5166
|
hasClaudeCode = true;
|
|
@@ -4642,6 +5175,15 @@ async function installCommand(opts = {}) {
|
|
|
4642
5175
|
skipTranscriptSync: !transcriptConsent
|
|
4643
5176
|
});
|
|
4644
5177
|
console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
|
|
5178
|
+
} else if (agent.kind === "cursor") {
|
|
5179
|
+
hasCursor = true;
|
|
5180
|
+
installCursorHooks(agent.settingsPath, {
|
|
5181
|
+
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
5182
|
+
editPrecheckScriptPath: scripts.cursorEditPrecheckScript,
|
|
5183
|
+
editCaptureScriptPath: scripts.cursorEditCaptureScript,
|
|
5184
|
+
bashFollowupScriptPath: scripts.cursorBashFollowupScript
|
|
5185
|
+
});
|
|
5186
|
+
console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
|
|
4645
5187
|
}
|
|
4646
5188
|
}
|
|
4647
5189
|
console.log();
|
|
@@ -4764,7 +5306,7 @@ function getClaudeProjectsFolder() {
|
|
|
4764
5306
|
const cwd = process.cwd();
|
|
4765
5307
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
4766
5308
|
const projectsDir = join12(homedir11(), ".claude", "projects", sanitized);
|
|
4767
|
-
return
|
|
5309
|
+
return existsSync13(projectsDir) ? projectsDir : null;
|
|
4768
5310
|
}
|
|
4769
5311
|
function extractSessionInsights(projectsDir) {
|
|
4770
5312
|
const insights = [];
|
|
@@ -4773,7 +5315,7 @@ function extractSessionInsights(projectsDir) {
|
|
|
4773
5315
|
const sessionId = file.replace(".jsonl", "");
|
|
4774
5316
|
const filePath = join12(projectsDir, file);
|
|
4775
5317
|
try {
|
|
4776
|
-
const content =
|
|
5318
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
4777
5319
|
const lines = content.split("\n").filter(Boolean);
|
|
4778
5320
|
for (let i = 0; i < lines.length; i++) {
|
|
4779
5321
|
try {
|
|
@@ -4849,7 +5391,7 @@ function extractTextContent(content) {
|
|
|
4849
5391
|
return "";
|
|
4850
5392
|
}
|
|
4851
5393
|
function parseTranscriptFile(filePath) {
|
|
4852
|
-
const content =
|
|
5394
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
4853
5395
|
const lines = content.split("\n").filter(Boolean);
|
|
4854
5396
|
const messages = [];
|
|
4855
5397
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -4931,9 +5473,9 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
4931
5473
|
const sessionId = file.replace(".jsonl", "");
|
|
4932
5474
|
const filePath = join12(projectsDir, file);
|
|
4933
5475
|
try {
|
|
4934
|
-
const content =
|
|
5476
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
4935
5477
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
4936
|
-
|
|
5478
|
+
writeFileSync8(join12(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
4937
5479
|
} catch {
|
|
4938
5480
|
}
|
|
4939
5481
|
}
|
|
@@ -4946,6 +5488,7 @@ var init_install2 = __esm({
|
|
|
4946
5488
|
"use strict";
|
|
4947
5489
|
init_agentDetect();
|
|
4948
5490
|
init_ccHookConfig();
|
|
5491
|
+
init_cursorHookConfig();
|
|
4949
5492
|
init_mcpConfig();
|
|
4950
5493
|
init_hookScripts();
|
|
4951
5494
|
init_stub();
|
|
@@ -5037,13 +5580,13 @@ var status_exports = {};
|
|
|
5037
5580
|
__export(status_exports, {
|
|
5038
5581
|
statusCommand: () => statusCommand
|
|
5039
5582
|
});
|
|
5040
|
-
import { existsSync as
|
|
5583
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
5041
5584
|
import { homedir as homedir12 } from "os";
|
|
5042
5585
|
import { join as join13 } from "path";
|
|
5043
5586
|
function readConfigEnv() {
|
|
5044
|
-
if (!
|
|
5587
|
+
if (!existsSync14(CONFIG_PATH4)) return {};
|
|
5045
5588
|
const out = {};
|
|
5046
|
-
const raw =
|
|
5589
|
+
const raw = readFileSync12(CONFIG_PATH4, "utf-8");
|
|
5047
5590
|
for (const line of raw.split("\n")) {
|
|
5048
5591
|
const trimmed = line.trim();
|
|
5049
5592
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -5112,6 +5655,15 @@ async function statusCommand() {
|
|
|
5112
5655
|
console.log(` \u2022 SessionEnd summary: ${hooks.sessionEnd ? "\u2713" : "\u2717"}`);
|
|
5113
5656
|
console.log(` \u2022 SessionStart: ${hooks.sessionStart ? "\u2713" : "\u2717"}`);
|
|
5114
5657
|
}
|
|
5658
|
+
} else if (a.kind === "cursor") {
|
|
5659
|
+
const hooks = inspectCursorHooks(a.settingsPath);
|
|
5660
|
+
console.log(` hooks installed: ${hooks.installed ? "\u2713" : "\u2717"}`);
|
|
5661
|
+
if (hooks.installed) {
|
|
5662
|
+
console.log(` \u2022 beforeShellExecution: ${hooks.beforeShellExecution ? "\u2713" : "\u2717"}`);
|
|
5663
|
+
console.log(` \u2022 preToolUse: ${hooks.preToolUse ? "\u2713" : "\u2717"}`);
|
|
5664
|
+
console.log(` \u2022 afterFileEdit: ${hooks.afterFileEdit ? "\u2713" : "\u2717"}`);
|
|
5665
|
+
console.log(` \u2022 postToolUse: ${hooks.postToolUse ? "\u2713" : "\u2717"}`);
|
|
5666
|
+
}
|
|
5115
5667
|
}
|
|
5116
5668
|
}
|
|
5117
5669
|
}
|
|
@@ -5122,13 +5674,23 @@ async function statusCommand() {
|
|
|
5122
5674
|
const editCaptureScript = join13(SYNKRO_DIR3, "hooks", "cc-edit-capture.sh");
|
|
5123
5675
|
const stopSummaryScript = join13(SYNKRO_DIR3, "hooks", "cc-stop-summary.sh");
|
|
5124
5676
|
const sessionStartScript = join13(SYNKRO_DIR3, "hooks", "cc-session-start.sh");
|
|
5677
|
+
const cursorBashJudgeScript = join13(SYNKRO_DIR3, "hooks", "cursor-bash-judge.sh");
|
|
5678
|
+
const cursorEditPrecheckScript = join13(SYNKRO_DIR3, "hooks", "cursor-edit-precheck.sh");
|
|
5679
|
+
const cursorEditCaptureScript = join13(SYNKRO_DIR3, "hooks", "cursor-edit-capture.sh");
|
|
5680
|
+
const cursorBashFollowupScript = join13(SYNKRO_DIR3, "hooks", "cursor-bash-followup.sh");
|
|
5681
|
+
const commonScript = join13(SYNKRO_DIR3, "hooks", "_synkro-common.sh");
|
|
5125
5682
|
console.log("Hook scripts:");
|
|
5126
|
-
console.log(` ${
|
|
5127
|
-
console.log(` ${
|
|
5128
|
-
console.log(` ${
|
|
5129
|
-
console.log(` ${
|
|
5130
|
-
console.log(` ${
|
|
5131
|
-
console.log(` ${
|
|
5683
|
+
console.log(` ${existsSync14(bashScript) ? "\u2713" : "\u2717"} ${bashScript}`);
|
|
5684
|
+
console.log(` ${existsSync14(bashFollowupScript) ? "\u2713" : "\u2717"} ${bashFollowupScript}`);
|
|
5685
|
+
console.log(` ${existsSync14(editPrecheckScript) ? "\u2713" : "\u2717"} ${editPrecheckScript}`);
|
|
5686
|
+
console.log(` ${existsSync14(editCaptureScript) ? "\u2713" : "\u2717"} ${editCaptureScript}`);
|
|
5687
|
+
console.log(` ${existsSync14(stopSummaryScript) ? "\u2713" : "\u2717"} ${stopSummaryScript}`);
|
|
5688
|
+
console.log(` ${existsSync14(sessionStartScript) ? "\u2713" : "\u2717"} ${sessionStartScript}`);
|
|
5689
|
+
console.log(` ${existsSync14(commonScript) ? "\u2713" : "\u2717"} ${commonScript}`);
|
|
5690
|
+
console.log(` ${existsSync14(cursorBashJudgeScript) ? "\u2713" : "\u2717"} ${cursorBashJudgeScript}`);
|
|
5691
|
+
console.log(` ${existsSync14(cursorEditPrecheckScript) ? "\u2713" : "\u2717"} ${cursorEditPrecheckScript}`);
|
|
5692
|
+
console.log(` ${existsSync14(cursorEditCaptureScript) ? "\u2713" : "\u2717"} ${cursorEditCaptureScript}`);
|
|
5693
|
+
console.log(` ${existsSync14(cursorBashFollowupScript) ? "\u2713" : "\u2717"} ${cursorBashFollowupScript}`);
|
|
5132
5694
|
console.log();
|
|
5133
5695
|
const mcp = inspectMcpConfig();
|
|
5134
5696
|
console.log("Guardrails MCP server (Claude Code):");
|
|
@@ -5147,6 +5709,7 @@ var init_status = __esm({
|
|
|
5147
5709
|
init_stub();
|
|
5148
5710
|
init_agentDetect();
|
|
5149
5711
|
init_ccHookConfig();
|
|
5712
|
+
init_cursorHookConfig();
|
|
5150
5713
|
init_mcpConfig();
|
|
5151
5714
|
SYNKRO_DIR3 = join13(homedir12(), ".synkro");
|
|
5152
5715
|
CONFIG_PATH4 = join13(SYNKRO_DIR3, "config.env");
|
|
@@ -5238,13 +5801,13 @@ var config_exports = {};
|
|
|
5238
5801
|
__export(config_exports, {
|
|
5239
5802
|
configCommand: () => configCommand
|
|
5240
5803
|
});
|
|
5241
|
-
import { readFileSync as
|
|
5804
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync15 } from "fs";
|
|
5242
5805
|
import { join as join14 } from "path";
|
|
5243
5806
|
import { homedir as homedir13 } from "os";
|
|
5244
5807
|
function readConfigEnv2() {
|
|
5245
|
-
if (!
|
|
5808
|
+
if (!existsSync15(CONFIG_PATH5)) return {};
|
|
5246
5809
|
const out = {};
|
|
5247
|
-
for (const line of
|
|
5810
|
+
for (const line of readFileSync13(CONFIG_PATH5, "utf-8").split("\n")) {
|
|
5248
5811
|
const t = line.trim();
|
|
5249
5812
|
if (!t || t.startsWith("#")) continue;
|
|
5250
5813
|
const eq = t.indexOf("=");
|
|
@@ -5253,11 +5816,11 @@ function readConfigEnv2() {
|
|
|
5253
5816
|
return out;
|
|
5254
5817
|
}
|
|
5255
5818
|
function updateConfigValue(key, value) {
|
|
5256
|
-
if (!
|
|
5819
|
+
if (!existsSync15(CONFIG_PATH5)) {
|
|
5257
5820
|
console.error("No config found. Run `synkro install` first.");
|
|
5258
5821
|
process.exit(1);
|
|
5259
5822
|
}
|
|
5260
|
-
const lines =
|
|
5823
|
+
const lines = readFileSync13(CONFIG_PATH5, "utf-8").split("\n");
|
|
5261
5824
|
const pattern = new RegExp(`^${key}=`);
|
|
5262
5825
|
let found = false;
|
|
5263
5826
|
const updated = lines.map((line) => {
|
|
@@ -5268,7 +5831,7 @@ function updateConfigValue(key, value) {
|
|
|
5268
5831
|
return line;
|
|
5269
5832
|
});
|
|
5270
5833
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
5271
|
-
|
|
5834
|
+
writeFileSync9(CONFIG_PATH5, updated.join("\n"), "utf-8");
|
|
5272
5835
|
}
|
|
5273
5836
|
async function configCommand(args2) {
|
|
5274
5837
|
if (args2.length === 0) {
|
|
@@ -5335,7 +5898,7 @@ __export(scanPr_exports, {
|
|
|
5335
5898
|
scanPrCommand: () => scanPrCommand
|
|
5336
5899
|
});
|
|
5337
5900
|
import { execSync as execSync6, spawn as spawn2 } from "child_process";
|
|
5338
|
-
import { readFileSync as
|
|
5901
|
+
import { readFileSync as readFileSync14, existsSync as existsSync16 } from "fs";
|
|
5339
5902
|
import { join as join15 } from "path";
|
|
5340
5903
|
function parseMatchSpec(condition) {
|
|
5341
5904
|
if (!condition.startsWith("match_spec:")) return null;
|
|
@@ -5816,9 +6379,9 @@ function shouldFail(findings, threshold) {
|
|
|
5816
6379
|
}
|
|
5817
6380
|
function readRepoDeps() {
|
|
5818
6381
|
const pkgPath = join15(process.cwd(), "package.json");
|
|
5819
|
-
if (!
|
|
6382
|
+
if (!existsSync16(pkgPath)) return {};
|
|
5820
6383
|
try {
|
|
5821
|
-
const pkg = JSON.parse(
|
|
6384
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
5822
6385
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5823
6386
|
} catch {
|
|
5824
6387
|
return {};
|
|
@@ -6080,7 +6643,7 @@ var disconnect_exports = {};
|
|
|
6080
6643
|
__export(disconnect_exports, {
|
|
6081
6644
|
disconnectCommand: () => disconnectCommand
|
|
6082
6645
|
});
|
|
6083
|
-
import { existsSync as
|
|
6646
|
+
import { existsSync as existsSync17, rmSync } from "fs";
|
|
6084
6647
|
import { homedir as homedir14 } from "os";
|
|
6085
6648
|
import { join as join16 } from "path";
|
|
6086
6649
|
function tearDownLocalCC() {
|
|
@@ -6105,6 +6668,9 @@ function disconnectCommand(args2 = []) {
|
|
|
6105
6668
|
sawClaudeCode = true;
|
|
6106
6669
|
const removed = uninstallCCHooks(agent.settingsPath);
|
|
6107
6670
|
console.log(`${removed ? "\u2713" : "\xB7"} ${agent.name}: ${removed ? "removed Synkro hook entries" : "no Synkro hooks found"}`);
|
|
6671
|
+
} else if (agent.kind === "cursor") {
|
|
6672
|
+
const removed = uninstallCursorHooks(agent.settingsPath);
|
|
6673
|
+
console.log(`${removed ? "\u2713" : "\xB7"} ${agent.name}: ${removed ? "removed Synkro hook entries" : "no Synkro hooks found"}`);
|
|
6108
6674
|
}
|
|
6109
6675
|
}
|
|
6110
6676
|
if (sawClaudeCode) {
|
|
@@ -6112,13 +6678,13 @@ function disconnectCommand(args2 = []) {
|
|
|
6112
6678
|
console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
|
|
6113
6679
|
}
|
|
6114
6680
|
if (purge) {
|
|
6115
|
-
if (
|
|
6681
|
+
if (existsSync17(SYNKRO_DIR5)) {
|
|
6116
6682
|
rmSync(SYNKRO_DIR5, { recursive: true, force: true });
|
|
6117
6683
|
console.log(`\u2713 Removed ${SYNKRO_DIR5}`);
|
|
6118
6684
|
} else {
|
|
6119
6685
|
console.log(`\xB7 ${SYNKRO_DIR5} already gone, nothing to remove`);
|
|
6120
6686
|
}
|
|
6121
|
-
} else if (
|
|
6687
|
+
} else if (existsSync17(SYNKRO_DIR5)) {
|
|
6122
6688
|
console.log(`Config preserved at ${SYNKRO_DIR5}. Run with --purge to remove.`);
|
|
6123
6689
|
}
|
|
6124
6690
|
console.log("\nSynkro disconnected.");
|
|
@@ -6129,6 +6695,7 @@ var init_disconnect = __esm({
|
|
|
6129
6695
|
"use strict";
|
|
6130
6696
|
init_agentDetect();
|
|
6131
6697
|
init_ccHookConfig();
|
|
6698
|
+
init_cursorHookConfig();
|
|
6132
6699
|
init_mcpConfig();
|
|
6133
6700
|
init_pueue();
|
|
6134
6701
|
init_install();
|
|
@@ -6181,7 +6748,7 @@ __export(localCc_exports, {
|
|
|
6181
6748
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
6182
6749
|
import { homedir as homedir15 } from "os";
|
|
6183
6750
|
import { join as join17 } from "path";
|
|
6184
|
-
import { existsSync as
|
|
6751
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
6185
6752
|
function printHelp() {
|
|
6186
6753
|
console.log(`synkro local-cc \u2014 manage the local Claude Code inference session
|
|
6187
6754
|
|
|
@@ -6271,15 +6838,15 @@ TROUBLESHOOTING
|
|
|
6271
6838
|
`);
|
|
6272
6839
|
}
|
|
6273
6840
|
function readGatewayUrl() {
|
|
6274
|
-
if (
|
|
6275
|
-
const m =
|
|
6841
|
+
if (existsSync18(CONFIG_PATH6)) {
|
|
6842
|
+
const m = readFileSync15(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
6276
6843
|
if (m) return m[1];
|
|
6277
6844
|
}
|
|
6278
6845
|
return "https://api.synkro.sh";
|
|
6279
6846
|
}
|
|
6280
6847
|
function updateLocalInferenceFlag2(enabled) {
|
|
6281
|
-
if (!
|
|
6282
|
-
let content =
|
|
6848
|
+
if (!existsSync18(CONFIG_PATH6)) return;
|
|
6849
|
+
let content = readFileSync15(CONFIG_PATH6, "utf-8");
|
|
6283
6850
|
const flag = enabled ? "yes" : "no";
|
|
6284
6851
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
6285
6852
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -6288,7 +6855,7 @@ function updateLocalInferenceFlag2(enabled) {
|
|
|
6288
6855
|
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
6289
6856
|
`;
|
|
6290
6857
|
}
|
|
6291
|
-
|
|
6858
|
+
writeFileSync10(CONFIG_PATH6, content, "utf-8");
|
|
6292
6859
|
}
|
|
6293
6860
|
async function setServerGradingProvider(provider) {
|
|
6294
6861
|
await ensureValidToken();
|
|
@@ -6615,15 +7182,15 @@ var init_grade = __esm({
|
|
|
6615
7182
|
});
|
|
6616
7183
|
|
|
6617
7184
|
// cli/bootstrap.js
|
|
6618
|
-
import { readFileSync as
|
|
7185
|
+
import { readFileSync as readFileSync16, existsSync as existsSync19 } from "fs";
|
|
6619
7186
|
import { resolve } from "path";
|
|
6620
7187
|
var envCandidates = [
|
|
6621
7188
|
resolve(process.cwd(), ".env"),
|
|
6622
7189
|
resolve(process.env.HOME ?? "", ".synkro", "config.env")
|
|
6623
7190
|
];
|
|
6624
7191
|
for (const envPath of envCandidates) {
|
|
6625
|
-
if (!
|
|
6626
|
-
const envContent =
|
|
7192
|
+
if (!existsSync19(envPath)) continue;
|
|
7193
|
+
const envContent = readFileSync16(envPath, "utf-8");
|
|
6627
7194
|
for (const line of envContent.split("\n")) {
|
|
6628
7195
|
const trimmed = line.trim();
|
|
6629
7196
|
if (!trimmed || trimmed.startsWith("#")) continue;
|