@synkro-sh/cli 1.7.8 → 1.7.18
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 +155 -15
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -11311,6 +11311,7 @@ var init_setupGithub = __esm({
|
|
|
11311
11311
|
var install_exports = {};
|
|
11312
11312
|
__export(install_exports, {
|
|
11313
11313
|
detectGitRepo: () => detectGitRepo2,
|
|
11314
|
+
discoverAndIngestSkills: () => discoverAndIngestSkills,
|
|
11314
11315
|
getOrMintCloudToken: () => getOrMintCloudToken,
|
|
11315
11316
|
installCommand: () => installCommand,
|
|
11316
11317
|
parseArgs: () => parseArgs,
|
|
@@ -11326,6 +11327,7 @@ import { homedir as homedir10 } from "os";
|
|
|
11326
11327
|
import { join as join10 } from "path";
|
|
11327
11328
|
import { execSync as execSync6 } from "child_process";
|
|
11328
11329
|
import { createInterface as createInterface3 } from "readline";
|
|
11330
|
+
import { createHash } from "crypto";
|
|
11329
11331
|
function resolvePersistedHookMode() {
|
|
11330
11332
|
if (process.env.SYNKRO_HOOK_MODE === "full") return "full";
|
|
11331
11333
|
if (process.env.SYNKRO_HOOK_MODE === "stub") return "stub";
|
|
@@ -11643,7 +11645,7 @@ function writeConfigEnv(opts) {
|
|
|
11643
11645
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11644
11646
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11645
11647
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11646
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.7.
|
|
11648
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.7.18")}`
|
|
11647
11649
|
];
|
|
11648
11650
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11649
11651
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -12537,6 +12539,7 @@ async function installCommand(opts = {}) {
|
|
|
12537
12539
|
if (workersUp) {
|
|
12538
12540
|
console.log(" \u2713 workers ready");
|
|
12539
12541
|
await syncSkillFiles();
|
|
12542
|
+
await discoverAndIngestSkills();
|
|
12540
12543
|
} else {
|
|
12541
12544
|
console.warn(" \u26A0 workers did not register within 30s \u2014 skill sync skipped");
|
|
12542
12545
|
}
|
|
@@ -12859,6 +12862,34 @@ function reconcileHarness() {
|
|
|
12859
12862
|
if (providers.length === 0) providers.push("claude_code");
|
|
12860
12863
|
return splitWorkers(total, providers);
|
|
12861
12864
|
}
|
|
12865
|
+
async function ingestSkillTasks(tasks, mcpPort) {
|
|
12866
|
+
const summary = { ingested: 0, rules: 0, rejected: [] };
|
|
12867
|
+
for (const { source, content } of tasks) {
|
|
12868
|
+
try {
|
|
12869
|
+
const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/local/skills/sync`, {
|
|
12870
|
+
method: "POST",
|
|
12871
|
+
headers: { "Content-Type": "application/json" },
|
|
12872
|
+
body: JSON.stringify({ source, content }),
|
|
12873
|
+
signal: AbortSignal.timeout(65e3)
|
|
12874
|
+
});
|
|
12875
|
+
if (!resp.ok) throw new Error(`${resp.status}`);
|
|
12876
|
+
const r = await resp.json();
|
|
12877
|
+
if (r.status === "rejected") {
|
|
12878
|
+
const reason = (r.reasons || []).slice(0, 2).join("; ") || r.message || "blocked by security vetting";
|
|
12879
|
+
summary.rejected.push({ source, reason });
|
|
12880
|
+
console.log(` \u26D4 skill ${source}: BLOCKED \u2014 ${reason}`);
|
|
12881
|
+
} else {
|
|
12882
|
+
const created = r.created || 0;
|
|
12883
|
+
summary.ingested++;
|
|
12884
|
+
summary.rules += created;
|
|
12885
|
+
console.log(created > 0 ? ` \u2713 skill ${source}: ${created} rule${created === 1 ? "" : "s"} \u2192 its own pack` : ` \u2713 skill ${source}: ${r.message || "up to date"}`);
|
|
12886
|
+
}
|
|
12887
|
+
} catch (e) {
|
|
12888
|
+
console.warn(` \u26A0 skill ${source}: sync failed (${e instanceof Error ? e.message : String(e)})`);
|
|
12889
|
+
}
|
|
12890
|
+
}
|
|
12891
|
+
return summary;
|
|
12892
|
+
}
|
|
12862
12893
|
async function syncSkillFiles() {
|
|
12863
12894
|
const sf = readFullSynkroFile();
|
|
12864
12895
|
if (!sf || sf.skills.length === 0) return;
|
|
@@ -12878,20 +12909,128 @@ async function syncSkillFiles() {
|
|
|
12878
12909
|
}).filter(Boolean);
|
|
12879
12910
|
if (tasks.length === 0) return;
|
|
12880
12911
|
console.log(` indexing ${tasks.length} skill file${tasks.length === 1 ? "" : "s"} \u2014 comparing against existing ruleset (this may take a few seconds)...`);
|
|
12881
|
-
|
|
12912
|
+
await ingestSkillTasks(tasks, mcpPort);
|
|
12913
|
+
}
|
|
12914
|
+
function discoverSkillFiles(repoRoot, excludeHashes) {
|
|
12915
|
+
const roots = [];
|
|
12916
|
+
const add = (p) => {
|
|
12882
12917
|
try {
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12918
|
+
if (p && existsSync11(p) && statSync(p).isDirectory()) roots.push(p);
|
|
12919
|
+
} catch {
|
|
12920
|
+
}
|
|
12921
|
+
};
|
|
12922
|
+
add(join10(homedir10(), ".claude", "skills"));
|
|
12923
|
+
add(join10(homedir10(), ".agents", "skills"));
|
|
12924
|
+
if (repoRoot) {
|
|
12925
|
+
add(join10(repoRoot, ".claude", "skills"));
|
|
12926
|
+
add(join10(repoRoot, ".agents", "skills"));
|
|
12927
|
+
}
|
|
12928
|
+
const out = [];
|
|
12929
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12930
|
+
const MAX = 200;
|
|
12931
|
+
const consider = (file, name) => {
|
|
12932
|
+
if (out.length >= MAX) return;
|
|
12933
|
+
let content = "";
|
|
12934
|
+
try {
|
|
12935
|
+
const st = statSync(file);
|
|
12936
|
+
if (!st.isFile() || st.size > 2e5) return;
|
|
12937
|
+
content = readFileSync10(file, "utf-8");
|
|
12938
|
+
} catch {
|
|
12939
|
+
return;
|
|
12940
|
+
}
|
|
12941
|
+
if (!content.trim()) return;
|
|
12942
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
12943
|
+
if (seen.has(hash) || excludeHashes.has(hash)) return;
|
|
12944
|
+
seen.add(hash);
|
|
12945
|
+
out.push({ source: `skill:${name}`, content, name, hash });
|
|
12946
|
+
};
|
|
12947
|
+
for (const root of roots) {
|
|
12948
|
+
let entries = [];
|
|
12949
|
+
try {
|
|
12950
|
+
entries = readdirSync3(root);
|
|
12951
|
+
} catch {
|
|
12952
|
+
continue;
|
|
12953
|
+
}
|
|
12954
|
+
for (const entry of entries) {
|
|
12955
|
+
if (entry.startsWith(".")) continue;
|
|
12956
|
+
const full = join10(root, entry);
|
|
12957
|
+
let st;
|
|
12958
|
+
try {
|
|
12959
|
+
st = statSync(full);
|
|
12960
|
+
} catch {
|
|
12961
|
+
continue;
|
|
12962
|
+
}
|
|
12963
|
+
if (st.isDirectory()) {
|
|
12964
|
+
const skillMd = join10(full, "SKILL.md");
|
|
12965
|
+
if (existsSync11(skillMd)) consider(skillMd, entry);
|
|
12966
|
+
} else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
|
|
12967
|
+
consider(full, entry.replace(/\.mdx?$/i, ""));
|
|
12968
|
+
}
|
|
12969
|
+
}
|
|
12970
|
+
}
|
|
12971
|
+
return out;
|
|
12972
|
+
}
|
|
12973
|
+
function discoverySetHash(found) {
|
|
12974
|
+
return createHash("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
|
|
12975
|
+
}
|
|
12976
|
+
async function promptSkillDiscovery(found) {
|
|
12977
|
+
if (!process.stdin.isTTY || found.length === 0) return [];
|
|
12978
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
12979
|
+
const ask3 = (q) => new Promise((res) => rl.question(q, (a) => res(a.trim())));
|
|
12980
|
+
try {
|
|
12981
|
+
console.log(`
|
|
12982
|
+
Found ${found.length} skill${found.length === 1 ? "" : "s"} in your Claude Code / skills.sh folders:`);
|
|
12983
|
+
found.forEach((s, i) => console.log(` ${i + 1}. ${s.name}`));
|
|
12984
|
+
console.log("Ingest into Synkro as enforceable rules? Each is security-vetted first \u2014 anything");
|
|
12985
|
+
console.log("malicious (prompt injection, guardrail-weakening, secret exfil) is blocked and reported.");
|
|
12986
|
+
const a = (await ask3("Ingest [all] / type numbers (e.g. 1,3) / [skip]: ")).toLowerCase();
|
|
12987
|
+
if (a === "skip" || a === "n" || a === "no" || a === "s") return [];
|
|
12988
|
+
if (a === "" || a === "all" || a === "y" || a === "yes" || a === "a") return found.map((_, i) => i);
|
|
12989
|
+
const picks = a.split(/[\s,]+/).map((x) => parseInt(x, 10) - 1).filter((i) => Number.isInteger(i) && i >= 0 && i < found.length);
|
|
12990
|
+
return [...new Set(picks)];
|
|
12991
|
+
} finally {
|
|
12992
|
+
rl.close();
|
|
12993
|
+
}
|
|
12994
|
+
}
|
|
12995
|
+
async function discoverAndIngestSkills() {
|
|
12996
|
+
try {
|
|
12997
|
+
const sf = readFullSynkroFile();
|
|
12998
|
+
const repoRoot = sf?._repoRoot || detectGitRepo2();
|
|
12999
|
+
const excludeHashes = /* @__PURE__ */ new Set();
|
|
13000
|
+
if (sf?.skills?.length) {
|
|
13001
|
+
for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
|
|
13002
|
+
try {
|
|
13003
|
+
excludeHashes.add(createHash("sha256").update(readFileSync10(fp, "utf-8")).digest("hex"));
|
|
13004
|
+
} catch {
|
|
13005
|
+
}
|
|
13006
|
+
}
|
|
12894
13007
|
}
|
|
13008
|
+
const found = discoverSkillFiles(repoRoot, excludeHashes);
|
|
13009
|
+
if (found.length === 0) return;
|
|
13010
|
+
const setHash = discoverySetHash(found);
|
|
13011
|
+
let prev = "";
|
|
13012
|
+
try {
|
|
13013
|
+
prev = readFileSync10(SKILLS_DISCOVERED_PATH, "utf-8").trim();
|
|
13014
|
+
} catch {
|
|
13015
|
+
}
|
|
13016
|
+
if (prev === setHash) return;
|
|
13017
|
+
const picks = await promptSkillDiscovery(found);
|
|
13018
|
+
try {
|
|
13019
|
+
writeFileSync8(SKILLS_DISCOVERED_PATH, setHash);
|
|
13020
|
+
} catch {
|
|
13021
|
+
}
|
|
13022
|
+
if (picks.length === 0) {
|
|
13023
|
+
console.log(" Skipped \u2014 you can ingest skills anytime from the Synkro dashboard or via the MCP tool.");
|
|
13024
|
+
return;
|
|
13025
|
+
}
|
|
13026
|
+
const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
|
|
13027
|
+
console.log(` Ingesting ${picks.length} skill${picks.length === 1 ? "" : "s"} (security-vetting each first)\u2026`);
|
|
13028
|
+
const summary = await ingestSkillTasks(picks.map((i) => ({ source: found[i].source, content: found[i].content })), mcpPort);
|
|
13029
|
+
const parts = [`${summary.ingested} ingested (${summary.rules} rule${summary.rules === 1 ? "" : "s"})`];
|
|
13030
|
+
if (summary.rejected.length) parts.push(`${summary.rejected.length} blocked`);
|
|
13031
|
+
console.log(` Skills: ${parts.join(", ")} \u2014 each in its own toggleable pack.`);
|
|
13032
|
+
} catch (e) {
|
|
13033
|
+
console.warn(` \u26A0 skill discovery skipped (${e instanceof Error ? e.message : String(e)})`);
|
|
12895
13034
|
}
|
|
12896
13035
|
}
|
|
12897
13036
|
function detectGitRepo2() {
|
|
@@ -13205,7 +13344,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
13205
13344
|
}
|
|
13206
13345
|
return { sessions: totalSessions, messages: totalMessages };
|
|
13207
13346
|
}
|
|
13208
|
-
var SYNKRO_DIR5, HOOKS_DIR, BIN_DIR, CONFIG_PATH2, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH;
|
|
13347
|
+
var SYNKRO_DIR5, HOOKS_DIR, BIN_DIR, CONFIG_PATH2, MCP_STDIO_PROXY_SRC, OFFSETS_DIR, CLOUD_JWT_PATH, SKILLS_DISCOVERED_PATH;
|
|
13209
13348
|
var init_install = __esm({
|
|
13210
13349
|
"cli/commands/install.ts"() {
|
|
13211
13350
|
"use strict";
|
|
@@ -13340,6 +13479,7 @@ rl.on('line', async (line) => {
|
|
|
13340
13479
|
`;
|
|
13341
13480
|
OFFSETS_DIR = join10(SYNKRO_DIR5, ".transcript-offsets");
|
|
13342
13481
|
CLOUD_JWT_PATH = join10(SYNKRO_DIR5, ".cloud-jwt");
|
|
13482
|
+
SKILLS_DISCOVERED_PATH = join10(SYNKRO_DIR5, ".skills-discovered");
|
|
13343
13483
|
}
|
|
13344
13484
|
});
|
|
13345
13485
|
|
|
@@ -15706,7 +15846,7 @@ var args = process.argv.slice(2);
|
|
|
15706
15846
|
var cmd = args[0] || "";
|
|
15707
15847
|
var subArgs = args.slice(1);
|
|
15708
15848
|
function printVersion() {
|
|
15709
|
-
console.log("1.7.
|
|
15849
|
+
console.log("1.7.18");
|
|
15710
15850
|
}
|
|
15711
15851
|
function printHelp2() {
|
|
15712
15852
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|