@synkro-sh/cli 1.7.18 → 1.7.27
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 +393 -274
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -269,6 +269,10 @@ function installCCHooks(settingsPath, config) {
|
|
|
269
269
|
],
|
|
270
270
|
[SYNKRO_MARKER]: true
|
|
271
271
|
});
|
|
272
|
+
if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {
|
|
273
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
274
|
+
if (Object.keys(settings.env).length === 0) delete settings.env;
|
|
275
|
+
}
|
|
272
276
|
writeSettingsAtomic(settingsPath, settings);
|
|
273
277
|
}
|
|
274
278
|
function uninstallCCHooks(settingsPath) {
|
|
@@ -287,14 +291,19 @@ function uninstallCCHooks(settingsPath) {
|
|
|
287
291
|
if (Object.keys(settings.hooks).length === 0) {
|
|
288
292
|
delete settings.hooks;
|
|
289
293
|
}
|
|
294
|
+
if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {
|
|
295
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
296
|
+
if (Object.keys(settings.env).length === 0) delete settings.env;
|
|
297
|
+
}
|
|
290
298
|
writeSettingsAtomic(settingsPath, settings);
|
|
291
299
|
return true;
|
|
292
300
|
}
|
|
293
|
-
var SYNKRO_MARKER;
|
|
301
|
+
var SYNKRO_MARKER, USAGE_PROXY_URL;
|
|
294
302
|
var init_ccHookConfig = __esm({
|
|
295
303
|
"cli/installer/ccHookConfig.ts"() {
|
|
296
304
|
"use strict";
|
|
297
305
|
SYNKRO_MARKER = "__synkro_managed__";
|
|
306
|
+
USAGE_PROXY_URL = `http://127.0.0.1:${process.env.SYNKRO_HOST_USAGE_PROXY_PORT || "18927"}`;
|
|
298
307
|
}
|
|
299
308
|
});
|
|
300
309
|
|
|
@@ -7133,6 +7142,15 @@ async function main() {
|
|
|
7133
7142
|
const usage = aggregateUsage(transcriptPath, { modelFallback });
|
|
7134
7143
|
emitUsageTick({ sessionId, usage, hookType: 'prompt_submit', gitRepo, modelFallback });
|
|
7135
7144
|
}
|
|
7145
|
+
// Nudge the local container to refresh account-wide coding-agent usage OUT OF BAND.
|
|
7146
|
+
// The endpoint returns 202 immediately and does its own Anthropic ping, so this
|
|
7147
|
+
// never gates the prompt; a stopped container just fails the fetch fast (caught).
|
|
7148
|
+
try {
|
|
7149
|
+
const usagePort = process.env.SYNKRO_MCP_HOST_PORT || '18931';
|
|
7150
|
+
await fetch('http://127.0.0.1:' + usagePort + '/api/local/usage-poll', {
|
|
7151
|
+
method: 'POST', signal: AbortSignal.timeout(1200),
|
|
7152
|
+
}).catch(() => {});
|
|
7153
|
+
} catch {}
|
|
7136
7154
|
outputEmpty();
|
|
7137
7155
|
} catch {
|
|
7138
7156
|
outputEmpty();
|
|
@@ -10550,13 +10568,14 @@ async function dockerInstall(opts = {}) {
|
|
|
10550
10568
|
}
|
|
10551
10569
|
mkdirSync8(CURSOR_CREDS_DIR, { recursive: true });
|
|
10552
10570
|
if (needsKeychainBridge()) {
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
if (
|
|
10571
|
+
const claudeCredsPath = exportKeychainCreds();
|
|
10572
|
+
if (!claudeCredsPath) {
|
|
10573
|
+
if (claudeWorkers > 0) {
|
|
10556
10574
|
throw new DockerInstallError(
|
|
10557
10575
|
"Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container."
|
|
10558
10576
|
);
|
|
10559
10577
|
}
|
|
10578
|
+
console.warn(" \u26A0 Claude Code keychain entry not found \u2014 live usage telemetry stays off until you sign in to Claude Code (grading is unaffected).");
|
|
10560
10579
|
}
|
|
10561
10580
|
if (cursorWorkers > 0 && !cursorApiKeyConfigured()) {
|
|
10562
10581
|
console.warn(" \u26A0 No Cursor API key found \u2014 Cursor grader workers will be idle.");
|
|
@@ -10979,6 +10998,87 @@ var init_setupToken = __esm({
|
|
|
10979
10998
|
}
|
|
10980
10999
|
});
|
|
10981
11000
|
|
|
11001
|
+
// cli/local-cc/codexCloudSetup.ts
|
|
11002
|
+
var codexCloudSetup_exports = {};
|
|
11003
|
+
__export(codexCloudSetup_exports, {
|
|
11004
|
+
setupCodexCloud: () => setupCodexCloud
|
|
11005
|
+
});
|
|
11006
|
+
import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
|
|
11007
|
+
import { readFileSync as readFileSync9, mkdirSync as mkdirSync9, rmSync, existsSync as existsSync10 } from "fs";
|
|
11008
|
+
import { homedir as homedir9 } from "os";
|
|
11009
|
+
import { join as join9 } from "path";
|
|
11010
|
+
function findCodexBinary() {
|
|
11011
|
+
if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
|
|
11012
|
+
const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
|
|
11013
|
+
const p = (r.stdout || "").trim().split("\n")[0];
|
|
11014
|
+
return p || null;
|
|
11015
|
+
}
|
|
11016
|
+
function runCodexLogin(codexBin) {
|
|
11017
|
+
mkdirSync9(CODEX_CLOUD_HOME, { recursive: true, mode: 448 });
|
|
11018
|
+
return new Promise((resolve4, reject) => {
|
|
11019
|
+
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
11020
|
+
stdio: "inherit",
|
|
11021
|
+
env: { ...process.env, CODEX_HOME: CODEX_CLOUD_HOME }
|
|
11022
|
+
});
|
|
11023
|
+
proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
|
|
11024
|
+
proc.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`codex login exited with code ${code}`)));
|
|
11025
|
+
});
|
|
11026
|
+
}
|
|
11027
|
+
async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
11028
|
+
const codexBin = findCodexBinary();
|
|
11029
|
+
if (!codexBin) {
|
|
11030
|
+
return { ok: false, error: "Codex CLI not found on PATH. Install Codex (https://developers.openai.com/codex), then re-run `synkro install`. (Set SYNKRO_CODEX_BIN to override the binary path.)" };
|
|
11031
|
+
}
|
|
11032
|
+
onStatus?.("Opening your browser to authorize your Codex subscription for Synkro Cloud\u2026");
|
|
11033
|
+
try {
|
|
11034
|
+
await runCodexLogin(codexBin);
|
|
11035
|
+
} catch (e) {
|
|
11036
|
+
return { ok: false, error: `Codex login failed: ${e.message}` };
|
|
11037
|
+
}
|
|
11038
|
+
const authPath = join9(CODEX_CLOUD_HOME, "auth.json");
|
|
11039
|
+
if (!existsSync10(authPath)) {
|
|
11040
|
+
return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
|
|
11041
|
+
}
|
|
11042
|
+
let auth;
|
|
11043
|
+
try {
|
|
11044
|
+
auth = JSON.parse(readFileSync9(authPath, "utf-8"));
|
|
11045
|
+
} catch (e) {
|
|
11046
|
+
return { ok: false, error: `could not read codex auth.json: ${e.message}` };
|
|
11047
|
+
}
|
|
11048
|
+
if (!auth || typeof auth !== "object" || !auth.tokens?.refresh_token) {
|
|
11049
|
+
return { ok: false, error: "codex auth.json has no refresh token \u2014 cloud refresh would fail. Re-run login." };
|
|
11050
|
+
}
|
|
11051
|
+
onStatus?.("Connecting your Codex session to Synkro Cloud\u2026");
|
|
11052
|
+
let resp;
|
|
11053
|
+
try {
|
|
11054
|
+
resp = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/seed`, {
|
|
11055
|
+
method: "POST",
|
|
11056
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
|
|
11057
|
+
body: JSON.stringify({ auth })
|
|
11058
|
+
});
|
|
11059
|
+
} catch (e) {
|
|
11060
|
+
return { ok: false, error: `failed to reach Synkro API: ${e.message}` };
|
|
11061
|
+
}
|
|
11062
|
+
if (!resp.ok) {
|
|
11063
|
+
const detail = await resp.text().catch(() => "");
|
|
11064
|
+
return { ok: false, error: `seed rejected (${resp.status}): ${detail.slice(0, 200)}` };
|
|
11065
|
+
}
|
|
11066
|
+
try {
|
|
11067
|
+
rmSync(CODEX_CLOUD_HOME, { recursive: true, force: true });
|
|
11068
|
+
} catch {
|
|
11069
|
+
}
|
|
11070
|
+
onStatus?.("\u2713 Codex subscription connected to Synkro Cloud.");
|
|
11071
|
+
return { ok: true };
|
|
11072
|
+
}
|
|
11073
|
+
var SYNKRO_DIR4, CODEX_CLOUD_HOME;
|
|
11074
|
+
var init_codexCloudSetup = __esm({
|
|
11075
|
+
"cli/local-cc/codexCloudSetup.ts"() {
|
|
11076
|
+
"use strict";
|
|
11077
|
+
SYNKRO_DIR4 = join9(homedir9(), ".synkro");
|
|
11078
|
+
CODEX_CLOUD_HOME = join9(SYNKRO_DIR4, "codex-cloud-session");
|
|
11079
|
+
}
|
|
11080
|
+
});
|
|
11081
|
+
|
|
10982
11082
|
// cli/commands/setupGithub.ts
|
|
10983
11083
|
var setupGithub_exports = {};
|
|
10984
11084
|
__export(setupGithub_exports, {
|
|
@@ -10988,14 +11088,14 @@ __export(setupGithub_exports, {
|
|
|
10988
11088
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
10989
11089
|
import { stdin as input, stdout as output } from "process";
|
|
10990
11090
|
import { execSync as execSync5 } from "child_process";
|
|
10991
|
-
import { existsSync as
|
|
10992
|
-
import { homedir as
|
|
10993
|
-
import { join as
|
|
11091
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
11092
|
+
import { homedir as homedir10, platform as platform4 } from "os";
|
|
11093
|
+
import { join as join10 } from "path";
|
|
10994
11094
|
import { execFile as execFile2 } from "child_process";
|
|
10995
11095
|
function readConfig() {
|
|
10996
|
-
if (!
|
|
11096
|
+
if (!existsSync11(CONFIG_PATH)) return {};
|
|
10997
11097
|
const out = {};
|
|
10998
|
-
for (const line of
|
|
11098
|
+
for (const line of readFileSync10(CONFIG_PATH, "utf-8").split("\n")) {
|
|
10999
11099
|
const t = line.trim();
|
|
11000
11100
|
if (!t || t.startsWith("#")) continue;
|
|
11001
11101
|
const eq = t.indexOf("=");
|
|
@@ -11295,15 +11395,15 @@ Will push secrets to ${selected.length} repo(s):`);
|
|
|
11295
11395
|
console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);
|
|
11296
11396
|
console.log("Open a PR on any selected repo to trigger your first Synkro scan.");
|
|
11297
11397
|
}
|
|
11298
|
-
var
|
|
11398
|
+
var SYNKRO_DIR5, CONFIG_PATH;
|
|
11299
11399
|
var init_setupGithub = __esm({
|
|
11300
11400
|
"cli/commands/setupGithub.ts"() {
|
|
11301
11401
|
"use strict";
|
|
11302
11402
|
init_githubSetup();
|
|
11303
11403
|
init_stub();
|
|
11304
11404
|
init_setupToken();
|
|
11305
|
-
|
|
11306
|
-
CONFIG_PATH =
|
|
11405
|
+
SYNKRO_DIR5 = join10(homedir10(), ".synkro");
|
|
11406
|
+
CONFIG_PATH = join10(SYNKRO_DIR5, "config.env");
|
|
11307
11407
|
}
|
|
11308
11408
|
});
|
|
11309
11409
|
|
|
@@ -11322,9 +11422,9 @@ __export(install_exports, {
|
|
|
11322
11422
|
syncSkillFiles: () => syncSkillFiles,
|
|
11323
11423
|
writeHookScripts: () => writeHookScripts
|
|
11324
11424
|
});
|
|
11325
|
-
import { existsSync as
|
|
11326
|
-
import { homedir as
|
|
11327
|
-
import { join as
|
|
11425
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, chmodSync as chmodSync2, readFileSync as readFileSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync4, statSync } from "fs";
|
|
11426
|
+
import { homedir as homedir11 } from "os";
|
|
11427
|
+
import { join as join11 } from "path";
|
|
11328
11428
|
import { execSync as execSync6 } from "child_process";
|
|
11329
11429
|
import { createInterface as createInterface3 } from "readline";
|
|
11330
11430
|
import { createHash } from "crypto";
|
|
@@ -11332,7 +11432,7 @@ function resolvePersistedHookMode() {
|
|
|
11332
11432
|
if (process.env.SYNKRO_HOOK_MODE === "full") return "full";
|
|
11333
11433
|
if (process.env.SYNKRO_HOOK_MODE === "stub") return "stub";
|
|
11334
11434
|
try {
|
|
11335
|
-
const env =
|
|
11435
|
+
const env = readFileSync11(CONFIG_PATH2, "utf-8");
|
|
11336
11436
|
if (/^SYNKRO_HOOK_MODE=['"]?full['"]?\s*$/m.test(env)) return "full";
|
|
11337
11437
|
} catch {
|
|
11338
11438
|
}
|
|
@@ -11468,37 +11568,51 @@ coding patterns and the dashboard shows full history. (Y/n) `
|
|
|
11468
11568
|
rl.close();
|
|
11469
11569
|
}
|
|
11470
11570
|
}
|
|
11571
|
+
async function promptCodexCloudSetup() {
|
|
11572
|
+
if (!process.stdin.isTTY) return false;
|
|
11573
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
11574
|
+
return new Promise((resolve4) => {
|
|
11575
|
+
rl.question(
|
|
11576
|
+
"Connect a Codex (ChatGPT subscription) session for cloud grading? This opens a\nbrowser to authorize; your subscription is stored securely and refreshed for you. (y/N) ",
|
|
11577
|
+
(answer) => {
|
|
11578
|
+
rl.close();
|
|
11579
|
+
const a = answer.trim().toLowerCase();
|
|
11580
|
+
resolve4(a === "y" || a === "yes");
|
|
11581
|
+
}
|
|
11582
|
+
);
|
|
11583
|
+
});
|
|
11584
|
+
}
|
|
11471
11585
|
function ensureSynkroDir() {
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11586
|
+
mkdirSync10(SYNKRO_DIR6, { recursive: true });
|
|
11587
|
+
mkdirSync10(HOOKS_DIR, { recursive: true });
|
|
11588
|
+
mkdirSync10(BIN_DIR, { recursive: true });
|
|
11589
|
+
mkdirSync10(OFFSETS_DIR, { recursive: true });
|
|
11590
|
+
mkdirSync10(join11(SYNKRO_DIR6, "sessions"), { recursive: true });
|
|
11477
11591
|
}
|
|
11478
11592
|
function writeHookScripts(mode = "stub") {
|
|
11479
|
-
const installExtractCorePath =
|
|
11480
|
-
const bashScriptPath =
|
|
11481
|
-
const bashFollowupScriptPath =
|
|
11482
|
-
const editPrecheckScriptPath =
|
|
11483
|
-
const cwePrecheckScriptPath =
|
|
11484
|
-
const cvePrecheckScriptPath =
|
|
11485
|
-
const planJudgeScriptPath =
|
|
11486
|
-
const agentJudgeScriptPath =
|
|
11487
|
-
const stopSummaryScriptPath =
|
|
11488
|
-
const sessionStartScriptPath =
|
|
11489
|
-
const transcriptSyncScriptPath =
|
|
11490
|
-
const userPromptSubmitScriptPath =
|
|
11491
|
-
const commonScriptPath =
|
|
11492
|
-
const commonBashScriptPath =
|
|
11493
|
-
const installScanScriptPath =
|
|
11494
|
-
const cursorBashJudgePath =
|
|
11495
|
-
const cursorEditCapturePath =
|
|
11496
|
-
const cursorAgentCapturePath =
|
|
11497
|
-
const mcpStdioProxyPath =
|
|
11498
|
-
const taskActivateIntentScriptPath =
|
|
11499
|
-
const mcpGateScriptPath =
|
|
11593
|
+
const installExtractCorePath = join11(HOOKS_DIR, "installExtractCore.ts");
|
|
11594
|
+
const bashScriptPath = join11(HOOKS_DIR, "cc-bash-judge.ts");
|
|
11595
|
+
const bashFollowupScriptPath = join11(HOOKS_DIR, "cc-bash-followup.ts");
|
|
11596
|
+
const editPrecheckScriptPath = join11(HOOKS_DIR, "cc-edit-precheck.ts");
|
|
11597
|
+
const cwePrecheckScriptPath = join11(HOOKS_DIR, "cc-cwe-precheck.ts");
|
|
11598
|
+
const cvePrecheckScriptPath = join11(HOOKS_DIR, "cc-cve-precheck.ts");
|
|
11599
|
+
const planJudgeScriptPath = join11(HOOKS_DIR, "cc-plan-judge.ts");
|
|
11600
|
+
const agentJudgeScriptPath = join11(HOOKS_DIR, "cc-agent-judge.ts");
|
|
11601
|
+
const stopSummaryScriptPath = join11(HOOKS_DIR, "cc-stop-summary.ts");
|
|
11602
|
+
const sessionStartScriptPath = join11(HOOKS_DIR, "cc-session-start.ts");
|
|
11603
|
+
const transcriptSyncScriptPath = join11(HOOKS_DIR, "cc-transcript-sync.ts");
|
|
11604
|
+
const userPromptSubmitScriptPath = join11(HOOKS_DIR, "cc-user-prompt-submit.ts");
|
|
11605
|
+
const commonScriptPath = join11(HOOKS_DIR, "_synkro-common.ts");
|
|
11606
|
+
const commonBashScriptPath = join11(HOOKS_DIR, "_synkro-common.sh");
|
|
11607
|
+
const installScanScriptPath = join11(HOOKS_DIR, "cc-install-scan.ts");
|
|
11608
|
+
const cursorBashJudgePath = join11(HOOKS_DIR, "cursor-bash-judge.ts");
|
|
11609
|
+
const cursorEditCapturePath = join11(HOOKS_DIR, "cursor-edit-capture.ts");
|
|
11610
|
+
const cursorAgentCapturePath = join11(HOOKS_DIR, "cursor-agent-capture.ts");
|
|
11611
|
+
const mcpStdioProxyPath = join11(HOOKS_DIR, "mcp-stdio-proxy.ts");
|
|
11612
|
+
const taskActivateIntentScriptPath = join11(HOOKS_DIR, "cc-task-activate-intent.ts");
|
|
11613
|
+
const mcpGateScriptPath = join11(HOOKS_DIR, "cc-mcp-gate.ts");
|
|
11500
11614
|
if (mode === "stub") {
|
|
11501
|
-
const stubCommonPath2 =
|
|
11615
|
+
const stubCommonPath2 = join11(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
11502
11616
|
const stubFiles = [
|
|
11503
11617
|
[stubCommonPath2, STUB_COMMON_TS],
|
|
11504
11618
|
[bashScriptPath, STUB_BASH_JUDGE_TS],
|
|
@@ -11527,7 +11641,7 @@ function writeHookScripts(mode = "stub") {
|
|
|
11527
11641
|
chmodSync2(mcpStdioProxyPath, 493);
|
|
11528
11642
|
for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
|
|
11529
11643
|
try {
|
|
11530
|
-
unlinkSync4(
|
|
11644
|
+
unlinkSync4(join11(HOOKS_DIR, stale));
|
|
11531
11645
|
} catch {
|
|
11532
11646
|
}
|
|
11533
11647
|
}
|
|
@@ -11571,7 +11685,7 @@ function writeHookScripts(mode = "stub") {
|
|
|
11571
11685
|
writeFileSync8(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
|
|
11572
11686
|
writeFileSync8(installExtractCorePath, "/**\n * Deterministic install-command extraction \u2014 no LLM, no network.\n * Shared by the API pkg-scan route and the hook scripts (copied to ~/.synkro/hooks/).\n */\nimport { parse as shellParse } from 'shell-quote';\n\nexport interface DeterministicPkgRequest {\n name: string;\n version: string;\n ecosystem: string;\n}\n\ninterface RawInstall {\n ecosystem: string;\n name: string;\n versionSpec: string | null;\n source: 'registry' | 'git' | 'local' | 'url' | 'unknown';\n}\n\nconst SEPARATOR_OPS = new Set(['&&', '||', ';', '|', '&', '\\n']);\n\n/** Split a shell command into command segments (each an argv string array). */\nexport function segmentCommand(command: string): string[][] {\n let tokens: unknown[];\n try {\n tokens = shellParse(command) as unknown[];\n } catch {\n return [command.split(/\\s+/).filter(Boolean)];\n }\n const segments: string[][] = [];\n let current: string[] = [];\n for (const tok of tokens) {\n if (typeof tok === 'string') {\n current.push(tok);\n continue;\n }\n if (tok && typeof tok === 'object') {\n const op = (tok as { op?: string }).op;\n const pattern = (tok as { pattern?: string }).pattern;\n if (op && SEPARATOR_OPS.has(op)) {\n if (current.length) segments.push(current);\n current = [];\n } else if (typeof pattern === 'string') {\n current.push(pattern);\n }\n }\n }\n if (current.length) segments.push(current);\n return segments;\n}\n\nconst PM_TABLE: Record<string, { subs: Set<string>; ecosystem: string }> = {\n npm: { subs: new Set(['install', 'i', 'add', 'ci']), ecosystem: 'npm' },\n pnpm: { subs: new Set(['add', 'install', 'i', 'dlx']), ecosystem: 'npm' },\n yarn: { subs: new Set(['add', 'install']), ecosystem: 'npm' },\n bun: { subs: new Set(['add', 'install', 'i']), ecosystem: 'npm' },\n pip: { subs: new Set(['install']), ecosystem: 'PyPI' },\n pip3: { subs: new Set(['install']), ecosystem: 'PyPI' },\n cargo: { subs: new Set(['add', 'install']), ecosystem: 'crates.io' },\n go: { subs: new Set(['get', 'install']), ecosystem: 'Go' },\n gem: { subs: new Set(['install']), ecosystem: 'RubyGems' },\n composer: { subs: new Set(['require']), ecosystem: 'Packagist' },\n};\nconst SYSTEM_PMS = new Set(['apt', 'apt-get', 'apk', 'brew', 'dnf', 'yum', 'pacman']);\nconst SYSTEM_SUBS = new Set(['install', 'add']);\n\nconst WRAPPERS = new Set(['sudo', 'doas', 'command', 'env', 'xargs', 'nice', 'time']);\nconst VALUE_FLAGS = new Set([\n '--filter', '-F', '-C', '--dir', '--prefix', '--registry', '--tag', '--features',\n '-v', '--version', '--index-url', '--extra-index-url', '--target', '-t',\n]);\n\nfunction basename(p: string): string {\n const i = p.lastIndexOf('/');\n return i >= 0 ? p.slice(i + 1) : p;\n}\n\nfunction stripPrefixes(argv: string[]): string[] {\n let i = 0;\n while (i < argv.length) {\n const t = argv[i];\n if (WRAPPERS.has(basename(t).toLowerCase())) { i++; continue; }\n if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(t)) { i++; continue; }\n break;\n }\n return argv.slice(i);\n}\n\nfunction looksLikePath(tok: string): boolean {\n return tok === '.' || tok === '..' || /^\\.{0,2}\\//.test(tok) || tok.startsWith('~/') || tok.startsWith('file:');\n}\n\n/** Shell redirect fragments (e.g. `2>&1` \u2192 argv `2`, `1`) \u2014 not package names. */\nfunction isRedirectFragment(tok: string): boolean {\n if (/^\\d+$/.test(tok)) return true;\n if (/^[<>]|[<>]$/.test(tok)) return true;\n if (tok === '&' || tok === '|') return true;\n if (/^\\d*[<>]/.test(tok)) return true;\n return false;\n}\n\nfunction parsePackageToken(tok: string, ecosystem: string): RawInstall | null {\n if (/^(https?:)?\\/\\//.test(tok) || tok.startsWith('git+') || tok.startsWith('git:')) {\n return { ecosystem, name: tok, versionSpec: null, source: tok.includes('git') ? 'git' : 'url' };\n }\n if (looksLikePath(tok)) {\n return { ecosystem, name: basename(tok.replace(/\\/+$/, '')) || tok, versionSpec: null, source: 'local' };\n }\n if (ecosystem === 'PyPI') {\n const noExtras = tok.replace(/\\[[^\\]]*\\]/g, '');\n const m = noExtras.match(/^([A-Za-z0-9_.-]+)\\s*([=~!<>].*)?$/);\n if (!m) return null;\n return { ecosystem, name: m[1], versionSpec: m[2] ? m[2].trim() : null, source: 'registry' };\n }\n if (ecosystem === 'Packagist') {\n // composer uses vendor/package:version-constraint (e.g. monolog/monolog:1.0.0).\n // Split on the first ':' after the vendor/ slash; never on the '/'.\n const ci = tok.indexOf(':');\n if (ci > 0) return { ecosystem, name: tok.slice(0, ci), versionSpec: tok.slice(ci + 1) || null, source: 'registry' };\n return { ecosystem, name: tok, versionSpec: null, source: 'registry' };\n }\n const at = tok.lastIndexOf('@');\n if (at > 0) {\n return { ecosystem, name: tok.slice(0, at), versionSpec: tok.slice(at + 1) || null, source: 'registry' };\n }\n return { ecosystem, name: tok, versionSpec: null, source: 'registry' };\n}\n\n/** Deterministic extraction for a single command segment. */\nexport function extractSegment(rawArgv: string[]): RawInstall[] {\n let argv = stripPrefixes(rawArgv);\n if (argv.length < 2) return [];\n let bin = basename(argv[0]).toLowerCase();\n\n if (bin === 'uv' && argv[1] === 'pip') { argv = argv.slice(1); bin = 'pip'; }\n if ((bin === 'python' || bin === 'python3') && argv.includes('-m')) {\n const mi = argv.indexOf('-m');\n if (argv[mi + 1] === 'pip') { argv = ['pip', ...argv.slice(mi + 2)]; bin = 'pip'; }\n }\n\n const isSystem = SYSTEM_PMS.has(bin);\n const entry = PM_TABLE[bin];\n if (!entry && !isSystem) return [];\n const ecosystem = entry ? entry.ecosystem : 'system';\n const subs = entry ? entry.subs : SYSTEM_SUBS;\n\n let subIdx = -1;\n for (let i = 1; i < argv.length; i++) {\n if (subs.has(argv[i].toLowerCase())) { subIdx = i; break; }\n }\n if (subIdx === -1) return [];\n\n const installs: RawInstall[] = [];\n // gem pins the version with a separate `-v`/`--version` flag rather than an\n // inline spec; capture it and apply to the package(s) in this segment.\n let flagVersion: string | null = null;\n for (let i = subIdx + 1; i < argv.length; i++) {\n const tok = argv[i];\n if (isRedirectFragment(tok)) break;\n if (tok.startsWith('-')) {\n const lower = tok.toLowerCase();\n if (ecosystem === 'RubyGems' && (lower === '-v' || lower === '--version')) {\n flagVersion = (argv[i + 1] || '').replace(/^[v=\\s]+/, '') || null;\n i++;\n continue;\n }\n if (VALUE_FLAGS.has(tok)) i++;\n continue;\n }\n const parsed = parsePackageToken(tok, ecosystem);\n if (parsed) installs.push(parsed);\n }\n if (flagVersion) {\n for (const ins of installs) {\n if (ins.source === 'registry' && !ins.versionSpec) ins.versionSpec = flagVersion;\n }\n }\n return installs;\n}\n\nconst ECO_TO_OSV: Record<string, string | null> = {\n npm: 'npm',\n pypi: 'PyPI', PyPI: 'PyPI',\n cargo: 'crates.io', 'crates.io': 'crates.io',\n go: 'Go', Go: 'Go',\n rubygems: 'RubyGems', RubyGems: 'RubyGems',\n packagist: 'Packagist', Packagist: 'Packagist',\n maven: 'Maven', Maven: 'Maven',\n nuget: 'NuGet', NuGet: 'NuGet',\n apt: null, brew: null, system: null, other: null,\n};\n\nfunction normalizeName(name: string, osvEco: string): string {\n const n = name.trim();\n if (osvEco === 'npm') return n.toLowerCase();\n if (osvEco === 'PyPI') return n.toLowerCase().replace(/[-_.]+/g, '-');\n return n;\n}\n\nfunction concretePin(spec: string | null): string | null {\n if (!spec) return null;\n const c = spec.trim().replace(/^[v=\\s]+/, '');\n if (c.toLowerCase() === 'latest' || c === '') return null;\n if (/[\\^~><|*\\sx]/i.test(c)) return null;\n return /^\\d[\\w.\\-+]*$/.test(c) ? c : null;\n}\n\nconst PKG_JSON_DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];\n\nfunction safeParseObject(text: string): Record<string, any> | null {\n try {\n const v = JSON.parse(text);\n return v && typeof v === 'object' && !Array.isArray(v) ? v : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Diff two package.json contents and return the registry packages that are\n * newly added or whose version spec changed in the new content. The caller\n * scans these against the vuln DB before letting the edit land \u2014 so a bare\n * `pnpm install` afterwards has nothing left to vet. Non-registry sources\n * (file:, link:, workspace:, git, http, relative paths) are skipped.\n */\nexport function extractPackageJsonDelta(oldText: string, newText: string): DeterministicPkgRequest[] {\n const newJson = safeParseObject(newText);\n if (!newJson) return [];\n const oldJson = safeParseObject(oldText) || {};\n\n const out = new Map<string, DeterministicPkgRequest>();\n for (const field of PKG_JSON_DEP_FIELDS) {\n const oldDeps = (oldJson[field] && typeof oldJson[field] === 'object') ? oldJson[field] : {};\n const newDeps = (newJson[field] && typeof newJson[field] === 'object') ? newJson[field] : {};\n for (const [rawName, version] of Object.entries(newDeps)) {\n if (typeof version !== 'string') continue;\n if (oldDeps[rawName] === version) continue;\n const spec = version.trim();\n if (\n spec.startsWith('file:') || spec.startsWith('link:') ||\n spec.startsWith('http') || spec.startsWith('git') ||\n spec.startsWith('workspace:') || spec.startsWith('catalog:') ||\n spec.startsWith('npm:') ||\n spec.startsWith('./') || spec.startsWith('../') ||\n spec === '' || spec === '*'\n ) continue;\n const name = rawName.toLowerCase();\n out.set(name, {\n name,\n version: concretePin(spec) ?? '*',\n ecosystem: 'npm',\n });\n }\n }\n return [...out.values()];\n}\n\n/**\n * Parse registry installs from a shell command without LLM/network.\n * Unpinned versions use '*' so OSV scans the full advisory history.\n */\nexport function extractDeterministicPkgRequests(command: string): DeterministicPkgRequest[] {\n const merged = new Map<string, DeterministicPkgRequest>();\n for (const r of segmentCommand(command).flatMap(extractSegment)) {\n if (r.source !== 'registry') continue;\n const osvEco = ECO_TO_OSV[r.ecosystem] ?? ECO_TO_OSV[r.ecosystem.toLowerCase()] ?? null;\n if (!osvEco) continue;\n const name = normalizeName(r.name, osvEco);\n if (!name) continue;\n const key = osvEco + '|' + name.toLowerCase();\n const version = concretePin(r.versionSpec) ?? '*';\n const prev = merged.get(key);\n if (!prev || (prev.version === '*' && version !== '*')) {\n merged.set(key, { name, version, ecosystem: osvEco });\n }\n }\n return [...merged.values()];\n}\n", "utf-8");
|
|
11573
11687
|
writeFileSync8(taskActivateIntentScriptPath, STUB_TASK_ACTIVATE_INTENT_TS, "utf-8");
|
|
11574
|
-
const stubCommonPath =
|
|
11688
|
+
const stubCommonPath = join11(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
11575
11689
|
writeFileSync8(stubCommonPath, STUB_COMMON_TS, "utf-8");
|
|
11576
11690
|
chmodSync2(stubCommonPath, 493);
|
|
11577
11691
|
writeFileSync8(mcpGateScriptPath, STUB_MCP_GATE_TS, "utf-8");
|
|
@@ -11625,11 +11739,11 @@ function shellQuoteSingle(value) {
|
|
|
11625
11739
|
}
|
|
11626
11740
|
function resolveSynkroBundle() {
|
|
11627
11741
|
const scriptPath = process.argv[1];
|
|
11628
|
-
if (scriptPath &&
|
|
11742
|
+
if (scriptPath && existsSync12(scriptPath)) return scriptPath;
|
|
11629
11743
|
return null;
|
|
11630
11744
|
}
|
|
11631
11745
|
function writeConfigEnv(opts) {
|
|
11632
|
-
const credsPath =
|
|
11746
|
+
const credsPath = join11(SYNKRO_DIR6, "credentials.json");
|
|
11633
11747
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
11634
11748
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
11635
11749
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -11645,7 +11759,7 @@ function writeConfigEnv(opts) {
|
|
|
11645
11759
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11646
11760
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11647
11761
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11648
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.7.
|
|
11762
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.7.27")}`
|
|
11649
11763
|
];
|
|
11650
11764
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11651
11765
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -11678,7 +11792,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
11678
11792
|
assertGatewayAllowed(gatewayUrl);
|
|
11679
11793
|
let stored = "";
|
|
11680
11794
|
try {
|
|
11681
|
-
stored =
|
|
11795
|
+
stored = readFileSync11(CLOUD_JWT_PATH, "utf-8").trim();
|
|
11682
11796
|
} catch {
|
|
11683
11797
|
}
|
|
11684
11798
|
if (stored && !jwtExpired(stored)) return stored;
|
|
@@ -11737,7 +11851,7 @@ async function provisionCloudContainer(opts) {
|
|
|
11737
11851
|
let cursorApiKey = "";
|
|
11738
11852
|
if (cursorTotal > 0) {
|
|
11739
11853
|
try {
|
|
11740
|
-
cursorApiKey =
|
|
11854
|
+
cursorApiKey = readFileSync11(join11(SYNKRO_DIR6, "cursor-creds", "api-key"), "utf-8").trim();
|
|
11741
11855
|
} catch {
|
|
11742
11856
|
}
|
|
11743
11857
|
}
|
|
@@ -11894,8 +12008,8 @@ async function verifyCloudGrader(jwt2) {
|
|
|
11894
12008
|
}
|
|
11895
12009
|
function readPersistedDeployLocation() {
|
|
11896
12010
|
try {
|
|
11897
|
-
if (
|
|
11898
|
-
const m =
|
|
12011
|
+
if (existsSync12(CONFIG_PATH2)) {
|
|
12012
|
+
const m = readFileSync11(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
|
|
11899
12013
|
if (m?.[1] === "cloud") return "cloud";
|
|
11900
12014
|
}
|
|
11901
12015
|
} catch {
|
|
@@ -11903,8 +12017,8 @@ function readPersistedDeployLocation() {
|
|
|
11903
12017
|
return "local";
|
|
11904
12018
|
}
|
|
11905
12019
|
function updateConfigEnvLocation(location) {
|
|
11906
|
-
if (!
|
|
11907
|
-
let env =
|
|
12020
|
+
if (!existsSync12(CONFIG_PATH2)) return;
|
|
12021
|
+
let env = readFileSync11(CONFIG_PATH2, "utf-8");
|
|
11908
12022
|
const set = (k, v) => {
|
|
11909
12023
|
const re = new RegExp(`^${k}=.*$`, "m");
|
|
11910
12024
|
const line = `${k}='${v}'`;
|
|
@@ -11919,7 +12033,7 @@ async function applyMcpConfig(opts) {
|
|
|
11919
12033
|
if (!opts.hasClaudeCode && !opts.hasCursor) return;
|
|
11920
12034
|
let mcpJwt = "";
|
|
11921
12035
|
try {
|
|
11922
|
-
mcpJwt =
|
|
12036
|
+
mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
11923
12037
|
} catch {
|
|
11924
12038
|
}
|
|
11925
12039
|
if (!mcpJwt) {
|
|
@@ -12037,8 +12151,8 @@ function resolveDeploymentMode() {
|
|
|
12037
12151
|
const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
|
|
12038
12152
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
12039
12153
|
try {
|
|
12040
|
-
if (
|
|
12041
|
-
const m =
|
|
12154
|
+
if (existsSync12(CONFIG_PATH2)) {
|
|
12155
|
+
const m = readFileSync11(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
|
|
12042
12156
|
const val = m?.[1]?.toLowerCase();
|
|
12043
12157
|
if (val === "bare-host" || val === "docker") return val;
|
|
12044
12158
|
}
|
|
@@ -12065,16 +12179,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
12065
12179
|
meta.cc_version = execSync6("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
12066
12180
|
} catch {
|
|
12067
12181
|
}
|
|
12068
|
-
const claudeDir =
|
|
12182
|
+
const claudeDir = join11(homedir11(), ".claude");
|
|
12069
12183
|
try {
|
|
12070
|
-
const settings = JSON.parse(
|
|
12184
|
+
const settings = JSON.parse(readFileSync11(join11(claudeDir, "settings.json"), "utf-8"));
|
|
12071
12185
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
12072
12186
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
12073
12187
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
12074
12188
|
} catch {
|
|
12075
12189
|
}
|
|
12076
12190
|
try {
|
|
12077
|
-
const mcpCache = JSON.parse(
|
|
12191
|
+
const mcpCache = JSON.parse(readFileSync11(join11(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
12078
12192
|
const mcpNames = Object.keys(mcpCache);
|
|
12079
12193
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
12080
12194
|
} catch {
|
|
@@ -12086,10 +12200,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
12086
12200
|
} catch {
|
|
12087
12201
|
}
|
|
12088
12202
|
try {
|
|
12089
|
-
const sessionsDir =
|
|
12203
|
+
const sessionsDir = join11(claudeDir, "sessions");
|
|
12090
12204
|
const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
12091
12205
|
for (const f of files) {
|
|
12092
|
-
const s = JSON.parse(
|
|
12206
|
+
const s = JSON.parse(readFileSync11(join11(sessionsDir, f), "utf-8"));
|
|
12093
12207
|
if (s.version) {
|
|
12094
12208
|
meta.cc_version = meta.cc_version || s.version;
|
|
12095
12209
|
break;
|
|
@@ -12277,9 +12391,9 @@ async function installCommand(opts = {}) {
|
|
|
12277
12391
|
const scripts = writeHookScripts(hookMode);
|
|
12278
12392
|
console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
|
|
12279
12393
|
for (const mode of ["edit", "bash"]) {
|
|
12280
|
-
const pidFile =
|
|
12394
|
+
const pidFile = join11(SYNKRO_DIR6, "daemon", mode, "daemon.pid");
|
|
12281
12395
|
try {
|
|
12282
|
-
const pid = parseInt(
|
|
12396
|
+
const pid = parseInt(readFileSync11(pidFile, "utf-8").trim(), 10);
|
|
12283
12397
|
if (pid > 0) {
|
|
12284
12398
|
process.kill(pid, "SIGTERM");
|
|
12285
12399
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -12362,7 +12476,7 @@ async function installCommand(opts = {}) {
|
|
|
12362
12476
|
if (mintResp.ok) {
|
|
12363
12477
|
const minted = await mintResp.json();
|
|
12364
12478
|
mcpJwt = minted.token;
|
|
12365
|
-
writeFileSync8(
|
|
12479
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), mcpJwt + "\n", { mode: 384 });
|
|
12366
12480
|
} else {
|
|
12367
12481
|
console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
|
|
12368
12482
|
}
|
|
@@ -12390,7 +12504,7 @@ async function installCommand(opts = {}) {
|
|
|
12390
12504
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
12391
12505
|
}
|
|
12392
12506
|
const minted = await mintResp.json();
|
|
12393
|
-
writeFileSync8(
|
|
12507
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
12394
12508
|
const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
12395
12509
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
12396
12510
|
console.log(` url: ${mcp.url}`);
|
|
@@ -12407,8 +12521,8 @@ async function installCommand(opts = {}) {
|
|
|
12407
12521
|
if (hasCursor && !opts.noMcp) {
|
|
12408
12522
|
try {
|
|
12409
12523
|
if (useLocalMcp) {
|
|
12410
|
-
const jwtPath =
|
|
12411
|
-
if (!
|
|
12524
|
+
const jwtPath = join11(SYNKRO_DIR6, ".mcp-jwt");
|
|
12525
|
+
if (!existsSync12(jwtPath)) {
|
|
12412
12526
|
const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
|
|
12413
12527
|
method: "POST",
|
|
12414
12528
|
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
|
|
@@ -12436,7 +12550,7 @@ async function installCommand(opts = {}) {
|
|
|
12436
12550
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
12437
12551
|
}
|
|
12438
12552
|
const minted = await mintResp.json();
|
|
12439
|
-
writeFileSync8(
|
|
12553
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
12440
12554
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
12441
12555
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
12442
12556
|
console.log(` url: ${mcp.url}`);
|
|
@@ -12518,7 +12632,7 @@ async function installCommand(opts = {}) {
|
|
|
12518
12632
|
const ready = await waitForContainerReady(6e4);
|
|
12519
12633
|
if (ready) {
|
|
12520
12634
|
console.log(" \u2713 container ready");
|
|
12521
|
-
const mcpJwt =
|
|
12635
|
+
const mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12522
12636
|
try {
|
|
12523
12637
|
const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
|
|
12524
12638
|
method: "POST",
|
|
@@ -12553,6 +12667,11 @@ async function installCommand(opts = {}) {
|
|
|
12553
12667
|
if (hasCursor) await promptCursorApiKey(opts);
|
|
12554
12668
|
await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
|
|
12555
12669
|
cloudGradeOk = await verifyCloudGrader(token);
|
|
12670
|
+
if (await promptCodexCloudSetup()) {
|
|
12671
|
+
const { setupCodexCloud: setupCodexCloud2 } = await Promise.resolve().then(() => (init_codexCloudSetup(), codexCloudSetup_exports));
|
|
12672
|
+
const res = await setupCodexCloud2(gatewayUrl, token, (m) => console.log(` ${m}`));
|
|
12673
|
+
if (!res.ok) console.warn(` \u26A0 Codex cloud setup skipped: ${res.error}`);
|
|
12674
|
+
}
|
|
12556
12675
|
}
|
|
12557
12676
|
if (transcriptConsent) {
|
|
12558
12677
|
const repo = detectGitRepo2();
|
|
@@ -12561,7 +12680,7 @@ async function installCommand(opts = {}) {
|
|
|
12561
12680
|
try {
|
|
12562
12681
|
let mcpToken = "";
|
|
12563
12682
|
try {
|
|
12564
|
-
mcpToken =
|
|
12683
|
+
mcpToken = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12565
12684
|
} catch {
|
|
12566
12685
|
}
|
|
12567
12686
|
if (mcpToken) {
|
|
@@ -12697,8 +12816,8 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
12697
12816
|
try {
|
|
12698
12817
|
const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12699
12818
|
if (!root) return;
|
|
12700
|
-
if (root ===
|
|
12701
|
-
const fp =
|
|
12819
|
+
if (root === homedir11()) return;
|
|
12820
|
+
const fp = join11(root, "synkro.toml");
|
|
12702
12821
|
let hasFile = false;
|
|
12703
12822
|
try {
|
|
12704
12823
|
hasFile = statSync(fp).isFile();
|
|
@@ -12743,9 +12862,9 @@ function readFullSynkroFile() {
|
|
|
12743
12862
|
try {
|
|
12744
12863
|
const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12745
12864
|
if (!root) return null;
|
|
12746
|
-
const fp =
|
|
12747
|
-
if (!
|
|
12748
|
-
const parsed = parseSynkroToml2(
|
|
12865
|
+
const fp = join11(root, "synkro.toml");
|
|
12866
|
+
if (!existsSync12(fp)) return null;
|
|
12867
|
+
const parsed = parseSynkroToml2(readFileSync11(fp, "utf-8"));
|
|
12749
12868
|
const valid = ["claude-code", "cursor"];
|
|
12750
12869
|
const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
|
|
12751
12870
|
const resolved = resolveGraderPool(parsed);
|
|
@@ -12783,7 +12902,7 @@ function reconcileHarness() {
|
|
|
12783
12902
|
console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
|
|
12784
12903
|
const scripts = writeHookScripts(resolvePersistedHookMode());
|
|
12785
12904
|
console.log("Wrote hook scripts to ~/.synkro/hooks/");
|
|
12786
|
-
const ccSettings =
|
|
12905
|
+
const ccSettings = join11(homedir11(), ".claude", "settings.json");
|
|
12787
12906
|
if (wantCC) {
|
|
12788
12907
|
installCCHooks(ccSettings, {
|
|
12789
12908
|
bashJudgeScriptPath: scripts.bashScript,
|
|
@@ -12803,7 +12922,7 @@ function reconcileHarness() {
|
|
|
12803
12922
|
});
|
|
12804
12923
|
console.log(" \u2713 Claude Code hooks registered");
|
|
12805
12924
|
try {
|
|
12806
|
-
const mcpJwt =
|
|
12925
|
+
const mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12807
12926
|
if (mcpJwt) {
|
|
12808
12927
|
installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt, local: true });
|
|
12809
12928
|
console.log(" \u2713 Claude Code MCP registered");
|
|
@@ -12815,7 +12934,7 @@ function reconcileHarness() {
|
|
|
12815
12934
|
if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
|
|
12816
12935
|
if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
|
|
12817
12936
|
}
|
|
12818
|
-
const cursorHooks =
|
|
12937
|
+
const cursorHooks = join11(homedir11(), ".cursor", "hooks.json");
|
|
12819
12938
|
if (wantCursor) {
|
|
12820
12939
|
installCursorHooks(cursorHooks, {
|
|
12821
12940
|
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
@@ -12897,7 +13016,7 @@ async function syncSkillFiles() {
|
|
|
12897
13016
|
if (resolved.length === 0) return;
|
|
12898
13017
|
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
12899
13018
|
const tasks = resolved.map((fp) => {
|
|
12900
|
-
const content =
|
|
13019
|
+
const content = readFileSync11(fp, "utf-8");
|
|
12901
13020
|
const source = `skill:${fp.split("/").pop()}`;
|
|
12902
13021
|
if (!content.trim()) {
|
|
12903
13022
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
@@ -12915,15 +13034,15 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
|
12915
13034
|
const roots = [];
|
|
12916
13035
|
const add = (p) => {
|
|
12917
13036
|
try {
|
|
12918
|
-
if (p &&
|
|
13037
|
+
if (p && existsSync12(p) && statSync(p).isDirectory()) roots.push(p);
|
|
12919
13038
|
} catch {
|
|
12920
13039
|
}
|
|
12921
13040
|
};
|
|
12922
|
-
add(
|
|
12923
|
-
add(
|
|
13041
|
+
add(join11(homedir11(), ".claude", "skills"));
|
|
13042
|
+
add(join11(homedir11(), ".agents", "skills"));
|
|
12924
13043
|
if (repoRoot) {
|
|
12925
|
-
add(
|
|
12926
|
-
add(
|
|
13044
|
+
add(join11(repoRoot, ".claude", "skills"));
|
|
13045
|
+
add(join11(repoRoot, ".agents", "skills"));
|
|
12927
13046
|
}
|
|
12928
13047
|
const out = [];
|
|
12929
13048
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12934,7 +13053,7 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
|
12934
13053
|
try {
|
|
12935
13054
|
const st = statSync(file);
|
|
12936
13055
|
if (!st.isFile() || st.size > 2e5) return;
|
|
12937
|
-
content =
|
|
13056
|
+
content = readFileSync11(file, "utf-8");
|
|
12938
13057
|
} catch {
|
|
12939
13058
|
return;
|
|
12940
13059
|
}
|
|
@@ -12953,7 +13072,7 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
|
12953
13072
|
}
|
|
12954
13073
|
for (const entry of entries) {
|
|
12955
13074
|
if (entry.startsWith(".")) continue;
|
|
12956
|
-
const full =
|
|
13075
|
+
const full = join11(root, entry);
|
|
12957
13076
|
let st;
|
|
12958
13077
|
try {
|
|
12959
13078
|
st = statSync(full);
|
|
@@ -12961,8 +13080,8 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
|
12961
13080
|
continue;
|
|
12962
13081
|
}
|
|
12963
13082
|
if (st.isDirectory()) {
|
|
12964
|
-
const skillMd =
|
|
12965
|
-
if (
|
|
13083
|
+
const skillMd = join11(full, "SKILL.md");
|
|
13084
|
+
if (existsSync12(skillMd)) consider(skillMd, entry);
|
|
12966
13085
|
} else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
|
|
12967
13086
|
consider(full, entry.replace(/\.mdx?$/i, ""));
|
|
12968
13087
|
}
|
|
@@ -13000,7 +13119,7 @@ async function discoverAndIngestSkills() {
|
|
|
13000
13119
|
if (sf?.skills?.length) {
|
|
13001
13120
|
for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
|
|
13002
13121
|
try {
|
|
13003
|
-
excludeHashes.add(createHash("sha256").update(
|
|
13122
|
+
excludeHashes.add(createHash("sha256").update(readFileSync11(fp, "utf-8")).digest("hex"));
|
|
13004
13123
|
} catch {
|
|
13005
13124
|
}
|
|
13006
13125
|
}
|
|
@@ -13010,7 +13129,7 @@ async function discoverAndIngestSkills() {
|
|
|
13010
13129
|
const setHash = discoverySetHash(found);
|
|
13011
13130
|
let prev = "";
|
|
13012
13131
|
try {
|
|
13013
|
-
prev =
|
|
13132
|
+
prev = readFileSync11(SKILLS_DISCOVERED_PATH, "utf-8").trim();
|
|
13014
13133
|
} catch {
|
|
13015
13134
|
}
|
|
13016
13135
|
if (prev === setHash) return;
|
|
@@ -13051,17 +13170,17 @@ function detectGitRepo2() {
|
|
|
13051
13170
|
function getClaudeProjectsFolder() {
|
|
13052
13171
|
const cwd = process.cwd();
|
|
13053
13172
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
13054
|
-
const projectsDir =
|
|
13055
|
-
return
|
|
13173
|
+
const projectsDir = join11(homedir11(), ".claude", "projects", sanitized);
|
|
13174
|
+
return existsSync12(projectsDir) ? projectsDir : null;
|
|
13056
13175
|
}
|
|
13057
13176
|
function extractSessionInsights(projectsDir) {
|
|
13058
13177
|
const insights = [];
|
|
13059
13178
|
const files = readdirSync3(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
13060
13179
|
for (const file of files) {
|
|
13061
13180
|
const sessionId = file.replace(".jsonl", "");
|
|
13062
|
-
const filePath =
|
|
13181
|
+
const filePath = join11(projectsDir, file);
|
|
13063
13182
|
try {
|
|
13064
|
-
const content =
|
|
13183
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13065
13184
|
const lines = content.split("\n").filter(Boolean);
|
|
13066
13185
|
for (let i = 0; i < lines.length; i++) {
|
|
13067
13186
|
try {
|
|
@@ -13140,14 +13259,14 @@ function cursorProjectSlug(workspaceRoot) {
|
|
|
13140
13259
|
return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
13141
13260
|
}
|
|
13142
13261
|
function getCursorTranscriptsDir() {
|
|
13143
|
-
const dir =
|
|
13144
|
-
return
|
|
13262
|
+
const dir = join11(homedir11(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
|
|
13263
|
+
return existsSync12(dir) ? dir : null;
|
|
13145
13264
|
}
|
|
13146
13265
|
function isSafeConvId(id) {
|
|
13147
13266
|
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
13148
13267
|
}
|
|
13149
13268
|
function parseCursorTranscriptFile(filePath) {
|
|
13150
|
-
const content =
|
|
13269
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13151
13270
|
const lines = content.split("\n").filter(Boolean);
|
|
13152
13271
|
const messages = [];
|
|
13153
13272
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -13179,8 +13298,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13179
13298
|
for (let i = 0; i < convDirs.length; i++) {
|
|
13180
13299
|
const convId = convDirs[i];
|
|
13181
13300
|
if (!isSafeConvId(convId)) continue;
|
|
13182
|
-
const filePath =
|
|
13183
|
-
if (!
|
|
13301
|
+
const filePath = join11(dir, convId, `${convId}.jsonl`);
|
|
13302
|
+
if (!existsSync12(filePath)) continue;
|
|
13184
13303
|
try {
|
|
13185
13304
|
const all = parseCursorTranscriptFile(filePath);
|
|
13186
13305
|
const messages = all.length > 500 ? all.slice(-500) : all;
|
|
@@ -13202,8 +13321,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13202
13321
|
process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
|
|
13203
13322
|
}
|
|
13204
13323
|
try {
|
|
13205
|
-
const lc =
|
|
13206
|
-
writeFileSync8(
|
|
13324
|
+
const lc = readFileSync11(filePath, "utf-8").split("\n").filter(Boolean).length;
|
|
13325
|
+
writeFileSync8(join11(OFFSETS_DIR, convId), String(lc), "utf-8");
|
|
13207
13326
|
} catch {
|
|
13208
13327
|
}
|
|
13209
13328
|
}
|
|
@@ -13211,7 +13330,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13211
13330
|
return { sessions: totalSessions, messages: totalMessages };
|
|
13212
13331
|
}
|
|
13213
13332
|
function parseTranscriptFile(filePath) {
|
|
13214
|
-
const content =
|
|
13333
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13215
13334
|
const lines = content.split("\n").filter(Boolean);
|
|
13216
13335
|
const messages = [];
|
|
13217
13336
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -13259,7 +13378,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13259
13378
|
for (let i = 0; i < files.length; i++) {
|
|
13260
13379
|
const file = files[i];
|
|
13261
13380
|
const sessionId = file.replace(".jsonl", "");
|
|
13262
|
-
const filePath =
|
|
13381
|
+
const filePath = join11(projectsDir, file);
|
|
13263
13382
|
try {
|
|
13264
13383
|
const allMessages = parseTranscriptFile(filePath);
|
|
13265
13384
|
const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
|
|
@@ -13281,9 +13400,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13281
13400
|
process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
|
|
13282
13401
|
}
|
|
13283
13402
|
try {
|
|
13284
|
-
const content =
|
|
13403
|
+
const content = readFileSync11(join11(projectsDir, file), "utf-8");
|
|
13285
13404
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
13286
|
-
writeFileSync8(
|
|
13405
|
+
writeFileSync8(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
13287
13406
|
} catch {
|
|
13288
13407
|
}
|
|
13289
13408
|
}
|
|
@@ -13304,7 +13423,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
13304
13423
|
const sessions = [];
|
|
13305
13424
|
for (const file of batch) {
|
|
13306
13425
|
const sessionId = file.replace(".jsonl", "");
|
|
13307
|
-
const filePath =
|
|
13426
|
+
const filePath = join11(projectsDir, file);
|
|
13308
13427
|
try {
|
|
13309
13428
|
const allMessages = parseTranscriptFile(filePath);
|
|
13310
13429
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -13333,18 +13452,18 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
13333
13452
|
}
|
|
13334
13453
|
for (const file of batch) {
|
|
13335
13454
|
const sessionId = file.replace(".jsonl", "");
|
|
13336
|
-
const filePath =
|
|
13455
|
+
const filePath = join11(projectsDir, file);
|
|
13337
13456
|
try {
|
|
13338
|
-
const content =
|
|
13457
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13339
13458
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
13340
|
-
writeFileSync8(
|
|
13459
|
+
writeFileSync8(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
13341
13460
|
} catch {
|
|
13342
13461
|
}
|
|
13343
13462
|
}
|
|
13344
13463
|
}
|
|
13345
13464
|
return { sessions: totalSessions, messages: totalMessages };
|
|
13346
13465
|
}
|
|
13347
|
-
var
|
|
13466
|
+
var SYNKRO_DIR6, HOOKS_DIR, BIN_DIR, CONFIG_PATH2, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
|
|
13348
13467
|
var init_install = __esm({
|
|
13349
13468
|
"cli/commands/install.ts"() {
|
|
13350
13469
|
"use strict";
|
|
@@ -13363,10 +13482,10 @@ var init_install = __esm({
|
|
|
13363
13482
|
init_claudeDesktopTap();
|
|
13364
13483
|
init_dockerInstall();
|
|
13365
13484
|
init_setupToken();
|
|
13366
|
-
|
|
13367
|
-
HOOKS_DIR =
|
|
13368
|
-
BIN_DIR =
|
|
13369
|
-
CONFIG_PATH2 =
|
|
13485
|
+
SYNKRO_DIR6 = join11(homedir11(), ".synkro");
|
|
13486
|
+
HOOKS_DIR = join11(SYNKRO_DIR6, "hooks");
|
|
13487
|
+
BIN_DIR = join11(SYNKRO_DIR6, "bin");
|
|
13488
|
+
CONFIG_PATH2 = join11(SYNKRO_DIR6, "config.env");
|
|
13370
13489
|
MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
|
|
13371
13490
|
import { readFileSync } from 'node:fs';
|
|
13372
13491
|
import { homedir } from 'node:os';
|
|
@@ -13477,21 +13596,21 @@ rl.on('line', async (line) => {
|
|
|
13477
13596
|
}
|
|
13478
13597
|
});
|
|
13479
13598
|
`;
|
|
13480
|
-
OFFSETS_DIR =
|
|
13481
|
-
CLOUD_JWT_PATH =
|
|
13482
|
-
SKILLS_DISCOVERED_PATH =
|
|
13599
|
+
OFFSETS_DIR = join11(SYNKRO_DIR6, ".transcript-offsets");
|
|
13600
|
+
CLOUD_JWT_PATH = join11(SYNKRO_DIR6, ".cloud-jwt");
|
|
13601
|
+
SKILLS_DISCOVERED_PATH = join11(SYNKRO_DIR6, ".skills-discovered");
|
|
13483
13602
|
}
|
|
13484
13603
|
});
|
|
13485
13604
|
|
|
13486
13605
|
// cli/local-cc/install.ts
|
|
13487
|
-
import { existsSync as
|
|
13488
|
-
import { join as
|
|
13489
|
-
import { homedir as
|
|
13490
|
-
import { spawnSync as
|
|
13606
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, readFileSync as readFileSync12, chmodSync as chmodSync3, copyFileSync as copyFileSync2, renameSync as renameSync4, unlinkSync as unlinkSync5, openSync, fsyncSync, closeSync } from "fs";
|
|
13607
|
+
import { join as join12 } from "path";
|
|
13608
|
+
import { homedir as homedir12 } from "os";
|
|
13609
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
13491
13610
|
function writePluginFiles() {
|
|
13492
13611
|
for (const c of CHANNELS) {
|
|
13493
|
-
|
|
13494
|
-
|
|
13612
|
+
mkdirSync11(c.sessionDir, { recursive: true });
|
|
13613
|
+
mkdirSync11(c.pluginSettingsDir, { recursive: true });
|
|
13495
13614
|
writeFileSync9(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
13496
13615
|
writeFileSync9(
|
|
13497
13616
|
c.pluginSettingsPath,
|
|
@@ -13514,7 +13633,7 @@ function writePluginFiles() {
|
|
|
13514
13633
|
}
|
|
13515
13634
|
function runBunInstall() {
|
|
13516
13635
|
for (const c of CHANNELS) {
|
|
13517
|
-
const r =
|
|
13636
|
+
const r = spawnSync6("bun", ["install", "--silent"], {
|
|
13518
13637
|
cwd: c.sessionDir,
|
|
13519
13638
|
encoding: "utf-8",
|
|
13520
13639
|
timeout: 12e4
|
|
@@ -13527,10 +13646,10 @@ function runBunInstall() {
|
|
|
13527
13646
|
}
|
|
13528
13647
|
}
|
|
13529
13648
|
function safelyMutateClaudeJson(mutator) {
|
|
13530
|
-
if (!
|
|
13649
|
+
if (!existsSync13(CLAUDE_JSON_PATH)) {
|
|
13531
13650
|
return;
|
|
13532
13651
|
}
|
|
13533
|
-
const originalText =
|
|
13652
|
+
const originalText = readFileSync12(CLAUDE_JSON_PATH, "utf-8");
|
|
13534
13653
|
let parsed;
|
|
13535
13654
|
try {
|
|
13536
13655
|
parsed = JSON.parse(originalText);
|
|
@@ -13630,15 +13749,15 @@ function patchClaudeJson() {
|
|
|
13630
13749
|
});
|
|
13631
13750
|
}
|
|
13632
13751
|
function installLocalCC() {
|
|
13633
|
-
let bunCheck =
|
|
13752
|
+
let bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
|
|
13634
13753
|
if (bunCheck.status !== 0) {
|
|
13635
13754
|
if (process.platform === "darwin") {
|
|
13636
13755
|
console.log(" Installing bun via brew...");
|
|
13637
|
-
const brewR =
|
|
13756
|
+
const brewR = spawnSync6("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
13638
13757
|
if (brewR.status !== 0) {
|
|
13639
13758
|
throw new LocalCCInstallError("bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash");
|
|
13640
13759
|
}
|
|
13641
|
-
bunCheck =
|
|
13760
|
+
bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
|
|
13642
13761
|
if (bunCheck.status !== 0) {
|
|
13643
13762
|
throw new LocalCCInstallError("bun installed but not found on PATH. Restart your terminal and re-run install.");
|
|
13644
13763
|
}
|
|
@@ -13672,42 +13791,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
|
|
|
13672
13791
|
var init_install2 = __esm({
|
|
13673
13792
|
"cli/local-cc/install.ts"() {
|
|
13674
13793
|
"use strict";
|
|
13675
|
-
CLAUDE_JSON_BACKUP_PATH =
|
|
13676
|
-
SESSION_DIR =
|
|
13677
|
-
PLUGIN_PATH =
|
|
13678
|
-
PLUGIN_PKG_PATH =
|
|
13679
|
-
PLUGIN_SETTINGS_DIR =
|
|
13680
|
-
PLUGIN_SETTINGS_PATH =
|
|
13681
|
-
PROJECT_MCP_PATH =
|
|
13682
|
-
CLAUDE_JSON_PATH =
|
|
13683
|
-
RUN_SCRIPT_PATH =
|
|
13794
|
+
CLAUDE_JSON_BACKUP_PATH = join12(homedir12(), ".claude.json.synkro-bak");
|
|
13795
|
+
SESSION_DIR = join12(homedir12(), ".synkro", "cc_sessions");
|
|
13796
|
+
PLUGIN_PATH = join12(SESSION_DIR, "synkro-channel.ts");
|
|
13797
|
+
PLUGIN_PKG_PATH = join12(SESSION_DIR, "package.json");
|
|
13798
|
+
PLUGIN_SETTINGS_DIR = join12(SESSION_DIR, ".claude");
|
|
13799
|
+
PLUGIN_SETTINGS_PATH = join12(PLUGIN_SETTINGS_DIR, "settings.json");
|
|
13800
|
+
PROJECT_MCP_PATH = join12(SESSION_DIR, ".mcp.json");
|
|
13801
|
+
CLAUDE_JSON_PATH = join12(homedir12(), ".claude.json");
|
|
13802
|
+
RUN_SCRIPT_PATH = join12(SESSION_DIR, "run-claude.sh");
|
|
13684
13803
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
13685
13804
|
CHANNEL_1_PORT = 8941;
|
|
13686
|
-
SESSION_DIR_2 =
|
|
13687
|
-
PLUGIN_PATH_2 =
|
|
13688
|
-
PLUGIN_PKG_PATH_2 =
|
|
13689
|
-
PLUGIN_SETTINGS_DIR_2 =
|
|
13690
|
-
PLUGIN_SETTINGS_PATH_2 =
|
|
13691
|
-
PROJECT_MCP_PATH_2 =
|
|
13692
|
-
RUN_SCRIPT_PATH_2 =
|
|
13805
|
+
SESSION_DIR_2 = join12(homedir12(), ".synkro", "cc_sessions_2");
|
|
13806
|
+
PLUGIN_PATH_2 = join12(SESSION_DIR_2, "synkro-channel.ts");
|
|
13807
|
+
PLUGIN_PKG_PATH_2 = join12(SESSION_DIR_2, "package.json");
|
|
13808
|
+
PLUGIN_SETTINGS_DIR_2 = join12(SESSION_DIR_2, ".claude");
|
|
13809
|
+
PLUGIN_SETTINGS_PATH_2 = join12(PLUGIN_SETTINGS_DIR_2, "settings.json");
|
|
13810
|
+
PROJECT_MCP_PATH_2 = join12(SESSION_DIR_2, ".mcp.json");
|
|
13811
|
+
RUN_SCRIPT_PATH_2 = join12(SESSION_DIR_2, "run-claude.sh");
|
|
13693
13812
|
TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
|
|
13694
13813
|
CHANNEL_2_PORT = 8951;
|
|
13695
|
-
SESSION_DIR_3 =
|
|
13696
|
-
PLUGIN_PATH_3 =
|
|
13697
|
-
PLUGIN_PKG_PATH_3 =
|
|
13698
|
-
PLUGIN_SETTINGS_DIR_3 =
|
|
13699
|
-
PLUGIN_SETTINGS_PATH_3 =
|
|
13700
|
-
PROJECT_MCP_PATH_3 =
|
|
13701
|
-
RUN_SCRIPT_PATH_3 =
|
|
13814
|
+
SESSION_DIR_3 = join12(homedir12(), ".synkro", "cc_sessions_3");
|
|
13815
|
+
PLUGIN_PATH_3 = join12(SESSION_DIR_3, "synkro-channel.ts");
|
|
13816
|
+
PLUGIN_PKG_PATH_3 = join12(SESSION_DIR_3, "package.json");
|
|
13817
|
+
PLUGIN_SETTINGS_DIR_3 = join12(SESSION_DIR_3, ".claude");
|
|
13818
|
+
PLUGIN_SETTINGS_PATH_3 = join12(PLUGIN_SETTINGS_DIR_3, "settings.json");
|
|
13819
|
+
PROJECT_MCP_PATH_3 = join12(SESSION_DIR_3, ".mcp.json");
|
|
13820
|
+
RUN_SCRIPT_PATH_3 = join12(SESSION_DIR_3, "run-claude.sh");
|
|
13702
13821
|
TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
|
|
13703
13822
|
CHANNEL_3_PORT = 8942;
|
|
13704
|
-
SESSION_DIR_4 =
|
|
13705
|
-
PLUGIN_PATH_4 =
|
|
13706
|
-
PLUGIN_PKG_PATH_4 =
|
|
13707
|
-
PLUGIN_SETTINGS_DIR_4 =
|
|
13708
|
-
PLUGIN_SETTINGS_PATH_4 =
|
|
13709
|
-
PROJECT_MCP_PATH_4 =
|
|
13710
|
-
RUN_SCRIPT_PATH_4 =
|
|
13823
|
+
SESSION_DIR_4 = join12(homedir12(), ".synkro", "cc_sessions_4");
|
|
13824
|
+
PLUGIN_PATH_4 = join12(SESSION_DIR_4, "synkro-channel.ts");
|
|
13825
|
+
PLUGIN_PKG_PATH_4 = join12(SESSION_DIR_4, "package.json");
|
|
13826
|
+
PLUGIN_SETTINGS_DIR_4 = join12(SESSION_DIR_4, ".claude");
|
|
13827
|
+
PLUGIN_SETTINGS_PATH_4 = join12(PLUGIN_SETTINGS_DIR_4, "settings.json");
|
|
13828
|
+
PROJECT_MCP_PATH_4 = join12(SESSION_DIR_4, ".mcp.json");
|
|
13829
|
+
RUN_SCRIPT_PATH_4 = join12(SESSION_DIR_4, "run-claude.sh");
|
|
13711
13830
|
TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
|
|
13712
13831
|
CHANNEL_4_PORT = 8952;
|
|
13713
13832
|
RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
|
|
@@ -13981,10 +14100,10 @@ var disconnect_exports = {};
|
|
|
13981
14100
|
__export(disconnect_exports, {
|
|
13982
14101
|
disconnectCommand: () => disconnectCommand
|
|
13983
14102
|
});
|
|
13984
|
-
import { existsSync as
|
|
13985
|
-
import { homedir as
|
|
13986
|
-
import { join as
|
|
13987
|
-
import { spawnSync as
|
|
14103
|
+
import { existsSync as existsSync14, rmSync as rmSync2, readdirSync as readdirSync4 } from "fs";
|
|
14104
|
+
import { homedir as homedir13 } from "os";
|
|
14105
|
+
import { join as join13 } from "path";
|
|
14106
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
13988
14107
|
import { createInterface as createInterface4 } from "readline";
|
|
13989
14108
|
async function tearDownLocalCC() {
|
|
13990
14109
|
const docker = dockerStatus();
|
|
@@ -13998,7 +14117,7 @@ async function tearDownLocalCC() {
|
|
|
13998
14117
|
console.log("\u2713 removed synkro-server container");
|
|
13999
14118
|
try {
|
|
14000
14119
|
const image = imageTag();
|
|
14001
|
-
const r =
|
|
14120
|
+
const r = spawnSync7("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
|
|
14002
14121
|
console.log(r.status === 0 ? `\u2713 removed Docker image ${image}` : "\xB7 no Docker image to remove");
|
|
14003
14122
|
} catch {
|
|
14004
14123
|
}
|
|
@@ -14061,34 +14180,34 @@ async function disconnectCommand(args2 = []) {
|
|
|
14061
14180
|
const desktopMcpRemoved = uninstallClaudeDesktopMcpConfig();
|
|
14062
14181
|
console.log(`${desktopMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? "removed from claude_desktop_config.json" : "no entry found"}`);
|
|
14063
14182
|
}
|
|
14064
|
-
if (
|
|
14183
|
+
if (existsSync14(SYNKRO_DIR7)) {
|
|
14065
14184
|
if (purge) {
|
|
14066
|
-
|
|
14067
|
-
console.log(`\u2713 wiped ${
|
|
14185
|
+
rmSync2(SYNKRO_DIR7, { recursive: true, force: true });
|
|
14186
|
+
console.log(`\u2713 wiped ${SYNKRO_DIR7} entirely \u2014 including all scan data and backups`);
|
|
14068
14187
|
} else {
|
|
14069
|
-
const keep = /* @__PURE__ */ new Set([
|
|
14188
|
+
const keep = /* @__PURE__ */ new Set([join13(SYNKRO_DIR7, "pgdata"), join13(SYNKRO_DIR7, "pgdata-backups")]);
|
|
14070
14189
|
const preserved = [];
|
|
14071
|
-
for (const entry of readdirSync4(
|
|
14072
|
-
const full =
|
|
14190
|
+
for (const entry of readdirSync4(SYNKRO_DIR7)) {
|
|
14191
|
+
const full = join13(SYNKRO_DIR7, entry);
|
|
14073
14192
|
if (keep.has(full)) {
|
|
14074
14193
|
preserved.push(entry);
|
|
14075
14194
|
continue;
|
|
14076
14195
|
}
|
|
14077
|
-
|
|
14196
|
+
rmSync2(full, { recursive: true, force: true });
|
|
14078
14197
|
}
|
|
14079
14198
|
if (preserved.length > 0) {
|
|
14080
|
-
console.log(`\u2713 removed Synkro config from ${
|
|
14199
|
+
console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR7} (kept your scan data: ${preserved.join(", ")})`);
|
|
14081
14200
|
console.log(" run `synkro uninstall --purge` to delete that too");
|
|
14082
14201
|
} else {
|
|
14083
|
-
console.log(`\u2713 removed ${
|
|
14202
|
+
console.log(`\u2713 removed ${SYNKRO_DIR7}`);
|
|
14084
14203
|
}
|
|
14085
14204
|
}
|
|
14086
14205
|
} else {
|
|
14087
|
-
console.log(`\xB7 ${
|
|
14206
|
+
console.log(`\xB7 ${SYNKRO_DIR7} already gone`);
|
|
14088
14207
|
}
|
|
14089
14208
|
console.log(purge ? "\nSynkro fully removed \u2014 this machine is clean, as if it was never installed." : "\nSynkro uninstalled. Your scan data is preserved.");
|
|
14090
14209
|
}
|
|
14091
|
-
var
|
|
14210
|
+
var SYNKRO_DIR7;
|
|
14092
14211
|
var init_disconnect = __esm({
|
|
14093
14212
|
"cli/commands/disconnect.ts"() {
|
|
14094
14213
|
"use strict";
|
|
@@ -14099,14 +14218,14 @@ var init_disconnect = __esm({
|
|
|
14099
14218
|
init_install2();
|
|
14100
14219
|
init_dockerInstall();
|
|
14101
14220
|
init_macKeychain();
|
|
14102
|
-
|
|
14221
|
+
SYNKRO_DIR7 = join13(homedir13(), ".synkro");
|
|
14103
14222
|
}
|
|
14104
14223
|
});
|
|
14105
14224
|
|
|
14106
14225
|
// cli/local-cc/turnLog.ts
|
|
14107
|
-
import { appendFileSync, existsSync as
|
|
14108
|
-
import { dirname as dirname6, join as
|
|
14109
|
-
import { homedir as
|
|
14226
|
+
import { appendFileSync, existsSync as existsSync15, mkdirSync as mkdirSync12, openSync as openSync2, readFileSync as readFileSync13, readSync, closeSync as closeSync2, statSync as statSync2, watchFile, unwatchFile } from "fs";
|
|
14227
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
14228
|
+
import { homedir as homedir14 } from "os";
|
|
14110
14229
|
function truncate(s, max = PREVIEW_MAX) {
|
|
14111
14230
|
if (s.length <= max) return s;
|
|
14112
14231
|
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
@@ -14126,7 +14245,7 @@ function extractSeverity(result) {
|
|
|
14126
14245
|
}
|
|
14127
14246
|
function appendTurn(args2) {
|
|
14128
14247
|
try {
|
|
14129
|
-
|
|
14248
|
+
mkdirSync12(dirname6(TURN_LOG_PATH), { recursive: true });
|
|
14130
14249
|
const entry = {
|
|
14131
14250
|
ts: new Date(args2.startedAt).toISOString(),
|
|
14132
14251
|
role: args2.role,
|
|
@@ -14142,11 +14261,11 @@ function appendTurn(args2) {
|
|
|
14142
14261
|
}
|
|
14143
14262
|
}
|
|
14144
14263
|
function readRecentTurns(n = 20) {
|
|
14145
|
-
if (!
|
|
14264
|
+
if (!existsSync15(TURN_LOG_PATH)) return [];
|
|
14146
14265
|
try {
|
|
14147
14266
|
const size = statSync2(TURN_LOG_PATH).size;
|
|
14148
14267
|
if (size === 0) return [];
|
|
14149
|
-
const text =
|
|
14268
|
+
const text = readFileSync13(TURN_LOG_PATH, "utf-8");
|
|
14150
14269
|
const lines = text.split("\n").filter(Boolean);
|
|
14151
14270
|
const lastN = lines.slice(-n).reverse();
|
|
14152
14271
|
return lastN.map((line) => {
|
|
@@ -14162,8 +14281,8 @@ function readRecentTurns(n = 20) {
|
|
|
14162
14281
|
}
|
|
14163
14282
|
function followTurns(onEntry) {
|
|
14164
14283
|
try {
|
|
14165
|
-
|
|
14166
|
-
if (!
|
|
14284
|
+
mkdirSync12(dirname6(TURN_LOG_PATH), { recursive: true });
|
|
14285
|
+
if (!existsSync15(TURN_LOG_PATH)) {
|
|
14167
14286
|
appendFileSync(TURN_LOG_PATH, "", "utf-8");
|
|
14168
14287
|
}
|
|
14169
14288
|
} catch {
|
|
@@ -14225,7 +14344,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
|
|
|
14225
14344
|
var init_turnLog = __esm({
|
|
14226
14345
|
"cli/local-cc/turnLog.ts"() {
|
|
14227
14346
|
"use strict";
|
|
14228
|
-
TURN_LOG_PATH =
|
|
14347
|
+
TURN_LOG_PATH = join14(homedir14(), ".synkro", "cc_sessions", "turns.log");
|
|
14229
14348
|
PREVIEW_MAX = 400;
|
|
14230
14349
|
}
|
|
14231
14350
|
});
|
|
@@ -14374,19 +14493,19 @@ var init_grade = __esm({
|
|
|
14374
14493
|
});
|
|
14375
14494
|
|
|
14376
14495
|
// cli/local-cc/pueue.ts
|
|
14377
|
-
import { execFileSync as execFileSync3, spawnSync as
|
|
14378
|
-
import { homedir as
|
|
14379
|
-
import { join as
|
|
14496
|
+
import { execFileSync as execFileSync3, spawnSync as spawnSync8, spawn as spawn2 } from "child_process";
|
|
14497
|
+
import { homedir as homedir15 } from "os";
|
|
14498
|
+
import { join as join15 } from "path";
|
|
14380
14499
|
import { connect as connect2 } from "net";
|
|
14381
14500
|
function pueueAvailable() {
|
|
14382
|
-
const r =
|
|
14501
|
+
const r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14383
14502
|
if (r.status !== 0) {
|
|
14384
14503
|
throw new PueueError("pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.");
|
|
14385
14504
|
}
|
|
14386
14505
|
}
|
|
14387
14506
|
function statusJson() {
|
|
14388
14507
|
pueueAvailable();
|
|
14389
|
-
const r =
|
|
14508
|
+
const r = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8" });
|
|
14390
14509
|
if (r.status !== 0) {
|
|
14391
14510
|
throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || "unknown error"} \u2014 is pueued running?`);
|
|
14392
14511
|
}
|
|
@@ -14431,18 +14550,18 @@ function startTask(opts = {}) {
|
|
|
14431
14550
|
let existing = findTask(ch);
|
|
14432
14551
|
while (existing) {
|
|
14433
14552
|
if (existing.status === "Running" || existing.status === "Queued") {
|
|
14434
|
-
|
|
14435
|
-
|
|
14553
|
+
spawnSync8("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
|
|
14554
|
+
spawnSync8("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
|
|
14436
14555
|
for (let i = 0; i < 10; i++) {
|
|
14437
14556
|
const check = findTask(ch);
|
|
14438
14557
|
if (!check || check.id !== existing.id || check.status !== "Running" && check.status !== "Queued") break;
|
|
14439
|
-
|
|
14558
|
+
spawnSync8("sleep", ["0.5"], { encoding: "utf-8" });
|
|
14440
14559
|
}
|
|
14441
14560
|
}
|
|
14442
|
-
|
|
14561
|
+
spawnSync8("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
|
|
14443
14562
|
existing = findTask(ch);
|
|
14444
14563
|
}
|
|
14445
|
-
const runScript =
|
|
14564
|
+
const runScript = join15(cwd, "run-claude.sh");
|
|
14446
14565
|
const args2 = [
|
|
14447
14566
|
"add",
|
|
14448
14567
|
"--label",
|
|
@@ -14453,7 +14572,7 @@ function startTask(opts = {}) {
|
|
|
14453
14572
|
"bash",
|
|
14454
14573
|
runScript
|
|
14455
14574
|
];
|
|
14456
|
-
const r =
|
|
14575
|
+
const r = spawnSync8("pueue", args2, { encoding: "utf-8" });
|
|
14457
14576
|
if (r.status !== 0) {
|
|
14458
14577
|
throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);
|
|
14459
14578
|
}
|
|
@@ -14464,25 +14583,25 @@ function startTask(opts = {}) {
|
|
|
14464
14583
|
return created;
|
|
14465
14584
|
}
|
|
14466
14585
|
function stopTask(channel = CHANNEL_PRIMARY) {
|
|
14467
|
-
|
|
14586
|
+
spawnSync8("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
|
|
14468
14587
|
let t = findTask(channel);
|
|
14469
14588
|
while (t) {
|
|
14470
14589
|
if (t.status === "Running" || t.status === "Queued") {
|
|
14471
|
-
|
|
14590
|
+
spawnSync8("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
|
|
14472
14591
|
for (let i = 0; i < 10; i++) {
|
|
14473
14592
|
const check = findTask(channel);
|
|
14474
14593
|
if (!check || check.id !== t.id || check.status !== "Running" && check.status !== "Queued") break;
|
|
14475
|
-
|
|
14594
|
+
spawnSync8("sleep", ["0.5"], { encoding: "utf-8" });
|
|
14476
14595
|
}
|
|
14477
14596
|
}
|
|
14478
|
-
|
|
14597
|
+
spawnSync8("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
|
|
14479
14598
|
t = findTask(channel);
|
|
14480
14599
|
}
|
|
14481
14600
|
}
|
|
14482
14601
|
function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
|
|
14483
14602
|
const t = findTask(channel);
|
|
14484
14603
|
if (!t) return `(no ${channel.taskLabel} task)`;
|
|
14485
|
-
const r =
|
|
14604
|
+
const r = spawnSync8("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
|
|
14486
14605
|
return r.stdout || r.stderr || "(no output)";
|
|
14487
14606
|
}
|
|
14488
14607
|
function ensureRunning(opts = {}) {
|
|
@@ -14507,8 +14626,8 @@ function probePort(host, port, timeoutMs = 500) {
|
|
|
14507
14626
|
});
|
|
14508
14627
|
}
|
|
14509
14628
|
function tmuxDismissPrompts(tmuxSession = TMUX_SESSION) {
|
|
14510
|
-
|
|
14511
|
-
|
|
14629
|
+
spawnSync8("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
|
|
14630
|
+
spawnSync8("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
|
|
14512
14631
|
}
|
|
14513
14632
|
async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tmuxSession = TMUX_SESSION) {
|
|
14514
14633
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -14520,46 +14639,46 @@ async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tm
|
|
|
14520
14639
|
return probePort(host, port);
|
|
14521
14640
|
}
|
|
14522
14641
|
function brewInstall(pkg) {
|
|
14523
|
-
const brew =
|
|
14642
|
+
const brew = spawnSync8("brew", ["--version"], { encoding: "utf-8" });
|
|
14524
14643
|
if (brew.status !== 0) return false;
|
|
14525
14644
|
console.log(` Installing ${pkg} via brew...`);
|
|
14526
|
-
const r =
|
|
14645
|
+
const r = spawnSync8("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
14527
14646
|
return r.status === 0;
|
|
14528
14647
|
}
|
|
14529
14648
|
function assertPueueInstalled() {
|
|
14530
|
-
let r =
|
|
14649
|
+
let r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14531
14650
|
if (r.status !== 0) {
|
|
14532
14651
|
if (process.platform === "darwin" && brewInstall("pueue")) {
|
|
14533
|
-
r =
|
|
14652
|
+
r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14534
14653
|
if (r.status !== 0) throw new PueueError("pueue install succeeded but binary not found on PATH.");
|
|
14535
14654
|
} else {
|
|
14536
14655
|
throw new PueueError("pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue");
|
|
14537
14656
|
}
|
|
14538
14657
|
}
|
|
14539
|
-
const status =
|
|
14658
|
+
const status = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
|
|
14540
14659
|
if (status.status !== 0) {
|
|
14541
14660
|
console.log(" Starting pueued daemon...");
|
|
14542
14661
|
const child = spawn2("pueued", ["-d"], { stdio: "ignore", detached: true });
|
|
14543
14662
|
child.unref();
|
|
14544
|
-
|
|
14545
|
-
const retry =
|
|
14663
|
+
spawnSync8("sleep", ["1"]);
|
|
14664
|
+
const retry = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
|
|
14546
14665
|
if (retry.status !== 0) {
|
|
14547
14666
|
throw new PueueError("pueue daemon not reachable after starting pueued. Check `pueued` manually.");
|
|
14548
14667
|
}
|
|
14549
14668
|
}
|
|
14550
|
-
|
|
14669
|
+
spawnSync8("pueue", ["parallel", "2"], { encoding: "utf-8" });
|
|
14551
14670
|
}
|
|
14552
14671
|
function assertClaudeInstalled() {
|
|
14553
|
-
const r =
|
|
14672
|
+
const r = spawnSync8("claude", ["--version"], { encoding: "utf-8" });
|
|
14554
14673
|
if (r.status !== 0) {
|
|
14555
14674
|
throw new PueueError("claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code");
|
|
14556
14675
|
}
|
|
14557
14676
|
}
|
|
14558
14677
|
function assertTmuxInstalled() {
|
|
14559
|
-
let r =
|
|
14678
|
+
let r = spawnSync8("tmux", ["-V"], { encoding: "utf-8" });
|
|
14560
14679
|
if (r.status !== 0) {
|
|
14561
14680
|
if (process.platform === "darwin" && brewInstall("tmux")) {
|
|
14562
|
-
r =
|
|
14681
|
+
r = spawnSync8("tmux", ["-V"], { encoding: "utf-8" });
|
|
14563
14682
|
if (r.status !== 0) throw new PueueError("tmux install succeeded but binary not found on PATH.");
|
|
14564
14683
|
} else {
|
|
14565
14684
|
throw new PueueError("tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)");
|
|
@@ -14572,12 +14691,12 @@ var init_pueue = __esm({
|
|
|
14572
14691
|
"use strict";
|
|
14573
14692
|
TASK_LABEL = "synkro-local-cc";
|
|
14574
14693
|
TMUX_SESSION = "synkro-local-cc";
|
|
14575
|
-
SESSION_DIR2 =
|
|
14694
|
+
SESSION_DIR2 = join15(homedir15(), ".synkro", "cc_sessions");
|
|
14576
14695
|
TASK_LABEL_2 = "synkro-local-cc-2";
|
|
14577
14696
|
TMUX_SESSION_2 = "synkro-local-cc-2";
|
|
14578
|
-
SESSION_DIR_22 =
|
|
14579
|
-
SESSION_DIR_32 =
|
|
14580
|
-
SESSION_DIR_42 =
|
|
14697
|
+
SESSION_DIR_22 = join15(homedir15(), ".synkro", "cc_sessions_2");
|
|
14698
|
+
SESSION_DIR_32 = join15(homedir15(), ".synkro", "cc_sessions_3");
|
|
14699
|
+
SESSION_DIR_42 = join15(homedir15(), ".synkro", "cc_sessions_4");
|
|
14581
14700
|
PueueError = class extends Error {
|
|
14582
14701
|
constructor(message, cause) {
|
|
14583
14702
|
super(message);
|
|
@@ -14592,13 +14711,13 @@ var init_pueue = __esm({
|
|
|
14592
14711
|
});
|
|
14593
14712
|
|
|
14594
14713
|
// cli/local-cc/settings.ts
|
|
14595
|
-
import { existsSync as
|
|
14596
|
-
import { homedir as
|
|
14597
|
-
import { join as
|
|
14714
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
14715
|
+
import { homedir as homedir16 } from "os";
|
|
14716
|
+
import { join as join16 } from "path";
|
|
14598
14717
|
function isLocalCCEnabled() {
|
|
14599
|
-
if (!
|
|
14718
|
+
if (!existsSync16(CONFIG_PATH3)) return false;
|
|
14600
14719
|
try {
|
|
14601
|
-
const content =
|
|
14720
|
+
const content = readFileSync14(CONFIG_PATH3, "utf-8");
|
|
14602
14721
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
14603
14722
|
return match?.[1] === "yes";
|
|
14604
14723
|
} catch {
|
|
@@ -14609,7 +14728,7 @@ var CONFIG_PATH3;
|
|
|
14609
14728
|
var init_settings = __esm({
|
|
14610
14729
|
"cli/local-cc/settings.ts"() {
|
|
14611
14730
|
"use strict";
|
|
14612
|
-
CONFIG_PATH3 =
|
|
14731
|
+
CONFIG_PATH3 = join16(homedir16(), ".synkro", "config.env");
|
|
14613
14732
|
}
|
|
14614
14733
|
});
|
|
14615
14734
|
|
|
@@ -14618,11 +14737,11 @@ var localCc_exports = {};
|
|
|
14618
14737
|
__export(localCc_exports, {
|
|
14619
14738
|
localCcCommand: () => localCcCommand
|
|
14620
14739
|
});
|
|
14621
|
-
import { spawnSync as
|
|
14622
|
-
import { homedir as
|
|
14623
|
-
import { join as
|
|
14740
|
+
import { spawnSync as spawnSync9 } from "child_process";
|
|
14741
|
+
import { homedir as homedir17 } from "os";
|
|
14742
|
+
import { join as join17 } from "path";
|
|
14624
14743
|
import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
|
|
14625
|
-
import { existsSync as
|
|
14744
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
14626
14745
|
function deploymentMode() {
|
|
14627
14746
|
const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
|
|
14628
14747
|
if (env === "docker") return "docker";
|
|
@@ -14728,15 +14847,15 @@ TROUBLESHOOTING
|
|
|
14728
14847
|
`);
|
|
14729
14848
|
}
|
|
14730
14849
|
function readGatewayUrl() {
|
|
14731
|
-
if (
|
|
14732
|
-
const m =
|
|
14850
|
+
if (existsSync17(CONFIG_PATH4)) {
|
|
14851
|
+
const m = readFileSync15(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
14733
14852
|
if (m) return m[1];
|
|
14734
14853
|
}
|
|
14735
14854
|
return "https://api.synkro.sh";
|
|
14736
14855
|
}
|
|
14737
14856
|
function updateLocalInferenceFlag(enabled) {
|
|
14738
|
-
if (!
|
|
14739
|
-
let content =
|
|
14857
|
+
if (!existsSync17(CONFIG_PATH4)) return;
|
|
14858
|
+
let content = readFileSync15(CONFIG_PATH4, "utf-8");
|
|
14740
14859
|
const flag = enabled ? "yes" : "no";
|
|
14741
14860
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
14742
14861
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -14799,7 +14918,7 @@ async function cmdStatus() {
|
|
|
14799
14918
|
}
|
|
14800
14919
|
const ch1Up = await isChannelAvailable();
|
|
14801
14920
|
console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? "reachable" : "unreachable"}`);
|
|
14802
|
-
const tmux1 =
|
|
14921
|
+
const tmux1 = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
|
|
14803
14922
|
console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? "live" : "absent"}`);
|
|
14804
14923
|
const t2 = findTask(CHANNEL_SECONDARY);
|
|
14805
14924
|
if (!t2) {
|
|
@@ -14809,7 +14928,7 @@ async function cmdStatus() {
|
|
|
14809
14928
|
}
|
|
14810
14929
|
const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);
|
|
14811
14930
|
console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? "reachable" : "unreachable"}`);
|
|
14812
|
-
const tmux2 =
|
|
14931
|
+
const tmux2 = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
|
|
14813
14932
|
console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? "live" : "absent"}`);
|
|
14814
14933
|
}
|
|
14815
14934
|
async function cmdEnable() {
|
|
@@ -15015,7 +15134,7 @@ function cmdLogs(rest) {
|
|
|
15015
15134
|
}
|
|
15016
15135
|
return "200";
|
|
15017
15136
|
})();
|
|
15018
|
-
|
|
15137
|
+
spawnSync9("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
|
|
15019
15138
|
return;
|
|
15020
15139
|
}
|
|
15021
15140
|
for (const arg of rest) {
|
|
@@ -15063,7 +15182,7 @@ function cmdLogs(rest) {
|
|
|
15063
15182
|
function cmdAttach(rest) {
|
|
15064
15183
|
assertTmuxInstalled();
|
|
15065
15184
|
const readonly = rest.some((a) => a === "--readonly" || a === "-r");
|
|
15066
|
-
const has =
|
|
15185
|
+
const has = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
|
|
15067
15186
|
if (has.status !== 0) {
|
|
15068
15187
|
console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);
|
|
15069
15188
|
process.exit(1);
|
|
@@ -15076,7 +15195,7 @@ function cmdAttach(rest) {
|
|
|
15076
15195
|
console.log("Detach with Ctrl-B then D. (Do not press Ctrl-C \u2014 that would interrupt claude.)");
|
|
15077
15196
|
console.log();
|
|
15078
15197
|
const args2 = readonly ? ["attach-session", "-r", "-t", TMUX_SESSION_NAME] : ["attach-session", "-t", TMUX_SESSION_NAME];
|
|
15079
|
-
const r =
|
|
15198
|
+
const r = spawnSync9("tmux", args2, { stdio: "inherit" });
|
|
15080
15199
|
process.exit(r.status ?? 0);
|
|
15081
15200
|
}
|
|
15082
15201
|
async function cmdTest() {
|
|
@@ -15177,8 +15296,8 @@ var init_localCc = __esm({
|
|
|
15177
15296
|
init_install();
|
|
15178
15297
|
init_client();
|
|
15179
15298
|
init_stub();
|
|
15180
|
-
SYNKRO_CONFIG_PATH =
|
|
15181
|
-
CONFIG_PATH4 =
|
|
15299
|
+
SYNKRO_CONFIG_PATH = join17(homedir17(), ".synkro", "config.env");
|
|
15300
|
+
CONFIG_PATH4 = join17(homedir17(), ".synkro", "config.env");
|
|
15182
15301
|
}
|
|
15183
15302
|
});
|
|
15184
15303
|
|
|
@@ -15187,15 +15306,15 @@ var import_exports = {};
|
|
|
15187
15306
|
__export(import_exports, {
|
|
15188
15307
|
importCommand: () => importCommand
|
|
15189
15308
|
});
|
|
15190
|
-
import { existsSync as
|
|
15191
|
-
import { homedir as
|
|
15192
|
-
import { join as
|
|
15309
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16, readdirSync as readdirSync5 } from "fs";
|
|
15310
|
+
import { homedir as homedir18 } from "os";
|
|
15311
|
+
import { join as join18 } from "path";
|
|
15193
15312
|
import { execSync as execSync7 } from "child_process";
|
|
15194
15313
|
import { createInterface as createInterface5 } from "readline";
|
|
15195
15314
|
function readConfigEnv() {
|
|
15196
15315
|
const out = {};
|
|
15197
15316
|
try {
|
|
15198
|
-
for (const line of
|
|
15317
|
+
for (const line of readFileSync16(CONFIG_PATH5, "utf-8").split("\n")) {
|
|
15199
15318
|
const t = line.trim();
|
|
15200
15319
|
if (!t || t.startsWith("#")) continue;
|
|
15201
15320
|
const eq = t.indexOf("=");
|
|
@@ -15207,8 +15326,8 @@ function readConfigEnv() {
|
|
|
15207
15326
|
}
|
|
15208
15327
|
function projectsFolder() {
|
|
15209
15328
|
const sanitized = process.cwd().replace(/\//g, "-");
|
|
15210
|
-
const dir =
|
|
15211
|
-
return
|
|
15329
|
+
const dir = join18(homedir18(), ".claude", "projects", sanitized);
|
|
15330
|
+
return existsSync18(dir) ? dir : null;
|
|
15212
15331
|
}
|
|
15213
15332
|
function repoName() {
|
|
15214
15333
|
try {
|
|
@@ -15231,7 +15350,7 @@ function extractText(content) {
|
|
|
15231
15350
|
return "";
|
|
15232
15351
|
}
|
|
15233
15352
|
function parseSession(filePath, sessionId) {
|
|
15234
|
-
const lines =
|
|
15353
|
+
const lines = readFileSync16(filePath, "utf-8").split("\n").filter(Boolean);
|
|
15235
15354
|
const messages = [];
|
|
15236
15355
|
const actions = [];
|
|
15237
15356
|
let step = 0;
|
|
@@ -15304,7 +15423,7 @@ async function importCommand() {
|
|
|
15304
15423
|
return;
|
|
15305
15424
|
}
|
|
15306
15425
|
}
|
|
15307
|
-
const sessions = files.map((f) => parseSession(
|
|
15426
|
+
const sessions = files.map((f) => parseSession(join18(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
|
|
15308
15427
|
const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
|
|
15309
15428
|
let ok = 0, fail = 0;
|
|
15310
15429
|
if (isCloud) {
|
|
@@ -15364,7 +15483,7 @@ var init_import = __esm({
|
|
|
15364
15483
|
"cli/commands/import.ts"() {
|
|
15365
15484
|
"use strict";
|
|
15366
15485
|
init_stub();
|
|
15367
|
-
CONFIG_PATH5 =
|
|
15486
|
+
CONFIG_PATH5 = join18(homedir18(), ".synkro", "config.env");
|
|
15368
15487
|
}
|
|
15369
15488
|
});
|
|
15370
15489
|
|
|
@@ -15406,10 +15525,10 @@ var init_packVerify = __esm({
|
|
|
15406
15525
|
});
|
|
15407
15526
|
|
|
15408
15527
|
// cli/installer/lockfile.ts
|
|
15409
|
-
import { existsSync as
|
|
15410
|
-
import { join as
|
|
15528
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
15529
|
+
import { join as join19 } from "path";
|
|
15411
15530
|
function lockPath(repoRoot) {
|
|
15412
|
-
return
|
|
15531
|
+
return join19(repoRoot, LOCK_FILE);
|
|
15413
15532
|
}
|
|
15414
15533
|
function writeLockfile(repoRoot, entries) {
|
|
15415
15534
|
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
@@ -15442,9 +15561,9 @@ var sync_exports = {};
|
|
|
15442
15561
|
__export(sync_exports, {
|
|
15443
15562
|
syncCommand: () => syncCommand
|
|
15444
15563
|
});
|
|
15445
|
-
import { existsSync as
|
|
15446
|
-
import { homedir as
|
|
15447
|
-
import { join as
|
|
15564
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync6, rmSync as rmSync3, writeFileSync as writeFileSync12 } from "fs";
|
|
15565
|
+
import { homedir as homedir19 } from "os";
|
|
15566
|
+
import { join as join20 } from "path";
|
|
15448
15567
|
function cacheKey(ref, version) {
|
|
15449
15568
|
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
15450
15569
|
}
|
|
@@ -15475,8 +15594,8 @@ async function syncCommand(_args = []) {
|
|
|
15475
15594
|
}
|
|
15476
15595
|
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
15477
15596
|
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
15478
|
-
const cacheDir =
|
|
15479
|
-
if (!cloud)
|
|
15597
|
+
const cacheDir = join20(homedir19(), ".synkro", "cache", "packs");
|
|
15598
|
+
if (!cloud) mkdirSync13(cacheDir, { recursive: true });
|
|
15480
15599
|
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
15481
15600
|
const lock = [];
|
|
15482
15601
|
const keptCacheFiles = /* @__PURE__ */ new Set();
|
|
@@ -15504,7 +15623,7 @@ async function syncCommand(_args = []) {
|
|
|
15504
15623
|
if (!cloud) {
|
|
15505
15624
|
const fname = cacheKey(ref, data.version);
|
|
15506
15625
|
keptCacheFiles.add(fname);
|
|
15507
|
-
writeFileSync12(
|
|
15626
|
+
writeFileSync12(join20(cacheDir, fname), JSON.stringify({
|
|
15508
15627
|
ref,
|
|
15509
15628
|
version: data.version,
|
|
15510
15629
|
digest: data.digest,
|
|
@@ -15516,11 +15635,11 @@ async function syncCommand(_args = []) {
|
|
|
15516
15635
|
const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
|
|
15517
15636
|
console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
|
|
15518
15637
|
}
|
|
15519
|
-
if (!cloud &&
|
|
15638
|
+
if (!cloud && existsSync20(cacheDir)) {
|
|
15520
15639
|
for (const f of readdirSync6(cacheDir)) {
|
|
15521
15640
|
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
15522
15641
|
try {
|
|
15523
|
-
|
|
15642
|
+
rmSync3(join20(cacheDir, f));
|
|
15524
15643
|
} catch {
|
|
15525
15644
|
}
|
|
15526
15645
|
}
|
|
@@ -15656,13 +15775,13 @@ var config_exports = {};
|
|
|
15656
15775
|
__export(config_exports, {
|
|
15657
15776
|
configCommand: () => configCommand
|
|
15658
15777
|
});
|
|
15659
|
-
import { readFileSync as
|
|
15660
|
-
import { join as
|
|
15661
|
-
import { homedir as
|
|
15778
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync21 } from "fs";
|
|
15779
|
+
import { join as join21 } from "path";
|
|
15780
|
+
import { homedir as homedir20 } from "os";
|
|
15662
15781
|
function readConfigEnv2() {
|
|
15663
|
-
if (!
|
|
15782
|
+
if (!existsSync21(CONFIG_PATH6)) return {};
|
|
15664
15783
|
const out = {};
|
|
15665
|
-
for (const line of
|
|
15784
|
+
for (const line of readFileSync18(CONFIG_PATH6, "utf-8").split("\n")) {
|
|
15666
15785
|
const t = line.trim();
|
|
15667
15786
|
if (!t || t.startsWith("#")) continue;
|
|
15668
15787
|
const eq = t.indexOf("=");
|
|
@@ -15671,11 +15790,11 @@ function readConfigEnv2() {
|
|
|
15671
15790
|
return out;
|
|
15672
15791
|
}
|
|
15673
15792
|
function updateConfigValue(key, value) {
|
|
15674
|
-
if (!
|
|
15793
|
+
if (!existsSync21(CONFIG_PATH6)) {
|
|
15675
15794
|
console.error("No config found. Run `synkro install` first.");
|
|
15676
15795
|
process.exit(1);
|
|
15677
15796
|
}
|
|
15678
|
-
const lines =
|
|
15797
|
+
const lines = readFileSync18(CONFIG_PATH6, "utf-8").split("\n");
|
|
15679
15798
|
const pattern = new RegExp(`^${key}=`);
|
|
15680
15799
|
let found = false;
|
|
15681
15800
|
const updated = lines.map((line) => {
|
|
@@ -15813,25 +15932,25 @@ To change:`);
|
|
|
15813
15932
|
}
|
|
15814
15933
|
if (inferenceValue !== "cloud") await reconcileContainer();
|
|
15815
15934
|
}
|
|
15816
|
-
var
|
|
15935
|
+
var SYNKRO_DIR8, CONFIG_PATH6;
|
|
15817
15936
|
var init_config = __esm({
|
|
15818
15937
|
"cli/commands/config.ts"() {
|
|
15819
15938
|
"use strict";
|
|
15820
15939
|
init_stub();
|
|
15821
|
-
|
|
15822
|
-
CONFIG_PATH6 =
|
|
15940
|
+
SYNKRO_DIR8 = join21(homedir20(), ".synkro");
|
|
15941
|
+
CONFIG_PATH6 = join21(SYNKRO_DIR8, "config.env");
|
|
15823
15942
|
}
|
|
15824
15943
|
});
|
|
15825
15944
|
|
|
15826
15945
|
// cli/bootstrap.js
|
|
15827
|
-
import { readFileSync as
|
|
15946
|
+
import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
|
|
15828
15947
|
import { resolve as resolve3 } from "path";
|
|
15829
15948
|
var envCandidates = [
|
|
15830
15949
|
resolve3(process.env.HOME ?? "", ".synkro", "config.env")
|
|
15831
15950
|
];
|
|
15832
15951
|
for (const envPath of envCandidates) {
|
|
15833
|
-
if (!
|
|
15834
|
-
const envContent =
|
|
15952
|
+
if (!existsSync22(envPath)) continue;
|
|
15953
|
+
const envContent = readFileSync19(envPath, "utf-8");
|
|
15835
15954
|
for (const line of envContent.split("\n")) {
|
|
15836
15955
|
const trimmed = line.trim();
|
|
15837
15956
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -15846,7 +15965,7 @@ var args = process.argv.slice(2);
|
|
|
15846
15965
|
var cmd = args[0] || "";
|
|
15847
15966
|
var subArgs = args.slice(1);
|
|
15848
15967
|
function printVersion() {
|
|
15849
|
-
console.log("1.7.
|
|
15968
|
+
console.log("1.7.27");
|
|
15850
15969
|
}
|
|
15851
15970
|
function printHelp2() {
|
|
15852
15971
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|