skillmds 1.0.7 → 1.0.9

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,14 @@
2
2
 
3
3
  All notable changes to `skillmds` are documented here. Versions follow [semver](https://semver.org).
4
4
 
5
+ ## 1.0.9
6
+
7
+ - Compact lint output: one line per skill (status dot, score, name, rule-id chips) with a single summary line, instead of a tall multi-row block per skill. Linting a single skill still shows full diagnostic messages.
8
+
9
+ ## 1.0.8
10
+
11
+ - `skillmds` with no command in a terminal now opens the interactive CLI menu; it only starts the MCP server when launched over piped stdio (how MCP clients run it). Previously a bare `npx skillmds` in a terminal appeared to hang on the silent MCP server.
12
+
5
13
  ## 1.0.7
6
14
 
7
15
  - Nicer CLI output: colored PASS/FAIL, quality-score bars, `ERROR`/`WARN`/`INFO` badges, and a summary with average score.
@@ -211,16 +211,21 @@ async function startMcpServer() {
211
211
  console.error("skillmd MCP server running (stdio)");
212
212
  }
213
213
 
214
- // This binary is invoked two ways:
215
- // - `skillmds` → start the MCP server (stdio; how MCP clients launch it)
214
+ // This binary is invoked three ways:
216
215
  // - `skillmds lint .` etc. → run the CLI (so `npx skillmds <cmd>` works without -p)
216
+ // - `skillmds` in a terminal → run the CLI (opens the interactive menu)
217
+ // - `skillmds` over piped stdio → start the MCP server (how MCP clients launch it)
218
+ // MCP clients always connect stdin as a pipe (never a TTY), so an interactive
219
+ // stdin is a reliable signal that a human is present.
217
220
  const CLI_COMMANDS = new Set([
218
221
  "lint", "check", "scan", "rules", "init", "search", "add", "list", "ls",
219
222
  "remove", "rm", "update", "publish", "submit", "login", "logout", "help",
220
223
  "--help", "-h", "--version", "-V",
221
224
  ]);
222
225
  const first = process.argv[2];
223
- if (first && CLI_COMMANDS.has(first)) {
226
+ const isCliCommand = first && CLI_COMMANDS.has(first);
227
+ const humanInTerminal = !first && process.stdin.isTTY;
228
+ if (isCliCommand || humanInTerminal) {
224
229
  await import("../dist/cli.js"); // the CLI parses process.argv itself
225
230
  } else {
226
231
  await startMcpServer();
package/dist/cli.js CHANGED
@@ -370,40 +370,63 @@ var decorate = (text3, kind) => {
370
370
  return pc.dim(text3);
371
371
  }
372
372
  };
373
- var badge = {
374
- error: pc.bgRed(pc.white(" ERROR ")),
375
- warn: pc.bgYellow(pc.black(" WARN ")),
376
- info: pc.bgCyan(pc.black(" INFO "))
373
+ var GLYPH = {
374
+ error: pc.red("\u2716"),
375
+ warn: pc.yellow("\u26A0"),
376
+ info: pc.cyan("\xB7")
377
377
  };
378
- function scoreBar(score) {
379
- const filled = Math.max(0, Math.min(10, Math.round(score / 10)));
380
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
381
- const color = score >= 80 ? pc.green : score >= 50 ? pc.yellow : pc.red;
382
- return `${color(bar)} ${pc.bold(String(score))}${pc.dim("/100")}`;
378
+ var scoreColor = (s) => s >= 80 ? pc.green : s >= 50 ? pc.yellow : pc.red;
379
+ function label(file) {
380
+ const parts = file.split(/[\\/]/).filter(Boolean);
381
+ const i = parts.lastIndexOf("SKILL.md");
382
+ return i > 0 ? parts[i - 1] : parts[parts.length - 1] || file;
383
383
  }
384
- function renderReport(results) {
385
- const out = [];
386
- for (const { file, result } of results) {
387
- const status = result.ok ? pc.green("\u25CF") + pc.bold(" PASS") : pc.red("\u25CF") + pc.bold(" FAIL");
388
- out.push("");
389
- out.push(`${status} ${pc.bold(file)}`);
390
- out.push(` ${scoreBar(result.score)}`);
391
- for (const d of result.diagnostics) {
392
- const loc = d.line !== void 0 ? pc.dim(`:${d.line}`) : "";
393
- out.push(` ${badge[d.severity]} ${pc.dim(d.id)}${loc} ${d.message}`);
394
- }
395
- const flags = result.security.flags.filter((f) => f !== "docs_only");
396
- if (flags.length) out.push(` ${pc.magenta("\u2691 security")} ${pc.dim(flags.join(", "))}`);
384
+ function statusDot(result) {
385
+ if (!result.ok) return pc.red("\u25CF");
386
+ return result.diagnostics.some((d) => d.severity === "warn") ? pc.yellow("\u25CF") : pc.green("\u25CF");
387
+ }
388
+ function securityFlags(result) {
389
+ return result.security.flags.filter((f) => f !== "docs_only");
390
+ }
391
+ function renderOne(file, result) {
392
+ const status = result.ok ? pc.green("\u25CF PASS") : pc.red("\u25CF FAIL");
393
+ const out = [`${status} ${pc.bold(label(file))} ${scoreColor(result.score)(`${result.score}/100`)}`];
394
+ for (const d of result.diagnostics) {
395
+ const loc = d.line !== void 0 ? pc.dim(`:${d.line}`) : "";
396
+ out.push(` ${GLYPH[d.severity]} ${pc.dim(d.id)}${loc} ${d.message}`);
397
397
  }
398
+ const flags = securityFlags(result);
399
+ if (flags.length) out.push(` ${pc.magenta(`\u2691 ${flags.join(", ")}`)}`);
400
+ return out.join("\n");
401
+ }
402
+ function renderMany(results) {
403
+ const names = results.map((r) => label(r.file));
404
+ const w = Math.min(34, Math.max(4, ...names.map((n) => n.length)));
405
+ const out = [];
406
+ results.forEach(({ result }, i) => {
407
+ const score = scoreColor(result.score)(String(result.score).padStart(3));
408
+ const name = names[i].length > w ? names[i].slice(0, w - 1) + "\u2026" : names[i].padEnd(w);
409
+ const issues = result.diagnostics.map((d) => `${GLYPH[d.severity]}${pc.dim(d.id)}`).join(" ");
410
+ const flags = securityFlags(result);
411
+ const sec = flags.length ? ` ${pc.magenta(`\u2691 ${flags.join(",")}`)}` : "";
412
+ out.push(`${statusDot(result)} ${score} ${name} ${issues}${sec}`.trimEnd());
413
+ });
398
414
  const failed = results.filter((r) => !r.result.ok).length;
399
- const passed = results.length - failed;
400
- const avg = results.length ? Math.round(results.reduce((s, r) => s + r.result.score, 0) / results.length) : 0;
415
+ const warned = results.filter((r) => r.result.ok && r.result.diagnostics.some((d) => d.severity === "warn")).length;
416
+ const clean = results.length - failed - warned;
417
+ const avg = Math.round(results.reduce((s, r) => s + r.result.score, 0) / results.length);
401
418
  out.push("");
402
- out.push(pc.dim("\u2500".repeat(44)));
403
- const tally = failed === 0 ? pc.green(`\u2713 ${passed} passed`) : `${pc.green(`${passed} passed`)} ${pc.red(`\u2717 ${failed} failed`)}`;
404
- out.push(`${tally} ${pc.dim("avg")} ${scoreBar(avg)}`);
419
+ const parts = [pc.green(`${clean} clean`)];
420
+ if (warned) parts.push(pc.yellow(`${warned} warn`));
421
+ if (failed) parts.push(pc.red(`${failed} fail`));
422
+ out.push(`${parts.join(pc.dim(" \xB7 "))} ${pc.dim("avg")} ${scoreColor(avg)(`${avg}/100`)}`);
405
423
  return out.join("\n");
406
424
  }
425
+ function renderReport(results) {
426
+ if (results.length === 0) return pc.dim("No skills found.");
427
+ if (results.length === 1) return renderOne(results[0].file, results[0].result);
428
+ return renderMany(results);
429
+ }
407
430
  function exitCodeFor(results, policy = {}) {
408
431
  for (const { result } of results) {
409
432
  const hasError = result.diagnostics.some((d) => d.severity === "error");
@@ -540,9 +563,9 @@ function rulesCommand() {
540
563
  console.log(JSON.stringify(list.map((r) => ({ id: r.id, severity: r.severity, description: r.description })), null, 2));
541
564
  return;
542
565
  }
543
- const badge2 = (sev) => sev === "error" ? pc4.bgRed(pc4.white(" error ")) : sev === "warn" ? pc4.bgYellow(pc4.black(" warn ")) : pc4.bgCyan(pc4.black(" info "));
566
+ const badge = (sev) => sev === "error" ? pc4.bgRed(pc4.white(" error ")) : sev === "warn" ? pc4.bgYellow(pc4.black(" warn ")) : pc4.bgCyan(pc4.black(" info "));
544
567
  console.log(pc4.bold("\nLint rules\n"));
545
- for (const r of list) console.log(` ${pc4.bold(r.id)} ${badge2(r.severity)} ${r.description}`);
568
+ for (const r of list) console.log(` ${pc4.bold(r.id)} ${badge(r.severity)} ${r.description}`);
546
569
  console.log("");
547
570
  });
548
571
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillmds",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
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": {