@synkro-sh/cli 1.6.11 → 1.6.13
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 +266 -95
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -990,6 +990,12 @@ export interface HookConfig {
|
|
|
990
990
|
policyName: string;
|
|
991
991
|
rules: Rule[];
|
|
992
992
|
scanExemptions: Array<{ path: string; cwe_id: string }>;
|
|
993
|
+
// User-owned data axes. gradingMode: 'local' (container worker pool) | 'byok'
|
|
994
|
+
// (LLM API with the user's own key). storageMode: 'local' (PGLite) | 'cloud'
|
|
995
|
+
// (Timescale). Sourced from config.env (the installed choice); the server
|
|
996
|
+
// value from /v1/hook/config is only a fallback when the env var is unset.
|
|
997
|
+
gradingMode: string;
|
|
998
|
+
storageMode: string;
|
|
993
999
|
}
|
|
994
1000
|
|
|
995
1001
|
export async function loadConfig(jwt: string, query?: string): Promise<HookConfig> {
|
|
@@ -1000,6 +1006,8 @@ export async function loadConfig(jwt: string, query?: string): Promise<HookConfi
|
|
|
1000
1006
|
policyName: '',
|
|
1001
1007
|
rules: [],
|
|
1002
1008
|
scanExemptions: [],
|
|
1009
|
+
gradingMode: process.env.SYNKRO_GRADING_MODE || 'local',
|
|
1010
|
+
storageMode: process.env.SYNKRO_STORAGE_MODE || 'local',
|
|
1003
1011
|
};
|
|
1004
1012
|
|
|
1005
1013
|
// Kick the telemetry spool drainer. Fire-and-forget: it runs concurrently
|
|
@@ -1047,6 +1055,9 @@ export async function loadConfig(jwt: string, query?: string): Promise<HookConfi
|
|
|
1047
1055
|
config.captureDepth = data.capture_depth || 'local_only';
|
|
1048
1056
|
config.tier = data.tier || 'standard';
|
|
1049
1057
|
config.silent = data.silent_mode === true || data.silent_mode === 'true';
|
|
1058
|
+
// Env var (config.env, the installed choice) wins; server value is fallback.
|
|
1059
|
+
if (!process.env.SYNKRO_GRADING_MODE && data.grading_mode) config.gradingMode = data.grading_mode;
|
|
1060
|
+
if (!process.env.SYNKRO_STORAGE_MODE && data.storage_mode) config.storageMode = data.storage_mode;
|
|
1050
1061
|
config.policyName = data.active_policy_name || '';
|
|
1051
1062
|
if (Array.isArray(data.scan_exemptions)) {
|
|
1052
1063
|
config.scanExemptions = data.scan_exemptions
|
|
@@ -1123,10 +1134,34 @@ async function channelGrade(role: GradeRole, prompt: string, _jwt: string, port:
|
|
|
1123
1134
|
return String(data.result || '');
|
|
1124
1135
|
}
|
|
1125
1136
|
|
|
1137
|
+
// BYOK grading \u2014 grade via the cloud /v1/grade endpoint, which runs the same
|
|
1138
|
+
// grader prompt through an LLM API using the org's own provider key. Any
|
|
1139
|
+
// non-2xx (incl. 422 when no key is configured) throws, so the caller's
|
|
1140
|
+
// existing catch falls open \u2014 never a hard block on a grader error.
|
|
1141
|
+
async function cloudGrade(surface: string, prompt: string, jwt: string, timeoutMs: number): Promise<string> {
|
|
1142
|
+
const resp = await fetch(GATEWAY_URL + '/api/v1/grade', {
|
|
1143
|
+
method: 'POST',
|
|
1144
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1145
|
+
body: JSON.stringify({ surface, grader_prompt: prompt }),
|
|
1146
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
1147
|
+
});
|
|
1148
|
+
if (!resp.ok) {
|
|
1149
|
+
const text = await resp.text().catch(() => '');
|
|
1150
|
+
throw new Error('cloud grade ' + resp.status + ': ' + text.slice(0, 200));
|
|
1151
|
+
}
|
|
1152
|
+
const data = await resp.json() as { verdict?: string };
|
|
1153
|
+
return String(data.verdict || '');
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1126
1156
|
export async function localGrade(surface: string, prompt: string, timeoutMs = 30000, agentKind: AgentKind = 'claude_code'): Promise<string> {
|
|
1127
|
-
if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');
|
|
1128
1157
|
const jwt = loadJwt();
|
|
1129
1158
|
if (!jwt) throw new Error('NO_JWT');
|
|
1159
|
+
// BYOK grading mode routes the grade through an LLM API instead of the
|
|
1160
|
+
// on-device channel worker pool. The grader prompt + parseVerdict are shared.
|
|
1161
|
+
if ((process.env.SYNKRO_GRADING_MODE || 'local') === 'byok') {
|
|
1162
|
+
return cloudGrade(surface, prompt, jwt, timeoutMs);
|
|
1163
|
+
}
|
|
1164
|
+
if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');
|
|
1130
1165
|
return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 18929, timeoutMs, agentKind);
|
|
1131
1166
|
}
|
|
1132
1167
|
|
|
@@ -1518,6 +1553,19 @@ export function parseVerdict(resp: string): Verdict {
|
|
|
1518
1553
|
|
|
1519
1554
|
// \u2500\u2500\u2500 Telemetry Dispatch \u2500\u2500\u2500
|
|
1520
1555
|
|
|
1556
|
+
// Gated cloud telemetry POST \u2014 fires only when storage mode is 'cloud'. The
|
|
1557
|
+
// local PGLite spool (appendLocalTelemetry) always gets the data regardless,
|
|
1558
|
+
// so 'local' storage keeps everything on the machine.
|
|
1559
|
+
function shipCloud(jwt: string, path: string, body: Record<string, any>, timeoutMs = 3000): void {
|
|
1560
|
+
if ((process.env.SYNKRO_STORAGE_MODE || 'local') !== 'cloud') return;
|
|
1561
|
+
fetch(GATEWAY_URL + path, {
|
|
1562
|
+
method: 'POST',
|
|
1563
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1564
|
+
body: JSON.stringify(body),
|
|
1565
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
1566
|
+
}).catch(() => {});
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1521
1569
|
export function dispatchCapture(
|
|
1522
1570
|
jwt: string,
|
|
1523
1571
|
hookType: string,
|
|
@@ -1540,9 +1588,6 @@ export function dispatchCapture(
|
|
|
1540
1588
|
// Fire-and-forget
|
|
1541
1589
|
const eventId = 'evt_' + Date.now() + '_' + process.pid;
|
|
1542
1590
|
const model = opts?.ccModel || 'unknown';
|
|
1543
|
-
const sendFull =
|
|
1544
|
-
captureDepth === 'full' ||
|
|
1545
|
-
(captureDepth === 'evidence_on_violation' && ['block', 'warning', 'deny'].includes(verdictStr));
|
|
1546
1591
|
|
|
1547
1592
|
const body: Record<string, any> = {
|
|
1548
1593
|
capture_type: 'local_verdict',
|
|
@@ -1570,24 +1615,16 @@ export function dispatchCapture(
|
|
|
1570
1615
|
}
|
|
1571
1616
|
appendLocalTelemetry(localBody);
|
|
1572
1617
|
|
|
1573
|
-
//
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
if (sendFull && opts) {
|
|
1577
|
-
body.capture_depth = captureDepth;
|
|
1618
|
+
// Cloud copy carries the same full content as the local spool; shipCloud
|
|
1619
|
+
// gates on storage mode (no-op when storage is local).
|
|
1620
|
+
if (opts) {
|
|
1578
1621
|
if (opts.command) body.command = opts.command;
|
|
1579
1622
|
if (opts.reasoning) body.reasoning = opts.reasoning;
|
|
1580
1623
|
if (opts.rulesChecked) body.rules_checked = opts.rulesChecked;
|
|
1581
1624
|
if (opts.violatedRules) body.violated_rules = opts.violatedRules;
|
|
1582
1625
|
if (opts.recentUserMessages) body.recent_user_messages = opts.recentUserMessages;
|
|
1583
1626
|
}
|
|
1584
|
-
|
|
1585
|
-
fetch(GATEWAY_URL + '/api/v1/hook/capture', {
|
|
1586
|
-
method: 'POST',
|
|
1587
|
-
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1588
|
-
body: JSON.stringify(body),
|
|
1589
|
-
signal: AbortSignal.timeout(3000),
|
|
1590
|
-
}).catch(() => {});
|
|
1627
|
+
shipCloud(jwt, '/api/v1/hook/capture', body);
|
|
1591
1628
|
}
|
|
1592
1629
|
|
|
1593
1630
|
// \u2500\u2500\u2500 Durable Telemetry Spool \u2500\u2500\u2500
|
|
@@ -2059,36 +2096,24 @@ export function dispatchFinding(
|
|
|
2059
2096
|
};
|
|
2060
2097
|
appendLocalTelemetry(localEntry);
|
|
2061
2098
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2099
|
+
// Cloud copy carries the full finding; shipCloud gates on storage mode.
|
|
2064
2100
|
const cloudBody: Record<string, any> = {
|
|
2065
2101
|
finding_type: finding.finding_type,
|
|
2066
2102
|
finding_id: finding.finding_id,
|
|
2067
2103
|
severity: finding.severity,
|
|
2068
2104
|
status: finding.status,
|
|
2069
2105
|
session_id: finding.session_id,
|
|
2106
|
+
file_path: finding.file_path,
|
|
2107
|
+
package_name: finding.package_name,
|
|
2108
|
+
package_version: finding.package_version,
|
|
2109
|
+
fixed_version: finding.fixed_version,
|
|
2110
|
+
aliases: finding.aliases,
|
|
2111
|
+
references: finding.references,
|
|
2112
|
+
cwe_name: finding.cwe_name,
|
|
2113
|
+
detail: finding.detail,
|
|
2114
|
+
description: finding.description,
|
|
2070
2115
|
};
|
|
2071
|
-
|
|
2072
|
-
if (captureDepth === 'evidence_on_violation' || captureDepth === 'full') {
|
|
2073
|
-
cloudBody.file_path = finding.file_path;
|
|
2074
|
-
cloudBody.package_name = finding.package_name;
|
|
2075
|
-
cloudBody.package_version = finding.package_version;
|
|
2076
|
-
cloudBody.fixed_version = finding.fixed_version;
|
|
2077
|
-
cloudBody.aliases = finding.aliases;
|
|
2078
|
-
cloudBody.references = finding.references;
|
|
2079
|
-
cloudBody.cwe_name = finding.cwe_name;
|
|
2080
|
-
}
|
|
2081
|
-
if (captureDepth === 'full') {
|
|
2082
|
-
cloudBody.detail = finding.detail;
|
|
2083
|
-
cloudBody.description = finding.description;
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
fetch(GATEWAY_URL + '/api/v1/hook/finding', {
|
|
2087
|
-
method: 'POST',
|
|
2088
|
-
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
2089
|
-
body: JSON.stringify(cloudBody),
|
|
2090
|
-
signal: AbortSignal.timeout(3000),
|
|
2091
|
-
}).catch(() => {});
|
|
2116
|
+
shipCloud(jwt, '/api/v1/hook/finding', cloudBody);
|
|
2092
2117
|
}
|
|
2093
2118
|
|
|
2094
2119
|
// \u2500\u2500\u2500 Hook tool-name sets (CC + Cursor) \u2500\u2500\u2500
|
|
@@ -3744,12 +3769,7 @@ async function main() {
|
|
|
3744
3769
|
...(sessionId ? { session_id: sessionId } : {}),
|
|
3745
3770
|
};
|
|
3746
3771
|
appendLocalTelemetry(usageBody);
|
|
3747
|
-
|
|
3748
|
-
method: 'POST',
|
|
3749
|
-
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
3750
|
-
body: JSON.stringify(usageBody),
|
|
3751
|
-
signal: AbortSignal.timeout(3000),
|
|
3752
|
-
}).catch(() => {});
|
|
3772
|
+
shipCloud(jwt, '/api/v1/hook/capture', usageBody);
|
|
3753
3773
|
}
|
|
3754
3774
|
}
|
|
3755
3775
|
|
|
@@ -3910,16 +3930,7 @@ async function main() {
|
|
|
3910
3930
|
};
|
|
3911
3931
|
|
|
3912
3932
|
appendLocalTelemetry(body);
|
|
3913
|
-
|
|
3914
|
-
const config = await loadConfig(jwt);
|
|
3915
|
-
if (config.captureDepth !== 'local_only') {
|
|
3916
|
-
fetch(GATEWAY_URL + '/api/v1/hook/capture', {
|
|
3917
|
-
method: 'POST',
|
|
3918
|
-
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
3919
|
-
body: JSON.stringify(body),
|
|
3920
|
-
signal: AbortSignal.timeout(3000),
|
|
3921
|
-
}).catch(() => {});
|
|
3922
|
-
}
|
|
3933
|
+
shipCloud(jwt, '/api/v1/hook/capture', body);
|
|
3923
3934
|
|
|
3924
3935
|
outputEmpty();
|
|
3925
3936
|
} catch {
|
|
@@ -3932,7 +3943,7 @@ main();
|
|
|
3932
3943
|
TRANSCRIPT_SYNC_TS = `#!/usr/bin/env bun
|
|
3933
3944
|
import {
|
|
3934
3945
|
loadJwt, detectRepo, readStdin, aggregateUsage, appendLocalTelemetry,
|
|
3935
|
-
outputEmpty, setupCursorHookSignals, hookSessionId, GATEWAY_URL,
|
|
3946
|
+
outputEmpty, setupCursorHookSignals, hookSessionId, GATEWAY_URL, readSessionLog,
|
|
3936
3947
|
} from './_synkro-common.ts';
|
|
3937
3948
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3938
3949
|
import { join, dirname } from 'node:path';
|
|
@@ -3976,12 +3987,7 @@ async function main() {
|
|
|
3976
3987
|
session_id: sessionId,
|
|
3977
3988
|
};
|
|
3978
3989
|
appendLocalTelemetry(usageBody);
|
|
3979
|
-
|
|
3980
|
-
method: 'POST',
|
|
3981
|
-
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
3982
|
-
body: JSON.stringify(usageBody),
|
|
3983
|
-
signal: AbortSignal.timeout(3000),
|
|
3984
|
-
}).catch(() => {});
|
|
3990
|
+
shipCloud(jwt, '/api/v1/hook/capture', usageBody);
|
|
3985
3991
|
}
|
|
3986
3992
|
|
|
3987
3993
|
// Transcript consent gates only CLOUD transmission. Local persistence \u2014
|
|
@@ -4056,25 +4062,19 @@ async function main() {
|
|
|
4056
4062
|
}).catch(() => {});
|
|
4057
4063
|
}
|
|
4058
4064
|
|
|
4059
|
-
// Cloud sync \u2014 only when
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
method: 'POST',
|
|
4073
|
-
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
4074
|
-
body: JSON.stringify({ repo: gitRepo, sessions: [{ cc_session_id: sessionId, messages }] }),
|
|
4075
|
-
signal: AbortSignal.timeout(10000),
|
|
4076
|
-
}).catch(() => {});
|
|
4077
|
-
}
|
|
4065
|
+
// Cloud sync \u2014 only when storage mode is cloud (and transcript consent
|
|
4066
|
+
// wasn't declined). Carries the transcript + the session step-log so cloud
|
|
4067
|
+
// storage matches the local PGLite shape.
|
|
4068
|
+
if (cloudConsent && gitRepo && (process.env.SYNKRO_STORAGE_MODE || 'local') === 'cloud') {
|
|
4069
|
+
fetch(GATEWAY_URL + '/api/v1/cli/sync-transcripts', {
|
|
4070
|
+
method: 'POST',
|
|
4071
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
4072
|
+
body: JSON.stringify({
|
|
4073
|
+
repo: gitRepo,
|
|
4074
|
+
sessions: [{ cc_session_id: sessionId, messages, actions: readSessionLog(sessionId) }],
|
|
4075
|
+
}),
|
|
4076
|
+
signal: AbortSignal.timeout(10000),
|
|
4077
|
+
}).catch(() => {});
|
|
4078
4078
|
}
|
|
4079
4079
|
|
|
4080
4080
|
outputEmpty();
|
|
@@ -4511,12 +4511,7 @@ async function main() {
|
|
|
4511
4511
|
if (repo) captureBody.repo = repo;
|
|
4512
4512
|
|
|
4513
4513
|
appendLocalTelemetry(captureBody);
|
|
4514
|
-
|
|
4515
|
-
method: 'POST',
|
|
4516
|
-
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
4517
|
-
body: JSON.stringify(captureBody),
|
|
4518
|
-
signal: AbortSignal.timeout(10000),
|
|
4519
|
-
}).catch(() => {});
|
|
4514
|
+
shipCloud(jwt, '/api/v1/hook/capture', captureBody, 10000);
|
|
4520
4515
|
|
|
4521
4516
|
finish();
|
|
4522
4517
|
} catch (e) {
|
|
@@ -6378,6 +6373,32 @@ async function promptCursorApiKey(opts) {
|
|
|
6378
6373
|
console.log(" \u26A0 Skipped \u2014 Cursor workers will be idle. Re-run install or pass --cursor-api-key=\u2026 later.");
|
|
6379
6374
|
}
|
|
6380
6375
|
}
|
|
6376
|
+
async function promptGradingMode() {
|
|
6377
|
+
if (!process.stdin.isTTY) return "local";
|
|
6378
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6379
|
+
return new Promise((resolve3) => {
|
|
6380
|
+
rl.question(
|
|
6381
|
+
"Where should grading run?\n local \u2014 on this machine, via the Synkro container (default)\n byok \u2014 via an LLM API using your own provider key\nChoose [local] / byok: ",
|
|
6382
|
+
(answer) => {
|
|
6383
|
+
rl.close();
|
|
6384
|
+
resolve3(answer.trim().toLowerCase() === "byok" ? "byok" : "local");
|
|
6385
|
+
}
|
|
6386
|
+
);
|
|
6387
|
+
});
|
|
6388
|
+
}
|
|
6389
|
+
async function promptStorageMode() {
|
|
6390
|
+
if (!process.stdin.isTTY) return "local";
|
|
6391
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6392
|
+
return new Promise((resolve3) => {
|
|
6393
|
+
rl.question(
|
|
6394
|
+
"Where should telemetry be stored?\n local \u2014 on this machine only (default)\n cloud \u2014 sent to Synkro cloud\nChoose [local] / cloud: ",
|
|
6395
|
+
(answer) => {
|
|
6396
|
+
rl.close();
|
|
6397
|
+
resolve3(answer.trim().toLowerCase() === "cloud" ? "cloud" : "local");
|
|
6398
|
+
}
|
|
6399
|
+
);
|
|
6400
|
+
});
|
|
6401
|
+
}
|
|
6381
6402
|
function ensureSynkroDir() {
|
|
6382
6403
|
mkdirSync8(SYNKRO_DIR4, { recursive: true });
|
|
6383
6404
|
mkdirSync8(HOOKS_DIR, { recursive: true });
|
|
@@ -6479,7 +6500,7 @@ function writeConfigEnv(opts) {
|
|
|
6479
6500
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
6480
6501
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
6481
6502
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
6482
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
6503
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.13")}`
|
|
6483
6504
|
];
|
|
6484
6505
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
6485
6506
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -6491,6 +6512,8 @@ function writeConfigEnv(opts) {
|
|
|
6491
6512
|
lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle(opts.localInference ? "yes" : "no")}`);
|
|
6492
6513
|
const safeMode = sanitizeConfigValue(opts.deploymentMode ?? "docker", 16);
|
|
6493
6514
|
lines.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle(safeMode)}`);
|
|
6515
|
+
lines.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.gradingMode ?? "local", 16))}`);
|
|
6516
|
+
lines.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle(sanitizeConfigValue(opts.storageMode ?? "local", 16))}`);
|
|
6494
6517
|
lines.push("");
|
|
6495
6518
|
writeFileSync7(CONFIG_PATH2, lines.join("\n"), "utf-8");
|
|
6496
6519
|
chmodSync2(CONFIG_PATH2, 384);
|
|
@@ -6668,6 +6691,14 @@ async function installCommand(opts = {}) {
|
|
|
6668
6691
|
console.log(`Installing hooks for: ${agents.map((a) => a.name).join(", ")}
|
|
6669
6692
|
`);
|
|
6670
6693
|
}
|
|
6694
|
+
const gradingMode = await promptGradingMode();
|
|
6695
|
+
const storageMode = await promptStorageMode();
|
|
6696
|
+
console.log(` grading: ${gradingMode} storage: ${storageMode}
|
|
6697
|
+
`);
|
|
6698
|
+
if (gradingMode === "byok") {
|
|
6699
|
+
console.log(" BYOK grading uses your own provider key \u2014 register one in the");
|
|
6700
|
+
console.log(" dashboard under Settings \u2192 Provider Keys if you have not already.\n");
|
|
6701
|
+
}
|
|
6671
6702
|
ensureSynkroDir();
|
|
6672
6703
|
const scripts = writeHookScripts();
|
|
6673
6704
|
console.log("Wrote hook scripts:");
|
|
@@ -6745,7 +6776,11 @@ async function installCommand(opts = {}) {
|
|
|
6745
6776
|
} catch {
|
|
6746
6777
|
}
|
|
6747
6778
|
const profile = await fetchUserProfile(gatewayUrl, token);
|
|
6748
|
-
const
|
|
6779
|
+
const cloudOnly = gradingMode === "byok" && storageMode === "cloud";
|
|
6780
|
+
const useLocalMcp = !cloudOnly;
|
|
6781
|
+
if (cloudOnly) {
|
|
6782
|
+
console.log("Cloud-only setup (BYOK grading + cloud storage) \u2014 skipping the local container.\n");
|
|
6783
|
+
}
|
|
6749
6784
|
if (hasClaudeCode && !opts.noMcp) {
|
|
6750
6785
|
if (useLocalMcp) {
|
|
6751
6786
|
try {
|
|
@@ -6845,7 +6880,7 @@ async function installCommand(opts = {}) {
|
|
|
6845
6880
|
}
|
|
6846
6881
|
const synkroBundle = resolveSynkroBundle();
|
|
6847
6882
|
const persistedMode = resolveDeploymentMode();
|
|
6848
|
-
writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, synkroBin: synkroBundle, transcriptConsent, localInference: profile.localInference, deploymentMode: persistedMode });
|
|
6883
|
+
writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, synkroBin: synkroBundle, transcriptConsent, localInference: profile.localInference, deploymentMode: persistedMode, gradingMode, storageMode });
|
|
6849
6884
|
console.log(`Wrote config to ${CONFIG_PATH2}`);
|
|
6850
6885
|
console.log(` inference: ${profile.inference} (server-side grading)`);
|
|
6851
6886
|
if (profile.localInference) console.log(` local inference: enabled (gradingProvider=claude-code)`);
|
|
@@ -6858,7 +6893,7 @@ async function installCommand(opts = {}) {
|
|
|
6858
6893
|
console.warn(` \u26A0 Could not cache judge prompts: ${err.message}`);
|
|
6859
6894
|
}
|
|
6860
6895
|
console.log();
|
|
6861
|
-
if (
|
|
6896
|
+
if (useLocalMcp) {
|
|
6862
6897
|
const { assertDockerAvailable: assertDockerAvailable2 } = await Promise.resolve().then(() => (init_dockerInstall(), dockerInstall_exports));
|
|
6863
6898
|
try {
|
|
6864
6899
|
assertDockerAvailable2();
|
|
@@ -8956,16 +8991,141 @@ var init_lifecycle = __esm({
|
|
|
8956
8991
|
}
|
|
8957
8992
|
});
|
|
8958
8993
|
|
|
8994
|
+
// cli/commands/config.ts
|
|
8995
|
+
var config_exports = {};
|
|
8996
|
+
__export(config_exports, {
|
|
8997
|
+
configCommand: () => configCommand
|
|
8998
|
+
});
|
|
8999
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync10, existsSync as existsSync15 } from "fs";
|
|
9000
|
+
import { join as join15 } from "path";
|
|
9001
|
+
import { homedir as homedir15 } from "os";
|
|
9002
|
+
function readConfigEnv() {
|
|
9003
|
+
if (!existsSync15(CONFIG_PATH5)) return {};
|
|
9004
|
+
const out = {};
|
|
9005
|
+
for (const line of readFileSync12(CONFIG_PATH5, "utf-8").split("\n")) {
|
|
9006
|
+
const t = line.trim();
|
|
9007
|
+
if (!t || t.startsWith("#")) continue;
|
|
9008
|
+
const eq = t.indexOf("=");
|
|
9009
|
+
if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
9010
|
+
}
|
|
9011
|
+
return out;
|
|
9012
|
+
}
|
|
9013
|
+
function updateConfigValue(key, value) {
|
|
9014
|
+
if (!existsSync15(CONFIG_PATH5)) {
|
|
9015
|
+
console.error("No config found. Run `synkro install` first.");
|
|
9016
|
+
process.exit(1);
|
|
9017
|
+
}
|
|
9018
|
+
const lines = readFileSync12(CONFIG_PATH5, "utf-8").split("\n");
|
|
9019
|
+
const pattern = new RegExp(`^${key}=`);
|
|
9020
|
+
let found = false;
|
|
9021
|
+
const updated = lines.map((line) => {
|
|
9022
|
+
if (pattern.test(line.trim())) {
|
|
9023
|
+
found = true;
|
|
9024
|
+
return `${key}='${value}'`;
|
|
9025
|
+
}
|
|
9026
|
+
return line;
|
|
9027
|
+
});
|
|
9028
|
+
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
9029
|
+
writeFileSync10(CONFIG_PATH5, updated.join("\n"), "utf-8");
|
|
9030
|
+
}
|
|
9031
|
+
async function configCommand(args2) {
|
|
9032
|
+
if (args2.length === 0) {
|
|
9033
|
+
const config2 = readConfigEnv();
|
|
9034
|
+
console.log("Synkro config:\n");
|
|
9035
|
+
console.log(` grading: ${config2.SYNKRO_GRADING_MODE || "local"}`);
|
|
9036
|
+
console.log(` storage: ${config2.SYNKRO_STORAGE_MODE || "local"}`);
|
|
9037
|
+
console.log(` inference: ${config2.SYNKRO_INFERENCE || "fast"}`);
|
|
9038
|
+
console.log(` tier: ${config2.SYNKRO_TIER || "pro"}`);
|
|
9039
|
+
console.log(` gateway: ${config2.SYNKRO_GATEWAY_URL || "https://api.synkro.sh"}`);
|
|
9040
|
+
console.log(` version: ${config2.SYNKRO_VERSION || "?"}`);
|
|
9041
|
+
console.log(`
|
|
9042
|
+
To change:`);
|
|
9043
|
+
console.log(` synkro config grading <local|byok> \u2014 where grading runs`);
|
|
9044
|
+
console.log(` synkro config storage <local|cloud> \u2014 where telemetry is stored`);
|
|
9045
|
+
console.log(` synkro config --inference fast|standard`);
|
|
9046
|
+
return;
|
|
9047
|
+
}
|
|
9048
|
+
if (args2[0] === "grading") {
|
|
9049
|
+
const value = args2[1];
|
|
9050
|
+
if (value !== "local" && value !== "byok") {
|
|
9051
|
+
console.error("Usage: synkro config grading <local|byok>");
|
|
9052
|
+
process.exit(1);
|
|
9053
|
+
}
|
|
9054
|
+
updateConfigValue("SYNKRO_GRADING_MODE", value);
|
|
9055
|
+
console.log(`\u2713 Grading mode set to '${value}'.`);
|
|
9056
|
+
if (value === "byok") {
|
|
9057
|
+
console.log(" BYOK grading uses your own provider key \u2014 register one in the");
|
|
9058
|
+
console.log(" dashboard under Settings \u2192 Provider Keys if you have not already.");
|
|
9059
|
+
}
|
|
9060
|
+
return;
|
|
9061
|
+
}
|
|
9062
|
+
if (args2[0] === "storage") {
|
|
9063
|
+
const value = args2[1];
|
|
9064
|
+
if (value !== "local" && value !== "cloud") {
|
|
9065
|
+
console.error("Usage: synkro config storage <local|cloud>");
|
|
9066
|
+
process.exit(1);
|
|
9067
|
+
}
|
|
9068
|
+
updateConfigValue("SYNKRO_STORAGE_MODE", value);
|
|
9069
|
+
console.log(`\u2713 Storage mode set to '${value}'.`);
|
|
9070
|
+
return;
|
|
9071
|
+
}
|
|
9072
|
+
let inferenceValue;
|
|
9073
|
+
for (const a of args2) {
|
|
9074
|
+
if (a.startsWith("--inference=")) inferenceValue = a.slice("--inference=".length);
|
|
9075
|
+
else if (a === "--inference" && args2.indexOf(a) + 1 < args2.length) inferenceValue = args2[args2.indexOf(a) + 1];
|
|
9076
|
+
}
|
|
9077
|
+
if (!inferenceValue || !["fast", "standard"].includes(inferenceValue)) {
|
|
9078
|
+
console.error("Usage: synkro config --inference fast|standard");
|
|
9079
|
+
process.exit(1);
|
|
9080
|
+
}
|
|
9081
|
+
if (!isAuthenticated()) {
|
|
9082
|
+
console.error("Not authenticated. Run `synkro login` first.");
|
|
9083
|
+
process.exit(1);
|
|
9084
|
+
}
|
|
9085
|
+
const token = getAccessToken();
|
|
9086
|
+
const config = readConfigEnv();
|
|
9087
|
+
const gatewayUrl = (config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
9088
|
+
try {
|
|
9089
|
+
const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
|
|
9090
|
+
method: "PATCH",
|
|
9091
|
+
headers: {
|
|
9092
|
+
"Authorization": `Bearer ${token}`,
|
|
9093
|
+
"Content-Type": "application/json"
|
|
9094
|
+
},
|
|
9095
|
+
body: JSON.stringify({ fast_inference: inferenceValue === "fast" })
|
|
9096
|
+
});
|
|
9097
|
+
if (!resp.ok) {
|
|
9098
|
+
const errText = await resp.text().catch(() => "");
|
|
9099
|
+
console.error(`Failed to update: ${resp.status} ${errText.slice(0, 200)}`);
|
|
9100
|
+
process.exit(1);
|
|
9101
|
+
}
|
|
9102
|
+
} catch (err) {
|
|
9103
|
+
console.error(`Failed to reach server: ${err.message}`);
|
|
9104
|
+
process.exit(1);
|
|
9105
|
+
}
|
|
9106
|
+
updateConfigValue("SYNKRO_INFERENCE", inferenceValue);
|
|
9107
|
+
console.log(`\u2713 Inference set to '${inferenceValue}'.`);
|
|
9108
|
+
}
|
|
9109
|
+
var SYNKRO_DIR6, CONFIG_PATH5;
|
|
9110
|
+
var init_config = __esm({
|
|
9111
|
+
"cli/commands/config.ts"() {
|
|
9112
|
+
"use strict";
|
|
9113
|
+
init_stub();
|
|
9114
|
+
SYNKRO_DIR6 = join15(homedir15(), ".synkro");
|
|
9115
|
+
CONFIG_PATH5 = join15(SYNKRO_DIR6, "config.env");
|
|
9116
|
+
}
|
|
9117
|
+
});
|
|
9118
|
+
|
|
8959
9119
|
// cli/bootstrap.js
|
|
8960
|
-
import { readFileSync as
|
|
9120
|
+
import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
|
|
8961
9121
|
import { resolve as resolve2 } from "path";
|
|
8962
9122
|
var envCandidates = [
|
|
8963
9123
|
resolve2(process.cwd(), ".env"),
|
|
8964
9124
|
resolve2(process.env.HOME ?? "", ".synkro", "config.env")
|
|
8965
9125
|
];
|
|
8966
9126
|
for (const envPath of envCandidates) {
|
|
8967
|
-
if (!
|
|
8968
|
-
const envContent =
|
|
9127
|
+
if (!existsSync16(envPath)) continue;
|
|
9128
|
+
const envContent = readFileSync13(envPath, "utf-8");
|
|
8969
9129
|
for (const line of envContent.split("\n")) {
|
|
8970
9130
|
const trimmed = line.trim();
|
|
8971
9131
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -8980,7 +9140,7 @@ var args = process.argv.slice(2);
|
|
|
8980
9140
|
var cmd = args[0] || "";
|
|
8981
9141
|
var subArgs = args.slice(1);
|
|
8982
9142
|
function printVersion() {
|
|
8983
|
-
console.log("1.6.
|
|
9143
|
+
console.log("1.6.13");
|
|
8984
9144
|
}
|
|
8985
9145
|
function printHelp2() {
|
|
8986
9146
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|
|
@@ -8995,8 +9155,14 @@ Commands:
|
|
|
8995
9155
|
start [opts] Start the server (with pgdata integrity check)
|
|
8996
9156
|
restart [opts] Safe restart (stop \u2192 start, data preserved)
|
|
8997
9157
|
update Pull the latest container image and safely restart
|
|
9158
|
+
config Show or change grading + storage modes
|
|
8998
9159
|
version Show version
|
|
8999
9160
|
|
|
9161
|
+
config:
|
|
9162
|
+
synkro config show current settings
|
|
9163
|
+
synkro config grading <local|byok> where grading runs
|
|
9164
|
+
synkro config storage <local|cloud> where telemetry is stored
|
|
9165
|
+
|
|
9000
9166
|
start/restart opts (recreate the worker pool):
|
|
9001
9167
|
--workers N total grader workers (default 8, even-split)
|
|
9002
9168
|
--providers a,b grading agents: claude, cursor (or both)
|
|
@@ -9063,6 +9229,11 @@ async function main() {
|
|
|
9063
9229
|
await updateCommand2();
|
|
9064
9230
|
break;
|
|
9065
9231
|
}
|
|
9232
|
+
case "config": {
|
|
9233
|
+
const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
9234
|
+
await configCommand2(args.slice(1));
|
|
9235
|
+
break;
|
|
9236
|
+
}
|
|
9066
9237
|
default: {
|
|
9067
9238
|
console.error(`Unknown command: ${cmd}`);
|
|
9068
9239
|
printHelp2();
|