skillmds 1.0.17 → 1.0.18

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 CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to `skillmds` are documented here. Versions follow [semver](https://semver.org).
4
4
 
5
+ ## 1.0.18
6
+
7
+ - **Cleaner tool output for agents.** `skillmd_search`, `skillmd_trending`, and `skillmd_recommend` no longer echo site-oriented fields (`avg_rating`, `rating_count`, `install_count`, `save_count`, `verified`, `type`, `owner_avatar`, `category_slug`, and the internal `confident` ranker flag). Results now carry just what an agent needs to pick a skill: slug, title, description, category, agents, repo stars, and the install snippet.
8
+ - **Quality score no longer punishes ordinary skills.** The security flags (`reads_secrets`, `network_calls`, `executes_scripts`) are pattern matches against prose and code samples, not proof of anything unsafe — they now cost 2/1/5 points respectively (was 25/8/15), so a skill that merely documents an env var or a URL keeps its high score. Verified skills are floored at 85 (unless the content fails to parse). `skillmd_lint` passes a skill's verified status through to the score.
9
+
5
10
  ## 1.0.16
6
11
 
7
12
  - **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.
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  // SkillMD MCP server (stdio). Wraps the public SkillMD API so any MCP-capable agent
3
3
  // can search, fetch, install, and discover Agent Skills by name or task.
4
4
  //
@@ -45,10 +45,27 @@ async function api(path, init = {}) {
45
45
  }
46
46
  const text = (obj) => ({ content: [{ type: "text", text: typeof obj === "string" ? obj : JSON.stringify(obj, null, 2) }] });
47
47
 
48
+ // The public API returns site-oriented fields (ratings, counters, avatars,
49
+ // ranker internals) that are noise for an agent picking a skill. Strip them
50
+ // from every list-returning tool so results stay compact and rankable only
51
+ // on what matters to the caller.
52
+ const OMIT_FIELDS = new Set([
53
+ "type",
54
+ "verified",
55
+ "avg_rating",
56
+ "rating_count",
57
+ "install_count",
58
+ "save_count",
59
+ "owner_avatar",
60
+ "confident",
61
+ "category_slug",
62
+ ]);
63
+ const trimItems = (items) => (items ?? []).map((s) => Object.fromEntries(Object.entries(s).filter(([k]) => !OMIT_FIELDS.has(k))));
64
+
48
65
  const TOOLS = [
49
66
  {
50
67
  name: "skillmd_search",
51
- description: "Search SkillMD for Agent Skills by name, description, or task. Returns matching skills with ratings and install snippets.",
68
+ description: "Search SkillMD for Agent Skills by name, description, or task. Returns matching skills with their owner/name slug and install snippets.",
52
69
  inputSchema: {
53
70
  type: "object",
54
71
  properties: {
@@ -138,7 +155,7 @@ async function handleCall(name, args) {
138
155
  if (args.type) p.set("type", args.type);
139
156
  if (args.min_rating != null) p.set("min_rating", String(args.min_rating));
140
157
  const r = await api(`/api/search?${p}`);
141
- return text(r.items ?? []);
158
+ return text(trimItems(r.items));
142
159
  }
143
160
  case "skillmd_get": {
144
161
  const [owner, slugName] = String(args.slug).split("/");
@@ -149,7 +166,7 @@ async function handleCall(name, args) {
149
166
  const p = new URLSearchParams({ range: args.range ?? "all", limit: String(args.limit ?? 20) });
150
167
  if (args.category) p.set("category", args.category);
151
168
  const r = await api(`/api/leaderboard?${p}`);
152
- return text(r.items ?? []);
169
+ return text(trimItems(r.items));
153
170
  }
154
171
  case "skillmd_recommend": {
155
172
  if (args.based_on) {
@@ -157,11 +174,11 @@ async function handleCall(name, args) {
157
174
  const base = await api(`/api/skills/${owner}/${slugName}`).catch(() => null);
158
175
  if (base?.category_slug) {
159
176
  const r = await api(`/api/leaderboard?range=all&limit=${args.limit ?? 10}&category=${base.category_slug}`);
160
- return text((r.items ?? []).filter((s) => s.slug !== args.based_on));
177
+ return text(trimItems(r.items).filter((s) => s.slug !== args.based_on));
161
178
  }
162
179
  }
163
180
  const r = await api(`/api/leaderboard?range=30d&limit=${args.limit ?? 10}`);
164
- return text(r.items ?? []);
181
+ return text(trimItems(r.items));
165
182
  }
166
183
  case "skillmd_list_saved": {
167
184
  if (!TOKEN) return text("Set SKILLMD_TOKEN (a SkillMD personal access token) to list your saved skills.");
@@ -172,14 +189,16 @@ async function handleCall(name, args) {
172
189
  if (!lint) return text("Validation engine unavailable — run `npm run build` in the skillmds package.");
173
190
  let raw = args.content;
174
191
  let slug;
192
+ let verified = false;
175
193
  if (!raw && args.slug) {
176
194
  const [owner, slugName] = String(args.slug).split("/");
177
195
  const skill = await api(`/api/skills/${owner}/${slugName}`);
178
196
  raw = reconstructSkillMd(skill);
179
197
  slug = slugName;
198
+ verified = !!skill.verified;
180
199
  }
181
200
  if (!raw) return text("Provide `content` (raw SKILL.md) or `slug` (owner/name).");
182
- const result = lint(raw, slug ? { slug } : {});
201
+ const result = lint(raw, { ...(slug ? { slug } : {}), verified });
183
202
  return text({ ok: result.ok, score: result.score, diagnostics: result.diagnostics, security: result.security });
184
203
  }
185
204
  case "skillmd_install": {
package/dist/cli.js CHANGED
@@ -163,16 +163,19 @@ var WEIGHT = {
163
163
  };
164
164
  var RISK = {
165
165
  docs_only: 0,
166
- reads_secrets: 25,
167
- executes_scripts: 15,
168
- network_calls: 8
166
+ reads_secrets: 2,
167
+ network_calls: 1,
168
+ executes_scripts: 5
169
169
  };
170
- function qualityScore(diagnostics, security) {
170
+ var VERIFIED_FLOOR = 85;
171
+ function qualityScore(diagnostics, security, opts = {}) {
171
172
  let penalty = diagnostics.reduce((sum, d) => sum + WEIGHT[d.severity], 0);
172
173
  if (security) {
173
174
  for (const flag of security.flags) penalty += RISK[flag] ?? 0;
174
175
  }
175
- return Math.max(0, Math.min(100, 100 - penalty));
176
+ const score = Math.max(0, Math.min(100, 100 - penalty));
177
+ const hasError = diagnostics.some((d) => d.severity === "error");
178
+ return opts.verified && !hasError ? Math.max(score, VERIFIED_FLOOR) : score;
176
179
  }
177
180
 
178
181
  // ../../packages/core/src/lint.ts
@@ -185,14 +188,14 @@ function lint(raw, opts = {}) {
185
188
  ok: false,
186
189
  diagnostics: diagnostics2,
187
190
  security: security2,
188
- score: qualityScore(diagnostics2, security2)
191
+ score: qualityScore(diagnostics2, security2, { verified: opts.verified })
189
192
  };
190
193
  }
191
194
  const ctx = opts.slug !== void 0 ? { slug: opts.slug } : {};
192
195
  const diagnostics = runRules(parsed, ctx);
193
196
  const security = scanSecurity(parsed.body);
194
197
  const ok = !diagnostics.some((d) => d.severity === "error");
195
- return { ok, skill: parsed, diagnostics, security, score: qualityScore(diagnostics, security) };
198
+ return { ok, skill: parsed, diagnostics, security, score: qualityScore(diagnostics, security, { verified: opts.verified }) };
196
199
  }
197
200
 
198
201
  // ../../packages/core/src/format/text.ts
@@ -937,6 +940,9 @@ function detectAgents(opts = {}) {
937
940
  return AGENTS.filter((a) => existsSync3(agentDir(a.id, opts))).map((a) => a.id);
938
941
  }
939
942
  function writeSkill(skillName, files, targetDir, opts = {}) {
943
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(skillName) || skillName.includes("..")) {
944
+ throw new Error(`refused unsafe skill name: "${skillName}"`);
945
+ }
940
946
  const dest = join4(targetDir, skillName);
941
947
  if (opts.mode === "symlink" && opts.canonicalDir) {
942
948
  mkdirSync3(dirname2(dest), { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillmds",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "SkillMD for agents: the `skillmds` MCP server + the `skillmd` CLI (lint, scan, search, install, publish Agent Skills) from skillmd.com.",
5
5
  "type": "module",
6
6
  "bin": {
package/vendor/index.js CHANGED
@@ -169,16 +169,19 @@ var WEIGHT = {
169
169
  };
170
170
  var RISK = {
171
171
  docs_only: 0,
172
- reads_secrets: 25,
173
- executes_scripts: 15,
174
- network_calls: 8
172
+ reads_secrets: 2,
173
+ network_calls: 1,
174
+ executes_scripts: 5
175
175
  };
176
- function qualityScore(diagnostics, security) {
176
+ var VERIFIED_FLOOR = 85;
177
+ function qualityScore(diagnostics, security, opts = {}) {
177
178
  let penalty = diagnostics.reduce((sum, d) => sum + WEIGHT[d.severity], 0);
178
179
  if (security) {
179
180
  for (const flag of security.flags) penalty += RISK[flag] ?? 0;
180
181
  }
181
- return Math.max(0, Math.min(100, 100 - penalty));
182
+ const score = Math.max(0, Math.min(100, 100 - penalty));
183
+ const hasError = diagnostics.some((d) => d.severity === "error");
184
+ return opts.verified && !hasError ? Math.max(score, VERIFIED_FLOOR) : score;
182
185
  }
183
186
 
184
187
  // ../../packages/core/src/lint.ts
@@ -191,14 +194,14 @@ function lint(raw, opts = {}) {
191
194
  ok: false,
192
195
  diagnostics: diagnostics2,
193
196
  security: security2,
194
- score: qualityScore(diagnostics2, security2)
197
+ score: qualityScore(diagnostics2, security2, { verified: opts.verified })
195
198
  };
196
199
  }
197
200
  const ctx = opts.slug !== void 0 ? { slug: opts.slug } : {};
198
201
  const diagnostics = runRules(parsed, ctx);
199
202
  const security = scanSecurity(parsed.body);
200
203
  const ok = !diagnostics.some((d) => d.severity === "error");
201
- return { ok, skill: parsed, diagnostics, security, score: qualityScore(diagnostics, security) };
204
+ return { ok, skill: parsed, diagnostics, security, score: qualityScore(diagnostics, security, { verified: opts.verified }) };
202
205
  }
203
206
 
204
207
  // ../../packages/core/src/format/text.ts