@synkro-sh/cli 1.6.59 → 1.6.61
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 +188 -4
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1593,6 +1593,13 @@ export async function loadConfig(jwt: string, query?: string): Promise<HookConfi
|
|
|
1593
1593
|
export async function route(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'cloud'> {
|
|
1594
1594
|
const gradingMode = synkroFile?.grader?.mode || process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';
|
|
1595
1595
|
if (gradingMode === 'byok') return 'cloud';
|
|
1596
|
+
// Cloud-container deploy ([grader] location = cloud): grade via localGrade(),
|
|
1597
|
+
// which routes to the hosted container (containers.synkro.sh) and emits the
|
|
1598
|
+
// same rich systemMessage. Return 'local' so the hook takes that branch \u2014 NOT
|
|
1599
|
+
// the legacy BYOK 'cloud' branch (which a non-BYOK org can't use, so it would
|
|
1600
|
+
// silently produce no verdict / no system message). channelUp() is false in
|
|
1601
|
+
// cloud (no local container), so without this it would wrongly fall to 'cloud'.
|
|
1602
|
+
if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') return 'local';
|
|
1596
1603
|
if (config.captureDepth === 'local_only') return 'local';
|
|
1597
1604
|
if (await channelUp()) return 'local';
|
|
1598
1605
|
return 'cloud';
|
|
@@ -1601,6 +1608,10 @@ export async function route(config: HookConfig, synkroFile?: SynkroFileConfig):
|
|
|
1601
1608
|
export async function cweRoute(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'byok' | 'skip'> {
|
|
1602
1609
|
const gradingMode = synkroFile?.grader?.mode || process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';
|
|
1603
1610
|
if (gradingMode === 'byok') return 'byok';
|
|
1611
|
+
// Cloud-container: CWE scanning runs on the hosted container via localGradeCwe()
|
|
1612
|
+
// (which honors SYNKRO_DEPLOY_LOCATION). Without this, cweChannelUp() is false in
|
|
1613
|
+
// cloud and we'd fall to 'skip' \u2014 silently disabling CWE scanning in cloud mode.
|
|
1614
|
+
if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') return 'local';
|
|
1604
1615
|
if (await cweChannelUp()) return 'local';
|
|
1605
1616
|
return 'skip';
|
|
1606
1617
|
}
|
|
@@ -5671,7 +5682,8 @@ async function main() {
|
|
|
5671
5682
|
const isChannelUp = await channelUp();
|
|
5672
5683
|
const synkroFile = loadSynkroFile(cwd);
|
|
5673
5684
|
const gradingMode = synkroFile.grader.mode || process.env.SYNKRO_GRADING_MODE || 'local';
|
|
5674
|
-
const
|
|
5685
|
+
const deployCloud = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud';
|
|
5686
|
+
const rt = gradingMode === 'byok' ? 'cloud' : (isChannelUp || deployCloud ? 'local' : 'cloud');
|
|
5675
5687
|
|
|
5676
5688
|
let policyName = '';
|
|
5677
5689
|
let silent = false;
|
|
@@ -5705,7 +5717,7 @@ async function main() {
|
|
|
5705
5717
|
|
|
5706
5718
|
const fakeConfig: HookConfig = { captureDepth: 'local_only', tier: 'standard', silent, policyName, rules: [], scanExemptions: [], gradingMode, storageMode: process.env.SYNKRO_STORAGE_MODE || 'local' };
|
|
5707
5719
|
const tagStr = tag(rt, fakeConfig);
|
|
5708
|
-
const routeLine = tagStr + ' inference: ' + (gradingMode === 'byok' ? 'cloud (BYOK)' : isChannelUp ? 'local-cc (channel reachable on 127.0.0.1:18929)' : 'cloud (local-cc channel not reachable)');
|
|
5720
|
+
const routeLine = tagStr + ' inference: ' + (gradingMode === 'byok' ? 'cloud (BYOK)' : deployCloud ? 'cloud container (containers.synkro.sh)' : isChannelUp ? 'local-cc (channel reachable on 127.0.0.1:18929)' : 'cloud (local-cc channel not reachable)');
|
|
5709
5721
|
|
|
5710
5722
|
if (!jwt) {
|
|
5711
5723
|
outputJson({ systemMessage: routeLine });
|
|
@@ -9977,6 +9989,7 @@ __export(install_exports, {
|
|
|
9977
9989
|
detectGitRepo: () => detectGitRepo2,
|
|
9978
9990
|
installCommand: () => installCommand,
|
|
9979
9991
|
parseArgs: () => parseArgs,
|
|
9992
|
+
reconcileDeployLocation: () => reconcileDeployLocation,
|
|
9980
9993
|
reconcileHarness: () => reconcileHarness,
|
|
9981
9994
|
syncSkillFiles: () => syncSkillFiles,
|
|
9982
9995
|
writeHookScripts: () => writeHookScripts
|
|
@@ -10304,7 +10317,7 @@ function writeConfigEnv(opts) {
|
|
|
10304
10317
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
10305
10318
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
10306
10319
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
10307
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
10320
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.61")}`
|
|
10308
10321
|
];
|
|
10309
10322
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
10310
10323
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -10372,6 +10385,171 @@ async function provisionCloudContainer(opts) {
|
|
|
10372
10385
|
setupToken = "";
|
|
10373
10386
|
}
|
|
10374
10387
|
}
|
|
10388
|
+
async function verifyCloudGrader(jwt2) {
|
|
10389
|
+
const base = (process.env.SYNKRO_CONTAINERS_URL || "https://containers.synkro.sh").replace(/\/$/, "");
|
|
10390
|
+
console.log(" Warming the cloud grader (booting container + checking workers)...");
|
|
10391
|
+
const deadline = Date.now() + 18e4;
|
|
10392
|
+
let healthy = 0;
|
|
10393
|
+
while (Date.now() < deadline) {
|
|
10394
|
+
try {
|
|
10395
|
+
const r = await fetch(`${base}/diag`, { headers: { Authorization: `Bearer ${jwt2}` }, signal: AbortSignal.timeout(6e4) });
|
|
10396
|
+
if (r.ok) {
|
|
10397
|
+
const j = await r.json();
|
|
10398
|
+
healthy = j.claude_code?.healthy ?? 0;
|
|
10399
|
+
if (healthy >= 1) break;
|
|
10400
|
+
}
|
|
10401
|
+
} catch {
|
|
10402
|
+
}
|
|
10403
|
+
await new Promise((res) => setTimeout(res, 5e3));
|
|
10404
|
+
}
|
|
10405
|
+
if (healthy < 1) {
|
|
10406
|
+
console.warn(" \u26A0 cloud grader did not report a healthy worker within ~3m.");
|
|
10407
|
+
console.warn(" Grading is NOT verified \u2014 check the container or re-run `synkro install`.\n");
|
|
10408
|
+
return false;
|
|
10409
|
+
}
|
|
10410
|
+
console.log(` \u2713 ${healthy} Claude worker(s) healthy`);
|
|
10411
|
+
try {
|
|
10412
|
+
const probe = 'Grade this edit. File: t.py. Change: os.system(request.args["cmd"])';
|
|
10413
|
+
const r = await fetch(`${base}/grade/submit`, {
|
|
10414
|
+
method: "POST",
|
|
10415
|
+
headers: { Authorization: `Bearer ${jwt2}`, "Content-Type": "application/json" },
|
|
10416
|
+
body: JSON.stringify({ role: "grade-edit", payload: probe, content: probe, agent_kind: "claude_code" }),
|
|
10417
|
+
signal: AbortSignal.timeout(12e4)
|
|
10418
|
+
});
|
|
10419
|
+
const body = await r.json().catch(() => ({}));
|
|
10420
|
+
if (r.ok && typeof body.result === "string" && body.result.includes("<synkro-verdict>")) {
|
|
10421
|
+
console.log(" \u2713 smoke grade passed \u2014 cloud grading is live\n");
|
|
10422
|
+
return true;
|
|
10423
|
+
}
|
|
10424
|
+
console.warn(` \u26A0 smoke grade did not return a verdict (HTTP ${r.status}).`);
|
|
10425
|
+
console.warn(" The container is provisioned but grading is not healthy \u2014 likely the Claude");
|
|
10426
|
+
console.warn(" setup-token. Re-run `synkro install` to re-authorize the hosted worker.\n");
|
|
10427
|
+
return false;
|
|
10428
|
+
} catch {
|
|
10429
|
+
console.warn(" \u26A0 smoke grade timed out / connection dropped \u2014 grading is not responding.");
|
|
10430
|
+
console.warn(" The container is provisioned but not grading; re-run `synkro install`.\n");
|
|
10431
|
+
return false;
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
function readPersistedDeployLocation() {
|
|
10435
|
+
try {
|
|
10436
|
+
if (existsSync11(CONFIG_PATH2)) {
|
|
10437
|
+
const m = readFileSync10(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
|
|
10438
|
+
if (m?.[1] === "cloud") return "cloud";
|
|
10439
|
+
}
|
|
10440
|
+
} catch {
|
|
10441
|
+
}
|
|
10442
|
+
return "local";
|
|
10443
|
+
}
|
|
10444
|
+
function updateConfigEnvLocation(location) {
|
|
10445
|
+
if (!existsSync11(CONFIG_PATH2)) return;
|
|
10446
|
+
let env = readFileSync10(CONFIG_PATH2, "utf-8");
|
|
10447
|
+
const set = (k, v) => {
|
|
10448
|
+
const re = new RegExp(`^${k}=.*$`, "m");
|
|
10449
|
+
const line = `${k}='${v}'`;
|
|
10450
|
+
env = re.test(env) ? env.replace(re, line) : `${env.replace(/\n?$/, "\n")}${line}`;
|
|
10451
|
+
};
|
|
10452
|
+
set("SYNKRO_DEPLOY_LOCATION", location);
|
|
10453
|
+
set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
|
|
10454
|
+
writeFileSync8(CONFIG_PATH2, env, "utf-8");
|
|
10455
|
+
chmodSync2(CONFIG_PATH2, 384);
|
|
10456
|
+
}
|
|
10457
|
+
async function applyMcpConfig(opts) {
|
|
10458
|
+
if (!opts.hasClaudeCode && !opts.hasCursor) return;
|
|
10459
|
+
let mcpJwt = "";
|
|
10460
|
+
try {
|
|
10461
|
+
mcpJwt = readFileSync10(join10(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
|
|
10462
|
+
} catch {
|
|
10463
|
+
}
|
|
10464
|
+
if (!mcpJwt) {
|
|
10465
|
+
try {
|
|
10466
|
+
const r = await fetch(`${opts.gatewayUrl}/api/v1/cli/mcp-token`, {
|
|
10467
|
+
method: "POST",
|
|
10468
|
+
headers: { Authorization: `Bearer ${opts.jwt}`, "Content-Type": "application/json" },
|
|
10469
|
+
body: "{}"
|
|
10470
|
+
});
|
|
10471
|
+
if (r.ok) mcpJwt = (await r.json()).token;
|
|
10472
|
+
else console.warn(` \u26A0 MCP token mint failed (${r.status}) \u2014 run \`synkro install\` to set MCP up.`);
|
|
10473
|
+
} catch {
|
|
10474
|
+
console.warn(" \u26A0 MCP token mint failed (network) \u2014 run `synkro install` to set MCP up.");
|
|
10475
|
+
}
|
|
10476
|
+
}
|
|
10477
|
+
if (opts.hasClaudeCode) {
|
|
10478
|
+
try {
|
|
10479
|
+
const mcp = installMcpConfig({ gatewayUrl: opts.gatewayUrl, bearerToken: mcpJwt, local: opts.local });
|
|
10480
|
+
console.log(` \u2713 Claude Code MCP \u2192 ${mcp.url}`);
|
|
10481
|
+
} catch (err) {
|
|
10482
|
+
console.warn(` \u26A0 Claude Code MCP config failed: ${err.message}`);
|
|
10483
|
+
}
|
|
10484
|
+
}
|
|
10485
|
+
if (opts.hasCursor) {
|
|
10486
|
+
try {
|
|
10487
|
+
const mcp = installCursorMcpConfig({ gatewayUrl: opts.gatewayUrl, bearerToken: opts.local ? "" : mcpJwt, local: opts.local });
|
|
10488
|
+
console.log(` \u2713 Cursor MCP \u2192 ${mcp.url}`);
|
|
10489
|
+
} catch (err) {
|
|
10490
|
+
console.warn(` \u26A0 Cursor MCP config failed: ${err.message}`);
|
|
10491
|
+
}
|
|
10492
|
+
}
|
|
10493
|
+
}
|
|
10494
|
+
async function reconcileDeployLocation() {
|
|
10495
|
+
const sf = readFullSynkroFile();
|
|
10496
|
+
const desired = sf?.grader.location === "cloud" ? "cloud" : "local";
|
|
10497
|
+
const current = readPersistedDeployLocation();
|
|
10498
|
+
if (desired === current) return desired;
|
|
10499
|
+
const hasClaudeCode = sf ? sf.harness.includes("claude-code") : true;
|
|
10500
|
+
const hasCursor = sf ? sf.harness.includes("cursor") : false;
|
|
10501
|
+
console.log(`
|
|
10502
|
+
synkro.toml: deploy location changed ${current} \u2192 ${desired}
|
|
10503
|
+
`);
|
|
10504
|
+
if (!isAuthenticated()) {
|
|
10505
|
+
console.log(" Opening browser for Synkro auth...");
|
|
10506
|
+
const ok = await authenticate(() => {
|
|
10507
|
+
});
|
|
10508
|
+
if (!ok) {
|
|
10509
|
+
console.error(" \u2717 authentication failed");
|
|
10510
|
+
process.exit(1);
|
|
10511
|
+
}
|
|
10512
|
+
}
|
|
10513
|
+
await ensureValidToken();
|
|
10514
|
+
const token = getAccessToken();
|
|
10515
|
+
if (!token) {
|
|
10516
|
+
console.error(" \u2717 no access token \u2014 run `synkro login`");
|
|
10517
|
+
process.exit(1);
|
|
10518
|
+
}
|
|
10519
|
+
const gatewayUrl = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL) || "https://api.synkro.sh";
|
|
10520
|
+
try {
|
|
10521
|
+
assertGatewayAllowed(gatewayUrl);
|
|
10522
|
+
} catch (err) {
|
|
10523
|
+
console.error(err.message);
|
|
10524
|
+
process.exit(1);
|
|
10525
|
+
}
|
|
10526
|
+
if (desired === "cloud") {
|
|
10527
|
+
if (dockerStatus().running) {
|
|
10528
|
+
console.log(" Stopping local grader container (snapshotting state first)...");
|
|
10529
|
+
await dockerSafeStop();
|
|
10530
|
+
}
|
|
10531
|
+
dockerRemove();
|
|
10532
|
+
console.log(" \u2713 local container stopped + removed");
|
|
10533
|
+
let orgId, userId, email;
|
|
10534
|
+
try {
|
|
10535
|
+
const info = getUserInfo();
|
|
10536
|
+
userId = info.id;
|
|
10537
|
+
orgId = info.org_id;
|
|
10538
|
+
email = info.email;
|
|
10539
|
+
} catch {
|
|
10540
|
+
}
|
|
10541
|
+
await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
|
|
10542
|
+
updateConfigEnvLocation("cloud");
|
|
10543
|
+
await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, local: false });
|
|
10544
|
+
await verifyCloudGrader(token);
|
|
10545
|
+
console.log(" \u2713 grading + rules/MCP now served from Synkro Cloud\n");
|
|
10546
|
+
} else {
|
|
10547
|
+
updateConfigEnvLocation("local");
|
|
10548
|
+
await applyMcpConfig({ gatewayUrl, jwt: token, hasClaudeCode, hasCursor, local: true });
|
|
10549
|
+
console.log(" \u2713 switched to local \u2014 bringing the local grader + MCP up\n");
|
|
10550
|
+
}
|
|
10551
|
+
return desired;
|
|
10552
|
+
}
|
|
10375
10553
|
function resolveDeploymentMode() {
|
|
10376
10554
|
const envOverride = process.env.SYNKRO_DEPLOYMENT_MODE?.toLowerCase();
|
|
10377
10555
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
@@ -10877,6 +11055,7 @@ async function installCommand(opts = {}) {
|
|
|
10877
11055
|
console.log();
|
|
10878
11056
|
} else if (target === "cloud-container") {
|
|
10879
11057
|
await provisionCloudContainer({ gatewayUrl, jwt: token, orgId, userId, email, hasClaudeCode, hasCursor });
|
|
11058
|
+
await verifyCloudGrader(token);
|
|
10880
11059
|
}
|
|
10881
11060
|
if (transcriptConsent) {
|
|
10882
11061
|
const repo = detectGitRepo2();
|
|
@@ -13420,6 +13599,11 @@ async function updateCommand() {
|
|
|
13420
13599
|
console.log("\nSynkro updated \u2014 now running the latest version.");
|
|
13421
13600
|
}
|
|
13422
13601
|
async function restartCommand(rest = []) {
|
|
13602
|
+
const location = await reconcileDeployLocation();
|
|
13603
|
+
if (location === "cloud") {
|
|
13604
|
+
console.log("Synkro: grading runs on Synkro Cloud \u2014 no local container to restart.");
|
|
13605
|
+
return;
|
|
13606
|
+
}
|
|
13423
13607
|
assertDockerAvailable();
|
|
13424
13608
|
const cfg = resolveWorkerConfig(rest);
|
|
13425
13609
|
let claudeWorkers = cfg.claudeWorkers;
|
|
@@ -13629,7 +13813,7 @@ var args = process.argv.slice(2);
|
|
|
13629
13813
|
var cmd = args[0] || "";
|
|
13630
13814
|
var subArgs = args.slice(1);
|
|
13631
13815
|
function printVersion() {
|
|
13632
|
-
console.log("1.6.
|
|
13816
|
+
console.log("1.6.61");
|
|
13633
13817
|
}
|
|
13634
13818
|
function printHelp2() {
|
|
13635
13819
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|