vet-skills 0.4.0 → 0.5.1
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/index.js +92 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7616,7 +7616,7 @@ import { parseArgs } from "node:util";
|
|
|
7616
7616
|
import { spawnSync } from "node:child_process";
|
|
7617
7617
|
import { readFileSync as readFileSync3, existsSync as existsSync4 } from "node:fs";
|
|
7618
7618
|
import { resolve, join as join4, dirname, basename } from "node:path";
|
|
7619
|
-
import { hostname, platform, userInfo } from "node:os";
|
|
7619
|
+
import { hostname, platform as platform2, userInfo } from "node:os";
|
|
7620
7620
|
import { createHash } from "node:crypto";
|
|
7621
7621
|
import { createInterface } from "node:readline";
|
|
7622
7622
|
|
|
@@ -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.5.1";
|
|
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;
|
|
@@ -7735,6 +7735,33 @@ function stateSummary() {
|
|
|
7735
7735
|
const sz = (f) => existsSync(join(HOME, f)) ? statSync(join(HOME, f)).size : 0;
|
|
7736
7736
|
return { home: HOME, cacheEntries: cacheSize(), cacheBytes: sz("cache.json"), logBytes: sz("log.jsonl") };
|
|
7737
7737
|
}
|
|
7738
|
+
function telemetryEnabled() {
|
|
7739
|
+
const env = (process.env.VET_SKILLS_TELEMETRY ?? "").toLowerCase();
|
|
7740
|
+
if (["0", "false", "off", "no"].includes(env)) return false;
|
|
7741
|
+
if (process.env.DO_NOT_TRACK === "1") return false;
|
|
7742
|
+
const p = join(HOME, "telemetry");
|
|
7743
|
+
if (existsSync(p)) return readFileSync(p, "utf8").trim() !== "off";
|
|
7744
|
+
return true;
|
|
7745
|
+
}
|
|
7746
|
+
function setTelemetry(on2) {
|
|
7747
|
+
ensureHome();
|
|
7748
|
+
writeFileSync(join(HOME, "telemetry"), on2 ? "on\n" : "off\n", { mode: 384 });
|
|
7749
|
+
}
|
|
7750
|
+
function telemetryNoticePending() {
|
|
7751
|
+
ensureHome();
|
|
7752
|
+
const p = join(HOME, "telemetry-notice");
|
|
7753
|
+
if (existsSync(p)) return false;
|
|
7754
|
+
writeFileSync(p, (/* @__PURE__ */ new Date()).toISOString() + "\n", { mode: 384 });
|
|
7755
|
+
return true;
|
|
7756
|
+
}
|
|
7757
|
+
function hintOncePerDay(key) {
|
|
7758
|
+
ensureHome();
|
|
7759
|
+
const p = join(HOME, `hint-${key.replace(/[^\w-]/g, "_")}`);
|
|
7760
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7761
|
+
if (existsSync(p) && readFileSync(p, "utf8").trim() === today) return false;
|
|
7762
|
+
writeFileSync(p, today + "\n", { mode: 384 });
|
|
7763
|
+
return true;
|
|
7764
|
+
}
|
|
7738
7765
|
|
|
7739
7766
|
// src/api.ts
|
|
7740
7767
|
async function call(path, init = {}, timeoutMs = 8e3) {
|
|
@@ -7792,6 +7819,12 @@ function detonate(tgz, opts = {}) {
|
|
|
7792
7819
|
const q = new URLSearchParams();
|
|
7793
7820
|
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);
|
|
7794
7821
|
}
|
|
7822
|
+
function sendTelemetry(body) {
|
|
7823
|
+
return call("/api/v1/telemetry", { method: "POST", body: JSON.stringify(body) }, 1500);
|
|
7824
|
+
}
|
|
7825
|
+
|
|
7826
|
+
// src/check.ts
|
|
7827
|
+
import { platform } from "node:os";
|
|
7795
7828
|
|
|
7796
7829
|
// src/render.ts
|
|
7797
7830
|
var on = !!process.stdout.isTTY && !process.env.NO_COLOR;
|
|
@@ -7859,7 +7892,7 @@ async function check(q, opts = {}) {
|
|
|
7859
7892
|
let source = "api";
|
|
7860
7893
|
let offline = false;
|
|
7861
7894
|
let cacheAge;
|
|
7862
|
-
let res = await resolveSkill(q);
|
|
7895
|
+
let res = await resolveSkill(q, opts.registry);
|
|
7863
7896
|
if (!res.ok && !res.offline && org) {
|
|
7864
7897
|
const bare = q.replace(/^[^/]+\//, "");
|
|
7865
7898
|
const priv = await resolveSkill(`${org.slug}/${bare}`, "private");
|
|
@@ -7908,6 +7941,10 @@ async function check(q, opts = {}) {
|
|
|
7908
7941
|
offline,
|
|
7909
7942
|
requestId: remote?.approvalRequestId
|
|
7910
7943
|
});
|
|
7944
|
+
if (!org && telemetryEnabled()) {
|
|
7945
|
+
if (telemetryNoticePending()) process.stderr.write(c.muted("vet-skills sends anonymous usage (skill name, version, decision, agent type; never code, prompts or paths). Opt out: vet-skills telemetry off") + "\n");
|
|
7946
|
+
await sendTelemetry({ skill: resolved?.skill.name ?? q, semver: resolved?.version.semver ?? null, registry: resolved?.skill.registry ?? null, decision: decision.decision, would_be: decision.wouldBe ?? null, agent_kind: opts.agentKind ?? "other", hook_version: VERSION, os: platform(), offline }).catch(() => null);
|
|
7947
|
+
}
|
|
7911
7948
|
return { resolved, decision, source, cacheAge, remote, offline, policyLabel };
|
|
7912
7949
|
}
|
|
7913
7950
|
function renderCheck(r) {
|
|
@@ -8071,10 +8108,41 @@ function extractRemoteMcp(command) {
|
|
|
8071
8108
|
const name = positional.find((a) => a !== url) ?? new URL(url).hostname;
|
|
8072
8109
|
return { name, url };
|
|
8073
8110
|
}
|
|
8111
|
+
function extractStdioPackage(command) {
|
|
8112
|
+
const tail = command.split(/\s--\s/)[1];
|
|
8113
|
+
if (!tail) return null;
|
|
8114
|
+
const args = (tail.match(/"[^"]*"|'[^']*'|\S+/g) ?? []).map((a) => a.replace(/^(['"])(.*)\1$/, "$2"));
|
|
8115
|
+
const [bin, ...rest2] = args;
|
|
8116
|
+
const strip = (s) => s.replace(/(.)@[^@/]+$/, "$1");
|
|
8117
|
+
if (/^(npx|bunx|pnpm)$/.test(bin ?? "")) {
|
|
8118
|
+
const pkg = rest2.find((a) => !a.startsWith("-") && a !== "dlx" && a !== "exec");
|
|
8119
|
+
return pkg ? { q: strip(pkg).replace(/^@/, ""), registry: "npm" } : null;
|
|
8120
|
+
}
|
|
8121
|
+
if (/^(uvx|pipx)$/.test(bin ?? "")) {
|
|
8122
|
+
const pkg = rest2.find((a) => !a.startsWith("-") && a !== "run");
|
|
8123
|
+
return pkg ? { q: strip(pkg).toLowerCase().replace(/\[.*\]$/, ""), registry: "pypi" } : null;
|
|
8124
|
+
}
|
|
8125
|
+
if (bin === "docker") {
|
|
8126
|
+
const img = rest2.find((a) => /^(ghcr\.io\/|docker\.io\/)?[\w.-]+\/[\w.-]+(:[\w.-]+)?$/.test(a) && !a.startsWith("-"));
|
|
8127
|
+
if (!img) return null;
|
|
8128
|
+
const m = img.replace(/:[\w.-]+$/, "").match(/mcp\/([\w.-]+)$/);
|
|
8129
|
+
return m ? { q: m[1], registry: "docker" } : null;
|
|
8130
|
+
}
|
|
8131
|
+
return null;
|
|
8132
|
+
}
|
|
8074
8133
|
function extractInstall(command) {
|
|
8075
8134
|
for (const p of INSTALL_PATTERNS) {
|
|
8076
8135
|
const m = command.match(p.re);
|
|
8077
|
-
if (m)
|
|
8136
|
+
if (!m) continue;
|
|
8137
|
+
const q = m[1].replace(/^['"]|['"]$/g, "");
|
|
8138
|
+
if (p.kind === "mcp") {
|
|
8139
|
+
const pkg = extractStdioPackage(command);
|
|
8140
|
+
if (pkg) return { q: pkg.q, kind: "mcp", registry: pkg.registry };
|
|
8141
|
+
const remote = extractRemoteMcp(command);
|
|
8142
|
+
if (remote) return { q: remote.name, kind: "mcp" };
|
|
8143
|
+
}
|
|
8144
|
+
if (p.kind === "plugin") return { q: q.split("@")[0], kind: "plugin", registry: "plugins" };
|
|
8145
|
+
return { q, kind: p.kind };
|
|
8078
8146
|
}
|
|
8079
8147
|
return null;
|
|
8080
8148
|
}
|
|
@@ -8090,7 +8158,7 @@ async function hookEval(stdin) {
|
|
|
8090
8158
|
if (/^\s*vet-skills\s+(check|why|status|sync|policy|detonate|hook|join|leave|block|publish)\b/.test(cmd2)) return 0;
|
|
8091
8159
|
const found = extractInstall(cmd2);
|
|
8092
8160
|
if (!found) return 0;
|
|
8093
|
-
const r = await check(found.q, { quiet: true, agentKind: "claude_code" });
|
|
8161
|
+
const r = await check(found.q, { quiet: true, agentKind: "claude_code", registry: found.registry });
|
|
8094
8162
|
const out = renderOutcome(r);
|
|
8095
8163
|
const remote = found.kind === "mcp" ? extractRemoteMcp(cmd2) : null;
|
|
8096
8164
|
const watch = async () => {
|
|
@@ -8122,10 +8190,11 @@ ${out}
|
|
|
8122
8190
|
`);
|
|
8123
8191
|
return 2;
|
|
8124
8192
|
case "block":
|
|
8125
|
-
process.stderr.write(`vet-skills blocked this install by org policy.
|
|
8193
|
+
process.stderr.write(`vet-skills blocked this install by ${readOrg() ? "org" : "your"} policy.
|
|
8126
8194
|
${renderCheck(r)}
|
|
8127
8195
|
${out}
|
|
8128
8196
|
`);
|
|
8197
|
+
if (!readOrg() && hintOncePerDay("teams")) process.stderr.write("Running this for a team? One policy across every developer's agents, approvals and inventory: https://killerskills.ai/pricing\n");
|
|
8129
8198
|
return 2;
|
|
8130
8199
|
}
|
|
8131
8200
|
return 0;
|
|
@@ -8180,6 +8249,11 @@ async function ask(question, choices) {
|
|
|
8180
8249
|
}
|
|
8181
8250
|
var USAGE = `vet-skills ${VERSION} \u2014 check agent skills and MCP servers before they run.
|
|
8182
8251
|
|
|
8252
|
+
Get started:
|
|
8253
|
+
vet-skills hook install claude-code check every install Claude Code makes (one time)
|
|
8254
|
+
vet-skills check <skill> see the verdict for anything before installing it
|
|
8255
|
+
vet-skills join <org> --token <t> enroll this machine in your team's policy (token from the dashboard)
|
|
8256
|
+
|
|
8183
8257
|
vet-skills check <skill> dry-run the decision
|
|
8184
8258
|
vet-skills install <skill> check, then \`npx skills add\`
|
|
8185
8259
|
vet-skills detonate <path> [--publisher h] upload a local bundle to the sandbox and print its verdict
|
|
@@ -8214,7 +8288,7 @@ async function main() {
|
|
|
8214
8288
|
usage();
|
|
8215
8289
|
return 1;
|
|
8216
8290
|
}
|
|
8217
|
-
const r = await check(rest[0], { agentKind: values.agent });
|
|
8291
|
+
const r = await check(rest[0], { agentKind: values.agent, registry: values.registry });
|
|
8218
8292
|
if (values.json) {
|
|
8219
8293
|
console.log(JSON.stringify({ decision: r.decision, resolved: r.resolved, source: r.source, offline: r.offline }, null, 2));
|
|
8220
8294
|
return r.decision.decision === "block" ? 2 : 0;
|
|
@@ -8365,7 +8439,7 @@ async function main() {
|
|
|
8365
8439
|
machine_label: hostname(),
|
|
8366
8440
|
agent_kind: values.agent ?? "other",
|
|
8367
8441
|
hook_version: VERSION,
|
|
8368
|
-
os:
|
|
8442
|
+
os: platform2(),
|
|
8369
8443
|
installed: [],
|
|
8370
8444
|
ci: values.ci === true
|
|
8371
8445
|
});
|
|
@@ -8432,6 +8506,16 @@ async function main() {
|
|
|
8432
8506
|
console.log(`${c.verified("\u2713")} ${handle} added to the personal block list${values.reason ? ` (${values.reason})` : ""}.`);
|
|
8433
8507
|
return 0;
|
|
8434
8508
|
}
|
|
8509
|
+
case "telemetry": {
|
|
8510
|
+
const sub = rest[0] ?? "status";
|
|
8511
|
+
if (sub === "on" || sub === "off") {
|
|
8512
|
+
setTelemetry(sub === "on");
|
|
8513
|
+
console.log(`${c.verified("\u2713")} telemetry ${sub}`);
|
|
8514
|
+
return 0;
|
|
8515
|
+
}
|
|
8516
|
+
console.log(`telemetry ${telemetryEnabled() ? "on" : "off"} ${c.muted(readOrg() ? "(enrolled: decisions report to the org, not here)" : "(anonymous: skill, version, decision, agent type; never code, prompts or paths)")}`);
|
|
8517
|
+
return 0;
|
|
8518
|
+
}
|
|
8435
8519
|
case "status": {
|
|
8436
8520
|
const org = readOrg();
|
|
8437
8521
|
const { policy, source } = readPolicy();
|