vet-skills 0.1.0 → 0.2.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 +52 -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.2.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);
|
|
@@ -8087,6 +8097,7 @@ var USAGE = `vet-skills ${VERSION} \u2014 check agent skills and MCP servers bef
|
|
|
8087
8097
|
vet-skills check <skill> dry-run the decision
|
|
8088
8098
|
vet-skills install <skill> check, then \`npx skills add\`
|
|
8089
8099
|
vet-skills detonate <path> [--publisher h] upload a local bundle to the sandbox and print its verdict
|
|
8100
|
+
vet-skills publish <path> detonate and publish to your organization's private registry
|
|
8090
8101
|
vet-skills why <skill> last decision and the rule behind it
|
|
8091
8102
|
vet-skills join <org> --token <t> [--email you@co] enroll this machine in an org
|
|
8092
8103
|
vet-skills leave back to the personal policy
|
|
@@ -8184,7 +8195,7 @@ async function main() {
|
|
|
8184
8195
|
}
|
|
8185
8196
|
process.stderr.write(`${c.muted("Detonating")} ${basename(dir)} ${c.muted(`(${(tar.stdout.length / 1024).toFixed(0)} KB)`)} \u2026
|
|
8186
8197
|
`);
|
|
8187
|
-
const res = await detonate(new Uint8Array(tar.stdout), { publisher: values.publisher
|
|
8198
|
+
const res = await detonate(new Uint8Array(tar.stdout), { publisher: values.publisher });
|
|
8188
8199
|
if (!res.ok) {
|
|
8189
8200
|
console.log(`${c.caution("!")} Detonation failed (${res.status ?? "offline"}): ${res.error}`);
|
|
8190
8201
|
return 1;
|
|
@@ -8205,6 +8216,42 @@ async function main() {
|
|
|
8205
8216
|
console.log(c.muted(`${API}${d.skill.page}`));
|
|
8206
8217
|
return d.verdict.label === "blocked" ? 2 : 0;
|
|
8207
8218
|
}
|
|
8219
|
+
case "publish": {
|
|
8220
|
+
if (!rest[0]) {
|
|
8221
|
+
usage();
|
|
8222
|
+
return 1;
|
|
8223
|
+
}
|
|
8224
|
+
const org = readOrg();
|
|
8225
|
+
if (!org) {
|
|
8226
|
+
console.log(`${c.blocked("\u2717")} not enrolled in an organization. Run ${c.bold("vet-skills join <org> --token \u2026")} first.`);
|
|
8227
|
+
return 1;
|
|
8228
|
+
}
|
|
8229
|
+
const dir = resolve(rest[0]);
|
|
8230
|
+
if (!existsSync3(join3(dir, "SKILL.md"))) {
|
|
8231
|
+
console.log(`${c.blocked("\u2717")} ${dir} has no SKILL.md (name and version come from its frontmatter)`);
|
|
8232
|
+
return 1;
|
|
8233
|
+
}
|
|
8234
|
+
const tar = spawnSync("tar", ["--exclude", "node_modules", "--exclude", ".git", "-czf", "-", "-C", dirname(dir), basename(dir)], { maxBuffer: 64 * 1024 * 1024 });
|
|
8235
|
+
if (tar.status !== 0) {
|
|
8236
|
+
console.log(`${c.blocked("\u2717")} tar failed: ${tar.stderr.toString().trim()}`);
|
|
8237
|
+
return 1;
|
|
8238
|
+
}
|
|
8239
|
+
process.stderr.write(`${c.muted("Publishing")} ${basename(dir)} ${c.muted(`to ${org.slug} (${(tar.stdout.length / 1024).toFixed(0)} KB) \u2014 detonating first \u2026`)}
|
|
8240
|
+
`);
|
|
8241
|
+
const res = await publish(new Uint8Array(tar.stdout));
|
|
8242
|
+
if (!res.ok) {
|
|
8243
|
+
console.log(`${c.caution("!")} Publish failed (${res.status ?? "offline"}): ${res.error}`);
|
|
8244
|
+
return 1;
|
|
8245
|
+
}
|
|
8246
|
+
const d = res.data;
|
|
8247
|
+
const paint = d.verdict.label === "verified" ? c.verified : d.verdict.label === "caution" ? c.caution : c.blocked;
|
|
8248
|
+
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`)}`);
|
|
8249
|
+
for (const f of d.findings.slice(0, 6)) console.log(` ${c.muted(f.rule_id)} ${f.severity.padEnd(8)} ${f.title}`);
|
|
8250
|
+
if (d.findings.length > 6) console.log(c.muted(` \u2026 ${d.findings.length - 6} more`));
|
|
8251
|
+
console.log(c.muted(`${API}/app/${org.slug}/registry \xB7 ${API}${d.skill.page}`));
|
|
8252
|
+
console.log(c.muted(`Teammates install it with: vet-skills install ${org.slug}/${d.skill.name}`));
|
|
8253
|
+
return d.verdict.label === "blocked" ? 2 : 0;
|
|
8254
|
+
}
|
|
8208
8255
|
case "why": {
|
|
8209
8256
|
const log = readLog().filter((e) => !rest[0] || e.skill === rest[0] || `${e.skill}@${e.semver}` === rest[0]);
|
|
8210
8257
|
const last = log.at(-1);
|