@synkro-sh/cli 1.4.0 → 1.4.2
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 +735 -743
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -3370,87 +3370,6 @@ var init_promptFetcher = __esm({
|
|
|
3370
3370
|
}
|
|
3371
3371
|
});
|
|
3372
3372
|
|
|
3373
|
-
// cli/storage/local.ts
|
|
3374
|
-
import Database from "better-sqlite3";
|
|
3375
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
3376
|
-
import { homedir as homedir6 } from "os";
|
|
3377
|
-
import { join as join7 } from "path";
|
|
3378
|
-
function getSetting(key) {
|
|
3379
|
-
const row = db.prepare("SELECT value FROM settings WHERE key = ?").get(key);
|
|
3380
|
-
return row?.value ?? null;
|
|
3381
|
-
}
|
|
3382
|
-
function setSetting(key, value) {
|
|
3383
|
-
db.prepare(
|
|
3384
|
-
`INSERT INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now'))
|
|
3385
|
-
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
|
|
3386
|
-
).run(key, value);
|
|
3387
|
-
}
|
|
3388
|
-
var SYNKRO_DIR2, DB_PATH, db;
|
|
3389
|
-
var init_local = __esm({
|
|
3390
|
-
"cli/storage/local.ts"() {
|
|
3391
|
-
"use strict";
|
|
3392
|
-
SYNKRO_DIR2 = join7(homedir6(), ".synkro");
|
|
3393
|
-
DB_PATH = join7(SYNKRO_DIR2, "sessions.db");
|
|
3394
|
-
if (!existsSync8(SYNKRO_DIR2)) {
|
|
3395
|
-
mkdirSync6(SYNKRO_DIR2, { recursive: true });
|
|
3396
|
-
}
|
|
3397
|
-
try {
|
|
3398
|
-
db = new Database(DB_PATH);
|
|
3399
|
-
} catch (err) {
|
|
3400
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3401
|
-
console.error(`Failed to initialize database at ${DB_PATH}: ${msg}`);
|
|
3402
|
-
console.error("Check that ~/.synkro/ is writable and disk is not full.");
|
|
3403
|
-
process.exit(1);
|
|
3404
|
-
}
|
|
3405
|
-
db.exec(`
|
|
3406
|
-
CREATE TABLE IF NOT EXISTS command_history (
|
|
3407
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3408
|
-
command TEXT NOT NULL,
|
|
3409
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
3410
|
-
);
|
|
3411
|
-
`);
|
|
3412
|
-
db.exec(`
|
|
3413
|
-
CREATE TABLE IF NOT EXISTS project_keys (
|
|
3414
|
-
slug TEXT PRIMARY KEY,
|
|
3415
|
-
project_id TEXT NOT NULL,
|
|
3416
|
-
project_name TEXT NOT NULL,
|
|
3417
|
-
api_key TEXT NOT NULL,
|
|
3418
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
3419
|
-
);
|
|
3420
|
-
`);
|
|
3421
|
-
db.exec(`
|
|
3422
|
-
CREATE TABLE IF NOT EXISTS settings (
|
|
3423
|
-
key TEXT PRIMARY KEY,
|
|
3424
|
-
value TEXT NOT NULL,
|
|
3425
|
-
updated_at TEXT DEFAULT (datetime('now'))
|
|
3426
|
-
);
|
|
3427
|
-
`);
|
|
3428
|
-
process.on("exit", () => {
|
|
3429
|
-
db.close();
|
|
3430
|
-
});
|
|
3431
|
-
}
|
|
3432
|
-
});
|
|
3433
|
-
|
|
3434
|
-
// cli/local-cc/settings.ts
|
|
3435
|
-
function getInferenceProvider() {
|
|
3436
|
-
const raw = getSetting(KEY);
|
|
3437
|
-
return raw === "local-cc" ? "local-cc" : "inngest";
|
|
3438
|
-
}
|
|
3439
|
-
function setInferenceProvider(value) {
|
|
3440
|
-
setSetting(KEY, value);
|
|
3441
|
-
}
|
|
3442
|
-
function isLocalCCEnabled() {
|
|
3443
|
-
return getInferenceProvider() === "local-cc";
|
|
3444
|
-
}
|
|
3445
|
-
var KEY;
|
|
3446
|
-
var init_settings = __esm({
|
|
3447
|
-
"cli/local-cc/settings.ts"() {
|
|
3448
|
-
"use strict";
|
|
3449
|
-
init_local();
|
|
3450
|
-
KEY = "inference_provider";
|
|
3451
|
-
}
|
|
3452
|
-
});
|
|
3453
|
-
|
|
3454
3373
|
// cli/local-cc/channelSource.ts
|
|
3455
3374
|
var CHANNEL_PLUGIN_SOURCE;
|
|
3456
3375
|
var init_channelSource = __esm({
|
|
@@ -3601,13 +3520,13 @@ await mcp.connect(new StdioServerTransport());
|
|
|
3601
3520
|
});
|
|
3602
3521
|
|
|
3603
3522
|
// cli/local-cc/install.ts
|
|
3604
|
-
import { existsSync as
|
|
3605
|
-
import { join as
|
|
3606
|
-
import { homedir as
|
|
3523
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, readFileSync as readFileSync6, chmodSync, copyFileSync, renameSync as renameSync3, unlinkSync as unlinkSync4, openSync, fsyncSync, closeSync } from "fs";
|
|
3524
|
+
import { join as join7 } from "path";
|
|
3525
|
+
import { homedir as homedir6 } from "os";
|
|
3607
3526
|
import { spawnSync } from "child_process";
|
|
3608
3527
|
function writePluginFiles() {
|
|
3609
|
-
|
|
3610
|
-
|
|
3528
|
+
mkdirSync6(SESSION_DIR, { recursive: true });
|
|
3529
|
+
mkdirSync6(PLUGIN_SETTINGS_DIR, { recursive: true });
|
|
3611
3530
|
writeFileSync6(PLUGIN_PATH, CHANNEL_PLUGIN_SOURCE, "utf-8");
|
|
3612
3531
|
chmodSync(PLUGIN_PATH, 493);
|
|
3613
3532
|
writeFileSync6(PLUGIN_PKG_PATH, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
@@ -3639,7 +3558,7 @@ function runBunInstall() {
|
|
|
3639
3558
|
}
|
|
3640
3559
|
}
|
|
3641
3560
|
function safelyMutateClaudeJson(mutator) {
|
|
3642
|
-
if (!
|
|
3561
|
+
if (!existsSync8(CLAUDE_JSON_PATH)) {
|
|
3643
3562
|
return;
|
|
3644
3563
|
}
|
|
3645
3564
|
const originalText = readFileSync6(CLAUDE_JSON_PATH, "utf-8");
|
|
@@ -3767,15 +3686,15 @@ var init_install = __esm({
|
|
|
3767
3686
|
"cli/local-cc/install.ts"() {
|
|
3768
3687
|
"use strict";
|
|
3769
3688
|
init_channelSource();
|
|
3770
|
-
CLAUDE_JSON_BACKUP_PATH =
|
|
3771
|
-
SESSION_DIR =
|
|
3772
|
-
PLUGIN_PATH =
|
|
3773
|
-
PLUGIN_PKG_PATH =
|
|
3774
|
-
PLUGIN_SETTINGS_DIR =
|
|
3775
|
-
PLUGIN_SETTINGS_PATH =
|
|
3776
|
-
PROJECT_MCP_PATH =
|
|
3777
|
-
CLAUDE_JSON_PATH =
|
|
3778
|
-
RUN_SCRIPT_PATH =
|
|
3689
|
+
CLAUDE_JSON_BACKUP_PATH = join7(homedir6(), ".claude.json.synkro-bak");
|
|
3690
|
+
SESSION_DIR = join7(homedir6(), ".synkro", "cc_sessions");
|
|
3691
|
+
PLUGIN_PATH = join7(SESSION_DIR, "synkro-channel.ts");
|
|
3692
|
+
PLUGIN_PKG_PATH = join7(SESSION_DIR, "package.json");
|
|
3693
|
+
PLUGIN_SETTINGS_DIR = join7(SESSION_DIR, ".claude");
|
|
3694
|
+
PLUGIN_SETTINGS_PATH = join7(PLUGIN_SETTINGS_DIR, "settings.json");
|
|
3695
|
+
PROJECT_MCP_PATH = join7(SESSION_DIR, ".mcp.json");
|
|
3696
|
+
CLAUDE_JSON_PATH = join7(homedir6(), ".claude.json");
|
|
3697
|
+
RUN_SCRIPT_PATH = join7(SESSION_DIR, "run-claude.sh");
|
|
3779
3698
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
3780
3699
|
RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
|
|
3781
3700
|
# Auto-generated by \`synkro install\`. Do not edit.
|
|
@@ -3827,8 +3746,8 @@ done
|
|
|
3827
3746
|
|
|
3828
3747
|
// cli/local-cc/pueue.ts
|
|
3829
3748
|
import { execFileSync, spawnSync as spawnSync2 } from "child_process";
|
|
3830
|
-
import { homedir as
|
|
3831
|
-
import { join as
|
|
3749
|
+
import { homedir as homedir7 } from "os";
|
|
3750
|
+
import { join as join8 } from "path";
|
|
3832
3751
|
import { connect } from "net";
|
|
3833
3752
|
function pueueAvailable() {
|
|
3834
3753
|
const r = spawnSync2("pueue", ["--version"], { encoding: "utf-8" });
|
|
@@ -3883,7 +3802,7 @@ function startTask(opts = {}) {
|
|
|
3883
3802
|
if (existing) {
|
|
3884
3803
|
spawnSync2("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
|
|
3885
3804
|
}
|
|
3886
|
-
const runScript =
|
|
3805
|
+
const runScript = join8(cwd, "run-claude.sh");
|
|
3887
3806
|
const args2 = [
|
|
3888
3807
|
"add",
|
|
3889
3808
|
"--label",
|
|
@@ -3975,7 +3894,7 @@ var init_pueue = __esm({
|
|
|
3975
3894
|
"use strict";
|
|
3976
3895
|
TASK_LABEL = "synkro-local-cc";
|
|
3977
3896
|
TMUX_SESSION = "synkro-local-cc";
|
|
3978
|
-
SESSION_DIR2 =
|
|
3897
|
+
SESSION_DIR2 = join8(homedir7(), ".synkro", "cc_sessions");
|
|
3979
3898
|
PueueError = class extends Error {
|
|
3980
3899
|
constructor(message, cause) {
|
|
3981
3900
|
super(message);
|
|
@@ -3987,189 +3906,453 @@ var init_pueue = __esm({
|
|
|
3987
3906
|
}
|
|
3988
3907
|
});
|
|
3989
3908
|
|
|
3990
|
-
// cli/
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
import { join as join10 } from "path";
|
|
3999
|
-
import { execSync as execSync5 } from "child_process";
|
|
4000
|
-
import { createInterface as createInterface3 } from "readline";
|
|
4001
|
-
function sanitizeGatewayCandidate(raw) {
|
|
4002
|
-
if (!raw) return void 0;
|
|
4003
|
-
return /^https?:\/\//.test(raw) ? raw : void 0;
|
|
4004
|
-
}
|
|
4005
|
-
function parseArgs(argv) {
|
|
4006
|
-
const opts = {};
|
|
4007
|
-
for (const a of argv) {
|
|
4008
|
-
if (a.startsWith("--api-key=")) opts.apiKey = a.slice("--api-key=".length);
|
|
4009
|
-
else if (a.startsWith("--gateway=")) opts.gatewayUrl = a.slice("--gateway=".length);
|
|
4010
|
-
else if (a === "--skip-auth") opts.skipAuth = true;
|
|
4011
|
-
else if (a === "--no-mcp") opts.noMcp = true;
|
|
4012
|
-
else if (a === "--force" || a === "-f") opts.force = true;
|
|
4013
|
-
else if (a === "--link-repo") opts.linkRepo = true;
|
|
3909
|
+
// cli/local-cc/prompts.ts
|
|
3910
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
3911
|
+
import { homedir as homedir8 } from "os";
|
|
3912
|
+
import { join as join9 } from "path";
|
|
3913
|
+
function loadCachedPrompts() {
|
|
3914
|
+
if (_cached) return _cached;
|
|
3915
|
+
if (!existsSync9(CACHE_PATH2)) {
|
|
3916
|
+
throw new Error("Prompts cache not found. Run `synkro install` or `synkro update` first.");
|
|
4014
3917
|
}
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
3918
|
+
try {
|
|
3919
|
+
_cached = JSON.parse(readFileSync7(CACHE_PATH2, "utf-8"));
|
|
3920
|
+
return _cached;
|
|
3921
|
+
} catch {
|
|
3922
|
+
throw new Error("Prompts cache is corrupted. Run `synkro update` to refresh.");
|
|
4018
3923
|
}
|
|
4019
|
-
return opts;
|
|
4020
|
-
}
|
|
4021
|
-
async function promptTranscriptConsent() {
|
|
4022
|
-
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
4023
|
-
return new Promise((resolve2) => {
|
|
4024
|
-
rl.question(
|
|
4025
|
-
"Would you like Synkro to use Claude Code session transcripts\nto generate guardrail rules and policies for your team? (Y/n) ",
|
|
4026
|
-
(answer) => {
|
|
4027
|
-
rl.close();
|
|
4028
|
-
const trimmed = answer.trim().toLowerCase();
|
|
4029
|
-
resolve2(trimmed === "" || trimmed === "y" || trimmed === "yes");
|
|
4030
|
-
}
|
|
4031
|
-
);
|
|
4032
|
-
});
|
|
4033
|
-
}
|
|
4034
|
-
function ensureSynkroDir() {
|
|
4035
|
-
mkdirSync8(SYNKRO_DIR3, { recursive: true });
|
|
4036
|
-
mkdirSync8(HOOKS_DIR, { recursive: true });
|
|
4037
|
-
mkdirSync8(BIN_DIR, { recursive: true });
|
|
4038
|
-
mkdirSync8(OFFSETS_DIR, { recursive: true });
|
|
4039
|
-
}
|
|
4040
|
-
function writeHookScripts() {
|
|
4041
|
-
const bashScriptPath = join10(HOOKS_DIR, "cc-bash-judge.sh");
|
|
4042
|
-
const bashFollowupScriptPath = join10(HOOKS_DIR, "cc-bash-followup.sh");
|
|
4043
|
-
const editCaptureScriptPath = join10(HOOKS_DIR, "cc-edit-capture.sh");
|
|
4044
|
-
const editPrecheckScriptPath = join10(HOOKS_DIR, "cc-edit-precheck.sh");
|
|
4045
|
-
const stopSummaryScriptPath = join10(HOOKS_DIR, "cc-stop-summary.sh");
|
|
4046
|
-
const sessionStartScriptPath = join10(HOOKS_DIR, "cc-session-start.sh");
|
|
4047
|
-
const transcriptSyncScriptPath = join10(HOOKS_DIR, "cc-transcript-sync.sh");
|
|
4048
|
-
writeFileSync7(bashScriptPath, CC_BASH_JUDGE_SCRIPT, "utf-8");
|
|
4049
|
-
writeFileSync7(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, "utf-8");
|
|
4050
|
-
writeFileSync7(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, "utf-8");
|
|
4051
|
-
writeFileSync7(editPrecheckScriptPath, CC_EDIT_PRECHECK_SCRIPT, "utf-8");
|
|
4052
|
-
writeFileSync7(stopSummaryScriptPath, CC_STOP_SUMMARY_SCRIPT, "utf-8");
|
|
4053
|
-
writeFileSync7(sessionStartScriptPath, CC_SESSION_START_SCRIPT, "utf-8");
|
|
4054
|
-
writeFileSync7(transcriptSyncScriptPath, CC_TRANSCRIPT_SYNC_SCRIPT, "utf-8");
|
|
4055
|
-
chmodSync2(bashScriptPath, 493);
|
|
4056
|
-
chmodSync2(bashFollowupScriptPath, 493);
|
|
4057
|
-
chmodSync2(editCaptureScriptPath, 493);
|
|
4058
|
-
chmodSync2(editPrecheckScriptPath, 493);
|
|
4059
|
-
chmodSync2(stopSummaryScriptPath, 493);
|
|
4060
|
-
chmodSync2(sessionStartScriptPath, 493);
|
|
4061
|
-
chmodSync2(transcriptSyncScriptPath, 493);
|
|
4062
|
-
return {
|
|
4063
|
-
bashScript: bashScriptPath,
|
|
4064
|
-
bashFollowupScript: bashFollowupScriptPath,
|
|
4065
|
-
editCaptureScript: editCaptureScriptPath,
|
|
4066
|
-
editPrecheckScript: editPrecheckScriptPath,
|
|
4067
|
-
stopSummaryScript: stopSummaryScriptPath,
|
|
4068
|
-
sessionStartScript: sessionStartScriptPath,
|
|
4069
|
-
transcriptSyncScript: transcriptSyncScriptPath
|
|
4070
|
-
};
|
|
4071
|
-
}
|
|
4072
|
-
function sanitizeConfigValue(raw, maxLen = 256) {
|
|
4073
|
-
if (!raw) return "";
|
|
4074
|
-
return raw.replace(/[^\x20-\x7E]/g, "").slice(0, maxLen);
|
|
4075
3924
|
}
|
|
4076
|
-
function
|
|
4077
|
-
|
|
3925
|
+
function getPrimer(role) {
|
|
3926
|
+
const cache = loadCachedPrompts();
|
|
3927
|
+
const primer = role === "grade-edit" ? cache.grader_primer_edit : cache.grader_primer_bash;
|
|
3928
|
+
if (!primer) {
|
|
3929
|
+
throw new Error(`No cached primer for role "${role}". Run \`synkro update\` to refresh prompts.`);
|
|
3930
|
+
}
|
|
3931
|
+
return primer;
|
|
4078
3932
|
}
|
|
4079
|
-
function
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
3933
|
+
function buildChannelContent(role, payload) {
|
|
3934
|
+
return `${getPrimer(role)}
|
|
3935
|
+
|
|
3936
|
+
---
|
|
3937
|
+
PAYLOAD (the input to evaluate):
|
|
3938
|
+
|
|
3939
|
+
${payload}`;
|
|
4083
3940
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
const safeTier = sanitizeConfigValue(opts.tier ?? "pro", 32);
|
|
4091
|
-
const safeInference = sanitizeConfigValue(opts.inference ?? "fast", 16);
|
|
4092
|
-
const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? "", 1024);
|
|
4093
|
-
const lines = [
|
|
4094
|
-
"# Synkro CLI config (managed by synkro install)",
|
|
4095
|
-
"# JWT auth \u2014 the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime",
|
|
4096
|
-
"# and send Authorization: Bearer <access_token> on every gateway call.",
|
|
4097
|
-
`SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
|
|
4098
|
-
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
4099
|
-
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
4100
|
-
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
4101
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.4.0")}`
|
|
4102
|
-
];
|
|
4103
|
-
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
4104
|
-
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
4105
|
-
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
4106
|
-
if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle(safeEmail)}`);
|
|
4107
|
-
if (opts.transcriptConsent !== void 0) {
|
|
4108
|
-
lines.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle(opts.transcriptConsent ? "yes" : "no")}`);
|
|
3941
|
+
var CACHE_PATH2, _cached;
|
|
3942
|
+
var init_prompts = __esm({
|
|
3943
|
+
"cli/local-cc/prompts.ts"() {
|
|
3944
|
+
"use strict";
|
|
3945
|
+
CACHE_PATH2 = join9(homedir8(), ".synkro", "prompts", "judge-prompts.json");
|
|
3946
|
+
_cached = null;
|
|
4109
3947
|
}
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
3948
|
+
});
|
|
3949
|
+
|
|
3950
|
+
// cli/local-cc/turnLog.ts
|
|
3951
|
+
import { appendFileSync, existsSync as existsSync10, mkdirSync as mkdirSync7, openSync as openSync2, readFileSync as readFileSync8, readSync, closeSync as closeSync2, statSync, watchFile, unwatchFile } from "fs";
|
|
3952
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
3953
|
+
import { homedir as homedir9 } from "os";
|
|
3954
|
+
function truncate(s, max = PREVIEW_MAX) {
|
|
3955
|
+
if (s.length <= max) return s;
|
|
3956
|
+
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
4113
3957
|
}
|
|
4114
|
-
function
|
|
4115
|
-
const
|
|
4116
|
-
|
|
4117
|
-
meta.display_name = execSync5("git config user.name", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
4118
|
-
} catch {
|
|
4119
|
-
}
|
|
4120
|
-
try {
|
|
4121
|
-
const remote = execSync5("git remote get-url origin", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
4122
|
-
const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
|
|
4123
|
-
const httpMatch = remote.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
|
|
4124
|
-
const m = sshMatch || httpMatch;
|
|
4125
|
-
if (m) meta.active_repo = m[1];
|
|
4126
|
-
} catch {
|
|
4127
|
-
}
|
|
4128
|
-
try {
|
|
4129
|
-
meta.cc_version = execSync5("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
4130
|
-
} catch {
|
|
4131
|
-
}
|
|
4132
|
-
const claudeDir = join10(homedir9(), ".claude");
|
|
3958
|
+
function extractSeverity(result) {
|
|
3959
|
+
const m = result.match(/<synkro-(?:verdict|intent)>([\s\S]*?)<\/synkro-(?:verdict|intent)>/);
|
|
3960
|
+
if (!m) return void 0;
|
|
4133
3961
|
try {
|
|
4134
|
-
const
|
|
4135
|
-
|
|
4136
|
-
if (
|
|
4137
|
-
if (
|
|
3962
|
+
const obj = JSON.parse(m[1]);
|
|
3963
|
+
if (obj.severity) return String(obj.severity);
|
|
3964
|
+
if (typeof obj.ok === "boolean") return obj.ok ? "ok" : "violations";
|
|
3965
|
+
if (obj.type) return String(obj.type);
|
|
3966
|
+
if (obj.verdict) return String(obj.verdict);
|
|
4138
3967
|
} catch {
|
|
4139
3968
|
}
|
|
3969
|
+
return void 0;
|
|
3970
|
+
}
|
|
3971
|
+
function appendTurn(args2) {
|
|
4140
3972
|
try {
|
|
4141
|
-
|
|
4142
|
-
const
|
|
4143
|
-
|
|
3973
|
+
mkdirSync7(dirname4(TURN_LOG_PATH), { recursive: true });
|
|
3974
|
+
const entry = {
|
|
3975
|
+
ts: new Date(args2.startedAt).toISOString(),
|
|
3976
|
+
role: args2.role,
|
|
3977
|
+
duration_ms: Date.now() - args2.startedAt,
|
|
3978
|
+
status: args2.status,
|
|
3979
|
+
request_preview: truncate(args2.request),
|
|
3980
|
+
response_preview: args2.result ? truncate(args2.result) : "",
|
|
3981
|
+
severity: args2.result ? extractSeverity(args2.result) : void 0,
|
|
3982
|
+
error: args2.error
|
|
3983
|
+
};
|
|
3984
|
+
appendFileSync(TURN_LOG_PATH, JSON.stringify(entry) + "\n", "utf-8");
|
|
4144
3985
|
} catch {
|
|
4145
3986
|
}
|
|
3987
|
+
}
|
|
3988
|
+
function readRecentTurns(n = 20) {
|
|
3989
|
+
if (!existsSync10(TURN_LOG_PATH)) return [];
|
|
4146
3990
|
try {
|
|
4147
|
-
const
|
|
4148
|
-
|
|
4149
|
-
|
|
3991
|
+
const size = statSync(TURN_LOG_PATH).size;
|
|
3992
|
+
if (size === 0) return [];
|
|
3993
|
+
const text = readFileSync8(TURN_LOG_PATH, "utf-8");
|
|
3994
|
+
const lines = text.split("\n").filter(Boolean);
|
|
3995
|
+
const lastN = lines.slice(-n).reverse();
|
|
3996
|
+
return lastN.map((line) => {
|
|
3997
|
+
try {
|
|
3998
|
+
return JSON.parse(line);
|
|
3999
|
+
} catch {
|
|
4000
|
+
return null;
|
|
4001
|
+
}
|
|
4002
|
+
}).filter((x) => x !== null);
|
|
4150
4003
|
} catch {
|
|
4004
|
+
return [];
|
|
4151
4005
|
}
|
|
4006
|
+
}
|
|
4007
|
+
function followTurns(onEntry) {
|
|
4152
4008
|
try {
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
const s = JSON.parse(readFileSync7(join10(sessionsDir, f), "utf-8"));
|
|
4157
|
-
if (s.version) {
|
|
4158
|
-
meta.cc_version = meta.cc_version || s.version;
|
|
4159
|
-
break;
|
|
4160
|
-
}
|
|
4009
|
+
mkdirSync7(dirname4(TURN_LOG_PATH), { recursive: true });
|
|
4010
|
+
if (!existsSync10(TURN_LOG_PATH)) {
|
|
4011
|
+
appendFileSync(TURN_LOG_PATH, "", "utf-8");
|
|
4161
4012
|
}
|
|
4162
4013
|
} catch {
|
|
4163
4014
|
}
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4015
|
+
let lastSize = (() => {
|
|
4016
|
+
try {
|
|
4017
|
+
return statSync(TURN_LOG_PATH).size;
|
|
4018
|
+
} catch {
|
|
4019
|
+
return 0;
|
|
4020
|
+
}
|
|
4021
|
+
})();
|
|
4022
|
+
let pendingPartial = "";
|
|
4023
|
+
const drainNewBytes = (from, to) => {
|
|
4024
|
+
if (to <= from) return;
|
|
4025
|
+
let fd = null;
|
|
4026
|
+
try {
|
|
4027
|
+
fd = openSync2(TURN_LOG_PATH, "r");
|
|
4028
|
+
const len = to - from;
|
|
4029
|
+
const buf = Buffer.alloc(len);
|
|
4030
|
+
readSync(fd, buf, 0, len, from);
|
|
4031
|
+
const text = pendingPartial + buf.toString("utf-8");
|
|
4032
|
+
const lastNewline = text.lastIndexOf("\n");
|
|
4033
|
+
if (lastNewline === -1) {
|
|
4034
|
+
pendingPartial = text;
|
|
4035
|
+
return;
|
|
4036
|
+
}
|
|
4037
|
+
const complete = text.slice(0, lastNewline);
|
|
4038
|
+
pendingPartial = text.slice(lastNewline + 1);
|
|
4039
|
+
for (const line of complete.split("\n")) {
|
|
4040
|
+
if (!line) continue;
|
|
4041
|
+
try {
|
|
4042
|
+
onEntry(JSON.parse(line));
|
|
4043
|
+
} catch {
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
} catch {
|
|
4047
|
+
} finally {
|
|
4048
|
+
if (fd !== null) {
|
|
4049
|
+
try {
|
|
4050
|
+
closeSync2(fd);
|
|
4051
|
+
} catch {
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
};
|
|
4056
|
+
watchFile(TURN_LOG_PATH, { interval: 250 }, (curr, prev) => {
|
|
4057
|
+
if (curr.size < lastSize) {
|
|
4058
|
+
lastSize = 0;
|
|
4059
|
+
pendingPartial = "";
|
|
4060
|
+
}
|
|
4061
|
+
if (curr.size > lastSize) {
|
|
4062
|
+
drainNewBytes(lastSize, curr.size);
|
|
4063
|
+
lastSize = curr.size;
|
|
4064
|
+
}
|
|
4065
|
+
});
|
|
4066
|
+
return () => unwatchFile(TURN_LOG_PATH);
|
|
4067
|
+
}
|
|
4068
|
+
var TURN_LOG_PATH, PREVIEW_MAX;
|
|
4069
|
+
var init_turnLog = __esm({
|
|
4070
|
+
"cli/local-cc/turnLog.ts"() {
|
|
4071
|
+
"use strict";
|
|
4072
|
+
TURN_LOG_PATH = join10(homedir9(), ".synkro", "cc_sessions", "turns.log");
|
|
4073
|
+
PREVIEW_MAX = 400;
|
|
4074
|
+
}
|
|
4075
|
+
});
|
|
4076
|
+
|
|
4077
|
+
// cli/local-cc/client.ts
|
|
4078
|
+
import { request as httpRequest } from "http";
|
|
4079
|
+
import { connect as connect2 } from "net";
|
|
4080
|
+
async function submitToChannel(role, payload, opts = {}) {
|
|
4081
|
+
const content = buildChannelContent(role, payload);
|
|
4082
|
+
const body = JSON.stringify({ role, content });
|
|
4083
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
4084
|
+
const startedAt = Date.now();
|
|
4085
|
+
try {
|
|
4086
|
+
const result = await new Promise((resolve2, reject) => {
|
|
4087
|
+
const req = httpRequest({
|
|
4088
|
+
host: CHANNEL_HOST,
|
|
4089
|
+
port: CHANNEL_PORT,
|
|
4090
|
+
method: "POST",
|
|
4091
|
+
path: "/submit",
|
|
4092
|
+
headers: {
|
|
4093
|
+
"Content-Type": "application/json",
|
|
4094
|
+
"Content-Length": Buffer.byteLength(body)
|
|
4095
|
+
},
|
|
4096
|
+
timeout: timeoutMs
|
|
4097
|
+
}, (res) => {
|
|
4098
|
+
const chunks = [];
|
|
4099
|
+
res.on("data", (c) => chunks.push(c));
|
|
4100
|
+
res.on("end", () => {
|
|
4101
|
+
const text = Buffer.concat(chunks).toString("utf-8");
|
|
4102
|
+
if (res.statusCode !== 200) {
|
|
4103
|
+
reject(new LocalCCError(`channel returned ${res.statusCode}: ${text.slice(0, 500)}`));
|
|
4104
|
+
return;
|
|
4105
|
+
}
|
|
4106
|
+
try {
|
|
4107
|
+
const parsed = JSON.parse(text);
|
|
4108
|
+
if (parsed.error) {
|
|
4109
|
+
reject(new LocalCCError(parsed.error));
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
resolve2(String(parsed.result ?? ""));
|
|
4113
|
+
} catch (err) {
|
|
4114
|
+
reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
|
|
4115
|
+
}
|
|
4116
|
+
});
|
|
4117
|
+
});
|
|
4118
|
+
req.on("timeout", () => {
|
|
4119
|
+
req.destroy(new LocalCCError(`channel request timed out after ${timeoutMs}ms`));
|
|
4120
|
+
});
|
|
4121
|
+
req.on("error", (err) => {
|
|
4122
|
+
const msg = err.code === "ECONNREFUSED" ? `channel connection refused at ${CHANNEL_HOST}:${CHANNEL_PORT} (is the pueue task running?)` : `channel request failed: ${err.message}`;
|
|
4123
|
+
reject(new LocalCCError(msg, err));
|
|
4124
|
+
});
|
|
4125
|
+
req.write(body);
|
|
4126
|
+
req.end();
|
|
4127
|
+
});
|
|
4128
|
+
appendTurn({ startedAt, role, request: payload, result, status: "ok" });
|
|
4129
|
+
return result;
|
|
4130
|
+
} catch (err) {
|
|
4131
|
+
const message = err.message ?? String(err);
|
|
4132
|
+
const status = /timed out/i.test(message) ? "timeout" : "error";
|
|
4133
|
+
appendTurn({ startedAt, role, request: payload, status, error: message });
|
|
4134
|
+
throw err;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
function isChannelAvailable(timeoutMs = 500) {
|
|
4138
|
+
return new Promise((resolve2) => {
|
|
4139
|
+
const sock = connect2(CHANNEL_PORT, CHANNEL_HOST);
|
|
4140
|
+
const done = (ok) => {
|
|
4141
|
+
try {
|
|
4142
|
+
sock.destroy();
|
|
4143
|
+
} catch {
|
|
4144
|
+
}
|
|
4145
|
+
resolve2(ok);
|
|
4146
|
+
};
|
|
4147
|
+
sock.once("connect", () => done(true));
|
|
4148
|
+
sock.once("error", () => done(false));
|
|
4149
|
+
sock.setTimeout(timeoutMs, () => done(false));
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
4152
|
+
var CHANNEL_HOST, CHANNEL_PORT, DEFAULT_TIMEOUT_MS, LocalCCError;
|
|
4153
|
+
var init_client = __esm({
|
|
4154
|
+
"cli/local-cc/client.ts"() {
|
|
4155
|
+
"use strict";
|
|
4156
|
+
init_prompts();
|
|
4157
|
+
init_turnLog();
|
|
4158
|
+
CHANNEL_HOST = "127.0.0.1";
|
|
4159
|
+
CHANNEL_PORT = parseInt(process.env.SYNKRO_CHANNEL_PORT || "8929", 10);
|
|
4160
|
+
DEFAULT_TIMEOUT_MS = 9e4;
|
|
4161
|
+
LocalCCError = class extends Error {
|
|
4162
|
+
constructor(message, cause) {
|
|
4163
|
+
super(message);
|
|
4164
|
+
this.cause = cause;
|
|
4165
|
+
this.name = "LocalCCError";
|
|
4166
|
+
}
|
|
4167
|
+
cause;
|
|
4168
|
+
};
|
|
4169
|
+
}
|
|
4170
|
+
});
|
|
4171
|
+
|
|
4172
|
+
// cli/commands/install.ts
|
|
4173
|
+
var install_exports = {};
|
|
4174
|
+
__export(install_exports, {
|
|
4175
|
+
installCommand: () => installCommand,
|
|
4176
|
+
parseArgs: () => parseArgs
|
|
4177
|
+
});
|
|
4178
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync9, readdirSync } from "fs";
|
|
4179
|
+
import { homedir as homedir10 } from "os";
|
|
4180
|
+
import { join as join11 } from "path";
|
|
4181
|
+
import { execSync as execSync5 } from "child_process";
|
|
4182
|
+
import { createInterface as createInterface3 } from "readline";
|
|
4183
|
+
function sanitizeGatewayCandidate(raw) {
|
|
4184
|
+
if (!raw) return void 0;
|
|
4185
|
+
return /^https?:\/\//.test(raw) ? raw : void 0;
|
|
4186
|
+
}
|
|
4187
|
+
function parseArgs(argv) {
|
|
4188
|
+
const opts = {};
|
|
4189
|
+
for (const a of argv) {
|
|
4190
|
+
if (a.startsWith("--api-key=")) opts.apiKey = a.slice("--api-key=".length);
|
|
4191
|
+
else if (a.startsWith("--gateway=")) opts.gatewayUrl = a.slice("--gateway=".length);
|
|
4192
|
+
else if (a === "--skip-auth") opts.skipAuth = true;
|
|
4193
|
+
else if (a === "--no-mcp") opts.noMcp = true;
|
|
4194
|
+
else if (a === "--force" || a === "-f") opts.force = true;
|
|
4195
|
+
else if (a === "--link-repo") opts.linkRepo = true;
|
|
4196
|
+
}
|
|
4197
|
+
if (!opts.gatewayUrl) {
|
|
4198
|
+
const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);
|
|
4199
|
+
if (fromEnv) opts.gatewayUrl = fromEnv;
|
|
4200
|
+
}
|
|
4201
|
+
return opts;
|
|
4202
|
+
}
|
|
4203
|
+
async function promptTranscriptConsent() {
|
|
4204
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
4205
|
+
return new Promise((resolve2) => {
|
|
4206
|
+
rl.question(
|
|
4207
|
+
"Would you like Synkro to use Claude Code session transcripts\nto generate guardrail rules and policies for your team? (Y/n) ",
|
|
4208
|
+
(answer) => {
|
|
4209
|
+
rl.close();
|
|
4210
|
+
const trimmed = answer.trim().toLowerCase();
|
|
4211
|
+
resolve2(trimmed === "" || trimmed === "y" || trimmed === "yes");
|
|
4212
|
+
}
|
|
4213
|
+
);
|
|
4214
|
+
});
|
|
4215
|
+
}
|
|
4216
|
+
function ensureSynkroDir() {
|
|
4217
|
+
mkdirSync8(SYNKRO_DIR2, { recursive: true });
|
|
4218
|
+
mkdirSync8(HOOKS_DIR, { recursive: true });
|
|
4219
|
+
mkdirSync8(BIN_DIR, { recursive: true });
|
|
4220
|
+
mkdirSync8(OFFSETS_DIR, { recursive: true });
|
|
4221
|
+
}
|
|
4222
|
+
function writeHookScripts() {
|
|
4223
|
+
const bashScriptPath = join11(HOOKS_DIR, "cc-bash-judge.sh");
|
|
4224
|
+
const bashFollowupScriptPath = join11(HOOKS_DIR, "cc-bash-followup.sh");
|
|
4225
|
+
const editCaptureScriptPath = join11(HOOKS_DIR, "cc-edit-capture.sh");
|
|
4226
|
+
const editPrecheckScriptPath = join11(HOOKS_DIR, "cc-edit-precheck.sh");
|
|
4227
|
+
const stopSummaryScriptPath = join11(HOOKS_DIR, "cc-stop-summary.sh");
|
|
4228
|
+
const sessionStartScriptPath = join11(HOOKS_DIR, "cc-session-start.sh");
|
|
4229
|
+
const transcriptSyncScriptPath = join11(HOOKS_DIR, "cc-transcript-sync.sh");
|
|
4230
|
+
writeFileSync7(bashScriptPath, CC_BASH_JUDGE_SCRIPT, "utf-8");
|
|
4231
|
+
writeFileSync7(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, "utf-8");
|
|
4232
|
+
writeFileSync7(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, "utf-8");
|
|
4233
|
+
writeFileSync7(editPrecheckScriptPath, CC_EDIT_PRECHECK_SCRIPT, "utf-8");
|
|
4234
|
+
writeFileSync7(stopSummaryScriptPath, CC_STOP_SUMMARY_SCRIPT, "utf-8");
|
|
4235
|
+
writeFileSync7(sessionStartScriptPath, CC_SESSION_START_SCRIPT, "utf-8");
|
|
4236
|
+
writeFileSync7(transcriptSyncScriptPath, CC_TRANSCRIPT_SYNC_SCRIPT, "utf-8");
|
|
4237
|
+
chmodSync2(bashScriptPath, 493);
|
|
4238
|
+
chmodSync2(bashFollowupScriptPath, 493);
|
|
4239
|
+
chmodSync2(editCaptureScriptPath, 493);
|
|
4240
|
+
chmodSync2(editPrecheckScriptPath, 493);
|
|
4241
|
+
chmodSync2(stopSummaryScriptPath, 493);
|
|
4242
|
+
chmodSync2(sessionStartScriptPath, 493);
|
|
4243
|
+
chmodSync2(transcriptSyncScriptPath, 493);
|
|
4244
|
+
return {
|
|
4245
|
+
bashScript: bashScriptPath,
|
|
4246
|
+
bashFollowupScript: bashFollowupScriptPath,
|
|
4247
|
+
editCaptureScript: editCaptureScriptPath,
|
|
4248
|
+
editPrecheckScript: editPrecheckScriptPath,
|
|
4249
|
+
stopSummaryScript: stopSummaryScriptPath,
|
|
4250
|
+
sessionStartScript: sessionStartScriptPath,
|
|
4251
|
+
transcriptSyncScript: transcriptSyncScriptPath
|
|
4252
|
+
};
|
|
4253
|
+
}
|
|
4254
|
+
function sanitizeConfigValue(raw, maxLen = 256) {
|
|
4255
|
+
if (!raw) return "";
|
|
4256
|
+
return raw.replace(/[^\x20-\x7E]/g, "").slice(0, maxLen);
|
|
4257
|
+
}
|
|
4258
|
+
function shellQuoteSingle(value) {
|
|
4259
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
4260
|
+
}
|
|
4261
|
+
function resolveSynkroBundle() {
|
|
4262
|
+
const scriptPath = process.argv[1];
|
|
4263
|
+
if (scriptPath && existsSync11(scriptPath)) return scriptPath;
|
|
4264
|
+
return null;
|
|
4265
|
+
}
|
|
4266
|
+
function writeConfigEnv(opts) {
|
|
4267
|
+
const credsPath = join11(SYNKRO_DIR2, "credentials.json");
|
|
4268
|
+
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
4269
|
+
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
4270
|
+
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
4271
|
+
const safeEmail = sanitizeConfigValue(opts.email);
|
|
4272
|
+
const safeTier = sanitizeConfigValue(opts.tier ?? "pro", 32);
|
|
4273
|
+
const safeInference = sanitizeConfigValue(opts.inference ?? "fast", 16);
|
|
4274
|
+
const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? "", 1024);
|
|
4275
|
+
const lines = [
|
|
4276
|
+
"# Synkro CLI config (managed by synkro install)",
|
|
4277
|
+
"# JWT auth \u2014 the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime",
|
|
4278
|
+
"# and send Authorization: Bearer <access_token> on every gateway call.",
|
|
4279
|
+
`SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
|
|
4280
|
+
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
4281
|
+
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
4282
|
+
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
4283
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.4.2")}`
|
|
4284
|
+
];
|
|
4285
|
+
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
4286
|
+
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
4287
|
+
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
4288
|
+
if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle(safeEmail)}`);
|
|
4289
|
+
if (opts.transcriptConsent !== void 0) {
|
|
4290
|
+
lines.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle(opts.transcriptConsent ? "yes" : "no")}`);
|
|
4291
|
+
}
|
|
4292
|
+
lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle(opts.localInference ? "yes" : "no")}`);
|
|
4293
|
+
lines.push("");
|
|
4294
|
+
writeFileSync7(CONFIG_PATH2, lines.join("\n"), "utf-8");
|
|
4295
|
+
chmodSync2(CONFIG_PATH2, 384);
|
|
4296
|
+
}
|
|
4297
|
+
function collectLocalMetadata() {
|
|
4298
|
+
const meta = { platform: process.platform };
|
|
4299
|
+
try {
|
|
4300
|
+
meta.display_name = execSync5("git config user.name", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
4301
|
+
} catch {
|
|
4302
|
+
}
|
|
4303
|
+
try {
|
|
4304
|
+
const remote = execSync5("git remote get-url origin", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
4305
|
+
const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
|
|
4306
|
+
const httpMatch = remote.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
|
|
4307
|
+
const m = sshMatch || httpMatch;
|
|
4308
|
+
if (m) meta.active_repo = m[1];
|
|
4309
|
+
} catch {
|
|
4310
|
+
}
|
|
4311
|
+
try {
|
|
4312
|
+
meta.cc_version = execSync5("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
4313
|
+
} catch {
|
|
4314
|
+
}
|
|
4315
|
+
const claudeDir = join11(homedir10(), ".claude");
|
|
4316
|
+
try {
|
|
4317
|
+
const settings = JSON.parse(readFileSync9(join11(claudeDir, "settings.json"), "utf-8"));
|
|
4318
|
+
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
4319
|
+
if (plugins.length) meta.enabled_plugins = plugins;
|
|
4320
|
+
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
4321
|
+
} catch {
|
|
4322
|
+
}
|
|
4323
|
+
try {
|
|
4324
|
+
const mcpCache = JSON.parse(readFileSync9(join11(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
4325
|
+
const mcpNames = Object.keys(mcpCache);
|
|
4326
|
+
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
4327
|
+
} catch {
|
|
4328
|
+
}
|
|
4329
|
+
try {
|
|
4330
|
+
const mcpList = execSync5("claude mcp list 2>/dev/null", { encoding: "utf-8", timeout: 1e4 });
|
|
4331
|
+
const connected = mcpList.split("\n").filter((l) => l.includes("Connected")).map((l) => l.split(":")[0].trim()).filter(Boolean);
|
|
4332
|
+
if (connected.length) meta.mcp_servers_connected = connected;
|
|
4333
|
+
} catch {
|
|
4334
|
+
}
|
|
4335
|
+
try {
|
|
4336
|
+
const sessionsDir = join11(claudeDir, "sessions");
|
|
4337
|
+
const files = readdirSync(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
4338
|
+
for (const f of files) {
|
|
4339
|
+
const s = JSON.parse(readFileSync9(join11(sessionsDir, f), "utf-8"));
|
|
4340
|
+
if (s.version) {
|
|
4341
|
+
meta.cc_version = meta.cc_version || s.version;
|
|
4342
|
+
break;
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
} catch {
|
|
4346
|
+
}
|
|
4347
|
+
return meta;
|
|
4348
|
+
}
|
|
4349
|
+
async function fetchUserProfile(gatewayUrl, token) {
|
|
4350
|
+
try {
|
|
4351
|
+
const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
|
|
4352
|
+
headers: { "Authorization": `Bearer ${token}` }
|
|
4353
|
+
});
|
|
4354
|
+
if (!resp.ok) return { tier: "pro", inference: "fast", localInference: false };
|
|
4355
|
+
const data = await resp.json();
|
|
4173
4356
|
const meta = collectLocalMetadata();
|
|
4174
4357
|
fetch(`${gatewayUrl}/api/v1/cli/me`, {
|
|
4175
4358
|
method: "PATCH",
|
|
@@ -4179,10 +4362,11 @@ async function fetchUserProfile(gatewayUrl, token) {
|
|
|
4179
4362
|
});
|
|
4180
4363
|
return {
|
|
4181
4364
|
tier: data.plan_tier ?? "pro",
|
|
4182
|
-
inference: data.fast_inference ? "fast" : "standard"
|
|
4365
|
+
inference: data.fast_inference ? "fast" : "standard",
|
|
4366
|
+
localInference: !!data.local_inference
|
|
4183
4367
|
};
|
|
4184
4368
|
} catch {
|
|
4185
|
-
return { tier: "pro", inference: "fast" };
|
|
4369
|
+
return { tier: "pro", inference: "fast", localInference: false };
|
|
4186
4370
|
}
|
|
4187
4371
|
}
|
|
4188
4372
|
function assertGatewayAllowed(gatewayUrl) {
|
|
@@ -4208,19 +4392,19 @@ function assertGatewayAllowed(gatewayUrl) {
|
|
|
4208
4392
|
}
|
|
4209
4393
|
function isAlreadyInstalled() {
|
|
4210
4394
|
const requiredScripts = [
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4395
|
+
join11(HOOKS_DIR, "cc-bash-judge.sh"),
|
|
4396
|
+
join11(HOOKS_DIR, "cc-bash-followup.sh"),
|
|
4397
|
+
join11(HOOKS_DIR, "cc-edit-precheck.sh"),
|
|
4398
|
+
join11(HOOKS_DIR, "cc-edit-capture.sh"),
|
|
4399
|
+
join11(HOOKS_DIR, "cc-stop-summary.sh"),
|
|
4400
|
+
join11(HOOKS_DIR, "cc-session-start.sh")
|
|
4217
4401
|
];
|
|
4218
|
-
if (!requiredScripts.every((p) =>
|
|
4219
|
-
if (!
|
|
4220
|
-
const settingsPath =
|
|
4221
|
-
if (!
|
|
4402
|
+
if (!requiredScripts.every((p) => existsSync11(p))) return false;
|
|
4403
|
+
if (!existsSync11(CONFIG_PATH2)) return false;
|
|
4404
|
+
const settingsPath = join11(homedir10(), ".claude", "settings.json");
|
|
4405
|
+
if (!existsSync11(settingsPath)) return false;
|
|
4222
4406
|
try {
|
|
4223
|
-
const settings = JSON.parse(
|
|
4407
|
+
const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
4224
4408
|
const hooks = settings?.hooks;
|
|
4225
4409
|
if (!hooks || typeof hooks !== "object") return false;
|
|
4226
4410
|
const hasManaged = (kind) => Array.isArray(hooks[kind]) && hooks[kind].some((entry) => entry?.__synkro_managed__ === true);
|
|
@@ -4328,9 +4512,9 @@ async function installCommand(opts = {}) {
|
|
|
4328
4512
|
console.log(` ${scripts.transcriptSyncScript}
|
|
4329
4513
|
`);
|
|
4330
4514
|
for (const mode of ["edit", "bash"]) {
|
|
4331
|
-
const pidFile =
|
|
4515
|
+
const pidFile = join11(SYNKRO_DIR2, "daemon", mode, "daemon.pid");
|
|
4332
4516
|
try {
|
|
4333
|
-
const pid = parseInt(
|
|
4517
|
+
const pid = parseInt(readFileSync9(pidFile, "utf-8").trim(), 10);
|
|
4334
4518
|
if (pid > 0) {
|
|
4335
4519
|
process.kill(pid, "SIGTERM");
|
|
4336
4520
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -4338,20 +4522,6 @@ async function installCommand(opts = {}) {
|
|
|
4338
4522
|
} catch {
|
|
4339
4523
|
}
|
|
4340
4524
|
}
|
|
4341
|
-
if (isLocalCCEnabled()) {
|
|
4342
|
-
try {
|
|
4343
|
-
assertClaudeInstalled();
|
|
4344
|
-
assertPueueInstalled();
|
|
4345
|
-
const r = installLocalCC();
|
|
4346
|
-
console.log(`Installed local-CC channel plugin at ${r.pluginPath}`);
|
|
4347
|
-
const t = ensureRunning();
|
|
4348
|
-
console.log(`Local-CC pueue task: id=${t.id} status=${t.status}
|
|
4349
|
-
`);
|
|
4350
|
-
} catch (err) {
|
|
4351
|
-
console.warn(` \u26A0 Local-CC setup skipped: ${err.message}`);
|
|
4352
|
-
console.warn(" Run `synkro local-cc enable` after fixing the issue.\n");
|
|
4353
|
-
}
|
|
4354
|
-
}
|
|
4355
4525
|
let transcriptConsent = true;
|
|
4356
4526
|
if (process.stdin.isTTY) {
|
|
4357
4527
|
transcriptConsent = await promptTranscriptConsent();
|
|
@@ -4418,9 +4588,10 @@ async function installCommand(opts = {}) {
|
|
|
4418
4588
|
}
|
|
4419
4589
|
const profile = await fetchUserProfile(gatewayUrl, token);
|
|
4420
4590
|
const synkroBundle = resolveSynkroBundle();
|
|
4421
|
-
writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, synkroBin: synkroBundle, transcriptConsent });
|
|
4591
|
+
writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, synkroBin: synkroBundle, transcriptConsent, localInference: profile.localInference });
|
|
4422
4592
|
console.log(`Wrote config to ${CONFIG_PATH2}`);
|
|
4423
4593
|
console.log(` inference: ${profile.inference} (server-side grading)`);
|
|
4594
|
+
if (profile.localInference) console.log(` local inference: enabled (gradingProvider=claude-code)`);
|
|
4424
4595
|
if (synkroBundle) console.log(` SYNKRO_CLI_BIN=${synkroBundle}`);
|
|
4425
4596
|
else console.warn(" \u26A0 Could not resolve synkro bundle path; hooks will fall back to PATH lookup of `synkro`.");
|
|
4426
4597
|
try {
|
|
@@ -4430,6 +4601,26 @@ async function installCommand(opts = {}) {
|
|
|
4430
4601
|
console.warn(` \u26A0 Could not cache judge prompts: ${err.message}`);
|
|
4431
4602
|
}
|
|
4432
4603
|
console.log();
|
|
4604
|
+
if (profile.localInference) {
|
|
4605
|
+
try {
|
|
4606
|
+
assertClaudeInstalled();
|
|
4607
|
+
assertPueueInstalled();
|
|
4608
|
+
assertTmuxInstalled();
|
|
4609
|
+
const r = installLocalCC();
|
|
4610
|
+
console.log(`Installed local-CC channel plugin at ${r.pluginPath}`);
|
|
4611
|
+
const t = ensureRunning();
|
|
4612
|
+
console.log(`Local-CC pueue task: id=${t.id} status=${t.status}`);
|
|
4613
|
+
console.log("Waiting for channel...");
|
|
4614
|
+
const ready = await waitForChannelReady(CHANNEL_PORT, 6e4, CHANNEL_HOST);
|
|
4615
|
+
if (ready) console.log(` channel ready at ${CHANNEL_HOST}:${CHANNEL_PORT}
|
|
4616
|
+
`);
|
|
4617
|
+
else console.warn(` \u26A0 channel did not come up within 60s \u2014 check \`synkro local-cc logs\`
|
|
4618
|
+
`);
|
|
4619
|
+
} catch (err) {
|
|
4620
|
+
console.warn(` \u26A0 Local-CC setup skipped: ${err.message}`);
|
|
4621
|
+
console.warn(" Install pueue, tmux, and claude, then re-run install.\n");
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4433
4624
|
if (transcriptConsent) {
|
|
4434
4625
|
try {
|
|
4435
4626
|
const repo = detectGitRepo2();
|
|
@@ -4476,17 +4667,17 @@ function detectGitRepo2() {
|
|
|
4476
4667
|
function getClaudeProjectsFolder() {
|
|
4477
4668
|
const cwd = process.cwd();
|
|
4478
4669
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
4479
|
-
const projectsDir =
|
|
4480
|
-
return
|
|
4670
|
+
const projectsDir = join11(homedir10(), ".claude", "projects", sanitized);
|
|
4671
|
+
return existsSync11(projectsDir) ? projectsDir : null;
|
|
4481
4672
|
}
|
|
4482
4673
|
function extractSessionInsights(projectsDir) {
|
|
4483
4674
|
const insights = [];
|
|
4484
4675
|
const files = readdirSync(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
4485
4676
|
for (const file of files) {
|
|
4486
4677
|
const sessionId = file.replace(".jsonl", "");
|
|
4487
|
-
const filePath =
|
|
4678
|
+
const filePath = join11(projectsDir, file);
|
|
4488
4679
|
try {
|
|
4489
|
-
const content =
|
|
4680
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4490
4681
|
const lines = content.split("\n").filter(Boolean);
|
|
4491
4682
|
for (let i = 0; i < lines.length; i++) {
|
|
4492
4683
|
try {
|
|
@@ -4562,7 +4753,7 @@ function extractTextContent(content) {
|
|
|
4562
4753
|
return "";
|
|
4563
4754
|
}
|
|
4564
4755
|
function parseTranscriptFile(filePath) {
|
|
4565
|
-
const content =
|
|
4756
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4566
4757
|
const lines = content.split("\n").filter(Boolean);
|
|
4567
4758
|
const messages = [];
|
|
4568
4759
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -4613,7 +4804,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
4613
4804
|
const sessions = [];
|
|
4614
4805
|
for (const file of batch) {
|
|
4615
4806
|
const sessionId = file.replace(".jsonl", "");
|
|
4616
|
-
const filePath =
|
|
4807
|
+
const filePath = join11(projectsDir, file);
|
|
4617
4808
|
try {
|
|
4618
4809
|
const allMessages = parseTranscriptFile(filePath);
|
|
4619
4810
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -4642,18 +4833,18 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
4642
4833
|
}
|
|
4643
4834
|
for (const file of batch) {
|
|
4644
4835
|
const sessionId = file.replace(".jsonl", "");
|
|
4645
|
-
const filePath =
|
|
4836
|
+
const filePath = join11(projectsDir, file);
|
|
4646
4837
|
try {
|
|
4647
|
-
const content =
|
|
4838
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4648
4839
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
4649
|
-
writeFileSync7(
|
|
4840
|
+
writeFileSync7(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
4650
4841
|
} catch {
|
|
4651
4842
|
}
|
|
4652
4843
|
}
|
|
4653
4844
|
}
|
|
4654
4845
|
return { sessions: totalSessions, messages: totalMessages };
|
|
4655
4846
|
}
|
|
4656
|
-
var
|
|
4847
|
+
var SYNKRO_DIR2, HOOKS_DIR, BIN_DIR, CONFIG_PATH2, OFFSETS_DIR;
|
|
4657
4848
|
var init_install2 = __esm({
|
|
4658
4849
|
"cli/commands/install.ts"() {
|
|
4659
4850
|
"use strict";
|
|
@@ -4666,14 +4857,14 @@ var init_install2 = __esm({
|
|
|
4666
4857
|
init_projects();
|
|
4667
4858
|
init_setupGithub();
|
|
4668
4859
|
init_promptFetcher();
|
|
4669
|
-
init_settings();
|
|
4670
4860
|
init_install();
|
|
4671
4861
|
init_pueue();
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4862
|
+
init_client();
|
|
4863
|
+
SYNKRO_DIR2 = join11(homedir10(), ".synkro");
|
|
4864
|
+
HOOKS_DIR = join11(SYNKRO_DIR2, "hooks");
|
|
4865
|
+
BIN_DIR = join11(SYNKRO_DIR2, "bin");
|
|
4866
|
+
CONFIG_PATH2 = join11(SYNKRO_DIR2, "config.env");
|
|
4867
|
+
OFFSETS_DIR = join11(SYNKRO_DIR2, ".transcript-offsets");
|
|
4677
4868
|
}
|
|
4678
4869
|
});
|
|
4679
4870
|
|
|
@@ -4748,14 +4939,14 @@ var init_logout = __esm({
|
|
|
4748
4939
|
var status_exports = {};
|
|
4749
4940
|
__export(status_exports, {
|
|
4750
4941
|
statusCommand: () => statusCommand
|
|
4751
|
-
});
|
|
4752
|
-
import { existsSync as
|
|
4753
|
-
import { homedir as
|
|
4754
|
-
import { join as
|
|
4942
|
+
});
|
|
4943
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
4944
|
+
import { homedir as homedir11 } from "os";
|
|
4945
|
+
import { join as join12 } from "path";
|
|
4755
4946
|
function readConfigEnv() {
|
|
4756
|
-
if (!
|
|
4947
|
+
if (!existsSync12(CONFIG_PATH3)) return {};
|
|
4757
4948
|
const out = {};
|
|
4758
|
-
const raw =
|
|
4949
|
+
const raw = readFileSync10(CONFIG_PATH3, "utf-8");
|
|
4759
4950
|
for (const line of raw.split("\n")) {
|
|
4760
4951
|
const trimmed = line.trim();
|
|
4761
4952
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -4828,19 +5019,19 @@ async function statusCommand() {
|
|
|
4828
5019
|
}
|
|
4829
5020
|
}
|
|
4830
5021
|
console.log();
|
|
4831
|
-
const bashScript =
|
|
4832
|
-
const bashFollowupScript =
|
|
4833
|
-
const editPrecheckScript =
|
|
4834
|
-
const editCaptureScript =
|
|
4835
|
-
const stopSummaryScript =
|
|
4836
|
-
const sessionStartScript =
|
|
5022
|
+
const bashScript = join12(SYNKRO_DIR3, "hooks", "cc-bash-judge.sh");
|
|
5023
|
+
const bashFollowupScript = join12(SYNKRO_DIR3, "hooks", "cc-bash-followup.sh");
|
|
5024
|
+
const editPrecheckScript = join12(SYNKRO_DIR3, "hooks", "cc-edit-precheck.sh");
|
|
5025
|
+
const editCaptureScript = join12(SYNKRO_DIR3, "hooks", "cc-edit-capture.sh");
|
|
5026
|
+
const stopSummaryScript = join12(SYNKRO_DIR3, "hooks", "cc-stop-summary.sh");
|
|
5027
|
+
const sessionStartScript = join12(SYNKRO_DIR3, "hooks", "cc-session-start.sh");
|
|
4837
5028
|
console.log("Hook scripts:");
|
|
4838
|
-
console.log(` ${
|
|
4839
|
-
console.log(` ${
|
|
4840
|
-
console.log(` ${
|
|
4841
|
-
console.log(` ${
|
|
4842
|
-
console.log(` ${
|
|
4843
|
-
console.log(` ${
|
|
5029
|
+
console.log(` ${existsSync12(bashScript) ? "\u2713" : "\u2717"} ${bashScript}`);
|
|
5030
|
+
console.log(` ${existsSync12(bashFollowupScript) ? "\u2713" : "\u2717"} ${bashFollowupScript}`);
|
|
5031
|
+
console.log(` ${existsSync12(editPrecheckScript) ? "\u2713" : "\u2717"} ${editPrecheckScript}`);
|
|
5032
|
+
console.log(` ${existsSync12(editCaptureScript) ? "\u2713" : "\u2717"} ${editCaptureScript}`);
|
|
5033
|
+
console.log(` ${existsSync12(stopSummaryScript) ? "\u2713" : "\u2717"} ${stopSummaryScript}`);
|
|
5034
|
+
console.log(` ${existsSync12(sessionStartScript) ? "\u2713" : "\u2717"} ${sessionStartScript}`);
|
|
4844
5035
|
console.log();
|
|
4845
5036
|
const mcp = inspectMcpConfig();
|
|
4846
5037
|
console.log("Guardrails MCP server (Claude Code):");
|
|
@@ -4852,7 +5043,7 @@ async function statusCommand() {
|
|
|
4852
5043
|
console.log(` expected at ${mcp.configPath} \u2192 mcpServers.synkro-guardrails`);
|
|
4853
5044
|
}
|
|
4854
5045
|
}
|
|
4855
|
-
var
|
|
5046
|
+
var SYNKRO_DIR3, CONFIG_PATH3;
|
|
4856
5047
|
var init_status = __esm({
|
|
4857
5048
|
"cli/commands/status.ts"() {
|
|
4858
5049
|
"use strict";
|
|
@@ -4860,8 +5051,8 @@ var init_status = __esm({
|
|
|
4860
5051
|
init_agentDetect();
|
|
4861
5052
|
init_ccHookConfig();
|
|
4862
5053
|
init_mcpConfig();
|
|
4863
|
-
|
|
4864
|
-
CONFIG_PATH3 =
|
|
5054
|
+
SYNKRO_DIR3 = join12(homedir11(), ".synkro");
|
|
5055
|
+
CONFIG_PATH3 = join12(SYNKRO_DIR3, "config.env");
|
|
4865
5056
|
}
|
|
4866
5057
|
});
|
|
4867
5058
|
|
|
@@ -4950,13 +5141,13 @@ var config_exports = {};
|
|
|
4950
5141
|
__export(config_exports, {
|
|
4951
5142
|
configCommand: () => configCommand
|
|
4952
5143
|
});
|
|
4953
|
-
import { readFileSync as
|
|
4954
|
-
import { join as
|
|
4955
|
-
import { homedir as
|
|
5144
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync13 } from "fs";
|
|
5145
|
+
import { join as join13 } from "path";
|
|
5146
|
+
import { homedir as homedir12 } from "os";
|
|
4956
5147
|
function readConfigEnv2() {
|
|
4957
|
-
if (!
|
|
5148
|
+
if (!existsSync13(CONFIG_PATH4)) return {};
|
|
4958
5149
|
const out = {};
|
|
4959
|
-
for (const line of
|
|
5150
|
+
for (const line of readFileSync11(CONFIG_PATH4, "utf-8").split("\n")) {
|
|
4960
5151
|
const t = line.trim();
|
|
4961
5152
|
if (!t || t.startsWith("#")) continue;
|
|
4962
5153
|
const eq = t.indexOf("=");
|
|
@@ -4965,11 +5156,11 @@ function readConfigEnv2() {
|
|
|
4965
5156
|
return out;
|
|
4966
5157
|
}
|
|
4967
5158
|
function updateConfigValue(key, value) {
|
|
4968
|
-
if (!
|
|
5159
|
+
if (!existsSync13(CONFIG_PATH4)) {
|
|
4969
5160
|
console.error("No config found. Run `synkro install` first.");
|
|
4970
5161
|
process.exit(1);
|
|
4971
5162
|
}
|
|
4972
|
-
const lines =
|
|
5163
|
+
const lines = readFileSync11(CONFIG_PATH4, "utf-8").split("\n");
|
|
4973
5164
|
const pattern = new RegExp(`^${key}=`);
|
|
4974
5165
|
let found = false;
|
|
4975
5166
|
const updated = lines.map((line) => {
|
|
@@ -5031,13 +5222,13 @@ To change: synkro config --inference fast|standard`);
|
|
|
5031
5222
|
updateConfigValue("SYNKRO_INFERENCE", inferenceValue);
|
|
5032
5223
|
console.log(`\u2713 Inference set to '${inferenceValue}'.`);
|
|
5033
5224
|
}
|
|
5034
|
-
var
|
|
5225
|
+
var SYNKRO_DIR4, CONFIG_PATH4;
|
|
5035
5226
|
var init_config = __esm({
|
|
5036
5227
|
"cli/commands/config.ts"() {
|
|
5037
5228
|
"use strict";
|
|
5038
5229
|
init_stub();
|
|
5039
|
-
|
|
5040
|
-
CONFIG_PATH4 =
|
|
5230
|
+
SYNKRO_DIR4 = join13(homedir12(), ".synkro");
|
|
5231
|
+
CONFIG_PATH4 = join13(SYNKRO_DIR4, "config.env");
|
|
5041
5232
|
}
|
|
5042
5233
|
});
|
|
5043
5234
|
|
|
@@ -5047,8 +5238,8 @@ __export(scanPr_exports, {
|
|
|
5047
5238
|
scanPrCommand: () => scanPrCommand
|
|
5048
5239
|
});
|
|
5049
5240
|
import { execSync as execSync6, spawn } from "child_process";
|
|
5050
|
-
import { readFileSync as
|
|
5051
|
-
import { join as
|
|
5241
|
+
import { readFileSync as readFileSync12, existsSync as existsSync14 } from "fs";
|
|
5242
|
+
import { join as join14 } from "path";
|
|
5052
5243
|
function parseMatchSpec(condition) {
|
|
5053
5244
|
if (!condition.startsWith("match_spec:")) return null;
|
|
5054
5245
|
try {
|
|
@@ -5527,10 +5718,10 @@ function shouldFail(findings, threshold) {
|
|
|
5527
5718
|
return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
|
|
5528
5719
|
}
|
|
5529
5720
|
function readRepoDeps() {
|
|
5530
|
-
const pkgPath =
|
|
5531
|
-
if (!
|
|
5721
|
+
const pkgPath = join14(process.cwd(), "package.json");
|
|
5722
|
+
if (!existsSync14(pkgPath)) return {};
|
|
5532
5723
|
try {
|
|
5533
|
-
const pkg = JSON.parse(
|
|
5724
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
5534
5725
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5535
5726
|
} catch {
|
|
5536
5727
|
return {};
|
|
@@ -5737,414 +5928,173 @@ Total: ${allFindings.length} finding(s) across ${eligible.length} file(s) in ${t
|
|
|
5737
5928
|
sha: activeSha,
|
|
5738
5929
|
findings: allFindings,
|
|
5739
5930
|
filesScanned: eligible.length,
|
|
5740
|
-
totalLatencyMs
|
|
5741
|
-
});
|
|
5742
|
-
console.log(`
|
|
5743
|
-
\u2713 Scan complete. Status: ${conclusion}.`);
|
|
5744
|
-
if (conclusion === "failure") {
|
|
5745
|
-
process.exit(1);
|
|
5746
|
-
}
|
|
5747
|
-
}
|
|
5748
|
-
var SKIP_FILE_PATTERNS, MAX_DIFF_LINES_PER_FILE, MAX_PARALLEL_FILES;
|
|
5749
|
-
var init_scanPr = __esm({
|
|
5750
|
-
"cli/commands/scanPr.ts"() {
|
|
5751
|
-
"use strict";
|
|
5752
|
-
SKIP_FILE_PATTERNS = [
|
|
5753
|
-
/\.lock$/i,
|
|
5754
|
-
/\.min\./i,
|
|
5755
|
-
/\.map$/i,
|
|
5756
|
-
/^dist\//,
|
|
5757
|
-
/^build\//,
|
|
5758
|
-
/^vendor\//,
|
|
5759
|
-
/^node_modules\//,
|
|
5760
|
-
/^\.next\//,
|
|
5761
|
-
/package-lock\.json$/,
|
|
5762
|
-
/yarn\.lock$/,
|
|
5763
|
-
/pnpm-lock\.yaml$/,
|
|
5764
|
-
/Cargo\.lock$/,
|
|
5765
|
-
/go\.sum$/
|
|
5766
|
-
];
|
|
5767
|
-
MAX_DIFF_LINES_PER_FILE = 1e3;
|
|
5768
|
-
MAX_PARALLEL_FILES = 10;
|
|
5769
|
-
}
|
|
5770
|
-
});
|
|
5771
|
-
|
|
5772
|
-
// cli/commands/update.ts
|
|
5773
|
-
var update_exports = {};
|
|
5774
|
-
__export(update_exports, {
|
|
5775
|
-
updateCommand: () => updateCommand
|
|
5776
|
-
});
|
|
5777
|
-
async function updateCommand() {
|
|
5778
|
-
console.log("Refreshing Synkro hook configs and prompts...\n");
|
|
5779
|
-
await installCommand();
|
|
5780
|
-
console.log("\n\u2713 Synkro updated.");
|
|
5781
|
-
console.log("To upgrade the CLI itself, run: npm install -g @synkro-sh/cli@latest");
|
|
5782
|
-
}
|
|
5783
|
-
var init_update = __esm({
|
|
5784
|
-
"cli/commands/update.ts"() {
|
|
5785
|
-
"use strict";
|
|
5786
|
-
init_install2();
|
|
5787
|
-
}
|
|
5788
|
-
});
|
|
5789
|
-
|
|
5790
|
-
// cli/commands/disconnect.ts
|
|
5791
|
-
var disconnect_exports = {};
|
|
5792
|
-
__export(disconnect_exports, {
|
|
5793
|
-
disconnectCommand: () => disconnectCommand
|
|
5794
|
-
});
|
|
5795
|
-
import { existsSync as existsSync14, rmSync } from "fs";
|
|
5796
|
-
import { homedir as homedir12 } from "os";
|
|
5797
|
-
import { join as join14 } from "path";
|
|
5798
|
-
function tearDownLocalCC() {
|
|
5799
|
-
let hadTask = false;
|
|
5800
|
-
try {
|
|
5801
|
-
hadTask = !!findTask();
|
|
5802
|
-
stopTask();
|
|
5803
|
-
} catch {
|
|
5804
|
-
}
|
|
5805
|
-
console.log(`${hadTask ? "\u2713" : "\xB7"} local-cc runtime: ${hadTask ? "stopped pueue task + tmux session" : "no live task"}`);
|
|
5806
|
-
uninstallLocalCC();
|
|
5807
|
-
console.log("\u2713 local-cc config: cleaned ~/.claude.json entries");
|
|
5808
|
-
}
|
|
5809
|
-
function disconnectCommand(args2 = []) {
|
|
5810
|
-
const purge = args2.includes("--purge");
|
|
5811
|
-
console.log("Synkro disconnect starting...\n");
|
|
5812
|
-
tearDownLocalCC();
|
|
5813
|
-
const agents = detectAgents();
|
|
5814
|
-
let sawClaudeCode = false;
|
|
5815
|
-
for (const agent of agents) {
|
|
5816
|
-
if (agent.kind === "claude_code") {
|
|
5817
|
-
sawClaudeCode = true;
|
|
5818
|
-
const removed = uninstallCCHooks(agent.settingsPath);
|
|
5819
|
-
console.log(`${removed ? "\u2713" : "\xB7"} ${agent.name}: ${removed ? "removed Synkro hook entries" : "no Synkro hooks found"}`);
|
|
5820
|
-
}
|
|
5821
|
-
}
|
|
5822
|
-
if (sawClaudeCode) {
|
|
5823
|
-
const mcpRemoved = uninstallMcpConfig();
|
|
5824
|
-
console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
|
|
5825
|
-
}
|
|
5826
|
-
if (purge) {
|
|
5827
|
-
if (existsSync14(SYNKRO_DIR6)) {
|
|
5828
|
-
rmSync(SYNKRO_DIR6, { recursive: true, force: true });
|
|
5829
|
-
console.log(`\u2713 Removed ${SYNKRO_DIR6}`);
|
|
5830
|
-
} else {
|
|
5831
|
-
console.log(`\xB7 ${SYNKRO_DIR6} already gone, nothing to remove`);
|
|
5832
|
-
}
|
|
5833
|
-
} else if (existsSync14(SYNKRO_DIR6)) {
|
|
5834
|
-
console.log(`Config preserved at ${SYNKRO_DIR6}. Run with --purge to remove.`);
|
|
5835
|
-
}
|
|
5836
|
-
console.log("\nSynkro disconnected.");
|
|
5837
|
-
}
|
|
5838
|
-
var SYNKRO_DIR6;
|
|
5839
|
-
var init_disconnect = __esm({
|
|
5840
|
-
"cli/commands/disconnect.ts"() {
|
|
5841
|
-
"use strict";
|
|
5842
|
-
init_agentDetect();
|
|
5843
|
-
init_ccHookConfig();
|
|
5844
|
-
init_mcpConfig();
|
|
5845
|
-
init_pueue();
|
|
5846
|
-
init_install();
|
|
5847
|
-
SYNKRO_DIR6 = join14(homedir12(), ".synkro");
|
|
5848
|
-
}
|
|
5849
|
-
});
|
|
5850
|
-
|
|
5851
|
-
// cli/commands/uninstall.ts
|
|
5852
|
-
var uninstall_exports = {};
|
|
5853
|
-
__export(uninstall_exports, {
|
|
5854
|
-
uninstallCommand: () => uninstallCommand
|
|
5855
|
-
});
|
|
5856
|
-
function uninstallCommand() {
|
|
5857
|
-
console.log("Uninstalling Synkro...\n");
|
|
5858
|
-
disconnectCommand(["--purge"]);
|
|
5859
|
-
console.log("\nTo reinstall later: synkro install");
|
|
5860
|
-
}
|
|
5861
|
-
var init_uninstall = __esm({
|
|
5862
|
-
"cli/commands/uninstall.ts"() {
|
|
5863
|
-
"use strict";
|
|
5864
|
-
init_disconnect();
|
|
5865
|
-
}
|
|
5866
|
-
});
|
|
5867
|
-
|
|
5868
|
-
// cli/commands/reinstall.ts
|
|
5869
|
-
var reinstall_exports = {};
|
|
5870
|
-
__export(reinstall_exports, {
|
|
5871
|
-
reinstallCommand: () => reinstallCommand
|
|
5872
|
-
});
|
|
5873
|
-
async function reinstallCommand() {
|
|
5874
|
-
console.log("Reinstalling Synkro...\n");
|
|
5875
|
-
disconnectCommand(["--purge"]);
|
|
5876
|
-
console.log("");
|
|
5877
|
-
await installCommand({ force: true });
|
|
5878
|
-
console.log("\n\u2713 Synkro reinstalled.");
|
|
5879
|
-
}
|
|
5880
|
-
var init_reinstall = __esm({
|
|
5881
|
-
"cli/commands/reinstall.ts"() {
|
|
5882
|
-
"use strict";
|
|
5883
|
-
init_disconnect();
|
|
5884
|
-
init_install2();
|
|
5885
|
-
}
|
|
5886
|
-
});
|
|
5887
|
-
|
|
5888
|
-
// cli/local-cc/turnLog.ts
|
|
5889
|
-
import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync9, openSync as openSync2, readFileSync as readFileSync11, readSync, closeSync as closeSync2, statSync, watchFile, unwatchFile } from "fs";
|
|
5890
|
-
import { dirname as dirname5, join as join15 } from "path";
|
|
5891
|
-
import { homedir as homedir13 } from "os";
|
|
5892
|
-
function truncate(s, max = PREVIEW_MAX) {
|
|
5893
|
-
if (s.length <= max) return s;
|
|
5894
|
-
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
5895
|
-
}
|
|
5896
|
-
function extractSeverity(result) {
|
|
5897
|
-
const m = result.match(/<synkro-(?:verdict|intent)>([\s\S]*?)<\/synkro-(?:verdict|intent)>/);
|
|
5898
|
-
if (!m) return void 0;
|
|
5899
|
-
try {
|
|
5900
|
-
const obj = JSON.parse(m[1]);
|
|
5901
|
-
if (obj.severity) return String(obj.severity);
|
|
5902
|
-
if (typeof obj.ok === "boolean") return obj.ok ? "ok" : "violations";
|
|
5903
|
-
if (obj.type) return String(obj.type);
|
|
5904
|
-
if (obj.verdict) return String(obj.verdict);
|
|
5905
|
-
} catch {
|
|
5906
|
-
}
|
|
5907
|
-
return void 0;
|
|
5908
|
-
}
|
|
5909
|
-
function appendTurn(args2) {
|
|
5910
|
-
try {
|
|
5911
|
-
mkdirSync9(dirname5(TURN_LOG_PATH), { recursive: true });
|
|
5912
|
-
const entry = {
|
|
5913
|
-
ts: new Date(args2.startedAt).toISOString(),
|
|
5914
|
-
role: args2.role,
|
|
5915
|
-
duration_ms: Date.now() - args2.startedAt,
|
|
5916
|
-
status: args2.status,
|
|
5917
|
-
request_preview: truncate(args2.request),
|
|
5918
|
-
response_preview: args2.result ? truncate(args2.result) : "",
|
|
5919
|
-
severity: args2.result ? extractSeverity(args2.result) : void 0,
|
|
5920
|
-
error: args2.error
|
|
5921
|
-
};
|
|
5922
|
-
appendFileSync(TURN_LOG_PATH, JSON.stringify(entry) + "\n", "utf-8");
|
|
5923
|
-
} catch {
|
|
5924
|
-
}
|
|
5925
|
-
}
|
|
5926
|
-
function readRecentTurns(n = 20) {
|
|
5927
|
-
if (!existsSync15(TURN_LOG_PATH)) return [];
|
|
5928
|
-
try {
|
|
5929
|
-
const size = statSync(TURN_LOG_PATH).size;
|
|
5930
|
-
if (size === 0) return [];
|
|
5931
|
-
const text = readFileSync11(TURN_LOG_PATH, "utf-8");
|
|
5932
|
-
const lines = text.split("\n").filter(Boolean);
|
|
5933
|
-
const lastN = lines.slice(-n).reverse();
|
|
5934
|
-
return lastN.map((line) => {
|
|
5935
|
-
try {
|
|
5936
|
-
return JSON.parse(line);
|
|
5937
|
-
} catch {
|
|
5938
|
-
return null;
|
|
5939
|
-
}
|
|
5940
|
-
}).filter((x) => x !== null);
|
|
5941
|
-
} catch {
|
|
5942
|
-
return [];
|
|
5943
|
-
}
|
|
5944
|
-
}
|
|
5945
|
-
function followTurns(onEntry) {
|
|
5946
|
-
try {
|
|
5947
|
-
mkdirSync9(dirname5(TURN_LOG_PATH), { recursive: true });
|
|
5948
|
-
if (!existsSync15(TURN_LOG_PATH)) {
|
|
5949
|
-
appendFileSync(TURN_LOG_PATH, "", "utf-8");
|
|
5950
|
-
}
|
|
5951
|
-
} catch {
|
|
5952
|
-
}
|
|
5953
|
-
let lastSize = (() => {
|
|
5954
|
-
try {
|
|
5955
|
-
return statSync(TURN_LOG_PATH).size;
|
|
5956
|
-
} catch {
|
|
5957
|
-
return 0;
|
|
5958
|
-
}
|
|
5959
|
-
})();
|
|
5960
|
-
let pendingPartial = "";
|
|
5961
|
-
const drainNewBytes = (from, to) => {
|
|
5962
|
-
if (to <= from) return;
|
|
5963
|
-
let fd = null;
|
|
5964
|
-
try {
|
|
5965
|
-
fd = openSync2(TURN_LOG_PATH, "r");
|
|
5966
|
-
const len = to - from;
|
|
5967
|
-
const buf = Buffer.alloc(len);
|
|
5968
|
-
readSync(fd, buf, 0, len, from);
|
|
5969
|
-
const text = pendingPartial + buf.toString("utf-8");
|
|
5970
|
-
const lastNewline = text.lastIndexOf("\n");
|
|
5971
|
-
if (lastNewline === -1) {
|
|
5972
|
-
pendingPartial = text;
|
|
5973
|
-
return;
|
|
5974
|
-
}
|
|
5975
|
-
const complete = text.slice(0, lastNewline);
|
|
5976
|
-
pendingPartial = text.slice(lastNewline + 1);
|
|
5977
|
-
for (const line of complete.split("\n")) {
|
|
5978
|
-
if (!line) continue;
|
|
5979
|
-
try {
|
|
5980
|
-
onEntry(JSON.parse(line));
|
|
5981
|
-
} catch {
|
|
5982
|
-
}
|
|
5983
|
-
}
|
|
5984
|
-
} catch {
|
|
5985
|
-
} finally {
|
|
5986
|
-
if (fd !== null) {
|
|
5987
|
-
try {
|
|
5988
|
-
closeSync2(fd);
|
|
5989
|
-
} catch {
|
|
5990
|
-
}
|
|
5991
|
-
}
|
|
5992
|
-
}
|
|
5993
|
-
};
|
|
5994
|
-
watchFile(TURN_LOG_PATH, { interval: 250 }, (curr, prev) => {
|
|
5995
|
-
if (curr.size < lastSize) {
|
|
5996
|
-
lastSize = 0;
|
|
5997
|
-
pendingPartial = "";
|
|
5998
|
-
}
|
|
5999
|
-
if (curr.size > lastSize) {
|
|
6000
|
-
drainNewBytes(lastSize, curr.size);
|
|
6001
|
-
lastSize = curr.size;
|
|
6002
|
-
}
|
|
5931
|
+
totalLatencyMs
|
|
6003
5932
|
});
|
|
6004
|
-
|
|
5933
|
+
console.log(`
|
|
5934
|
+
\u2713 Scan complete. Status: ${conclusion}.`);
|
|
5935
|
+
if (conclusion === "failure") {
|
|
5936
|
+
process.exit(1);
|
|
5937
|
+
}
|
|
6005
5938
|
}
|
|
6006
|
-
var
|
|
6007
|
-
var
|
|
6008
|
-
"cli/
|
|
5939
|
+
var SKIP_FILE_PATTERNS, MAX_DIFF_LINES_PER_FILE, MAX_PARALLEL_FILES;
|
|
5940
|
+
var init_scanPr = __esm({
|
|
5941
|
+
"cli/commands/scanPr.ts"() {
|
|
6009
5942
|
"use strict";
|
|
6010
|
-
|
|
6011
|
-
|
|
5943
|
+
SKIP_FILE_PATTERNS = [
|
|
5944
|
+
/\.lock$/i,
|
|
5945
|
+
/\.min\./i,
|
|
5946
|
+
/\.map$/i,
|
|
5947
|
+
/^dist\//,
|
|
5948
|
+
/^build\//,
|
|
5949
|
+
/^vendor\//,
|
|
5950
|
+
/^node_modules\//,
|
|
5951
|
+
/^\.next\//,
|
|
5952
|
+
/package-lock\.json$/,
|
|
5953
|
+
/yarn\.lock$/,
|
|
5954
|
+
/pnpm-lock\.yaml$/,
|
|
5955
|
+
/Cargo\.lock$/,
|
|
5956
|
+
/go\.sum$/
|
|
5957
|
+
];
|
|
5958
|
+
MAX_DIFF_LINES_PER_FILE = 1e3;
|
|
5959
|
+
MAX_PARALLEL_FILES = 10;
|
|
6012
5960
|
}
|
|
6013
5961
|
});
|
|
6014
5962
|
|
|
6015
|
-
// cli/
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
5963
|
+
// cli/commands/update.ts
|
|
5964
|
+
var update_exports = {};
|
|
5965
|
+
__export(update_exports, {
|
|
5966
|
+
updateCommand: () => updateCommand
|
|
5967
|
+
});
|
|
5968
|
+
async function updateCommand() {
|
|
5969
|
+
console.log("Refreshing Synkro hook configs and prompts...\n");
|
|
5970
|
+
await installCommand();
|
|
5971
|
+
console.log("\n\u2713 Synkro updated.");
|
|
5972
|
+
console.log("To upgrade the CLI itself, run: npm install -g @synkro-sh/cli@latest");
|
|
5973
|
+
}
|
|
5974
|
+
var init_update = __esm({
|
|
5975
|
+
"cli/commands/update.ts"() {
|
|
5976
|
+
"use strict";
|
|
5977
|
+
init_install2();
|
|
6023
5978
|
}
|
|
5979
|
+
});
|
|
5980
|
+
|
|
5981
|
+
// cli/commands/disconnect.ts
|
|
5982
|
+
var disconnect_exports = {};
|
|
5983
|
+
__export(disconnect_exports, {
|
|
5984
|
+
disconnectCommand: () => disconnectCommand
|
|
5985
|
+
});
|
|
5986
|
+
import { existsSync as existsSync15, rmSync } from "fs";
|
|
5987
|
+
import { homedir as homedir13 } from "os";
|
|
5988
|
+
import { join as join15 } from "path";
|
|
5989
|
+
function tearDownLocalCC() {
|
|
5990
|
+
let hadTask = false;
|
|
6024
5991
|
try {
|
|
6025
|
-
|
|
6026
|
-
|
|
5992
|
+
hadTask = !!findTask();
|
|
5993
|
+
stopTask();
|
|
6027
5994
|
} catch {
|
|
6028
|
-
throw new Error("Prompts cache is corrupted. Run `synkro update` to refresh.");
|
|
6029
5995
|
}
|
|
5996
|
+
console.log(`${hadTask ? "\u2713" : "\xB7"} local-cc runtime: ${hadTask ? "stopped pueue task + tmux session" : "no live task"}`);
|
|
5997
|
+
uninstallLocalCC();
|
|
5998
|
+
console.log("\u2713 local-cc config: cleaned ~/.claude.json entries");
|
|
6030
5999
|
}
|
|
6031
|
-
function
|
|
6032
|
-
const
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6000
|
+
function disconnectCommand(args2 = []) {
|
|
6001
|
+
const purge = args2.includes("--purge");
|
|
6002
|
+
console.log("Synkro disconnect starting...\n");
|
|
6003
|
+
tearDownLocalCC();
|
|
6004
|
+
const agents = detectAgents();
|
|
6005
|
+
let sawClaudeCode = false;
|
|
6006
|
+
for (const agent of agents) {
|
|
6007
|
+
if (agent.kind === "claude_code") {
|
|
6008
|
+
sawClaudeCode = true;
|
|
6009
|
+
const removed = uninstallCCHooks(agent.settingsPath);
|
|
6010
|
+
console.log(`${removed ? "\u2713" : "\xB7"} ${agent.name}: ${removed ? "removed Synkro hook entries" : "no Synkro hooks found"}`);
|
|
6011
|
+
}
|
|
6036
6012
|
}
|
|
6037
|
-
|
|
6013
|
+
if (sawClaudeCode) {
|
|
6014
|
+
const mcpRemoved = uninstallMcpConfig();
|
|
6015
|
+
console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
|
|
6016
|
+
}
|
|
6017
|
+
if (purge) {
|
|
6018
|
+
if (existsSync15(SYNKRO_DIR5)) {
|
|
6019
|
+
rmSync(SYNKRO_DIR5, { recursive: true, force: true });
|
|
6020
|
+
console.log(`\u2713 Removed ${SYNKRO_DIR5}`);
|
|
6021
|
+
} else {
|
|
6022
|
+
console.log(`\xB7 ${SYNKRO_DIR5} already gone, nothing to remove`);
|
|
6023
|
+
}
|
|
6024
|
+
} else if (existsSync15(SYNKRO_DIR5)) {
|
|
6025
|
+
console.log(`Config preserved at ${SYNKRO_DIR5}. Run with --purge to remove.`);
|
|
6026
|
+
}
|
|
6027
|
+
console.log("\nSynkro disconnected.");
|
|
6038
6028
|
}
|
|
6039
|
-
|
|
6040
|
-
|
|
6029
|
+
var SYNKRO_DIR5;
|
|
6030
|
+
var init_disconnect = __esm({
|
|
6031
|
+
"cli/commands/disconnect.ts"() {
|
|
6032
|
+
"use strict";
|
|
6033
|
+
init_agentDetect();
|
|
6034
|
+
init_ccHookConfig();
|
|
6035
|
+
init_mcpConfig();
|
|
6036
|
+
init_pueue();
|
|
6037
|
+
init_install();
|
|
6038
|
+
SYNKRO_DIR5 = join15(homedir13(), ".synkro");
|
|
6039
|
+
}
|
|
6040
|
+
});
|
|
6041
6041
|
|
|
6042
|
-
|
|
6043
|
-
|
|
6042
|
+
// cli/commands/uninstall.ts
|
|
6043
|
+
var uninstall_exports = {};
|
|
6044
|
+
__export(uninstall_exports, {
|
|
6045
|
+
uninstallCommand: () => uninstallCommand
|
|
6046
|
+
});
|
|
6047
|
+
function uninstallCommand() {
|
|
6048
|
+
console.log("Uninstalling Synkro...\n");
|
|
6049
|
+
disconnectCommand(["--purge"]);
|
|
6050
|
+
console.log("\nTo reinstall later: synkro install");
|
|
6051
|
+
}
|
|
6052
|
+
var init_uninstall = __esm({
|
|
6053
|
+
"cli/commands/uninstall.ts"() {
|
|
6054
|
+
"use strict";
|
|
6055
|
+
init_disconnect();
|
|
6056
|
+
}
|
|
6057
|
+
});
|
|
6044
6058
|
|
|
6045
|
-
|
|
6059
|
+
// cli/commands/reinstall.ts
|
|
6060
|
+
var reinstall_exports = {};
|
|
6061
|
+
__export(reinstall_exports, {
|
|
6062
|
+
reinstallCommand: () => reinstallCommand
|
|
6063
|
+
});
|
|
6064
|
+
async function reinstallCommand() {
|
|
6065
|
+
console.log("Reinstalling Synkro...\n");
|
|
6066
|
+
disconnectCommand(["--purge"]);
|
|
6067
|
+
console.log("");
|
|
6068
|
+
await installCommand({ force: true });
|
|
6069
|
+
console.log("\n\u2713 Synkro reinstalled.");
|
|
6046
6070
|
}
|
|
6047
|
-
var
|
|
6048
|
-
|
|
6049
|
-
"cli/local-cc/prompts.ts"() {
|
|
6071
|
+
var init_reinstall = __esm({
|
|
6072
|
+
"cli/commands/reinstall.ts"() {
|
|
6050
6073
|
"use strict";
|
|
6051
|
-
|
|
6052
|
-
|
|
6074
|
+
init_disconnect();
|
|
6075
|
+
init_install2();
|
|
6053
6076
|
}
|
|
6054
6077
|
});
|
|
6055
6078
|
|
|
6056
|
-
// cli/local-cc/
|
|
6057
|
-
import {
|
|
6058
|
-
import {
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
6063
|
-
const startedAt = Date.now();
|
|
6079
|
+
// cli/local-cc/settings.ts
|
|
6080
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
|
|
6081
|
+
import { homedir as homedir14 } from "os";
|
|
6082
|
+
import { join as join16 } from "path";
|
|
6083
|
+
function isLocalCCEnabled() {
|
|
6084
|
+
if (!existsSync16(CONFIG_PATH5)) return false;
|
|
6064
6085
|
try {
|
|
6065
|
-
const
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
path: "/submit",
|
|
6071
|
-
headers: {
|
|
6072
|
-
"Content-Type": "application/json",
|
|
6073
|
-
"Content-Length": Buffer.byteLength(body)
|
|
6074
|
-
},
|
|
6075
|
-
timeout: timeoutMs
|
|
6076
|
-
}, (res) => {
|
|
6077
|
-
const chunks = [];
|
|
6078
|
-
res.on("data", (c) => chunks.push(c));
|
|
6079
|
-
res.on("end", () => {
|
|
6080
|
-
const text = Buffer.concat(chunks).toString("utf-8");
|
|
6081
|
-
if (res.statusCode !== 200) {
|
|
6082
|
-
reject(new LocalCCError(`channel returned ${res.statusCode}: ${text.slice(0, 500)}`));
|
|
6083
|
-
return;
|
|
6084
|
-
}
|
|
6085
|
-
try {
|
|
6086
|
-
const parsed = JSON.parse(text);
|
|
6087
|
-
if (parsed.error) {
|
|
6088
|
-
reject(new LocalCCError(parsed.error));
|
|
6089
|
-
return;
|
|
6090
|
-
}
|
|
6091
|
-
resolve2(String(parsed.result ?? ""));
|
|
6092
|
-
} catch (err) {
|
|
6093
|
-
reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
|
|
6094
|
-
}
|
|
6095
|
-
});
|
|
6096
|
-
});
|
|
6097
|
-
req.on("timeout", () => {
|
|
6098
|
-
req.destroy(new LocalCCError(`channel request timed out after ${timeoutMs}ms`));
|
|
6099
|
-
});
|
|
6100
|
-
req.on("error", (err) => {
|
|
6101
|
-
const msg = err.code === "ECONNREFUSED" ? `channel connection refused at ${CHANNEL_HOST}:${CHANNEL_PORT} (is the pueue task running?)` : `channel request failed: ${err.message}`;
|
|
6102
|
-
reject(new LocalCCError(msg, err));
|
|
6103
|
-
});
|
|
6104
|
-
req.write(body);
|
|
6105
|
-
req.end();
|
|
6106
|
-
});
|
|
6107
|
-
appendTurn({ startedAt, role, request: payload, result, status: "ok" });
|
|
6108
|
-
return result;
|
|
6109
|
-
} catch (err) {
|
|
6110
|
-
const message = err.message ?? String(err);
|
|
6111
|
-
const status = /timed out/i.test(message) ? "timeout" : "error";
|
|
6112
|
-
appendTurn({ startedAt, role, request: payload, status, error: message });
|
|
6113
|
-
throw err;
|
|
6086
|
+
const content = readFileSync13(CONFIG_PATH5, "utf-8");
|
|
6087
|
+
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
6088
|
+
return match?.[1] === "yes";
|
|
6089
|
+
} catch {
|
|
6090
|
+
return false;
|
|
6114
6091
|
}
|
|
6115
6092
|
}
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
const done = (ok) => {
|
|
6120
|
-
try {
|
|
6121
|
-
sock.destroy();
|
|
6122
|
-
} catch {
|
|
6123
|
-
}
|
|
6124
|
-
resolve2(ok);
|
|
6125
|
-
};
|
|
6126
|
-
sock.once("connect", () => done(true));
|
|
6127
|
-
sock.once("error", () => done(false));
|
|
6128
|
-
sock.setTimeout(timeoutMs, () => done(false));
|
|
6129
|
-
});
|
|
6130
|
-
}
|
|
6131
|
-
var CHANNEL_HOST, CHANNEL_PORT, DEFAULT_TIMEOUT_MS, LocalCCError;
|
|
6132
|
-
var init_client = __esm({
|
|
6133
|
-
"cli/local-cc/client.ts"() {
|
|
6093
|
+
var CONFIG_PATH5;
|
|
6094
|
+
var init_settings = __esm({
|
|
6095
|
+
"cli/local-cc/settings.ts"() {
|
|
6134
6096
|
"use strict";
|
|
6135
|
-
|
|
6136
|
-
init_turnLog();
|
|
6137
|
-
CHANNEL_HOST = "127.0.0.1";
|
|
6138
|
-
CHANNEL_PORT = parseInt(process.env.SYNKRO_CHANNEL_PORT || "8929", 10);
|
|
6139
|
-
DEFAULT_TIMEOUT_MS = 9e4;
|
|
6140
|
-
LocalCCError = class extends Error {
|
|
6141
|
-
constructor(message, cause) {
|
|
6142
|
-
super(message);
|
|
6143
|
-
this.cause = cause;
|
|
6144
|
-
this.name = "LocalCCError";
|
|
6145
|
-
}
|
|
6146
|
-
cause;
|
|
6147
|
-
};
|
|
6097
|
+
CONFIG_PATH5 = join16(homedir14(), ".synkro", "config.env");
|
|
6148
6098
|
}
|
|
6149
6099
|
});
|
|
6150
6100
|
|
|
@@ -6156,8 +6106,8 @@ __export(localCc_exports, {
|
|
|
6156
6106
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
6157
6107
|
import { homedir as homedir15 } from "os";
|
|
6158
6108
|
import { join as join17 } from "path";
|
|
6109
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
6159
6110
|
function printHelp() {
|
|
6160
|
-
const dbPath = join17(homedir15(), ".synkro", "sessions.db");
|
|
6161
6111
|
console.log(`synkro local-cc \u2014 manage the local Claude Code inference session
|
|
6162
6112
|
|
|
6163
6113
|
OVERVIEW
|
|
@@ -6199,11 +6149,10 @@ SUBCOMMANDS
|
|
|
6199
6149
|
help Show this message
|
|
6200
6150
|
|
|
6201
6151
|
CONFIGURATION
|
|
6202
|
-
Provider toggle
|
|
6203
|
-
Stored in
|
|
6152
|
+
Provider toggle:
|
|
6153
|
+
Stored server-side in your inference settings (gradingProvider).
|
|
6204
6154
|
Toggle via: synkro local-cc enable / disable
|
|
6205
|
-
Or
|
|
6206
|
-
INSERT OR REPLACE INTO settings (key, value) VALUES ('inference_provider', 'local-cc');
|
|
6155
|
+
Or via dashboard inference settings (set grading provider to claude-code).
|
|
6207
6156
|
|
|
6208
6157
|
Claude Code session settings (scoped to ~/.synkro/cc_sessions only):
|
|
6209
6158
|
${PLUGIN_SETTINGS_PATH}
|
|
@@ -6246,8 +6195,44 @@ TROUBLESHOOTING
|
|
|
6246
6195
|
synkro local-cc attach
|
|
6247
6196
|
`);
|
|
6248
6197
|
}
|
|
6198
|
+
function readGatewayUrl() {
|
|
6199
|
+
if (existsSync17(CONFIG_PATH6)) {
|
|
6200
|
+
const m = readFileSync14(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
6201
|
+
if (m) return m[1];
|
|
6202
|
+
}
|
|
6203
|
+
return "https://api.synkro.sh";
|
|
6204
|
+
}
|
|
6205
|
+
function updateLocalInferenceFlag(enabled) {
|
|
6206
|
+
if (!existsSync17(CONFIG_PATH6)) return;
|
|
6207
|
+
let content = readFileSync14(CONFIG_PATH6, "utf-8");
|
|
6208
|
+
const flag = enabled ? "yes" : "no";
|
|
6209
|
+
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
6210
|
+
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
6211
|
+
} else {
|
|
6212
|
+
content = content.trimEnd() + `
|
|
6213
|
+
SYNKRO_LOCAL_INFERENCE='${flag}'
|
|
6214
|
+
`;
|
|
6215
|
+
}
|
|
6216
|
+
writeFileSync9(CONFIG_PATH6, content, "utf-8");
|
|
6217
|
+
}
|
|
6218
|
+
async function setServerGradingProvider(provider) {
|
|
6219
|
+
await ensureValidToken();
|
|
6220
|
+
const jwt2 = getAccessToken();
|
|
6221
|
+
if (!jwt2) throw new Error("Not authenticated. Run `synkro install` first.");
|
|
6222
|
+
const gatewayUrl = readGatewayUrl();
|
|
6223
|
+
const body = provider ? { roles: { grading: { provider, model: "default" } } } : { roles: { grading: { provider: null, model: null } } };
|
|
6224
|
+
const resp = await fetch(`${gatewayUrl}/api/settings/inference?scope=user`, {
|
|
6225
|
+
method: "PUT",
|
|
6226
|
+
headers: { "Authorization": `Bearer ${jwt2}`, "Content-Type": "application/json" },
|
|
6227
|
+
body: JSON.stringify(body)
|
|
6228
|
+
});
|
|
6229
|
+
if (!resp.ok) {
|
|
6230
|
+
const text = await resp.text().catch(() => "");
|
|
6231
|
+
throw new Error(`Failed to update inference settings: ${resp.status} ${text.slice(0, 200)}`);
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6249
6234
|
async function cmdStatus() {
|
|
6250
|
-
console.log(`
|
|
6235
|
+
console.log(`Local inference: ${isLocalCCEnabled() ? "enabled" : "disabled"}`);
|
|
6251
6236
|
try {
|
|
6252
6237
|
assertPueueInstalled();
|
|
6253
6238
|
} catch (err) {
|
|
@@ -6281,12 +6266,16 @@ async function cmdEnable() {
|
|
|
6281
6266
|
const ready = await waitForChannelReady(CHANNEL_PORT, 6e4, CHANNEL_HOST);
|
|
6282
6267
|
if (ready) console.log(` channel ready at ${CHANNEL_HOST}:${CHANNEL_PORT}`);
|
|
6283
6268
|
else console.warn(` \u26A0 channel did not come up within 60s \u2014 check \`synkro local-cc logs\``);
|
|
6284
|
-
|
|
6285
|
-
|
|
6269
|
+
console.log("Updating inference settings...");
|
|
6270
|
+
await setServerGradingProvider("claude-code");
|
|
6271
|
+
updateLocalInferenceFlag(true);
|
|
6272
|
+
console.log("Grading provider set to claude-code (local inference enabled).");
|
|
6286
6273
|
}
|
|
6287
|
-
function cmdDisable() {
|
|
6288
|
-
|
|
6289
|
-
|
|
6274
|
+
async function cmdDisable() {
|
|
6275
|
+
console.log("Updating inference settings...");
|
|
6276
|
+
await setServerGradingProvider(null);
|
|
6277
|
+
updateLocalInferenceFlag(false);
|
|
6278
|
+
console.log("Grading provider cleared (remote inference restored). Pueue task left running \u2014 use `synkro local-cc stop` to terminate.");
|
|
6290
6279
|
}
|
|
6291
6280
|
async function cmdStart() {
|
|
6292
6281
|
assertClaudeInstalled();
|
|
@@ -6489,6 +6478,7 @@ async function localCcCommand(args2) {
|
|
|
6489
6478
|
process.exit(1);
|
|
6490
6479
|
}
|
|
6491
6480
|
}
|
|
6481
|
+
var CONFIG_PATH6;
|
|
6492
6482
|
var init_localCc = __esm({
|
|
6493
6483
|
"cli/commands/localCc.ts"() {
|
|
6494
6484
|
"use strict";
|
|
@@ -6497,6 +6487,8 @@ var init_localCc = __esm({
|
|
|
6497
6487
|
init_pueue();
|
|
6498
6488
|
init_settings();
|
|
6499
6489
|
init_client();
|
|
6490
|
+
init_stub();
|
|
6491
|
+
CONFIG_PATH6 = join17(homedir15(), ".synkro", "config.env");
|
|
6500
6492
|
}
|
|
6501
6493
|
});
|
|
6502
6494
|
|
|
@@ -6548,15 +6540,15 @@ var init_grade = __esm({
|
|
|
6548
6540
|
});
|
|
6549
6541
|
|
|
6550
6542
|
// cli/bootstrap.js
|
|
6551
|
-
import { readFileSync as
|
|
6543
|
+
import { readFileSync as readFileSync15, existsSync as existsSync18 } from "fs";
|
|
6552
6544
|
import { resolve } from "path";
|
|
6553
6545
|
var envCandidates = [
|
|
6554
6546
|
resolve(process.cwd(), ".env"),
|
|
6555
6547
|
resolve(process.env.HOME ?? "", ".synkro", "config.env")
|
|
6556
6548
|
];
|
|
6557
6549
|
for (const envPath of envCandidates) {
|
|
6558
|
-
if (!
|
|
6559
|
-
const envContent =
|
|
6550
|
+
if (!existsSync18(envPath)) continue;
|
|
6551
|
+
const envContent = readFileSync15(envPath, "utf-8");
|
|
6560
6552
|
for (const line of envContent.split("\n")) {
|
|
6561
6553
|
const trimmed = line.trim();
|
|
6562
6554
|
if (!trimmed || trimmed.startsWith("#")) continue;
|