@synkro-sh/cli 1.4.66 → 1.4.68
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 +633 -509
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -2
package/dist/bootstrap.js
CHANGED
|
@@ -283,22 +283,41 @@ var init_ccHookConfig = __esm({
|
|
|
283
283
|
});
|
|
284
284
|
|
|
285
285
|
// cli/installer/cursorHookConfig.ts
|
|
286
|
-
import {
|
|
287
|
-
import { dirname as dirname2 } from "path";
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync as renameSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
287
|
+
import { dirname as dirname2, resolve, normalize } from "path";
|
|
288
|
+
import { homedir as homedir2 } from "os";
|
|
289
|
+
function shellQuote(s) {
|
|
290
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
291
|
+
}
|
|
292
|
+
function cursorCcCmd(scriptPath) {
|
|
293
|
+
return "env SYNKRO_HOOK_FORMAT=cursor bun run " + shellQuote(scriptPath);
|
|
294
|
+
}
|
|
295
|
+
function bunRunCmd(scriptPath) {
|
|
296
|
+
return "bun run " + shellQuote(scriptPath);
|
|
297
|
+
}
|
|
298
|
+
function validateHooksPath(path) {
|
|
299
|
+
const resolved = resolve(normalize(path));
|
|
300
|
+
if (!ALLOWED_PARENT_DIRS.some((dir) => resolved.startsWith(dir + "/") || resolved === dir)) {
|
|
301
|
+
throw new Error(`Hooks path must be under ~/.cursor or ~/.config/cursor, got: ${resolved}`);
|
|
302
|
+
}
|
|
303
|
+
return resolved;
|
|
304
|
+
}
|
|
305
|
+
function readHooksFile(rawPath) {
|
|
306
|
+
const safePath = validateHooksPath(rawPath);
|
|
290
307
|
try {
|
|
291
|
-
const raw = readFileSync2(
|
|
308
|
+
const raw = readFileSync2(safePath, "utf-8");
|
|
292
309
|
return JSON.parse(raw);
|
|
293
310
|
} catch (err) {
|
|
294
|
-
|
|
311
|
+
if (err?.code === "ENOENT") return { version: 1, hooks: {} };
|
|
312
|
+
throw new Error(`Failed to parse ${safePath}: ${err.message}`);
|
|
295
313
|
}
|
|
296
314
|
}
|
|
297
|
-
function writeHooksFileAtomic(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
315
|
+
function writeHooksFileAtomic(rawPath, data) {
|
|
316
|
+
const safePath = validateHooksPath(rawPath);
|
|
317
|
+
mkdirSync2(dirname2(safePath), { recursive: true });
|
|
318
|
+
const tmpPath = `${safePath}.synkro.tmp`;
|
|
319
|
+
writeFileSync2(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
320
|
+
renameSync2(tmpPath, safePath);
|
|
302
321
|
}
|
|
303
322
|
function isSynkroEntry2(entry) {
|
|
304
323
|
if (entry?.[SYNKRO_MARKER2]) return true;
|
|
@@ -310,50 +329,89 @@ function removeSynkroEntries2(hooks, event) {
|
|
|
310
329
|
if (!Array.isArray(arr)) return;
|
|
311
330
|
hooks[event] = arr.filter((entry) => !isSynkroEntry2(entry));
|
|
312
331
|
}
|
|
332
|
+
function pushCcHook(hooks, event, scriptPath, opts) {
|
|
333
|
+
hooks[event] = hooks[event] ?? [];
|
|
334
|
+
hooks[event].push({
|
|
335
|
+
command: cursorCcCmd(scriptPath),
|
|
336
|
+
timeout: opts.timeout,
|
|
337
|
+
failClosed: opts.failClosed ?? false,
|
|
338
|
+
...opts.matcher ? { matcher: opts.matcher } : {},
|
|
339
|
+
[SYNKRO_MARKER2]: true
|
|
340
|
+
});
|
|
341
|
+
}
|
|
313
342
|
function installCursorHooks(hooksJsonPath, config) {
|
|
314
343
|
const file = readHooksFile(hooksJsonPath);
|
|
315
344
|
file.version = file.version ?? 1;
|
|
316
345
|
file.hooks = file.hooks ?? {};
|
|
317
|
-
const
|
|
318
|
-
for (const evt of events) {
|
|
346
|
+
for (const evt of ALL_EVENTS) {
|
|
319
347
|
removeSynkroEntries2(file.hooks, evt);
|
|
320
348
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
349
|
+
const h = file.hooks;
|
|
350
|
+
pushCcHook(h, "sessionStart", config.sessionStartScriptPath, { timeout: 5 });
|
|
351
|
+
pushCcHook(h, "sessionEnd", config.stopSummaryScriptPath, { timeout: 10 });
|
|
352
|
+
pushCcHook(h, "beforeSubmitPrompt", config.userPromptSubmitScriptPath, { timeout: 5 });
|
|
353
|
+
pushCcHook(h, "stop", config.transcriptSyncScriptPath, { timeout: 3 });
|
|
354
|
+
h.beforeShellExecution = h.beforeShellExecution ?? [];
|
|
355
|
+
h.beforeShellExecution.push({
|
|
356
|
+
command: bunRunCmd(config.bashJudgeScriptPath),
|
|
357
|
+
timeout: 15,
|
|
358
|
+
failClosed: false,
|
|
326
359
|
[SYNKRO_MARKER2]: true
|
|
327
360
|
});
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
361
|
+
pushCcHook(h, "afterShellExecution", config.bashFollowupScriptPath, { timeout: 10 });
|
|
362
|
+
h.preToolUse = h.preToolUse ?? [];
|
|
363
|
+
h.preToolUse.push({
|
|
364
|
+
command: bunRunCmd(config.bashJudgeScriptPath),
|
|
331
365
|
timeout: 15,
|
|
366
|
+
failClosed: false,
|
|
367
|
+
matcher: "Shell|Bash|Read|Grep|Glob",
|
|
332
368
|
[SYNKRO_MARKER2]: true
|
|
333
369
|
});
|
|
334
|
-
|
|
335
|
-
file.hooks.afterFileEdit.push({
|
|
336
|
-
command: config.editCaptureScriptPath,
|
|
370
|
+
pushCcHook(h, "preToolUse", config.editPrecheckScriptPath, {
|
|
337
371
|
timeout: 15,
|
|
338
|
-
|
|
372
|
+
matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit"
|
|
339
373
|
});
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
374
|
+
pushCcHook(h, "preToolUse", config.cwePrecheckScriptPath, {
|
|
375
|
+
timeout: 15,
|
|
376
|
+
matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit"
|
|
377
|
+
});
|
|
378
|
+
pushCcHook(h, "preToolUse", config.cvePrecheckScriptPath, {
|
|
343
379
|
timeout: 10,
|
|
380
|
+
matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit"
|
|
381
|
+
});
|
|
382
|
+
pushCcHook(h, "preToolUse", config.agentJudgeScriptPath, {
|
|
383
|
+
timeout: 15,
|
|
384
|
+
matcher: "Agent|Task"
|
|
385
|
+
});
|
|
386
|
+
pushCcHook(h, "preToolUse", config.planJudgeScriptPath, {
|
|
387
|
+
timeout: 20,
|
|
388
|
+
matcher: "ExitPlanMode|SwitchMode|CreatePlan"
|
|
389
|
+
});
|
|
390
|
+
h.afterFileEdit = h.afterFileEdit ?? [];
|
|
391
|
+
h.afterFileEdit.push({
|
|
392
|
+
command: bunRunCmd(config.editCaptureScriptPath),
|
|
393
|
+
timeout: 15,
|
|
394
|
+
failClosed: false,
|
|
344
395
|
[SYNKRO_MARKER2]: true
|
|
345
396
|
});
|
|
397
|
+
pushCcHook(h, "postToolUse", config.bashFollowupScriptPath, {
|
|
398
|
+
timeout: 10,
|
|
399
|
+
matcher: "Shell|Bash"
|
|
400
|
+
});
|
|
346
401
|
writeHooksFileAtomic(hooksJsonPath, file);
|
|
347
402
|
}
|
|
348
403
|
function uninstallCursorHooks(hooksJsonPath) {
|
|
349
|
-
|
|
350
|
-
|
|
404
|
+
let file;
|
|
405
|
+
try {
|
|
406
|
+
file = readHooksFile(hooksJsonPath);
|
|
407
|
+
} catch {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
351
410
|
if (!file.hooks) return false;
|
|
352
|
-
const
|
|
353
|
-
for (const evt of events) {
|
|
411
|
+
for (const evt of ALL_EVENTS) {
|
|
354
412
|
removeSynkroEntries2(file.hooks, evt);
|
|
355
413
|
}
|
|
356
|
-
for (const evt of
|
|
414
|
+
for (const evt of ALL_EVENTS) {
|
|
357
415
|
if (Array.isArray(file.hooks[evt]) && file.hooks[evt].length === 0) {
|
|
358
416
|
delete file.hooks[evt];
|
|
359
417
|
}
|
|
@@ -364,38 +422,100 @@ function uninstallCursorHooks(hooksJsonPath) {
|
|
|
364
422
|
writeHooksFileAtomic(hooksJsonPath, file);
|
|
365
423
|
return true;
|
|
366
424
|
}
|
|
425
|
+
function preToolUseUsesScript(hooks, scriptBasename) {
|
|
426
|
+
return (hooks ?? []).some(
|
|
427
|
+
(e) => isSynkroEntry2(e) && typeof e.command === "string" && e.command.includes(scriptBasename)
|
|
428
|
+
);
|
|
429
|
+
}
|
|
367
430
|
function inspectCursorHooks(hooksJsonPath) {
|
|
368
|
-
|
|
369
|
-
|
|
431
|
+
let file;
|
|
432
|
+
try {
|
|
433
|
+
file = readHooksFile(hooksJsonPath);
|
|
434
|
+
} catch {
|
|
435
|
+
return {
|
|
436
|
+
installed: false,
|
|
437
|
+
sessionStart: false,
|
|
438
|
+
sessionEnd: false,
|
|
439
|
+
beforeSubmitPrompt: false,
|
|
440
|
+
stop: false,
|
|
441
|
+
beforeShellExecution: false,
|
|
442
|
+
afterShellExecution: false,
|
|
443
|
+
preToolUse: false,
|
|
444
|
+
preToolUseBash: false,
|
|
445
|
+
preToolUseEdit: false,
|
|
446
|
+
preToolUseCwe: false,
|
|
447
|
+
preToolUseCve: false,
|
|
448
|
+
preToolUseAgent: false,
|
|
449
|
+
preToolUsePlan: false,
|
|
450
|
+
afterFileEdit: false,
|
|
451
|
+
postToolUse: false
|
|
452
|
+
};
|
|
370
453
|
}
|
|
371
|
-
const file = readHooksFile(hooksJsonPath);
|
|
372
454
|
const h = file.hooks ?? {};
|
|
455
|
+
const sessionStart = (h.sessionStart ?? []).some((e) => isSynkroEntry2(e));
|
|
456
|
+
const sessionEnd = (h.sessionEnd ?? []).some((e) => isSynkroEntry2(e));
|
|
457
|
+
const beforeSubmitPrompt = (h.beforeSubmitPrompt ?? []).some((e) => isSynkroEntry2(e));
|
|
458
|
+
const stop = (h.stop ?? []).some((e) => isSynkroEntry2(e));
|
|
373
459
|
const beforeShellExecution = (h.beforeShellExecution ?? []).some((e) => isSynkroEntry2(e));
|
|
374
|
-
const
|
|
460
|
+
const afterShellExecution = (h.afterShellExecution ?? []).some((e) => isSynkroEntry2(e));
|
|
461
|
+
const pre = h.preToolUse ?? [];
|
|
462
|
+
const preToolUseBash = preToolUseUsesScript(pre, "cc-bash-judge") || preToolUseUsesScript(pre, "cursor-bash-judge");
|
|
463
|
+
const preToolUseEdit = preToolUseUsesScript(pre, "cc-edit-precheck") || preToolUseUsesScript(pre, "cursor-edit-precheck");
|
|
464
|
+
const preToolUseCwe = preToolUseUsesScript(pre, "cc-cwe-precheck");
|
|
465
|
+
const preToolUseCve = preToolUseUsesScript(pre, "cc-cve-precheck");
|
|
466
|
+
const preToolUseAgent = preToolUseUsesScript(pre, "cc-agent-judge");
|
|
467
|
+
const preToolUsePlan = preToolUseUsesScript(pre, "cc-plan-judge");
|
|
468
|
+
const preToolUse = preToolUseBash || preToolUseEdit || preToolUseCwe || preToolUseCve || preToolUseAgent || preToolUsePlan;
|
|
375
469
|
const afterFileEdit = (h.afterFileEdit ?? []).some((e) => isSynkroEntry2(e));
|
|
376
470
|
const postToolUse = (h.postToolUse ?? []).some((e) => isSynkroEntry2(e));
|
|
377
471
|
return {
|
|
378
|
-
installed: beforeShellExecution || preToolUse || afterFileEdit || postToolUse,
|
|
472
|
+
installed: sessionStart || sessionEnd || beforeSubmitPrompt || stop || beforeShellExecution || afterShellExecution || preToolUse || afterFileEdit || postToolUse,
|
|
473
|
+
sessionStart,
|
|
474
|
+
sessionEnd,
|
|
475
|
+
beforeSubmitPrompt,
|
|
476
|
+
stop,
|
|
379
477
|
beforeShellExecution,
|
|
478
|
+
afterShellExecution,
|
|
380
479
|
preToolUse,
|
|
480
|
+
preToolUseBash,
|
|
481
|
+
preToolUseEdit,
|
|
482
|
+
preToolUseCwe,
|
|
483
|
+
preToolUseCve,
|
|
484
|
+
preToolUseAgent,
|
|
485
|
+
preToolUsePlan,
|
|
381
486
|
afterFileEdit,
|
|
382
487
|
postToolUse
|
|
383
488
|
};
|
|
384
489
|
}
|
|
385
|
-
var SYNKRO_MARKER2;
|
|
490
|
+
var SYNKRO_MARKER2, ALLOWED_PARENT_DIRS, ALL_EVENTS;
|
|
386
491
|
var init_cursorHookConfig = __esm({
|
|
387
492
|
"cli/installer/cursorHookConfig.ts"() {
|
|
388
493
|
"use strict";
|
|
389
494
|
SYNKRO_MARKER2 = "__synkro_managed__";
|
|
495
|
+
ALLOWED_PARENT_DIRS = [
|
|
496
|
+
resolve(homedir2(), ".cursor"),
|
|
497
|
+
resolve(homedir2(), ".config", "cursor")
|
|
498
|
+
];
|
|
499
|
+
ALL_EVENTS = [
|
|
500
|
+
"sessionStart",
|
|
501
|
+
"sessionEnd",
|
|
502
|
+
"beforeSubmitPrompt",
|
|
503
|
+
"stop",
|
|
504
|
+
"beforeShellExecution",
|
|
505
|
+
"afterShellExecution",
|
|
506
|
+
"preToolUse",
|
|
507
|
+
"afterFileEdit",
|
|
508
|
+
"postToolUse"
|
|
509
|
+
];
|
|
390
510
|
}
|
|
391
511
|
});
|
|
392
512
|
|
|
393
513
|
// cli/installer/mcpConfig.ts
|
|
394
|
-
import { existsSync as
|
|
395
|
-
import { homedir as
|
|
514
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, renameSync as renameSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
515
|
+
import { homedir as homedir3 } from "os";
|
|
396
516
|
import { dirname as dirname3, join as join2 } from "path";
|
|
397
517
|
function readClaudeJson() {
|
|
398
|
-
if (!
|
|
518
|
+
if (!existsSync3(CC_CONFIG_PATH)) return {};
|
|
399
519
|
try {
|
|
400
520
|
const raw = readFileSync3(CC_CONFIG_PATH, "utf-8");
|
|
401
521
|
return JSON.parse(raw);
|
|
@@ -417,7 +537,7 @@ function installMcpConfig(opts) {
|
|
|
417
537
|
}
|
|
418
538
|
if (opts.local) {
|
|
419
539
|
const url2 = "http://127.0.0.1:8931/";
|
|
420
|
-
const tokenPath = join2(
|
|
540
|
+
const tokenPath = join2(homedir3(), ".synkro", ".mcp-local-token");
|
|
421
541
|
let localToken = "";
|
|
422
542
|
try {
|
|
423
543
|
localToken = readFileSync3(tokenPath, "utf-8").trim();
|
|
@@ -443,7 +563,7 @@ function installMcpConfig(opts) {
|
|
|
443
563
|
return { path: CC_CONFIG_PATH, url };
|
|
444
564
|
}
|
|
445
565
|
function uninstallMcpConfig() {
|
|
446
|
-
if (!
|
|
566
|
+
if (!existsSync3(CC_CONFIG_PATH)) return false;
|
|
447
567
|
const config = readClaudeJson();
|
|
448
568
|
if (!config.mcpServers || Object.keys(config.mcpServers).length === 0) return false;
|
|
449
569
|
let removed = false;
|
|
@@ -459,7 +579,7 @@ function uninstallMcpConfig() {
|
|
|
459
579
|
return true;
|
|
460
580
|
}
|
|
461
581
|
function inspectMcpConfig() {
|
|
462
|
-
if (!
|
|
582
|
+
if (!existsSync3(CC_CONFIG_PATH)) {
|
|
463
583
|
return { installed: false, configPath: CC_CONFIG_PATH };
|
|
464
584
|
}
|
|
465
585
|
const config = readClaudeJson();
|
|
@@ -475,7 +595,7 @@ var init_mcpConfig = __esm({
|
|
|
475
595
|
"use strict";
|
|
476
596
|
SYNKRO_MARKER3 = "__synkro_managed__";
|
|
477
597
|
SYNKRO_SERVER_NAME = "synkro-guardrails";
|
|
478
|
-
CC_CONFIG_PATH = join2(
|
|
598
|
+
CC_CONFIG_PATH = join2(homedir3(), ".claude.json");
|
|
479
599
|
}
|
|
480
600
|
});
|
|
481
601
|
|
|
@@ -675,7 +795,7 @@ synkro_post_with_retry() {
|
|
|
675
795
|
});
|
|
676
796
|
|
|
677
797
|
// cli/installer/hookScriptsTs.ts
|
|
678
|
-
var SYNKRO_COMMON_TS, EDIT_PRECHECK_TS, CWE_PRECHECK_TS, CVE_PRECHECK_TS, BASH_JUDGE_TS, AGENT_JUDGE_TS, PLAN_JUDGE_TS, STOP_SUMMARY_TS, SESSION_START_TS, BASH_FOLLOWUP_TS, TRANSCRIPT_SYNC_TS, USER_PROMPT_SUBMIT_TS, CURSOR_BASH_JUDGE_TS,
|
|
798
|
+
var SYNKRO_COMMON_TS, EDIT_PRECHECK_TS, CWE_PRECHECK_TS, CVE_PRECHECK_TS, BASH_JUDGE_TS, AGENT_JUDGE_TS, PLAN_JUDGE_TS, STOP_SUMMARY_TS, SESSION_START_TS, BASH_FOLLOWUP_TS, TRANSCRIPT_SYNC_TS, USER_PROMPT_SUBMIT_TS, CURSOR_BASH_JUDGE_TS, CURSOR_EDIT_CAPTURE_TS;
|
|
679
799
|
var init_hookScriptsTs = __esm({
|
|
680
800
|
"cli/installer/hookScriptsTs.ts"() {
|
|
681
801
|
"use strict";
|
|
@@ -712,7 +832,17 @@ if (existsSync(CONFIG_PATH)) {
|
|
|
712
832
|
} catch {}
|
|
713
833
|
}
|
|
714
834
|
|
|
715
|
-
|
|
835
|
+
const ALLOWED_GATEWAY_HOSTS = new Set(['api.synkro.sh', 'localhost', '127.0.0.1']);
|
|
836
|
+
function validateGatewayUrl(raw: string): string {
|
|
837
|
+
try {
|
|
838
|
+
const u = new URL(raw);
|
|
839
|
+
if (!ALLOWED_GATEWAY_HOSTS.has(u.hostname)) return 'https://api.synkro.sh';
|
|
840
|
+
return raw.replace(/\\/+$/, '');
|
|
841
|
+
} catch {
|
|
842
|
+
return 'https://api.synkro.sh';
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
export const GATEWAY_URL = validateGatewayUrl(process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh');
|
|
716
846
|
export const CREDS_PATH = process.env.SYNKRO_CREDENTIALS_PATH || join(HOME, '.synkro', 'credentials.json');
|
|
717
847
|
const LAST_PROMPT_FILE = join(HOME, '.synkro', '.last-prompt');
|
|
718
848
|
|
|
@@ -855,7 +985,7 @@ export async function ensureFreshJwt(jwt: string): Promise<string> {
|
|
|
855
985
|
|
|
856
986
|
export function detectRepo(cwd: string): string {
|
|
857
987
|
try {
|
|
858
|
-
const url = execSync('git remote get-url origin', { cwd, timeout: 3000, encoding: 'utf-8' }).trim();
|
|
988
|
+
const url = execSync('git remote get-url origin 2>/dev/null', { cwd, timeout: 3000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
859
989
|
if (!url) return '';
|
|
860
990
|
return url
|
|
861
991
|
.replace(/^git@[^:]+:/, '')
|
|
@@ -1056,11 +1186,11 @@ async function channelGrade(role: GradeRole, prompt: string, jwt: string, port:
|
|
|
1056
1186
|
return String(data.result || '');
|
|
1057
1187
|
}
|
|
1058
1188
|
|
|
1059
|
-
export async function localGrade(surface: string, prompt: string): Promise<string> {
|
|
1189
|
+
export async function localGrade(surface: string, prompt: string, timeoutMs = 20000): Promise<string> {
|
|
1060
1190
|
if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');
|
|
1061
1191
|
const jwt = loadJwt();
|
|
1062
1192
|
if (!jwt) throw new Error('NO_JWT');
|
|
1063
|
-
return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 8929);
|
|
1193
|
+
return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 8929, timeoutMs);
|
|
1064
1194
|
}
|
|
1065
1195
|
|
|
1066
1196
|
export async function localGradeCwe(prompt: string): Promise<string> {
|
|
@@ -1074,6 +1204,7 @@ export async function localGradeCwe(prompt: string): Promise<string> {
|
|
|
1074
1204
|
export interface Verdict {
|
|
1075
1205
|
ok: boolean;
|
|
1076
1206
|
reason: string;
|
|
1207
|
+
suggestedFix: string;
|
|
1077
1208
|
ruleId: string;
|
|
1078
1209
|
ruleMode: string;
|
|
1079
1210
|
severity: string;
|
|
@@ -1084,6 +1215,7 @@ export function parseVerdict(resp: string): Verdict {
|
|
|
1084
1215
|
const verdict: Verdict = {
|
|
1085
1216
|
ok: true,
|
|
1086
1217
|
reason: '',
|
|
1218
|
+
suggestedFix: '',
|
|
1087
1219
|
ruleId: '',
|
|
1088
1220
|
ruleMode: '',
|
|
1089
1221
|
severity: 'low',
|
|
@@ -1102,6 +1234,9 @@ export function parseVerdict(resp: string): Verdict {
|
|
|
1102
1234
|
const reasonMatch = inner.match(/<reason>(.*?)<\\/reason>/) || inner.match(/<reasoning>(.*?)<\\/reasoning>/);
|
|
1103
1235
|
if (reasonMatch) verdict.reason = reasonMatch[1].trim();
|
|
1104
1236
|
|
|
1237
|
+
const fixMatch = inner.match(/<suggested_fix>(.*?)<\\/suggested_fix>/);
|
|
1238
|
+
if (fixMatch) verdict.suggestedFix = fixMatch[1].trim();
|
|
1239
|
+
|
|
1105
1240
|
if (!verdict.ok) {
|
|
1106
1241
|
const ruleIdMatch = inner.match(/<rule_id>(.*?)<\\/rule_id>/);
|
|
1107
1242
|
const ruleModeMatch = inner.match(/<rule_mode>(.*?)<\\/rule_mode>/);
|
|
@@ -1120,6 +1255,10 @@ export function parseVerdict(resp: string): Verdict {
|
|
|
1120
1255
|
const vReason = vBlock.match(/<reason>(.*?)<\\/reason>/);
|
|
1121
1256
|
if (vReason) verdict.reason = vReason[1].trim();
|
|
1122
1257
|
}
|
|
1258
|
+
if (!verdict.suggestedFix) {
|
|
1259
|
+
const vFix = vBlock.match(/<suggested_fix>(.*?)<\\/suggested_fix>/);
|
|
1260
|
+
if (vFix) verdict.suggestedFix = vFix[1].trim();
|
|
1261
|
+
}
|
|
1123
1262
|
if (!sevMatch) {
|
|
1124
1263
|
const vSev = vBlock.match(/<severity>(.*?)<\\/severity>/);
|
|
1125
1264
|
if (vSev) verdict.severity = vSev[1].trim();
|
|
@@ -1274,8 +1413,10 @@ export function reconstructContent(toolName: string, toolInput: any, filePath: s
|
|
|
1274
1413
|
}
|
|
1275
1414
|
case 'NotebookEdit':
|
|
1276
1415
|
return toolInput.new_source || '';
|
|
1416
|
+
case 'StrReplace':
|
|
1417
|
+
return toolInput.new_string || toolInput.content || toolInput.code_edit || '';
|
|
1277
1418
|
default:
|
|
1278
|
-
return '';
|
|
1419
|
+
return toolInput.content || toolInput.new_string || toolInput.code_edit || '';
|
|
1279
1420
|
}
|
|
1280
1421
|
}
|
|
1281
1422
|
|
|
@@ -1589,13 +1730,90 @@ export function dispatchFinding(
|
|
|
1589
1730
|
}).catch(() => {});
|
|
1590
1731
|
}
|
|
1591
1732
|
|
|
1733
|
+
// \u2500\u2500\u2500 Hook tool-name sets (CC + Cursor) \u2500\u2500\u2500
|
|
1734
|
+
|
|
1735
|
+
export const EDIT_TOOL_NAMES = new Set([
|
|
1736
|
+
'Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'StrReplace',
|
|
1737
|
+
]);
|
|
1738
|
+
export const SHELL_TOOL_NAMES = new Set([
|
|
1739
|
+
'Bash', 'Shell', 'Read', 'Grep', 'Glob', 'terminal', 'run_terminal_cmd', 'execute_command',
|
|
1740
|
+
]);
|
|
1741
|
+
export const AGENT_TOOL_NAMES = new Set(['Agent', 'Task']);
|
|
1742
|
+
export const PLAN_TOOL_NAMES = new Set(['ExitPlanMode', 'SwitchMode', 'CreatePlan']);
|
|
1743
|
+
|
|
1744
|
+
export function isEditTool(toolName: string): boolean {
|
|
1745
|
+
return EDIT_TOOL_NAMES.has(toolName);
|
|
1746
|
+
}
|
|
1747
|
+
export function isShellTool(toolName: string): boolean {
|
|
1748
|
+
return SHELL_TOOL_NAMES.has(toolName);
|
|
1749
|
+
}
|
|
1750
|
+
export function isAgentTool(toolName: string): boolean {
|
|
1751
|
+
return AGENT_TOOL_NAMES.has(toolName);
|
|
1752
|
+
}
|
|
1753
|
+
export function isPlanTool(toolName: string): boolean {
|
|
1754
|
+
return PLAN_TOOL_NAMES.has(toolName);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
export function hookSessionId(payload: Record<string, unknown>): string {
|
|
1758
|
+
return String(payload.session_id ?? payload.conversation_id ?? '');
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
export function isCursorHookFormat(): boolean {
|
|
1762
|
+
return process.env.SYNKRO_HOOK_FORMAT === 'cursor';
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
let cursorHookExited = false;
|
|
1766
|
+
|
|
1767
|
+
export function setupCursorHookSignals(): void {
|
|
1768
|
+
if (!isCursorHookFormat()) return;
|
|
1769
|
+
process.on('SIGTERM', () => outputEmpty());
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function cursorHookExit(): never {
|
|
1773
|
+
cursorHookExited = true;
|
|
1774
|
+
process.exit(0);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1592
1777
|
// \u2500\u2500\u2500 Output Helpers \u2500\u2500\u2500
|
|
1593
1778
|
|
|
1594
1779
|
export function outputJson(obj: any): void {
|
|
1780
|
+
if (isCursorHookFormat()) {
|
|
1781
|
+
const hso = obj?.hookSpecificOutput;
|
|
1782
|
+
const sys = typeof obj?.systemMessage === 'string' ? obj.systemMessage : '';
|
|
1783
|
+
if (hso?.permissionDecision === 'deny') {
|
|
1784
|
+
const reason = hso.permissionDecisionReason || hso.additionalContext || sys;
|
|
1785
|
+
if (!cursorHookExited) {
|
|
1786
|
+
cursorHookExited = true;
|
|
1787
|
+
process.stdout.write(JSON.stringify({
|
|
1788
|
+
permission: 'deny',
|
|
1789
|
+
user_message: sys || reason,
|
|
1790
|
+
agent_message: hso.additionalContext || reason,
|
|
1791
|
+
}) + '\\n');
|
|
1792
|
+
}
|
|
1793
|
+
cursorHookExit();
|
|
1794
|
+
}
|
|
1795
|
+
const ctx = sys || hso?.additionalContext;
|
|
1796
|
+
if (ctx) {
|
|
1797
|
+
if (!cursorHookExited) {
|
|
1798
|
+
cursorHookExited = true;
|
|
1799
|
+
process.stdout.write(JSON.stringify({ additional_context: ctx }) + '\\n');
|
|
1800
|
+
}
|
|
1801
|
+
cursorHookExit();
|
|
1802
|
+
}
|
|
1803
|
+
outputEmpty();
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1595
1806
|
console.log(JSON.stringify(obj));
|
|
1596
1807
|
}
|
|
1597
1808
|
|
|
1598
1809
|
export function outputEmpty(): void {
|
|
1810
|
+
if (isCursorHookFormat()) {
|
|
1811
|
+
if (!cursorHookExited) {
|
|
1812
|
+
cursorHookExited = true;
|
|
1813
|
+
try { process.stdout.write('{}\\n'); } catch {}
|
|
1814
|
+
}
|
|
1815
|
+
cursorHookExit();
|
|
1816
|
+
}
|
|
1599
1817
|
console.log('{}');
|
|
1600
1818
|
}
|
|
1601
1819
|
`;
|
|
@@ -1604,26 +1822,27 @@ import {
|
|
|
1604
1822
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
1605
1823
|
parseVerdict, dispatchCapture, ruleMode, reconstructContent, isPathUnder, postWithRetry,
|
|
1606
1824
|
readStdin, extractTranscript, readLastPrompt, findNearestDeps, log,
|
|
1607
|
-
outputJson, outputEmpty, GATEWAY_URL,
|
|
1825
|
+
outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, GATEWAY_URL,
|
|
1608
1826
|
type HookConfig, type Rule,
|
|
1609
1827
|
} from './_synkro-common.ts';
|
|
1610
1828
|
import { existsSync, readFileSync } from 'node:fs';
|
|
1611
1829
|
import { basename, dirname, join } from 'node:path';
|
|
1612
1830
|
|
|
1613
1831
|
async function main() {
|
|
1832
|
+
setupCursorHookSignals();
|
|
1614
1833
|
try {
|
|
1615
1834
|
const input = await readStdin();
|
|
1616
1835
|
if (!input.trim()) { outputEmpty(); return; }
|
|
1617
1836
|
|
|
1618
1837
|
const payload = JSON.parse(input);
|
|
1619
1838
|
const toolName = payload.tool_name || '';
|
|
1620
|
-
if (!
|
|
1839
|
+
if (!isEditTool(toolName)) {
|
|
1621
1840
|
outputEmpty();
|
|
1622
1841
|
return;
|
|
1623
1842
|
}
|
|
1624
1843
|
|
|
1625
1844
|
const toolInput = payload.tool_input || {};
|
|
1626
|
-
const sessionId = payload
|
|
1845
|
+
const sessionId = hookSessionId(payload);
|
|
1627
1846
|
const toolUseId = payload.tool_use_id || '';
|
|
1628
1847
|
const cwd = payload.cwd || '';
|
|
1629
1848
|
const permissionMode = payload.permission_mode || '';
|
|
@@ -1814,24 +2033,25 @@ main();
|
|
|
1814
2033
|
import {
|
|
1815
2034
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, cweRoute, tag,
|
|
1816
2035
|
localGradeCwe, parseVerdict, reconstructContent, readStdin, log,
|
|
1817
|
-
outputJson, outputEmpty, dispatchFinding, GATEWAY_URL,
|
|
2036
|
+
outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, dispatchFinding, GATEWAY_URL,
|
|
1818
2037
|
} from './_synkro-common.ts';
|
|
1819
2038
|
import { basename, extname } from 'node:path';
|
|
1820
2039
|
|
|
1821
2040
|
async function main() {
|
|
2041
|
+
setupCursorHookSignals();
|
|
1822
2042
|
try {
|
|
1823
2043
|
const input = await readStdin();
|
|
1824
2044
|
if (!input.trim()) { outputEmpty(); return; }
|
|
1825
2045
|
|
|
1826
2046
|
const payload = JSON.parse(input);
|
|
1827
2047
|
const toolName = payload.tool_name || '';
|
|
1828
|
-
if (!
|
|
2048
|
+
if (!isEditTool(toolName)) {
|
|
1829
2049
|
outputEmpty();
|
|
1830
2050
|
return;
|
|
1831
2051
|
}
|
|
1832
2052
|
|
|
1833
2053
|
const toolInput = payload.tool_input || {};
|
|
1834
|
-
const sessionId = payload
|
|
2054
|
+
const sessionId = hookSessionId(payload);
|
|
1835
2055
|
const cwd = payload.cwd || '';
|
|
1836
2056
|
const gitRepo = detectRepo(cwd || '.');
|
|
1837
2057
|
|
|
@@ -1932,6 +2152,12 @@ async function main() {
|
|
|
1932
2152
|
if (id && !cweIds.includes(id)) cweIds.push(id);
|
|
1933
2153
|
}
|
|
1934
2154
|
|
|
2155
|
+
const fixMatches = gradeResp.match(/<suggested_fix>([^<]+)<\\/suggested_fix>/g) || [];
|
|
2156
|
+
const fixes: Record<string, string> = {};
|
|
2157
|
+
for (let i = 0; i < Math.min(cweIds.length, fixMatches.length); i++) {
|
|
2158
|
+
fixes[cweIds[i]] = fixMatches[i].replace(/<\\/?suggested_fix>/g, '').trim();
|
|
2159
|
+
}
|
|
2160
|
+
|
|
1935
2161
|
// Filter out exempted CWEs for this file
|
|
1936
2162
|
const activeCweIds = cweIds.filter(id => !exemptedCwes.has(id.toUpperCase()));
|
|
1937
2163
|
|
|
@@ -1950,7 +2176,11 @@ async function main() {
|
|
|
1950
2176
|
const label = count === 1 ? 'match' : 'matches';
|
|
1951
2177
|
const cweMsg = cweTag + ' ' + fileShort + ' \\u2192 ' + count + ' CWE ' + label + ' (' + displayIds + ')';
|
|
1952
2178
|
const denyDetail = '[' + displayIds + '] ' + (verdict.reason || 'code weakness detected');
|
|
1953
|
-
const
|
|
2179
|
+
const fixLines = activeCweIds
|
|
2180
|
+
.filter(id => fixes[id])
|
|
2181
|
+
.map(id => '[' + id + '] Fix: ' + fixes[id]);
|
|
2182
|
+
const fixHint = fixLines.length > 0 ? '\\n' + fixLines.join('\\n') : '';
|
|
2183
|
+
const ctx = 'CWE: ' + denyDetail + fixHint + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \u2014 resolve the weakness in code yourself.';
|
|
1954
2184
|
|
|
1955
2185
|
for (const cweId of activeCweIds) {
|
|
1956
2186
|
dispatchFinding(jwt, {
|
|
@@ -1999,7 +2229,7 @@ main();
|
|
|
1999
2229
|
import {
|
|
2000
2230
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,
|
|
2001
2231
|
reconstructContent, readStdin, findNearestDeps, log,
|
|
2002
|
-
outputJson, outputEmpty, dispatchFinding, GATEWAY_URL,
|
|
2232
|
+
outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, dispatchFinding, GATEWAY_URL,
|
|
2003
2233
|
} from './_synkro-common.ts';
|
|
2004
2234
|
import { basename } from 'node:path';
|
|
2005
2235
|
|
|
@@ -2017,19 +2247,20 @@ function isManifest(filename: string): boolean {
|
|
|
2017
2247
|
}
|
|
2018
2248
|
|
|
2019
2249
|
async function main() {
|
|
2250
|
+
setupCursorHookSignals();
|
|
2020
2251
|
try {
|
|
2021
2252
|
const input = await readStdin();
|
|
2022
2253
|
if (!input.trim()) { outputEmpty(); return; }
|
|
2023
2254
|
|
|
2024
2255
|
const payload = JSON.parse(input);
|
|
2025
2256
|
const toolName = payload.tool_name || '';
|
|
2026
|
-
if (!
|
|
2257
|
+
if (!isEditTool(toolName)) {
|
|
2027
2258
|
outputEmpty();
|
|
2028
2259
|
return;
|
|
2029
2260
|
}
|
|
2030
2261
|
|
|
2031
2262
|
const toolInput = payload.tool_input || {};
|
|
2032
|
-
const sessionId = payload
|
|
2263
|
+
const sessionId = hookSessionId(payload);
|
|
2033
2264
|
const cwd = payload.cwd || '';
|
|
2034
2265
|
|
|
2035
2266
|
const filePath = toolInput.file_path || toolInput.notebook_path || toolInput.path || '';
|
|
@@ -2146,7 +2377,7 @@ import {
|
|
|
2146
2377
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
2147
2378
|
parseVerdict, dispatchCapture, dispatchFinding, ruleMode, postWithRetry, readStdin,
|
|
2148
2379
|
extractTranscript, readLastPrompt, log,
|
|
2149
|
-
outputJson, outputEmpty, GATEWAY_URL,
|
|
2380
|
+
outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,
|
|
2150
2381
|
type HookConfig, type Rule,
|
|
2151
2382
|
} from './_synkro-common.ts';
|
|
2152
2383
|
|
|
@@ -2222,19 +2453,20 @@ interface PkgMeta {
|
|
|
2222
2453
|
}
|
|
2223
2454
|
|
|
2224
2455
|
async function main() {
|
|
2456
|
+
setupCursorHookSignals();
|
|
2225
2457
|
try {
|
|
2226
2458
|
const input = await readStdin();
|
|
2227
2459
|
if (!input.trim()) { outputEmpty(); return; }
|
|
2228
2460
|
|
|
2229
2461
|
const payload = JSON.parse(input);
|
|
2230
2462
|
const toolName = payload.tool_name || '';
|
|
2231
|
-
if (!
|
|
2463
|
+
if (!isShellTool(toolName)) {
|
|
2232
2464
|
outputEmpty();
|
|
2233
2465
|
return;
|
|
2234
2466
|
}
|
|
2235
2467
|
|
|
2236
2468
|
const toolInput = payload.tool_input || {};
|
|
2237
|
-
const sessionId = payload
|
|
2469
|
+
const sessionId = hookSessionId(payload);
|
|
2238
2470
|
const toolUseId = payload.tool_use_id || '';
|
|
2239
2471
|
const cwd = payload.cwd || '';
|
|
2240
2472
|
const permissionMode = payload.permission_mode || '';
|
|
@@ -2243,7 +2475,12 @@ async function main() {
|
|
|
2243
2475
|
|
|
2244
2476
|
let command = '';
|
|
2245
2477
|
switch (toolName) {
|
|
2246
|
-
case 'Bash':
|
|
2478
|
+
case 'Bash':
|
|
2479
|
+
case 'Shell':
|
|
2480
|
+
case 'terminal':
|
|
2481
|
+
case 'run_terminal_cmd':
|
|
2482
|
+
case 'execute_command':
|
|
2483
|
+
command = toolInput.command || ''; break;
|
|
2247
2484
|
case 'Read': command = 'cat ' + (toolInput.file_path || ''); break;
|
|
2248
2485
|
case 'Grep': command = "grep -r '" + (toolInput.pattern || '') + "' " + (toolInput.path || '.'); break;
|
|
2249
2486
|
case 'Glob': command = "find . -name '" + (toolInput.pattern || '') + "'"; break;
|
|
@@ -2261,7 +2498,7 @@ async function main() {
|
|
|
2261
2498
|
let installScanMsg = '';
|
|
2262
2499
|
if (toolName === 'Bash') {
|
|
2263
2500
|
const pkgInstallMatch = command.match(
|
|
2264
|
-
/(?:npm\\s+(?:install|i|add)|pnpm\\s+(?:add|install|i)|yarn\\s+add|bun\\s+(?:add|install|i)|(?:uv\\s+)?pip3?\\s+install|go\\s+get|cargo\\s+add|gem\\s+install|composer\\s+require)\\s+(
|
|
2501
|
+
/(?:npm\\s+(?:install|i|add)|pnpm\\s+(?:add|install|i)|yarn\\s+add|bun\\s+(?:add|install|i)|(?:uv\\s+)?pip3?\\s+install|go\\s+get|cargo\\s+add|gem\\s+install|composer\\s+require)\\s+([^|;&><]+)/
|
|
2265
2502
|
);
|
|
2266
2503
|
const isPip = /(?:uv\\s+)?pip3?\\s+install/.test(command);
|
|
2267
2504
|
const isGo = command.match(/^go\\s+get/);
|
|
@@ -2275,6 +2512,7 @@ async function main() {
|
|
|
2275
2512
|
let skipNext = false;
|
|
2276
2513
|
for (const token of tokens) {
|
|
2277
2514
|
if (skipNext) { skipNext = false; continue; }
|
|
2515
|
+
if (!token || !/^[@a-zA-Z]/.test(token)) continue;
|
|
2278
2516
|
if (token.startsWith('-')) {
|
|
2279
2517
|
if (/^--(python|target|prefix|root|constraint|requirement|index-url|extra-index-url|find-links|build|src|cache-dir|filter|workspace)$/.test(token)) skipNext = true;
|
|
2280
2518
|
continue;
|
|
@@ -2313,8 +2551,9 @@ async function main() {
|
|
|
2313
2551
|
warnings.push('\\u26a0 ' + pkg + ': package not found on PyPI \\u2014 may not exist');
|
|
2314
2552
|
}
|
|
2315
2553
|
} else {
|
|
2554
|
+
const verSlug = deps[pkg] !== '*' ? deps[pkg] : 'latest';
|
|
2316
2555
|
const [metaResp, dlResp] = await Promise.all([
|
|
2317
|
-
fetch('https://registry.npmjs.org/' + encodeURIComponent(pkg) + '/
|
|
2556
|
+
fetch('https://registry.npmjs.org/' + encodeURIComponent(pkg) + '/' + verSlug, { signal: AbortSignal.timeout(4000) }),
|
|
2318
2557
|
fetch('https://api.npmjs.org/downloads/point/last-week/' + encodeURIComponent(pkg), { signal: AbortSignal.timeout(4000) }),
|
|
2319
2558
|
]);
|
|
2320
2559
|
if (metaResp.ok) {
|
|
@@ -2572,24 +2811,25 @@ import {
|
|
|
2572
2811
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
2573
2812
|
parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,
|
|
2574
2813
|
extractTranscript, readLastPrompt, log,
|
|
2575
|
-
outputJson, outputEmpty, GATEWAY_URL,
|
|
2814
|
+
outputJson, outputEmpty, setupCursorHookSignals, isAgentTool, hookSessionId, GATEWAY_URL,
|
|
2576
2815
|
type HookConfig, type Rule,
|
|
2577
2816
|
} from './_synkro-common.ts';
|
|
2578
2817
|
|
|
2579
2818
|
async function main() {
|
|
2819
|
+
setupCursorHookSignals();
|
|
2580
2820
|
try {
|
|
2581
2821
|
const input = await readStdin();
|
|
2582
2822
|
if (!input.trim()) { outputEmpty(); return; }
|
|
2583
2823
|
|
|
2584
2824
|
const payload = JSON.parse(input);
|
|
2585
2825
|
const toolName = payload.tool_name || '';
|
|
2586
|
-
if (toolName
|
|
2826
|
+
if (!isAgentTool(toolName)) {
|
|
2587
2827
|
outputEmpty();
|
|
2588
2828
|
return;
|
|
2589
2829
|
}
|
|
2590
2830
|
|
|
2591
2831
|
const toolInput = payload.tool_input || {};
|
|
2592
|
-
const sessionId = payload
|
|
2832
|
+
const sessionId = hookSessionId(payload);
|
|
2593
2833
|
const toolUseId = payload.tool_use_id || '';
|
|
2594
2834
|
const cwd = payload.cwd || '';
|
|
2595
2835
|
const permissionMode = payload.permission_mode || '';
|
|
@@ -2735,14 +2975,13 @@ main();
|
|
|
2735
2975
|
import {
|
|
2736
2976
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
2737
2977
|
parseVerdict, dispatchCapture, postWithRetry, readStdin, log,
|
|
2738
|
-
outputJson, outputEmpty, GATEWAY_URL,
|
|
2978
|
+
outputJson, outputEmpty, setupCursorHookSignals, isPlanTool, hookSessionId, GATEWAY_URL,
|
|
2739
2979
|
} from './_synkro-common.ts';
|
|
2740
2980
|
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
|
2741
2981
|
import { join } from 'node:path';
|
|
2742
2982
|
import { homedir } from 'node:os';
|
|
2743
2983
|
|
|
2744
|
-
function
|
|
2745
|
-
const plansDir = join(homedir(), '.claude', 'plans');
|
|
2984
|
+
function findLatestPlanInDir(plansDir: string): string | null {
|
|
2746
2985
|
if (!existsSync(plansDir)) return null;
|
|
2747
2986
|
try {
|
|
2748
2987
|
const files = readdirSync(plansDir)
|
|
@@ -2755,6 +2994,23 @@ function findLatestPlan(): string | null {
|
|
|
2755
2994
|
}
|
|
2756
2995
|
}
|
|
2757
2996
|
|
|
2997
|
+
function findLatestPlan(): string | null {
|
|
2998
|
+
const dirs = [
|
|
2999
|
+
join(homedir(), '.claude', 'plans'),
|
|
3000
|
+
join(homedir(), '.cursor', 'plans'),
|
|
3001
|
+
];
|
|
3002
|
+
let best: { path: string; mtime: number } | null = null;
|
|
3003
|
+
for (const dir of dirs) {
|
|
3004
|
+
const p = findLatestPlanInDir(dir);
|
|
3005
|
+
if (!p) continue;
|
|
3006
|
+
try {
|
|
3007
|
+
const mtime = statSync(p).mtimeMs;
|
|
3008
|
+
if (!best || mtime > best.mtime) best = { path: p, mtime };
|
|
3009
|
+
} catch {}
|
|
3010
|
+
}
|
|
3011
|
+
return best?.path ?? null;
|
|
3012
|
+
}
|
|
3013
|
+
|
|
2758
3014
|
function appendReviewToPlan(planFile: string, verdict: string): void {
|
|
2759
3015
|
try {
|
|
2760
3016
|
let content = readFileSync(planFile, 'utf-8');
|
|
@@ -2766,20 +3022,21 @@ function appendReviewToPlan(planFile: string, verdict: string): void {
|
|
|
2766
3022
|
}
|
|
2767
3023
|
|
|
2768
3024
|
async function main() {
|
|
3025
|
+
setupCursorHookSignals();
|
|
2769
3026
|
try {
|
|
2770
3027
|
const input = await readStdin();
|
|
2771
3028
|
if (!input.trim()) { outputEmpty(); return; }
|
|
2772
3029
|
|
|
2773
3030
|
const payload = JSON.parse(input);
|
|
2774
3031
|
const toolName = payload.tool_name || '';
|
|
2775
|
-
if (toolName
|
|
3032
|
+
if (!isPlanTool(toolName)) { outputEmpty(); return; }
|
|
2776
3033
|
|
|
2777
3034
|
const planFile = findLatestPlan();
|
|
2778
3035
|
if (!planFile) { outputEmpty(); return; }
|
|
2779
3036
|
const plan = readFileSync(planFile, 'utf-8');
|
|
2780
3037
|
if (plan.length < 20) { outputEmpty(); return; }
|
|
2781
3038
|
|
|
2782
|
-
const sessionId = payload
|
|
3039
|
+
const sessionId = hookSessionId(payload);
|
|
2783
3040
|
const cwd = payload.cwd || '';
|
|
2784
3041
|
const gitRepo = detectRepo(cwd || '.');
|
|
2785
3042
|
|
|
@@ -2884,16 +3141,17 @@ main();
|
|
|
2884
3141
|
STOP_SUMMARY_TS = `#!/usr/bin/env bun
|
|
2885
3142
|
import {
|
|
2886
3143
|
loadJwt, detectRepo, loadConfig, tag, readStdin, aggregateUsage,
|
|
2887
|
-
outputJson, outputEmpty, appendLocalTelemetry, GATEWAY_URL,
|
|
3144
|
+
outputJson, outputEmpty, appendLocalTelemetry, setupCursorHookSignals, hookSessionId, GATEWAY_URL,
|
|
2888
3145
|
} from './_synkro-common.ts';
|
|
2889
3146
|
|
|
2890
3147
|
async function main() {
|
|
3148
|
+
setupCursorHookSignals();
|
|
2891
3149
|
try {
|
|
2892
3150
|
const input = await readStdin();
|
|
2893
3151
|
if (!input.trim()) { outputEmpty(); return; }
|
|
2894
3152
|
|
|
2895
3153
|
const payload = JSON.parse(input);
|
|
2896
|
-
const sessionId = payload
|
|
3154
|
+
const sessionId = hookSessionId(payload);
|
|
2897
3155
|
if (!sessionId) { outputEmpty(); return; }
|
|
2898
3156
|
|
|
2899
3157
|
const cwd = payload.cwd || '';
|
|
@@ -2971,18 +3229,19 @@ main();
|
|
|
2971
3229
|
SESSION_START_TS = `#!/usr/bin/env bun
|
|
2972
3230
|
import {
|
|
2973
3231
|
loadJwt, detectRepo, channelUp, tag, readStdin,
|
|
2974
|
-
outputJson, outputEmpty, GATEWAY_URL,
|
|
3232
|
+
outputJson, outputEmpty, setupCursorHookSignals, hookSessionId, GATEWAY_URL,
|
|
2975
3233
|
type HookConfig,
|
|
2976
3234
|
} from './_synkro-common.ts';
|
|
2977
3235
|
|
|
2978
3236
|
async function main() {
|
|
3237
|
+
setupCursorHookSignals();
|
|
2979
3238
|
try {
|
|
2980
3239
|
const input = await readStdin();
|
|
2981
3240
|
if (!input.trim()) { outputEmpty(); return; }
|
|
2982
3241
|
|
|
2983
3242
|
const payload = JSON.parse(input);
|
|
2984
3243
|
const cwd = payload.cwd || '';
|
|
2985
|
-
const sessionId = payload
|
|
3244
|
+
const sessionId = hookSessionId(payload);
|
|
2986
3245
|
const gitRepo = detectRepo(cwd || '.');
|
|
2987
3246
|
|
|
2988
3247
|
let jwt = loadJwt();
|
|
@@ -3035,27 +3294,33 @@ main();
|
|
|
3035
3294
|
BASH_FOLLOWUP_TS = `#!/usr/bin/env bun
|
|
3036
3295
|
import {
|
|
3037
3296
|
loadJwt, loadConfig, readStdin, hashCommand, consentGrant, consentHasActive, consentConsume,
|
|
3038
|
-
outputEmpty, appendLocalTelemetry, GATEWAY_URL,
|
|
3297
|
+
outputEmpty, appendLocalTelemetry, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,
|
|
3039
3298
|
} from './_synkro-common.ts';
|
|
3040
3299
|
|
|
3041
3300
|
async function main() {
|
|
3301
|
+
setupCursorHookSignals();
|
|
3042
3302
|
try {
|
|
3043
3303
|
const input = await readStdin();
|
|
3044
3304
|
if (!input.trim()) { outputEmpty(); return; }
|
|
3045
3305
|
|
|
3046
3306
|
const payload = JSON.parse(input);
|
|
3047
3307
|
const toolName = payload.tool_name || '';
|
|
3048
|
-
|
|
3308
|
+
const shellCmd = typeof payload.command === 'string' ? payload.command : (payload.tool_input?.command || '');
|
|
3309
|
+
if (!isShellTool(toolName) && !shellCmd) { outputEmpty(); return; }
|
|
3049
3310
|
|
|
3050
3311
|
const jwt = loadJwt();
|
|
3051
3312
|
if (!jwt) { outputEmpty(); return; }
|
|
3052
3313
|
|
|
3053
|
-
const sessionId = payload
|
|
3054
|
-
const toolUseId = payload.tool_use_id || '';
|
|
3055
|
-
if (!sessionId
|
|
3314
|
+
const sessionId = hookSessionId(payload);
|
|
3315
|
+
const toolUseId = payload.tool_use_id || payload.tool_call_id || 'cursor-shell';
|
|
3316
|
+
if (!sessionId) { outputEmpty(); return; }
|
|
3056
3317
|
|
|
3057
|
-
|
|
3058
|
-
|
|
3318
|
+
let isError = payload.tool_result?.is_error === true;
|
|
3319
|
+
try {
|
|
3320
|
+
const out = JSON.parse(payload.tool_output || '{}');
|
|
3321
|
+
if (out.exitCode !== 0 || out.is_error === true) isError = true;
|
|
3322
|
+
} catch {}
|
|
3323
|
+
const cmd = shellCmd;
|
|
3059
3324
|
const cmdHash = cmd ? hashCommand(cmd) : '';
|
|
3060
3325
|
|
|
3061
3326
|
if (cmdHash && sessionId) {
|
|
@@ -3099,19 +3364,20 @@ main();
|
|
|
3099
3364
|
TRANSCRIPT_SYNC_TS = `#!/usr/bin/env bun
|
|
3100
3365
|
import {
|
|
3101
3366
|
loadJwt, detectRepo, readStdin, aggregateUsage, appendLocalTelemetry,
|
|
3102
|
-
outputEmpty, GATEWAY_URL,
|
|
3367
|
+
outputEmpty, setupCursorHookSignals, hookSessionId, GATEWAY_URL,
|
|
3103
3368
|
} from './_synkro-common.ts';
|
|
3104
3369
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3105
3370
|
import { join, dirname } from 'node:path';
|
|
3106
3371
|
import { homedir } from 'node:os';
|
|
3107
3372
|
|
|
3108
3373
|
async function main() {
|
|
3374
|
+
setupCursorHookSignals();
|
|
3109
3375
|
try {
|
|
3110
3376
|
const input = await readStdin();
|
|
3111
3377
|
if (!input.trim()) { outputEmpty(); return; }
|
|
3112
3378
|
|
|
3113
3379
|
const payload = JSON.parse(input);
|
|
3114
|
-
const sessionId = payload
|
|
3380
|
+
const sessionId = hookSessionId(payload);
|
|
3115
3381
|
const transcriptPath = payload.transcript_path || '';
|
|
3116
3382
|
const cwd = payload.cwd || '';
|
|
3117
3383
|
|
|
@@ -3239,15 +3505,16 @@ async function main() {
|
|
|
3239
3505
|
main();
|
|
3240
3506
|
`;
|
|
3241
3507
|
USER_PROMPT_SUBMIT_TS = `#!/usr/bin/env bun
|
|
3242
|
-
import { readStdin, appendLocalTelemetry, aggregateUsage } from './_synkro-common.ts';
|
|
3508
|
+
import { readStdin, appendLocalTelemetry, aggregateUsage, outputEmpty, setupCursorHookSignals, hookSessionId } from './_synkro-common.ts';
|
|
3243
3509
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3244
3510
|
import { join, dirname } from 'node:path';
|
|
3245
3511
|
import { homedir } from 'node:os';
|
|
3246
3512
|
|
|
3247
3513
|
async function main() {
|
|
3514
|
+
setupCursorHookSignals();
|
|
3248
3515
|
try {
|
|
3249
3516
|
const input = await readStdin();
|
|
3250
|
-
if (!input.trim()) return;
|
|
3517
|
+
if (!input.trim()) { outputEmpty(); return; }
|
|
3251
3518
|
const payload = JSON.parse(input);
|
|
3252
3519
|
const msg = payload.message || payload.prompt || payload.content || '';
|
|
3253
3520
|
if (msg) {
|
|
@@ -3256,7 +3523,7 @@ async function main() {
|
|
|
3256
3523
|
writeFileSync(promptFile, msg, 'utf-8');
|
|
3257
3524
|
}
|
|
3258
3525
|
|
|
3259
|
-
const sessionId = payload
|
|
3526
|
+
const sessionId = hookSessionId(payload);
|
|
3260
3527
|
const transcriptPath = payload.transcript_path || '';
|
|
3261
3528
|
if (sessionId && transcriptPath) {
|
|
3262
3529
|
const usage = aggregateUsage(transcriptPath);
|
|
@@ -3277,7 +3544,10 @@ async function main() {
|
|
|
3277
3544
|
});
|
|
3278
3545
|
}
|
|
3279
3546
|
}
|
|
3280
|
-
|
|
3547
|
+
outputEmpty();
|
|
3548
|
+
} catch {
|
|
3549
|
+
outputEmpty();
|
|
3550
|
+
}
|
|
3281
3551
|
}
|
|
3282
3552
|
|
|
3283
3553
|
main();
|
|
@@ -3286,38 +3556,99 @@ main();
|
|
|
3286
3556
|
import {
|
|
3287
3557
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
3288
3558
|
parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,
|
|
3289
|
-
|
|
3290
|
-
type
|
|
3559
|
+
extractTranscript, readLastPrompt, log, GATEWAY_URL,
|
|
3560
|
+
type Rule,
|
|
3291
3561
|
} from './_synkro-common.ts';
|
|
3292
3562
|
|
|
3563
|
+
// Cursor beforeShellExecution timeout is 15s; stay under it (JWT refresh + grade).
|
|
3564
|
+
const CURSOR_GRADE_TIMEOUT_MS = 7500;
|
|
3565
|
+
const CURSOR_CLOUD_TIMEOUT_MS = 6000;
|
|
3566
|
+
|
|
3567
|
+
let hookDone = false;
|
|
3568
|
+
|
|
3569
|
+
function finishAllow(): never {
|
|
3570
|
+
if (!hookDone) {
|
|
3571
|
+
hookDone = true;
|
|
3572
|
+
try { process.stdout.write('{}\\n'); } catch {}
|
|
3573
|
+
}
|
|
3574
|
+
process.exit(0);
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
function finishWith(payload: Record<string, unknown>): never {
|
|
3578
|
+
hookDone = true;
|
|
3579
|
+
process.stdout.write(JSON.stringify(payload) + '\\n');
|
|
3580
|
+
process.exit(0);
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
process.on('SIGTERM', () => finishAllow());
|
|
3584
|
+
|
|
3585
|
+
const SHELL_TOOL_NAMES = new Set(['Bash', 'Shell', 'terminal', 'run_terminal_cmd', 'execute_command']);
|
|
3586
|
+
const BASH_PRE_TOOL_NAMES = new Set(['Bash', 'Shell', 'Read', 'Grep', 'Glob', ...SHELL_TOOL_NAMES]);
|
|
3587
|
+
|
|
3588
|
+
function extractCommand(payload: Record<string, unknown>): { command: string; toolName: string } {
|
|
3589
|
+
const direct = typeof payload.command === 'string' ? payload.command : '';
|
|
3590
|
+
if (direct) return { command: direct, toolName: 'Bash' };
|
|
3591
|
+
|
|
3592
|
+
const toolName = typeof payload.tool_name === 'string' ? payload.tool_name : '';
|
|
3593
|
+
if (!BASH_PRE_TOOL_NAMES.has(toolName)) return { command: '', toolName };
|
|
3594
|
+
|
|
3595
|
+
const toolInput = (payload.tool_input && typeof payload.tool_input === 'object')
|
|
3596
|
+
? payload.tool_input as Record<string, unknown>
|
|
3597
|
+
: {};
|
|
3598
|
+
|
|
3599
|
+
let command = '';
|
|
3600
|
+
switch (toolName) {
|
|
3601
|
+
case 'Bash':
|
|
3602
|
+
case 'Shell':
|
|
3603
|
+
case 'terminal':
|
|
3604
|
+
case 'run_terminal_cmd':
|
|
3605
|
+
case 'execute_command':
|
|
3606
|
+
command = String(toolInput.command ?? '');
|
|
3607
|
+
break;
|
|
3608
|
+
case 'Read':
|
|
3609
|
+
command = 'cat ' + String(toolInput.file_path ?? toolInput.path ?? '');
|
|
3610
|
+
break;
|
|
3611
|
+
case 'Grep':
|
|
3612
|
+
command = "grep -r '" + String(toolInput.pattern ?? '') + "' " + String(toolInput.path ?? '.');
|
|
3613
|
+
break;
|
|
3614
|
+
case 'Glob':
|
|
3615
|
+
command = "find . -name '" + String(toolInput.pattern ?? '') + "'";
|
|
3616
|
+
break;
|
|
3617
|
+
}
|
|
3618
|
+
return { command, toolName: toolName || 'Bash' };
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3293
3621
|
async function main() {
|
|
3294
3622
|
try {
|
|
3295
3623
|
const input = await readStdin();
|
|
3296
|
-
if (!input.trim())
|
|
3624
|
+
if (!input.trim()) finishAllow();
|
|
3297
3625
|
|
|
3298
|
-
const payload = JSON.parse(input)
|
|
3299
|
-
const command = payload
|
|
3300
|
-
if (!command)
|
|
3626
|
+
const payload = JSON.parse(input) as Record<string, unknown>;
|
|
3627
|
+
const { command, toolName } = extractCommand(payload);
|
|
3628
|
+
if (!command) finishAllow();
|
|
3301
3629
|
|
|
3302
|
-
const cwd = payload.cwd
|
|
3303
|
-
const sessionId = payload.conversation_id
|
|
3630
|
+
const cwd = typeof payload.cwd === 'string' ? payload.cwd : '';
|
|
3631
|
+
const sessionId = String(payload.conversation_id ?? payload.session_id ?? '');
|
|
3632
|
+
const transcriptPath = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';
|
|
3304
3633
|
const repo = detectRepo(cwd || '.');
|
|
3305
3634
|
|
|
3306
3635
|
const cmdShort = command.slice(0, 80);
|
|
3307
3636
|
log('bashGuard checking: ' + cmdShort);
|
|
3308
3637
|
|
|
3309
3638
|
let jwt = loadJwt();
|
|
3310
|
-
if (!jwt)
|
|
3639
|
+
if (!jwt) finishAllow();
|
|
3311
3640
|
jwt = await ensureFreshJwt(jwt);
|
|
3312
3641
|
|
|
3642
|
+
const transcript = extractTranscript(transcriptPath);
|
|
3643
|
+
const lastPrompt = readLastPrompt();
|
|
3644
|
+
|
|
3313
3645
|
const config = await loadConfig(jwt);
|
|
3314
|
-
if (config.silent)
|
|
3646
|
+
if (config.silent) finishAllow();
|
|
3315
3647
|
|
|
3316
3648
|
const rt = await route(config);
|
|
3317
3649
|
const tagStr = tag(rt, config);
|
|
3318
3650
|
|
|
3319
3651
|
if (rt === 'local') {
|
|
3320
|
-
// Build grading prompt with rules
|
|
3321
3652
|
const rulesBlock = config.rules.map((r: Rule, i: number) =>
|
|
3322
3653
|
(i + 1) + '. [' + r.rule_id + '] (' + r.severity + '/' + r.mode + ') ' + r.text
|
|
3323
3654
|
).join('\\n');
|
|
@@ -3328,14 +3659,17 @@ async function main() {
|
|
|
3328
3659
|
'',
|
|
3329
3660
|
'COMMAND TO EVALUATE:',
|
|
3330
3661
|
command,
|
|
3662
|
+
'',
|
|
3663
|
+
'User intent (last human message): ' + (transcript.userIntent || lastPrompt || 'none stated'),
|
|
3664
|
+
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
3331
3665
|
].join('\\n');
|
|
3332
3666
|
|
|
3333
3667
|
let gradeResp: string;
|
|
3334
3668
|
try {
|
|
3335
|
-
gradeResp = await localGrade('bash', graderPrompt);
|
|
3336
|
-
} catch {
|
|
3337
|
-
|
|
3338
|
-
|
|
3669
|
+
gradeResp = await localGrade('bash', graderPrompt, CURSOR_GRADE_TIMEOUT_MS);
|
|
3670
|
+
} catch (e) {
|
|
3671
|
+
log('bashGuard ' + cmdShort + ' \u2192 pass (grade unavailable): ' + String(e));
|
|
3672
|
+
finishAllow();
|
|
3339
3673
|
}
|
|
3340
3674
|
|
|
3341
3675
|
const verdict = parseVerdict(gradeResp);
|
|
@@ -3350,16 +3684,13 @@ async function main() {
|
|
|
3350
3684
|
command, reasoning: guardReason,
|
|
3351
3685
|
rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],
|
|
3352
3686
|
});
|
|
3353
|
-
|
|
3687
|
+
finishWith({
|
|
3354
3688
|
permission: 'deny',
|
|
3355
|
-
user_message: tagStr + ' bashGuard
|
|
3689
|
+
user_message: tagStr + ' bashGuard \u2192 block: ' + guardReason,
|
|
3356
3690
|
agent_message: 'Synkro safety judge. Reasoning: ' + (verdict.reason || guardReason),
|
|
3357
|
-
};
|
|
3358
|
-
process.stdout.write(JSON.stringify(result) + '\\n');
|
|
3359
|
-
return;
|
|
3691
|
+
});
|
|
3360
3692
|
}
|
|
3361
3693
|
|
|
3362
|
-
// Audit mode \u2014 warn but allow
|
|
3363
3694
|
dispatchCapture(jwt, 'bash', 'warning', verdict.severity || 'medium', verdict.category || 'security',
|
|
3364
3695
|
'Bash', repo, sessionId, config.captureDepth, {
|
|
3365
3696
|
command, reasoning: guardReason,
|
|
@@ -3373,177 +3704,46 @@ async function main() {
|
|
|
3373
3704
|
});
|
|
3374
3705
|
}
|
|
3375
3706
|
|
|
3376
|
-
|
|
3377
|
-
|
|
3707
|
+
log('bashGuard ' + cmdShort + ' \u2192 pass');
|
|
3708
|
+
finishAllow();
|
|
3378
3709
|
}
|
|
3379
3710
|
|
|
3380
|
-
|
|
3381
|
-
const body = {
|
|
3711
|
+
const body: Record<string, any> = {
|
|
3382
3712
|
hook_event: 'PreToolUse',
|
|
3383
|
-
tool_name: 'Bash',
|
|
3713
|
+
tool_name: toolName || 'Bash',
|
|
3384
3714
|
tool_input: { command },
|
|
3385
3715
|
response_format: 'cursor',
|
|
3716
|
+
user_intent: transcript.userIntent || null,
|
|
3717
|
+
last_user_message: lastPrompt || null,
|
|
3718
|
+
recent_user_messages: transcript.recentUserMessages,
|
|
3719
|
+
recent_messages: transcript.recentMessages,
|
|
3386
3720
|
session_id: sessionId || null,
|
|
3387
3721
|
cwd: cwd || null,
|
|
3388
3722
|
repo: repo || null,
|
|
3389
3723
|
};
|
|
3390
3724
|
|
|
3391
|
-
const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt,
|
|
3392
|
-
|
|
3393
|
-
if (!resp) {
|
|
3394
|
-
log('bashGuard ' + cmdShort + ' \\u2192 error (timeout)');
|
|
3395
|
-
process.stdout.write('{}\\n');
|
|
3396
|
-
return;
|
|
3397
|
-
}
|
|
3398
|
-
|
|
3399
|
-
if (resp.hook_response) {
|
|
3400
|
-
process.stdout.write(JSON.stringify(resp.hook_response) + '\\n');
|
|
3401
|
-
} else {
|
|
3402
|
-
process.stdout.write('{}\\n');
|
|
3403
|
-
}
|
|
3404
|
-
} catch {
|
|
3405
|
-
process.stdout.write('{}\\n');
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
|
|
3409
|
-
main();
|
|
3410
|
-
`;
|
|
3411
|
-
CURSOR_EDIT_PRECHECK_TS = `#!/usr/bin/env bun
|
|
3412
|
-
import {
|
|
3413
|
-
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
3414
|
-
parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,
|
|
3415
|
-
appendLocalTelemetry, log, GATEWAY_URL,
|
|
3416
|
-
type HookConfig, type Rule,
|
|
3417
|
-
} from './_synkro-common.ts';
|
|
3418
|
-
import { basename } from 'node:path';
|
|
3419
|
-
|
|
3420
|
-
async function main() {
|
|
3421
|
-
try {
|
|
3422
|
-
const input = await readStdin();
|
|
3423
|
-
if (!input.trim()) { process.stdout.write('{}\\n'); return; }
|
|
3424
|
-
|
|
3425
|
-
const payload = JSON.parse(input);
|
|
3426
|
-
const toolName = payload.tool_name || '';
|
|
3427
|
-
const toolInput = payload.tool_input || {};
|
|
3428
|
-
const cwd = payload.cwd || '';
|
|
3429
|
-
const sessionId = payload.conversation_id || '';
|
|
3430
|
-
|
|
3431
|
-
const filePath = toolInput.file_path || toolInput.path || toolInput.target_file || '';
|
|
3432
|
-
const content = toolInput.content || toolInput.new_string || toolInput.code_edit || '';
|
|
3433
|
-
if (!filePath) { process.stdout.write('{}\\n'); return; }
|
|
3434
|
-
|
|
3435
|
-
const fileShort = basename(filePath);
|
|
3436
|
-
log('editGuard checking: ' + fileShort);
|
|
3437
|
-
|
|
3438
|
-
const repo = detectRepo(cwd || '.');
|
|
3439
|
-
|
|
3440
|
-
let jwt = loadJwt();
|
|
3441
|
-
if (!jwt) { process.stdout.write('{}\\n'); return; }
|
|
3442
|
-
jwt = await ensureFreshJwt(jwt);
|
|
3443
|
-
|
|
3444
|
-
const config = await loadConfig(jwt);
|
|
3445
|
-
if (config.silent) { process.stdout.write('{}\\n'); return; }
|
|
3446
|
-
|
|
3447
|
-
const rt = await route(config);
|
|
3448
|
-
const tagStr = tag(rt, config);
|
|
3449
|
-
|
|
3450
|
-
if (rt === 'local') {
|
|
3451
|
-
const contentShort = content.slice(0, 4000);
|
|
3452
|
-
const rulesBlock = config.rules.map((r: Rule, i: number) =>
|
|
3453
|
-
(i + 1) + '. [' + r.rule_id + '] (' + r.severity + '/' + r.mode + ') ' + r.text
|
|
3454
|
-
).join('\\n');
|
|
3455
|
-
|
|
3456
|
-
const graderPrompt = [
|
|
3457
|
-
'RULES:',
|
|
3458
|
-
rulesBlock || '(none)',
|
|
3459
|
-
'',
|
|
3460
|
-
'FILE: ' + filePath,
|
|
3461
|
-
'',
|
|
3462
|
-
'CONTENT TO EVALUATE (first 4000 chars):',
|
|
3463
|
-
contentShort,
|
|
3464
|
-
].join('\\n');
|
|
3465
|
-
|
|
3466
|
-
let gradeResp: string;
|
|
3467
|
-
try {
|
|
3468
|
-
gradeResp = await localGrade('edit', graderPrompt);
|
|
3469
|
-
} catch {
|
|
3470
|
-
process.stdout.write('{}\\n');
|
|
3471
|
-
return;
|
|
3472
|
-
}
|
|
3473
|
-
|
|
3474
|
-
const verdict = parseVerdict(gradeResp);
|
|
3475
|
-
const editContent = 'file=' + filePath + ' content=' + content.slice(0, 2000);
|
|
3476
|
-
|
|
3477
|
-
if (!verdict.ok) {
|
|
3478
|
-
const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);
|
|
3479
|
-
const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');
|
|
3480
|
-
|
|
3481
|
-
if (mode !== 'audit') {
|
|
3482
|
-
dispatchCapture(jwt, 'edit', 'block', verdict.severity || 'critical', verdict.category || 'security',
|
|
3483
|
-
toolName || 'Edit', repo, sessionId, config.captureDepth, {
|
|
3484
|
-
command: editContent, reasoning: guardReason,
|
|
3485
|
-
rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],
|
|
3486
|
-
});
|
|
3487
|
-
const result = {
|
|
3488
|
-
permission: 'deny',
|
|
3489
|
-
user_message: tagStr + ' editGuard ' + fileShort + ' \\u2192 block: ' + guardReason,
|
|
3490
|
-
agent_message: 'Synkro safety judge. Reasoning: ' + (verdict.reason || guardReason),
|
|
3491
|
-
};
|
|
3492
|
-
process.stdout.write(JSON.stringify(result) + '\\n');
|
|
3493
|
-
return;
|
|
3494
|
-
}
|
|
3495
|
-
|
|
3496
|
-
// Audit mode
|
|
3497
|
-
dispatchCapture(jwt, 'edit', 'warning', verdict.severity || 'medium', verdict.category || 'security',
|
|
3498
|
-
toolName || 'Edit', repo, sessionId, config.captureDepth, {
|
|
3499
|
-
command: editContent, reasoning: guardReason,
|
|
3500
|
-
rulesChecked: config.rules, violatedRules: verdict.ruleId ? [verdict.ruleId] : [],
|
|
3501
|
-
});
|
|
3502
|
-
} else {
|
|
3503
|
-
dispatchCapture(jwt, 'edit', 'pass', 'audit', verdict.category || 'trivial_edit',
|
|
3504
|
-
toolName || 'Edit', repo, sessionId, config.captureDepth, {
|
|
3505
|
-
command: editContent, reasoning: verdict.reason || 'no policy violations detected',
|
|
3506
|
-
rulesChecked: config.rules, violatedRules: [],
|
|
3507
|
-
});
|
|
3508
|
-
}
|
|
3509
|
-
|
|
3510
|
-
process.stdout.write('{}\\n');
|
|
3511
|
-
return;
|
|
3512
|
-
}
|
|
3513
|
-
|
|
3514
|
-
// \u2500\u2500\u2500 Cloud grading \u2500\u2500\u2500
|
|
3515
|
-
const body = {
|
|
3516
|
-
hook_event: 'PreToolUse',
|
|
3517
|
-
tool_name: toolName || 'Edit',
|
|
3518
|
-
tool_input: { file_path: filePath, content },
|
|
3519
|
-
file_path: filePath,
|
|
3520
|
-
content,
|
|
3521
|
-
response_format: 'cursor',
|
|
3522
|
-
session_id: sessionId || null,
|
|
3523
|
-
cwd: cwd || null,
|
|
3524
|
-
repo: repo || null,
|
|
3525
|
-
};
|
|
3526
|
-
|
|
3527
|
-
const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, 8000);
|
|
3725
|
+
const resp = await postWithRetry(GATEWAY_URL + '/api/v1/hook/judge', body, jwt, CURSOR_CLOUD_TIMEOUT_MS);
|
|
3528
3726
|
|
|
3529
3727
|
if (!resp) {
|
|
3530
|
-
log('
|
|
3531
|
-
|
|
3532
|
-
return;
|
|
3728
|
+
log('bashGuard ' + cmdShort + ' \u2192 pass (cloud timeout)');
|
|
3729
|
+
finishAllow();
|
|
3533
3730
|
}
|
|
3534
3731
|
|
|
3535
3732
|
if (resp.hook_response) {
|
|
3536
|
-
|
|
3537
|
-
} else {
|
|
3538
|
-
process.stdout.write('{}\\n');
|
|
3733
|
+
finishWith(resp.hook_response as Record<string, unknown>);
|
|
3539
3734
|
}
|
|
3540
|
-
|
|
3541
|
-
|
|
3735
|
+
log('bashGuard ' + cmdShort + ' \u2192 pass (no hook_response)');
|
|
3736
|
+
finishAllow();
|
|
3737
|
+
} catch (e) {
|
|
3738
|
+
log('bashGuard error: ' + String(e));
|
|
3739
|
+
finishAllow();
|
|
3542
3740
|
}
|
|
3543
3741
|
}
|
|
3544
3742
|
|
|
3545
|
-
main()
|
|
3546
|
-
|
|
3743
|
+
main().catch((e) => {
|
|
3744
|
+
log('bashGuard fatal: ' + String(e));
|
|
3745
|
+
finishAllow();
|
|
3746
|
+
});`;
|
|
3547
3747
|
CURSOR_EDIT_CAPTURE_TS = `#!/usr/bin/env bun
|
|
3548
3748
|
import {
|
|
3549
3749
|
loadJwt, ensureFreshJwt, detectRepo, readStdin,
|
|
@@ -3553,26 +3753,37 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
3553
3753
|
import { basename, dirname, join } from 'node:path';
|
|
3554
3754
|
import { homedir } from 'node:os';
|
|
3555
3755
|
|
|
3756
|
+
let hookDone = false;
|
|
3757
|
+
|
|
3758
|
+
function finish(): never {
|
|
3759
|
+
if (!hookDone) {
|
|
3760
|
+
hookDone = true;
|
|
3761
|
+
try { process.stdout.write('{}\\n'); } catch {}
|
|
3762
|
+
}
|
|
3763
|
+
process.exit(0);
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
process.on('SIGTERM', () => finish());
|
|
3767
|
+
|
|
3556
3768
|
async function main() {
|
|
3557
3769
|
try {
|
|
3558
3770
|
const input = await readStdin();
|
|
3559
|
-
if (!input.trim())
|
|
3771
|
+
if (!input.trim()) finish();
|
|
3560
3772
|
|
|
3561
3773
|
const payload = JSON.parse(input);
|
|
3562
3774
|
const filePath = payload.file_path || '';
|
|
3563
|
-
if (!filePath)
|
|
3775
|
+
if (!filePath) finish();
|
|
3564
3776
|
|
|
3565
|
-
const cwd = payload.cwd || '';
|
|
3777
|
+
const cwd = payload.cwd || payload.workspace_roots?.[0] || '';
|
|
3566
3778
|
const sessionId = payload.conversation_id || '';
|
|
3567
3779
|
const repo = detectRepo(cwd || '.');
|
|
3568
3780
|
|
|
3569
3781
|
log('editScan ' + basename(filePath));
|
|
3570
3782
|
|
|
3571
3783
|
let jwt = loadJwt();
|
|
3572
|
-
if (!jwt)
|
|
3784
|
+
if (!jwt) finish();
|
|
3573
3785
|
jwt = await ensureFreshJwt(jwt);
|
|
3574
3786
|
|
|
3575
|
-
// Read actual file content (up to 50KB)
|
|
3576
3787
|
let fileContent = '';
|
|
3577
3788
|
const fullPath = filePath.startsWith('/') ? filePath : (cwd ? join(cwd, filePath) : filePath);
|
|
3578
3789
|
try {
|
|
@@ -3582,7 +3793,6 @@ async function main() {
|
|
|
3582
3793
|
}
|
|
3583
3794
|
} catch {}
|
|
3584
3795
|
|
|
3585
|
-
// Walk up to find package.json dependencies
|
|
3586
3796
|
let dependencies: Record<string, string> = {};
|
|
3587
3797
|
let pkgDir = cwd || dirname(fullPath);
|
|
3588
3798
|
while (pkgDir !== '/' && pkgDir !== '.') {
|
|
@@ -3609,12 +3819,10 @@ async function main() {
|
|
|
3609
3819
|
if (cwd) captureBody.cwd = cwd;
|
|
3610
3820
|
if (repo) captureBody.repo = repo;
|
|
3611
3821
|
|
|
3612
|
-
// Check if local_only
|
|
3613
3822
|
const rulesPath = join(homedir(), '.synkro', 'rules.json');
|
|
3614
3823
|
if (existsSync(rulesPath)) {
|
|
3615
3824
|
appendLocalTelemetry(captureBody);
|
|
3616
3825
|
} else {
|
|
3617
|
-
// Fire-and-forget to cloud
|
|
3618
3826
|
fetch(GATEWAY_URL + '/api/v1/hook/capture', {
|
|
3619
3827
|
method: 'POST',
|
|
3620
3828
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
@@ -3624,128 +3832,24 @@ async function main() {
|
|
|
3624
3832
|
appendLocalTelemetry(captureBody);
|
|
3625
3833
|
}
|
|
3626
3834
|
|
|
3627
|
-
|
|
3628
|
-
} catch {
|
|
3629
|
-
|
|
3835
|
+
finish();
|
|
3836
|
+
} catch (e) {
|
|
3837
|
+
log('editScan error: ' + String(e));
|
|
3838
|
+
finish();
|
|
3630
3839
|
}
|
|
3631
3840
|
}
|
|
3632
3841
|
|
|
3633
|
-
main()
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
loadJwt, readStdin, appendLocalTelemetry, log, GATEWAY_URL,
|
|
3638
|
-
} from './_synkro-common.ts';
|
|
3639
|
-
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'node:fs';
|
|
3640
|
-
import { join, dirname } from 'node:path';
|
|
3641
|
-
import { createHash } from 'node:crypto';
|
|
3642
|
-
import { homedir } from 'node:os';
|
|
3643
|
-
|
|
3644
|
-
const CONSENT_FILE = join(homedir(), '.synkro', '.local-consent');
|
|
3645
|
-
|
|
3646
|
-
function hashCmd(cmd: string): string {
|
|
3647
|
-
return createHash('sha256').update(cmd).digest('hex').slice(0, 16);
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
function consentGrant(sid: string, hash: string): void {
|
|
3651
|
-
try {
|
|
3652
|
-
const dir = dirname(CONSENT_FILE);
|
|
3653
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
3654
|
-
appendFileSync(CONSENT_FILE, sid + '\\t' + hash + '\\tactive\\n', 'utf-8');
|
|
3655
|
-
} catch {}
|
|
3656
|
-
}
|
|
3657
|
-
|
|
3658
|
-
function consentHasActive(sid: string, hash: string): boolean {
|
|
3659
|
-
try {
|
|
3660
|
-
if (!existsSync(CONSENT_FILE)) return false;
|
|
3661
|
-
const content = readFileSync(CONSENT_FILE, 'utf-8');
|
|
3662
|
-
return content.includes(sid + '\\t' + hash + '\\tactive');
|
|
3663
|
-
} catch {
|
|
3664
|
-
return false;
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
|
|
3668
|
-
function consentConsume(sid: string, hash: string): void {
|
|
3669
|
-
try {
|
|
3670
|
-
if (!existsSync(CONSENT_FILE)) return;
|
|
3671
|
-
const content = readFileSync(CONSENT_FILE, 'utf-8');
|
|
3672
|
-
const target = sid + '\\t' + hash + '\\tactive';
|
|
3673
|
-
const replacement = sid + '\\t' + hash + '\\tconsumed';
|
|
3674
|
-
const updated = content.split('\\n').map((l: string) => l === target ? replacement : l).join('\\n');
|
|
3675
|
-
writeFileSync(CONSENT_FILE, updated, 'utf-8');
|
|
3676
|
-
} catch {}
|
|
3677
|
-
}
|
|
3678
|
-
|
|
3679
|
-
async function main() {
|
|
3680
|
-
try {
|
|
3681
|
-
const input = await readStdin();
|
|
3682
|
-
if (!input.trim()) { process.stdout.write('{}\\n'); return; }
|
|
3683
|
-
|
|
3684
|
-
const payload = JSON.parse(input);
|
|
3685
|
-
const toolName = payload.tool_name || '';
|
|
3686
|
-
|
|
3687
|
-
// Only process shell/bash tool types
|
|
3688
|
-
const shellTools = ['Shell', 'Bash', 'terminal', 'run_terminal_cmd', 'execute_command'];
|
|
3689
|
-
if (!shellTools.includes(toolName)) { process.stdout.write('{}\\n'); return; }
|
|
3690
|
-
|
|
3691
|
-
const sessionId = payload.conversation_id || '';
|
|
3692
|
-
const toolUseId = payload.tool_use_id || '';
|
|
3693
|
-
const isError = payload.tool_result?.is_error === true;
|
|
3694
|
-
const command = payload.tool_input?.command || '';
|
|
3695
|
-
const cmdHash = command ? hashCmd(command) : '';
|
|
3696
|
-
|
|
3697
|
-
// Consent tracking
|
|
3698
|
-
if (cmdHash && sessionId) {
|
|
3699
|
-
if (!isError) {
|
|
3700
|
-
consentConsume(sessionId, cmdHash);
|
|
3701
|
-
} else {
|
|
3702
|
-
if (!consentHasActive(sessionId, cmdHash)) {
|
|
3703
|
-
consentGrant(sessionId, cmdHash);
|
|
3704
|
-
}
|
|
3705
|
-
}
|
|
3706
|
-
}
|
|
3707
|
-
|
|
3708
|
-
// Build capture body
|
|
3709
|
-
const captureBody: Record<string, any> = {
|
|
3710
|
-
capture_type: 'bash_followup',
|
|
3711
|
-
session_id: sessionId || null,
|
|
3712
|
-
tool_use_id: toolUseId || null,
|
|
3713
|
-
is_error: isError,
|
|
3714
|
-
command_hash: cmdHash,
|
|
3715
|
-
};
|
|
3716
|
-
|
|
3717
|
-
// Check if local_only
|
|
3718
|
-
const rulesPath = join(homedir(), '.synkro', 'rules.json');
|
|
3719
|
-
if (existsSync(rulesPath)) {
|
|
3720
|
-
appendLocalTelemetry(captureBody);
|
|
3721
|
-
} else {
|
|
3722
|
-
const jwt = loadJwt();
|
|
3723
|
-
if (jwt && sessionId && toolUseId) {
|
|
3724
|
-
fetch(GATEWAY_URL + '/api/v1/hook/capture', {
|
|
3725
|
-
method: 'POST',
|
|
3726
|
-
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
3727
|
-
body: JSON.stringify(captureBody),
|
|
3728
|
-
signal: AbortSignal.timeout(3000),
|
|
3729
|
-
}).catch(() => {});
|
|
3730
|
-
}
|
|
3731
|
-
appendLocalTelemetry(captureBody);
|
|
3732
|
-
}
|
|
3733
|
-
|
|
3734
|
-
process.stdout.write('{}\\n');
|
|
3735
|
-
} catch {
|
|
3736
|
-
process.stdout.write('{}\\n');
|
|
3737
|
-
}
|
|
3738
|
-
}
|
|
3739
|
-
|
|
3740
|
-
main();
|
|
3741
|
-
`;
|
|
3842
|
+
main().catch((e) => {
|
|
3843
|
+
log('editScan fatal: ' + String(e));
|
|
3844
|
+
finish();
|
|
3845
|
+
});`;
|
|
3742
3846
|
}
|
|
3743
3847
|
});
|
|
3744
3848
|
|
|
3745
3849
|
// cli/auth/stub.ts
|
|
3746
3850
|
import { createServer } from "http";
|
|
3747
|
-
import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, existsSync as
|
|
3748
|
-
import { homedir as
|
|
3851
|
+
import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
3852
|
+
import { homedir as homedir4, platform } from "os";
|
|
3749
3853
|
import { join as join3, dirname as dirname4 } from "path";
|
|
3750
3854
|
import { execFile } from "child_process";
|
|
3751
3855
|
import jwt from "jsonwebtoken";
|
|
@@ -3775,13 +3879,13 @@ function openBrowser(url) {
|
|
|
3775
3879
|
}
|
|
3776
3880
|
function saveCredentials(data) {
|
|
3777
3881
|
const dir = dirname4(AUTH_FILE);
|
|
3778
|
-
if (!
|
|
3882
|
+
if (!existsSync4(dir)) {
|
|
3779
3883
|
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
3780
3884
|
}
|
|
3781
3885
|
writeFileSync4(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
|
|
3782
3886
|
}
|
|
3783
3887
|
function loadCredentials() {
|
|
3784
|
-
if (!
|
|
3888
|
+
if (!existsSync4(AUTH_FILE)) {
|
|
3785
3889
|
return null;
|
|
3786
3890
|
}
|
|
3787
3891
|
try {
|
|
@@ -3798,7 +3902,7 @@ function createCallbackServer() {
|
|
|
3798
3902
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
3799
3903
|
"Vary": "Origin"
|
|
3800
3904
|
};
|
|
3801
|
-
return new Promise((
|
|
3905
|
+
return new Promise((resolve3, reject) => {
|
|
3802
3906
|
const server = createServer((req, res) => {
|
|
3803
3907
|
if (req.method === "OPTIONS") {
|
|
3804
3908
|
const origin = req.headers.origin;
|
|
@@ -3887,7 +3991,7 @@ function createCallbackServer() {
|
|
|
3887
3991
|
res.end(JSON.stringify({ ok: true }));
|
|
3888
3992
|
setTimeout(() => {
|
|
3889
3993
|
server.close();
|
|
3890
|
-
|
|
3994
|
+
resolve3(authData);
|
|
3891
3995
|
}, 200);
|
|
3892
3996
|
});
|
|
3893
3997
|
req.on("error", (e) => {
|
|
@@ -4029,7 +4133,7 @@ async function ensureValidToken() {
|
|
|
4029
4133
|
return true;
|
|
4030
4134
|
}
|
|
4031
4135
|
function clearCredentials() {
|
|
4032
|
-
if (
|
|
4136
|
+
if (existsSync4(AUTH_FILE)) {
|
|
4033
4137
|
unlinkSync2(AUTH_FILE);
|
|
4034
4138
|
}
|
|
4035
4139
|
}
|
|
@@ -4040,7 +4144,7 @@ var init_stub = __esm({
|
|
|
4040
4144
|
PORT = 8100;
|
|
4041
4145
|
RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;
|
|
4042
4146
|
SYNKRO_WEB_AUTH_URL = RAW_WEB_AUTH_URL && /^https?:\/\//.test(RAW_WEB_AUTH_URL) ? RAW_WEB_AUTH_URL : "https://app.synkro.sh";
|
|
4043
|
-
AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join3(
|
|
4147
|
+
AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join3(homedir4(), ".synkro", "credentials.json");
|
|
4044
4148
|
RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
|
|
4045
4149
|
SYNKRO_API_URL = RAW_API_URL && /^https?:\/\//.test(RAW_API_URL) ? RAW_API_URL : "https://api.synkro.sh";
|
|
4046
4150
|
ERROR_HTML = `
|
|
@@ -4203,7 +4307,7 @@ jobs:
|
|
|
4203
4307
|
});
|
|
4204
4308
|
|
|
4205
4309
|
// cli/installer/githubSetup.ts
|
|
4206
|
-
import { existsSync as
|
|
4310
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
4207
4311
|
import { execSync as execSync2 } from "child_process";
|
|
4208
4312
|
import { join as join4 } from "path";
|
|
4209
4313
|
function ghSecretSet(token, owner, repo, name, value) {
|
|
@@ -4260,7 +4364,7 @@ function writeWorkflowFile(repoRootPath) {
|
|
|
4260
4364
|
function findGitRoot(startCwd) {
|
|
4261
4365
|
let cur = startCwd;
|
|
4262
4366
|
while (cur && cur !== "/") {
|
|
4263
|
-
if (
|
|
4367
|
+
if (existsSync5(join4(cur, ".git"))) return cur;
|
|
4264
4368
|
const parent = join4(cur, "..");
|
|
4265
4369
|
if (parent === cur) break;
|
|
4266
4370
|
cur = parent;
|
|
@@ -4298,10 +4402,10 @@ function detectGitRepo() {
|
|
|
4298
4402
|
}
|
|
4299
4403
|
}
|
|
4300
4404
|
function ask(rl, question) {
|
|
4301
|
-
return new Promise((
|
|
4405
|
+
return new Promise((resolve3) => rl.question(question, resolve3));
|
|
4302
4406
|
}
|
|
4303
4407
|
function waitForGithubToken() {
|
|
4304
|
-
return new Promise((
|
|
4408
|
+
return new Promise((resolve3, reject) => {
|
|
4305
4409
|
const server = createServer2((req, res) => {
|
|
4306
4410
|
if (req.method === "OPTIONS") {
|
|
4307
4411
|
res.writeHead(204, {
|
|
@@ -4338,7 +4442,7 @@ function waitForGithubToken() {
|
|
|
4338
4442
|
});
|
|
4339
4443
|
res.end(JSON.stringify({ ok: true }));
|
|
4340
4444
|
setTimeout(() => server.close(), 200);
|
|
4341
|
-
|
|
4445
|
+
resolve3(parsed.github_token);
|
|
4342
4446
|
} catch {
|
|
4343
4447
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
4344
4448
|
res.end(JSON.stringify({ error: "invalid json" }));
|
|
@@ -4503,12 +4607,12 @@ __export(setupGithub_exports, {
|
|
|
4503
4607
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
4504
4608
|
import { stdin as input, stdout as output } from "process";
|
|
4505
4609
|
import { execSync as execSync4, spawn as nodeSpawn } from "child_process";
|
|
4506
|
-
import { existsSync as
|
|
4507
|
-
import { homedir as
|
|
4610
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
4611
|
+
import { homedir as homedir5, platform as platform2 } from "os";
|
|
4508
4612
|
import { join as join5 } from "path";
|
|
4509
4613
|
import { execFile as execFile2 } from "child_process";
|
|
4510
4614
|
function readConfig() {
|
|
4511
|
-
if (!
|
|
4615
|
+
if (!existsSync6(CONFIG_PATH)) return {};
|
|
4512
4616
|
const out = {};
|
|
4513
4617
|
for (const line of readFileSync5(CONFIG_PATH, "utf-8").split("\n")) {
|
|
4514
4618
|
const t = line.trim();
|
|
@@ -4523,7 +4627,7 @@ async function prompt(rl, q, opts = {}) {
|
|
|
4523
4627
|
process.stdout.write(q);
|
|
4524
4628
|
const wasRaw = process.stdin.isRaw;
|
|
4525
4629
|
if (process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
4526
|
-
return await new Promise((
|
|
4630
|
+
return await new Promise((resolve3) => {
|
|
4527
4631
|
let chunk = "";
|
|
4528
4632
|
const onData = (data) => {
|
|
4529
4633
|
const s = data.toString("utf-8");
|
|
@@ -4531,7 +4635,7 @@ async function prompt(rl, q, opts = {}) {
|
|
|
4531
4635
|
process.stdin.removeListener("data", onData);
|
|
4532
4636
|
if (process.stdin.setRawMode) process.stdin.setRawMode(wasRaw ?? false);
|
|
4533
4637
|
process.stdout.write("\n");
|
|
4534
|
-
|
|
4638
|
+
resolve3(chunk);
|
|
4535
4639
|
return;
|
|
4536
4640
|
}
|
|
4537
4641
|
if (s === "") process.exit(130);
|
|
@@ -4572,7 +4676,7 @@ function sleep(ms) {
|
|
|
4572
4676
|
}
|
|
4573
4677
|
function captureClaudeSetupToken() {
|
|
4574
4678
|
const tmpFile = join5(SYNKRO_DIR, `token-capture-${Date.now()}.raw`);
|
|
4575
|
-
return new Promise((
|
|
4679
|
+
return new Promise((resolve3, reject) => {
|
|
4576
4680
|
const proc = nodeSpawn("script", ["-q", tmpFile, "claude", "setup-token"], {
|
|
4577
4681
|
stdio: "inherit"
|
|
4578
4682
|
});
|
|
@@ -4602,7 +4706,7 @@ function captureClaudeSetupToken() {
|
|
|
4602
4706
|
reject(new Error(`Could not find token in claude setup-token output (file=${raw.length}b, yellow=${yellow.length}b)`));
|
|
4603
4707
|
return;
|
|
4604
4708
|
}
|
|
4605
|
-
|
|
4709
|
+
resolve3(token[0]);
|
|
4606
4710
|
});
|
|
4607
4711
|
});
|
|
4608
4712
|
}
|
|
@@ -4852,7 +4956,7 @@ var init_setupGithub = __esm({
|
|
|
4852
4956
|
"use strict";
|
|
4853
4957
|
init_githubSetup();
|
|
4854
4958
|
init_stub();
|
|
4855
|
-
SYNKRO_DIR = join5(
|
|
4959
|
+
SYNKRO_DIR = join5(homedir5(), ".synkro");
|
|
4856
4960
|
CONFIG_PATH = join5(SYNKRO_DIR, "config.env");
|
|
4857
4961
|
}
|
|
4858
4962
|
});
|
|
@@ -4881,11 +4985,11 @@ var init_promptFetcher = __esm({
|
|
|
4881
4985
|
});
|
|
4882
4986
|
|
|
4883
4987
|
// cli/local-cc/settings.ts
|
|
4884
|
-
import { existsSync as
|
|
4885
|
-
import { homedir as
|
|
4988
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
4989
|
+
import { homedir as homedir6 } from "os";
|
|
4886
4990
|
import { join as join6 } from "path";
|
|
4887
4991
|
function isLocalCCEnabled() {
|
|
4888
|
-
if (!
|
|
4992
|
+
if (!existsSync7(CONFIG_PATH2)) return false;
|
|
4889
4993
|
try {
|
|
4890
4994
|
const content = readFileSync6(CONFIG_PATH2, "utf-8");
|
|
4891
4995
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
@@ -4898,7 +5002,7 @@ var CONFIG_PATH2;
|
|
|
4898
5002
|
var init_settings = __esm({
|
|
4899
5003
|
"cli/local-cc/settings.ts"() {
|
|
4900
5004
|
"use strict";
|
|
4901
|
-
CONFIG_PATH2 = join6(
|
|
5005
|
+
CONFIG_PATH2 = join6(homedir6(), ".synkro", "config.env");
|
|
4902
5006
|
}
|
|
4903
5007
|
});
|
|
4904
5008
|
|
|
@@ -5052,9 +5156,9 @@ await mcp.connect(new StdioServerTransport());
|
|
|
5052
5156
|
});
|
|
5053
5157
|
|
|
5054
5158
|
// cli/local-cc/install.ts
|
|
5055
|
-
import { existsSync as
|
|
5159
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, readFileSync as readFileSync7, chmodSync, copyFileSync, renameSync as renameSync4, unlinkSync as unlinkSync4, openSync, fsyncSync, closeSync } from "fs";
|
|
5056
5160
|
import { join as join7 } from "path";
|
|
5057
|
-
import { homedir as
|
|
5161
|
+
import { homedir as homedir7 } from "os";
|
|
5058
5162
|
import { spawnSync } from "child_process";
|
|
5059
5163
|
function writePluginFiles() {
|
|
5060
5164
|
mkdirSync6(SESSION_DIR, { recursive: true });
|
|
@@ -5103,7 +5207,7 @@ function runBunInstall() {
|
|
|
5103
5207
|
}
|
|
5104
5208
|
}
|
|
5105
5209
|
function safelyMutateClaudeJson(mutator) {
|
|
5106
|
-
if (!
|
|
5210
|
+
if (!existsSync8(CLAUDE_JSON_PATH)) {
|
|
5107
5211
|
return;
|
|
5108
5212
|
}
|
|
5109
5213
|
const originalText = readFileSync7(CLAUDE_JSON_PATH, "utf-8");
|
|
@@ -5256,17 +5360,17 @@ var init_install = __esm({
|
|
|
5256
5360
|
"cli/local-cc/install.ts"() {
|
|
5257
5361
|
"use strict";
|
|
5258
5362
|
init_channelSource();
|
|
5259
|
-
CLAUDE_JSON_BACKUP_PATH = join7(
|
|
5260
|
-
SESSION_DIR = join7(
|
|
5363
|
+
CLAUDE_JSON_BACKUP_PATH = join7(homedir7(), ".claude.json.synkro-bak");
|
|
5364
|
+
SESSION_DIR = join7(homedir7(), ".synkro", "cc_sessions");
|
|
5261
5365
|
PLUGIN_PATH = join7(SESSION_DIR, "synkro-channel.ts");
|
|
5262
5366
|
PLUGIN_PKG_PATH = join7(SESSION_DIR, "package.json");
|
|
5263
5367
|
PLUGIN_SETTINGS_DIR = join7(SESSION_DIR, ".claude");
|
|
5264
5368
|
PLUGIN_SETTINGS_PATH = join7(PLUGIN_SETTINGS_DIR, "settings.json");
|
|
5265
5369
|
PROJECT_MCP_PATH = join7(SESSION_DIR, ".mcp.json");
|
|
5266
|
-
CLAUDE_JSON_PATH = join7(
|
|
5370
|
+
CLAUDE_JSON_PATH = join7(homedir7(), ".claude.json");
|
|
5267
5371
|
RUN_SCRIPT_PATH = join7(SESSION_DIR, "run-claude.sh");
|
|
5268
5372
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
5269
|
-
SESSION_DIR_2 = join7(
|
|
5373
|
+
SESSION_DIR_2 = join7(homedir7(), ".synkro", "cc_sessions_2");
|
|
5270
5374
|
PLUGIN_PATH_2 = join7(SESSION_DIR_2, "synkro-channel.ts");
|
|
5271
5375
|
PLUGIN_PKG_PATH_2 = join7(SESSION_DIR_2, "package.json");
|
|
5272
5376
|
PLUGIN_SETTINGS_DIR_2 = join7(SESSION_DIR_2, ".claude");
|
|
@@ -5425,7 +5529,7 @@ log "tmux session ended."
|
|
|
5425
5529
|
|
|
5426
5530
|
// cli/local-cc/pueue.ts
|
|
5427
5531
|
import { execFileSync, spawnSync as spawnSync2, spawn } from "child_process";
|
|
5428
|
-
import { homedir as
|
|
5532
|
+
import { homedir as homedir8 } from "os";
|
|
5429
5533
|
import { join as join8 } from "path";
|
|
5430
5534
|
import { connect } from "net";
|
|
5431
5535
|
function pueueAvailable() {
|
|
@@ -5542,14 +5646,14 @@ function ensureRunning(opts = {}) {
|
|
|
5542
5646
|
return startTask(opts);
|
|
5543
5647
|
}
|
|
5544
5648
|
function probePort(host, port, timeoutMs = 500) {
|
|
5545
|
-
return new Promise((
|
|
5649
|
+
return new Promise((resolve3) => {
|
|
5546
5650
|
const sock = connect(port, host);
|
|
5547
5651
|
const done = (ok) => {
|
|
5548
5652
|
try {
|
|
5549
5653
|
sock.destroy();
|
|
5550
5654
|
} catch {
|
|
5551
5655
|
}
|
|
5552
|
-
|
|
5656
|
+
resolve3(ok);
|
|
5553
5657
|
};
|
|
5554
5658
|
sock.once("connect", () => done(true));
|
|
5555
5659
|
sock.once("error", () => done(false));
|
|
@@ -5622,10 +5726,10 @@ var init_pueue = __esm({
|
|
|
5622
5726
|
"use strict";
|
|
5623
5727
|
TASK_LABEL = "synkro-local-cc";
|
|
5624
5728
|
TMUX_SESSION = "synkro-local-cc";
|
|
5625
|
-
SESSION_DIR2 = join8(
|
|
5729
|
+
SESSION_DIR2 = join8(homedir8(), ".synkro", "cc_sessions");
|
|
5626
5730
|
TASK_LABEL_2 = "synkro-local-cc-2";
|
|
5627
5731
|
TMUX_SESSION_2 = "synkro-local-cc-2";
|
|
5628
|
-
SESSION_DIR_22 = join8(
|
|
5732
|
+
SESSION_DIR_22 = join8(homedir8(), ".synkro", "cc_sessions_2");
|
|
5629
5733
|
PueueError = class extends Error {
|
|
5630
5734
|
constructor(message, cause) {
|
|
5631
5735
|
super(message);
|
|
@@ -5641,7 +5745,7 @@ var init_pueue = __esm({
|
|
|
5641
5745
|
|
|
5642
5746
|
// cli/local-cc/prompts.ts
|
|
5643
5747
|
import { readFileSync as readFileSync8 } from "fs";
|
|
5644
|
-
import { homedir as
|
|
5748
|
+
import { homedir as homedir9 } from "os";
|
|
5645
5749
|
import { join as join9 } from "path";
|
|
5646
5750
|
async function fetchPrimers() {
|
|
5647
5751
|
let jwt2 = "";
|
|
@@ -5684,7 +5788,7 @@ var CREDS_PATH, CHANNEL_REPLY_INSTRUCTIONS;
|
|
|
5684
5788
|
var init_prompts = __esm({
|
|
5685
5789
|
"cli/local-cc/prompts.ts"() {
|
|
5686
5790
|
"use strict";
|
|
5687
|
-
CREDS_PATH = join9(
|
|
5791
|
+
CREDS_PATH = join9(homedir9(), ".synkro", "credentials.json");
|
|
5688
5792
|
CHANNEL_REPLY_INSTRUCTIONS = `
|
|
5689
5793
|
DELIVERY METHOD \u2014 MANDATORY, OVERRIDES ALL OTHER OUTPUT RULES:
|
|
5690
5794
|
You are running inside a Synkro MCP channel. Do NOT output your verdict as text.
|
|
@@ -5696,9 +5800,9 @@ Any text output is silently discarded. Only the reply tool call is captured.`;
|
|
|
5696
5800
|
});
|
|
5697
5801
|
|
|
5698
5802
|
// cli/local-cc/turnLog.ts
|
|
5699
|
-
import { appendFileSync, existsSync as
|
|
5803
|
+
import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync7, openSync as openSync2, readFileSync as readFileSync9, readSync, closeSync as closeSync2, statSync, watchFile, unwatchFile } from "fs";
|
|
5700
5804
|
import { dirname as dirname5, join as join10 } from "path";
|
|
5701
|
-
import { homedir as
|
|
5805
|
+
import { homedir as homedir10 } from "os";
|
|
5702
5806
|
function truncate(s, max = PREVIEW_MAX) {
|
|
5703
5807
|
if (s.length <= max) return s;
|
|
5704
5808
|
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
@@ -5734,7 +5838,7 @@ function appendTurn(args2) {
|
|
|
5734
5838
|
}
|
|
5735
5839
|
}
|
|
5736
5840
|
function readRecentTurns(n = 20) {
|
|
5737
|
-
if (!
|
|
5841
|
+
if (!existsSync9(TURN_LOG_PATH)) return [];
|
|
5738
5842
|
try {
|
|
5739
5843
|
const size = statSync(TURN_LOG_PATH).size;
|
|
5740
5844
|
if (size === 0) return [];
|
|
@@ -5755,7 +5859,7 @@ function readRecentTurns(n = 20) {
|
|
|
5755
5859
|
function followTurns(onEntry) {
|
|
5756
5860
|
try {
|
|
5757
5861
|
mkdirSync7(dirname5(TURN_LOG_PATH), { recursive: true });
|
|
5758
|
-
if (!
|
|
5862
|
+
if (!existsSync9(TURN_LOG_PATH)) {
|
|
5759
5863
|
appendFileSync(TURN_LOG_PATH, "", "utf-8");
|
|
5760
5864
|
}
|
|
5761
5865
|
} catch {
|
|
@@ -5817,7 +5921,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
|
|
|
5817
5921
|
var init_turnLog = __esm({
|
|
5818
5922
|
"cli/local-cc/turnLog.ts"() {
|
|
5819
5923
|
"use strict";
|
|
5820
|
-
TURN_LOG_PATH = join10(
|
|
5924
|
+
TURN_LOG_PATH = join10(homedir10(), ".synkro", "cc_sessions", "turns.log");
|
|
5821
5925
|
PREVIEW_MAX = 400;
|
|
5822
5926
|
}
|
|
5823
5927
|
});
|
|
@@ -5832,7 +5936,7 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
5832
5936
|
const port = opts.port ?? CHANNEL_PORT;
|
|
5833
5937
|
const startedAt = Date.now();
|
|
5834
5938
|
try {
|
|
5835
|
-
const result = await new Promise((
|
|
5939
|
+
const result = await new Promise((resolve3, reject) => {
|
|
5836
5940
|
const req = httpRequest({
|
|
5837
5941
|
host: CHANNEL_HOST,
|
|
5838
5942
|
port,
|
|
@@ -5858,7 +5962,7 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
5858
5962
|
reject(new LocalCCError(parsed.error));
|
|
5859
5963
|
return;
|
|
5860
5964
|
}
|
|
5861
|
-
|
|
5965
|
+
resolve3(String(parsed.result ?? ""));
|
|
5862
5966
|
} catch (err) {
|
|
5863
5967
|
reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
|
|
5864
5968
|
}
|
|
@@ -5884,14 +5988,14 @@ async function submitToChannel(role, payload, opts = {}) {
|
|
|
5884
5988
|
}
|
|
5885
5989
|
}
|
|
5886
5990
|
function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500) {
|
|
5887
|
-
return new Promise((
|
|
5991
|
+
return new Promise((resolve3) => {
|
|
5888
5992
|
const sock = connect2(port, CHANNEL_HOST);
|
|
5889
5993
|
const done = (ok) => {
|
|
5890
5994
|
try {
|
|
5891
5995
|
sock.destroy();
|
|
5892
5996
|
} catch {
|
|
5893
5997
|
}
|
|
5894
|
-
|
|
5998
|
+
resolve3(ok);
|
|
5895
5999
|
};
|
|
5896
6000
|
sock.once("connect", () => done(true));
|
|
5897
6001
|
sock.once("error", () => done(false));
|
|
@@ -5924,8 +6028,8 @@ __export(install_exports, {
|
|
|
5924
6028
|
installCommand: () => installCommand,
|
|
5925
6029
|
parseArgs: () => parseArgs
|
|
5926
6030
|
});
|
|
5927
|
-
import { existsSync as
|
|
5928
|
-
import { homedir as
|
|
6031
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync10, readdirSync, appendFileSync as appendFileSync2, renameSync as renameSync5 } from "fs";
|
|
6032
|
+
import { homedir as homedir11 } from "os";
|
|
5929
6033
|
import { join as join11 } from "path";
|
|
5930
6034
|
import { execSync as execSync5, spawnSync as spawnSync3, spawn as spawn2 } from "child_process";
|
|
5931
6035
|
import { createInterface as createInterface3 } from "readline";
|
|
@@ -5951,13 +6055,13 @@ function parseArgs(argv) {
|
|
|
5951
6055
|
}
|
|
5952
6056
|
async function promptTranscriptConsent() {
|
|
5953
6057
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
5954
|
-
return new Promise((
|
|
6058
|
+
return new Promise((resolve3) => {
|
|
5955
6059
|
rl.question(
|
|
5956
6060
|
"Would you like Synkro to use Claude Code session transcripts\nto generate guardrail rules and policies for your team? (Y/n) ",
|
|
5957
6061
|
(answer) => {
|
|
5958
6062
|
rl.close();
|
|
5959
6063
|
const trimmed = answer.trim().toLowerCase();
|
|
5960
|
-
|
|
6064
|
+
resolve3(trimmed === "" || trimmed === "y" || trimmed === "yes");
|
|
5961
6065
|
}
|
|
5962
6066
|
);
|
|
5963
6067
|
});
|
|
@@ -5983,9 +6087,7 @@ function writeHookScripts() {
|
|
|
5983
6087
|
const commonScriptPath = join11(HOOKS_DIR, "_synkro-common.ts");
|
|
5984
6088
|
const commonBashScriptPath = join11(HOOKS_DIR, "_synkro-common.sh");
|
|
5985
6089
|
const cursorBashJudgePath = join11(HOOKS_DIR, "cursor-bash-judge.ts");
|
|
5986
|
-
const cursorEditPrecheckPath = join11(HOOKS_DIR, "cursor-edit-precheck.ts");
|
|
5987
6090
|
const cursorEditCapturePath = join11(HOOKS_DIR, "cursor-edit-capture.ts");
|
|
5988
|
-
const cursorBashFollowupPath = join11(HOOKS_DIR, "cursor-bash-followup.ts");
|
|
5989
6091
|
const mcpLocalServerPath = join11(HOOKS_DIR, "mcp-local-server.ts");
|
|
5990
6092
|
writeFileSync7(bashScriptPath, BASH_JUDGE_TS, "utf-8");
|
|
5991
6093
|
writeFileSync7(bashFollowupScriptPath, BASH_FOLLOWUP_TS, "utf-8");
|
|
@@ -6001,9 +6103,7 @@ function writeHookScripts() {
|
|
|
6001
6103
|
writeFileSync7(commonScriptPath, SYNKRO_COMMON_TS, "utf-8");
|
|
6002
6104
|
writeFileSync7(commonBashScriptPath, SYNKRO_COMMON_SCRIPT, "utf-8");
|
|
6003
6105
|
writeFileSync7(cursorBashJudgePath, CURSOR_BASH_JUDGE_TS, "utf-8");
|
|
6004
|
-
writeFileSync7(cursorEditPrecheckPath, CURSOR_EDIT_PRECHECK_TS, "utf-8");
|
|
6005
6106
|
writeFileSync7(cursorEditCapturePath, CURSOR_EDIT_CAPTURE_TS, "utf-8");
|
|
6006
|
-
writeFileSync7(cursorBashFollowupPath, CURSOR_BASH_FOLLOWUP_TS, "utf-8");
|
|
6007
6107
|
writeFileSync7(mcpLocalServerPath, `#!/usr/bin/env bun
|
|
6008
6108
|
/**
|
|
6009
6109
|
* Local MCP guardrails server \u2014 runs on port 8931, stores rules in ~/.synkro/rules.json.
|
|
@@ -6824,9 +6924,7 @@ console.log(\`[synkro] local MCP guardrails server listening on http://127.0.0.1
|
|
|
6824
6924
|
chmodSync2(commonScriptPath, 493);
|
|
6825
6925
|
chmodSync2(commonBashScriptPath, 493);
|
|
6826
6926
|
chmodSync2(cursorBashJudgePath, 493);
|
|
6827
|
-
chmodSync2(cursorEditPrecheckPath, 493);
|
|
6828
6927
|
chmodSync2(cursorEditCapturePath, 493);
|
|
6829
|
-
chmodSync2(cursorBashFollowupPath, 493);
|
|
6830
6928
|
chmodSync2(mcpLocalServerPath, 493);
|
|
6831
6929
|
return {
|
|
6832
6930
|
bashScript: bashScriptPath,
|
|
@@ -6841,9 +6939,7 @@ console.log(\`[synkro] local MCP guardrails server listening on http://127.0.0.1
|
|
|
6841
6939
|
transcriptSyncScript: transcriptSyncScriptPath,
|
|
6842
6940
|
userPromptSubmitScript: userPromptSubmitScriptPath,
|
|
6843
6941
|
cursorBashJudgeScript: cursorBashJudgePath,
|
|
6844
|
-
cursorEditPrecheckScript: cursorEditPrecheckPath,
|
|
6845
6942
|
cursorEditCaptureScript: cursorEditCapturePath,
|
|
6846
|
-
cursorBashFollowupScript: cursorBashFollowupPath,
|
|
6847
6943
|
mcpLocalServerScript: mcpLocalServerPath
|
|
6848
6944
|
};
|
|
6849
6945
|
}
|
|
@@ -6856,7 +6952,7 @@ function shellQuoteSingle(value) {
|
|
|
6856
6952
|
}
|
|
6857
6953
|
function resolveSynkroBundle() {
|
|
6858
6954
|
const scriptPath = process.argv[1];
|
|
6859
|
-
if (scriptPath &&
|
|
6955
|
+
if (scriptPath && existsSync10(scriptPath)) return scriptPath;
|
|
6860
6956
|
return null;
|
|
6861
6957
|
}
|
|
6862
6958
|
function writeConfigEnv(opts) {
|
|
@@ -6876,7 +6972,7 @@ function writeConfigEnv(opts) {
|
|
|
6876
6972
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
6877
6973
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
6878
6974
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
6879
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.4.
|
|
6975
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.4.68")}`
|
|
6880
6976
|
];
|
|
6881
6977
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
6882
6978
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -6891,7 +6987,7 @@ function writeConfigEnv(opts) {
|
|
|
6891
6987
|
chmodSync2(CONFIG_PATH3, 384);
|
|
6892
6988
|
}
|
|
6893
6989
|
function updateLocalInferenceFlag(enabled) {
|
|
6894
|
-
if (!
|
|
6990
|
+
if (!existsSync10(CONFIG_PATH3)) return;
|
|
6895
6991
|
let content = readFileSync10(CONFIG_PATH3, "utf-8");
|
|
6896
6992
|
const flag = enabled ? "yes" : "no";
|
|
6897
6993
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
@@ -6921,7 +7017,7 @@ function collectLocalMetadata() {
|
|
|
6921
7017
|
meta.cc_version = execSync5("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
6922
7018
|
} catch {
|
|
6923
7019
|
}
|
|
6924
|
-
const claudeDir = join11(
|
|
7020
|
+
const claudeDir = join11(homedir11(), ".claude");
|
|
6925
7021
|
try {
|
|
6926
7022
|
const settings = JSON.parse(readFileSync10(join11(claudeDir, "settings.json"), "utf-8"));
|
|
6927
7023
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
@@ -7011,10 +7107,10 @@ function isAlreadyInstalled() {
|
|
|
7011
7107
|
join11(HOOKS_DIR, "cc-stop-summary.ts"),
|
|
7012
7108
|
join11(HOOKS_DIR, "cc-session-start.ts")
|
|
7013
7109
|
];
|
|
7014
|
-
if (!requiredScripts.every((p) =>
|
|
7015
|
-
if (!
|
|
7016
|
-
const settingsPath = join11(
|
|
7017
|
-
if (!
|
|
7110
|
+
if (!requiredScripts.every((p) => existsSync10(p))) return false;
|
|
7111
|
+
if (!existsSync10(CONFIG_PATH3)) return false;
|
|
7112
|
+
const settingsPath = join11(homedir11(), ".claude", "settings.json");
|
|
7113
|
+
if (!existsSync10(settingsPath)) return false;
|
|
7018
7114
|
try {
|
|
7019
7115
|
const settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
|
|
7020
7116
|
const hooks = settings?.hooks;
|
|
@@ -7048,8 +7144,8 @@ function printChannelDiagnostics() {
|
|
|
7048
7144
|
}
|
|
7049
7145
|
}
|
|
7050
7146
|
}
|
|
7051
|
-
const logPath = join11(
|
|
7052
|
-
if (
|
|
7147
|
+
const logPath = join11(homedir11(), ".synkro", "cc_sessions", "run-claude.log");
|
|
7148
|
+
if (existsSync10(logPath)) {
|
|
7053
7149
|
const logContent = readFileSync10(logPath, "utf-8").trim().split("\n").slice(-10);
|
|
7054
7150
|
console.warn(` run-claude.log:`);
|
|
7055
7151
|
for (const line of logContent) console.warn(` ${line}`);
|
|
@@ -7059,7 +7155,7 @@ function printChannelDiagnostics() {
|
|
|
7059
7155
|
console.warn(` Run \`synkro local-cc status\` and \`synkro local-cc logs --tmux\` to debug.`);
|
|
7060
7156
|
}
|
|
7061
7157
|
async function backfillLocalRules(gatewayUrl, token) {
|
|
7062
|
-
if (
|
|
7158
|
+
if (existsSync10(RULES_PATH)) {
|
|
7063
7159
|
console.log(" Local rules already exist \u2014 skipping cloud backfill.");
|
|
7064
7160
|
return;
|
|
7065
7161
|
}
|
|
@@ -7123,7 +7219,7 @@ async function backfillLocalRules(gatewayUrl, token) {
|
|
|
7123
7219
|
}
|
|
7124
7220
|
async function startLocalMcpServer() {
|
|
7125
7221
|
const serverScript = join11(HOOKS_DIR, "mcp-local-server.ts");
|
|
7126
|
-
if (!
|
|
7222
|
+
if (!existsSync10(serverScript)) {
|
|
7127
7223
|
console.warn(" \u26A0 Local MCP server script not found \u2014 skipping.");
|
|
7128
7224
|
return;
|
|
7129
7225
|
}
|
|
@@ -7327,9 +7423,17 @@ async function installCommand(opts = {}) {
|
|
|
7327
7423
|
hasCursor = true;
|
|
7328
7424
|
installCursorHooks(agent.settingsPath, {
|
|
7329
7425
|
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
7330
|
-
editPrecheckScriptPath: scripts.cursorEditPrecheckScript,
|
|
7331
7426
|
editCaptureScriptPath: scripts.cursorEditCaptureScript,
|
|
7332
|
-
bashFollowupScriptPath: scripts.
|
|
7427
|
+
bashFollowupScriptPath: scripts.bashFollowupScript,
|
|
7428
|
+
editPrecheckScriptPath: scripts.editPrecheckScript,
|
|
7429
|
+
cwePrecheckScriptPath: scripts.cwePrecheckScript,
|
|
7430
|
+
cvePrecheckScriptPath: scripts.cvePrecheckScript,
|
|
7431
|
+
planJudgeScriptPath: scripts.planJudgeScript,
|
|
7432
|
+
agentJudgeScriptPath: scripts.agentJudgeScript,
|
|
7433
|
+
stopSummaryScriptPath: scripts.stopSummaryScript,
|
|
7434
|
+
sessionStartScriptPath: scripts.sessionStartScript,
|
|
7435
|
+
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
7436
|
+
transcriptSyncScriptPath: scripts.transcriptSyncScript
|
|
7333
7437
|
});
|
|
7334
7438
|
console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
|
|
7335
7439
|
}
|
|
@@ -7542,8 +7646,8 @@ function detectGitRepo2() {
|
|
|
7542
7646
|
function getClaudeProjectsFolder() {
|
|
7543
7647
|
const cwd = process.cwd();
|
|
7544
7648
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
7545
|
-
const projectsDir = join11(
|
|
7546
|
-
return
|
|
7649
|
+
const projectsDir = join11(homedir11(), ".claude", "projects", sanitized);
|
|
7650
|
+
return existsSync10(projectsDir) ? projectsDir : null;
|
|
7547
7651
|
}
|
|
7548
7652
|
function extractSessionInsights(projectsDir) {
|
|
7549
7653
|
const insights = [];
|
|
@@ -7738,7 +7842,7 @@ var init_install2 = __esm({
|
|
|
7738
7842
|
init_install();
|
|
7739
7843
|
init_pueue();
|
|
7740
7844
|
init_client();
|
|
7741
|
-
SYNKRO_DIR2 = join11(
|
|
7845
|
+
SYNKRO_DIR2 = join11(homedir11(), ".synkro");
|
|
7742
7846
|
HOOKS_DIR = join11(SYNKRO_DIR2, "hooks");
|
|
7743
7847
|
BIN_DIR = join11(SYNKRO_DIR2, "bin");
|
|
7744
7848
|
CONFIG_PATH3 = join11(SYNKRO_DIR2, "config.env");
|
|
@@ -7820,11 +7924,11 @@ var status_exports = {};
|
|
|
7820
7924
|
__export(status_exports, {
|
|
7821
7925
|
statusCommand: () => statusCommand
|
|
7822
7926
|
});
|
|
7823
|
-
import { existsSync as
|
|
7824
|
-
import { homedir as
|
|
7927
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
7928
|
+
import { homedir as homedir12 } from "os";
|
|
7825
7929
|
import { join as join12 } from "path";
|
|
7826
7930
|
function readConfigEnv() {
|
|
7827
|
-
if (!
|
|
7931
|
+
if (!existsSync11(CONFIG_PATH4)) return {};
|
|
7828
7932
|
const out = {};
|
|
7829
7933
|
const raw = readFileSync11(CONFIG_PATH4, "utf-8");
|
|
7830
7934
|
for (const line of raw.split("\n")) {
|
|
@@ -7901,10 +8005,20 @@ async function statusCommand() {
|
|
|
7901
8005
|
const hooks = inspectCursorHooks(a.settingsPath);
|
|
7902
8006
|
console.log(` hooks installed: ${hooks.installed ? "\u2713" : "\u2717"}`);
|
|
7903
8007
|
if (hooks.installed) {
|
|
8008
|
+
console.log(` \u2022 sessionStart: ${hooks.sessionStart ? "\u2713" : "\u2717"}`);
|
|
8009
|
+
console.log(` \u2022 sessionEnd: ${hooks.sessionEnd ? "\u2713" : "\u2717"}`);
|
|
8010
|
+
console.log(` \u2022 beforeSubmitPrompt: ${hooks.beforeSubmitPrompt ? "\u2713" : "\u2717"}`);
|
|
7904
8011
|
console.log(` \u2022 beforeShellExecution: ${hooks.beforeShellExecution ? "\u2713" : "\u2717"}`);
|
|
7905
|
-
console.log(` \u2022
|
|
8012
|
+
console.log(` \u2022 afterShellExecution: ${hooks.afterShellExecution ? "\u2713" : "\u2717"}`);
|
|
8013
|
+
console.log(` \u2022 PreToolUse Bash: ${hooks.preToolUseBash ? "\u2713" : "\u2717"}`);
|
|
8014
|
+
console.log(` \u2022 PreToolUse Edit: ${hooks.preToolUseEdit ? "\u2713" : "\u2717"}`);
|
|
8015
|
+
console.log(` \u2022 PreToolUse CWE: ${hooks.preToolUseCwe ? "\u2713" : "\u2717"}`);
|
|
8016
|
+
console.log(` \u2022 PreToolUse CVE: ${hooks.preToolUseCve ? "\u2713" : "\u2717"}`);
|
|
8017
|
+
console.log(` \u2022 PreToolUse Agent: ${hooks.preToolUseAgent ? "\u2713" : "\u2717"}`);
|
|
8018
|
+
console.log(` \u2022 PreToolUse Plan: ${hooks.preToolUsePlan ? "\u2713" : "\u2717"}`);
|
|
7906
8019
|
console.log(` \u2022 afterFileEdit: ${hooks.afterFileEdit ? "\u2713" : "\u2717"}`);
|
|
7907
8020
|
console.log(` \u2022 postToolUse: ${hooks.postToolUse ? "\u2713" : "\u2717"}`);
|
|
8021
|
+
console.log(` \u2022 stop (transcript): ${hooks.stop ? "\u2713" : "\u2717"}`);
|
|
7908
8022
|
}
|
|
7909
8023
|
}
|
|
7910
8024
|
}
|
|
@@ -7918,6 +8032,7 @@ async function statusCommand() {
|
|
|
7918
8032
|
"cc-cwe-precheck.ts",
|
|
7919
8033
|
"cc-cve-precheck.ts",
|
|
7920
8034
|
"cc-plan-judge.ts",
|
|
8035
|
+
"cc-agent-judge.ts",
|
|
7921
8036
|
"cc-stop-summary.ts",
|
|
7922
8037
|
"cc-session-start.ts",
|
|
7923
8038
|
"cc-transcript-sync.ts",
|
|
@@ -7925,20 +8040,29 @@ async function statusCommand() {
|
|
|
7925
8040
|
"_synkro-common.ts"
|
|
7926
8041
|
];
|
|
7927
8042
|
const cursorHooks = [
|
|
7928
|
-
"cursor-bash-judge.
|
|
7929
|
-
"cursor-edit-
|
|
7930
|
-
"
|
|
7931
|
-
"
|
|
8043
|
+
"cursor-bash-judge.ts",
|
|
8044
|
+
"cursor-edit-capture.ts",
|
|
8045
|
+
"cc-edit-precheck.ts",
|
|
8046
|
+
"cc-cwe-precheck.ts",
|
|
8047
|
+
"cc-cve-precheck.ts",
|
|
8048
|
+
"cc-agent-judge.ts",
|
|
8049
|
+
"cc-plan-judge.ts",
|
|
8050
|
+
"cc-session-start.ts",
|
|
8051
|
+
"cc-stop-summary.ts",
|
|
8052
|
+
"cc-bash-followup.ts",
|
|
8053
|
+
"cc-user-prompt-submit.ts",
|
|
8054
|
+
"cc-transcript-sync.ts",
|
|
8055
|
+
"_synkro-common.ts"
|
|
7932
8056
|
];
|
|
7933
8057
|
console.log("Hook scripts (Claude Code):");
|
|
7934
8058
|
for (const f of ccHooks) {
|
|
7935
8059
|
const p = join12(HOOKS_DIR2, f);
|
|
7936
|
-
console.log(` ${
|
|
8060
|
+
console.log(` ${existsSync11(p) ? "\u2713" : "\u2717"} ${p}`);
|
|
7937
8061
|
}
|
|
7938
8062
|
console.log("Hook scripts (Cursor):");
|
|
7939
8063
|
for (const f of cursorHooks) {
|
|
7940
8064
|
const p = join12(HOOKS_DIR2, f);
|
|
7941
|
-
console.log(` ${
|
|
8065
|
+
console.log(` ${existsSync11(p) ? "\u2713" : "\u2717"} ${p}`);
|
|
7942
8066
|
}
|
|
7943
8067
|
console.log();
|
|
7944
8068
|
if (localInference) {
|
|
@@ -7981,7 +8105,7 @@ var init_status = __esm({
|
|
|
7981
8105
|
init_cursorHookConfig();
|
|
7982
8106
|
init_mcpConfig();
|
|
7983
8107
|
init_pueue();
|
|
7984
|
-
SYNKRO_DIR3 = join12(
|
|
8108
|
+
SYNKRO_DIR3 = join12(homedir12(), ".synkro");
|
|
7985
8109
|
CONFIG_PATH4 = join12(SYNKRO_DIR3, "config.env");
|
|
7986
8110
|
}
|
|
7987
8111
|
});
|
|
@@ -8014,7 +8138,7 @@ __export(unlink_exports, {
|
|
|
8014
8138
|
});
|
|
8015
8139
|
import { createInterface as createInterface4 } from "readline";
|
|
8016
8140
|
function ask2(rl, question) {
|
|
8017
|
-
return new Promise((
|
|
8141
|
+
return new Promise((resolve3) => rl.question(question, resolve3));
|
|
8018
8142
|
}
|
|
8019
8143
|
async function unlinkCommand() {
|
|
8020
8144
|
if (!isAuthenticated()) {
|
|
@@ -8071,11 +8195,11 @@ var config_exports = {};
|
|
|
8071
8195
|
__export(config_exports, {
|
|
8072
8196
|
configCommand: () => configCommand
|
|
8073
8197
|
});
|
|
8074
|
-
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as
|
|
8198
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync12 } from "fs";
|
|
8075
8199
|
import { join as join13 } from "path";
|
|
8076
|
-
import { homedir as
|
|
8200
|
+
import { homedir as homedir13 } from "os";
|
|
8077
8201
|
function readConfigEnv2() {
|
|
8078
|
-
if (!
|
|
8202
|
+
if (!existsSync12(CONFIG_PATH5)) return {};
|
|
8079
8203
|
const out = {};
|
|
8080
8204
|
for (const line of readFileSync12(CONFIG_PATH5, "utf-8").split("\n")) {
|
|
8081
8205
|
const t = line.trim();
|
|
@@ -8086,7 +8210,7 @@ function readConfigEnv2() {
|
|
|
8086
8210
|
return out;
|
|
8087
8211
|
}
|
|
8088
8212
|
function updateConfigValue(key, value) {
|
|
8089
|
-
if (!
|
|
8213
|
+
if (!existsSync12(CONFIG_PATH5)) {
|
|
8090
8214
|
console.error("No config found. Run `synkro install` first.");
|
|
8091
8215
|
process.exit(1);
|
|
8092
8216
|
}
|
|
@@ -8157,7 +8281,7 @@ var init_config = __esm({
|
|
|
8157
8281
|
"cli/commands/config.ts"() {
|
|
8158
8282
|
"use strict";
|
|
8159
8283
|
init_stub();
|
|
8160
|
-
SYNKRO_DIR4 = join13(
|
|
8284
|
+
SYNKRO_DIR4 = join13(homedir13(), ".synkro");
|
|
8161
8285
|
CONFIG_PATH5 = join13(SYNKRO_DIR4, "config.env");
|
|
8162
8286
|
}
|
|
8163
8287
|
});
|
|
@@ -8168,7 +8292,7 @@ __export(scanPr_exports, {
|
|
|
8168
8292
|
scanPrCommand: () => scanPrCommand
|
|
8169
8293
|
});
|
|
8170
8294
|
import { execSync as execSync6, spawn as spawn3 } from "child_process";
|
|
8171
|
-
import { readFileSync as readFileSync13, existsSync as
|
|
8295
|
+
import { readFileSync as readFileSync13, existsSync as existsSync13 } from "fs";
|
|
8172
8296
|
import { join as join14 } from "path";
|
|
8173
8297
|
function parseMatchSpec(condition) {
|
|
8174
8298
|
if (!condition.startsWith("match_spec:")) return null;
|
|
@@ -8376,7 +8500,7 @@ function spawnClaudeJudge(file, claudeToken, promptHeader) {
|
|
|
8376
8500
|
Diff:
|
|
8377
8501
|
${hunks}`;
|
|
8378
8502
|
const fullPrompt = promptHeader + userMessage;
|
|
8379
|
-
return new Promise((
|
|
8503
|
+
return new Promise((resolve3) => {
|
|
8380
8504
|
const t0 = Date.now();
|
|
8381
8505
|
const proc = spawn3(
|
|
8382
8506
|
"claude",
|
|
@@ -8404,7 +8528,7 @@ ${hunks}`;
|
|
|
8404
8528
|
const latencyMs = Date.now() - t0;
|
|
8405
8529
|
if (code !== 0) {
|
|
8406
8530
|
console.warn(` claude exited ${code}: ${(stderr || stdout).slice(0, 500)}`);
|
|
8407
|
-
|
|
8531
|
+
resolve3({ findings: [], latencyMs });
|
|
8408
8532
|
return;
|
|
8409
8533
|
}
|
|
8410
8534
|
try {
|
|
@@ -8423,10 +8547,10 @@ ${hunks}`;
|
|
|
8423
8547
|
description: f.description,
|
|
8424
8548
|
fix: f.fix
|
|
8425
8549
|
}));
|
|
8426
|
-
|
|
8550
|
+
resolve3({ findings, latencyMs });
|
|
8427
8551
|
} catch (parseErr) {
|
|
8428
8552
|
console.warn(` failed to parse claude response: ${stdout.slice(0, 300)}`);
|
|
8429
|
-
|
|
8553
|
+
resolve3({ findings: [], latencyMs });
|
|
8430
8554
|
}
|
|
8431
8555
|
});
|
|
8432
8556
|
});
|
|
@@ -8475,7 +8599,7 @@ ${JSON.stringify(findings, null, 2)}
|
|
|
8475
8599
|
`;
|
|
8476
8600
|
}
|
|
8477
8601
|
function spawnOpusConsolidator(findings, claudeToken) {
|
|
8478
|
-
return new Promise((
|
|
8602
|
+
return new Promise((resolve3) => {
|
|
8479
8603
|
const prompt2 = buildConsolidationPrompt(findings);
|
|
8480
8604
|
const proc = spawn3(
|
|
8481
8605
|
"claude",
|
|
@@ -8502,7 +8626,7 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
8502
8626
|
proc.on("close", (code) => {
|
|
8503
8627
|
if (code !== 0) {
|
|
8504
8628
|
console.warn(` opus consolidation exited ${code}: ${(stderr || stdout).slice(0, 300)}`);
|
|
8505
|
-
|
|
8629
|
+
resolve3(fallbackReview(findings));
|
|
8506
8630
|
return;
|
|
8507
8631
|
}
|
|
8508
8632
|
try {
|
|
@@ -8523,10 +8647,10 @@ function spawnOpusConsolidator(findings, claudeToken) {
|
|
|
8523
8647
|
const order = ["low", "medium", "high", "critical"];
|
|
8524
8648
|
return order.indexOf(f.severity) > order.indexOf(max) ? f.severity : max;
|
|
8525
8649
|
}, "low");
|
|
8526
|
-
|
|
8650
|
+
resolve3({ summary: review.summary || "", comments, severity: maxSeverity });
|
|
8527
8651
|
} catch {
|
|
8528
8652
|
console.warn(` failed to parse opus response, using fallback`);
|
|
8529
|
-
|
|
8653
|
+
resolve3(fallbackReview(findings));
|
|
8530
8654
|
}
|
|
8531
8655
|
});
|
|
8532
8656
|
});
|
|
@@ -8649,7 +8773,7 @@ function shouldFail(findings, threshold) {
|
|
|
8649
8773
|
}
|
|
8650
8774
|
function readRepoDeps() {
|
|
8651
8775
|
const pkgPath = join14(process.cwd(), "package.json");
|
|
8652
|
-
if (!
|
|
8776
|
+
if (!existsSync13(pkgPath)) return {};
|
|
8653
8777
|
try {
|
|
8654
8778
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
8655
8779
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -8913,8 +9037,8 @@ var disconnect_exports = {};
|
|
|
8913
9037
|
__export(disconnect_exports, {
|
|
8914
9038
|
disconnectCommand: () => disconnectCommand
|
|
8915
9039
|
});
|
|
8916
|
-
import { existsSync as
|
|
8917
|
-
import { homedir as
|
|
9040
|
+
import { existsSync as existsSync14, rmSync } from "fs";
|
|
9041
|
+
import { homedir as homedir14 } from "os";
|
|
8918
9042
|
import { join as join15 } from "path";
|
|
8919
9043
|
function tearDownLocalCC() {
|
|
8920
9044
|
let hadTask = false;
|
|
@@ -8951,13 +9075,13 @@ function disconnectCommand(args2 = []) {
|
|
|
8951
9075
|
console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
|
|
8952
9076
|
}
|
|
8953
9077
|
if (purge) {
|
|
8954
|
-
if (
|
|
9078
|
+
if (existsSync14(SYNKRO_DIR5)) {
|
|
8955
9079
|
rmSync(SYNKRO_DIR5, { recursive: true, force: true });
|
|
8956
9080
|
console.log(`\u2713 Removed ${SYNKRO_DIR5}`);
|
|
8957
9081
|
} else {
|
|
8958
9082
|
console.log(`\xB7 ${SYNKRO_DIR5} already gone, nothing to remove`);
|
|
8959
9083
|
}
|
|
8960
|
-
} else if (
|
|
9084
|
+
} else if (existsSync14(SYNKRO_DIR5)) {
|
|
8961
9085
|
console.log(`Config preserved at ${SYNKRO_DIR5}. Run with --purge to remove.`);
|
|
8962
9086
|
}
|
|
8963
9087
|
console.log("\nSynkro disconnected.");
|
|
@@ -8972,7 +9096,7 @@ var init_disconnect = __esm({
|
|
|
8972
9096
|
init_mcpConfig();
|
|
8973
9097
|
init_pueue();
|
|
8974
9098
|
init_install();
|
|
8975
|
-
SYNKRO_DIR5 = join15(
|
|
9099
|
+
SYNKRO_DIR5 = join15(homedir14(), ".synkro");
|
|
8976
9100
|
}
|
|
8977
9101
|
});
|
|
8978
9102
|
|
|
@@ -9019,9 +9143,9 @@ __export(localCc_exports, {
|
|
|
9019
9143
|
localCcCommand: () => localCcCommand
|
|
9020
9144
|
});
|
|
9021
9145
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
9022
|
-
import { homedir as
|
|
9146
|
+
import { homedir as homedir15 } from "os";
|
|
9023
9147
|
import { join as join16 } from "path";
|
|
9024
|
-
import { existsSync as
|
|
9148
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
9025
9149
|
function printHelp() {
|
|
9026
9150
|
console.log(`synkro local-cc \u2014 manage the local Claude Code inference session
|
|
9027
9151
|
|
|
@@ -9111,14 +9235,14 @@ TROUBLESHOOTING
|
|
|
9111
9235
|
`);
|
|
9112
9236
|
}
|
|
9113
9237
|
function readGatewayUrl() {
|
|
9114
|
-
if (
|
|
9238
|
+
if (existsSync15(CONFIG_PATH6)) {
|
|
9115
9239
|
const m = readFileSync14(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
9116
9240
|
if (m) return m[1];
|
|
9117
9241
|
}
|
|
9118
9242
|
return "https://api.synkro.sh";
|
|
9119
9243
|
}
|
|
9120
9244
|
function updateLocalInferenceFlag2(enabled) {
|
|
9121
|
-
if (!
|
|
9245
|
+
if (!existsSync15(CONFIG_PATH6)) return;
|
|
9122
9246
|
let content = readFileSync14(CONFIG_PATH6, "utf-8");
|
|
9123
9247
|
const flag = enabled ? "yes" : "no";
|
|
9124
9248
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
@@ -9348,7 +9472,7 @@ function cmdLogs(rest) {
|
|
|
9348
9472
|
if (!raw) console.log(" " + colorize("(use --raw / -r to see full payloads, --live / -f to follow)", 90));
|
|
9349
9473
|
return;
|
|
9350
9474
|
}
|
|
9351
|
-
return new Promise((
|
|
9475
|
+
return new Promise((resolve3) => {
|
|
9352
9476
|
console.log(" " + colorize("\u2014 following new turns (Ctrl-C to exit) \u2014", 90));
|
|
9353
9477
|
const stop = followTurns((t) => {
|
|
9354
9478
|
console.log(" " + formatTurn(t, raw));
|
|
@@ -9356,7 +9480,7 @@ function cmdLogs(rest) {
|
|
|
9356
9480
|
const onSigint = () => {
|
|
9357
9481
|
stop();
|
|
9358
9482
|
process.removeListener("SIGINT", onSigint);
|
|
9359
|
-
|
|
9483
|
+
resolve3();
|
|
9360
9484
|
};
|
|
9361
9485
|
process.on("SIGINT", onSigint);
|
|
9362
9486
|
});
|
|
@@ -9453,7 +9577,7 @@ var init_localCc = __esm({
|
|
|
9453
9577
|
init_settings();
|
|
9454
9578
|
init_client();
|
|
9455
9579
|
init_stub();
|
|
9456
|
-
CONFIG_PATH6 = join16(
|
|
9580
|
+
CONFIG_PATH6 = join16(homedir15(), ".synkro", "config.env");
|
|
9457
9581
|
}
|
|
9458
9582
|
});
|
|
9459
9583
|
|
|
@@ -9463,10 +9587,10 @@ __export(grade_exports, {
|
|
|
9463
9587
|
gradeCommand: () => gradeCommand
|
|
9464
9588
|
});
|
|
9465
9589
|
async function readStdin() {
|
|
9466
|
-
return new Promise((
|
|
9590
|
+
return new Promise((resolve3, reject) => {
|
|
9467
9591
|
const chunks = [];
|
|
9468
9592
|
process.stdin.on("data", (c) => chunks.push(c));
|
|
9469
|
-
process.stdin.on("end", () =>
|
|
9593
|
+
process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
|
|
9470
9594
|
process.stdin.on("error", reject);
|
|
9471
9595
|
});
|
|
9472
9596
|
}
|
|
@@ -9507,14 +9631,14 @@ var init_grade = __esm({
|
|
|
9507
9631
|
});
|
|
9508
9632
|
|
|
9509
9633
|
// cli/bootstrap.js
|
|
9510
|
-
import { readFileSync as readFileSync15, existsSync as
|
|
9511
|
-
import { resolve } from "path";
|
|
9634
|
+
import { readFileSync as readFileSync15, existsSync as existsSync16 } from "fs";
|
|
9635
|
+
import { resolve as resolve2 } from "path";
|
|
9512
9636
|
var envCandidates = [
|
|
9513
|
-
|
|
9514
|
-
|
|
9637
|
+
resolve2(process.cwd(), ".env"),
|
|
9638
|
+
resolve2(process.env.HOME ?? "", ".synkro", "config.env")
|
|
9515
9639
|
];
|
|
9516
9640
|
for (const envPath of envCandidates) {
|
|
9517
|
-
if (!
|
|
9641
|
+
if (!existsSync16(envPath)) continue;
|
|
9518
9642
|
const envContent = readFileSync15(envPath, "utf-8");
|
|
9519
9643
|
for (const line of envContent.split("\n")) {
|
|
9520
9644
|
const trimmed = line.trim();
|