@synkro-sh/cli 1.7.9 → 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 +528 -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
|
|
|
@@ -11311,6 +11408,7 @@ var init_setupGithub = __esm({
|
|
|
11311
11408
|
var install_exports = {};
|
|
11312
11409
|
__export(install_exports, {
|
|
11313
11410
|
detectGitRepo: () => detectGitRepo2,
|
|
11411
|
+
discoverAndIngestSkills: () => discoverAndIngestSkills,
|
|
11314
11412
|
getOrMintCloudToken: () => getOrMintCloudToken,
|
|
11315
11413
|
installCommand: () => installCommand,
|
|
11316
11414
|
parseArgs: () => parseArgs,
|
|
@@ -11321,16 +11419,17 @@ __export(install_exports, {
|
|
|
11321
11419
|
syncSkillFiles: () => syncSkillFiles,
|
|
11322
11420
|
writeHookScripts: () => writeHookScripts
|
|
11323
11421
|
});
|
|
11324
|
-
import { existsSync as
|
|
11325
|
-
import { homedir as
|
|
11326
|
-
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";
|
|
11327
11425
|
import { execSync as execSync6 } from "child_process";
|
|
11328
11426
|
import { createInterface as createInterface3 } from "readline";
|
|
11427
|
+
import { createHash } from "crypto";
|
|
11329
11428
|
function resolvePersistedHookMode() {
|
|
11330
11429
|
if (process.env.SYNKRO_HOOK_MODE === "full") return "full";
|
|
11331
11430
|
if (process.env.SYNKRO_HOOK_MODE === "stub") return "stub";
|
|
11332
11431
|
try {
|
|
11333
|
-
const env =
|
|
11432
|
+
const env = readFileSync11(CONFIG_PATH2, "utf-8");
|
|
11334
11433
|
if (/^SYNKRO_HOOK_MODE=['"]?full['"]?\s*$/m.test(env)) return "full";
|
|
11335
11434
|
} catch {
|
|
11336
11435
|
}
|
|
@@ -11466,37 +11565,51 @@ coding patterns and the dashboard shows full history. (Y/n) `
|
|
|
11466
11565
|
rl.close();
|
|
11467
11566
|
}
|
|
11468
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
|
+
}
|
|
11469
11582
|
function ensureSynkroDir() {
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
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 });
|
|
11475
11588
|
}
|
|
11476
11589
|
function writeHookScripts(mode = "stub") {
|
|
11477
|
-
const installExtractCorePath =
|
|
11478
|
-
const bashScriptPath =
|
|
11479
|
-
const bashFollowupScriptPath =
|
|
11480
|
-
const editPrecheckScriptPath =
|
|
11481
|
-
const cwePrecheckScriptPath =
|
|
11482
|
-
const cvePrecheckScriptPath =
|
|
11483
|
-
const planJudgeScriptPath =
|
|
11484
|
-
const agentJudgeScriptPath =
|
|
11485
|
-
const stopSummaryScriptPath =
|
|
11486
|
-
const sessionStartScriptPath =
|
|
11487
|
-
const transcriptSyncScriptPath =
|
|
11488
|
-
const userPromptSubmitScriptPath =
|
|
11489
|
-
const commonScriptPath =
|
|
11490
|
-
const commonBashScriptPath =
|
|
11491
|
-
const installScanScriptPath =
|
|
11492
|
-
const cursorBashJudgePath =
|
|
11493
|
-
const cursorEditCapturePath =
|
|
11494
|
-
const cursorAgentCapturePath =
|
|
11495
|
-
const mcpStdioProxyPath =
|
|
11496
|
-
const taskActivateIntentScriptPath =
|
|
11497
|
-
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");
|
|
11498
11611
|
if (mode === "stub") {
|
|
11499
|
-
const stubCommonPath2 =
|
|
11612
|
+
const stubCommonPath2 = join11(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
11500
11613
|
const stubFiles = [
|
|
11501
11614
|
[stubCommonPath2, STUB_COMMON_TS],
|
|
11502
11615
|
[bashScriptPath, STUB_BASH_JUDGE_TS],
|
|
@@ -11525,7 +11638,7 @@ function writeHookScripts(mode = "stub") {
|
|
|
11525
11638
|
chmodSync2(mcpStdioProxyPath, 493);
|
|
11526
11639
|
for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
|
|
11527
11640
|
try {
|
|
11528
|
-
unlinkSync4(
|
|
11641
|
+
unlinkSync4(join11(HOOKS_DIR, stale));
|
|
11529
11642
|
} catch {
|
|
11530
11643
|
}
|
|
11531
11644
|
}
|
|
@@ -11569,7 +11682,7 @@ function writeHookScripts(mode = "stub") {
|
|
|
11569
11682
|
writeFileSync8(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
|
|
11570
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");
|
|
11571
11684
|
writeFileSync8(taskActivateIntentScriptPath, STUB_TASK_ACTIVATE_INTENT_TS, "utf-8");
|
|
11572
|
-
const stubCommonPath =
|
|
11685
|
+
const stubCommonPath = join11(HOOKS_DIR, "_synkro-stub-common.ts");
|
|
11573
11686
|
writeFileSync8(stubCommonPath, STUB_COMMON_TS, "utf-8");
|
|
11574
11687
|
chmodSync2(stubCommonPath, 493);
|
|
11575
11688
|
writeFileSync8(mcpGateScriptPath, STUB_MCP_GATE_TS, "utf-8");
|
|
@@ -11623,11 +11736,11 @@ function shellQuoteSingle(value) {
|
|
|
11623
11736
|
}
|
|
11624
11737
|
function resolveSynkroBundle() {
|
|
11625
11738
|
const scriptPath = process.argv[1];
|
|
11626
|
-
if (scriptPath &&
|
|
11739
|
+
if (scriptPath && existsSync12(scriptPath)) return scriptPath;
|
|
11627
11740
|
return null;
|
|
11628
11741
|
}
|
|
11629
11742
|
function writeConfigEnv(opts) {
|
|
11630
|
-
const credsPath =
|
|
11743
|
+
const credsPath = join11(SYNKRO_DIR6, "credentials.json");
|
|
11631
11744
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
11632
11745
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
11633
11746
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -11643,7 +11756,7 @@ function writeConfigEnv(opts) {
|
|
|
11643
11756
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11644
11757
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11645
11758
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11646
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.7.
|
|
11759
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.7.24")}`
|
|
11647
11760
|
];
|
|
11648
11761
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11649
11762
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -11676,7 +11789,7 @@ async function getOrMintCloudToken(gatewayUrl) {
|
|
|
11676
11789
|
assertGatewayAllowed(gatewayUrl);
|
|
11677
11790
|
let stored = "";
|
|
11678
11791
|
try {
|
|
11679
|
-
stored =
|
|
11792
|
+
stored = readFileSync11(CLOUD_JWT_PATH, "utf-8").trim();
|
|
11680
11793
|
} catch {
|
|
11681
11794
|
}
|
|
11682
11795
|
if (stored && !jwtExpired(stored)) return stored;
|
|
@@ -11735,7 +11848,7 @@ async function provisionCloudContainer(opts) {
|
|
|
11735
11848
|
let cursorApiKey = "";
|
|
11736
11849
|
if (cursorTotal > 0) {
|
|
11737
11850
|
try {
|
|
11738
|
-
cursorApiKey =
|
|
11851
|
+
cursorApiKey = readFileSync11(join11(SYNKRO_DIR6, "cursor-creds", "api-key"), "utf-8").trim();
|
|
11739
11852
|
} catch {
|
|
11740
11853
|
}
|
|
11741
11854
|
}
|
|
@@ -11892,8 +12005,8 @@ async function verifyCloudGrader(jwt2) {
|
|
|
11892
12005
|
}
|
|
11893
12006
|
function readPersistedDeployLocation() {
|
|
11894
12007
|
try {
|
|
11895
|
-
if (
|
|
11896
|
-
const m =
|
|
12008
|
+
if (existsSync12(CONFIG_PATH2)) {
|
|
12009
|
+
const m = readFileSync11(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
|
|
11897
12010
|
if (m?.[1] === "cloud") return "cloud";
|
|
11898
12011
|
}
|
|
11899
12012
|
} catch {
|
|
@@ -11901,8 +12014,8 @@ function readPersistedDeployLocation() {
|
|
|
11901
12014
|
return "local";
|
|
11902
12015
|
}
|
|
11903
12016
|
function updateConfigEnvLocation(location) {
|
|
11904
|
-
if (!
|
|
11905
|
-
let env =
|
|
12017
|
+
if (!existsSync12(CONFIG_PATH2)) return;
|
|
12018
|
+
let env = readFileSync11(CONFIG_PATH2, "utf-8");
|
|
11906
12019
|
const set = (k, v) => {
|
|
11907
12020
|
const re = new RegExp(`^${k}=.*$`, "m");
|
|
11908
12021
|
const line = `${k}='${v}'`;
|
|
@@ -11917,7 +12030,7 @@ async function applyMcpConfig(opts) {
|
|
|
11917
12030
|
if (!opts.hasClaudeCode && !opts.hasCursor) return;
|
|
11918
12031
|
let mcpJwt = "";
|
|
11919
12032
|
try {
|
|
11920
|
-
mcpJwt =
|
|
12033
|
+
mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
11921
12034
|
} catch {
|
|
11922
12035
|
}
|
|
11923
12036
|
if (!mcpJwt) {
|
|
@@ -12035,8 +12148,8 @@ function resolveDeploymentMode() {
|
|
|
12035
12148
|
const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
|
|
12036
12149
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
12037
12150
|
try {
|
|
12038
|
-
if (
|
|
12039
|
-
const m =
|
|
12151
|
+
if (existsSync12(CONFIG_PATH2)) {
|
|
12152
|
+
const m = readFileSync11(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
|
|
12040
12153
|
const val = m?.[1]?.toLowerCase();
|
|
12041
12154
|
if (val === "bare-host" || val === "docker") return val;
|
|
12042
12155
|
}
|
|
@@ -12063,16 +12176,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
12063
12176
|
meta.cc_version = execSync6("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
12064
12177
|
} catch {
|
|
12065
12178
|
}
|
|
12066
|
-
const claudeDir =
|
|
12179
|
+
const claudeDir = join11(homedir11(), ".claude");
|
|
12067
12180
|
try {
|
|
12068
|
-
const settings = JSON.parse(
|
|
12181
|
+
const settings = JSON.parse(readFileSync11(join11(claudeDir, "settings.json"), "utf-8"));
|
|
12069
12182
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
12070
12183
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
12071
12184
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
12072
12185
|
} catch {
|
|
12073
12186
|
}
|
|
12074
12187
|
try {
|
|
12075
|
-
const mcpCache = JSON.parse(
|
|
12188
|
+
const mcpCache = JSON.parse(readFileSync11(join11(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
12076
12189
|
const mcpNames = Object.keys(mcpCache);
|
|
12077
12190
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
12078
12191
|
} catch {
|
|
@@ -12084,10 +12197,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
|
|
|
12084
12197
|
} catch {
|
|
12085
12198
|
}
|
|
12086
12199
|
try {
|
|
12087
|
-
const sessionsDir =
|
|
12200
|
+
const sessionsDir = join11(claudeDir, "sessions");
|
|
12088
12201
|
const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
12089
12202
|
for (const f of files) {
|
|
12090
|
-
const s = JSON.parse(
|
|
12203
|
+
const s = JSON.parse(readFileSync11(join11(sessionsDir, f), "utf-8"));
|
|
12091
12204
|
if (s.version) {
|
|
12092
12205
|
meta.cc_version = meta.cc_version || s.version;
|
|
12093
12206
|
break;
|
|
@@ -12275,9 +12388,9 @@ async function installCommand(opts = {}) {
|
|
|
12275
12388
|
const scripts = writeHookScripts(hookMode);
|
|
12276
12389
|
console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
|
|
12277
12390
|
for (const mode of ["edit", "bash"]) {
|
|
12278
|
-
const pidFile =
|
|
12391
|
+
const pidFile = join11(SYNKRO_DIR6, "daemon", mode, "daemon.pid");
|
|
12279
12392
|
try {
|
|
12280
|
-
const pid = parseInt(
|
|
12393
|
+
const pid = parseInt(readFileSync11(pidFile, "utf-8").trim(), 10);
|
|
12281
12394
|
if (pid > 0) {
|
|
12282
12395
|
process.kill(pid, "SIGTERM");
|
|
12283
12396
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -12360,7 +12473,7 @@ async function installCommand(opts = {}) {
|
|
|
12360
12473
|
if (mintResp.ok) {
|
|
12361
12474
|
const minted = await mintResp.json();
|
|
12362
12475
|
mcpJwt = minted.token;
|
|
12363
|
-
writeFileSync8(
|
|
12476
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), mcpJwt + "\n", { mode: 384 });
|
|
12364
12477
|
} else {
|
|
12365
12478
|
console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
|
|
12366
12479
|
}
|
|
@@ -12388,7 +12501,7 @@ async function installCommand(opts = {}) {
|
|
|
12388
12501
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
12389
12502
|
}
|
|
12390
12503
|
const minted = await mintResp.json();
|
|
12391
|
-
writeFileSync8(
|
|
12504
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
12392
12505
|
const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
12393
12506
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
12394
12507
|
console.log(` url: ${mcp.url}`);
|
|
@@ -12405,8 +12518,8 @@ async function installCommand(opts = {}) {
|
|
|
12405
12518
|
if (hasCursor && !opts.noMcp) {
|
|
12406
12519
|
try {
|
|
12407
12520
|
if (useLocalMcp) {
|
|
12408
|
-
const jwtPath =
|
|
12409
|
-
if (!
|
|
12521
|
+
const jwtPath = join11(SYNKRO_DIR6, ".mcp-jwt");
|
|
12522
|
+
if (!existsSync12(jwtPath)) {
|
|
12410
12523
|
const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
|
|
12411
12524
|
method: "POST",
|
|
12412
12525
|
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
|
|
@@ -12434,7 +12547,7 @@ async function installCommand(opts = {}) {
|
|
|
12434
12547
|
throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
|
|
12435
12548
|
}
|
|
12436
12549
|
const minted = await mintResp.json();
|
|
12437
|
-
writeFileSync8(
|
|
12550
|
+
writeFileSync8(join11(SYNKRO_DIR6, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
|
|
12438
12551
|
const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
12439
12552
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
12440
12553
|
console.log(` url: ${mcp.url}`);
|
|
@@ -12516,7 +12629,7 @@ async function installCommand(opts = {}) {
|
|
|
12516
12629
|
const ready = await waitForContainerReady(6e4);
|
|
12517
12630
|
if (ready) {
|
|
12518
12631
|
console.log(" \u2713 container ready");
|
|
12519
|
-
const mcpJwt =
|
|
12632
|
+
const mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12520
12633
|
try {
|
|
12521
12634
|
const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
|
|
12522
12635
|
method: "POST",
|
|
@@ -12537,6 +12650,7 @@ async function installCommand(opts = {}) {
|
|
|
12537
12650
|
if (workersUp) {
|
|
12538
12651
|
console.log(" \u2713 workers ready");
|
|
12539
12652
|
await syncSkillFiles();
|
|
12653
|
+
await discoverAndIngestSkills();
|
|
12540
12654
|
} else {
|
|
12541
12655
|
console.warn(" \u26A0 workers did not register within 30s \u2014 skill sync skipped");
|
|
12542
12656
|
}
|
|
@@ -12550,6 +12664,11 @@ async function installCommand(opts = {}) {
|
|
|
12550
12664
|
if (hasCursor) await promptCursorApiKey(opts);
|
|
12551
12665
|
await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
|
|
12552
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
|
+
}
|
|
12553
12672
|
}
|
|
12554
12673
|
if (transcriptConsent) {
|
|
12555
12674
|
const repo = detectGitRepo2();
|
|
@@ -12558,7 +12677,7 @@ async function installCommand(opts = {}) {
|
|
|
12558
12677
|
try {
|
|
12559
12678
|
let mcpToken = "";
|
|
12560
12679
|
try {
|
|
12561
|
-
mcpToken =
|
|
12680
|
+
mcpToken = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12562
12681
|
} catch {
|
|
12563
12682
|
}
|
|
12564
12683
|
if (mcpToken) {
|
|
@@ -12694,8 +12813,8 @@ function writeSynkroFileIfMissing(opts) {
|
|
|
12694
12813
|
try {
|
|
12695
12814
|
const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12696
12815
|
if (!root) return;
|
|
12697
|
-
if (root ===
|
|
12698
|
-
const fp =
|
|
12816
|
+
if (root === homedir11()) return;
|
|
12817
|
+
const fp = join11(root, "synkro.toml");
|
|
12699
12818
|
let hasFile = false;
|
|
12700
12819
|
try {
|
|
12701
12820
|
hasFile = statSync(fp).isFile();
|
|
@@ -12740,9 +12859,9 @@ function readFullSynkroFile() {
|
|
|
12740
12859
|
try {
|
|
12741
12860
|
const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12742
12861
|
if (!root) return null;
|
|
12743
|
-
const fp =
|
|
12744
|
-
if (!
|
|
12745
|
-
const parsed = parseSynkroToml2(
|
|
12862
|
+
const fp = join11(root, "synkro.toml");
|
|
12863
|
+
if (!existsSync12(fp)) return null;
|
|
12864
|
+
const parsed = parseSynkroToml2(readFileSync11(fp, "utf-8"));
|
|
12746
12865
|
const valid = ["claude-code", "cursor"];
|
|
12747
12866
|
const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
|
|
12748
12867
|
const resolved = resolveGraderPool(parsed);
|
|
@@ -12780,7 +12899,7 @@ function reconcileHarness() {
|
|
|
12780
12899
|
console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
|
|
12781
12900
|
const scripts = writeHookScripts(resolvePersistedHookMode());
|
|
12782
12901
|
console.log("Wrote hook scripts to ~/.synkro/hooks/");
|
|
12783
|
-
const ccSettings =
|
|
12902
|
+
const ccSettings = join11(homedir11(), ".claude", "settings.json");
|
|
12784
12903
|
if (wantCC) {
|
|
12785
12904
|
installCCHooks(ccSettings, {
|
|
12786
12905
|
bashJudgeScriptPath: scripts.bashScript,
|
|
@@ -12800,7 +12919,7 @@ function reconcileHarness() {
|
|
|
12800
12919
|
});
|
|
12801
12920
|
console.log(" \u2713 Claude Code hooks registered");
|
|
12802
12921
|
try {
|
|
12803
|
-
const mcpJwt =
|
|
12922
|
+
const mcpJwt = readFileSync11(join11(SYNKRO_DIR6, ".mcp-jwt"), "utf-8").trim();
|
|
12804
12923
|
if (mcpJwt) {
|
|
12805
12924
|
installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt, local: true });
|
|
12806
12925
|
console.log(" \u2713 Claude Code MCP registered");
|
|
@@ -12812,7 +12931,7 @@ function reconcileHarness() {
|
|
|
12812
12931
|
if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
|
|
12813
12932
|
if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
|
|
12814
12933
|
}
|
|
12815
|
-
const cursorHooks =
|
|
12934
|
+
const cursorHooks = join11(homedir11(), ".cursor", "hooks.json");
|
|
12816
12935
|
if (wantCursor) {
|
|
12817
12936
|
installCursorHooks(cursorHooks, {
|
|
12818
12937
|
bashJudgeScriptPath: scripts.cursorBashJudgeScript,
|
|
@@ -12859,6 +12978,34 @@ function reconcileHarness() {
|
|
|
12859
12978
|
if (providers.length === 0) providers.push("claude_code");
|
|
12860
12979
|
return splitWorkers(total, providers);
|
|
12861
12980
|
}
|
|
12981
|
+
async function ingestSkillTasks(tasks, mcpPort) {
|
|
12982
|
+
const summary = { ingested: 0, rules: 0, rejected: [] };
|
|
12983
|
+
for (const { source, content } of tasks) {
|
|
12984
|
+
try {
|
|
12985
|
+
const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {
|
|
12986
|
+
method: "POST",
|
|
12987
|
+
headers: { "Content-Type": "application/json" },
|
|
12988
|
+
body: JSON.stringify({ source, content }),
|
|
12989
|
+
signal: AbortSignal.timeout(65e3)
|
|
12990
|
+
});
|
|
12991
|
+
if (!resp.ok) throw new Error(`${resp.status}`);
|
|
12992
|
+
const r = await resp.json();
|
|
12993
|
+
if (r.status === "rejected") {
|
|
12994
|
+
const reason = (r.reasons || []).slice(0, 2).join("; ") || r.message || "blocked by security vetting";
|
|
12995
|
+
summary.rejected.push({ source, reason });
|
|
12996
|
+
console.log(` \u26D4 skill ${source}: BLOCKED \u2014 ${reason}`);
|
|
12997
|
+
} else {
|
|
12998
|
+
const created = r.created || 0;
|
|
12999
|
+
summary.ingested++;
|
|
13000
|
+
summary.rules += created;
|
|
13001
|
+
console.log(created > 0 ? ` \u2713 skill ${source}: ${created} rule${created === 1 ? "" : "s"} \u2192 its own pack` : ` \u2713 skill ${source}: ${r.message || "up to date"}`);
|
|
13002
|
+
}
|
|
13003
|
+
} catch (e) {
|
|
13004
|
+
console.warn(` \u26A0 skill ${source}: sync failed (${e instanceof Error ? e.message : String(e)})`);
|
|
13005
|
+
}
|
|
13006
|
+
}
|
|
13007
|
+
return summary;
|
|
13008
|
+
}
|
|
12862
13009
|
async function syncSkillFiles() {
|
|
12863
13010
|
const sf = readFullSynkroFile();
|
|
12864
13011
|
if (!sf || sf.skills.length === 0) return;
|
|
@@ -12866,7 +13013,7 @@ async function syncSkillFiles() {
|
|
|
12866
13013
|
if (resolved.length === 0) return;
|
|
12867
13014
|
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
12868
13015
|
const tasks = resolved.map((fp) => {
|
|
12869
|
-
const content =
|
|
13016
|
+
const content = readFileSync11(fp, "utf-8");
|
|
12870
13017
|
const source = `skill:${fp.split("/").pop()}`;
|
|
12871
13018
|
if (!content.trim()) {
|
|
12872
13019
|
console.log(` \u2298 skill ${source}: empty file, skipped`);
|
|
@@ -12878,20 +13025,128 @@ async function syncSkillFiles() {
|
|
|
12878
13025
|
}).filter(Boolean);
|
|
12879
13026
|
if (tasks.length === 0) return;
|
|
12880
13027
|
console.log(` indexing ${tasks.length} skill file${tasks.length === 1 ? "" : "s"} \u2014 comparing against existing ruleset (this may take a few seconds)...`);
|
|
12881
|
-
|
|
13028
|
+
await ingestSkillTasks(tasks, mcpPort);
|
|
13029
|
+
}
|
|
13030
|
+
function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
13031
|
+
const roots = [];
|
|
13032
|
+
const add = (p) => {
|
|
12882
13033
|
try {
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
13034
|
+
if (p && existsSync12(p) && statSync(p).isDirectory()) roots.push(p);
|
|
13035
|
+
} catch {
|
|
13036
|
+
}
|
|
13037
|
+
};
|
|
13038
|
+
add(join11(homedir11(), ".claude", "skills"));
|
|
13039
|
+
add(join11(homedir11(), ".agents", "skills"));
|
|
13040
|
+
if (repoRoot) {
|
|
13041
|
+
add(join11(repoRoot, ".claude", "skills"));
|
|
13042
|
+
add(join11(repoRoot, ".agents", "skills"));
|
|
13043
|
+
}
|
|
13044
|
+
const out = [];
|
|
13045
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13046
|
+
const MAX = 200;
|
|
13047
|
+
const consider = (file, name) => {
|
|
13048
|
+
if (out.length >= MAX) return;
|
|
13049
|
+
let content = "";
|
|
13050
|
+
try {
|
|
13051
|
+
const st = statSync(file);
|
|
13052
|
+
if (!st.isFile() || st.size > 2e5) return;
|
|
13053
|
+
content = readFileSync11(file, "utf-8");
|
|
13054
|
+
} catch {
|
|
13055
|
+
return;
|
|
13056
|
+
}
|
|
13057
|
+
if (!content.trim()) return;
|
|
13058
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
13059
|
+
if (seen.has(hash) || excludeHashes.has(hash)) return;
|
|
13060
|
+
seen.add(hash);
|
|
13061
|
+
out.push({ source: `skill:${name}`, content, name, hash });
|
|
13062
|
+
};
|
|
13063
|
+
for (const root of roots) {
|
|
13064
|
+
let entries = [];
|
|
13065
|
+
try {
|
|
13066
|
+
entries = readdirSync3(root);
|
|
13067
|
+
} catch {
|
|
13068
|
+
continue;
|
|
13069
|
+
}
|
|
13070
|
+
for (const entry of entries) {
|
|
13071
|
+
if (entry.startsWith(".")) continue;
|
|
13072
|
+
const full = join11(root, entry);
|
|
13073
|
+
let st;
|
|
13074
|
+
try {
|
|
13075
|
+
st = statSync(full);
|
|
13076
|
+
} catch {
|
|
13077
|
+
continue;
|
|
13078
|
+
}
|
|
13079
|
+
if (st.isDirectory()) {
|
|
13080
|
+
const skillMd = join11(full, "SKILL.md");
|
|
13081
|
+
if (existsSync12(skillMd)) consider(skillMd, entry);
|
|
13082
|
+
} else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
|
|
13083
|
+
consider(full, entry.replace(/\.mdx?$/i, ""));
|
|
13084
|
+
}
|
|
13085
|
+
}
|
|
13086
|
+
}
|
|
13087
|
+
return out;
|
|
13088
|
+
}
|
|
13089
|
+
function discoverySetHash(found) {
|
|
13090
|
+
return createHash("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
|
|
13091
|
+
}
|
|
13092
|
+
async function promptSkillDiscovery(found) {
|
|
13093
|
+
if (!process.stdin.isTTY || found.length === 0) return [];
|
|
13094
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
13095
|
+
const ask3 = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
|
|
13096
|
+
try {
|
|
13097
|
+
console.log(`
|
|
13098
|
+
Found ${found.length} skill${found.length === 1 ? "" : "s"} in your Claude Code / skills.sh folders:`);
|
|
13099
|
+
found.forEach((s, i) => console.log(` ${i + 1}. ${s.name}`));
|
|
13100
|
+
console.log("Ingest into Synkro as enforceable rules? Each is security-vetted first \u2014 anything");
|
|
13101
|
+
console.log("malicious (prompt injection, guardrail-weakening, secret exfil) is blocked and reported.");
|
|
13102
|
+
const a = (await ask3("Ingest [all] / type numbers (e.g. 1,3) / [skip]: ")).toLowerCase();
|
|
13103
|
+
if (a === "skip" || a === "n" || a === "no" || a === "s") return [];
|
|
13104
|
+
if (a === "" || a === "all" || a === "y" || a === "yes" || a === "a") return found.map((_, i) => i);
|
|
13105
|
+
const picks = a.split(/[\s,]+/).map((x) => parseInt(x, 10) - 1).filter((i) => Number.isInteger(i) && i >= 0 && i < found.length);
|
|
13106
|
+
return [...new Set(picks)];
|
|
13107
|
+
} finally {
|
|
13108
|
+
rl.close();
|
|
13109
|
+
}
|
|
13110
|
+
}
|
|
13111
|
+
async function discoverAndIngestSkills() {
|
|
13112
|
+
try {
|
|
13113
|
+
const sf = readFullSynkroFile();
|
|
13114
|
+
const repoRoot = sf?._repoRoot || detectGitRepo2();
|
|
13115
|
+
const excludeHashes = /* @__PURE__ */ new Set();
|
|
13116
|
+
if (sf?.skills?.length) {
|
|
13117
|
+
for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
|
|
13118
|
+
try {
|
|
13119
|
+
excludeHashes.add(createHash("sha256").update(readFileSync11(fp, "utf-8")).digest("hex"));
|
|
13120
|
+
} catch {
|
|
13121
|
+
}
|
|
13122
|
+
}
|
|
13123
|
+
}
|
|
13124
|
+
const found = discoverSkillFiles(repoRoot, excludeHashes);
|
|
13125
|
+
if (found.length === 0) return;
|
|
13126
|
+
const setHash = discoverySetHash(found);
|
|
13127
|
+
let prev = "";
|
|
13128
|
+
try {
|
|
13129
|
+
prev = readFileSync11(SKILLS_DISCOVERED_PATH, "utf-8").trim();
|
|
13130
|
+
} catch {
|
|
13131
|
+
}
|
|
13132
|
+
if (prev === setHash) return;
|
|
13133
|
+
const picks = await promptSkillDiscovery(found);
|
|
13134
|
+
try {
|
|
13135
|
+
writeFileSync8(SKILLS_DISCOVERED_PATH, setHash);
|
|
13136
|
+
} catch {
|
|
12894
13137
|
}
|
|
13138
|
+
if (picks.length === 0) {
|
|
13139
|
+
console.log(" Skipped \u2014 you can ingest skills anytime from the Synkro dashboard or via the MCP tool.");
|
|
13140
|
+
return;
|
|
13141
|
+
}
|
|
13142
|
+
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
13143
|
+
console.log(` Ingesting ${picks.length} skill${picks.length === 1 ? "" : "s"} (security-vetting each first)\u2026`);
|
|
13144
|
+
const summary = await ingestSkillTasks(picks.map((i) => ({ source: found[i].source, content: found[i].content })), mcpPort);
|
|
13145
|
+
const parts = [`${summary.ingested} ingested (${summary.rules} rule${summary.rules === 1 ? "" : "s"})`];
|
|
13146
|
+
if (summary.rejected.length) parts.push(`${summary.rejected.length} blocked`);
|
|
13147
|
+
console.log(` Skills: ${parts.join(", ")} \u2014 each in its own toggleable pack.`);
|
|
13148
|
+
} catch (e) {
|
|
13149
|
+
console.warn(` \u26A0 skill discovery skipped (${e instanceof Error ? e.message : String(e)})`);
|
|
12895
13150
|
}
|
|
12896
13151
|
}
|
|
12897
13152
|
function detectGitRepo2() {
|
|
@@ -12912,17 +13167,17 @@ function detectGitRepo2() {
|
|
|
12912
13167
|
function getClaudeProjectsFolder() {
|
|
12913
13168
|
const cwd = process.cwd();
|
|
12914
13169
|
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
12915
|
-
const projectsDir =
|
|
12916
|
-
return
|
|
13170
|
+
const projectsDir = join11(homedir11(), ".claude", "projects", sanitized);
|
|
13171
|
+
return existsSync12(projectsDir) ? projectsDir : null;
|
|
12917
13172
|
}
|
|
12918
13173
|
function extractSessionInsights(projectsDir) {
|
|
12919
13174
|
const insights = [];
|
|
12920
13175
|
const files = readdirSync3(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
12921
13176
|
for (const file of files) {
|
|
12922
13177
|
const sessionId = file.replace(".jsonl", "");
|
|
12923
|
-
const filePath =
|
|
13178
|
+
const filePath = join11(projectsDir, file);
|
|
12924
13179
|
try {
|
|
12925
|
-
const content =
|
|
13180
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
12926
13181
|
const lines = content.split("\n").filter(Boolean);
|
|
12927
13182
|
for (let i = 0; i < lines.length; i++) {
|
|
12928
13183
|
try {
|
|
@@ -13001,14 +13256,14 @@ function cursorProjectSlug(workspaceRoot) {
|
|
|
13001
13256
|
return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
13002
13257
|
}
|
|
13003
13258
|
function getCursorTranscriptsDir() {
|
|
13004
|
-
const dir =
|
|
13005
|
-
return
|
|
13259
|
+
const dir = join11(homedir11(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
|
|
13260
|
+
return existsSync12(dir) ? dir : null;
|
|
13006
13261
|
}
|
|
13007
13262
|
function isSafeConvId(id) {
|
|
13008
13263
|
return /^[A-Za-z0-9_-]+$/.test(id);
|
|
13009
13264
|
}
|
|
13010
13265
|
function parseCursorTranscriptFile(filePath) {
|
|
13011
|
-
const content =
|
|
13266
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13012
13267
|
const lines = content.split("\n").filter(Boolean);
|
|
13013
13268
|
const messages = [];
|
|
13014
13269
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -13040,8 +13295,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13040
13295
|
for (let i = 0; i < convDirs.length; i++) {
|
|
13041
13296
|
const convId = convDirs[i];
|
|
13042
13297
|
if (!isSafeConvId(convId)) continue;
|
|
13043
|
-
const filePath =
|
|
13044
|
-
if (!
|
|
13298
|
+
const filePath = join11(dir, convId, `${convId}.jsonl`);
|
|
13299
|
+
if (!existsSync12(filePath)) continue;
|
|
13045
13300
|
try {
|
|
13046
13301
|
const all = parseCursorTranscriptFile(filePath);
|
|
13047
13302
|
const messages = all.length > 500 ? all.slice(-500) : all;
|
|
@@ -13063,8 +13318,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13063
13318
|
process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
|
|
13064
13319
|
}
|
|
13065
13320
|
try {
|
|
13066
|
-
const lc =
|
|
13067
|
-
writeFileSync8(
|
|
13321
|
+
const lc = readFileSync11(filePath, "utf-8").split("\n").filter(Boolean).length;
|
|
13322
|
+
writeFileSync8(join11(OFFSETS_DIR, convId), String(lc), "utf-8");
|
|
13068
13323
|
} catch {
|
|
13069
13324
|
}
|
|
13070
13325
|
}
|
|
@@ -13072,7 +13327,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13072
13327
|
return { sessions: totalSessions, messages: totalMessages };
|
|
13073
13328
|
}
|
|
13074
13329
|
function parseTranscriptFile(filePath) {
|
|
13075
|
-
const content =
|
|
13330
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13076
13331
|
const lines = content.split("\n").filter(Boolean);
|
|
13077
13332
|
const messages = [];
|
|
13078
13333
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -13120,7 +13375,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13120
13375
|
for (let i = 0; i < files.length; i++) {
|
|
13121
13376
|
const file = files[i];
|
|
13122
13377
|
const sessionId = file.replace(".jsonl", "");
|
|
13123
|
-
const filePath =
|
|
13378
|
+
const filePath = join11(projectsDir, file);
|
|
13124
13379
|
try {
|
|
13125
13380
|
const allMessages = parseTranscriptFile(filePath);
|
|
13126
13381
|
const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
|
|
@@ -13142,9 +13397,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
13142
13397
|
process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
|
|
13143
13398
|
}
|
|
13144
13399
|
try {
|
|
13145
|
-
const content =
|
|
13400
|
+
const content = readFileSync11(join11(projectsDir, file), "utf-8");
|
|
13146
13401
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
13147
|
-
writeFileSync8(
|
|
13402
|
+
writeFileSync8(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
13148
13403
|
} catch {
|
|
13149
13404
|
}
|
|
13150
13405
|
}
|
|
@@ -13165,7 +13420,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
13165
13420
|
const sessions = [];
|
|
13166
13421
|
for (const file of batch) {
|
|
13167
13422
|
const sessionId = file.replace(".jsonl", "");
|
|
13168
|
-
const filePath =
|
|
13423
|
+
const filePath = join11(projectsDir, file);
|
|
13169
13424
|
try {
|
|
13170
13425
|
const allMessages = parseTranscriptFile(filePath);
|
|
13171
13426
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -13194,18 +13449,18 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
13194
13449
|
}
|
|
13195
13450
|
for (const file of batch) {
|
|
13196
13451
|
const sessionId = file.replace(".jsonl", "");
|
|
13197
|
-
const filePath =
|
|
13452
|
+
const filePath = join11(projectsDir, file);
|
|
13198
13453
|
try {
|
|
13199
|
-
const content =
|
|
13454
|
+
const content = readFileSync11(filePath, "utf-8");
|
|
13200
13455
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
13201
|
-
writeFileSync8(
|
|
13456
|
+
writeFileSync8(join11(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
13202
13457
|
} catch {
|
|
13203
13458
|
}
|
|
13204
13459
|
}
|
|
13205
13460
|
}
|
|
13206
13461
|
return { sessions: totalSessions, messages: totalMessages };
|
|
13207
13462
|
}
|
|
13208
|
-
var
|
|
13463
|
+
var SYNKRO_DIR6, HOOKS_DIR, BIN_DIR, CONFIG_PATH2, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
|
|
13209
13464
|
var init_install = __esm({
|
|
13210
13465
|
"cli/commands/install.ts"() {
|
|
13211
13466
|
"use strict";
|
|
@@ -13224,10 +13479,10 @@ var init_install = __esm({
|
|
|
13224
13479
|
init_claudeDesktopTap();
|
|
13225
13480
|
init_dockerInstall();
|
|
13226
13481
|
init_setupToken();
|
|
13227
|
-
|
|
13228
|
-
HOOKS_DIR =
|
|
13229
|
-
BIN_DIR =
|
|
13230
|
-
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");
|
|
13231
13486
|
MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
|
|
13232
13487
|
import { readFileSync } from 'node:fs';
|
|
13233
13488
|
import { homedir } from 'node:os';
|
|
@@ -13338,20 +13593,21 @@ rl.on('line', async (line) => {
|
|
|
13338
13593
|
}
|
|
13339
13594
|
});
|
|
13340
13595
|
`;
|
|
13341
|
-
OFFSETS_DIR =
|
|
13342
|
-
CLOUD_JWT_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");
|
|
13343
13599
|
}
|
|
13344
13600
|
});
|
|
13345
13601
|
|
|
13346
13602
|
// cli/local-cc/install.ts
|
|
13347
|
-
import { existsSync as
|
|
13348
|
-
import { join as
|
|
13349
|
-
import { homedir as
|
|
13350
|
-
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";
|
|
13351
13607
|
function writePluginFiles() {
|
|
13352
13608
|
for (const c of CHANNELS) {
|
|
13353
|
-
|
|
13354
|
-
|
|
13609
|
+
mkdirSync11(c.sessionDir, { recursive: true });
|
|
13610
|
+
mkdirSync11(c.pluginSettingsDir, { recursive: true });
|
|
13355
13611
|
writeFileSync9(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
|
|
13356
13612
|
writeFileSync9(
|
|
13357
13613
|
c.pluginSettingsPath,
|
|
@@ -13374,7 +13630,7 @@ function writePluginFiles() {
|
|
|
13374
13630
|
}
|
|
13375
13631
|
function runBunInstall() {
|
|
13376
13632
|
for (const c of CHANNELS) {
|
|
13377
|
-
const r =
|
|
13633
|
+
const r = spawnSync6("bun", ["install", "--silent"], {
|
|
13378
13634
|
cwd: c.sessionDir,
|
|
13379
13635
|
encoding: "utf-8",
|
|
13380
13636
|
timeout: 12e4
|
|
@@ -13387,10 +13643,10 @@ function runBunInstall() {
|
|
|
13387
13643
|
}
|
|
13388
13644
|
}
|
|
13389
13645
|
function safelyMutateClaudeJson(mutator) {
|
|
13390
|
-
if (!
|
|
13646
|
+
if (!existsSync13(CLAUDE_JSON_PATH)) {
|
|
13391
13647
|
return;
|
|
13392
13648
|
}
|
|
13393
|
-
const originalText =
|
|
13649
|
+
const originalText = readFileSync12(CLAUDE_JSON_PATH, "utf-8");
|
|
13394
13650
|
let parsed;
|
|
13395
13651
|
try {
|
|
13396
13652
|
parsed = JSON.parse(originalText);
|
|
@@ -13490,15 +13746,15 @@ function patchClaudeJson() {
|
|
|
13490
13746
|
});
|
|
13491
13747
|
}
|
|
13492
13748
|
function installLocalCC() {
|
|
13493
|
-
let bunCheck =
|
|
13749
|
+
let bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
|
|
13494
13750
|
if (bunCheck.status !== 0) {
|
|
13495
13751
|
if (process.platform === "darwin") {
|
|
13496
13752
|
console.log(" Installing bun via brew...");
|
|
13497
|
-
const brewR =
|
|
13753
|
+
const brewR = spawnSync6("brew", ["install", "oven-sh/bun/bun"], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
13498
13754
|
if (brewR.status !== 0) {
|
|
13499
13755
|
throw new LocalCCInstallError("bun auto-install failed. Install manually: curl -fsSL https://bun.sh/install | bash");
|
|
13500
13756
|
}
|
|
13501
|
-
bunCheck =
|
|
13757
|
+
bunCheck = spawnSync6("bun", ["--version"], { encoding: "utf-8" });
|
|
13502
13758
|
if (bunCheck.status !== 0) {
|
|
13503
13759
|
throw new LocalCCInstallError("bun installed but not found on PATH. Restart your terminal and re-run install.");
|
|
13504
13760
|
}
|
|
@@ -13532,42 +13788,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
|
|
|
13532
13788
|
var init_install2 = __esm({
|
|
13533
13789
|
"cli/local-cc/install.ts"() {
|
|
13534
13790
|
"use strict";
|
|
13535
|
-
CLAUDE_JSON_BACKUP_PATH =
|
|
13536
|
-
SESSION_DIR =
|
|
13537
|
-
PLUGIN_PATH =
|
|
13538
|
-
PLUGIN_PKG_PATH =
|
|
13539
|
-
PLUGIN_SETTINGS_DIR =
|
|
13540
|
-
PLUGIN_SETTINGS_PATH =
|
|
13541
|
-
PROJECT_MCP_PATH =
|
|
13542
|
-
CLAUDE_JSON_PATH =
|
|
13543
|
-
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");
|
|
13544
13800
|
TMUX_SESSION_NAME = "synkro-local-cc";
|
|
13545
13801
|
CHANNEL_1_PORT = 8941;
|
|
13546
|
-
SESSION_DIR_2 =
|
|
13547
|
-
PLUGIN_PATH_2 =
|
|
13548
|
-
PLUGIN_PKG_PATH_2 =
|
|
13549
|
-
PLUGIN_SETTINGS_DIR_2 =
|
|
13550
|
-
PLUGIN_SETTINGS_PATH_2 =
|
|
13551
|
-
PROJECT_MCP_PATH_2 =
|
|
13552
|
-
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");
|
|
13553
13809
|
TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
|
|
13554
13810
|
CHANNEL_2_PORT = 8951;
|
|
13555
|
-
SESSION_DIR_3 =
|
|
13556
|
-
PLUGIN_PATH_3 =
|
|
13557
|
-
PLUGIN_PKG_PATH_3 =
|
|
13558
|
-
PLUGIN_SETTINGS_DIR_3 =
|
|
13559
|
-
PLUGIN_SETTINGS_PATH_3 =
|
|
13560
|
-
PROJECT_MCP_PATH_3 =
|
|
13561
|
-
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");
|
|
13562
13818
|
TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
|
|
13563
13819
|
CHANNEL_3_PORT = 8942;
|
|
13564
|
-
SESSION_DIR_4 =
|
|
13565
|
-
PLUGIN_PATH_4 =
|
|
13566
|
-
PLUGIN_PKG_PATH_4 =
|
|
13567
|
-
PLUGIN_SETTINGS_DIR_4 =
|
|
13568
|
-
PLUGIN_SETTINGS_PATH_4 =
|
|
13569
|
-
PROJECT_MCP_PATH_4 =
|
|
13570
|
-
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");
|
|
13571
13827
|
TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
|
|
13572
13828
|
CHANNEL_4_PORT = 8952;
|
|
13573
13829
|
RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
|
|
@@ -13841,10 +14097,10 @@ var disconnect_exports = {};
|
|
|
13841
14097
|
__export(disconnect_exports, {
|
|
13842
14098
|
disconnectCommand: () => disconnectCommand
|
|
13843
14099
|
});
|
|
13844
|
-
import { existsSync as
|
|
13845
|
-
import { homedir as
|
|
13846
|
-
import { join as
|
|
13847
|
-
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";
|
|
13848
14104
|
import { createInterface as createInterface4 } from "readline";
|
|
13849
14105
|
async function tearDownLocalCC() {
|
|
13850
14106
|
const docker = dockerStatus();
|
|
@@ -13858,7 +14114,7 @@ async function tearDownLocalCC() {
|
|
|
13858
14114
|
console.log("\u2713 removed synkro-server container");
|
|
13859
14115
|
try {
|
|
13860
14116
|
const image = imageTag();
|
|
13861
|
-
const r =
|
|
14117
|
+
const r = spawnSync7("docker", ["rmi", "-f", image], { encoding: "utf-8", timeout: 3e4 });
|
|
13862
14118
|
console.log(r.status === 0 ? `\u2713 removed Docker image ${image}` : "\xB7 no Docker image to remove");
|
|
13863
14119
|
} catch {
|
|
13864
14120
|
}
|
|
@@ -13921,34 +14177,34 @@ async function disconnectCommand(args2 = []) {
|
|
|
13921
14177
|
const desktopMcpRemoved = uninstallClaudeDesktopMcpConfig();
|
|
13922
14178
|
console.log(`${desktopMcpRemoved ? "\u2713" : "\xB7"} MCP guardrails (Claude Desktop): ${desktopMcpRemoved ? "removed from claude_desktop_config.json" : "no entry found"}`);
|
|
13923
14179
|
}
|
|
13924
|
-
if (
|
|
14180
|
+
if (existsSync14(SYNKRO_DIR7)) {
|
|
13925
14181
|
if (purge) {
|
|
13926
|
-
|
|
13927
|
-
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`);
|
|
13928
14184
|
} else {
|
|
13929
|
-
const keep = /* @__PURE__ */ new Set([
|
|
14185
|
+
const keep = /* @__PURE__ */ new Set([join13(SYNKRO_DIR7, "pgdata"), join13(SYNKRO_DIR7, "pgdata-backups")]);
|
|
13930
14186
|
const preserved = [];
|
|
13931
|
-
for (const entry of readdirSync4(
|
|
13932
|
-
const full =
|
|
14187
|
+
for (const entry of readdirSync4(SYNKRO_DIR7)) {
|
|
14188
|
+
const full = join13(SYNKRO_DIR7, entry);
|
|
13933
14189
|
if (keep.has(full)) {
|
|
13934
14190
|
preserved.push(entry);
|
|
13935
14191
|
continue;
|
|
13936
14192
|
}
|
|
13937
|
-
|
|
14193
|
+
rmSync2(full, { recursive: true, force: true });
|
|
13938
14194
|
}
|
|
13939
14195
|
if (preserved.length > 0) {
|
|
13940
|
-
console.log(`\u2713 removed Synkro config from ${
|
|
14196
|
+
console.log(`\u2713 removed Synkro config from ${SYNKRO_DIR7} (kept your scan data: ${preserved.join(", ")})`);
|
|
13941
14197
|
console.log(" run `synkro uninstall --purge` to delete that too");
|
|
13942
14198
|
} else {
|
|
13943
|
-
console.log(`\u2713 removed ${
|
|
14199
|
+
console.log(`\u2713 removed ${SYNKRO_DIR7}`);
|
|
13944
14200
|
}
|
|
13945
14201
|
}
|
|
13946
14202
|
} else {
|
|
13947
|
-
console.log(`\xB7 ${
|
|
14203
|
+
console.log(`\xB7 ${SYNKRO_DIR7} already gone`);
|
|
13948
14204
|
}
|
|
13949
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.");
|
|
13950
14206
|
}
|
|
13951
|
-
var
|
|
14207
|
+
var SYNKRO_DIR7;
|
|
13952
14208
|
var init_disconnect = __esm({
|
|
13953
14209
|
"cli/commands/disconnect.ts"() {
|
|
13954
14210
|
"use strict";
|
|
@@ -13959,14 +14215,14 @@ var init_disconnect = __esm({
|
|
|
13959
14215
|
init_install2();
|
|
13960
14216
|
init_dockerInstall();
|
|
13961
14217
|
init_macKeychain();
|
|
13962
|
-
|
|
14218
|
+
SYNKRO_DIR7 = join13(homedir13(), ".synkro");
|
|
13963
14219
|
}
|
|
13964
14220
|
});
|
|
13965
14221
|
|
|
13966
14222
|
// cli/local-cc/turnLog.ts
|
|
13967
|
-
import { appendFileSync, existsSync as
|
|
13968
|
-
import { dirname as dirname6, join as
|
|
13969
|
-
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";
|
|
13970
14226
|
function truncate(s, max = PREVIEW_MAX) {
|
|
13971
14227
|
if (s.length <= max) return s;
|
|
13972
14228
|
return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
|
|
@@ -13986,7 +14242,7 @@ function extractSeverity(result) {
|
|
|
13986
14242
|
}
|
|
13987
14243
|
function appendTurn(args2) {
|
|
13988
14244
|
try {
|
|
13989
|
-
|
|
14245
|
+
mkdirSync12(dirname6(TURN_LOG_PATH), { recursive: true });
|
|
13990
14246
|
const entry = {
|
|
13991
14247
|
ts: new Date(args2.startedAt).toISOString(),
|
|
13992
14248
|
role: args2.role,
|
|
@@ -14002,11 +14258,11 @@ function appendTurn(args2) {
|
|
|
14002
14258
|
}
|
|
14003
14259
|
}
|
|
14004
14260
|
function readRecentTurns(n = 20) {
|
|
14005
|
-
if (!
|
|
14261
|
+
if (!existsSync15(TURN_LOG_PATH)) return [];
|
|
14006
14262
|
try {
|
|
14007
14263
|
const size = statSync2(TURN_LOG_PATH).size;
|
|
14008
14264
|
if (size === 0) return [];
|
|
14009
|
-
const text =
|
|
14265
|
+
const text = readFileSync13(TURN_LOG_PATH, "utf-8");
|
|
14010
14266
|
const lines = text.split("\n").filter(Boolean);
|
|
14011
14267
|
const lastN = lines.slice(-n).reverse();
|
|
14012
14268
|
return lastN.map((line) => {
|
|
@@ -14022,8 +14278,8 @@ function readRecentTurns(n = 20) {
|
|
|
14022
14278
|
}
|
|
14023
14279
|
function followTurns(onEntry) {
|
|
14024
14280
|
try {
|
|
14025
|
-
|
|
14026
|
-
if (!
|
|
14281
|
+
mkdirSync12(dirname6(TURN_LOG_PATH), { recursive: true });
|
|
14282
|
+
if (!existsSync15(TURN_LOG_PATH)) {
|
|
14027
14283
|
appendFileSync(TURN_LOG_PATH, "", "utf-8");
|
|
14028
14284
|
}
|
|
14029
14285
|
} catch {
|
|
@@ -14085,7 +14341,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
|
|
|
14085
14341
|
var init_turnLog = __esm({
|
|
14086
14342
|
"cli/local-cc/turnLog.ts"() {
|
|
14087
14343
|
"use strict";
|
|
14088
|
-
TURN_LOG_PATH =
|
|
14344
|
+
TURN_LOG_PATH = join14(homedir14(), ".synkro", "cc_sessions", "turns.log");
|
|
14089
14345
|
PREVIEW_MAX = 400;
|
|
14090
14346
|
}
|
|
14091
14347
|
});
|
|
@@ -14234,19 +14490,19 @@ var init_grade = __esm({
|
|
|
14234
14490
|
});
|
|
14235
14491
|
|
|
14236
14492
|
// cli/local-cc/pueue.ts
|
|
14237
|
-
import { execFileSync as execFileSync3, spawnSync as
|
|
14238
|
-
import { homedir as
|
|
14239
|
-
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";
|
|
14240
14496
|
import { connect as connect2 } from "net";
|
|
14241
14497
|
function pueueAvailable() {
|
|
14242
|
-
const r =
|
|
14498
|
+
const r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14243
14499
|
if (r.status !== 0) {
|
|
14244
14500
|
throw new PueueError("pueue CLI not found on PATH. Install pueue (https://github.com/Nukesor/pueue) and start `pueued`.");
|
|
14245
14501
|
}
|
|
14246
14502
|
}
|
|
14247
14503
|
function statusJson() {
|
|
14248
14504
|
pueueAvailable();
|
|
14249
|
-
const r =
|
|
14505
|
+
const r = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8" });
|
|
14250
14506
|
if (r.status !== 0) {
|
|
14251
14507
|
throw new PueueError(`pueue status failed: ${r.stderr || r.stdout || "unknown error"} \u2014 is pueued running?`);
|
|
14252
14508
|
}
|
|
@@ -14291,18 +14547,18 @@ function startTask(opts = {}) {
|
|
|
14291
14547
|
let existing = findTask(ch);
|
|
14292
14548
|
while (existing) {
|
|
14293
14549
|
if (existing.status === "Running" || existing.status === "Queued") {
|
|
14294
|
-
|
|
14295
|
-
|
|
14550
|
+
spawnSync8("tmux", ["kill-session", "-t", `=${ch.tmuxSession}`], { encoding: "utf-8" });
|
|
14551
|
+
spawnSync8("pueue", ["kill", String(existing.id)], { encoding: "utf-8" });
|
|
14296
14552
|
for (let i = 0; i < 10; i++) {
|
|
14297
14553
|
const check = findTask(ch);
|
|
14298
14554
|
if (!check || check.id !== existing.id || check.status !== "Running" && check.status !== "Queued") break;
|
|
14299
|
-
|
|
14555
|
+
spawnSync8("sleep", ["0.5"], { encoding: "utf-8" });
|
|
14300
14556
|
}
|
|
14301
14557
|
}
|
|
14302
|
-
|
|
14558
|
+
spawnSync8("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
|
|
14303
14559
|
existing = findTask(ch);
|
|
14304
14560
|
}
|
|
14305
|
-
const runScript =
|
|
14561
|
+
const runScript = join15(cwd, "run-claude.sh");
|
|
14306
14562
|
const args2 = [
|
|
14307
14563
|
"add",
|
|
14308
14564
|
"--label",
|
|
@@ -14313,7 +14569,7 @@ function startTask(opts = {}) {
|
|
|
14313
14569
|
"bash",
|
|
14314
14570
|
runScript
|
|
14315
14571
|
];
|
|
14316
|
-
const r =
|
|
14572
|
+
const r = spawnSync8("pueue", args2, { encoding: "utf-8" });
|
|
14317
14573
|
if (r.status !== 0) {
|
|
14318
14574
|
throw new PueueError(`pueue add failed: ${r.stderr || r.stdout}`);
|
|
14319
14575
|
}
|
|
@@ -14324,25 +14580,25 @@ function startTask(opts = {}) {
|
|
|
14324
14580
|
return created;
|
|
14325
14581
|
}
|
|
14326
14582
|
function stopTask(channel = CHANNEL_PRIMARY) {
|
|
14327
|
-
|
|
14583
|
+
spawnSync8("tmux", ["kill-session", "-t", `=${channel.tmuxSession}`], { encoding: "utf-8" });
|
|
14328
14584
|
let t = findTask(channel);
|
|
14329
14585
|
while (t) {
|
|
14330
14586
|
if (t.status === "Running" || t.status === "Queued") {
|
|
14331
|
-
|
|
14587
|
+
spawnSync8("pueue", ["kill", String(t.id)], { encoding: "utf-8" });
|
|
14332
14588
|
for (let i = 0; i < 10; i++) {
|
|
14333
14589
|
const check = findTask(channel);
|
|
14334
14590
|
if (!check || check.id !== t.id || check.status !== "Running" && check.status !== "Queued") break;
|
|
14335
|
-
|
|
14591
|
+
spawnSync8("sleep", ["0.5"], { encoding: "utf-8" });
|
|
14336
14592
|
}
|
|
14337
14593
|
}
|
|
14338
|
-
|
|
14594
|
+
spawnSync8("pueue", ["remove", String(t.id)], { encoding: "utf-8" });
|
|
14339
14595
|
t = findTask(channel);
|
|
14340
14596
|
}
|
|
14341
14597
|
}
|
|
14342
14598
|
function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
|
|
14343
14599
|
const t = findTask(channel);
|
|
14344
14600
|
if (!t) return `(no ${channel.taskLabel} task)`;
|
|
14345
|
-
const r =
|
|
14601
|
+
const r = spawnSync8("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
|
|
14346
14602
|
return r.stdout || r.stderr || "(no output)";
|
|
14347
14603
|
}
|
|
14348
14604
|
function ensureRunning(opts = {}) {
|
|
@@ -14367,8 +14623,8 @@ function probePort(host, port, timeoutMs = 500) {
|
|
|
14367
14623
|
});
|
|
14368
14624
|
}
|
|
14369
14625
|
function tmuxDismissPrompts(tmuxSession = TMUX_SESSION) {
|
|
14370
|
-
|
|
14371
|
-
|
|
14626
|
+
spawnSync8("tmux", ["send-keys", "-t", tmuxSession, "1"], { encoding: "utf-8" });
|
|
14627
|
+
spawnSync8("tmux", ["send-keys", "-t", tmuxSession, "Enter"], { encoding: "utf-8" });
|
|
14372
14628
|
}
|
|
14373
14629
|
async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tmuxSession = TMUX_SESSION) {
|
|
14374
14630
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -14380,46 +14636,46 @@ async function waitForChannelReady(port, timeoutMs = 6e4, host = "127.0.0.1", tm
|
|
|
14380
14636
|
return probePort(host, port);
|
|
14381
14637
|
}
|
|
14382
14638
|
function brewInstall(pkg) {
|
|
14383
|
-
const brew =
|
|
14639
|
+
const brew = spawnSync8("brew", ["--version"], { encoding: "utf-8" });
|
|
14384
14640
|
if (brew.status !== 0) return false;
|
|
14385
14641
|
console.log(` Installing ${pkg} via brew...`);
|
|
14386
|
-
const r =
|
|
14642
|
+
const r = spawnSync8("brew", ["install", pkg], { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
14387
14643
|
return r.status === 0;
|
|
14388
14644
|
}
|
|
14389
14645
|
function assertPueueInstalled() {
|
|
14390
|
-
let r =
|
|
14646
|
+
let r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14391
14647
|
if (r.status !== 0) {
|
|
14392
14648
|
if (process.platform === "darwin" && brewInstall("pueue")) {
|
|
14393
|
-
r =
|
|
14649
|
+
r = spawnSync8("pueue", ["--version"], { encoding: "utf-8" });
|
|
14394
14650
|
if (r.status !== 0) throw new PueueError("pueue install succeeded but binary not found on PATH.");
|
|
14395
14651
|
} else {
|
|
14396
14652
|
throw new PueueError("pueue not found. Install it: brew install pueue (macOS) or https://github.com/Nukesor/pueue");
|
|
14397
14653
|
}
|
|
14398
14654
|
}
|
|
14399
|
-
const status =
|
|
14655
|
+
const status = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
|
|
14400
14656
|
if (status.status !== 0) {
|
|
14401
14657
|
console.log(" Starting pueued daemon...");
|
|
14402
14658
|
const child = spawn2("pueued", ["-d"], { stdio: "ignore", detached: true });
|
|
14403
14659
|
child.unref();
|
|
14404
|
-
|
|
14405
|
-
const retry =
|
|
14660
|
+
spawnSync8("sleep", ["1"]);
|
|
14661
|
+
const retry = spawnSync8("pueue", ["status", "--json"], { encoding: "utf-8", timeout: 5e3 });
|
|
14406
14662
|
if (retry.status !== 0) {
|
|
14407
14663
|
throw new PueueError("pueue daemon not reachable after starting pueued. Check `pueued` manually.");
|
|
14408
14664
|
}
|
|
14409
14665
|
}
|
|
14410
|
-
|
|
14666
|
+
spawnSync8("pueue", ["parallel", "2"], { encoding: "utf-8" });
|
|
14411
14667
|
}
|
|
14412
14668
|
function assertClaudeInstalled() {
|
|
14413
|
-
const r =
|
|
14669
|
+
const r = spawnSync8("claude", ["--version"], { encoding: "utf-8" });
|
|
14414
14670
|
if (r.status !== 0) {
|
|
14415
14671
|
throw new PueueError("claude CLI not found on PATH. Install Claude Code first: https://docs.claude.com/claude-code");
|
|
14416
14672
|
}
|
|
14417
14673
|
}
|
|
14418
14674
|
function assertTmuxInstalled() {
|
|
14419
|
-
let r =
|
|
14675
|
+
let r = spawnSync8("tmux", ["-V"], { encoding: "utf-8" });
|
|
14420
14676
|
if (r.status !== 0) {
|
|
14421
14677
|
if (process.platform === "darwin" && brewInstall("tmux")) {
|
|
14422
|
-
r =
|
|
14678
|
+
r = spawnSync8("tmux", ["-V"], { encoding: "utf-8" });
|
|
14423
14679
|
if (r.status !== 0) throw new PueueError("tmux install succeeded but binary not found on PATH.");
|
|
14424
14680
|
} else {
|
|
14425
14681
|
throw new PueueError("tmux not found. Install it: brew install tmux (macOS) or apt install tmux (Linux)");
|
|
@@ -14432,12 +14688,12 @@ var init_pueue = __esm({
|
|
|
14432
14688
|
"use strict";
|
|
14433
14689
|
TASK_LABEL = "synkro-local-cc";
|
|
14434
14690
|
TMUX_SESSION = "synkro-local-cc";
|
|
14435
|
-
SESSION_DIR2 =
|
|
14691
|
+
SESSION_DIR2 = join15(homedir15(), ".synkro", "cc_sessions");
|
|
14436
14692
|
TASK_LABEL_2 = "synkro-local-cc-2";
|
|
14437
14693
|
TMUX_SESSION_2 = "synkro-local-cc-2";
|
|
14438
|
-
SESSION_DIR_22 =
|
|
14439
|
-
SESSION_DIR_32 =
|
|
14440
|
-
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");
|
|
14441
14697
|
PueueError = class extends Error {
|
|
14442
14698
|
constructor(message, cause) {
|
|
14443
14699
|
super(message);
|
|
@@ -14452,13 +14708,13 @@ var init_pueue = __esm({
|
|
|
14452
14708
|
});
|
|
14453
14709
|
|
|
14454
14710
|
// cli/local-cc/settings.ts
|
|
14455
|
-
import { existsSync as
|
|
14456
|
-
import { homedir as
|
|
14457
|
-
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";
|
|
14458
14714
|
function isLocalCCEnabled() {
|
|
14459
|
-
if (!
|
|
14715
|
+
if (!existsSync16(CONFIG_PATH3)) return false;
|
|
14460
14716
|
try {
|
|
14461
|
-
const content =
|
|
14717
|
+
const content = readFileSync14(CONFIG_PATH3, "utf-8");
|
|
14462
14718
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
14463
14719
|
return match?.[1] === "yes";
|
|
14464
14720
|
} catch {
|
|
@@ -14469,7 +14725,7 @@ var CONFIG_PATH3;
|
|
|
14469
14725
|
var init_settings = __esm({
|
|
14470
14726
|
"cli/local-cc/settings.ts"() {
|
|
14471
14727
|
"use strict";
|
|
14472
|
-
CONFIG_PATH3 =
|
|
14728
|
+
CONFIG_PATH3 = join16(homedir16(), ".synkro", "config.env");
|
|
14473
14729
|
}
|
|
14474
14730
|
});
|
|
14475
14731
|
|
|
@@ -14478,11 +14734,11 @@ var localCc_exports = {};
|
|
|
14478
14734
|
__export(localCc_exports, {
|
|
14479
14735
|
localCcCommand: () => localCcCommand
|
|
14480
14736
|
});
|
|
14481
|
-
import { spawnSync as
|
|
14482
|
-
import { homedir as
|
|
14483
|
-
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";
|
|
14484
14740
|
import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
|
|
14485
|
-
import { existsSync as
|
|
14741
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
14486
14742
|
function deploymentMode() {
|
|
14487
14743
|
const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
|
|
14488
14744
|
if (env === "docker") return "docker";
|
|
@@ -14588,15 +14844,15 @@ TROUBLESHOOTING
|
|
|
14588
14844
|
`);
|
|
14589
14845
|
}
|
|
14590
14846
|
function readGatewayUrl() {
|
|
14591
|
-
if (
|
|
14592
|
-
const m =
|
|
14847
|
+
if (existsSync17(CONFIG_PATH4)) {
|
|
14848
|
+
const m = readFileSync15(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
14593
14849
|
if (m) return m[1];
|
|
14594
14850
|
}
|
|
14595
14851
|
return "https://api.synkro.sh";
|
|
14596
14852
|
}
|
|
14597
14853
|
function updateLocalInferenceFlag(enabled) {
|
|
14598
|
-
if (!
|
|
14599
|
-
let content =
|
|
14854
|
+
if (!existsSync17(CONFIG_PATH4)) return;
|
|
14855
|
+
let content = readFileSync15(CONFIG_PATH4, "utf-8");
|
|
14600
14856
|
const flag = enabled ? "yes" : "no";
|
|
14601
14857
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
14602
14858
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -14659,7 +14915,7 @@ async function cmdStatus() {
|
|
|
14659
14915
|
}
|
|
14660
14916
|
const ch1Up = await isChannelAvailable();
|
|
14661
14917
|
console.log(`Channel 1 ${CHANNEL_HOST}:${CHANNEL_PORT}: ${ch1Up ? "reachable" : "unreachable"}`);
|
|
14662
|
-
const tmux1 =
|
|
14918
|
+
const tmux1 = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
|
|
14663
14919
|
console.log(`tmux '${TMUX_SESSION_NAME}': ${tmux1.status === 0 ? "live" : "absent"}`);
|
|
14664
14920
|
const t2 = findTask(CHANNEL_SECONDARY);
|
|
14665
14921
|
if (!t2) {
|
|
@@ -14669,7 +14925,7 @@ async function cmdStatus() {
|
|
|
14669
14925
|
}
|
|
14670
14926
|
const ch2Up = await isChannelAvailable(CHANNEL_2_PORT);
|
|
14671
14927
|
console.log(`Channel 2 ${CHANNEL_HOST}:${CHANNEL_2_PORT}: ${ch2Up ? "reachable" : "unreachable"}`);
|
|
14672
|
-
const tmux2 =
|
|
14928
|
+
const tmux2 = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME_2}`], { encoding: "utf-8" });
|
|
14673
14929
|
console.log(`tmux '${TMUX_SESSION_NAME_2}': ${tmux2.status === 0 ? "live" : "absent"}`);
|
|
14674
14930
|
}
|
|
14675
14931
|
async function cmdEnable() {
|
|
@@ -14875,7 +15131,7 @@ function cmdLogs(rest) {
|
|
|
14875
15131
|
}
|
|
14876
15132
|
return "200";
|
|
14877
15133
|
})();
|
|
14878
|
-
|
|
15134
|
+
spawnSync9("docker", ["logs", "--tail", tailArg, ...followFlag, "synkro-server"], { stdio: "inherit" });
|
|
14879
15135
|
return;
|
|
14880
15136
|
}
|
|
14881
15137
|
for (const arg of rest) {
|
|
@@ -14923,7 +15179,7 @@ function cmdLogs(rest) {
|
|
|
14923
15179
|
function cmdAttach(rest) {
|
|
14924
15180
|
assertTmuxInstalled();
|
|
14925
15181
|
const readonly = rest.some((a) => a === "--readonly" || a === "-r");
|
|
14926
|
-
const has =
|
|
15182
|
+
const has = spawnSync9("tmux", ["has-session", "-t", `=${TMUX_SESSION_NAME}`], { encoding: "utf-8" });
|
|
14927
15183
|
if (has.status !== 0) {
|
|
14928
15184
|
console.error(`No tmux session '${TMUX_SESSION_NAME}' running. Start it with: synkro local-cc start`);
|
|
14929
15185
|
process.exit(1);
|
|
@@ -14936,7 +15192,7 @@ function cmdAttach(rest) {
|
|
|
14936
15192
|
console.log("Detach with Ctrl-B then D. (Do not press Ctrl-C \u2014 that would interrupt claude.)");
|
|
14937
15193
|
console.log();
|
|
14938
15194
|
const args2 = readonly ? ["attach-session", "-r", "-t", TMUX_SESSION_NAME] : ["attach-session", "-t", TMUX_SESSION_NAME];
|
|
14939
|
-
const r =
|
|
15195
|
+
const r = spawnSync9("tmux", args2, { stdio: "inherit" });
|
|
14940
15196
|
process.exit(r.status ?? 0);
|
|
14941
15197
|
}
|
|
14942
15198
|
async function cmdTest() {
|
|
@@ -15037,8 +15293,8 @@ var init_localCc = __esm({
|
|
|
15037
15293
|
init_install();
|
|
15038
15294
|
init_client();
|
|
15039
15295
|
init_stub();
|
|
15040
|
-
SYNKRO_CONFIG_PATH =
|
|
15041
|
-
CONFIG_PATH4 =
|
|
15296
|
+
SYNKRO_CONFIG_PATH = join17(homedir17(), ".synkro", "config.env");
|
|
15297
|
+
CONFIG_PATH4 = join17(homedir17(), ".synkro", "config.env");
|
|
15042
15298
|
}
|
|
15043
15299
|
});
|
|
15044
15300
|
|
|
@@ -15047,15 +15303,15 @@ var import_exports = {};
|
|
|
15047
15303
|
__export(import_exports, {
|
|
15048
15304
|
importCommand: () => importCommand
|
|
15049
15305
|
});
|
|
15050
|
-
import { existsSync as
|
|
15051
|
-
import { homedir as
|
|
15052
|
-
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";
|
|
15053
15309
|
import { execSync as execSync7 } from "child_process";
|
|
15054
15310
|
import { createInterface as createInterface5 } from "readline";
|
|
15055
15311
|
function readConfigEnv() {
|
|
15056
15312
|
const out = {};
|
|
15057
15313
|
try {
|
|
15058
|
-
for (const line of
|
|
15314
|
+
for (const line of readFileSync16(CONFIG_PATH5, "utf-8").split("\n")) {
|
|
15059
15315
|
const t = line.trim();
|
|
15060
15316
|
if (!t || t.startsWith("#")) continue;
|
|
15061
15317
|
const eq = t.indexOf("=");
|
|
@@ -15067,8 +15323,8 @@ function readConfigEnv() {
|
|
|
15067
15323
|
}
|
|
15068
15324
|
function projectsFolder() {
|
|
15069
15325
|
const sanitized = process.cwd().replace(/\//g, "-");
|
|
15070
|
-
const dir =
|
|
15071
|
-
return
|
|
15326
|
+
const dir = join18(homedir18(), ".claude", "projects", sanitized);
|
|
15327
|
+
return existsSync18(dir) ? dir : null;
|
|
15072
15328
|
}
|
|
15073
15329
|
function repoName() {
|
|
15074
15330
|
try {
|
|
@@ -15091,7 +15347,7 @@ function extractText(content) {
|
|
|
15091
15347
|
return "";
|
|
15092
15348
|
}
|
|
15093
15349
|
function parseSession(filePath, sessionId) {
|
|
15094
|
-
const lines =
|
|
15350
|
+
const lines = readFileSync16(filePath, "utf-8").split("\n").filter(Boolean);
|
|
15095
15351
|
const messages = [];
|
|
15096
15352
|
const actions = [];
|
|
15097
15353
|
let step = 0;
|
|
@@ -15164,7 +15420,7 @@ async function importCommand() {
|
|
|
15164
15420
|
return;
|
|
15165
15421
|
}
|
|
15166
15422
|
}
|
|
15167
|
-
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);
|
|
15168
15424
|
const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
|
|
15169
15425
|
let ok = 0, fail = 0;
|
|
15170
15426
|
if (isCloud) {
|
|
@@ -15224,7 +15480,7 @@ var init_import = __esm({
|
|
|
15224
15480
|
"cli/commands/import.ts"() {
|
|
15225
15481
|
"use strict";
|
|
15226
15482
|
init_stub();
|
|
15227
|
-
CONFIG_PATH5 =
|
|
15483
|
+
CONFIG_PATH5 = join18(homedir18(), ".synkro", "config.env");
|
|
15228
15484
|
}
|
|
15229
15485
|
});
|
|
15230
15486
|
|
|
@@ -15266,10 +15522,10 @@ var init_packVerify = __esm({
|
|
|
15266
15522
|
});
|
|
15267
15523
|
|
|
15268
15524
|
// cli/installer/lockfile.ts
|
|
15269
|
-
import { existsSync as
|
|
15270
|
-
import { join as
|
|
15525
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
15526
|
+
import { join as join19 } from "path";
|
|
15271
15527
|
function lockPath(repoRoot) {
|
|
15272
|
-
return
|
|
15528
|
+
return join19(repoRoot, LOCK_FILE);
|
|
15273
15529
|
}
|
|
15274
15530
|
function writeLockfile(repoRoot, entries) {
|
|
15275
15531
|
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
@@ -15302,9 +15558,9 @@ var sync_exports = {};
|
|
|
15302
15558
|
__export(sync_exports, {
|
|
15303
15559
|
syncCommand: () => syncCommand
|
|
15304
15560
|
});
|
|
15305
|
-
import { existsSync as
|
|
15306
|
-
import { homedir as
|
|
15307
|
-
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";
|
|
15308
15564
|
function cacheKey(ref, version) {
|
|
15309
15565
|
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
15310
15566
|
}
|
|
@@ -15335,8 +15591,8 @@ async function syncCommand(_args = []) {
|
|
|
15335
15591
|
}
|
|
15336
15592
|
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
15337
15593
|
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
15338
|
-
const cacheDir =
|
|
15339
|
-
if (!cloud)
|
|
15594
|
+
const cacheDir = join20(homedir19(), ".synkro", "cache", "packs");
|
|
15595
|
+
if (!cloud) mkdirSync13(cacheDir, { recursive: true });
|
|
15340
15596
|
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
15341
15597
|
const lock = [];
|
|
15342
15598
|
const keptCacheFiles = /* @__PURE__ */ new Set();
|
|
@@ -15364,7 +15620,7 @@ async function syncCommand(_args = []) {
|
|
|
15364
15620
|
if (!cloud) {
|
|
15365
15621
|
const fname = cacheKey(ref, data.version);
|
|
15366
15622
|
keptCacheFiles.add(fname);
|
|
15367
|
-
writeFileSync12(
|
|
15623
|
+
writeFileSync12(join20(cacheDir, fname), JSON.stringify({
|
|
15368
15624
|
ref,
|
|
15369
15625
|
version: data.version,
|
|
15370
15626
|
digest: data.digest,
|
|
@@ -15376,11 +15632,11 @@ async function syncCommand(_args = []) {
|
|
|
15376
15632
|
const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
|
|
15377
15633
|
console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
|
|
15378
15634
|
}
|
|
15379
|
-
if (!cloud &&
|
|
15635
|
+
if (!cloud && existsSync20(cacheDir)) {
|
|
15380
15636
|
for (const f of readdirSync6(cacheDir)) {
|
|
15381
15637
|
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
15382
15638
|
try {
|
|
15383
|
-
|
|
15639
|
+
rmSync3(join20(cacheDir, f));
|
|
15384
15640
|
} catch {
|
|
15385
15641
|
}
|
|
15386
15642
|
}
|
|
@@ -15516,13 +15772,13 @@ var config_exports = {};
|
|
|
15516
15772
|
__export(config_exports, {
|
|
15517
15773
|
configCommand: () => configCommand
|
|
15518
15774
|
});
|
|
15519
|
-
import { readFileSync as
|
|
15520
|
-
import { join as
|
|
15521
|
-
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";
|
|
15522
15778
|
function readConfigEnv2() {
|
|
15523
|
-
if (!
|
|
15779
|
+
if (!existsSync21(CONFIG_PATH6)) return {};
|
|
15524
15780
|
const out = {};
|
|
15525
|
-
for (const line of
|
|
15781
|
+
for (const line of readFileSync18(CONFIG_PATH6, "utf-8").split("\n")) {
|
|
15526
15782
|
const t = line.trim();
|
|
15527
15783
|
if (!t || t.startsWith("#")) continue;
|
|
15528
15784
|
const eq = t.indexOf("=");
|
|
@@ -15531,11 +15787,11 @@ function readConfigEnv2() {
|
|
|
15531
15787
|
return out;
|
|
15532
15788
|
}
|
|
15533
15789
|
function updateConfigValue(key, value) {
|
|
15534
|
-
if (!
|
|
15790
|
+
if (!existsSync21(CONFIG_PATH6)) {
|
|
15535
15791
|
console.error("No config found. Run `synkro install` first.");
|
|
15536
15792
|
process.exit(1);
|
|
15537
15793
|
}
|
|
15538
|
-
const lines =
|
|
15794
|
+
const lines = readFileSync18(CONFIG_PATH6, "utf-8").split("\n");
|
|
15539
15795
|
const pattern = new RegExp(`^${key}=`);
|
|
15540
15796
|
let found = false;
|
|
15541
15797
|
const updated = lines.map((line) => {
|
|
@@ -15673,25 +15929,25 @@ To change:`);
|
|
|
15673
15929
|
}
|
|
15674
15930
|
if (inferenceValue !== "cloud") await reconcileContainer();
|
|
15675
15931
|
}
|
|
15676
|
-
var
|
|
15932
|
+
var SYNKRO_DIR8, CONFIG_PATH6;
|
|
15677
15933
|
var init_config = __esm({
|
|
15678
15934
|
"cli/commands/config.ts"() {
|
|
15679
15935
|
"use strict";
|
|
15680
15936
|
init_stub();
|
|
15681
|
-
|
|
15682
|
-
CONFIG_PATH6 =
|
|
15937
|
+
SYNKRO_DIR8 = join21(homedir20(), ".synkro");
|
|
15938
|
+
CONFIG_PATH6 = join21(SYNKRO_DIR8, "config.env");
|
|
15683
15939
|
}
|
|
15684
15940
|
});
|
|
15685
15941
|
|
|
15686
15942
|
// cli/bootstrap.js
|
|
15687
|
-
import { readFileSync as
|
|
15943
|
+
import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
|
|
15688
15944
|
import { resolve as resolve3 } from "path";
|
|
15689
15945
|
var envCandidates = [
|
|
15690
15946
|
resolve3(process.env.HOME ?? "", ".synkro", "config.env")
|
|
15691
15947
|
];
|
|
15692
15948
|
for (const envPath of envCandidates) {
|
|
15693
|
-
if (!
|
|
15694
|
-
const envContent =
|
|
15949
|
+
if (!existsSync22(envPath)) continue;
|
|
15950
|
+
const envContent = readFileSync19(envPath, "utf-8");
|
|
15695
15951
|
for (const line of envContent.split("\n")) {
|
|
15696
15952
|
const trimmed = line.trim();
|
|
15697
15953
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -15706,7 +15962,7 @@ var args = process.argv.slice(2);
|
|
|
15706
15962
|
var cmd = args[0] || "";
|
|
15707
15963
|
var subArgs = args.slice(1);
|
|
15708
15964
|
function printVersion() {
|
|
15709
|
-
console.log("1.7.
|
|
15965
|
+
console.log("1.7.24");
|
|
15710
15966
|
}
|
|
15711
15967
|
function printHelp2() {
|
|
15712
15968
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|