@synkro-sh/cli 1.6.84 → 1.6.85
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 +221 -19
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1846,6 +1846,33 @@ export function ruleFilterText(action: string, userMessage?: string | null): str
|
|
|
1846
1846
|
|
|
1847
1847
|
export async function filterRules(commandText: string, allRules: Rule[]): Promise<Rule[]> {
|
|
1848
1848
|
if (allRules.length <= 3) return allRules;
|
|
1849
|
+
|
|
1850
|
+
// Cloud: the embedding index lives server-side (the container grade path can't
|
|
1851
|
+
// reach 127.0.0.1), so ask the gateway for the top-3 relevant rules \u2014 same
|
|
1852
|
+
// CF/BYOK bge-base vectors, same behavior as local. Without this, cloud grades
|
|
1853
|
+
// dumped EVERY rule into the prompt (9KB+), the dominant cause of grade
|
|
1854
|
+
// timeouts. Any failure falls back to all rules (status quo), never blocks.
|
|
1855
|
+
if (!isLocalStorageMode()) {
|
|
1856
|
+
const jwt = loadJwt();
|
|
1857
|
+
if (!jwt) return allRules;
|
|
1858
|
+
try {
|
|
1859
|
+
const resp = await fetch(GATEWAY_URL + '/api/v1/hook/filter-rules', {
|
|
1860
|
+
method: 'POST',
|
|
1861
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1862
|
+
body: JSON.stringify({ text: commandText, top_k: 3 }),
|
|
1863
|
+
signal: AbortSignal.timeout(2000),
|
|
1864
|
+
});
|
|
1865
|
+
if (!resp.ok) return allRules;
|
|
1866
|
+
const data = await resp.json() as { rules?: Array<Record<string, unknown>> };
|
|
1867
|
+
if (!data.rules || data.rules.length === 0) return allRules;
|
|
1868
|
+
const selectedIds = new Set(data.rules.map(r => String(r.rule_id || '')));
|
|
1869
|
+
return allRules.filter(r => selectedIds.has(r.rule_id));
|
|
1870
|
+
} catch {
|
|
1871
|
+
return allRules;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// Local: the on-device PGLite embedding index owns the rule set.
|
|
1849
1876
|
const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';
|
|
1850
1877
|
try {
|
|
1851
1878
|
const resp = await fetch('http://127.0.0.1:' + mcpPort + '/api/local/filter-rules', {
|
|
@@ -1857,10 +1884,7 @@ export async function filterRules(commandText: string, allRules: Rule[]): Promis
|
|
|
1857
1884
|
if (!resp.ok) return allRules;
|
|
1858
1885
|
const data = await resp.json() as { rules?: Array<Record<string, unknown>> };
|
|
1859
1886
|
if (!data.rules || data.rules.length === 0) return allRules;
|
|
1860
|
-
|
|
1861
|
-
if (isLocalStorageMode()) return mapHookRules(data.rules);
|
|
1862
|
-
const selectedIds = new Set(data.rules.map(r => String(r.rule_id || '')));
|
|
1863
|
-
return allRules.filter(r => selectedIds.has(r.rule_id));
|
|
1887
|
+
return mapHookRules(data.rules);
|
|
1864
1888
|
} catch {
|
|
1865
1889
|
return allRules;
|
|
1866
1890
|
}
|
|
@@ -10516,6 +10540,7 @@ __export(install_exports, {
|
|
|
10516
10540
|
getOrMintCloudToken: () => getOrMintCloudToken,
|
|
10517
10541
|
installCommand: () => installCommand,
|
|
10518
10542
|
parseArgs: () => parseArgs,
|
|
10543
|
+
readFullSynkroFile: () => readFullSynkroFile,
|
|
10519
10544
|
reconcileDeployLocation: () => reconcileDeployLocation,
|
|
10520
10545
|
reconcileHarness: () => reconcileHarness,
|
|
10521
10546
|
recycleCloudContainer: () => recycleCloudContainer,
|
|
@@ -10844,7 +10869,7 @@ function writeConfigEnv(opts) {
|
|
|
10844
10869
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
10845
10870
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
10846
10871
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
10847
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
10872
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.85")}`
|
|
10848
10873
|
];
|
|
10849
10874
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
10850
10875
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -11961,6 +11986,7 @@ function readFullSynkroFile() {
|
|
|
11961
11986
|
ruleset: parsed.ruleset || "default",
|
|
11962
11987
|
skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && s.endsWith(".md")) : [],
|
|
11963
11988
|
scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },
|
|
11989
|
+
standards: parsed.standards && typeof parsed.standards === "object" ? Object.fromEntries(Object.entries(parsed.standards).filter(([k, v]) => typeof v === "string" && k.includes("/")).map(([k, v]) => [k, String(v)])) : {},
|
|
11964
11990
|
_repoRoot: root
|
|
11965
11991
|
};
|
|
11966
11992
|
} catch {
|
|
@@ -14425,6 +14451,177 @@ var init_import = __esm({
|
|
|
14425
14451
|
}
|
|
14426
14452
|
});
|
|
14427
14453
|
|
|
14454
|
+
// cli/installer/packVerify.ts
|
|
14455
|
+
import crypto from "crypto";
|
|
14456
|
+
function stableStringify(value) {
|
|
14457
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
14458
|
+
if (Array.isArray(value)) return "[" + value.map(stableStringify).join(",") + "]";
|
|
14459
|
+
const obj = value;
|
|
14460
|
+
const keys = Object.keys(obj).sort();
|
|
14461
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
14462
|
+
}
|
|
14463
|
+
function canonicalize(pack) {
|
|
14464
|
+
return stableStringify({
|
|
14465
|
+
rules: pack.rules ?? [],
|
|
14466
|
+
docs: pack.docs ?? [],
|
|
14467
|
+
manifest: pack.manifest ?? {}
|
|
14468
|
+
});
|
|
14469
|
+
}
|
|
14470
|
+
function computeDigest(canonical) {
|
|
14471
|
+
return "sha256:" + crypto.createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
14472
|
+
}
|
|
14473
|
+
function verifySignature(digest, signatureB64, publicKeyPem) {
|
|
14474
|
+
try {
|
|
14475
|
+
const key = crypto.createPublicKey(publicKeyPem);
|
|
14476
|
+
return crypto.verify(null, Buffer.from(digest, "utf8"), key, Buffer.from(signatureB64, "base64"));
|
|
14477
|
+
} catch {
|
|
14478
|
+
return false;
|
|
14479
|
+
}
|
|
14480
|
+
}
|
|
14481
|
+
function verifyPack(pack, claimedDigest, signatureB64, publicKeyPem) {
|
|
14482
|
+
const recomputed = computeDigest(canonicalize(pack));
|
|
14483
|
+
if (recomputed !== claimedDigest) return false;
|
|
14484
|
+
return verifySignature(claimedDigest, signatureB64, publicKeyPem);
|
|
14485
|
+
}
|
|
14486
|
+
var init_packVerify = __esm({
|
|
14487
|
+
"cli/installer/packVerify.ts"() {
|
|
14488
|
+
"use strict";
|
|
14489
|
+
}
|
|
14490
|
+
});
|
|
14491
|
+
|
|
14492
|
+
// cli/installer/lockfile.ts
|
|
14493
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
|
|
14494
|
+
import { join as join18 } from "path";
|
|
14495
|
+
function lockPath(repoRoot) {
|
|
14496
|
+
return join18(repoRoot, LOCK_FILE);
|
|
14497
|
+
}
|
|
14498
|
+
function writeLockfile(repoRoot, entries) {
|
|
14499
|
+
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
14500
|
+
const body = [
|
|
14501
|
+
"# synkro.lock \u2014 generated by `synkro sync`. Commit this file.",
|
|
14502
|
+
"# Pins each subscribed pack to its verified, attested digest.",
|
|
14503
|
+
"",
|
|
14504
|
+
...sorted.flatMap((e) => [
|
|
14505
|
+
"[[pack]]",
|
|
14506
|
+
`ref = "${e.ref}"`,
|
|
14507
|
+
`version = "${e.version}"`,
|
|
14508
|
+
`digest = "${e.digest}"`,
|
|
14509
|
+
`signature = "${e.signature}"`,
|
|
14510
|
+
`signing_key_id = "${e.signingKeyId}"`,
|
|
14511
|
+
""
|
|
14512
|
+
])
|
|
14513
|
+
].join("\n");
|
|
14514
|
+
writeFileSync11(lockPath(repoRoot), body, "utf-8");
|
|
14515
|
+
}
|
|
14516
|
+
var LOCK_FILE;
|
|
14517
|
+
var init_lockfile = __esm({
|
|
14518
|
+
"cli/installer/lockfile.ts"() {
|
|
14519
|
+
"use strict";
|
|
14520
|
+
LOCK_FILE = "synkro.lock";
|
|
14521
|
+
}
|
|
14522
|
+
});
|
|
14523
|
+
|
|
14524
|
+
// cli/commands/sync.ts
|
|
14525
|
+
var sync_exports = {};
|
|
14526
|
+
__export(sync_exports, {
|
|
14527
|
+
syncCommand: () => syncCommand
|
|
14528
|
+
});
|
|
14529
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync12 } from "fs";
|
|
14530
|
+
import { homedir as homedir18 } from "os";
|
|
14531
|
+
import { join as join19 } from "path";
|
|
14532
|
+
function cacheKey(ref, version) {
|
|
14533
|
+
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
14534
|
+
}
|
|
14535
|
+
async function syncCommand(_args = []) {
|
|
14536
|
+
const sf = readFullSynkroFile();
|
|
14537
|
+
if (!sf) {
|
|
14538
|
+
console.error("No synkro.toml found in the repo root. Run `synkro install` first.");
|
|
14539
|
+
process.exitCode = 1;
|
|
14540
|
+
return;
|
|
14541
|
+
}
|
|
14542
|
+
const refs = Object.entries(sf.standards);
|
|
14543
|
+
if (refs.length === 0) {
|
|
14544
|
+
console.log("No [standards] subscriptions in synkro.toml \u2014 nothing to sync.");
|
|
14545
|
+
return;
|
|
14546
|
+
}
|
|
14547
|
+
await ensureValidToken();
|
|
14548
|
+
const token = getAccessToken();
|
|
14549
|
+
if (!token) {
|
|
14550
|
+
console.error("Not logged in. Run `synkro login` and try again.");
|
|
14551
|
+
process.exitCode = 1;
|
|
14552
|
+
return;
|
|
14553
|
+
}
|
|
14554
|
+
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
14555
|
+
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
14556
|
+
const cacheDir = join19(homedir18(), ".synkro", "cache", "packs");
|
|
14557
|
+
if (!cloud) mkdirSync12(cacheDir, { recursive: true });
|
|
14558
|
+
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
14559
|
+
const lock = [];
|
|
14560
|
+
const keptCacheFiles = /* @__PURE__ */ new Set();
|
|
14561
|
+
for (const [ref, version] of refs) {
|
|
14562
|
+
let url = `${gateway}/api/v1/registry/resolve?ref=${encodeURIComponent(ref)}`;
|
|
14563
|
+
if (version && version !== "latest") url += `&version=${encodeURIComponent(version)}`;
|
|
14564
|
+
let data;
|
|
14565
|
+
try {
|
|
14566
|
+
const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
14567
|
+
if (!resp.ok) {
|
|
14568
|
+
console.error(` \u2717 ${ref}: resolve failed (${resp.status} ${resp.statusText})`);
|
|
14569
|
+
continue;
|
|
14570
|
+
}
|
|
14571
|
+
data = await resp.json();
|
|
14572
|
+
} catch (err) {
|
|
14573
|
+
console.error(` \u2717 ${ref}: ${err.message}`);
|
|
14574
|
+
continue;
|
|
14575
|
+
}
|
|
14576
|
+
const pack = { rules: data.rules ?? [], docs: data.docs ?? [], manifest: data.manifest ?? {} };
|
|
14577
|
+
if (!verifyPack(pack, data.digest, data.signature, data.publicKey)) {
|
|
14578
|
+
console.error(` \u2717 ${ref}: signature/digest verification FAILED \u2014 refusing to apply.`);
|
|
14579
|
+
continue;
|
|
14580
|
+
}
|
|
14581
|
+
lock.push({ ref, version: data.version, digest: data.digest, signature: data.signature, signingKeyId: data.signingKeyId });
|
|
14582
|
+
if (!cloud) {
|
|
14583
|
+
const fname = cacheKey(ref, data.version);
|
|
14584
|
+
keptCacheFiles.add(fname);
|
|
14585
|
+
writeFileSync12(join19(cacheDir, fname), JSON.stringify({
|
|
14586
|
+
ref,
|
|
14587
|
+
version: data.version,
|
|
14588
|
+
digest: data.digest,
|
|
14589
|
+
rules: pack.rules,
|
|
14590
|
+
docs: pack.docs,
|
|
14591
|
+
manifest: pack.manifest
|
|
14592
|
+
}), "utf-8");
|
|
14593
|
+
}
|
|
14594
|
+
const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
|
|
14595
|
+
console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
|
|
14596
|
+
}
|
|
14597
|
+
if (!cloud && existsSync19(cacheDir)) {
|
|
14598
|
+
for (const f of readdirSync6(cacheDir)) {
|
|
14599
|
+
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
14600
|
+
try {
|
|
14601
|
+
rmSync2(join19(cacheDir, f));
|
|
14602
|
+
} catch {
|
|
14603
|
+
}
|
|
14604
|
+
}
|
|
14605
|
+
}
|
|
14606
|
+
}
|
|
14607
|
+
if (lock.length > 0) {
|
|
14608
|
+
writeLockfile(sf._repoRoot, lock);
|
|
14609
|
+
console.log(`Wrote synkro.lock (${lock.length} pack${lock.length === 1 ? "" : "s"} pinned).`);
|
|
14610
|
+
} else {
|
|
14611
|
+
console.error("No packs synced successfully.");
|
|
14612
|
+
process.exitCode = 1;
|
|
14613
|
+
}
|
|
14614
|
+
}
|
|
14615
|
+
var init_sync = __esm({
|
|
14616
|
+
"cli/commands/sync.ts"() {
|
|
14617
|
+
"use strict";
|
|
14618
|
+
init_install();
|
|
14619
|
+
init_stub();
|
|
14620
|
+
init_packVerify();
|
|
14621
|
+
init_lockfile();
|
|
14622
|
+
}
|
|
14623
|
+
});
|
|
14624
|
+
|
|
14428
14625
|
// cli/commands/lifecycle.ts
|
|
14429
14626
|
var lifecycle_exports = {};
|
|
14430
14627
|
__export(lifecycle_exports, {
|
|
@@ -14537,13 +14734,13 @@ var config_exports = {};
|
|
|
14537
14734
|
__export(config_exports, {
|
|
14538
14735
|
configCommand: () => configCommand
|
|
14539
14736
|
});
|
|
14540
|
-
import { readFileSync as
|
|
14541
|
-
import { join as
|
|
14542
|
-
import { homedir as
|
|
14737
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync20 } from "fs";
|
|
14738
|
+
import { join as join20 } from "path";
|
|
14739
|
+
import { homedir as homedir19 } from "os";
|
|
14543
14740
|
function readConfigEnv2() {
|
|
14544
|
-
if (!
|
|
14741
|
+
if (!existsSync20(CONFIG_PATH6)) return {};
|
|
14545
14742
|
const out = {};
|
|
14546
|
-
for (const line of
|
|
14743
|
+
for (const line of readFileSync17(CONFIG_PATH6, "utf-8").split("\n")) {
|
|
14547
14744
|
const t = line.trim();
|
|
14548
14745
|
if (!t || t.startsWith("#")) continue;
|
|
14549
14746
|
const eq = t.indexOf("=");
|
|
@@ -14552,11 +14749,11 @@ function readConfigEnv2() {
|
|
|
14552
14749
|
return out;
|
|
14553
14750
|
}
|
|
14554
14751
|
function updateConfigValue(key, value) {
|
|
14555
|
-
if (!
|
|
14752
|
+
if (!existsSync20(CONFIG_PATH6)) {
|
|
14556
14753
|
console.error("No config found. Run `synkro install` first.");
|
|
14557
14754
|
process.exit(1);
|
|
14558
14755
|
}
|
|
14559
|
-
const lines =
|
|
14756
|
+
const lines = readFileSync17(CONFIG_PATH6, "utf-8").split("\n");
|
|
14560
14757
|
const pattern = new RegExp(`^${key}=`);
|
|
14561
14758
|
let found = false;
|
|
14562
14759
|
const updated = lines.map((line) => {
|
|
@@ -14567,7 +14764,7 @@ function updateConfigValue(key, value) {
|
|
|
14567
14764
|
return line;
|
|
14568
14765
|
});
|
|
14569
14766
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
14570
|
-
|
|
14767
|
+
writeFileSync13(CONFIG_PATH6, updated.join("\n"), "utf-8");
|
|
14571
14768
|
}
|
|
14572
14769
|
async function reconcileContainer() {
|
|
14573
14770
|
const cfg = readConfigEnv2();
|
|
@@ -14677,20 +14874,20 @@ var init_config = __esm({
|
|
|
14677
14874
|
"cli/commands/config.ts"() {
|
|
14678
14875
|
"use strict";
|
|
14679
14876
|
init_stub();
|
|
14680
|
-
SYNKRO_DIR7 =
|
|
14681
|
-
CONFIG_PATH6 =
|
|
14877
|
+
SYNKRO_DIR7 = join20(homedir19(), ".synkro");
|
|
14878
|
+
CONFIG_PATH6 = join20(SYNKRO_DIR7, "config.env");
|
|
14682
14879
|
}
|
|
14683
14880
|
});
|
|
14684
14881
|
|
|
14685
14882
|
// cli/bootstrap.js
|
|
14686
|
-
import { readFileSync as
|
|
14883
|
+
import { readFileSync as readFileSync18, existsSync as existsSync21 } from "fs";
|
|
14687
14884
|
import { resolve as resolve3 } from "path";
|
|
14688
14885
|
var envCandidates = [
|
|
14689
14886
|
resolve3(process.env.HOME ?? "", ".synkro", "config.env")
|
|
14690
14887
|
];
|
|
14691
14888
|
for (const envPath of envCandidates) {
|
|
14692
|
-
if (!
|
|
14693
|
-
const envContent =
|
|
14889
|
+
if (!existsSync21(envPath)) continue;
|
|
14890
|
+
const envContent = readFileSync18(envPath, "utf-8");
|
|
14694
14891
|
for (const line of envContent.split("\n")) {
|
|
14695
14892
|
const trimmed = line.trim();
|
|
14696
14893
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -14705,7 +14902,7 @@ var args = process.argv.slice(2);
|
|
|
14705
14902
|
var cmd = args[0] || "";
|
|
14706
14903
|
var subArgs = args.slice(1);
|
|
14707
14904
|
function printVersion() {
|
|
14708
|
-
console.log("1.6.
|
|
14905
|
+
console.log("1.6.85");
|
|
14709
14906
|
}
|
|
14710
14907
|
function printHelp2() {
|
|
14711
14908
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|
|
@@ -14784,6 +14981,11 @@ async function main() {
|
|
|
14784
14981
|
await importCommand2();
|
|
14785
14982
|
break;
|
|
14786
14983
|
}
|
|
14984
|
+
case "sync": {
|
|
14985
|
+
const { syncCommand: syncCommand2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
14986
|
+
await syncCommand2(subArgs);
|
|
14987
|
+
break;
|
|
14988
|
+
}
|
|
14787
14989
|
case "version":
|
|
14788
14990
|
case "--version":
|
|
14789
14991
|
case "-v": {
|