@synkro-sh/cli 1.6.90 → 1.6.92
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 +111 -23
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1415,8 +1415,19 @@ export function loadSynkroFile(cwd?: string): SynkroFileConfig {
|
|
|
1415
1415
|
}
|
|
1416
1416
|
}
|
|
1417
1417
|
|
|
1418
|
-
export function effectiveGraderPool(synkroFile: SynkroFileConfig, hookAgentKind: AgentKind): AgentKind {
|
|
1419
|
-
|
|
1418
|
+
export function effectiveGraderPool(synkroFile: SynkroFileConfig, hookAgentKind: AgentKind, config?: HookConfig): AgentKind {
|
|
1419
|
+
// Source of truth for the pool SPLIT depends on grading location:
|
|
1420
|
+
// \u2022 cloud / byok \u2192 SERVER-managed (the user's dashboard choice, delivered as
|
|
1421
|
+
// config.graderPool from /v1/hook/config). synkro.toml isn't even written
|
|
1422
|
+
// for cloud installs, so it can't be the source there.
|
|
1423
|
+
// \u2022 local \u2192 synkro.toml [grader] pool (on-device, user-owned).
|
|
1424
|
+
// Falls back to synkro.toml whenever the server pool is absent, so a missing
|
|
1425
|
+
// value never breaks grading \u2014 it just reverts to today's behavior.
|
|
1426
|
+
const managed = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud'
|
|
1427
|
+
|| (process.env.SYNKRO_GRADING_MODE || synkroFile.grader?.mode || config?.gradingMode) === 'byok';
|
|
1428
|
+
const pool = (managed && config?.graderPool)
|
|
1429
|
+
? normalizePoolToken(config.graderPool)
|
|
1430
|
+
: normalizePoolToken(synkroFile.grader.pool);
|
|
1420
1431
|
if (pool === 'auto') return hookAgentKind;
|
|
1421
1432
|
if (pool === 'claude') return 'claude_code';
|
|
1422
1433
|
return 'cursor';
|
|
@@ -1517,6 +1528,11 @@ export interface HookConfig {
|
|
|
1517
1528
|
// value from /v1/hook/config is only a fallback when the env var is unset.
|
|
1518
1529
|
gradingMode: string;
|
|
1519
1530
|
storageMode: string;
|
|
1531
|
+
// Server-managed pool SPLIT (auto | claude | cursor) + canonical grading
|
|
1532
|
+
// location, from /v1/hook/config. Authoritative ONLY for cloud/byok grading
|
|
1533
|
+
// (synkro.toml governs local). Undefined when the gateway didn't report one.
|
|
1534
|
+
graderPool?: string;
|
|
1535
|
+
inferenceMode?: string;
|
|
1520
1536
|
}
|
|
1521
1537
|
|
|
1522
1538
|
/** True when telemetry + rules must stay on-machine (PGLite). Default: local. */
|
|
@@ -1597,6 +1613,9 @@ export async function loadConfig(jwt: string, query?: string): Promise<HookConfi
|
|
|
1597
1613
|
config.silent = data.silent_mode === true || data.silent_mode === 'true';
|
|
1598
1614
|
if (!process.env.SYNKRO_GRADING_MODE && data.grading_mode) config.gradingMode = data.grading_mode;
|
|
1599
1615
|
if (!process.env.SYNKRO_STORAGE_MODE && data.storage_mode) config.storageMode = data.storage_mode;
|
|
1616
|
+
// Server-managed pool split + location (cloud/byok only \u2014 see effectiveGraderPool).
|
|
1617
|
+
if (data.grader_pool) config.graderPool = data.grader_pool;
|
|
1618
|
+
if (data.inference_mode) config.inferenceMode = data.inference_mode;
|
|
1600
1619
|
config.policyName = data.active_policy_name || '';
|
|
1601
1620
|
if (Array.isArray(data.scan_exemptions)) {
|
|
1602
1621
|
config.scanExemptions = data.scan_exemptions
|
|
@@ -2218,6 +2237,44 @@ function isPlaceholderSecret(v: string): boolean {
|
|
|
2218
2237
|
const tail = v.replace(/^(sk-ant-|[rs]k_(?:live|test)_|sk-|AKIA|gh[posru]_|AIza|xox[baprs]-|Bearer[ ]+)/, '');
|
|
2219
2238
|
return /^(x+|0+|a+|placeholder|example|changeme|your[-_a-z0-9]*|dummy|test|fake|redacted|sample)$/i.test(tail);
|
|
2220
2239
|
}
|
|
2240
|
+
// \u2500\u2500 Opaque-secret heuristic (catches the FORMAT-LESS tail the regex can't) \u2500\u2500
|
|
2241
|
+
// A high-entropy value sitting in a SECRET CONTEXT \u2014 assigned to a secret-named
|
|
2242
|
+
// variable, in an auth header, or as a connection-string password. Local +
|
|
2243
|
+
// deterministic, no model. Char-class regexes only (template-literal backslash safe).
|
|
2244
|
+
function shannonEntropy(s: string): number {
|
|
2245
|
+
const m: Record<string, number> = {};
|
|
2246
|
+
for (const ch of s) m[ch] = (m[ch] || 0) + 1;
|
|
2247
|
+
let h = 0;
|
|
2248
|
+
for (const k in m) { const p = m[k] / s.length; h -= p * Math.log2(p); }
|
|
2249
|
+
return h;
|
|
2250
|
+
}
|
|
2251
|
+
function looksOpaqueSecret(v: string): boolean {
|
|
2252
|
+
if (v.length < 16) return false;
|
|
2253
|
+
if (/[$<>{}]/.test(v)) return false; // env ref / placeholder
|
|
2254
|
+
if (/^[0-9a-f]{32,}$/i.test(v)) return false; // hex hash / sha
|
|
2255
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v)) return false; // uuid
|
|
2256
|
+
if (!(/[a-zA-Z]/.test(v) && /[0-9]/.test(v))) return false; // real tokens mix letters+digits
|
|
2257
|
+
return shannonEntropy(v) >= 3.2;
|
|
2258
|
+
}
|
|
2259
|
+
const OPAQUE_CTX_PATTERNS: RegExp[] = [
|
|
2260
|
+
/[A-Za-z_][A-Za-z0-9_-]*(?:secret|token|key|password|passwd|api[_-]?key|auth|credential)[A-Za-z0-9_-]*[ ]*[=:][ ]*["']?([^ "'&|;]+)/gi,
|
|
2261
|
+
/(?:authorization|x-api-key|x-auth-token|api-key)[ ]*:[ ]*(?:bearer[ ]+)?([^ "']+)/gi,
|
|
2262
|
+
/[a-z][a-z0-9+.-]*:[/][/][^:/@ ]+:([^@/ ]+)@/gi,
|
|
2263
|
+
];
|
|
2264
|
+
export function detectOpaqueSecrets(text: string): SecretHit[] {
|
|
2265
|
+
if (!text) return [];
|
|
2266
|
+
const hits: SecretHit[] = [];
|
|
2267
|
+
const seen = new Set<string>();
|
|
2268
|
+
for (const re of OPAQUE_CTX_PATTERNS) {
|
|
2269
|
+
re.lastIndex = 0;
|
|
2270
|
+
let m: RegExpExecArray | null;
|
|
2271
|
+
while ((m = re.exec(text)) !== null) {
|
|
2272
|
+
const v = m[1];
|
|
2273
|
+
if (v && !seen.has(v) && looksOpaqueSecret(v)) { seen.add(v); hits.push({ type: 'opaque-secret', value: v }); }
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return hits;
|
|
2277
|
+
}
|
|
2221
2278
|
export function detectHardSecrets(text: string): SecretHit[] {
|
|
2222
2279
|
if (!text) return [];
|
|
2223
2280
|
const hits: SecretHit[] = [];
|
|
@@ -2232,6 +2289,10 @@ export function detectHardSecrets(text: string): SecretHit[] {
|
|
|
2232
2289
|
hits.push({ type, value: v });
|
|
2233
2290
|
}
|
|
2234
2291
|
}
|
|
2292
|
+
// Union the format-less opaque-secret tail (entropy + secret context).
|
|
2293
|
+
for (const h of detectOpaqueSecrets(text)) {
|
|
2294
|
+
if (!seen.has(h.value)) { seen.add(h.value); hits.push(h); }
|
|
2295
|
+
}
|
|
2235
2296
|
return hits;
|
|
2236
2297
|
}
|
|
2237
2298
|
// Shape-preserving redaction for persist/log/grade boundaries: the reader still
|
|
@@ -2247,6 +2308,8 @@ export function scrubSecrets(text: string): string {
|
|
|
2247
2308
|
if (/[$<>{}]/.test(val)) return full;
|
|
2248
2309
|
return pfx + '<redacted:credential>';
|
|
2249
2310
|
});
|
|
2311
|
+
// Format-less opaque secrets (entropy + secret context) the patterns above miss.
|
|
2312
|
+
for (const h of detectOpaqueSecrets(out)) out = out.split(h.value).join('<redacted:opaque-secret>');
|
|
2250
2313
|
return out;
|
|
2251
2314
|
}
|
|
2252
2315
|
function guessEnvName(type: string, i: number): string {
|
|
@@ -4320,7 +4383,7 @@ async function main() {
|
|
|
4320
4383
|
// Load config and decide route
|
|
4321
4384
|
const config = await loadConfig(jwt);
|
|
4322
4385
|
const synkroFile = loadSynkroFile(cwd);
|
|
4323
|
-
const graderPool = effectiveGraderPool(synkroFile, agentKind);
|
|
4386
|
+
const graderPool = effectiveGraderPool(synkroFile, agentKind, config);
|
|
4324
4387
|
const rt = await route(config, synkroFile);
|
|
4325
4388
|
const tagStr = tag(rt, config, graderPool);
|
|
4326
4389
|
|
|
@@ -4814,7 +4877,7 @@ async function main() {
|
|
|
4814
4877
|
|
|
4815
4878
|
const config = await loadConfig(jwt);
|
|
4816
4879
|
const synkroFile = loadSynkroFile(cwd);
|
|
4817
|
-
const graderPool = effectiveGraderPool(synkroFile, agentKind);
|
|
4880
|
+
const graderPool = effectiveGraderPool(synkroFile, agentKind, config);
|
|
4818
4881
|
const rt = await cweRoute(config, synkroFile);
|
|
4819
4882
|
|
|
4820
4883
|
// Combined-grade mode: the edit-precheck hook runs CWE inside its single
|
|
@@ -5542,7 +5605,7 @@ async function main() {
|
|
|
5542
5605
|
const config = await loadConfig(jwt);
|
|
5543
5606
|
const isCursor = isCursorHookFormat();
|
|
5544
5607
|
const synkroFile = loadSynkroFile(cwd);
|
|
5545
|
-
const graderPool = effectiveGraderPool(synkroFile, isCursor ? 'cursor' : 'claude_code');
|
|
5608
|
+
const graderPool = effectiveGraderPool(synkroFile, isCursor ? 'cursor' : 'claude_code', config);
|
|
5546
5609
|
const rt = await route(config, synkroFile);
|
|
5547
5610
|
const tagStr = tag(rt, config, graderPool);
|
|
5548
5611
|
|
|
@@ -5724,7 +5787,7 @@ async function main() {
|
|
|
5724
5787
|
|
|
5725
5788
|
const config = await loadConfig(jwt);
|
|
5726
5789
|
const synkroFile = loadSynkroFile(cwd);
|
|
5727
|
-
const graderPool = effectiveGraderPool(synkroFile, 'claude_code');
|
|
5790
|
+
const graderPool = effectiveGraderPool(synkroFile, 'claude_code', config);
|
|
5728
5791
|
const rt = await route(config, synkroFile);
|
|
5729
5792
|
const tagStr = tag(rt, config, graderPool);
|
|
5730
5793
|
|
|
@@ -6014,7 +6077,7 @@ async function main() {
|
|
|
6014
6077
|
|
|
6015
6078
|
const config = await loadConfig(jwt);
|
|
6016
6079
|
const synkroFile = loadSynkroFile(cwd);
|
|
6017
|
-
const graderPool = effectiveGraderPool(synkroFile, agentKind);
|
|
6080
|
+
const graderPool = effectiveGraderPool(synkroFile, agentKind, config);
|
|
6018
6081
|
const rt = await route(config, synkroFile);
|
|
6019
6082
|
const tagStr = tag(rt, config, graderPool);
|
|
6020
6083
|
|
|
@@ -6233,7 +6296,7 @@ async function main() {
|
|
|
6233
6296
|
|
|
6234
6297
|
const config = await loadConfig(jwt);
|
|
6235
6298
|
const synkroFile = loadSynkroFile(cwd);
|
|
6236
|
-
const graderPool = effectiveGraderPool(synkroFile, agentKind);
|
|
6299
|
+
const graderPool = effectiveGraderPool(synkroFile, agentKind, config);
|
|
6237
6300
|
const rt = await route(config, synkroFile);
|
|
6238
6301
|
const tagStr = tag(rt, config, graderPool);
|
|
6239
6302
|
|
|
@@ -6469,12 +6532,15 @@ async function main() {
|
|
|
6469
6532
|
return;
|
|
6470
6533
|
}
|
|
6471
6534
|
|
|
6472
|
-
// Sync grading
|
|
6473
|
-
|
|
6535
|
+
// Sync the canonical 3-state grading location to the API profile so the
|
|
6536
|
+
// dashboard reflects the machine's ACTUAL config. byok (own API key) wins;
|
|
6537
|
+
// else the cloud worker pool; else on-device workers. The API derives the
|
|
6538
|
+
// legacy fast_inference boolean from this, so we only send one field.
|
|
6539
|
+
const inferenceMode = gradingMode === 'byok' ? 'byok' : (deployCloud ? 'cloud' : 'local');
|
|
6474
6540
|
fetch(GATEWAY_URL + '/api/v1/cli/me', {
|
|
6475
6541
|
method: 'PATCH',
|
|
6476
6542
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
6477
|
-
body: JSON.stringify({
|
|
6543
|
+
body: JSON.stringify({ inference_mode: inferenceMode }),
|
|
6478
6544
|
signal: AbortSignal.timeout(3000),
|
|
6479
6545
|
}).catch(() => {});
|
|
6480
6546
|
|
|
@@ -6829,7 +6895,7 @@ async function main() {
|
|
|
6829
6895
|
if (config.silent) finishAllow();
|
|
6830
6896
|
|
|
6831
6897
|
const synkroFile = loadSynkroFile(cwd);
|
|
6832
|
-
const graderPool = effectiveGraderPool(synkroFile, 'cursor');
|
|
6898
|
+
const graderPool = effectiveGraderPool(synkroFile, 'cursor', config);
|
|
6833
6899
|
const rt = await route(config, synkroFile);
|
|
6834
6900
|
const tagStr = tag(rt, config, graderPool);
|
|
6835
6901
|
|
|
@@ -11164,7 +11230,7 @@ function writeConfigEnv(opts) {
|
|
|
11164
11230
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11165
11231
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11166
11232
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11167
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
11233
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.92")}`
|
|
11168
11234
|
];
|
|
11169
11235
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11170
11236
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -15071,6 +15137,11 @@ function updateConfigValue(key, value) {
|
|
|
15071
15137
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
15072
15138
|
writeFileSync13(CONFIG_PATH6, updated.join("\n"), "utf-8");
|
|
15073
15139
|
}
|
|
15140
|
+
function resolveInferenceMode(cfg) {
|
|
15141
|
+
if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
|
|
15142
|
+
if (cfg.SYNKRO_DEPLOY_LOCATION === "cloud") return "cloud";
|
|
15143
|
+
return "local";
|
|
15144
|
+
}
|
|
15074
15145
|
async function reconcileContainer() {
|
|
15075
15146
|
const cfg = readConfigEnv2();
|
|
15076
15147
|
const cloudOnly = (cfg.SYNKRO_GRADING_MODE || "local") === "byok" && (cfg.SYNKRO_STORAGE_MODE || "local") === "cloud";
|
|
@@ -15098,17 +15169,18 @@ async function configCommand(args2) {
|
|
|
15098
15169
|
if (args2.length === 0) {
|
|
15099
15170
|
const config2 = readConfigEnv2();
|
|
15100
15171
|
console.log("Synkro config:\n");
|
|
15101
|
-
console.log(`
|
|
15172
|
+
console.log(` inference: ${resolveInferenceMode(config2)}`);
|
|
15102
15173
|
console.log(` storage: ${config2.SYNKRO_STORAGE_MODE || "local"}`);
|
|
15103
|
-
console.log(` inference: ${config2.SYNKRO_INFERENCE || "fast"}`);
|
|
15104
15174
|
console.log(` tier: ${config2.SYNKRO_TIER || "pro"}`);
|
|
15105
15175
|
console.log(` gateway: ${config2.SYNKRO_GATEWAY_URL || "https://api.synkro.sh"}`);
|
|
15106
15176
|
console.log(` version: ${config2.SYNKRO_VERSION || "?"}`);
|
|
15107
15177
|
console.log(`
|
|
15108
15178
|
To change:`);
|
|
15109
|
-
console.log(` synkro config
|
|
15110
|
-
console.log(`
|
|
15111
|
-
console.log(`
|
|
15179
|
+
console.log(` synkro config --inference <local|cloud|byok> \u2014 where grading runs`);
|
|
15180
|
+
console.log(` local \u2014 Claude/Cursor workers on this machine (your subscription)`);
|
|
15181
|
+
console.log(` cloud \u2014 Claude/Cursor workers in the Synkro cloud pool`);
|
|
15182
|
+
console.log(` byok \u2014 graded via your own provider API key`);
|
|
15183
|
+
console.log(` synkro config storage <local|cloud> \u2014 where telemetry is stored`);
|
|
15112
15184
|
return;
|
|
15113
15185
|
}
|
|
15114
15186
|
if (args2[0] === "grading") {
|
|
@@ -15142,8 +15214,8 @@ To change:`);
|
|
|
15142
15214
|
if (a.startsWith("--inference=")) inferenceValue = a.slice("--inference=".length);
|
|
15143
15215
|
else if (a === "--inference" && args2.indexOf(a) + 1 < args2.length) inferenceValue = args2[args2.indexOf(a) + 1];
|
|
15144
15216
|
}
|
|
15145
|
-
if (!inferenceValue || !["
|
|
15146
|
-
console.error("Usage: synkro config --inference
|
|
15217
|
+
if (!inferenceValue || !["local", "cloud", "byok"].includes(inferenceValue)) {
|
|
15218
|
+
console.error("Usage: synkro config --inference <local|cloud|byok>");
|
|
15147
15219
|
process.exit(1);
|
|
15148
15220
|
}
|
|
15149
15221
|
if (!isAuthenticated()) {
|
|
@@ -15160,7 +15232,7 @@ To change:`);
|
|
|
15160
15232
|
"Authorization": `Bearer ${token}`,
|
|
15161
15233
|
"Content-Type": "application/json"
|
|
15162
15234
|
},
|
|
15163
|
-
body: JSON.stringify({
|
|
15235
|
+
body: JSON.stringify({ inference_mode: inferenceValue })
|
|
15164
15236
|
});
|
|
15165
15237
|
if (!resp.ok) {
|
|
15166
15238
|
const errText = await resp.text().catch(() => "");
|
|
@@ -15171,8 +15243,24 @@ To change:`);
|
|
|
15171
15243
|
console.error(`Failed to reach server: ${err.message}`);
|
|
15172
15244
|
process.exit(1);
|
|
15173
15245
|
}
|
|
15174
|
-
|
|
15246
|
+
if (inferenceValue === "byok") {
|
|
15247
|
+
updateConfigValue("SYNKRO_GRADING_MODE", "byok");
|
|
15248
|
+
updateConfigValue("SYNKRO_DEPLOY_LOCATION", "local");
|
|
15249
|
+
} else if (inferenceValue === "cloud") {
|
|
15250
|
+
updateConfigValue("SYNKRO_GRADING_MODE", "local");
|
|
15251
|
+
updateConfigValue("SYNKRO_DEPLOY_LOCATION", "cloud");
|
|
15252
|
+
} else {
|
|
15253
|
+
updateConfigValue("SYNKRO_GRADING_MODE", "local");
|
|
15254
|
+
updateConfigValue("SYNKRO_DEPLOY_LOCATION", "local");
|
|
15255
|
+
}
|
|
15175
15256
|
console.log(`\u2713 Inference set to '${inferenceValue}'.`);
|
|
15257
|
+
if (inferenceValue === "byok") {
|
|
15258
|
+
console.log(" BYOK grading uses your own provider key \u2014 register one in the");
|
|
15259
|
+
console.log(" dashboard under Settings \u2192 Provider Keys if you have not already.");
|
|
15260
|
+
} else if (inferenceValue === "cloud") {
|
|
15261
|
+
console.log(" Grading runs on the Synkro cloud worker pool (containers.synkro.sh).");
|
|
15262
|
+
}
|
|
15263
|
+
if (inferenceValue !== "cloud") await reconcileContainer();
|
|
15176
15264
|
}
|
|
15177
15265
|
var SYNKRO_DIR7, CONFIG_PATH6;
|
|
15178
15266
|
var init_config = __esm({
|
|
@@ -15207,7 +15295,7 @@ var args = process.argv.slice(2);
|
|
|
15207
15295
|
var cmd = args[0] || "";
|
|
15208
15296
|
var subArgs = args.slice(1);
|
|
15209
15297
|
function printVersion() {
|
|
15210
|
-
console.log("1.6.
|
|
15298
|
+
console.log("1.6.92");
|
|
15211
15299
|
}
|
|
15212
15300
|
function printHelp2() {
|
|
15213
15301
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|