skillmds 1.0.2 → 1.0.4

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 ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to `skillmds` are documented here. Versions follow [semver](https://semver.org).
4
+
5
+ ## 1.0.4
6
+
7
+ - `skillmd lint`/`scan` print a progress line to stderr and warn when scanning a home directory, so a directory scan never looks like a frozen blank screen.
8
+ - Directory scans skip large build/OS directories (`AppData`, `node_modules`, `target`, …) and cap recursion depth, so `lint .` in a big tree terminates quickly.
9
+ - Clearer message when no `SKILL.md` is found.
10
+ - Bare `skillmd` (no command) prints help.
11
+
12
+ ## 1.0.3
13
+
14
+ - CLI (`skillmd`) and MCP server (`skillmds`) now report their version from `package.json` instead of a hardcoded string.
15
+ - READMEs rewritten; added this changelog.
16
+
17
+ ## 1.0.2
18
+
19
+ - `skillmd lint`: skip directories that cannot be read (e.g. `EPERM` on protected system folders) instead of aborting the scan.
20
+
21
+ ## 1.0.1
22
+
23
+ - Fix `skillmd_lint` in the MCP server: keep `yaml` external in the bundled engine so it loads at runtime (bundling a CommonJS module into ESM raised `Dynamic require of "process"`).
24
+
25
+ ## 1.0.0
26
+
27
+ - Add the `skillmd` CLI, shipped alongside the MCP server in this package. Commands: `lint`/`check`, `scan`, `rules`, `init`, `search`, `add`, `list`, `remove`, `update`, `publish`/`submit`, `login`, `logout`.
28
+ - Validation engine shared with the SkillMD registry: `SKILL.md` parser, lint rules `SK001`–`SK040`, security scan, quality score, and `text`/`json`/`sarif`/`github` output.
29
+ - Add the `skillmd_lint` MCP tool.
30
+ - MCP server and CLI authenticate with `Authorization: Bearer` tokens.
31
+
32
+ ## 0.1.1
33
+
34
+ - Initial MCP server: `skillmd_search`, `skillmd_get`, `skillmd_install`, `skillmd_trending`, `skillmd_recommend`, `skillmd_list_saved`.
package/README.md CHANGED
@@ -1,127 +1,111 @@
1
1
  <p align="center">
2
- <img src="https://skillmd.com/skillmd-banner.jpg" alt="SkillMD" width="640">
2
+ <img src="https://skillmd.com/skillmd-banner.jpg" alt="SkillMD" width="560">
3
3
  </p>
4
4
 
5
5
  # skillmds
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/skillmds.svg)](https://www.npmjs.com/package/skillmds)
8
- [![npm downloads](https://img.shields.io/npm/dm/skillmds.svg)](https://www.npmjs.com/package/skillmds)
9
8
  [![license](https://img.shields.io/npm/l/skillmds.svg)](#license)
10
9
 
11
- > SkillMD for agents — the official **MCP server** *and* the **`skillmd` CLI** for **[SkillMD](https://skillmd.com)**, the open, verified registry of Agent Skills.
10
+ Tools for working with [Agent Skills](https://docs.claude.com/en/docs/agents-and-tools/agent-skills) (`SKILL.md`) from the [SkillMD](https://skillmd.com) registry. One package, two binaries:
12
11
 
13
- This one package ships two tools:
12
+ - **`skillmd`** — a CLI to lint, scan, search, install, and publish skills.
13
+ - **`skillmds`** — an MCP server exposing the registry to MCP clients (Claude Code, Claude Desktop, Cursor, …).
14
14
 
15
- - **`skillmds`** — an MCP server so any MCP-capable agent (Claude Code, Claude Desktop, Cursor, …) can search, fetch, validate, and install `SKILL.md` skills with no copy-paste.
16
- - **`skillmd`** — a CLI that lints and security-scans skills before installing or publishing them. *The registry that lints.*
15
+ Both validate `SKILL.md` with the same engine, so the CLI and the registry apply identical rules.
17
16
 
18
- ---
19
-
20
- ## CLI quick start
17
+ ## Install
21
18
 
22
19
  ```bash
23
- npm i -g skillmds # installs the `skillmd` command (and `skillmds`)
24
-
25
- skillmd lint ./my-skill # validate against the spec + quality score
26
- skillmd lint . --format sarif # SARIF for GitHub Code Scanning
27
- skillmd scan . --deny executes_scripts
28
- skillmd search pdf --verified
29
- skillmd add anthropic/pdf -a claude-code -g # lints before installing
30
- skillmd init my-skill
31
- skillmd login && skillmd publish ./my-skill # refuses to publish on lint errors
20
+ npm i -g skillmds
32
21
  ```
33
22
 
34
- Without a global install, run a one-off with `npx -p skillmds skillmd lint .`.
35
- Commands: `lint`/`check`, `scan`, `rules`, `init`, `search`, `add`, `list`, `remove`, `update`, `publish`, `login`. All support `--json`.
36
-
37
- ---
38
-
39
- ## MCP server
40
-
41
- ## Install
42
-
43
- **Claude Code** (one line):
23
+ This installs both the `skillmd` and `skillmds` commands. To run the CLI without installing:
44
24
 
45
25
  ```bash
46
- claude mcp add skillmds -- npx -y skillmds
26
+ npx -p skillmds skillmd lint .
47
27
  ```
48
28
 
49
- **Claude Desktop / Cursor / any MCP client** — add to your MCP config:
29
+ ## CLI: `skillmd`
50
30
 
51
- ```jsonc
52
- {
53
- "mcpServers": {
54
- "skillmds": {
55
- "command": "npx",
56
- "args": ["-y", "skillmds"]
57
- }
58
- }
59
- }
60
- ```
31
+ | Command | Description |
32
+ |---|---|
33
+ | `skillmd lint [path]` (`check`) | Validate `SKILL.md` files; print diagnostics and a quality score. |
34
+ | `skillmd scan [path]` | Report scripts, network calls, and secret access. |
35
+ | `skillmd rules [id]` | List lint rules, or show one. |
36
+ | `skillmd init [name]` | Create a `SKILL.md` from a template. |
37
+ | `skillmd search <query>` | Search the registry. |
38
+ | `skillmd add <source>` | Install a skill (registry slug, GitHub repo, or local path). Lints before writing. |
39
+ | `skillmd list` (`ls`) | List installed skills. |
40
+ | `skillmd remove <names...>` (`rm`) | Remove installed skills. |
41
+ | `skillmd update [names...]` | Update installed skills from the registry. |
42
+ | `skillmd publish [path]` (`submit`) | Publish to the registry. Exits non-zero on lint errors. |
43
+ | `skillmd login` / `logout` | Store or remove a token. |
61
44
 
62
- That's it. Restart your client and the SkillMD tools are available.
45
+ All commands accept `--json`.
63
46
 
64
- ---
47
+ ### lint
65
48
 
66
- ## What you can do
49
+ ```bash
50
+ skillmd lint ./my-skill
51
+ skillmd lint . --format sarif > skillmd.sarif
52
+ skillmd lint . --strict
53
+ skillmd lint . --fix
54
+ ```
67
55
 
68
- Once connected, just ask your agent in plain language:
56
+ Formats: `text` (default), `json`, `sarif` (GitHub Code Scanning), `github` (Actions annotations).
57
+ Exit codes: `0` clean, `1` errors (or warnings with `--strict` / `--fail-on-warning`), `2` usage error.
69
58
 
70
- - *"Find a skill for optimizing Dockerfiles."*
71
- - *"Install the anthropic/pdf skill."*
72
- - *"What are the top-rated data skills right now?"*
59
+ ### add
73
60
 
74
- The agent picks the right tool, fetches the skill from SkillMD, and writes it into your skills directory.
61
+ ```bash
62
+ skillmd add anthropic/pdf # registry slug (verified-only by default)
63
+ skillmd add owner/repo # GitHub repo
64
+ skillmd add ./path # local
65
+ skillmd add anthropic/pdf -a claude-code -g
66
+ skillmd add some/skill --deny executes_scripts
67
+ skillmd add some/skill --skip-lint
68
+ ```
75
69
 
76
- ## Tools
70
+ ### publish
77
71
 
78
- | Tool | Auth | What it does |
79
- | --- | --- | --- |
80
- | `skillmd_search` | – | Search skills by name, description, or task (filter by category, type, rating, verified). |
81
- | `skillmd_get` | – | Fetch one skill by `owner/name`, with its `SKILL.md`, provenance, license, and security flags. |
82
- | `skillmd_install` | – | Write a skill's `SKILL.md` into your agent's skills dir. **Never executes scripts.** |
83
- | `skillmd_trending` | – | Top skills on the leaderboard (`30d` or `all`, optional category). |
84
- | `skillmd_recommend` | – | Skills similar to one you give, or trending if none. |
85
- | `skillmd_list_saved` | token | Your saved skills (requires `SKILLMD_TOKEN`). |
86
- | `skillmd_lint` | – | Validate a SKILL.md against the spec; returns diagnostics, security flags, and a quality score (0-100). Pass `content` or a `slug`. |
72
+ ```bash
73
+ skillmd login # store a token from your skillmd.com account
74
+ skillmd publish ./my-skill # blocked on lint errors
75
+ skillmd publish ./my-skill --dry-run
76
+ skillmd publish ./my-skill --force # publish despite warnings; never bypasses errors
77
+ ```
87
78
 
88
- By default, `skillmd_install` writes to `~/.claude/skills/<name>/SKILL.md`. Pass a `dest` to target another directory.
79
+ ## MCP server: `skillmds`
89
80
 
90
- ## Configuration
81
+ Claude Code:
91
82
 
92
- Both are optional environment variables:
93
-
94
- | Variable | Default | Purpose |
95
- | --- | --- | --- |
96
- | `SKILLMD_API` | `https://skillmd-api.vinoor.workers.dev` | Override the API base URL. |
97
- | `SKILLMD_TOKEN` | – | SkillMD personal access token; enables `skillmd_list_saved` and personalized recommendations. |
83
+ ```bash
84
+ claude mcp add skillmds -- npx -y skillmds
85
+ ```
98
86
 
99
- Example with a token:
87
+ Other MCP clients:
100
88
 
101
89
  ```jsonc
102
90
  {
103
91
  "mcpServers": {
104
- "skillmds": {
105
- "command": "npx",
106
- "args": ["-y", "skillmds"],
107
- "env": { "SKILLMD_TOKEN": "skillmd_pat_…" }
108
- }
92
+ "skillmds": { "command": "npx", "args": ["-y", "skillmds"] }
109
93
  }
110
94
  }
111
95
  ```
112
96
 
113
- ## Safety
114
-
115
- SkillMD shows security flags (executes scripts, network calls, reads secrets, or docs-only) parsed from every skill's files, and **this server never runs a skill's scripts** — `skillmd_install` only writes the `SKILL.md` to disk. Review a skill before relying on it, especially from unverified authors.
97
+ Tools: `skillmd_search`, `skillmd_get`, `skillmd_install`, `skillmd_trending`, `skillmd_recommend`, `skillmd_list_saved`, `skillmd_lint`. `skillmd_install` writes a skill's `SKILL.md` and never executes scripts.
116
98
 
117
- ## Requirements
99
+ ## Configuration
118
100
 
119
- - Node.js **18+**
101
+ Resolution order, highest first:
120
102
 
121
- ## Links
103
+ | Setting | Sources |
104
+ |---|---|
105
+ | Token | `--token` → `SKILLMD_TOKEN` → `~/.skillmd/config.json` |
106
+ | API base | `--api` → `SKILLMD_API` → default registry |
122
107
 
123
- - 🌐 Registry: **[skillmd.com](https://skillmd.com)**
124
- - 🧩 What is an Agent Skill? See the [FAQ](https://skillmd.com/#faq)
108
+ `SKILLMD_TOKEN` enables `skillmd_list_saved`, personalized recommendations, and publishing.
125
109
 
126
110
  ## License
127
111
 
@@ -12,8 +12,17 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
14
14
  import { mkdir, writeFile } from "node:fs/promises";
15
+ import { readFileSync } from "node:fs";
15
16
  import { homedir } from "node:os";
16
17
  import { join, resolve } from "node:path";
18
+
19
+ const VERSION = (() => {
20
+ try {
21
+ return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
22
+ } catch {
23
+ return "0.0.0";
24
+ }
25
+ })();
17
26
  // Shared validation engine, bundled into ./vendor at publish time (npm run build).
18
27
  // Falls back gracefully if the vendor build is absent (e.g. running from source).
19
28
  let lint = null;
@@ -187,7 +196,7 @@ async function handleCall(name, args) {
187
196
  }
188
197
  }
189
198
 
190
- const server = new Server({ name: "skillmd", version: "1.0.1" }, { capabilities: { tools: {} } });
199
+ const server = new Server({ name: "skillmd", version: VERSION }, { capabilities: { tools: {} } });
191
200
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
192
201
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
193
202
  try {
package/dist/cli.js CHANGED
@@ -1,9 +1,12 @@
1
1
  // ../cli/src/index.ts
2
2
  import { Command as Command12 } from "commander";
3
+ import { readFileSync as readFileSync4 } from "fs";
3
4
 
4
5
  // ../cli/src/commands/lint.ts
5
6
  import { Command } from "commander";
6
7
  import { writeFileSync } from "fs";
8
+ import { homedir } from "os";
9
+ import { resolve } from "path";
7
10
 
8
11
  // ../../packages/core/src/parse.ts
9
12
  import { parse as parseYaml } from "yaml";
@@ -272,9 +275,35 @@ function toGithub(results) {
272
275
  import { existsSync, statSync, readFileSync, readdirSync, mkdtempSync } from "fs";
273
276
  import { tmpdir } from "os";
274
277
  import { join, basename, dirname } from "path";
275
- var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".wrangler"]);
276
- function findSkillFiles(dir) {
278
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
279
+ // build/dependency dirs
280
+ "node_modules",
281
+ ".git",
282
+ "dist",
283
+ "build",
284
+ "out",
285
+ ".next",
286
+ ".wrangler",
287
+ ".cache",
288
+ ".npm",
289
+ ".pnpm-store",
290
+ ".venv",
291
+ "venv",
292
+ "__pycache__",
293
+ "target",
294
+ "vendor",
295
+ // large OS / user dirs (mostly relevant when run from a home directory)
296
+ "AppData",
297
+ "Library",
298
+ "$Recycle.Bin",
299
+ "System Volume Information",
300
+ "Windows",
301
+ "Program Files"
302
+ ]);
303
+ var MAX_DEPTH = 12;
304
+ function findSkillFiles(dir, depth = 0) {
277
305
  const out = [];
306
+ if (depth > MAX_DEPTH) return out;
278
307
  let entries;
279
308
  try {
280
309
  entries = readdirSync(dir, { withFileTypes: true });
@@ -284,7 +313,7 @@ function findSkillFiles(dir) {
284
313
  for (const entry of entries) {
285
314
  if (entry.isDirectory()) {
286
315
  if (IGNORE_DIRS.has(entry.name)) continue;
287
- out.push(...findSkillFiles(join(dir, entry.name)));
316
+ out.push(...findSkillFiles(join(dir, entry.name), depth + 1));
288
317
  } else if (entry.name === "SKILL.md") {
289
318
  out.push(join(dir, entry.name));
290
319
  }
@@ -303,7 +332,9 @@ async function resolveSource(arg) {
303
332
  const st = statSync(arg);
304
333
  if (st.isFile()) return [loadLocal(arg)];
305
334
  const files2 = findSkillFiles(arg);
306
- if (files2.length === 0) throw new Error(`No SKILL.md found under ${arg}`);
335
+ if (files2.length === 0) {
336
+ throw new Error(`No SKILL.md found under ${arg}. Point at a skill directory, e.g. "skillmd lint ./my-skill", or run "skillmd init" to create one.`);
337
+ }
307
338
  return files2.map(loadLocal);
308
339
  }
309
340
  const { downloadTemplate } = await import("giget");
@@ -388,6 +419,11 @@ async function runLint(target, flags) {
388
419
  }
389
420
  function lintCommand() {
390
421
  return new Command("lint").alias("check").description("Validate SKILL.md file(s) against the spec and report a quality score").argument("[target]", "local path or GitHub source", ".").option("--format <fmt>", "text | json | sarif | github", "text").option("--strict", "treat warnings as errors").option("--fail-on-warning", "exit non-zero when warnings exist").option("--fix", "apply safe automatic fixes (e.g. add missing license)").action(async (target, opts) => {
422
+ if (resolve(target) === homedir()) {
423
+ process.stderr.write("Warning: scanning your entire home directory. Pass a narrower path, e.g. skillmd lint ./my-skill\n");
424
+ }
425
+ process.stderr.write(`Looking for SKILL.md under ${target} \u2026
426
+ `);
391
427
  const run = await runLint(target, opts);
392
428
  console.log(run.output);
393
429
  process.exitCode = run.exitCode;
@@ -521,11 +557,11 @@ import pc4 from "picocolors";
521
557
 
522
558
  // ../cli/src/config.ts
523
559
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
524
- import { homedir } from "os";
560
+ import { homedir as homedir2 } from "os";
525
561
  import { join as join3 } from "path";
526
562
  var DEFAULT_API = "https://skillmd-api.vinoor.workers.dev";
527
563
  function configPath() {
528
- return join3(homedir(), ".skillmd", "config.json");
564
+ return join3(homedir2(), ".skillmd", "config.json");
529
565
  }
530
566
  function readConfig() {
531
567
  try {
@@ -535,7 +571,7 @@ function readConfig() {
535
571
  }
536
572
  }
537
573
  function writeConfig(cfg) {
538
- const dir = join3(homedir(), ".skillmd");
574
+ const dir = join3(homedir2(), ".skillmd");
539
575
  mkdirSync2(dir, { recursive: true });
540
576
  writeFileSync3(configPath(), JSON.stringify(cfg, null, 2), { encoding: "utf8", mode: 384 });
541
577
  }
@@ -604,8 +640,8 @@ import pc5 from "picocolors";
604
640
 
605
641
  // ../cli/src/agents.ts
606
642
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, rmSync, symlinkSync, existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync3 } from "fs";
607
- import { homedir as homedir2 } from "os";
608
- import { join as join4, resolve, dirname as dirname2 } from "path";
643
+ import { homedir as homedir3 } from "os";
644
+ import { join as join4, resolve as resolve2, dirname as dirname2 } from "path";
609
645
  var AGENTS = [
610
646
  { id: "claude-code", segments: [".claude", "skills"] },
611
647
  { id: "cursor", segments: [".cursor", "skills"] },
@@ -616,7 +652,7 @@ var AGENTS = [
616
652
  function agentDir(agentId, opts = {}) {
617
653
  const agent = AGENTS.find((a) => a.id === agentId);
618
654
  if (!agent) throw new Error(`Unknown agent: ${agentId}. Known: ${AGENTS.map((a) => a.id).join(", ")}`);
619
- const root = opts.global ? homedir2() : opts.cwd ?? process.cwd();
655
+ const root = opts.global ? homedir3() : opts.cwd ?? process.cwd();
620
656
  return join4(root, ...agent.segments);
621
657
  }
622
658
  function detectAgents(opts = {}) {
@@ -627,7 +663,7 @@ function writeSkill(skillName, files, targetDir, opts = {}) {
627
663
  if (opts.mode === "symlink" && opts.canonicalDir) {
628
664
  mkdirSync3(dirname2(dest), { recursive: true });
629
665
  rmSync(dest, { recursive: true, force: true });
630
- symlinkSync(resolve(opts.canonicalDir), dest, "dir");
666
+ symlinkSync(resolve2(opts.canonicalDir), dest, "dir");
631
667
  return dest;
632
668
  }
633
669
  mkdirSync3(dest, { recursive: true });
@@ -920,7 +956,12 @@ function logoutCommand() {
920
956
  }
921
957
 
922
958
  // ../cli/src/index.ts
923
- var program = new Command12("skillmd").version("1.0.0").description("The registry that lints. Discover, validate, and install verified Agent Skills.").option("--json", "machine-readable output").option("--token <token>", "SkillMD personal access token").option("--api <url>", "override the registry API base URL");
959
+ var version = "0.0.0";
960
+ try {
961
+ version = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8")).version;
962
+ } catch {
963
+ }
964
+ var program = new Command12("skillmd").version(version).description("Lint, validate, and install Agent Skills from the SkillMD registry.").option("--json", "machine-readable output").option("--token <token>", "SkillMD personal access token").option("--api <url>", "override the registry API base URL");
924
965
  program.addCommand(lintCommand());
925
966
  program.addCommand(scanCommand());
926
967
  program.addCommand(rulesCommand());
@@ -933,6 +974,10 @@ program.addCommand(updateCommand());
933
974
  program.addCommand(publishCommand());
934
975
  program.addCommand(loginCommand());
935
976
  program.addCommand(logoutCommand());
977
+ if (process.argv.slice(2).length === 0) {
978
+ program.outputHelp();
979
+ process.exit(0);
980
+ }
936
981
  program.parseAsync(process.argv).catch((err) => {
937
982
  console.error(err instanceof Error ? err.message : String(err));
938
983
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillmds",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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": {
@@ -27,7 +27,7 @@
27
27
  "bugs": { "url": "https://skillmd.com/contact" },
28
28
  "author": "SkillMD",
29
29
  "license": "MIT",
30
- "files": ["bin", "dist", "vendor", "README.md"],
30
+ "files": ["bin", "dist", "vendor", "README.md", "CHANGELOG.md"],
31
31
  "engines": { "node": ">=18" },
32
32
  "dependencies": {
33
33
  "@clack/prompts": "^0.8.0",