@synkro-sh/cli 1.6.84 → 1.6.86
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 +232 -33
- 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
|
}
|
|
@@ -4080,9 +4104,6 @@ async function main() {
|
|
|
4080
4104
|
'User intent (last human message): ' + (transcript.userIntent || 'none stated'),
|
|
4081
4105
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
4082
4106
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
4083
|
-
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
4084
|
-
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
4085
|
-
'The rules shown were pre-selected as the ones relevant to this edit \u2014 every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no hardcoded secrets in file. R005: in-repo path only." Cover every rule shown.',
|
|
4086
4107
|
].join('\\n');
|
|
4087
4108
|
const graderPrompt = buildGraderPrompt(proposedShort);
|
|
4088
4109
|
|
|
@@ -5513,10 +5534,6 @@ async function main() {
|
|
|
5513
5534
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
5514
5535
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
5515
5536
|
scanConcern,
|
|
5516
|
-
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix — your job is only to detect violations.',
|
|
5517
|
-
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent — only a response to a shown block counts.',
|
|
5518
|
-
'The rules shown were pre-selected as the ones relevant to this command — every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no secrets in grep args. R005: in-repo path only." Cover every rule shown.',
|
|
5519
|
-
'Rules with preconditions (e.g. "run X before Y") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',
|
|
5520
5537
|
].filter(Boolean).join('\\n');
|
|
5521
5538
|
|
|
5522
5539
|
let gradeResp: string;
|
|
@@ -5733,8 +5750,6 @@ async function main() {
|
|
|
5733
5750
|
'User intent (last human message): ' + (transcript.userIntent || 'none stated'),
|
|
5734
5751
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
5735
5752
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
5736
|
-
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
5737
|
-
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
5738
5753
|
].filter(Boolean).join('\\n');
|
|
5739
5754
|
|
|
5740
5755
|
let gradeResp: string;
|
|
@@ -6559,10 +6574,6 @@ async function main() {
|
|
|
6559
6574
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
6560
6575
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
6561
6576
|
scanConcern,
|
|
6562
|
-
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
6563
|
-
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. An initial user instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
6564
|
-
'The rules shown were pre-selected as the ones relevant to this command \u2014 every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no secrets in grep args. R005: in-repo path only." Cover every rule shown.',
|
|
6565
|
-
'Rules with preconditions (e.g. "run X before Y") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',
|
|
6566
6577
|
].filter(Boolean).join('\\n');
|
|
6567
6578
|
|
|
6568
6579
|
let gradeResp: string;
|
|
@@ -10516,6 +10527,7 @@ __export(install_exports, {
|
|
|
10516
10527
|
getOrMintCloudToken: () => getOrMintCloudToken,
|
|
10517
10528
|
installCommand: () => installCommand,
|
|
10518
10529
|
parseArgs: () => parseArgs,
|
|
10530
|
+
readFullSynkroFile: () => readFullSynkroFile,
|
|
10519
10531
|
reconcileDeployLocation: () => reconcileDeployLocation,
|
|
10520
10532
|
reconcileHarness: () => reconcileHarness,
|
|
10521
10533
|
recycleCloudContainer: () => recycleCloudContainer,
|
|
@@ -10844,7 +10856,7 @@ function writeConfigEnv(opts) {
|
|
|
10844
10856
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
10845
10857
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
10846
10858
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
10847
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
10859
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.86")}`
|
|
10848
10860
|
];
|
|
10849
10861
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
10850
10862
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -11660,7 +11672,12 @@ async function installCommand(opts = {}) {
|
|
|
11660
11672
|
} catch (err) {
|
|
11661
11673
|
console.warn(` \u26A0 Could not cache judge prompts: ${err.message}`);
|
|
11662
11674
|
}
|
|
11663
|
-
|
|
11675
|
+
if (deployLocation !== "cloud") {
|
|
11676
|
+
writeSynkroFileIfMissing({ hasClaudeCode, hasCursor, gradingMode, deployLocation });
|
|
11677
|
+
} else {
|
|
11678
|
+
console.log(" Cloud mode: standards + grader pool are configured in the dashboard");
|
|
11679
|
+
console.log(" (Settings \u2192 Standards / Pool) \u2014 no synkro.toml needed.");
|
|
11680
|
+
}
|
|
11664
11681
|
console.log();
|
|
11665
11682
|
let cloudGradeOk = null;
|
|
11666
11683
|
if (useLocalMcp) {
|
|
@@ -11961,6 +11978,7 @@ function readFullSynkroFile() {
|
|
|
11961
11978
|
ruleset: parsed.ruleset || "default",
|
|
11962
11979
|
skills: Array.isArray(parsed.skills) ? parsed.skills.filter((s) => typeof s === "string" && s.endsWith(".md")) : [],
|
|
11963
11980
|
scanning: { cwe: parsed.scanning?.cwe !== false, cve: parsed.scanning?.cve !== false },
|
|
11981
|
+
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
11982
|
_repoRoot: root
|
|
11965
11983
|
};
|
|
11966
11984
|
} catch {
|
|
@@ -14425,6 +14443,182 @@ var init_import = __esm({
|
|
|
14425
14443
|
}
|
|
14426
14444
|
});
|
|
14427
14445
|
|
|
14446
|
+
// cli/installer/packVerify.ts
|
|
14447
|
+
import crypto from "crypto";
|
|
14448
|
+
function stableStringify(value) {
|
|
14449
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
14450
|
+
if (Array.isArray(value)) return "[" + value.map(stableStringify).join(",") + "]";
|
|
14451
|
+
const obj = value;
|
|
14452
|
+
const keys = Object.keys(obj).sort();
|
|
14453
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
14454
|
+
}
|
|
14455
|
+
function canonicalize(pack) {
|
|
14456
|
+
return stableStringify({
|
|
14457
|
+
rules: pack.rules ?? [],
|
|
14458
|
+
docs: pack.docs ?? []
|
|
14459
|
+
});
|
|
14460
|
+
}
|
|
14461
|
+
function computeDigest(canonical) {
|
|
14462
|
+
return "sha256:" + crypto.createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
14463
|
+
}
|
|
14464
|
+
function verifySignature(digest, signatureB64, publicKeyPem) {
|
|
14465
|
+
try {
|
|
14466
|
+
const key = crypto.createPublicKey(publicKeyPem);
|
|
14467
|
+
return crypto.verify(null, Buffer.from(digest, "utf8"), key, Buffer.from(signatureB64, "base64"));
|
|
14468
|
+
} catch {
|
|
14469
|
+
return false;
|
|
14470
|
+
}
|
|
14471
|
+
}
|
|
14472
|
+
function verifyPack(pack, claimedDigest, signatureB64, publicKeyPem) {
|
|
14473
|
+
const recomputed = computeDigest(canonicalize(pack));
|
|
14474
|
+
if (recomputed !== claimedDigest) return false;
|
|
14475
|
+
return verifySignature(claimedDigest, signatureB64, publicKeyPem);
|
|
14476
|
+
}
|
|
14477
|
+
var init_packVerify = __esm({
|
|
14478
|
+
"cli/installer/packVerify.ts"() {
|
|
14479
|
+
"use strict";
|
|
14480
|
+
}
|
|
14481
|
+
});
|
|
14482
|
+
|
|
14483
|
+
// cli/installer/lockfile.ts
|
|
14484
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
|
|
14485
|
+
import { join as join18 } from "path";
|
|
14486
|
+
function lockPath(repoRoot) {
|
|
14487
|
+
return join18(repoRoot, LOCK_FILE);
|
|
14488
|
+
}
|
|
14489
|
+
function writeLockfile(repoRoot, entries) {
|
|
14490
|
+
const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
14491
|
+
const body = [
|
|
14492
|
+
"# synkro.lock \u2014 generated by `synkro sync`. Commit this file.",
|
|
14493
|
+
"# Pins each subscribed pack to its verified, attested digest.",
|
|
14494
|
+
"",
|
|
14495
|
+
...sorted.flatMap((e) => [
|
|
14496
|
+
"[[pack]]",
|
|
14497
|
+
`ref = "${e.ref}"`,
|
|
14498
|
+
`version = "${e.version}"`,
|
|
14499
|
+
`digest = "${e.digest}"`,
|
|
14500
|
+
`signature = "${e.signature}"`,
|
|
14501
|
+
`signing_key_id = "${e.signingKeyId}"`,
|
|
14502
|
+
""
|
|
14503
|
+
])
|
|
14504
|
+
].join("\n");
|
|
14505
|
+
writeFileSync11(lockPath(repoRoot), body, "utf-8");
|
|
14506
|
+
}
|
|
14507
|
+
var LOCK_FILE;
|
|
14508
|
+
var init_lockfile = __esm({
|
|
14509
|
+
"cli/installer/lockfile.ts"() {
|
|
14510
|
+
"use strict";
|
|
14511
|
+
LOCK_FILE = "synkro.lock";
|
|
14512
|
+
}
|
|
14513
|
+
});
|
|
14514
|
+
|
|
14515
|
+
// cli/commands/sync.ts
|
|
14516
|
+
var sync_exports = {};
|
|
14517
|
+
__export(sync_exports, {
|
|
14518
|
+
syncCommand: () => syncCommand
|
|
14519
|
+
});
|
|
14520
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync12 } from "fs";
|
|
14521
|
+
import { homedir as homedir18 } from "os";
|
|
14522
|
+
import { join as join19 } from "path";
|
|
14523
|
+
function cacheKey(ref, version) {
|
|
14524
|
+
return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
|
|
14525
|
+
}
|
|
14526
|
+
async function syncCommand(_args = []) {
|
|
14527
|
+
if (process.env.SYNKRO_DEPLOY_LOCATION === "cloud") {
|
|
14528
|
+
console.log("Cloud mode: standards are applied org-wide from the dashboard");
|
|
14529
|
+
console.log("(Settings \u2192 Standards). No synkro.toml or `synkro sync` needed \u2014");
|
|
14530
|
+
console.log("packs enforce automatically wherever Synkro is installed.");
|
|
14531
|
+
return;
|
|
14532
|
+
}
|
|
14533
|
+
const sf = readFullSynkroFile();
|
|
14534
|
+
if (!sf) {
|
|
14535
|
+
console.error("No synkro.toml found in the repo root. Run `synkro install` first.");
|
|
14536
|
+
process.exitCode = 1;
|
|
14537
|
+
return;
|
|
14538
|
+
}
|
|
14539
|
+
const refs = Object.entries(sf.standards);
|
|
14540
|
+
if (refs.length === 0) {
|
|
14541
|
+
console.log("No [standards] subscriptions in synkro.toml \u2014 nothing to sync.");
|
|
14542
|
+
return;
|
|
14543
|
+
}
|
|
14544
|
+
await ensureValidToken();
|
|
14545
|
+
const token = getAccessToken();
|
|
14546
|
+
if (!token) {
|
|
14547
|
+
console.error("Not logged in. Run `synkro login` and try again.");
|
|
14548
|
+
process.exitCode = 1;
|
|
14549
|
+
return;
|
|
14550
|
+
}
|
|
14551
|
+
const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
14552
|
+
const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
|
|
14553
|
+
const cacheDir = join19(homedir18(), ".synkro", "cache", "packs");
|
|
14554
|
+
if (!cloud) mkdirSync12(cacheDir, { recursive: true });
|
|
14555
|
+
console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
|
|
14556
|
+
const lock = [];
|
|
14557
|
+
const keptCacheFiles = /* @__PURE__ */ new Set();
|
|
14558
|
+
for (const [ref, version] of refs) {
|
|
14559
|
+
let url = `${gateway}/api/v1/registry/resolve?ref=${encodeURIComponent(ref)}`;
|
|
14560
|
+
if (version && version !== "latest") url += `&version=${encodeURIComponent(version)}`;
|
|
14561
|
+
let data;
|
|
14562
|
+
try {
|
|
14563
|
+
const resp = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
14564
|
+
if (!resp.ok) {
|
|
14565
|
+
console.error(` \u2717 ${ref}: resolve failed (${resp.status} ${resp.statusText})`);
|
|
14566
|
+
continue;
|
|
14567
|
+
}
|
|
14568
|
+
data = await resp.json();
|
|
14569
|
+
} catch (err) {
|
|
14570
|
+
console.error(` \u2717 ${ref}: ${err.message}`);
|
|
14571
|
+
continue;
|
|
14572
|
+
}
|
|
14573
|
+
const pack = { rules: data.rules ?? [], docs: data.docs ?? [], manifest: data.manifest ?? {} };
|
|
14574
|
+
if (!verifyPack(pack, data.digest, data.signature, data.publicKey)) {
|
|
14575
|
+
console.error(` \u2717 ${ref}: signature/digest verification FAILED \u2014 refusing to apply.`);
|
|
14576
|
+
continue;
|
|
14577
|
+
}
|
|
14578
|
+
lock.push({ ref, version: data.version, digest: data.digest, signature: data.signature, signingKeyId: data.signingKeyId });
|
|
14579
|
+
if (!cloud) {
|
|
14580
|
+
const fname = cacheKey(ref, data.version);
|
|
14581
|
+
keptCacheFiles.add(fname);
|
|
14582
|
+
writeFileSync12(join19(cacheDir, fname), JSON.stringify({
|
|
14583
|
+
ref,
|
|
14584
|
+
version: data.version,
|
|
14585
|
+
digest: data.digest,
|
|
14586
|
+
rules: pack.rules,
|
|
14587
|
+
docs: pack.docs,
|
|
14588
|
+
manifest: pack.manifest
|
|
14589
|
+
}), "utf-8");
|
|
14590
|
+
}
|
|
14591
|
+
const ruleCount = Array.isArray(pack.rules) ? pack.rules.length : 0;
|
|
14592
|
+
console.log(` \u2713 ${ref}:${data.version} \u2014 verified (${ruleCount} rule${ruleCount === 1 ? "" : "s"})`);
|
|
14593
|
+
}
|
|
14594
|
+
if (!cloud && existsSync19(cacheDir)) {
|
|
14595
|
+
for (const f of readdirSync6(cacheDir)) {
|
|
14596
|
+
if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
|
|
14597
|
+
try {
|
|
14598
|
+
rmSync2(join19(cacheDir, f));
|
|
14599
|
+
} catch {
|
|
14600
|
+
}
|
|
14601
|
+
}
|
|
14602
|
+
}
|
|
14603
|
+
}
|
|
14604
|
+
if (lock.length > 0) {
|
|
14605
|
+
writeLockfile(sf._repoRoot, lock);
|
|
14606
|
+
console.log(`Wrote synkro.lock (${lock.length} pack${lock.length === 1 ? "" : "s"} pinned).`);
|
|
14607
|
+
} else {
|
|
14608
|
+
console.error("No packs synced successfully.");
|
|
14609
|
+
process.exitCode = 1;
|
|
14610
|
+
}
|
|
14611
|
+
}
|
|
14612
|
+
var init_sync = __esm({
|
|
14613
|
+
"cli/commands/sync.ts"() {
|
|
14614
|
+
"use strict";
|
|
14615
|
+
init_install();
|
|
14616
|
+
init_stub();
|
|
14617
|
+
init_packVerify();
|
|
14618
|
+
init_lockfile();
|
|
14619
|
+
}
|
|
14620
|
+
});
|
|
14621
|
+
|
|
14428
14622
|
// cli/commands/lifecycle.ts
|
|
14429
14623
|
var lifecycle_exports = {};
|
|
14430
14624
|
__export(lifecycle_exports, {
|
|
@@ -14537,13 +14731,13 @@ var config_exports = {};
|
|
|
14537
14731
|
__export(config_exports, {
|
|
14538
14732
|
configCommand: () => configCommand
|
|
14539
14733
|
});
|
|
14540
|
-
import { readFileSync as
|
|
14541
|
-
import { join as
|
|
14542
|
-
import { homedir as
|
|
14734
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync20 } from "fs";
|
|
14735
|
+
import { join as join20 } from "path";
|
|
14736
|
+
import { homedir as homedir19 } from "os";
|
|
14543
14737
|
function readConfigEnv2() {
|
|
14544
|
-
if (!
|
|
14738
|
+
if (!existsSync20(CONFIG_PATH6)) return {};
|
|
14545
14739
|
const out = {};
|
|
14546
|
-
for (const line of
|
|
14740
|
+
for (const line of readFileSync17(CONFIG_PATH6, "utf-8").split("\n")) {
|
|
14547
14741
|
const t = line.trim();
|
|
14548
14742
|
if (!t || t.startsWith("#")) continue;
|
|
14549
14743
|
const eq = t.indexOf("=");
|
|
@@ -14552,11 +14746,11 @@ function readConfigEnv2() {
|
|
|
14552
14746
|
return out;
|
|
14553
14747
|
}
|
|
14554
14748
|
function updateConfigValue(key, value) {
|
|
14555
|
-
if (!
|
|
14749
|
+
if (!existsSync20(CONFIG_PATH6)) {
|
|
14556
14750
|
console.error("No config found. Run `synkro install` first.");
|
|
14557
14751
|
process.exit(1);
|
|
14558
14752
|
}
|
|
14559
|
-
const lines =
|
|
14753
|
+
const lines = readFileSync17(CONFIG_PATH6, "utf-8").split("\n");
|
|
14560
14754
|
const pattern = new RegExp(`^${key}=`);
|
|
14561
14755
|
let found = false;
|
|
14562
14756
|
const updated = lines.map((line) => {
|
|
@@ -14567,7 +14761,7 @@ function updateConfigValue(key, value) {
|
|
|
14567
14761
|
return line;
|
|
14568
14762
|
});
|
|
14569
14763
|
if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
|
|
14570
|
-
|
|
14764
|
+
writeFileSync13(CONFIG_PATH6, updated.join("\n"), "utf-8");
|
|
14571
14765
|
}
|
|
14572
14766
|
async function reconcileContainer() {
|
|
14573
14767
|
const cfg = readConfigEnv2();
|
|
@@ -14677,20 +14871,20 @@ var init_config = __esm({
|
|
|
14677
14871
|
"cli/commands/config.ts"() {
|
|
14678
14872
|
"use strict";
|
|
14679
14873
|
init_stub();
|
|
14680
|
-
SYNKRO_DIR7 =
|
|
14681
|
-
CONFIG_PATH6 =
|
|
14874
|
+
SYNKRO_DIR7 = join20(homedir19(), ".synkro");
|
|
14875
|
+
CONFIG_PATH6 = join20(SYNKRO_DIR7, "config.env");
|
|
14682
14876
|
}
|
|
14683
14877
|
});
|
|
14684
14878
|
|
|
14685
14879
|
// cli/bootstrap.js
|
|
14686
|
-
import { readFileSync as
|
|
14880
|
+
import { readFileSync as readFileSync18, existsSync as existsSync21 } from "fs";
|
|
14687
14881
|
import { resolve as resolve3 } from "path";
|
|
14688
14882
|
var envCandidates = [
|
|
14689
14883
|
resolve3(process.env.HOME ?? "", ".synkro", "config.env")
|
|
14690
14884
|
];
|
|
14691
14885
|
for (const envPath of envCandidates) {
|
|
14692
|
-
if (!
|
|
14693
|
-
const envContent =
|
|
14886
|
+
if (!existsSync21(envPath)) continue;
|
|
14887
|
+
const envContent = readFileSync18(envPath, "utf-8");
|
|
14694
14888
|
for (const line of envContent.split("\n")) {
|
|
14695
14889
|
const trimmed = line.trim();
|
|
14696
14890
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -14705,7 +14899,7 @@ var args = process.argv.slice(2);
|
|
|
14705
14899
|
var cmd = args[0] || "";
|
|
14706
14900
|
var subArgs = args.slice(1);
|
|
14707
14901
|
function printVersion() {
|
|
14708
|
-
console.log("1.6.
|
|
14902
|
+
console.log("1.6.86");
|
|
14709
14903
|
}
|
|
14710
14904
|
function printHelp2() {
|
|
14711
14905
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|
|
@@ -14784,6 +14978,11 @@ async function main() {
|
|
|
14784
14978
|
await importCommand2();
|
|
14785
14979
|
break;
|
|
14786
14980
|
}
|
|
14981
|
+
case "sync": {
|
|
14982
|
+
const { syncCommand: syncCommand2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
14983
|
+
await syncCommand2(subArgs);
|
|
14984
|
+
break;
|
|
14985
|
+
}
|
|
14787
14986
|
case "version":
|
|
14788
14987
|
case "--version":
|
|
14789
14988
|
case "-v": {
|