skillmds 1.0.14 → 1.0.16
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/CHANGELOG.md +10 -0
- package/bin/skillmd-mcp.mjs +3 -9
- package/dist/cli.js +99 -30
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `skillmds` are documented here. Versions follow [semver](https://semver.org).
|
|
4
4
|
|
|
5
|
+
## 1.0.16
|
|
6
|
+
|
|
7
|
+
- **Installs are no longer gated on verification.** Any skill installs directly — verified or not — with no flag, no prompt, and no second step, from both the CLI (`skillmd add`) and the MCP `skillmd_install` tool. Security flags (network_calls, executes_scripts, reads_secrets, docs_only) are still shown on the result as information, but never block. `--deny <flag>` remains available as an opt-in for anyone who wants to exclude a flag; `--allow-unverified` is now a no-op kept for backward compatibility.
|
|
8
|
+
|
|
9
|
+
## 1.0.15
|
|
10
|
+
|
|
11
|
+
- Search is now **browsable in the interactive menu**: arrow through results, press Enter on one to see a card, and install it to your project or user directory — all without leaving the CLI (`← Back` at each level; Esc goes up one step, never quits).
|
|
12
|
+
- Search results are clean two-line entries showing the source repo's **star count** (e.g. `★ 223.7k`) instead of the placeholder rating, with the description truncated to fit your terminal.
|
|
13
|
+
- Fixes for narrow terminals: result rows are width-budgeted so they don't wrap and garble the menu; the result card prints once instead of repainting on every action.
|
|
14
|
+
|
|
5
15
|
## 1.0.14
|
|
6
16
|
|
|
7
17
|
- Interactive menu (`skillmds` in a terminal) no longer exits after one action — it returns to the menu until you pick **Quit**; cancelling a sub-prompt goes back to the menu instead of quitting.
|
package/bin/skillmd-mcp.mjs
CHANGED
|
@@ -70,13 +70,12 @@ const TOOLS = [
|
|
|
70
70
|
{
|
|
71
71
|
name: "skillmd_install",
|
|
72
72
|
description:
|
|
73
|
-
"Install a skill by slug:
|
|
73
|
+
"Install a skill by slug: writes its SKILL.md into the agent's skills directory. Never executes any scripts. Any skill installs; the response reports its security flags (docs_only / network_calls / executes_scripts / reads_secrets) as information.",
|
|
74
74
|
inputSchema: {
|
|
75
75
|
type: "object",
|
|
76
76
|
properties: {
|
|
77
77
|
slug: { type: "string", description: "owner/name, e.g. anthropic/pdf" },
|
|
78
78
|
dest: { type: "string", description: "Target skills dir. Default ~/.claude/skills" },
|
|
79
|
-
allow_unverified: { type: "boolean", description: "Install even if the skill is not verified by SkillMD. Never set this on your own initiative — only after the human has explicitly approved this specific skill." },
|
|
80
79
|
},
|
|
81
80
|
required: ["slug"],
|
|
82
81
|
},
|
|
@@ -193,13 +192,8 @@ async function handleCall(name, args) {
|
|
|
193
192
|
}
|
|
194
193
|
const skill = await api(`/api/skills/${owner}/${slugName}`);
|
|
195
194
|
const flags = skill.security_flags ?? [];
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
`Refused: ${args.slug} is not yet verified by SkillMD (security flags: ${flags.join(", ") || "unscanned"}). ` +
|
|
199
|
-
`Review it at https://skillmd.com/skills/${args.slug}. Only call skillmd_install again with allow_unverified: true ` +
|
|
200
|
-
`if the human explicitly approves installing this specific unverified skill — do not decide this yourself.`,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
195
|
+
// Verification never gates installs — any skill installs; flags are
|
|
196
|
+
// reported in the response as information, not a wall.
|
|
203
197
|
const raw = reconstructSkillMd(skill); // returns raw_md verbatim when the registry has it
|
|
204
198
|
if (lint) {
|
|
205
199
|
const result = lint(raw, { slug: slugName });
|
package/dist/cli.js
CHANGED
|
@@ -795,6 +795,34 @@ ${skill.body_md || ""}`;
|
|
|
795
795
|
}
|
|
796
796
|
|
|
797
797
|
// ../cli/src/commands/search.ts
|
|
798
|
+
var MAX_LINE_WIDTH = 100;
|
|
799
|
+
function formatStars(n) {
|
|
800
|
+
if (n >= 1e6) {
|
|
801
|
+
const m = Math.round(n / 1e6 * 10) / 10;
|
|
802
|
+
return `${m % 1 === 0 ? m.toFixed(0) : m.toFixed(1)}M`;
|
|
803
|
+
}
|
|
804
|
+
if (n >= 1e3) {
|
|
805
|
+
const k = Math.round(n / 1e3 * 10) / 10;
|
|
806
|
+
return `${k % 1 === 0 ? k.toFixed(0) : k.toFixed(1)}k`;
|
|
807
|
+
}
|
|
808
|
+
return String(n);
|
|
809
|
+
}
|
|
810
|
+
function truncate(text3, width) {
|
|
811
|
+
if (text3.length <= width) return text3;
|
|
812
|
+
if (width <= 1) return "\u2026";
|
|
813
|
+
return `${text3.slice(0, width - 1).trimEnd()}\u2026`;
|
|
814
|
+
}
|
|
815
|
+
function formatSearchResult(item, width = process.stdout.columns ?? 80) {
|
|
816
|
+
const stars = item.repo_stars != null ? pc5.dim(`\u2605 ${formatStars(item.repo_stars)}`) : "";
|
|
817
|
+
const check = item.verified ? pc5.green("\u2713") : "";
|
|
818
|
+
const pack = item.type === "pack" ? pc5.dim("[pack]") : "";
|
|
819
|
+
const head = [pc5.bold(item.slug), stars, check, pack].filter(Boolean).join(" ");
|
|
820
|
+
const indent = " ";
|
|
821
|
+
const lineWidth = Math.max(10, Math.min(width, MAX_LINE_WIDTH) - indent.length);
|
|
822
|
+
const desc = `${indent}${pc5.dim(truncate(item.description ?? "", lineWidth))}`;
|
|
823
|
+
return `${head}
|
|
824
|
+
${desc}`;
|
|
825
|
+
}
|
|
798
826
|
async function runSearch(query, flags) {
|
|
799
827
|
const { api } = createClient(flags);
|
|
800
828
|
const params = new URLSearchParams({ q: query, limit: String(flags.limit ?? 20) });
|
|
@@ -805,13 +833,8 @@ async function runSearch(query, flags) {
|
|
|
805
833
|
const r = await api(`/api/search?${params}`);
|
|
806
834
|
const items = r.items ?? [];
|
|
807
835
|
if (flags.json) return { items, output: JSON.stringify(items, null, 2) };
|
|
808
|
-
const lines = items.map((s) =>
|
|
809
|
-
|
|
810
|
-
const rating = s.avg_rating ? pc5.dim(`\u2605${s.avg_rating.toFixed(1)} (${s.rating_count ?? 0})`) : "";
|
|
811
|
-
return `${check} ${pc5.bold(s.slug)} ${rating}
|
|
812
|
-
${pc5.dim(s.description)}`;
|
|
813
|
-
});
|
|
814
|
-
return { items, output: lines.join("\n") || "No skills found." };
|
|
836
|
+
const lines = items.map((s) => formatSearchResult(s));
|
|
837
|
+
return { items, output: lines.join("\n\n") || "No skills found." };
|
|
815
838
|
}
|
|
816
839
|
function searchCommand() {
|
|
817
840
|
return new Command5("search").description("Search the SkillMD registry").argument("<query>", "search terms").option("--category <slug>", "filter by category").option("--verified", "only verified skills").option("--type <type>", "single | pack").option("--min-rating <n>", "minimum average rating (0-5)").option("--limit <n>", "max results", "20").option("--json", "machine-readable output").action(async (query, opts, cmd) => {
|
|
@@ -871,7 +894,7 @@ function infoCommand() {
|
|
|
871
894
|
}
|
|
872
895
|
|
|
873
896
|
// ../cli/src/commands/add.ts
|
|
874
|
-
import { Command as Command7 } from "commander";
|
|
897
|
+
import { Command as Command7, Option } from "commander";
|
|
875
898
|
import { existsSync as existsSync4 } from "fs";
|
|
876
899
|
import pc7 from "picocolors";
|
|
877
900
|
|
|
@@ -984,16 +1007,6 @@ var defaultDeps = {
|
|
|
984
1007
|
body: JSON.stringify({ via: "cli" })
|
|
985
1008
|
}).catch(() => {
|
|
986
1009
|
});
|
|
987
|
-
},
|
|
988
|
-
async confirmUnverified(c, score, secFlags) {
|
|
989
|
-
if (!process.stdin.isTTY) return false;
|
|
990
|
-
const { confirm: confirm2, isCancel: isCancel4 } = await import("@clack/prompts");
|
|
991
|
-
const ok = await confirm2({
|
|
992
|
-
message: `${c.name} is not yet verified by SkillMD (lint score ${score}${secFlags.length ? `; flags: ${secFlags.join(", ")}` : ""}). Install anyway?`,
|
|
993
|
-
initialValue: false
|
|
994
|
-
});
|
|
995
|
-
if (isCancel4(ok)) return false;
|
|
996
|
-
return ok === true;
|
|
997
1010
|
}
|
|
998
1011
|
};
|
|
999
1012
|
async function runAdd(arg, flags, deps = defaultDeps) {
|
|
@@ -1016,18 +1029,9 @@ async function runAdd(arg, flags, deps = defaultDeps) {
|
|
|
1016
1029
|
}
|
|
1017
1030
|
if (deniedFlags.length) {
|
|
1018
1031
|
blocked.push({ name: c.name, reason: `denied security flags: ${deniedFlags.join(", ")}` });
|
|
1019
|
-
lines.push(pc7.red(`\u2717 ${c.name} blocked \u2014 ${deniedFlags.join(", ")}`));
|
|
1032
|
+
lines.push(pc7.red(`\u2717 ${c.name} blocked \u2014 you passed --deny ${deniedFlags.join(", ")}`));
|
|
1020
1033
|
continue;
|
|
1021
1034
|
}
|
|
1022
|
-
if (c.registrySlug && c.verified === false && !flags.allowUnverified) {
|
|
1023
|
-
const ok = !flags.yes && deps.confirmUnverified ? await deps.confirmUnverified(c, result.score, allFlags) : false;
|
|
1024
|
-
if (!ok) {
|
|
1025
|
-
blocked.push({ name: c.name, reason: "unverified registry skill (use --allow-unverified)" });
|
|
1026
|
-
lines.push(pc7.yellow(`\u2717 ${c.name} blocked \u2014 not yet verified by SkillMD${allFlags.length ? ` (flags: ${allFlags.join(", ")})` : ""}`));
|
|
1027
|
-
lines.push(pc7.dim(` review at https://skillmd.com/skills/${c.registrySlug}, then re-run with --allow-unverified`));
|
|
1028
|
-
continue;
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
1035
|
const files = c.files ?? [{ path: "SKILL.md", contents: c.raw }];
|
|
1032
1036
|
for (const agentId of targets) {
|
|
1033
1037
|
const dir = agentDir(agentId, scope);
|
|
@@ -1047,7 +1051,7 @@ async function runAdd(arg, flags, deps = defaultDeps) {
|
|
|
1047
1051
|
return { written, blocked, exitCode, output: lines.join("\n") };
|
|
1048
1052
|
}
|
|
1049
1053
|
function addCommand() {
|
|
1050
|
-
return new Command7("add").description("Install a skill from the registry, a GitHub repo, or a local path (lints before writing)").argument("<source>", "registry slug (owner/name), GitHub source, or local path").option("-g, --global", "install to the user directory instead of the project").option("-a, --agent <agents...>", "target specific agents").option("-s, --skill <names...>", "install only specific skills by name ('*' for all)").option("--copy", "copy files (default)", true).option("-y, --yes", "skip confirmation prompts").
|
|
1054
|
+
return new Command7("add").description("Install a skill from the registry, a GitHub repo, or a local path (lints before writing)").argument("<source>", "registry slug (owner/name), GitHub source, or local path").option("-g, --global", "install to the user directory instead of the project").option("-a, --agent <agents...>", "target specific agents").option("-s, --skill <names...>", "install only specific skills by name ('*' for all)").option("--copy", "copy files (default)", true).option("-y, --yes", "skip confirmation prompts").addOption(new Option("--allow-unverified").hideHelp()).option("--skip-lint", "install even if the skill fails lint").option("--deny <flags...>", "refuse skills carrying any of these security flags (opt-in)").action(async (source, opts, cmd) => {
|
|
1051
1055
|
const merged = { ...cmd.parent?.opts(), ...opts };
|
|
1052
1056
|
const run = await runAdd(source, merged);
|
|
1053
1057
|
console.log(run.output);
|
|
@@ -1250,6 +1254,67 @@ function bail(v) {
|
|
|
1250
1254
|
}
|
|
1251
1255
|
return false;
|
|
1252
1256
|
}
|
|
1257
|
+
async function browseSearchResults(items) {
|
|
1258
|
+
const cols = process.stdout.columns ?? 80;
|
|
1259
|
+
for (; ; ) {
|
|
1260
|
+
const picked = await p4.select({
|
|
1261
|
+
message: "Search results",
|
|
1262
|
+
options: [
|
|
1263
|
+
...items.map((item2) => {
|
|
1264
|
+
const starsTxt = item2.repo_stars != null ? ` \u2605${formatStars(item2.repo_stars)}` : "";
|
|
1265
|
+
const checkTxt = item2.verified ? " \u2713" : "";
|
|
1266
|
+
const plainLabel = `${item2.slug}${starsTxt}${checkTxt}`;
|
|
1267
|
+
const label2 = `${pc12.bold(item2.slug)}${starsTxt ? pc12.dim(starsTxt) : ""}${checkTxt ? pc12.green(checkTxt) : ""}`;
|
|
1268
|
+
const hintWidth = Math.max(10, cols - plainLabel.length - 6);
|
|
1269
|
+
return {
|
|
1270
|
+
value: item2.slug,
|
|
1271
|
+
label: label2,
|
|
1272
|
+
hint: truncate(item2.description ?? "", hintWidth)
|
|
1273
|
+
};
|
|
1274
|
+
}),
|
|
1275
|
+
{ value: "__back", label: "\u2190 Back to menu" }
|
|
1276
|
+
]
|
|
1277
|
+
});
|
|
1278
|
+
if (p4.isCancel(picked) || picked === "__back") return;
|
|
1279
|
+
const item = items.find((i) => i.slug === picked);
|
|
1280
|
+
if (!item) continue;
|
|
1281
|
+
await browseOneResult(item);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
async function browseOneResult(item) {
|
|
1285
|
+
const starsTxt = item.repo_stars != null ? `\u2605 ${formatStars(item.repo_stars)} stars` : "";
|
|
1286
|
+
const meta = [
|
|
1287
|
+
starsTxt,
|
|
1288
|
+
item.verified ? pc12.green("\u2713 verified") : "unverified",
|
|
1289
|
+
item.type === "pack" ? "pack" : ""
|
|
1290
|
+
].filter(Boolean).join(" \xB7 ");
|
|
1291
|
+
p4.note([item.description ?? "", pc12.dim(meta)].join("\n"), item.slug);
|
|
1292
|
+
for (; ; ) {
|
|
1293
|
+
const action = await p4.select({
|
|
1294
|
+
message: "What next?",
|
|
1295
|
+
options: [
|
|
1296
|
+
{ value: "project", label: "Install to this project" },
|
|
1297
|
+
{ value: "global", label: "Install globally (user dir)" },
|
|
1298
|
+
{ value: "url", label: "Show page URL" },
|
|
1299
|
+
{ value: "back", label: "\u2190 Back to results" }
|
|
1300
|
+
]
|
|
1301
|
+
});
|
|
1302
|
+
if (p4.isCancel(action) || action === "back") return;
|
|
1303
|
+
if (action === "url") {
|
|
1304
|
+
p4.log.message(pc12.dim(`https://skillmd.com/skills/${item.slug}`));
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
p4.log.step(`Installing ${item.slug}\u2026`);
|
|
1308
|
+
try {
|
|
1309
|
+
const run = await runAdd(item.slug, action === "global" ? { global: true } : {});
|
|
1310
|
+
console.log(run.output);
|
|
1311
|
+
if (run.exitCode === 0) p4.log.success("Installed.");
|
|
1312
|
+
else p4.log.warn("Not installed.");
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
p4.log.error(err instanceof Error ? err.message : String(err));
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1253
1318
|
async function runInteractive() {
|
|
1254
1319
|
p4.intro(pc12.bgCyan(pc12.black(" skillmd ")));
|
|
1255
1320
|
for (; ; ) {
|
|
@@ -1295,7 +1360,11 @@ async function runInteractive() {
|
|
|
1295
1360
|
try {
|
|
1296
1361
|
const run = await runSearch(String(query), { verified: Boolean(verified), limit: "20" });
|
|
1297
1362
|
s.stop(`${run.items.length} result${run.items.length === 1 ? "" : "s"}`);
|
|
1298
|
-
|
|
1363
|
+
if (run.items.length === 0) {
|
|
1364
|
+
p4.log.info("No results.");
|
|
1365
|
+
break;
|
|
1366
|
+
}
|
|
1367
|
+
await browseSearchResults(run.items);
|
|
1299
1368
|
} catch (err) {
|
|
1300
1369
|
s.stop(pc12.red(err instanceof Error ? err.message : String(err)));
|
|
1301
1370
|
process.exitCode = 1;
|
package/package.json
CHANGED