vet-skills 0.1.0 → 0.3.0
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/README.md +3 -2
- package/dist/index.js +79 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,9 +4,10 @@ Check agent skills and MCP servers before they run. Verdicts come from
|
|
|
4
4
|
[KillerSkills](https://killerskills.ai); the policy is yours or your org's.
|
|
5
5
|
|
|
6
6
|
```
|
|
7
|
-
|
|
8
|
-
npm i -g vet-skills # the CLI
|
|
7
|
+
npm i -g vet-skills # the CLI (Node 20+)
|
|
9
8
|
vet-skills hook install claude-code # intercept every install in Claude Code
|
|
9
|
+
vet-skills check <skill> # or just ask before you add one
|
|
10
|
+
vet-skills publish ./my-skill # org members: detonate + list in the private registry
|
|
10
11
|
```
|
|
11
12
|
|
|
12
13
|
## What it does
|
package/dist/index.js
CHANGED
|
@@ -7629,7 +7629,7 @@ import { mkdirSync, readFileSync, writeFileSync, existsSync, appendFileSync, chm
|
|
|
7629
7629
|
import { homedir } from "node:os";
|
|
7630
7630
|
import { join } from "node:path";
|
|
7631
7631
|
import { randomUUID } from "node:crypto";
|
|
7632
|
-
var VERSION = "0.
|
|
7632
|
+
var VERSION = "0.3.0";
|
|
7633
7633
|
var HOME = process.env.VET_SKILLS_HOME ?? join(homedir(), ".vet-skills");
|
|
7634
7634
|
var API = (process.env.KILLERSKILLS_API ?? "https://killerskills.ai").replace(/\/$/, "");
|
|
7635
7635
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -7759,7 +7759,7 @@ async function call(path, init = {}, timeoutMs = 8e3) {
|
|
|
7759
7759
|
function resolveSkill(q, registry) {
|
|
7760
7760
|
const qs = new URLSearchParams({ q });
|
|
7761
7761
|
if (registry) qs.set("registry", registry);
|
|
7762
|
-
return call(`/api/v1/resolve?${qs}
|
|
7762
|
+
return call(`/api/v1/resolve?${qs}`, { headers: orgHeaders() });
|
|
7763
7763
|
}
|
|
7764
7764
|
function orgHeaders() {
|
|
7765
7765
|
const token = readToken();
|
|
@@ -7778,8 +7778,13 @@ function report(body) {
|
|
|
7778
7778
|
if (!org) return Promise.resolve({ ok: false, error: "not joined", offline: false });
|
|
7779
7779
|
return call(`/api/v1/orgs/${org.slug}/agents/${org.agentId}/report`, { method: "POST", body: JSON.stringify(body), headers: orgHeaders() }, 8e3);
|
|
7780
7780
|
}
|
|
7781
|
+
function publish(tgz) {
|
|
7782
|
+
const org = readOrg();
|
|
7783
|
+
if (!org) return Promise.resolve({ ok: false, status: 0, error: "not enrolled in an organization (vet-skills join <org> --token \u2026)", offline: false });
|
|
7784
|
+
return call(`/api/v1/orgs/${org.slug}/agents/${org.agentId}/publish`, { method: "POST", body: new Blob([tgz]), headers: { "content-type": "application/gzip", ...orgHeaders() } }, 18e4);
|
|
7785
|
+
}
|
|
7781
7786
|
function detonate(tgz, opts = {}) {
|
|
7782
|
-
const q = new URLSearchParams(
|
|
7787
|
+
const q = new URLSearchParams();
|
|
7783
7788
|
return call(`/api/v1/detonate?${q}`, { method: "POST", body: new Blob([tgz]), headers: { "content-type": "application/gzip", ...opts.publisher ? { "x-vs-publisher": opts.publisher } : {} } }, 18e4);
|
|
7784
7789
|
}
|
|
7785
7790
|
|
|
@@ -7849,7 +7854,12 @@ async function check(q, opts = {}) {
|
|
|
7849
7854
|
let source = "api";
|
|
7850
7855
|
let offline = false;
|
|
7851
7856
|
let cacheAge;
|
|
7852
|
-
|
|
7857
|
+
let res = await resolveSkill(q);
|
|
7858
|
+
if (!res.ok && !res.offline && org) {
|
|
7859
|
+
const bare = q.replace(/^[^/]+\//, "");
|
|
7860
|
+
const priv = await resolveSkill(`${org.slug}/${bare}`, "private");
|
|
7861
|
+
if (priv.ok) res = priv;
|
|
7862
|
+
}
|
|
7853
7863
|
if (res.ok) {
|
|
7854
7864
|
resolved = res.data;
|
|
7855
7865
|
const cached = cacheGet(resolved.version.artifact_hash);
|
|
@@ -7985,6 +7995,27 @@ var INSTALL_PATTERNS = [
|
|
|
7985
7995
|
{ re: /claude\s+mcp\s+add(?:-json)?\s+(\S+)/, kind: "mcp" },
|
|
7986
7996
|
{ re: /vet-skills\s+install\s+(\S+)/, kind: "skill" }
|
|
7987
7997
|
];
|
|
7998
|
+
function extractRemoteMcp(command) {
|
|
7999
|
+
const m = command.match(/claude\s+mcp\s+add(?:-json)?\s+(.*)$/s);
|
|
8000
|
+
if (!m) return null;
|
|
8001
|
+
const args = (m[1].match(/"[^"]*"|'[^']*'|\S+/g) ?? []).map((a) => a.replace(/^(['"])(.*)\1$/, "$2"));
|
|
8002
|
+
const takesValue = /^(--transport|-t|--scope|-s|--header|-H|--env|-e)$/;
|
|
8003
|
+
const positional = [];
|
|
8004
|
+
for (let i = 0; i < args.length; i++) {
|
|
8005
|
+
const a = args[i];
|
|
8006
|
+
if (a === "--") break;
|
|
8007
|
+
if (takesValue.test(a)) {
|
|
8008
|
+
i += 1;
|
|
8009
|
+
continue;
|
|
8010
|
+
}
|
|
8011
|
+
if (a.startsWith("-")) continue;
|
|
8012
|
+
positional.push(a);
|
|
8013
|
+
}
|
|
8014
|
+
const url = positional.find((a) => /^https?:\/\//.test(a));
|
|
8015
|
+
if (!url) return null;
|
|
8016
|
+
const name = positional.find((a) => a !== url) ?? new URL(url).hostname;
|
|
8017
|
+
return { name, url };
|
|
8018
|
+
}
|
|
7988
8019
|
function extractInstall(command) {
|
|
7989
8020
|
for (const p of INSTALL_PATTERNS) {
|
|
7990
8021
|
const m = command.match(p.re);
|
|
@@ -8006,12 +8037,18 @@ async function hookEval(stdin) {
|
|
|
8006
8037
|
if (!found) return 0;
|
|
8007
8038
|
const r = await check(found.q, { quiet: true, agentKind: "claude_code" });
|
|
8008
8039
|
const out = renderOutcome(r);
|
|
8040
|
+
const remote = found.kind === "mcp" ? extractRemoteMcp(cmd2) : null;
|
|
8041
|
+
const watch = async () => {
|
|
8042
|
+
if (remote && readOrg()) await report({ installed: [], removed: [], mcp_servers: [remote] }).catch(() => null);
|
|
8043
|
+
};
|
|
8009
8044
|
switch (r.decision.decision) {
|
|
8010
8045
|
case "allow":
|
|
8011
8046
|
case "observe":
|
|
8047
|
+
await watch();
|
|
8012
8048
|
process.stderr.write(out + "\n");
|
|
8013
8049
|
return 0;
|
|
8014
8050
|
case "sandbox":
|
|
8051
|
+
await watch();
|
|
8015
8052
|
process.stderr.write(out + "\n");
|
|
8016
8053
|
return 0;
|
|
8017
8054
|
case "approval_required":
|
|
@@ -8087,6 +8124,7 @@ var USAGE = `vet-skills ${VERSION} \u2014 check agent skills and MCP servers bef
|
|
|
8087
8124
|
vet-skills check <skill> dry-run the decision
|
|
8088
8125
|
vet-skills install <skill> check, then \`npx skills add\`
|
|
8089
8126
|
vet-skills detonate <path> [--publisher h] upload a local bundle to the sandbox and print its verdict
|
|
8127
|
+
vet-skills publish <path> detonate and publish to your organization's private registry
|
|
8090
8128
|
vet-skills why <skill> last decision and the rule behind it
|
|
8091
8129
|
vet-skills join <org> --token <t> [--email you@co] enroll this machine in an org
|
|
8092
8130
|
vet-skills leave back to the personal policy
|
|
@@ -8184,7 +8222,7 @@ async function main() {
|
|
|
8184
8222
|
}
|
|
8185
8223
|
process.stderr.write(`${c.muted("Detonating")} ${basename(dir)} ${c.muted(`(${(tar.stdout.length / 1024).toFixed(0)} KB)`)} \u2026
|
|
8186
8224
|
`);
|
|
8187
|
-
const res = await detonate(new Uint8Array(tar.stdout), { publisher: values.publisher
|
|
8225
|
+
const res = await detonate(new Uint8Array(tar.stdout), { publisher: values.publisher });
|
|
8188
8226
|
if (!res.ok) {
|
|
8189
8227
|
console.log(`${c.caution("!")} Detonation failed (${res.status ?? "offline"}): ${res.error}`);
|
|
8190
8228
|
return 1;
|
|
@@ -8205,6 +8243,42 @@ async function main() {
|
|
|
8205
8243
|
console.log(c.muted(`${API}${d.skill.page}`));
|
|
8206
8244
|
return d.verdict.label === "blocked" ? 2 : 0;
|
|
8207
8245
|
}
|
|
8246
|
+
case "publish": {
|
|
8247
|
+
if (!rest[0]) {
|
|
8248
|
+
usage();
|
|
8249
|
+
return 1;
|
|
8250
|
+
}
|
|
8251
|
+
const org = readOrg();
|
|
8252
|
+
if (!org) {
|
|
8253
|
+
console.log(`${c.blocked("\u2717")} not enrolled in an organization. Run ${c.bold("vet-skills join <org> --token \u2026")} first.`);
|
|
8254
|
+
return 1;
|
|
8255
|
+
}
|
|
8256
|
+
const dir = resolve(rest[0]);
|
|
8257
|
+
if (!existsSync3(join3(dir, "SKILL.md"))) {
|
|
8258
|
+
console.log(`${c.blocked("\u2717")} ${dir} has no SKILL.md (name and version come from its frontmatter)`);
|
|
8259
|
+
return 1;
|
|
8260
|
+
}
|
|
8261
|
+
const tar = spawnSync("tar", ["--exclude", "node_modules", "--exclude", ".git", "-czf", "-", "-C", dirname(dir), basename(dir)], { maxBuffer: 64 * 1024 * 1024 });
|
|
8262
|
+
if (tar.status !== 0) {
|
|
8263
|
+
console.log(`${c.blocked("\u2717")} tar failed: ${tar.stderr.toString().trim()}`);
|
|
8264
|
+
return 1;
|
|
8265
|
+
}
|
|
8266
|
+
process.stderr.write(`${c.muted("Publishing")} ${basename(dir)} ${c.muted(`to ${org.slug} (${(tar.stdout.length / 1024).toFixed(0)} KB) \u2014 detonating first \u2026`)}
|
|
8267
|
+
`);
|
|
8268
|
+
const res = await publish(new Uint8Array(tar.stdout));
|
|
8269
|
+
if (!res.ok) {
|
|
8270
|
+
console.log(`${c.caution("!")} Publish failed (${res.status ?? "offline"}): ${res.error}`);
|
|
8271
|
+
return 1;
|
|
8272
|
+
}
|
|
8273
|
+
const d = res.data;
|
|
8274
|
+
const paint = d.verdict.label === "verified" ? c.verified : d.verdict.label === "caution" ? c.caution : c.blocked;
|
|
8275
|
+
console.log(`${paint(d.verdict.label.toUpperCase())} ${c.bold(String(d.verdict.score))} ${c.muted(`\xB7 ${d.skill.publisher}/${d.skill.name}@${d.skill.semver} \xB7 ${d.skill.created ? "published" : "re-detonated (same bytes already published)"} \xB7 ${d.verdict.duration_ms} ms`)}`);
|
|
8276
|
+
for (const f of d.findings.slice(0, 6)) console.log(` ${c.muted(f.rule_id)} ${f.severity.padEnd(8)} ${f.title}`);
|
|
8277
|
+
if (d.findings.length > 6) console.log(c.muted(` \u2026 ${d.findings.length - 6} more`));
|
|
8278
|
+
console.log(c.muted(`${API}/app/${org.slug}/registry \xB7 ${API}${d.skill.page}`));
|
|
8279
|
+
console.log(c.muted(`Teammates install it with: vet-skills install ${org.slug}/${d.skill.name}`));
|
|
8280
|
+
return d.verdict.label === "blocked" ? 2 : 0;
|
|
8281
|
+
}
|
|
8208
8282
|
case "why": {
|
|
8209
8283
|
const log = readLog().filter((e) => !rest[0] || e.skill === rest[0] || `${e.skill}@${e.semver}` === rest[0]);
|
|
8210
8284
|
const last = log.at(-1);
|