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 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
- npx skills add killerskills/vet-skills # soon: the meta-skill
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.1.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({ registry: opts.registry ?? "private" });
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
- const res = await resolveSkill(q);
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, registry: values.registry });
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vet-skills",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Vet agent skills and MCP servers before they run. Verdicts from KillerSkills, policy from your org.",
5
5
  "type": "module",
6
6
  "bin": {