@synkro-sh/cli 1.7.18 → 1.7.24
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 +388 -272
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -269,6 +269,14 @@ function installCCHooks(settingsPath, config) {
|
|
|
269
269
|
],
|
|
270
270
|
[SYNKRO_MARKER]: true
|
|
271
271
|
});
|
|
272
|
+
settings.env = settings.env ?? {};
|
|
273
|
+
const existingBaseUrl = settings.env.ANTHROPIC_BASE_URL;
|
|
274
|
+
if (!existingBaseUrl || existingBaseUrl === USAGE_PROXY_URL) {
|
|
275
|
+
settings.env.ANTHROPIC_BASE_URL = USAGE_PROXY_URL;
|
|
276
|
+
} else {
|
|
277
|
+
console.warn(` \u26A0 ANTHROPIC_BASE_URL already set to ${existingBaseUrl} \u2014 leaving it; live usage capture is off for this agent.`);
|
|
278
|
+
}
|
|
279
|
+
if (Object.keys(settings.env).length === 0) delete settings.env;
|
|
272
280
|
writeSettingsAtomic(settingsPath, settings);
|
|
273
281
|
}
|
|
274
282
|
function uninstallCCHooks(settingsPath) {
|
|
@@ -287,14 +295,19 @@ function uninstallCCHooks(settingsPath) {
|
|
|
287
295
|
if (Object.keys(settings.hooks).length === 0) {
|
|
288
296
|
delete settings.hooks;
|
|
289
297
|
}
|
|
298
|
+
if (settings.env && settings.env.ANTHROPIC_BASE_URL === USAGE_PROXY_URL) {
|
|
299
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
300
|
+
if (Object.keys(settings.env).length === 0) delete settings.env;
|
|
301
|
+
}
|
|
290
302
|
writeSettingsAtomic(settingsPath, settings);
|
|
291
303
|
return true;
|
|
292
304
|
}
|
|
293
|
-
var SYNKRO_MARKER;
|
|
305
|
+
var SYNKRO_MARKER, USAGE_PROXY_URL;
|
|
294
306
|
var init_ccHookConfig = __esm({
|
|
295
307
|
"cli/installer/ccHookConfig.ts"() {
|
|
296
308
|
"use strict";
|
|
297
309
|
SYNKRO_MARKER = "__synkro_managed__";
|
|
310
|
+
USAGE_PROXY_URL = `http://127.0.0.1:${process.env.SYNKRO_HOST_USAGE_PROXY_PORT || "18927"}`;
|
|
298
311
|
}
|
|
299
312
|
});
|
|
300
313
|
|
|
@@ -10610,6 +10623,8 @@ async function dockerInstall(opts = {}) {
|
|
|
10610
10623
|
"-p",
|
|
10611
10624
|
`127.0.0.1:${HOST_CWE_PORT}:8930`,
|
|
10612
10625
|
"-p",
|
|
10626
|
+
`127.0.0.1:${HOST_USAGE_PROXY_PORT}:8927`,
|
|
10627
|
+
"-p",
|
|
10613
10628
|
`127.0.0.1:${HOST_PGLITE_PORT}:5433`,
|
|
10614
10629
|
"-v",
|
|
10615
10630
|
`${PGDATA_PATH}:/data/pgdata`,
|
|
@@ -10860,7 +10875,7 @@ function checkPgdata() {
|
|
|
10860
10875
|
if (!hasPgControl) return { healthy: false, details: "pg_control/global directory missing" };
|
|
10861
10876
|
return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };
|
|
10862
10877
|
}
|
|
10863
|
-
var SYNKRO_DIR2, MCP_JWT_PATH, PGDATA_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
|
|
10878
|
+
var SYNKRO_DIR2, MCP_JWT_PATH, PGDATA_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_USAGE_PROXY_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
|
|
10864
10879
|
var init_dockerInstall = __esm({
|
|
10865
10880
|
"cli/local-cc/dockerInstall.ts"() {
|
|
10866
10881
|
"use strict";
|
|
@@ -10874,6 +10889,7 @@ var init_dockerInstall = __esm({
|
|
|
10874
10889
|
HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
|
|
10875
10890
|
HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || "18929", 10);
|
|
10876
10891
|
HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || "18930", 10);
|
|
10892
|
+
HOST_USAGE_PROXY_PORT = parseInt(process.env.SYNKRO_HOST_USAGE_PROXY_PORT || "18927", 10);
|
|
10877
10893
|
HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
|
|
10878
10894
|
CONTAINER_NAME = "synkro-server";
|
|
10879
10895
|
DEFAULT_IMAGE = "ghcr.io/synkro-sh/synkro-server:latest";
|
|
@@ -10979,6 +10995,87 @@ var init_setupToken = __esm({
|
|
|
10979
10995
|
}
|
|
10980
10996
|
});
|
|
10981
10997
|
|
|
10998
|
+
// cli/local-cc/codexCloudSetup.ts
|
|
10999
|
+
var codexCloudSetup_exports = {};
|
|
11000
|
+
__export(codexCloudSetup_exports, {
|
|
11001
|
+
setupCodexCloud: () => setupCodexCloud
|
|
11002
|
+
});
|
|
11003
|
+
import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
|
|
11004
|
+
import { readFileSync as readFileSync9, mkdirSync as mkdirSync9, rmSync, existsSync as existsSync10 } from "fs";
|
|
11005
|
+
import { homedir as homedir9 } from "os";
|
|
11006
|
+
import { join as join9 } from "path";
|
|
11007
|
+
function findCodexBinary() {
|
|
11008
|
+
if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
|
|
11009
|
+
const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
|
|
11010
|
+
const p = (r.stdout || "").trim().split("\n")[0];
|
|
11011
|
+
return p || null;
|
|
11012
|
+
}
|
|
11013
|
+
function runCodexLogin(codexBin) {
|
|
11014
|
+
mkdirSync9(CODEX_CLOUD_HOME, { recursive: true, mode: 448 });
|
|
11015
|
+
return new Promise((resolve4, reject) => {
|
|
11016
|
+
const proc = nodeSpawn2(codexBin, ["login"], {
|
|
11017
|
+
stdio: "inherit",
|
|
11018
|
+
env: { ...process.env, CODEX_HOME: CODEX_CLOUD_HOME }
|
|
11019
|
+
});
|
|
11020
|
+
proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
|
|
11021
|
+
proc.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`codex login exited with code ${code}`)));
|
|
11022
|
+
});
|
|
11023
|
+
}
|
|
11024
|
+
async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
|
|
11025
|
+
const codexBin = findCodexBinary();
|
|
11026
|
+
if (!codexBin) {
|
|
11027
|
+
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.)" };
|
|
11028
|
+
}
|
|
11029
|
+
onStatus?.("Opening your browser to authorize your Codex subscription for Synkro Cloud\u2026");
|
|
11030
|
+
try {
|
|
11031
|
+
await runCodexLogin(codexBin);
|
|
11032
|
+
} catch (e) {
|
|
11033
|
+
return { ok: false, error: `Codex login failed: ${e.message}` };
|
|
11034
|
+
}
|
|
11035
|
+
const authPath = join9(CODEX_CLOUD_HOME, "auth.json");
|
|
11036
|
+
if (!existsSync10(authPath)) {
|
|
11037
|
+
return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
|
|
11038
|
+
}
|
|
11039
|
+
let auth;
|
|
11040
|
+
try {
|
|
11041
|
+
auth = JSON.parse(readFileSync9(authPath, "utf-8"));
|
|
11042
|
+
} catch (e) {
|
|
11043
|
+
return { ok: false, error: `could not read codex auth.json: ${e.message}` };
|
|
11044
|
+
}
|
|
11045
|
+
if (!auth || typeof auth !== "object" || !auth.tokens?.refresh_token) {
|
|
11046
|
+
return { ok: false, error: "codex auth.json has no refresh token \u2014 cloud refresh would fail. Re-run login." };
|
|
11047
|
+
}
|
|
11048
|
+
onStatus?.("Connecting your Codex session to Synkro Cloud\u2026");
|
|
11049
|
+
let resp;
|
|
11050
|
+
try {
|
|
11051
|
+
resp = await fetch(`${gatewayUrl.replace(/\/+$/, "")}/codex/seed`, {
|
|
11052
|
+
method: "POST",
|
|
11053
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${bearerToken}` },
|
|
11054
|
+
body: JSON.stringify({ auth })
|
|
11055
|
+
});
|
|
11056
|
+
} catch (e) {
|
|
11057
|
+
return { ok: false, error: `failed to reach Synkro API: ${e.message}` };
|
|
11058
|
+
}
|
|
11059
|
+
if (!resp.ok) {
|
|
11060
|
+
const detail = await resp.text().catch(() => "");
|
|
11061
|
+
return { ok: false, error: `seed rejected (${resp.status}): ${detail.slice(0, 200)}` };
|
|
11062
|
+
}
|
|
11063
|
+
try {
|
|
11064
|
+
rmSync(CODEX_CLOUD_HOME, { recursive: true, force: true });
|
|
11065
|
+
} catch {
|
|
11066
|
+
}
|
|
11067
|
+
onStatus?.("\u2713 Codex subscription connected to Synkro Cloud.");
|
|
11068
|
+
return { ok: true };
|
|
11069
|
+
}
|
|
11070
|
+
var SYNKRO_DIR4, CODEX_CLOUD_HOME;
|
|
11071
|
+
var init_codexCloudSetup = __esm({
|
|
11072
|
+
"cli/local-cc/codexCloudSetup.ts"() {
|
|
11073
|
+
"use strict";
|
|
11074
|
+
SYNKRO_DIR4 = join9(homedir9(), ".synkro");
|
|
11075
|
+
CODEX_CLOUD_HOME = join9(SYNKRO_DIR4, "codex-cloud-session");
|
|
11076
|
+
}
|
|
11077
|
+
});
|
|
11078
|
+
|
|
10982
11079
|
// cli/commands/setupGithub.ts
|
|
10983
11080
|
var setupGithub_exports = {};
|
|
10984
11081
|
__export(setupGithub_exports, {
|
|
@@ -10988,14 +11085,14 @@ __export(setupGithub_exports, {
|
|
|
10988
11085
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
10989
11086
|
import { stdin as input, stdout as output } from "process";
|
|
10990
11087
|
import { execSync as execSync5 } from "child_process";
|
|
10991
|
-
import { existsSync as
|
|
10992
|
-
import { homedir as
|
|
10993
|
-
import { join as
|
|
11088
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
11089
|
+
import { homedir as homedir10, platform as platform4 } from "os";
|
|
11090
|
+
import { join as join10 } from "path";
|
|
10994
11091
|
import { execFile as execFile2 } from "child_process";
|
|
10995
11092
|
function readConfig() {
|
|
10996
|
-
if (!
|
|
11093
|
+
if (!existsSync11(CONFIG_PATH)) return {};
|
|
10997
11094
|
const out = {};
|
|
10998
|
-
for (const line of
|
|
11095
|
+
for (const line of readFileSync10(CONFIG_PATH, "utf-8").split("\n")) {
|
|
10999
11096
|
const t = line.trim();
|
|
11000
11097
|
if (!t || t.startsWith("#")) continue;
|
|
11001
11098
|
const eq = t.indexOf("=");
|
|
@@ -11295,15 +11392,15 @@ Will push secrets to ${selected.length} repo(s):`);
|
|
|
11295
11392
|
console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);
|
|
11296
11393
|
console.log("Open a PR on any selected repo to trigger your first Synkro scan.");
|
|
11297
11394
|
}
|
|
11298
|
-
var
|
|
11395
|
+
var SYNKRO_DIR5, CONFIG_PATH;
|
|
11299
11396
|
var init_setupGithub = __esm({
|
|
11300
11397
|
"cli/commands/setupGithub.ts"() {
|
|
11301
11398
|
"use strict";
|
|
11302
11399
|
init_githubSetup();
|
|
11303
11400
|
init_stub();
|
|
11304
11401
|
init_setupToken();
|
|
11305
|
-
|
|
11306
|
-
CONFIG_PATH =
|
|
11402
|
+
SYNKRO_DIR5 = join10(homedir10(), ".synkro");
|
|
11403
|
+
CONFIG_PATH = join10(SYNKRO_DIR5, "config.env");
|
|
11307
11404
|
}
|
|
11308
11405
|
});
|
|
11309
11406
|
|
|
@@ -11322,9 +11419,9 @@ __export(install_exports, {
|
|
|
11322
11419
|
syncSkillFiles: () => syncSkillFiles,
|
|
11323
11420
|
writeHookScripts: () => writeHookScripts
|
|
11324
11421
|
});
|
|
11325
|
-
import { existsSync as
|
|
11326
|
-
import { homedir as
|
|
11327
|
-
import { join as
|
|
11422
|
+
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";
|
|
11423
|
+
import { homedir as homedir11 } from "os";
|
|
11424
|
+
import { join as join11 } from "path";
|
|
11328
11425
|
import { execSync as execSync6 } from "child_process";
|
|
11329
11426
|
import { createInterface as createInterface3 } from "readline";
|
|
11330
11427
|
import { createHash } from "crypto";
|
|
@@ -11332,7 +11429,7 @@ function resolvePersistedHookMode() {
|
|
|
11332
11429
|
if (process.env.SYNKRO_HOOK_MODE === "full") return "full";
|
|
11333
11430
|
if (process.env.SYNKRO_HOOK_MODE === "stub") return "stub";
|
|
11334
11431
|
try {
|
|
11335
|
-
const env =
|
|
11432
|
+
const env = readFileSync11(CONFIG_PATH2, "utf-8");
|
|
11336
11433
|
if (/^SYNKRO_HOOK_MODE=['"]?full['"]?\s*$/m.test(env)) return "full";
|
|
11337
11434
|
} catch {
|
|
11338
11435
|
}
|
|
@@ -11468,37 +11565,51 @@ coding patterns and the dashboard shows full history. (Y/n) `
|
|
|
11468
11565
|
rl.close();
|
|
11469
11566
|
}
|
|
11470
11567
|
}
|
|
11568
|
+
async function promptCodexCloudSetup() {
|
|
11569
|
+
if (!process.stdin.isTTY) return false;
|
|
11570
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
11571
|
+
return new Promise((resolve4) => {
|
|
11572
|
+
rl.question(
|
|
11573
|
+
"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) ",
|
|
11574
|
+
(answer) => {
|
|
11575
|
+
rl.close();
|
|
11576
|
+
const a = answer.trim().toLowerCase();
|
|
11577
|
+
resolve4(a === "y" || a === "yes");
|
|
11578
|
+
}
|
|
11579
|
+
);
|
|
11580
|
+
});
|
|
11581
|
+
}
|
|
11471
11582
|
function ensureSynkroDir() {
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11583
|
+
mkdirSync10(SYNKRO_DIR6, { recursive: true });
|
|
11584
|
+
mkdirSync10(HOOKS_DIR, { recursive: true });
|
|
11585
|
+
mkdirSync10(BIN_DIR, { recursive: true });
|
|
11586
|
+
mkdirSync10(OFFSETS_DIR, { recursive: true });
|
|
11587
|
+
mkdirSync10(join11(SYNKRO_DIR6, "sessions"), { recursive: true });
|
|
11477
11588
|
}
|
|
11478
11589
|
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 =
|
|
11590
|
+
const installExtractCorePath = join11(HOOKS_DIR, "installExtractCore.ts");
|
|
11591
|
+
const bashScriptPath = join11(HOOKS_DIR, "cc-bash-judge.ts");
|
|
11592
|
+
const bashFollowupScriptPath = join11(HOOKS_DIR, "cc-bash-followup.ts");
|
|
11593
|
+
const editPrecheckScriptPath = join11(HOOKS_DIR, "cc-edit-precheck.ts");
|
|
11594
|
+
const cwePrecheckScriptPath = join11(HOOKS_DIR, "cc-cwe-precheck.ts");
|
|
11595
|
+
const cvePrecheckScriptPath = join11(HOOKS_DIR, "cc-cve-precheck.ts");
|
|
11596
|
+
const planJudgeScriptPath = join11(HOOKS_DIR, "cc-plan-judge.ts");
|
|
11597
|
+
const agentJudgeScriptPath = join11(HOOKS_DIR, "cc-agent-judge.ts");
|
|
11598
|
+
const stopSummaryScriptPath = join11(HOOKS_DIR, "cc-stop-summary.ts");
|
|
11599
|
+
const sessionStartScriptPath = join11(HOOKS_DIR, "cc-session-start.ts");
|
|
11600
|
+
const transcriptSyncScriptPath = join11(HOOKS_DIR, "cc-transcript-sync.ts");
|
|
11601
|
+
const userPromptSubmitScriptPath = join11(HOOKS_DIR, "cc-user-prompt-submit.ts");
|
|
11602
|
+
const commonScriptPath = join11(HOOKS_DIR, "_synkro-common.ts");
|
|
11603
|
+
const commonBashScriptPath = join11(HOOKS_DIR, "_synkro-common.sh");
|
|
11604
|
+
const installScanScriptPath = join11(HOOKS_DIR, "cc-install-scan.ts");
|
|
11605
|
+
const cursorBashJudgePath = join11(HOOKS_DIR, "cursor-bash-judge.ts");
|
|
11606
|
+
const cursorEditCapturePath = join11(HOOKS_DIR, "cursor-edit-capture.ts");
|
|
11607
|
+
const cursorAgentCapturePath = join11(HOOKS_DIR, "cursor-agent-capture.ts");
|
|
11608
|
+
const mcpStdioProxyPath = join11(HOOKS_DIR, "mcp-stdio-proxy.ts");
|
|
11609
|
+
const taskActivateIntentScriptPath = join11(HOOKS_DIR, "cc-task-activate-intent.ts");
|
|
11610
|
+
const mcpGateScriptPath = join11(HOOKS_DIR, "cc-mcp-gate.ts");
|
|
11500
11611
|
if (mode === "stub") {
|
|
11501
|
-
const stubCommonPath2 =
|
|
11612
|
+
const stubCommonPath2 = join11(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
11502
11613
|
const stubFiles = [
|
|
11503
11614
|
[stubCommonPath2, STUB_COMMON_TS],
|
|
11504
11615
|
[bashScriptPath, STUB_BASH_JUDGE_TS],
|
|
@@ -11527,7 +11638,7 @@ function writeHookScripts(mode = "stub") {
|
|
|
11527
11638
|
chmodSync2(mcpStdioProxyPath, 493);
|
|
11528
11639
|
for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
|
|
11529
11640
|
try {
|
|
11530
|
-
unlinkSync4(
|
|
11641
|
+
unlinkSync4(join11(HOOKS_DIR, stale));
|
|
11531
11642
|
} catch {
|
|
11532
11643
|
}
|
|
11533
11644
|
}
|
|
@@ -11571,7 +11682,7 @@ function writeHookScripts(mode = "stub") {
|
|
|
11571
11682
|
writeFileSync8(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
|
|
11572
11683
|
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
11684
|
writeFileSync8(taskActivateIntentScriptPath, STUB_TASK_ACTIVATE_INTENT_TS, "utf-8");
|
|
11574
|
-
const stubCommonPath =
|
|
11685
|
+
const stubCommonPath = join11(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
11575
11686
|
writeFileSync8(stubCommonPath, STUB_COMMON_TS, "utf-8");
|
|
11576
11687
|
chmodSync2(stubCommonPath, 493);
|
|
11577
11688
|
writeFileSync8(mcpGateScriptPath, STUB_MCP_GATE_TS, "utf-8");
|
|
@@ -11625,11 +11736,11 @@ function shellQuoteSingle(value) {
|
|
|
11625
11736
|
}
|
|
11626
11737
|
function resolveSynkroBundle() {
|
|
11627
11738
|
const scriptPath = process.argv[1];
|
|
11628
|
-
if (scriptPath &&
|
|
11739
|
+
if (scriptPath && existsSync12(scriptPath)) return scriptPath;
|
|
11629
11740
|
return null;
|
|
11630
11741
|
}
|
|
11631
11742
|
function writeConfigEnv(opts) {
|
|
11632
|
-
const credsPath =
|
|
11743
|
+
const credsPath = join11(SYNKRO_DIR6, "credentials.json");
|
|
11633
11744
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
11634
11745
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
11635
11746
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -11645,7 +11756,7 @@ function writeConfigEnv(opts) {
|
|
|
11645
11756
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11646
11757
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11647
11758
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11648
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.7.
|
|
11759
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.7.24")}`
|
|
11649
11760
|
];
|
|
11650
11761
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11651
11762
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -11678,7 +11789,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
11678
11789
|
assertGatewayAllowed(gatewayUrl);
|
|
11679
11790
|
let stored = "";
|
|
11680
11791
|
try {
|
|
11681
|
-
stored =
|
|
11792
|
+
stored = readFileSync11(CLOUD_JWT_PATH, "utf-8").trim();
|
|
11682
11793
|
} catch {
|
|
11683
11794
|
}
|
|
11684
11795
|
if (stored && !jwtExpired(stored)) return stored;
|
|
@@ -11737,7 +11848,7 @@ async function provisionCloudContainer(opts) {
|
|
|
11737
11848
|
let cursorApiKey = "";
|
|
11738
11849
|
if (cursorTotal > 0) {
|
|
11739
11850
|
try {
|
|
11740
|
-
cursorApiKey =
|
|
11851
|
+
cursorApiKey = readFileSync11(join11(SYNKRO_DIR6, "cursor-creds", "api-key"), "utf-8").trim();
|
|
11741
11852
|
} catch {
|
|
11742
11853
|
}
|
|
11743
11854
|
}
|
|
@@ -11894,8 +12005,8 @@ async function verifyCloudGrader(jwt2) {
|
|
|
11894
12005
|
}
|
|
11895
12006
|
function readPersistedDeployLocation() {
|
|
11896
12007
|
try {
|
|
11897
|
-
if (
|
|
11898
|
-
const m =
|
|
12008
|
+
if (existsSync12(CONFIG_PATH2)) {
|
|
12009
|
+
const m = readFileSync11(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
|
|
11899
12010
|
if (m?.[1] === "cloud") return "cloud";
|
|
11900
12011
|
}
|
|
11901
12012
|
} catch {
|
|
@@ -11903,8 +12014,8 @@ function readPersistedDeployLocation() {
|
|
|
11903
12014
|
return "local";
|
|
11904
12015
|
}
|
|
11905
12016
|
function updateConfigEnvLocation(location) {
|
|
11906
|
-
if (!
|
|
11907
|
-
let env =
|
|
12017
|
+
if (!existsSync12(CONFIG_PATH2)) return;
|
|
12018
|
+
let env = readFileSync11(CONFIG_PATH2, "utf-8");
|
|
11908
12019
|
const set = (k, v) => {
|
|
11909
12020
|
const re = new RegExp(`^${k}=.*$`, "m");
|
|
11910
12021
|
const line = `${k}='${v}'`;
|
|
@@ -11919,7 +12030,7 @@ async function applyMcpConfig(opts) {
|
|
|
11919
12030
|
if (!opts.hasClaudeCode && !opts.hasCursor) return;
|
|
11920
12031
|
let mcpJwt = "";
|
|
11921
12032
|
try {
|
|
11922
|
-
mcpJwt =
|
|
12033
|
+
mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
11923
12034
|
} catch {
|
|
11924
12035
|
}
|
|
11925
12036
|
if (!mcpJwt) {
|
|
@@ -12037,8 +12148,8 @@ function resolveDeploymentMode() {
|
|
|
12037
12148
|
const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
|
|
12038
12149
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
12039
12150
|
try {
|
|
12040
|
-
if (
|
|
12041
|
-
const m =
|
|
12151
|
+
if (existsSync12(CONFIG_PATH2)) {
|
|
12152
|
+
const m = readFileSync11(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
|
|
12042
12153
|
const val = m?.[1]?.toLowerCase();
|
|
12043
12154
|
if (val === "bare-host" || val === "docker") return val;
|
|
12044
12155
|
}
|
|
@@ -12065,16 +12176,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
12065
12176
|
meta.cc_version = execSync6("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
12066
12177
|
} catch {
|
|
12067
12178
|
}
|
|
12068
|
-
const claudeDir =
|
|
12179
|
+
const claudeDir = join11(homedir11(), ".claude");
|
|
12069
12180
|
try {
|
|
12070
|
-
const settings = JSON.parse(
|
|
12181
|
+
const settings = JSON.parse(readFileSync11(join11(claudeDir, "settings.json"), "utf-8"));
|
|
12071
12182
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
12072
12183
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
12073
12184
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
12074
12185
|
} catch {
|
|
12075
12186
|
}
|
|
12076
12187
|
try {
|
|
12077
|
-
const mcpCache = JSON.parse(
|
|
12188
|
+
const mcpCache = JSON.parse(readFileSync11(join11(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
12078
12189
|
const mcpNames = Object.keys(mcpCache);
|
|
12079
12190
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
12080
12191
|
} catch {
|
|
@@ -12086,10 +12197,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
12086
12197
|
} catch {
|
|
12087
12198
|
}
|
|
12088
12199
|
try {
|
|
12089
|
-
const sessionsDir =
|
|
12200
|
+
const sessionsDir = join11(claudeDir, "sessions");
|
|
12090
12201
|
const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
12091
12202
|
for (const f of files) {
|
|
12092
|
-
const s = JSON.parse(
|
|
12203
|
+
const s = JSON.parse(readFileSync11(join11(sessionsDir, f), "utf-8"));
|
|
12093
12204
|
if (s.version) {
|
|
12094
12205
|
meta.cc_version = meta.cc_version || s.version;
|
|
12095
12206
|
break;
|
|
@@ -12277,9 +12388,9 @@ async function installCommand(opts = {}) {
|
|
|
12277
12388
|
const scripts = writeHookScripts(hookMode);
|
|
12278
12389
|
console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
|
|
12279
12390
|
for (const mode of ["edit", "bash"]) {
|
|
12280
|
-
const pidFile =
|
|
12391
|
+
const pidFile = join11(SYNKRO_DIR6, "daemon", mode, "daemon.pid");
|
|
12281
12392
|
try {
|
|
12282
|
-
const pid = parseInt(
|
|
12393
|
+
const pid = parseInt(readFileSync11(pidFile, "utf-8").trim(), 10);
|
|
12283
12394
|
if (pid > 0) {
|
|
12284
12395
|
process.kill(pid, "SIGTERM");
|
|
12285
12396
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -12362,7 +12473,7 @@ async function installCommand(opts = {}) {
|
|
|
12362
12473
|
if (mintResp.ok) {
|
|
12363
12474
|
const minted = await mintResp.json();
|
|
12364
12475
|
mcpJwt = minted.token;
|
|
12365
|
-
writeFileSync8(
|
|
12476
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), mcpJwt + "\n", { mode: 384 });
|
|
12366
12477
|
} else {
|
|
12367
12478
|
console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
|
|
12368
12479
|
}
|
|
@@ -12390,7 +12501,7 @@ async function installCommand(opts = {}) {
|
|
|
12390
12501
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
12391
12502
|
}
|
|
12392
12503
|
const minted = await mintResp.json();
|
|
12393
|
-
writeFileSync8(
|
|
12504
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
12394
12505
|
const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
12395
12506
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
12396
12507
|
console.log(` url: ${mcp.url}`);
|
|
@@ -12407,8 +12518,8 @@ async function installCommand(opts = {}) {
|
|
|
12407
12518
|
if (hasCursor && !opts.noMcp) {
|
|
12408
12519
|
try {
|
|
12409
12520
|
if (useLocalMcp) {
|
|
12410
|
-
const jwtPath =
|
|
12411
|
-
if (!
|
|
12521
|
+
const jwtPath = join11(SYNKRO_DIR6, ".mcp-jwt");
|
|
12522
|
+
if (!existsSync12(jwtPath)) {
|
|
12412
12523
|
const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
|
|
12413
12524
|
method: "POST",
|
|
12414
12525
|
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
|
|
@@ -12436,7 +12547,7 @@ async function installCommand(opts = {}) {
|
|
|
12436
12547
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
12437
12548
|
}
|
|
12438
12549
|
const minted = await mintResp.json();
|
|
12439
|
-
writeFileSync8(
|
|
12550
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
12440
12551
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
12441
12552
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
12442
12553
|
console.log(` url: ${mcp.url}`);
|
|
@@ -12518,7 +12629,7 @@ async function installCommand(opts = {}) {
|
|
|
12518
12629
|
const ready = await waitForContainerReady(6e4);
|
|
12519
12630
|
if (ready) {
|
|
12520
12631
|
console.log(" \u2713 container ready");
|
|
12521
|
-
const mcpJwt =
|
|
12632
|
+
const mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12522
12633
|
try {
|
|
12523
12634
|
const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
|
|
12524
12635
|
method: "POST",
|
|
@@ -12553,6 +12664,11 @@ async function installCommand(opts = {}) {
|
|
|
12553
12664
|
if (hasCursor) await promptCursorApiKey(opts);
|
|
12554
12665
|
await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
|
|
12555
12666
|
cloudGradeOk = await verifyCloudGrader(token);
|
|
12667
|
+
if (await promptCodexCloudSetup()) {
|
|
12668
|
+
const { setupCodexCloud: setupCodexCloud2 } = await Promise.resolve().then(() => (init_codexCloudSetup(), codexCloudSetup_exports));
|
|
12669
|
+
const res = await setupCodexCloud2(gatewayUrl, token, (m) => console.log(` ${m}`));
|
|
12670
|
+
if (!res.ok) console.warn(` \u26A0 Codex cloud setup skipped: ${res.error}`);
|
|
12671
|
+
}
|
|
12556
12672
|
}
|
|
12557
12673
|
if (transcriptConsent) {
|
|
12558
12674
|
const repo = detectGitRepo2();
|
|
@@ -12561,7 +12677,7 @@ async function installCommand(opts = {}) {
|
|
|
12561
12677
|
try {
|
|
12562
12678
|
let mcpToken = "";
|
|
12563
12679
|
try {
|
|
12564
|
-
mcpToken =
|
|
12680
|
+
mcpToken = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12565
12681
|
} catch {
|
|
12566
12682
|
}
|
|
12567
12683
|
if (mcpToken) {
|
|
@@ -12697,8 +12813,8 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
12697
12813
|
try {
|
|
12698
12814
|
const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12699
12815
|
if (!root) return;
|
|
12700
|
-
if (root ===
|
|
12701
|
-
const fp =
|
|
12816
|
+
if (root === homedir11()) return;
|
|
12817
|
+
const fp = join11(root, "synkro.toml");
|
|
12702
12818
|
let hasFile = false;
|
|
12703
12819
|
try {
|
|
12704
12820
|
hasFile = statSync(fp).isFile();
|
|
@@ -12743,9 +12859,9 @@ function readFullSynkroFile() {
|
|
|
12743
12859
|
try {
|
|
12744
12860
|
const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12745
12861
|
if (!root) return null;
|
|
12746
|
-
const fp =
|
|
12747
|
-
if (!
|
|
12748
|
-
const parsed = parseSynkroToml2(
|
|
12862
|
+
const fp = join11(root, "synkro.toml");
|
|
12863
|
+
if (!existsSync12(fp)) return null;
|
|
12864
|
+
const parsed = parseSynkroToml2(readFileSync11(fp, "utf-8"));
|
|
12749
12865
|
const valid = ["claude-code", "cursor"];
|
|
12750
12866
|
const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
|
|
12751
12867
|
const resolved = resolveGraderPool(parsed);
|
|
@@ -12783,7 +12899,7 @@ function reconcileHarness() {
|
|
|
12783
12899
|
console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
|
|
12784
12900
|
const scripts = writeHookScripts(resolvePersistedHookMode());
|
|
12785
12901
|
console.log("Wrote hook scripts to ~/.synkro/hooks/");
|
|
12786
|
-
const ccSettings =
|
|
12902
|
+
const ccSettings = join11(homedir11(), ".claude", "settings.json");
|
|
12787
12903
|
if (wantCC) {
|
|
12788
12904
|
installCCHooks(ccSettings, {
|
|
12789
12905
|
bashJudgeScriptPath: scripts.bashScript,
|
|
@@ -12803,7 +12919,7 @@ function reconcileHarness() {
|
|
|
12803
12919
|
});
|
|
12804
12920
|
console.log(" \u2713 Claude Code hooks registered");
|
|
12805
12921
|
try {
|
|
12806
|
-
const mcpJwt =
|
|
12922
|
+
const mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12807
12923
|
if (mcpJwt) {
|
|
12808
12924
|
installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt, local: true });
|
|
12809
12925
|
console.log(" \u2713 Claude Code MCP registered");
|
|
@@ -12815,7 +12931,7 @@ function reconcileHarness() {
|
|
|
12815
12931
|
if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
|
|
12816
12932
|
if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
|
|
12817
12933
|
}
|
|
12818
|
-
const cursorHooks =
|
|
12934
|
+
const cursorHooks = join11(homedir11(), ".cursor", "hooks.json");
|
|
12819
12935
|
if (wantCursor) {
|
|
12820
12936
|
installCursorHooks(cursorHooks, {
|
|
12821
12937
|
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
@@ -12897,7 +13013,7 @@ async function syncSkillFiles() {
|
|
|
12897
13013
|
if (resolved.length === 0) return;
|
|
12898
13014
|
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
12899
13015
|
const tasks = resolved.map((fp) => {
|
|
12900
|
-
const content =
|
|
13016
|
+
const content = readFileSync11(fp, "utf-8");
|
|
12901
13017
|
const source = `skill:${fp.split("/").pop()}`;
|
|
12902
13018
|
if (!content.trim()) {
|
|
12903
13019
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
@@ -12915,15 +13031,15 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
|
12915
13031
|
const roots = [];
|
|
12916
13032
|
const add = (p) => {
|
|
12917
13033
|
try {
|
|
12918
|
-
if (p &&
|
|
13034
|
+
if (p && existsSync12(p) && statSync(p).isDirectory()) roots.push(p);
|
|
12919
13035
|
} catch {
|
|
12920
13036
|
}
|
|
12921
13037
|
};
|
|
12922
|
-
add(
|
|
12923
|
-
add(
|
|
13038
|
+
add(join11(homedir11(), ".claude", "skills"));
|
|
13039
|
+
add(join11(homedir11(), ".agents", "skills"));
|
|
12924
13040
|
if (repoRoot) {
|
|
12925
|
-
add(
|
|
12926
|
-
add(
|
|
13041
|
+
add(join11(repoRoot, ".claude", "skills"));
|
|
13042
|
+
add(join11(repoRoot, ".agents", "skills"));
|
|
12927
13043
|
}
|
|
12928
13044
|
const out = [];
|
|
12929
13045
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -12934,7 +13050,7 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
|
12934
13050
|
try {
|
|
12935
13051
|
const st = statSync(file);
|
|
12936
13052
|
if (!st.isFile() || st.size > 2e5) return;
|
|
12937
|
-
content =
|
|
13053
|
+
content = readFileSync11(file, "utf-8");
|
|
12938
13054
|
} catch {
|
|
12939
13055
|
return;
|
|
12940
13056
|
}
|
|
@@ -12953,7 +13069,7 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
|
12953
13069
|
}
|
|
12954
13070
|
for (const entry of entries) {
|
|
12955
13071
|
if (entry.startsWith(".")) continue;
|
|
12956
|
-
const full =
|
|
13072
|
+
const full = join11(root, entry);
|
|
12957
13073
|
let st;
|
|
12958
13074
|
try {
|
|
12959
13075
|
st = statSync(full);
|
|
@@ -12961,8 +13077,8 @@ function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
|
12961
13077
|
continue;
|
|
12962
13078
|
}
|
|
12963
13079
|
if (st.isDirectory()) {
|
|
12964
|
-
const skillMd =
|
|
12965
|
-
if (
|
|
13080
|
+
const skillMd = join11(full, "SKILL.md");
|
|
13081
|
+
if (existsSync12(skillMd)) consider(skillMd, entry);
|
|
12966
13082
|
} else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
|
|
12967
13083
|
consider(full, entry.replace(/\.mdx?$/i, ""));
|
|
12968
13084
|
}
|
|
@@ -13000,7 +13116,7 @@ async function discoverAndIngestSkills() {
|
|
|
13000
13116
|
if (sf?.skills?.length) {
|
|
13001
13117
|
for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
|
|
13002
13118
|
try {
|
|
13003
|
-
excludeHashes.add(createHash("sha256").update(
|
|
13119
|
+
excludeHashes.add(createHash("sha256").update(readFileSync11(fp, "utf-8")).digest("hex"));
|
|
13004
13120
|
} catch {
|
|
13005
13121
|
}
|
|
13006
13122
|
}
|
|
@@ -13010,7 +13126,7 @@ async function discoverAndIngestSkills() {
|
|
|
13010
13126
|
const setHash = discoverySetHash(found);
|
|
13011
13127
|
let prev = "";
|
|
13012
13128
|
try {
|
|
13013
|
-
prev =
|
|
13129
|
+
prev = readFileSync11(SKILLS_DISCOVERED_PATH, "utf-8").trim();
|
|
13014
13130
|
} catch {
|
|
13015
13131
|
}
|
|
13016
13132
|
if (prev === setHash) return;
|
|
@@ -13051,17 +13167,17 @@ function detectGitRepo2() {
|
|
|
13051
13167
|
function getClaudeProjectsFolder() {
|
|
13052
13168
|
const cwd = process.cwd();
|
|
13053
13169
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
13054
|
-
const projectsDir =
|
|
13055
|
-
return
|
|
13170
|
+
const projectsDir = join11(homedir11(), ".claude", "projects", sanitized);
|
|
13171
|
+
return existsSync12(projectsDir) ? projectsDir : null;
|
|
13056
13172
|
}
|
|
13057
13173
|
function extractSessionInsights(projectsDir) {
|
|
13058
13174
|
const insights = [];
|
|
13059
13175
|
const files = readdirSync3(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
13060
13176
|
for (const file of files) {
|
|
13061
13177
|
const sessionId = file.replace(".jsonl", "");
|
|
13062
|
-
const filePath =
|
|
13178
|
+
const filePath = join11(projectsDir, file);
|
|
13063
13179
|
try {
|
|
13064
|
-
const content =
|
|
13180
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13065
13181
|
const lines = content.split("\n").filter(Boolean);
|
|
13066
13182
|
for (let i = 0; i < lines.length; i++) {
|
|
13067
13183
|
try {
|
|
@@ -13140,14 +13256,14 @@ function cursorProjectSlug(workspaceRoot) {
|
|
|
13140
13256
|
return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
13141
13257
|
}
|
|
13142
13258
|
function getCursorTranscriptsDir() {
|
|
13143
|
-
const dir =
|
|
13144
|
-
return
|
|
13259
|
+
const dir = join11(homedir11(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
|
|
13260
|
+
return existsSync12(dir) ? dir : null;
|
|
13145
13261
|
}
|
|
13146
13262
|
function isSafeConvId(id) {
|
|
13147
13263
|
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
13148
13264
|
}
|
|
13149
13265
|
function parseCursorTranscriptFile(filePath) {
|
|
13150
|
-
const content =
|
|
13266
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13151
13267
|
const lines = content.split("\n").filter(Boolean);
|
|
13152
13268
|
const messages = [];
|
|
13153
13269
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -13179,8 +13295,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13179
13295
|
for (let i = 0; i < convDirs.length; i++) {
|
|
13180
13296
|
const convId = convDirs[i];
|
|
13181
13297
|
if (!isSafeConvId(convId)) continue;
|
|
13182
|
-
const filePath =
|
|
13183
|
-
if (!
|
|
13298
|
+
const filePath = join11(dir, convId, `${convId}.jsonl`);
|
|
13299
|
+
if (!existsSync12(filePath)) continue;
|
|
13184
13300
|
try {
|
|
13185
13301
|
const all = parseCursorTranscriptFile(filePath);
|
|
13186
13302
|
const messages = all.length > 500 ? all.slice(-500) : all;
|
|
@@ -13202,8 +13318,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13202
13318
|
process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
|
|
13203
13319
|
}
|
|
13204
13320
|
try {
|
|
13205
|
-
const lc =
|
|
13206
|
-
writeFileSync8(
|
|
13321
|
+
const lc = readFileSync11(filePath, "utf-8").split("\n").filter(Boolean).length;
|
|
13322
|
+
writeFileSync8(join11(OFFSETS_DIR, convId), String(lc), "utf-8");
|
|
13207
13323
|
} catch {
|
|
13208
13324
|
}
|
|
13209
13325
|
}
|
|
@@ -13211,7 +13327,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13211
13327
|
return { sessions: totalSessions, messages: totalMessages };
|
|
13212
13328
|
}
|
|
13213
13329
|
function parseTranscriptFile(filePath) {
|
|
13214
|
-
const content =
|
|
13330
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13215
13331
|
const lines = content.split("\n").filter(Boolean);
|
|
13216
13332
|
const messages = [];
|
|
13217
13333
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -13259,7 +13375,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13259
13375
|
for (let i = 0; i < files.length; i++) {
|
|
13260
13376
|
const file = files[i];
|
|
13261
13377
|
const sessionId = file.replace(".jsonl", "");
|
|
13262
|
-
const filePath =
|
|
13378
|
+
const filePath = join11(projectsDir, file);
|
|
13263
13379
|
try {
|
|
13264
13380
|
const allMessages = parseTranscriptFile(filePath);
|
|
13265
13381
|
const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
|
|
@@ -13281,9 +13397,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13281
13397
|
process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
|
|
13282
13398
|
}
|
|
13283
13399
|
try {
|
|
13284
|
-
const content =
|
|
13400
|
+
const content = readFileSync11(join11(projectsDir, file), "utf-8");
|
|
13285
13401
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
13286
|
-
writeFileSync8(
|
|
13402
|
+
writeFileSync8(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
13287
13403
|
} catch {
|
|
13288
13404
|
}
|
|
13289
13405
|
}
|
|
@@ -13304,7 +13420,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
13304
13420
|
const sessions = [];
|
|
13305
13421
|
for (const file of batch) {
|
|
13306
13422
|
const sessionId = file.replace(".jsonl", "");
|
|
13307
|
-
const filePath =
|
|
13423
|
+
const filePath = join11(projectsDir, file);
|
|
13308
13424
|
try {
|
|
13309
13425
|
const allMessages = parseTranscriptFile(filePath);
|
|
13310
13426
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -13333,18 +13449,18 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
13333
13449
|
}
|
|
13334
13450
|
for (const file of batch) {
|
|
13335
13451
|
const sessionId = file.replace(".jsonl", "");
|
|
13336
|
-
const filePath =
|
|
13452
|
+
const filePath = join11(projectsDir, file);
|
|
13337
13453
|
try {
|
|
13338
|
-
const content =
|
|
13454
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13339
13455
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
13340
|
-
writeFileSync8(
|
|
13456
|
+
writeFileSync8(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
13341
13457
|
} catch {
|
|
13342
13458
|
}
|
|
13343
13459
|
}
|
|
13344
13460
|
}
|
|
13345
13461
|
return { sessions: totalSessions, messages: totalMessages };
|
|
13346
13462
|
}
|
|
13347
|
-
var
|
|
13463
|
+
var SYNKRO_DIR6, HOOKS_DIR, BIN_DIR, CONFIG_PATH2, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
|
|
13348
13464
|
var init_install = __esm({
|
|
13349
13465
|
"cli/commands/install.ts"() {
|
|
13350
13466
|
"use strict";
|
|
@@ -13363,10 +13479,10 @@ var init_install = __esm({
|
|
|
13363
13479
|
init_claudeDesktopTap();
|
|
13364
13480
|
init_dockerInstall();
|
|
13365
13481
|
init_setupToken();
|
|
13366
|
-
|
|
13367
|
-
HOOKS_DIR =
|
|
13368
|
-
BIN_DIR =
|
|
13369
|
-
CONFIG_PATH2 =
|
|
13482
|
+
SYNKRO_DIR6 = join11(homedir11(), ".synkro");
|
|
13483
|
+
HOOKS_DIR = join11(SYNKRO_DIR6, "hooks");
|
|
13484
|
+
BIN_DIR = join11(SYNKRO_DIR6, "bin");
|
|
13485
|
+
CONFIG_PATH2 = join11(SYNKRO_DIR6, "config.env");
|
|
13370
13486
|
MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
|
|
13371
13487
|
import { readFileSync } from 'node:fs';
|
|
13372
13488
|
import { homedir } from 'node:os';
|
|
@@ -13477,21 +13593,21 @@ rl.on('line', async (line) => {
|
|
|
13477
13593
|
}
|
|
13478
13594
|
});
|
|
13479
13595
|
`;
|
|
13480
|
-
OFFSETS_DIR =
|
|
13481
|
-
CLOUD_JWT_PATH =
|
|
13482
|
-
SKILLS_DISCOVERED_PATH =
|
|
13596
|
+
OFFSETS_DIR = join11(SYNKRO_DIR6, ".transcript-offsets");
|
|
13597
|
+
CLOUD_JWT_PATH = join11(SYNKRO_DIR6, ".cloud-jwt");
|
|
13598
|
+
SKILLS_DISCOVERED_PATH = join11(SYNKRO_DIR6, ".skills-discovered");
|
|
13483
13599
|
}
|
|
13484
13600
|
});
|
|
13485
13601
|
|
|
13486
13602
|
// cli/local-cc/install.ts
|
|
13487
|
-
import { existsSync as
|
|
13488
|
-
import { join as
|
|
13489
|
-
import { homedir as
|
|
13490
|
-
import { spawnSync as
|
|
13603
|
+
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";
|
|
13604
|
+
import { join as join12 } from "path";
|
|
13605
|
+
import { homedir as homedir12 } from "os";
|
|
13606
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
13491
13607
|
function writePluginFiles() {
|
|
13492
13608
|
for (const c of CHANNELS) {
|
|
13493
|
-
|
|
13494
|
-
|
|
13609
|
+
mkdirSync11(c.sessionDir, { recursive: true });
|
|
13610
|
+
mkdirSync11(c.pluginSettingsDir, { recursive: true });
|
|
13495
13611
|
writeFileSync9(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
13496
13612
|
writeFileSync9(
|
|
13497
13613
|
c.pluginSettingsPath,
|
|
@@ -13514,7 +13630,7 @@ function writePluginFiles() {
|
|
|
13514
13630
|
}
|
|
13515
13631
|
function runBunInstall() {
|
|
13516
13632
|
for (const c of CHANNELS) {
|
|
13517
|
-
const r =
|
|
13633
|
+
const r = spawnSync6("bun", ["install", "--silent"], {
|
|
13518
13634
|
cwd: c.sessionDir,
|
|
13519
13635
|
encoding: "utf-8",
|
|
13520
13636
|
timeout: 12e4
|
|
@@ -13527,10 +13643,10 @@ function runBunInstall() {
|
|
|
13527
13643
|
}
|
|
13528
13644
|
}
|
|
13529
13645
|
function safelyMutateClaudeJson(mutator) {
|
|
13530
|
-
if (!
|
|
13646
|
+
if (!existsSync13(CLAUDE_JSON_PATH)) {
|
|
13531
13647
|
return;
|
|
13532
13648
|
}
|
|
13533
|
-
const originalText =
|
|
13649
|
+
const originalText = readFileSync12(CLAUDE_JSON_PATH, "utf-8");
|
|
13534
13650
|
let parsed;
|
|
13535
13651
|
try {
|
|
13536
13652
|
parsed = JSON.parse(originalText);
|
|
@@ -13630,15 +13746,15 @@ function patchClaudeJson() {
|
|
|
13630
13746
|
});
|
|
13631
13747
|
}
|
|
13632
13748
|
function installLocalCC() {
|
|
13633
|
-
let bunCheck =
|
|
13749
|
+
let bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
|
|
13634
13750
|
if (bunCheck.status !== 0) {
|
|
13635
13751
|
if (process.platform === "darwin") {
|
|
13636
13752
|
console.log(" Installing bun via brew...");
|
|
13637
|
-
const brewR =
|
|
13753
|
+
const brewR = spawnSync6("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
13638
13754
|
if (brewR.status !== 0) {
|
|
13639
13755
|
throw new LocalCCInstallError("bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash");
|
|
13640
13756
|
}
|
|
13641
|
-
bunCheck =
|
|
13757
|
+
bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
|
|
13642
13758
|
if (bunCheck.status !== 0) {
|
|
13643
13759
|
throw new LocalCCInstallError("bun installed but not found on PATH. Restart your terminal and re-run install.");
|
|
13644
13760
|
}
|
|
@@ -13672,42 +13788,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
|
|
|
13672
13788
|
var init_install2 = __esm({
|
|
13673
13789
|
"cli/local-cc/install.ts"() {
|
|
13674
13790
|
"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 =
|
|
13791
|
+
CLAUDE_JSON_BACKUP_PATH = join12(homedir12(), ".claude.json.synkro-bak");
|
|
13792
|
+
SESSION_DIR = join12(homedir12(), ".synkro", "cc_sessions");
|
|
13793
|
+
PLUGIN_PATH = join12(SESSION_DIR, "synkro-channel.ts");
|
|
13794
|
+
PLUGIN_PKG_PATH = join12(SESSION_DIR, "package.json");
|
|
13795
|
+
PLUGIN_SETTINGS_DIR = join12(SESSION_DIR, ".claude");
|
|
13796
|
+
PLUGIN_SETTINGS_PATH = join12(PLUGIN_SETTINGS_DIR, "settings.json");
|
|
13797
|
+
PROJECT_MCP_PATH = join12(SESSION_DIR, ".mcp.json");
|
|
13798
|
+
CLAUDE_JSON_PATH = join12(homedir12(), ".claude.json");
|
|
13799
|
+
RUN_SCRIPT_PATH = join12(SESSION_DIR, "run-claude.sh");
|
|
13684
13800
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
13685
13801
|
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 =
|
|
13802
|
+
SESSION_DIR_2 = join12(homedir12(), ".synkro", "cc_sessions_2");
|
|
13803
|
+
PLUGIN_PATH_2 = join12(SESSION_DIR_2, "synkro-channel.ts");
|
|
13804
|
+
PLUGIN_PKG_PATH_2 = join12(SESSION_DIR_2, "package.json");
|
|
13805
|
+
PLUGIN_SETTINGS_DIR_2 = join12(SESSION_DIR_2, ".claude");
|
|
13806
|
+
PLUGIN_SETTINGS_PATH_2 = join12(PLUGIN_SETTINGS_DIR_2, "settings.json");
|
|
13807
|
+
PROJECT_MCP_PATH_2 = join12(SESSION_DIR_2, ".mcp.json");
|
|
13808
|
+
RUN_SCRIPT_PATH_2 = join12(SESSION_DIR_2, "run-claude.sh");
|
|
13693
13809
|
TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
|
|
13694
13810
|
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 =
|
|
13811
|
+
SESSION_DIR_3 = join12(homedir12(), ".synkro", "cc_sessions_3");
|
|
13812
|
+
PLUGIN_PATH_3 = join12(SESSION_DIR_3, "synkro-channel.ts");
|
|
13813
|
+
PLUGIN_PKG_PATH_3 = join12(SESSION_DIR_3, "package.json");
|
|
13814
|
+
PLUGIN_SETTINGS_DIR_3 = join12(SESSION_DIR_3, ".claude");
|
|
13815
|
+
PLUGIN_SETTINGS_PATH_3 = join12(PLUGIN_SETTINGS_DIR_3, "settings.json");
|
|
13816
|
+
PROJECT_MCP_PATH_3 = join12(SESSION_DIR_3, ".mcp.json");
|
|
13817
|
+
RUN_SCRIPT_PATH_3 = join12(SESSION_DIR_3, "run-claude.sh");
|
|
13702
13818
|
TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
|
|
13703
13819
|
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 =
|
|
13820
|
+
SESSION_DIR_4 = join12(homedir12(), ".synkro", "cc_sessions_4");
|
|
13821
|
+
PLUGIN_PATH_4 = join12(SESSION_DIR_4, "synkro-channel.ts");
|
|
13822
|
+
PLUGIN_PKG_PATH_4 = join12(SESSION_DIR_4, "package.json");
|
|
13823
|
+
PLUGIN_SETTINGS_DIR_4 = join12(SESSION_DIR_4, ".claude");
|
|
13824
|
+
PLUGIN_SETTINGS_PATH_4 = join12(PLUGIN_SETTINGS_DIR_4, "settings.json");
|
|
13825
|
+
PROJECT_MCP_PATH_4 = join12(SESSION_DIR_4, ".mcp.json");
|
|
13826
|
+
RUN_SCRIPT_PATH_4 = join12(SESSION_DIR_4, "run-claude.sh");
|
|
13711
13827
|
TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
|
|
13712
13828
|
CHANNEL_4_PORT = 8952;
|
|
13713
13829
|
RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
|
|
@@ -13981,10 +14097,10 @@ var disconnect_exports = {};
|
|
|
13981
14097
|
__export(disconnect_exports, {
|
|
13982
14098
|
disconnectCommand: () => disconnectCommand
|
|
13983
14099
|
});
|
|
13984
|
-
import { existsSync as
|
|
13985
|
-
import { homedir as
|
|
13986
|
-
import { join as
|
|
13987
|
-
import { spawnSync as
|
|
14100
|
+
import { existsSync as existsSync14, rmSync as rmSync2, readdirSync as readdirSync4 } from "fs";
|
|
14101
|
+
import { homedir as homedir13 } from "os";
|
|
14102
|
+
import { join as join13 } from "path";
|
|
14103
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
13988
14104
|
import { createInterface as createInterface4 } from "readline";
|
|
13989
14105
|
async function tearDownLocalCC() {
|
|
13990
14106
|
const docker = dockerStatus();
|
|
@@ -13998,7 +14114,7 @@ async function tearDownLocalCC() {
|
|
|
13998
14114
|
console.log("\u2713 removed synkro-server container");
|
|
13999
14115
|
try {
|
|
14000
14116
|
const image = imageTag();
|
|
14001
|
-
const r =
|
|
14117
|
+
const r = spawnSync7("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
|
|
14002
14118
|
console.log(r.status === 0 ? `\u2713 removed Docker image ${image}` : "\xB7 no Docker image to remove");
|
|
14003
14119
|
} catch {
|
|
14004
14120
|
}
|
|
@@ -14061,34 +14177,34 @@ async function disconnectCommand(args2 = []) {
|
|
|
14061
14177
|
const desktopMcpRemoved = uninstallClaudeDesktopMcpConfig();
|
|
14062
14178
|
console.log(`${desktopMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? "removed from claude_desktop_config.json" : "no entry found"}`);
|
|
14063
14179
|
}
|
|
14064
|
-
if (
|
|
14180
|
+
if (existsSync14(SYNKRO_DIR7)) {
|
|
14065
14181
|
if (purge) {
|
|
14066
|
-
|
|
14067
|
-
console.log(`\u2713 wiped ${
|
|
14182
|
+
rmSync2(SYNKRO_DIR7, { recursive: true, force: true });
|
|
14183
|
+
console.log(`\u2713 wiped ${SYNKRO_DIR7} entirely \u2014 including all scan data and backups`);
|
|
14068
14184
|
} else {
|
|
14069
|
-
const keep = /* @__PURE__ */ new Set([
|
|
14185
|
+
const keep = /* @__PURE__ */ new Set([join13(SYNKRO_DIR7, "pgdata"), join13(SYNKRO_DIR7, "pgdata-backups")]);
|
|
14070
14186
|
const preserved = [];
|
|
14071
|
-
for (const entry of readdirSync4(
|
|
14072
|
-
const full =
|
|
14187
|
+
for (const entry of readdirSync4(SYNKRO_DIR7)) {
|
|
14188
|
+
const full = join13(SYNKRO_DIR7, entry);
|
|
14073
14189
|
if (keep.has(full)) {
|
|
14074
14190
|
preserved.push(entry);
|
|
14075
14191
|
continue;
|
|
14076
14192
|
}
|
|
14077
|
-
|
|
14193
|
+
rmSync2(full, { recursive: true, force: true });
|
|
14078
14194
|
}
|
|
14079
14195
|
if (preserved.length > 0) {
|
|
14080
|
-
console.log(`\u2713 removed Synkro config from ${
|
|
14196
|
+
console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR7} (kept your scan data: ${preserved.join(", ")})`);
|
|
14081
14197
|
console.log(" run `synkro uninstall --purge` to delete that too");
|
|
14082
14198
|
} else {
|
|
14083
|
-
console.log(`\u2713 removed ${
|
|
14199
|
+
console.log(`\u2713 removed ${SYNKRO_DIR7}`);
|
|
14084
14200
|
}
|
|
14085
14201
|
}
|
|
14086
14202
|
} else {
|
|
14087
|
-
console.log(`\xB7 ${
|
|
14203
|
+
console.log(`\xB7 ${SYNKRO_DIR7} already gone`);
|
|
14088
14204
|
}
|
|
14089
14205
|
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
14206
|
}
|
|
14091
|
-
var
|
|
14207
|
+
var SYNKRO_DIR7;
|
|
14092
14208
|
var init_disconnect = __esm({
|
|
14093
14209
|
"cli/commands/disconnect.ts"() {
|
|
14094
14210
|
"use strict";
|
|
@@ -14099,14 +14215,14 @@ var init_disconnect = __esm({
|
|
|
14099
14215
|
init_install2();
|
|
14100
14216
|
init_dockerInstall();
|
|
14101
14217
|
init_macKeychain();
|
|
14102
|
-
|
|
14218
|
+
SYNKRO_DIR7 = join13(homedir13(), ".synkro");
|
|
14103
14219
|
}
|
|
14104
14220
|
});
|
|
14105
14221
|
|
|
14106
14222
|
// cli/local-cc/turnLog.ts
|
|
14107
|
-
import { appendFileSync, existsSync as
|
|
14108
|
-
import { dirname as dirname6, join as
|
|
14109
|
-
import { homedir as
|
|
14223
|
+
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";
|
|
14224
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
14225
|
+
import { homedir as homedir14 } from "os";
|
|
14110
14226
|
function truncate(s, max = PREVIEW_MAX) {
|
|
14111
14227
|
if (s.length <= max) return s;
|
|
14112
14228
|
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
@@ -14126,7 +14242,7 @@ function extractSeverity(result) {
|
|
|
14126
14242
|
}
|
|
14127
14243
|
function appendTurn(args2) {
|
|
14128
14244
|
try {
|
|
14129
|
-
|
|
14245
|
+
mkdirSync12(dirname6(TURN_LOG_PATH), { recursive: true });
|
|
14130
14246
|
const entry = {
|
|
14131
14247
|
ts: new Date(args2.startedAt).toISOString(),
|
|
14132
14248
|
role: args2.role,
|
|
@@ -14142,11 +14258,11 @@ function appendTurn(args2) {
|
|
|
14142
14258
|
}
|
|
14143
14259
|
}
|
|
14144
14260
|
function readRecentTurns(n = 20) {
|
|
14145
|
-
if (!
|
|
14261
|
+
if (!existsSync15(TURN_LOG_PATH)) return [];
|
|
14146
14262
|
try {
|
|
14147
14263
|
const size = statSync2(TURN_LOG_PATH).size;
|
|
14148
14264
|
if (size === 0) return [];
|
|
14149
|
-
const text =
|
|
14265
|
+
const text = readFileSync13(TURN_LOG_PATH, "utf-8");
|
|
14150
14266
|
const lines = text.split("\n").filter(Boolean);
|
|
14151
14267
|
const lastN = lines.slice(-n).reverse();
|
|
14152
14268
|
return lastN.map((line) => {
|
|
@@ -14162,8 +14278,8 @@ function readRecentTurns(n = 20) {
|
|
|
14162
14278
|
}
|
|
14163
14279
|
function followTurns(onEntry) {
|
|
14164
14280
|
try {
|
|
14165
|
-
|
|
14166
|
-
if (!
|
|
14281
|
+
mkdirSync12(dirname6(TURN_LOG_PATH), { recursive: true });
|
|
14282
|
+
if (!existsSync15(TURN_LOG_PATH)) {
|
|
14167
14283
|
appendFileSync(TURN_LOG_PATH, "", "utf-8");
|
|
14168
14284
|
}
|
|
14169
14285
|
} catch {
|
|
@@ -14225,7 +14341,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
|
|
|
14225
14341
|
var init_turnLog = __esm({
|
|
14226
14342
|
"cli/local-cc/turnLog.ts"() {
|
|
14227
14343
|
"use strict";
|
|
14228
|
-
TURN_LOG_PATH =
|
|
14344
|
+
TURN_LOG_PATH = join14(homedir14(), ".synkro", "cc_sessions", "turns.log");
|
|
14229
14345
|
PREVIEW_MAX = 400;
|
|
14230
14346
|
}
|
|
14231
14347
|
});
|
|
@@ -14374,19 +14490,19 @@ var init_grade = __esm({
|
|
|
14374
14490
|
});
|
|
14375
14491
|
|
|
14376
14492
|
// cli/local-cc/pueue.ts
|
|
14377
|
-
import { execFileSync as execFileSync3, spawnSync as
|
|
14378
|
-
import { homedir as
|
|
14379
|
-
import { join as
|
|
14493
|
+
import { execFileSync as execFileSync3, spawnSync as spawnSync8, spawn as spawn2 } from "child_process";
|
|
14494
|
+
import { homedir as homedir15 } from "os";
|
|
14495
|
+
import { join as join15 } from "path";
|
|
14380
14496
|
import { connect as connect2 } from "net";
|
|
14381
14497
|
function pueueAvailable() {
|
|
14382
|
-
const r =
|
|
14498
|
+
const r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14383
14499
|
if (r.status !== 0) {
|
|
14384
14500
|
throw new PueueError("pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.");
|
|
14385
14501
|
}
|
|
14386
14502
|
}
|
|
14387
14503
|
function statusJson() {
|
|
14388
14504
|
pueueAvailable();
|
|
14389
|
-
const r =
|
|
14505
|
+
const r = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8" });
|
|
14390
14506
|
if (r.status !== 0) {
|
|
14391
14507
|
throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || "unknown error"} \u2014 is pueued running?`);
|
|
14392
14508
|
}
|
|
@@ -14431,18 +14547,18 @@ function startTask(opts = {}) {
|
|
|
14431
14547
|
let existing = findTask(ch);
|
|
14432
14548
|
while (existing) {
|
|
14433
14549
|
if (existing.status === "Running" || existing.status === "Queued") {
|
|
14434
|
-
|
|
14435
|
-
|
|
14550
|
+
spawnSync8("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
|
|
14551
|
+
spawnSync8("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
|
|
14436
14552
|
for (let i = 0; i < 10; i++) {
|
|
14437
14553
|
const check = findTask(ch);
|
|
14438
14554
|
if (!check || check.id !== existing.id || check.status !== "Running" && check.status !== "Queued") break;
|
|
14439
|
-
|
|
14555
|
+
spawnSync8("sleep", ["0.5"], { encoding: "utf-8" });
|
|
14440
14556
|
}
|
|
14441
14557
|
}
|
|
14442
|
-
|
|
14558
|
+
spawnSync8("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
|
|
14443
14559
|
existing = findTask(ch);
|
|
14444
14560
|
}
|
|
14445
|
-
const runScript =
|
|
14561
|
+
const runScript = join15(cwd, "run-claude.sh");
|
|
14446
14562
|
const args2 = [
|
|
14447
14563
|
"add",
|
|
14448
14564
|
"--label",
|
|
@@ -14453,7 +14569,7 @@ function startTask(opts = {}) {
|
|
|
14453
14569
|
"bash",
|
|
14454
14570
|
runScript
|
|
14455
14571
|
];
|
|
14456
|
-
const r =
|
|
14572
|
+
const r = spawnSync8("pueue", args2, { encoding: "utf-8" });
|
|
14457
14573
|
if (r.status !== 0) {
|
|
14458
14574
|
throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);
|
|
14459
14575
|
}
|
|
@@ -14464,25 +14580,25 @@ function startTask(opts = {}) {
|
|
|
14464
14580
|
return created;
|
|
14465
14581
|
}
|
|
14466
14582
|
function stopTask(channel = CHANNEL_PRIMARY) {
|
|
14467
|
-
|
|
14583
|
+
spawnSync8("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
|
|
14468
14584
|
let t = findTask(channel);
|
|
14469
14585
|
while (t) {
|
|
14470
14586
|
if (t.status === "Running" || t.status === "Queued") {
|
|
14471
|
-
|
|
14587
|
+
spawnSync8("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
|
|
14472
14588
|
for (let i = 0; i < 10; i++) {
|
|
14473
14589
|
const check = findTask(channel);
|
|
14474
14590
|
if (!check || check.id !== t.id || check.status !== "Running" && check.status !== "Queued") break;
|
|
14475
|
-
|
|
14591
|
+
spawnSync8("sleep", ["0.5"], { encoding: "utf-8" });
|
|
14476
14592
|
}
|
|
14477
14593
|
}
|
|
14478
|
-
|
|
14594
|
+
spawnSync8("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
|
|
14479
14595
|
t = findTask(channel);
|
|
14480
14596
|
}
|
|
14481
14597
|
}
|
|
14482
14598
|
function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
|
|
14483
14599
|
const t = findTask(channel);
|
|
14484
14600
|
if (!t) return `(no ${channel.taskLabel} task)`;
|
|
14485
|
-
const r =
|
|
14601
|
+
const r = spawnSync8("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
|
|
14486
14602
|
return r.stdout || r.stderr || "(no output)";
|
|
14487
14603
|
}
|
|
14488
14604
|
function ensureRunning(opts = {}) {
|
|
@@ -14507,8 +14623,8 @@ function probePort(host, port, timeoutMs = 500) {
|
|
|
14507
14623
|
});
|
|
14508
14624
|
}
|
|
14509
14625
|
function tmuxDismissPrompts(tmuxSession = TMUX_SESSION) {
|
|
14510
|
-
|
|
14511
|
-
|
|
14626
|
+
spawnSync8("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
|
|
14627
|
+
spawnSync8("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
|
|
14512
14628
|
}
|
|
14513
14629
|
async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tmuxSession = TMUX_SESSION) {
|
|
14514
14630
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -14520,46 +14636,46 @@ async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tm
|
|
|
14520
14636
|
return probePort(host, port);
|
|
14521
14637
|
}
|
|
14522
14638
|
function brewInstall(pkg) {
|
|
14523
|
-
const brew =
|
|
14639
|
+
const brew = spawnSync8("brew", ["--version"], { encoding: "utf-8" });
|
|
14524
14640
|
if (brew.status !== 0) return false;
|
|
14525
14641
|
console.log(` Installing ${pkg} via brew...`);
|
|
14526
|
-
const r =
|
|
14642
|
+
const r = spawnSync8("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
14527
14643
|
return r.status === 0;
|
|
14528
14644
|
}
|
|
14529
14645
|
function assertPueueInstalled() {
|
|
14530
|
-
let r =
|
|
14646
|
+
let r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14531
14647
|
if (r.status !== 0) {
|
|
14532
14648
|
if (process.platform === "darwin" && brewInstall("pueue")) {
|
|
14533
|
-
r =
|
|
14649
|
+
r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14534
14650
|
if (r.status !== 0) throw new PueueError("pueue install succeeded but binary not found on PATH.");
|
|
14535
14651
|
} else {
|
|
14536
14652
|
throw new PueueError("pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue");
|
|
14537
14653
|
}
|
|
14538
14654
|
}
|
|
14539
|
-
const status =
|
|
14655
|
+
const status = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
|
|
14540
14656
|
if (status.status !== 0) {
|
|
14541
14657
|
console.log(" Starting pueued daemon...");
|
|
14542
14658
|
const child = spawn2("pueued", ["-d"], { stdio: "ignore", detached: true });
|
|
14543
14659
|
child.unref();
|
|
14544
|
-
|
|
14545
|
-
const retry =
|
|
14660
|
+
spawnSync8("sleep", ["1"]);
|
|
14661
|
+
const retry = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
|
|
14546
14662
|
if (retry.status !== 0) {
|
|
14547
14663
|
throw new PueueError("pueue daemon not reachable after starting pueued. Check `pueued` manually.");
|
|
14548
14664
|
}
|
|
14549
14665
|
}
|
|
14550
|
-
|
|
14666
|
+
spawnSync8("pueue", ["parallel", "2"], { encoding: "utf-8" });
|
|
14551
14667
|
}
|
|
14552
14668
|
function assertClaudeInstalled() {
|
|
14553
|
-
const r =
|
|
14669
|
+
const r = spawnSync8("claude", ["--version"], { encoding: "utf-8" });
|
|
14554
14670
|
if (r.status !== 0) {
|
|
14555
14671
|
throw new PueueError("claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code");
|
|
14556
14672
|
}
|
|
14557
14673
|
}
|
|
14558
14674
|
function assertTmuxInstalled() {
|
|
14559
|
-
let r =
|
|
14675
|
+
let r = spawnSync8("tmux", ["-V"], { encoding: "utf-8" });
|
|
14560
14676
|
if (r.status !== 0) {
|
|
14561
14677
|
if (process.platform === "darwin" && brewInstall("tmux")) {
|
|
14562
|
-
r =
|
|
14678
|
+
r = spawnSync8("tmux", ["-V"], { encoding: "utf-8" });
|
|
14563
14679
|
if (r.status !== 0) throw new PueueError("tmux install succeeded but binary not found on PATH.");
|
|
14564
14680
|
} else {
|
|
14565
14681
|
throw new PueueError("tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)");
|
|
@@ -14572,12 +14688,12 @@ var init_pueue = __esm({
|
|
|
14572
14688
|
"use strict";
|
|
14573
14689
|
TASK_LABEL = "synkro-local-cc";
|
|
14574
14690
|
TMUX_SESSION = "synkro-local-cc";
|
|
14575
|
-
SESSION_DIR2 =
|
|
14691
|
+
SESSION_DIR2 = join15(homedir15(), ".synkro", "cc_sessions");
|
|
14576
14692
|
TASK_LABEL_2 = "synkro-local-cc-2";
|
|
14577
14693
|
TMUX_SESSION_2 = "synkro-local-cc-2";
|
|
14578
|
-
SESSION_DIR_22 =
|
|
14579
|
-
SESSION_DIR_32 =
|
|
14580
|
-
SESSION_DIR_42 =
|
|
14694
|
+
SESSION_DIR_22 = join15(homedir15(), ".synkro", "cc_sessions_2");
|
|
14695
|
+
SESSION_DIR_32 = join15(homedir15(), ".synkro", "cc_sessions_3");
|
|
14696
|
+
SESSION_DIR_42 = join15(homedir15(), ".synkro", "cc_sessions_4");
|
|
14581
14697
|
PueueError = class extends Error {
|
|
14582
14698
|
constructor(message, cause) {
|
|
14583
14699
|
super(message);
|
|
@@ -14592,13 +14708,13 @@ var init_pueue = __esm({
|
|
|
14592
14708
|
});
|
|
14593
14709
|
|
|
14594
14710
|
// cli/local-cc/settings.ts
|
|
14595
|
-
import { existsSync as
|
|
14596
|
-
import { homedir as
|
|
14597
|
-
import { join as
|
|
14711
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
14712
|
+
import { homedir as homedir16 } from "os";
|
|
14713
|
+
import { join as join16 } from "path";
|
|
14598
14714
|
function isLocalCCEnabled() {
|
|
14599
|
-
if (!
|
|
14715
|
+
if (!existsSync16(CONFIG_PATH3)) return false;
|
|
14600
14716
|
try {
|
|
14601
|
-
const content =
|
|
14717
|
+
const content = readFileSync14(CONFIG_PATH3, "utf-8");
|
|
14602
14718
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
14603
14719
|
return match?.[1] === "yes";
|
|
14604
14720
|
} catch {
|
|
@@ -14609,7 +14725,7 @@ var CONFIG_PATH3;
|
|
|
14609
14725
|
var init_settings = __esm({
|
|
14610
14726
|
"cli/local-cc/settings.ts"() {
|
|
14611
14727
|
"use strict";
|
|
14612
|
-
CONFIG_PATH3 =
|
|
14728
|
+
CONFIG_PATH3 = join16(homedir16(), ".synkro", "config.env");
|
|
14613
14729
|
}
|
|
14614
14730
|
});
|
|
14615
14731
|
|
|
@@ -14618,11 +14734,11 @@ var localCc_exports = {};
|
|
|
14618
14734
|
__export(localCc_exports, {
|
|
14619
14735
|
localCcCommand: () => localCcCommand
|
|
14620
14736
|
});
|
|
14621
|
-
import { spawnSync as
|
|
14622
|
-
import { homedir as
|
|
14623
|
-
import { join as
|
|
14737
|
+
import { spawnSync as spawnSync9 } from "child_process";
|
|
14738
|
+
import { homedir as homedir17 } from "os";
|
|
14739
|
+
import { join as join17 } from "path";
|
|
14624
14740
|
import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
|
|
14625
|
-
import { existsSync as
|
|
14741
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
14626
14742
|
function deploymentMode() {
|
|
14627
14743
|
const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
|
|
14628
14744
|
if (env === "docker") return "docker";
|
|
@@ -14728,15 +14844,15 @@ TROUBLESHOOTING
|
|
|
14728
14844
|
`);
|
|
14729
14845
|
}
|
|
14730
14846
|
function readGatewayUrl() {
|
|
14731
|
-
if (
|
|
14732
|
-
const m =
|
|
14847
|
+
if (existsSync17(CONFIG_PATH4)) {
|
|
14848
|
+
const m = readFileSync15(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
14733
14849
|
if (m) return m[1];
|
|
14734
14850
|
}
|
|
14735
14851
|
return "https://api.synkro.sh";
|
|
14736
14852
|
}
|
|
14737
14853
|
function updateLocalInferenceFlag(enabled) {
|
|
14738
|
-
if (!
|
|
14739
|
-
let content =
|
|
14854
|
+
if (!existsSync17(CONFIG_PATH4)) return;
|
|
14855
|
+
let content = readFileSync15(CONFIG_PATH4, "utf-8");
|
|
14740
14856
|
const flag = enabled ? "yes" : "no";
|
|
14741
14857
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
14742
14858
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -14799,7 +14915,7 @@ async function cmdStatus() {
|
|
|
14799
14915
|
}
|
|
14800
14916
|
const ch1Up = await isChannelAvailable();
|
|
14801
14917
|
console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? "reachable" : "unreachable"}`);
|
|
14802
|
-
const tmux1 =
|
|
14918
|
+
const tmux1 = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
|
|
14803
14919
|
console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? "live" : "absent"}`);
|
|
14804
14920
|
const t2 = findTask(CHANNEL_SECONDARY);
|
|
14805
14921
|
if (!t2) {
|
|
@@ -14809,7 +14925,7 @@ async function cmdStatus() {
|
|
|
14809
14925
|
}
|
|
14810
14926
|
const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);
|
|
14811
14927
|
console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? "reachable" : "unreachable"}`);
|
|
14812
|
-
const tmux2 =
|
|
14928
|
+
const tmux2 = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
|
|
14813
14929
|
console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? "live" : "absent"}`);
|
|
14814
14930
|
}
|
|
14815
14931
|
async function cmdEnable() {
|
|
@@ -15015,7 +15131,7 @@ function cmdLogs(rest) {
|
|
|
15015
15131
|
}
|
|
15016
15132
|
return "200";
|
|
15017
15133
|
})();
|
|
15018
|
-
|
|
15134
|
+
spawnSync9("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
|
|
15019
15135
|
return;
|
|
15020
15136
|
}
|
|
15021
15137
|
for (const arg of rest) {
|
|
@@ -15063,7 +15179,7 @@ function cmdLogs(rest) {
|
|
|
15063
15179
|
function cmdAttach(rest) {
|
|
15064
15180
|
assertTmuxInstalled();
|
|
15065
15181
|
const readonly = rest.some((a) => a === "--readonly" || a === "-r");
|
|
15066
|
-
const has =
|
|
15182
|
+
const has = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
|
|
15067
15183
|
if (has.status !== 0) {
|
|
15068
15184
|
console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);
|
|
15069
15185
|
process.exit(1);
|
|
@@ -15076,7 +15192,7 @@ function cmdAttach(rest) {
|
|
|
15076
15192
|
console.log("Detach with Ctrl-B then D. (Do not press Ctrl-C \u2014 that would interrupt claude.)");
|
|
15077
15193
|
console.log();
|
|
15078
15194
|
const args2 = readonly ? ["attach-session", "-r", "-t", TMUX_SESSION_NAME] : ["attach-session", "-t", TMUX_SESSION_NAME];
|
|
15079
|
-
const r =
|
|
15195
|
+
const r = spawnSync9("tmux", args2, { stdio: "inherit" });
|
|
15080
15196
|
process.exit(r.status ?? 0);
|
|
15081
15197
|
}
|
|
15082
15198
|
async function cmdTest() {
|
|
@@ -15177,8 +15293,8 @@ var init_localCc = __esm({
|
|
|
15177
15293
|
init_install();
|
|
15178
15294
|
init_client();
|
|
15179
15295
|
init_stub();
|
|
15180
|
-
SYNKRO_CONFIG_PATH =
|
|
15181
|
-
CONFIG_PATH4 =
|
|
15296
|
+
SYNKRO_CONFIG_PATH = join17(homedir17(), ".synkro", "config.env");
|
|
15297
|
+
CONFIG_PATH4 = join17(homedir17(), ".synkro", "config.env");
|
|
15182
15298
|
}
|
|
15183
15299
|
});
|
|
15184
15300
|
|
|
@@ -15187,15 +15303,15 @@ var import_exports = {};
|
|
|
15187
15303
|
__export(import_exports, {
|
|
15188
15304
|
importCommand: () => importCommand
|
|
15189
15305
|
});
|
|
15190
|
-
import { existsSync as
|
|
15191
|
-
import { homedir as
|
|
15192
|
-
import { join as
|
|
15306
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16, readdirSync as readdirSync5 } from "fs";
|
|
15307
|
+
import { homedir as homedir18 } from "os";
|
|
15308
|
+
import { join as join18 } from "path";
|
|
15193
15309
|
import { execSync as execSync7 } from "child_process";
|
|
15194
15310
|
import { createInterface as createInterface5 } from "readline";
|
|
15195
15311
|
function readConfigEnv() {
|
|
15196
15312
|
const out = {};
|
|
15197
15313
|
try {
|
|
15198
|
-
for (const line of
|
|
15314
|
+
for (const line of readFileSync16(CONFIG_PATH5, "utf-8").split("\n")) {
|
|
15199
15315
|
const t = line.trim();
|
|
15200
15316
|
if (!t || t.startsWith("#")) continue;
|
|
15201
15317
|
const eq = t.indexOf("=");
|
|
@@ -15207,8 +15323,8 @@ function readConfigEnv() {
|
|
|
15207
15323
|
}
|
|
15208
15324
|
function projectsFolder() {
|
|
15209
15325
|
const sanitized = process.cwd().replace(/\//g, "-");
|
|
15210
|
-
const dir =
|
|
15211
|
-
return
|
|
15326
|
+
const dir = join18(homedir18(), ".claude", "projects", sanitized);
|
|
15327
|
+
return existsSync18(dir) ? dir : null;
|
|
15212
15328
|
}
|
|
15213
15329
|
function repoName() {
|
|
15214
15330
|
try {
|
|
@@ -15231,7 +15347,7 @@ function extractText(content) {
|
|
|
15231
15347
|
return "";
|
|
15232
15348
|
}
|
|
15233
15349
|
function parseSession(filePath, sessionId) {
|
|
15234
|
-
const lines =
|
|
15350
|
+
const lines = readFileSync16(filePath, "utf-8").split("\n").filter(Boolean);
|
|
15235
15351
|
const messages = [];
|
|
15236
15352
|
const actions = [];
|
|
15237
15353
|
let step = 0;
|
|
@@ -15304,7 +15420,7 @@ async function importCommand() {
|
|
|
15304
15420
|
return;
|
|
15305
15421
|
}
|
|
15306
15422
|
}
|
|
15307
|
-
const sessions = files.map((f) => parseSession(
|
|
15423
|
+
const sessions = files.map((f) => parseSession(join18(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
|
|
15308
15424
|
const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
|
|
15309
15425
|
let ok = 0, fail = 0;
|
|
15310
15426
|
if (isCloud) {
|
|
@@ -15364,7 +15480,7 @@ var init_import = __esm({
|
|
|
15364
15480
|
"cli/commands/import.ts"() {
|
|
15365
15481
|
"use strict";
|
|
15366
15482
|
init_stub();
|
|
15367
|
-
CONFIG_PATH5 =
|
|
15483
|
+
CONFIG_PATH5 = join18(homedir18(), ".synkro", "config.env");
|
|
15368
15484
|
}
|
|
15369
15485
|
});
|
|
15370
15486
|
|
|
@@ -15406,10 +15522,10 @@ var init_packVerify = __esm({
|
|
|
15406
15522
|
});
|
|
15407
15523
|
|
|
15408
15524
|
// cli/installer/lockfile.ts
|
|
15409
|
-
import { existsSync as
|
|
15410
|
-
import { join as
|
|
15525
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
15526
|
+
import { join as join19 } from "path";
|
|
15411
15527
|
function lockPath(repoRoot) {
|
|
15412
|
-
return
|
|
15528
|
+
return join19(repoRoot, LOCK_FILE);
|
|
15413
15529
|
}
|
|
15414
15530
|
function writeLockfile(repoRoot, entries) {
|
|
15415
15531
|
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
@@ -15442,9 +15558,9 @@ var sync_exports = {};
|
|
|
15442
15558
|
__export(sync_exports, {
|
|
15443
15559
|
syncCommand: () => syncCommand
|
|
15444
15560
|
});
|
|
15445
|
-
import { existsSync as
|
|
15446
|
-
import { homedir as
|
|
15447
|
-
import { join as
|
|
15561
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync6, rmSync as rmSync3, writeFileSync as writeFileSync12 } from "fs";
|
|
15562
|
+
import { homedir as homedir19 } from "os";
|
|
15563
|
+
import { join as join20 } from "path";
|
|
15448
15564
|
function cacheKey(ref, version) {
|
|
15449
15565
|
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
15450
15566
|
}
|
|
@@ -15475,8 +15591,8 @@ async function syncCommand(_args = []) {
|
|
|
15475
15591
|
}
|
|
15476
15592
|
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
15477
15593
|
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
15478
|
-
const cacheDir =
|
|
15479
|
-
if (!cloud)
|
|
15594
|
+
const cacheDir = join20(homedir19(), ".synkro", "cache", "packs");
|
|
15595
|
+
if (!cloud) mkdirSync13(cacheDir, { recursive: true });
|
|
15480
15596
|
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
15481
15597
|
const lock = [];
|
|
15482
15598
|
const keptCacheFiles = /* @__PURE__ */ new Set();
|
|
@@ -15504,7 +15620,7 @@ async function syncCommand(_args = []) {
|
|
|
15504
15620
|
if (!cloud) {
|
|
15505
15621
|
const fname = cacheKey(ref, data.version);
|
|
15506
15622
|
keptCacheFiles.add(fname);
|
|
15507
|
-
writeFileSync12(
|
|
15623
|
+
writeFileSync12(join20(cacheDir, fname), JSON.stringify({
|
|
15508
15624
|
ref,
|
|
15509
15625
|
version: data.version,
|
|
15510
15626
|
digest: data.digest,
|
|
@@ -15516,11 +15632,11 @@ async function syncCommand(_args = []) {
|
|
|
15516
15632
|
const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
|
|
15517
15633
|
console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
|
|
15518
15634
|
}
|
|
15519
|
-
if (!cloud &&
|
|
15635
|
+
if (!cloud && existsSync20(cacheDir)) {
|
|
15520
15636
|
for (const f of readdirSync6(cacheDir)) {
|
|
15521
15637
|
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
15522
15638
|
try {
|
|
15523
|
-
|
|
15639
|
+
rmSync3(join20(cacheDir, f));
|
|
15524
15640
|
} catch {
|
|
15525
15641
|
}
|
|
15526
15642
|
}
|
|
@@ -15656,13 +15772,13 @@ var config_exports = {};
|
|
|
15656
15772
|
__export(config_exports, {
|
|
15657
15773
|
configCommand: () => configCommand
|
|
15658
15774
|
});
|
|
15659
|
-
import { readFileSync as
|
|
15660
|
-
import { join as
|
|
15661
|
-
import { homedir as
|
|
15775
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync21 } from "fs";
|
|
15776
|
+
import { join as join21 } from "path";
|
|
15777
|
+
import { homedir as homedir20 } from "os";
|
|
15662
15778
|
function readConfigEnv2() {
|
|
15663
|
-
if (!
|
|
15779
|
+
if (!existsSync21(CONFIG_PATH6)) return {};
|
|
15664
15780
|
const out = {};
|
|
15665
|
-
for (const line of
|
|
15781
|
+
for (const line of readFileSync18(CONFIG_PATH6, "utf-8").split("\n")) {
|
|
15666
15782
|
const t = line.trim();
|
|
15667
15783
|
if (!t || t.startsWith("#")) continue;
|
|
15668
15784
|
const eq = t.indexOf("=");
|
|
@@ -15671,11 +15787,11 @@ function readConfigEnv2() {
|
|
|
15671
15787
|
return out;
|
|
15672
15788
|
}
|
|
15673
15789
|
function updateConfigValue(key, value) {
|
|
15674
|
-
if (!
|
|
15790
|
+
if (!existsSync21(CONFIG_PATH6)) {
|
|
15675
15791
|
console.error("No config found. Run `synkro install` first.");
|
|
15676
15792
|
process.exit(1);
|
|
15677
15793
|
}
|
|
15678
|
-
const lines =
|
|
15794
|
+
const lines = readFileSync18(CONFIG_PATH6, "utf-8").split("\n");
|
|
15679
15795
|
const pattern = new RegExp(`^${key}=`);
|
|
15680
15796
|
let found = false;
|
|
15681
15797
|
const updated = lines.map((line) => {
|
|
@@ -15813,25 +15929,25 @@ To change:`);
|
|
|
15813
15929
|
}
|
|
15814
15930
|
if (inferenceValue !== "cloud") await reconcileContainer();
|
|
15815
15931
|
}
|
|
15816
|
-
var
|
|
15932
|
+
var SYNKRO_DIR8, CONFIG_PATH6;
|
|
15817
15933
|
var init_config = __esm({
|
|
15818
15934
|
"cli/commands/config.ts"() {
|
|
15819
15935
|
"use strict";
|
|
15820
15936
|
init_stub();
|
|
15821
|
-
|
|
15822
|
-
CONFIG_PATH6 =
|
|
15937
|
+
SYNKRO_DIR8 = join21(homedir20(), ".synkro");
|
|
15938
|
+
CONFIG_PATH6 = join21(SYNKRO_DIR8, "config.env");
|
|
15823
15939
|
}
|
|
15824
15940
|
});
|
|
15825
15941
|
|
|
15826
15942
|
// cli/bootstrap.js
|
|
15827
|
-
import { readFileSync as
|
|
15943
|
+
import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
|
|
15828
15944
|
import { resolve as resolve3 } from "path";
|
|
15829
15945
|
var envCandidates = [
|
|
15830
15946
|
resolve3(process.env.HOME ?? "", ".synkro", "config.env")
|
|
15831
15947
|
];
|
|
15832
15948
|
for (const envPath of envCandidates) {
|
|
15833
|
-
if (!
|
|
15834
|
-
const envContent =
|
|
15949
|
+
if (!existsSync22(envPath)) continue;
|
|
15950
|
+
const envContent = readFileSync19(envPath, "utf-8");
|
|
15835
15951
|
for (const line of envContent.split("\n")) {
|
|
15836
15952
|
const trimmed = line.trim();
|
|
15837
15953
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -15846,7 +15962,7 @@ var args = process.argv.slice(2);
|
|
|
15846
15962
|
var cmd = args[0] || "";
|
|
15847
15963
|
var subArgs = args.slice(1);
|
|
15848
15964
|
function printVersion() {
|
|
15849
|
-
console.log("1.7.
|
|
15965
|
+
console.log("1.7.24");
|
|
15850
15966
|
}
|
|
15851
15967
|
function printHelp2() {
|
|
15852
15968
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|