skillmds 1.0.20 → 1.1.0
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 +125 -113
- package/LICENSE +21 -0
- package/README.md +25 -6
- package/bin/skillmd-mcp.mjs +309 -329
- package/bin/skillmd.mjs +0 -0
- package/dist/cli.js +42 -351
- package/package.json +34 -21
- package/vendor/index.js +0 -333
package/bin/skillmd.mjs
CHANGED
|
File without changes
|
package/dist/cli.js
CHANGED
|
@@ -1,314 +1,17 @@
|
|
|
1
|
-
//
|
|
1
|
+
// src/index.ts
|
|
2
2
|
import { Command as Command13 } from "commander";
|
|
3
3
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// src/commands/lint.ts
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { writeFileSync } from "fs";
|
|
8
8
|
import { homedir } from "os";
|
|
9
9
|
import { resolve } from "path";
|
|
10
10
|
import pc2 from "picocolors";
|
|
11
11
|
import * as p from "@clack/prompts";
|
|
12
|
+
import { lint, toJson, toSarif, toGithub } from "@skillmds/core";
|
|
12
13
|
|
|
13
|
-
//
|
|
14
|
-
import { parse as parseYaml } from "yaml";
|
|
15
|
-
var MAX_SKILL_BYTES = 256 * 1024;
|
|
16
|
-
var MAX_DESCRIPTION = 1024;
|
|
17
|
-
var MAX_NAME = 120;
|
|
18
|
-
var slugify = (s) => String(s).toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "skill";
|
|
19
|
-
function parseSkillMd(raw, type = "single") {
|
|
20
|
-
if (!raw || raw.length > MAX_SKILL_BYTES) return { error: "SKILL.md missing or too large (max 256KB)" };
|
|
21
|
-
const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
22
|
-
if (!m) return { error: "Missing YAML frontmatter (--- ... ---)" };
|
|
23
|
-
let fm;
|
|
24
|
-
try {
|
|
25
|
-
fm = parseYaml(m[1] ?? "") ?? {};
|
|
26
|
-
} catch {
|
|
27
|
-
return { error: "Invalid YAML frontmatter" };
|
|
28
|
-
}
|
|
29
|
-
const name = String(fm.name ?? "").trim();
|
|
30
|
-
const description = String(fm.description ?? "").replace(/\s+/g, " ").trim();
|
|
31
|
-
if (!name) return { error: "frontmatter 'name' is required" };
|
|
32
|
-
if (!description) return { error: "frontmatter 'description' is required" };
|
|
33
|
-
if (description.length > MAX_DESCRIPTION) return { error: `description must be <= ${MAX_DESCRIPTION} chars` };
|
|
34
|
-
return {
|
|
35
|
-
name: name.slice(0, MAX_NAME),
|
|
36
|
-
description: description.slice(0, MAX_DESCRIPTION),
|
|
37
|
-
license: fm.license ? String(fm.license).slice(0, 64) : null,
|
|
38
|
-
body: m[2] ?? "",
|
|
39
|
-
type
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ../../packages/core/src/security.ts
|
|
44
|
-
var UNTRUSTED_INSTALL_RE = new RegExp(
|
|
45
|
-
[
|
|
46
|
-
/(?:npx|npm|yarn|pnpm|bun)\b[^\n]*--registry[= ]/.source,
|
|
47
|
-
// custom npm registry
|
|
48
|
-
/npm\s+config\s+set\s+registry/.source,
|
|
49
|
-
/pip3?\s+install\b[^\n]*(?:--index-url|--extra-index-url|-i\s+https?:)/.source,
|
|
50
|
-
// custom pip index
|
|
51
|
-
/(?:curl|wget)\b[^\n|]*\|\s*(?:sudo\s+)?(?:ba|z|fi)?sh\b/.source,
|
|
52
|
-
// curl … | sh
|
|
53
|
-
/(?:iwr|irm|invoke-webrequest|invoke-restmethod)\b[^\n|]*\|\s*iex\b/.source,
|
|
54
|
-
// PowerShell pipe-to-exec
|
|
55
|
-
/bash\s+<\(\s*(?:curl|wget)/.source,
|
|
56
|
-
// bash <(curl …)
|
|
57
|
-
/(?:pip3?|npm|npx)\s+(?:install|--yes|-y)?\s*[^\n]*https?:\/\/(?!registry\.npmjs\.org|pypi\.org|files\.pythonhosted\.org)[^\s'"`]+\.(?:whl|tar\.gz|tgz|zip)\b/.source
|
|
58
|
-
// install from raw archive URL
|
|
59
|
-
].join("|"),
|
|
60
|
-
"i"
|
|
61
|
-
);
|
|
62
|
-
var RULES = [
|
|
63
|
-
["untrusted_install", UNTRUSTED_INSTALL_RE],
|
|
64
|
-
["executes_scripts", /```(?:sh|bash|python|py|js|ts|ruby|rb)\b|\b(subprocess|os\.system|child_process|exec\()/i],
|
|
65
|
-
["network_calls", /\b(fetch|requests\.|urllib|axios|curl|wget|https?:\/\/|websocket)\b/i],
|
|
66
|
-
["reads_secrets", /\b(api[_-]?key|secret|token|password|os\.environ|process\.env|getenv)\b/i]
|
|
67
|
-
];
|
|
68
|
-
function scanSecurity(body) {
|
|
69
|
-
const findings = [];
|
|
70
|
-
body.split(/\r?\n/).forEach((text3, i) => {
|
|
71
|
-
for (const [flag, re] of RULES) {
|
|
72
|
-
if (re.test(text3)) findings.push({ flag, line: i + 1, snippet: text3.trim().slice(0, 120) });
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
const flags = [...new Set(findings.map((f) => f.flag))];
|
|
76
|
-
return { flags: flags.length ? flags : ["docs_only"], findings };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ../../packages/core/src/rules.ts
|
|
80
|
-
function utf8Bytes(s) {
|
|
81
|
-
let bytes = 0;
|
|
82
|
-
for (let i = 0; i < s.length; i++) {
|
|
83
|
-
const c = s.charCodeAt(i);
|
|
84
|
-
if (c < 128) bytes += 1;
|
|
85
|
-
else if (c < 2048) bytes += 2;
|
|
86
|
-
else if (c >= 55296 && c <= 56319) {
|
|
87
|
-
bytes += 4;
|
|
88
|
-
i++;
|
|
89
|
-
} else bytes += 3;
|
|
90
|
-
}
|
|
91
|
-
return bytes;
|
|
92
|
-
}
|
|
93
|
-
var diag = (rule, message, line) => ({
|
|
94
|
-
id: rule.id,
|
|
95
|
-
severity: rule.severity,
|
|
96
|
-
message,
|
|
97
|
-
...line !== void 0 ? { line } : {}
|
|
98
|
-
});
|
|
99
|
-
var RULES2 = [
|
|
100
|
-
{
|
|
101
|
-
id: "SK002",
|
|
102
|
-
severity: "error",
|
|
103
|
-
description: "name must be present and \u2264 120 chars",
|
|
104
|
-
check(s, _ctx) {
|
|
105
|
-
const out = [];
|
|
106
|
-
if (!s.name.trim()) out.push(diag(this, "frontmatter 'name' is required"));
|
|
107
|
-
else if (s.name.length > MAX_NAME) out.push(diag(this, `name must be \u2264 ${MAX_NAME} chars`));
|
|
108
|
-
return out;
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
id: "SK003",
|
|
113
|
-
severity: "error",
|
|
114
|
-
description: "description must be present and \u2264 1024 chars",
|
|
115
|
-
check(s, _ctx) {
|
|
116
|
-
const out = [];
|
|
117
|
-
if (!s.description.trim()) out.push(diag(this, "frontmatter 'description' is required"));
|
|
118
|
-
else if (s.description.length > MAX_DESCRIPTION) out.push(diag(this, `description must be \u2264 ${MAX_DESCRIPTION} chars`));
|
|
119
|
-
return out;
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
id: "SK010",
|
|
124
|
-
severity: "warn",
|
|
125
|
-
description: "description should be \u2265 20 chars (not too terse)",
|
|
126
|
-
check(s, _ctx) {
|
|
127
|
-
return s.description.trim().length > 0 && s.description.trim().length < 20 ? [diag(this, "description is very terse (< 20 chars); add more detail to help discovery")] : [];
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "SK011",
|
|
132
|
-
severity: "warn",
|
|
133
|
-
description: "license should be declared in frontmatter",
|
|
134
|
-
check(s, _ctx) {
|
|
135
|
-
return s.license ? [] : [diag(this, "no 'license' declared in frontmatter")];
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
id: "SK020",
|
|
140
|
-
severity: "warn",
|
|
141
|
-
description: "body should be \u2265 200 chars (not a stub)",
|
|
142
|
-
check(s, _ctx) {
|
|
143
|
-
return s.body.trim().length < 200 ? [diag(this, "body is very short (< 200 chars); this looks like a stub")] : [];
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: "SK021",
|
|
148
|
-
severity: "warn",
|
|
149
|
-
description: "body should contain at least one Markdown heading",
|
|
150
|
-
check(s, _ctx) {
|
|
151
|
-
return /^#{1,6}\s/m.test(s.body) ? [] : [diag(this, "body has no Markdown headings; add structure with '#' headings")];
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: "SK030",
|
|
156
|
-
severity: "error",
|
|
157
|
-
description: "total SKILL.md size must be \u2264 256KB",
|
|
158
|
-
check(s, _ctx) {
|
|
159
|
-
return utf8Bytes(s.body) > MAX_SKILL_BYTES ? [diag(this, "SKILL.md exceeds 256KB")] : [];
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: "SK040",
|
|
164
|
-
severity: "warn",
|
|
165
|
-
description: "name should match its directory/slug",
|
|
166
|
-
check(s, ctx) {
|
|
167
|
-
if (!ctx.slug) return [];
|
|
168
|
-
const expected = slugify(s.name);
|
|
169
|
-
return expected === ctx.slug ? [] : [diag(this, `name slug '${expected}' does not match directory '${ctx.slug}'`)];
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
];
|
|
173
|
-
function runRules(skill, ctx = {}) {
|
|
174
|
-
return RULES2.flatMap((r) => r.check(skill, ctx));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ../../packages/core/src/quality.ts
|
|
178
|
-
var WEIGHT = {
|
|
179
|
-
error: 30,
|
|
180
|
-
warn: 10,
|
|
181
|
-
info: 3
|
|
182
|
-
};
|
|
183
|
-
var RISK = {
|
|
184
|
-
docs_only: 0,
|
|
185
|
-
reads_secrets: 2,
|
|
186
|
-
network_calls: 1,
|
|
187
|
-
executes_scripts: 5,
|
|
188
|
-
// fetch-and-execute from a non-standard source — the supply-chain pattern the
|
|
189
|
-
// AI verdict floors to "warning"; the heaviest penalty here for the same reason
|
|
190
|
-
untrusted_install: 15
|
|
191
|
-
};
|
|
192
|
-
var VERIFIED_FLOOR = 85;
|
|
193
|
-
function qualityScore(diagnostics, security, opts = {}) {
|
|
194
|
-
let penalty = diagnostics.reduce((sum, d) => sum + WEIGHT[d.severity], 0);
|
|
195
|
-
if (security) {
|
|
196
|
-
for (const flag of security.flags) penalty += RISK[flag] ?? 0;
|
|
197
|
-
}
|
|
198
|
-
const score = Math.max(0, Math.min(100, 100 - penalty));
|
|
199
|
-
const hasError = diagnostics.some((d) => d.severity === "error");
|
|
200
|
-
return opts.verified && !hasError ? Math.max(score, VERIFIED_FLOOR) : score;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// ../../packages/core/src/lint.ts
|
|
204
|
-
function lint(raw, opts = {}) {
|
|
205
|
-
const parsed = parseSkillMd(raw, opts.type ?? "single");
|
|
206
|
-
if ("error" in parsed) {
|
|
207
|
-
const diagnostics2 = [{ id: "SK001", severity: "error", message: parsed.error }];
|
|
208
|
-
const security2 = scanSecurity(raw);
|
|
209
|
-
return {
|
|
210
|
-
ok: false,
|
|
211
|
-
diagnostics: diagnostics2,
|
|
212
|
-
security: security2,
|
|
213
|
-
score: qualityScore(diagnostics2, security2, { verified: opts.verified })
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
const ctx = opts.slug !== void 0 ? { slug: opts.slug } : {};
|
|
217
|
-
const diagnostics = runRules(parsed, ctx);
|
|
218
|
-
const security = scanSecurity(parsed.body);
|
|
219
|
-
const ok = !diagnostics.some((d) => d.severity === "error");
|
|
220
|
-
return { ok, skill: parsed, diagnostics, security, score: qualityScore(diagnostics, security, { verified: opts.verified }) };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ../../packages/core/src/format/text.ts
|
|
224
|
-
var BADGE = { error: "ERROR", warn: " WARN", info: " INFO" };
|
|
225
|
-
function toText(results, opts = {}) {
|
|
226
|
-
const paint = opts.decorate ?? ((t) => t);
|
|
227
|
-
const out = [];
|
|
228
|
-
for (const { file, result } of results) {
|
|
229
|
-
const status = result.ok ? paint("PASS", "ok") : paint("FAIL", "error");
|
|
230
|
-
out.push(`${status} ${file} ${paint(`score ${result.score}/100`, "dim")}`);
|
|
231
|
-
for (const d of result.diagnostics) {
|
|
232
|
-
const where = d.line !== void 0 ? `:${d.line}` : "";
|
|
233
|
-
out.push(` ${paint(BADGE[d.severity], d.severity)} ${d.id}${where} ${d.message}`);
|
|
234
|
-
}
|
|
235
|
-
if (result.security.flags.length && result.security.flags[0] !== "docs_only") {
|
|
236
|
-
out.push(` ${paint("SEC ", "warn")} flags: ${result.security.flags.join(", ")}`);
|
|
237
|
-
}
|
|
238
|
-
out.push("");
|
|
239
|
-
}
|
|
240
|
-
const failed = results.filter((r) => !r.result.ok).length;
|
|
241
|
-
const summary = failed === 0 ? paint(`\u2713 ${results.length} skill(s) passed`, "ok") : paint(`\u2717 ${failed}/${results.length} skill(s) failed`, "error");
|
|
242
|
-
out.push(summary);
|
|
243
|
-
return out.join("\n");
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// ../../packages/core/src/format/json.ts
|
|
247
|
-
function toJson(results) {
|
|
248
|
-
return results.map(({ file, result }) => ({
|
|
249
|
-
file,
|
|
250
|
-
ok: result.ok,
|
|
251
|
-
score: result.score,
|
|
252
|
-
diagnostics: result.diagnostics,
|
|
253
|
-
security: result.security
|
|
254
|
-
}));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ../../packages/core/src/format/sarif.ts
|
|
258
|
-
var SARIF_LEVEL = { error: "error", warn: "warning", info: "note" };
|
|
259
|
-
function toSarif(results) {
|
|
260
|
-
const sarifResults = results.flatMap(
|
|
261
|
-
({ file, result }) => result.diagnostics.map((d) => ({
|
|
262
|
-
ruleId: d.id,
|
|
263
|
-
level: SARIF_LEVEL[d.severity] ?? "note",
|
|
264
|
-
message: { text: d.message },
|
|
265
|
-
locations: [
|
|
266
|
-
{
|
|
267
|
-
physicalLocation: {
|
|
268
|
-
artifactLocation: { uri: file },
|
|
269
|
-
region: { startLine: d.line ?? 1 }
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
]
|
|
273
|
-
}))
|
|
274
|
-
);
|
|
275
|
-
return {
|
|
276
|
-
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
277
|
-
version: "2.1.0",
|
|
278
|
-
runs: [
|
|
279
|
-
{
|
|
280
|
-
tool: {
|
|
281
|
-
driver: {
|
|
282
|
-
name: "skillmd",
|
|
283
|
-
informationUri: "https://skillmd.com",
|
|
284
|
-
rules: RULES2.map((r) => ({
|
|
285
|
-
id: r.id,
|
|
286
|
-
shortDescription: { text: r.description },
|
|
287
|
-
defaultConfiguration: { level: SARIF_LEVEL[r.severity] ?? "note" }
|
|
288
|
-
}))
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
results: sarifResults
|
|
292
|
-
}
|
|
293
|
-
]
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// ../../packages/core/src/format/github.ts
|
|
298
|
-
var LEVEL = { error: "error", warn: "warning", info: "notice" };
|
|
299
|
-
function toGithub(results) {
|
|
300
|
-
const lines = [];
|
|
301
|
-
for (const { file, result } of results) {
|
|
302
|
-
for (const d of result.diagnostics) {
|
|
303
|
-
const level = LEVEL[d.severity] ?? "notice";
|
|
304
|
-
const loc = d.line !== void 0 ? `,line=${d.line}` : "";
|
|
305
|
-
lines.push(`::${level} file=${file}${loc},title=${d.id}::${d.message}`);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return lines.join("\n");
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// ../cli/src/source.ts
|
|
14
|
+
// src/source.ts
|
|
312
15
|
import { existsSync, statSync, lstatSync, readFileSync, readdirSync, mkdtempSync, rmSync } from "fs";
|
|
313
16
|
import { tmpdir } from "os";
|
|
314
17
|
import { join, basename, dirname, relative, sep } from "path";
|
|
@@ -427,7 +130,7 @@ function normalizeGiget(arg) {
|
|
|
427
130
|
return `github:${arg}`;
|
|
428
131
|
}
|
|
429
132
|
|
|
430
|
-
//
|
|
133
|
+
// src/ui.ts
|
|
431
134
|
import pc from "picocolors";
|
|
432
135
|
var decorate = (text3, kind) => {
|
|
433
136
|
switch (kind) {
|
|
@@ -561,7 +264,7 @@ function exitCodeFor(results, policy = {}) {
|
|
|
561
264
|
return 0;
|
|
562
265
|
}
|
|
563
266
|
|
|
564
|
-
//
|
|
267
|
+
// src/commands/lint.ts
|
|
565
268
|
function applyLicenseFix(raw) {
|
|
566
269
|
if (/^---[\s\S]*?\blicense:\s*\S+[\s\S]*?^---/m.test(raw)) return null;
|
|
567
270
|
const m = raw.match(/^(---\r?\n)([\s\S]*?)(\r?\n---)/);
|
|
@@ -633,8 +336,9 @@ function lintCommand() {
|
|
|
633
336
|
});
|
|
634
337
|
}
|
|
635
338
|
|
|
636
|
-
//
|
|
339
|
+
// src/commands/scan.ts
|
|
637
340
|
import { Command as Command2 } from "commander";
|
|
341
|
+
import { scanSecurity, parseSkillMd } from "@skillmds/core";
|
|
638
342
|
import pc3 from "picocolors";
|
|
639
343
|
async function runScan(target, flags) {
|
|
640
344
|
const resolved = await resolveSource(target);
|
|
@@ -673,12 +377,13 @@ function scanCommand() {
|
|
|
673
377
|
});
|
|
674
378
|
}
|
|
675
379
|
|
|
676
|
-
//
|
|
380
|
+
// src/commands/rules.ts
|
|
677
381
|
import { Command as Command3 } from "commander";
|
|
382
|
+
import { RULES } from "@skillmds/core";
|
|
678
383
|
import pc4 from "picocolors";
|
|
679
384
|
function rulesCommand() {
|
|
680
385
|
return new Command3("rules").description("List lint rules, or show detail for one rule id").argument("[id]", "a rule id, e.g. SK010").option("--json", "machine-readable output").action((id, opts) => {
|
|
681
|
-
const list = id ?
|
|
386
|
+
const list = id ? RULES.filter((r) => r.id.toLowerCase() === id.toLowerCase()) : RULES;
|
|
682
387
|
if (id && list.length === 0) {
|
|
683
388
|
console.error(`Unknown rule: ${id}`);
|
|
684
389
|
process.exitCode = 1;
|
|
@@ -695,10 +400,11 @@ function rulesCommand() {
|
|
|
695
400
|
});
|
|
696
401
|
}
|
|
697
402
|
|
|
698
|
-
//
|
|
403
|
+
// src/commands/init.ts
|
|
699
404
|
import { Command as Command4 } from "commander";
|
|
700
405
|
import { mkdirSync, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
701
406
|
import { join as join2 } from "path";
|
|
407
|
+
import { slugify } from "@skillmds/core";
|
|
702
408
|
import * as p2 from "@clack/prompts";
|
|
703
409
|
function skillTemplate(name, description, license) {
|
|
704
410
|
const desc = description.trim() || `A skill that helps agents with ${name}.`;
|
|
@@ -756,15 +462,15 @@ function initCommand() {
|
|
|
756
462
|
});
|
|
757
463
|
}
|
|
758
464
|
|
|
759
|
-
//
|
|
465
|
+
// src/commands/search.ts
|
|
760
466
|
import { Command as Command5 } from "commander";
|
|
761
467
|
import pc5 from "picocolors";
|
|
762
468
|
|
|
763
|
-
//
|
|
469
|
+
// src/api.ts
|
|
764
470
|
import { createHash } from "crypto";
|
|
765
|
-
import {
|
|
471
|
+
import { isAllowedSourceUrl } from "@skillmds/core";
|
|
766
472
|
|
|
767
|
-
//
|
|
473
|
+
// src/config.ts
|
|
768
474
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
769
475
|
import { homedir as homedir2 } from "os";
|
|
770
476
|
import { join as join3 } from "path";
|
|
@@ -791,7 +497,8 @@ function resolveApi(flags = {}) {
|
|
|
791
497
|
return flags.api || process.env.SKILLMD_API || readConfig().api || DEFAULT_API;
|
|
792
498
|
}
|
|
793
499
|
|
|
794
|
-
//
|
|
500
|
+
// src/api.ts
|
|
501
|
+
import { isAllowedSourceUrl as isAllowedSourceUrl2, reconstructSkillMd, skillMdFor } from "@skillmds/core";
|
|
795
502
|
var IntegrityError = class extends Error {
|
|
796
503
|
integrity = true;
|
|
797
504
|
constructor(message) {
|
|
@@ -807,15 +514,6 @@ var RegistryError = class extends Error {
|
|
|
807
514
|
}
|
|
808
515
|
status;
|
|
809
516
|
};
|
|
810
|
-
var ALLOWED_SOURCE_HOSTS = /* @__PURE__ */ new Set(["raw.githubusercontent.com", "gist.githubusercontent.com"]);
|
|
811
|
-
function isAllowedSourceUrl(u) {
|
|
812
|
-
try {
|
|
813
|
-
const parsed = new URL(u);
|
|
814
|
-
return parsed.protocol === "https:" && ALLOWED_SOURCE_HOSTS.has(parsed.hostname.toLowerCase());
|
|
815
|
-
} catch {
|
|
816
|
-
return false;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
517
|
function createClient(flags = {}) {
|
|
820
518
|
const base = resolveApi(flags);
|
|
821
519
|
const token = resolveToken(flags);
|
|
@@ -862,20 +560,8 @@ async function fetchBundle(base, slug, token) {
|
|
|
862
560
|
}
|
|
863
561
|
return out;
|
|
864
562
|
}
|
|
865
|
-
function skillMdFor(skill) {
|
|
866
|
-
if (skill.raw_md) return skill.raw_md;
|
|
867
|
-
return reconstructSkillMd(skill);
|
|
868
|
-
}
|
|
869
|
-
function reconstructSkillMd(skill) {
|
|
870
|
-
const name = skill.slug.split("/")[1] || skill.title;
|
|
871
|
-
const fm = { name, description: String(skill.description).replace(/\n/g, " ") };
|
|
872
|
-
if (skill.license) fm.license = skill.license;
|
|
873
|
-
return `---
|
|
874
|
-
${stringifyYaml(fm)}---
|
|
875
|
-
${skill.body_md || ""}`;
|
|
876
|
-
}
|
|
877
563
|
|
|
878
|
-
//
|
|
564
|
+
// src/commands/search.ts
|
|
879
565
|
var MAX_LINE_WIDTH = 100;
|
|
880
566
|
function formatStars(n) {
|
|
881
567
|
if (n >= 1e6) {
|
|
@@ -925,7 +611,7 @@ function searchCommand() {
|
|
|
925
611
|
});
|
|
926
612
|
}
|
|
927
613
|
|
|
928
|
-
//
|
|
614
|
+
// src/commands/info.ts
|
|
929
615
|
import { Command as Command6 } from "commander";
|
|
930
616
|
import pc6 from "picocolors";
|
|
931
617
|
async function runInfo(slug, flags) {
|
|
@@ -974,14 +660,15 @@ function infoCommand() {
|
|
|
974
660
|
});
|
|
975
661
|
}
|
|
976
662
|
|
|
977
|
-
//
|
|
663
|
+
// src/commands/add.ts
|
|
978
664
|
import { Command as Command7, Option } from "commander";
|
|
979
665
|
import { existsSync as existsSync4 } from "fs";
|
|
980
666
|
import { homedir as homedir4 } from "os";
|
|
981
667
|
import { resolve as resolve3 } from "path";
|
|
982
668
|
import pc7 from "picocolors";
|
|
669
|
+
import { lint as lint2 } from "@skillmds/core";
|
|
983
670
|
|
|
984
|
-
//
|
|
671
|
+
// src/agents.ts
|
|
985
672
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, rmSync as rmSync2, symlinkSync, existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync3 } from "fs";
|
|
986
673
|
import { homedir as homedir3 } from "os";
|
|
987
674
|
import { join as join4, resolve as resolve2, dirname as dirname2, sep as sep2 } from "path";
|
|
@@ -1106,7 +793,7 @@ function installedSkills(opts = {}) {
|
|
|
1106
793
|
return out;
|
|
1107
794
|
}
|
|
1108
795
|
|
|
1109
|
-
//
|
|
796
|
+
// src/commands/add.ts
|
|
1110
797
|
function isLocalish(arg) {
|
|
1111
798
|
return arg === "." || arg.startsWith("./") || arg.startsWith("../") || arg.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(arg) || existsSync4(arg);
|
|
1112
799
|
}
|
|
@@ -1229,7 +916,7 @@ async function runAdd(arg, flags, deps = defaultDeps) {
|
|
|
1229
916
|
dirAgents.set(dir, [...dirAgents.get(dir) ?? [], agentId]);
|
|
1230
917
|
}
|
|
1231
918
|
for (const c of candidates) {
|
|
1232
|
-
const result =
|
|
919
|
+
const result = lint2(c.raw, { slug: c.slug });
|
|
1233
920
|
const allFlags = [.../* @__PURE__ */ new Set([...result.security.flags, ...c.securityFlags ?? []])];
|
|
1234
921
|
const deniedFlags = allFlags.filter((f) => deny.has(f));
|
|
1235
922
|
if (!flags.skipLint && !result.ok) {
|
|
@@ -1271,9 +958,10 @@ function addCommand() {
|
|
|
1271
958
|
});
|
|
1272
959
|
}
|
|
1273
960
|
|
|
1274
|
-
//
|
|
961
|
+
// src/commands/list.ts
|
|
1275
962
|
import { Command as Command8 } from "commander";
|
|
1276
963
|
import pc8 from "picocolors";
|
|
964
|
+
import { parseSkillMd as parseSkillMd2 } from "@skillmds/core";
|
|
1277
965
|
function listCommand() {
|
|
1278
966
|
return new Command8("list").alias("ls").description("List installed skills across detected agents").option("-g, --global", "only global skills").option("--json", "machine-readable output").action((opts) => {
|
|
1279
967
|
const scope = { global: opts.global };
|
|
@@ -1287,7 +975,7 @@ function listCommand() {
|
|
|
1287
975
|
return;
|
|
1288
976
|
}
|
|
1289
977
|
for (const s of skills) {
|
|
1290
|
-
const parsed =
|
|
978
|
+
const parsed = parseSkillMd2(s.raw);
|
|
1291
979
|
const desc = "error" in parsed ? pc8.red(`(invalid SKILL.md \u2014 ${parsed.error})`) : pc8.dim(parsed.description.slice(0, 80));
|
|
1292
980
|
console.log(`${pc8.bold(s.name)} ${pc8.dim(`[${s.agent}/${s.scope}]`)}
|
|
1293
981
|
${desc}`);
|
|
@@ -1295,7 +983,7 @@ function listCommand() {
|
|
|
1295
983
|
});
|
|
1296
984
|
}
|
|
1297
985
|
|
|
1298
|
-
//
|
|
986
|
+
// src/commands/remove.ts
|
|
1299
987
|
import { Command as Command9 } from "commander";
|
|
1300
988
|
import { rmSync as rmSync3 } from "fs";
|
|
1301
989
|
import pc9 from "picocolors";
|
|
@@ -1321,11 +1009,12 @@ function removeCommand() {
|
|
|
1321
1009
|
});
|
|
1322
1010
|
}
|
|
1323
1011
|
|
|
1324
|
-
//
|
|
1012
|
+
// src/commands/update.ts
|
|
1325
1013
|
import { Command as Command10 } from "commander";
|
|
1326
1014
|
import { writeFileSync as writeFileSync5 } from "fs";
|
|
1327
1015
|
import { join as join5 } from "path";
|
|
1328
1016
|
import pc10 from "picocolors";
|
|
1017
|
+
import { lint as lint3 } from "@skillmds/core";
|
|
1329
1018
|
async function runUpdate(names, flags, scope = {}) {
|
|
1330
1019
|
const { api } = createClient(flags);
|
|
1331
1020
|
const installed = installedSkills(scope).filter((s) => names.length === 0 || names.includes(s.name));
|
|
@@ -1345,7 +1034,7 @@ async function runUpdate(names, flags, scope = {}) {
|
|
|
1345
1034
|
lines.push(pc10.dim(`- ${s.name} already up to date`));
|
|
1346
1035
|
continue;
|
|
1347
1036
|
}
|
|
1348
|
-
const result =
|
|
1037
|
+
const result = lint3(next, { slug: s.name });
|
|
1349
1038
|
if (!result.ok) {
|
|
1350
1039
|
lines.push(pc10.red(`\u2717 ${s.name} update failed lint \u2014 skipped`));
|
|
1351
1040
|
continue;
|
|
@@ -1372,9 +1061,10 @@ function updateCommand() {
|
|
|
1372
1061
|
});
|
|
1373
1062
|
}
|
|
1374
1063
|
|
|
1375
|
-
//
|
|
1064
|
+
// src/commands/publish.ts
|
|
1376
1065
|
import { Command as Command11 } from "commander";
|
|
1377
1066
|
import pc11 from "picocolors";
|
|
1067
|
+
import { lint as lint4, toText } from "@skillmds/core";
|
|
1378
1068
|
var defaultDeps2 = {
|
|
1379
1069
|
resolve: resolveSource,
|
|
1380
1070
|
async submit(payload, flags) {
|
|
@@ -1390,10 +1080,10 @@ var defaultDeps2 = {
|
|
|
1390
1080
|
async function runPublish(target, flags, deps = defaultDeps2) {
|
|
1391
1081
|
const sources = await deps.resolve(target);
|
|
1392
1082
|
if (sources.length !== 1) {
|
|
1393
|
-
return { result:
|
|
1083
|
+
return { result: lint4(""), published: false, exitCode: 1, output: `Expected exactly one SKILL.md, found ${sources.length}.` };
|
|
1394
1084
|
}
|
|
1395
1085
|
const src = sources[0];
|
|
1396
|
-
const result =
|
|
1086
|
+
const result = lint4(src.raw, { slug: src.slug });
|
|
1397
1087
|
const report = toText([{ file: src.path, result }], { decorate });
|
|
1398
1088
|
if (!result.ok) {
|
|
1399
1089
|
return { result, published: false, exitCode: 1, output: `${report}
|
|
@@ -1421,7 +1111,7 @@ function publishCommand() {
|
|
|
1421
1111
|
});
|
|
1422
1112
|
}
|
|
1423
1113
|
|
|
1424
|
-
//
|
|
1114
|
+
// src/commands/login.ts
|
|
1425
1115
|
import { Command as Command12 } from "commander";
|
|
1426
1116
|
import * as p3 from "@clack/prompts";
|
|
1427
1117
|
function loginCommand() {
|
|
@@ -1456,9 +1146,10 @@ function logoutCommand() {
|
|
|
1456
1146
|
});
|
|
1457
1147
|
}
|
|
1458
1148
|
|
|
1459
|
-
//
|
|
1149
|
+
// src/commands/interactive.ts
|
|
1460
1150
|
import * as p4 from "@clack/prompts";
|
|
1461
1151
|
import pc12 from "picocolors";
|
|
1152
|
+
import { parseSkillMd as parseSkillMd3 } from "@skillmds/core";
|
|
1462
1153
|
function bail(v) {
|
|
1463
1154
|
if (p4.isCancel(v)) {
|
|
1464
1155
|
p4.cancel("Cancelled.");
|
|
@@ -1604,7 +1295,7 @@ async function runInteractive() {
|
|
|
1604
1295
|
} else {
|
|
1605
1296
|
p4.log.message(
|
|
1606
1297
|
skills.map((sk) => {
|
|
1607
|
-
const parsed =
|
|
1298
|
+
const parsed = parseSkillMd3(sk.raw);
|
|
1608
1299
|
const desc = "error" in parsed ? pc12.red(`(invalid SKILL.md \u2014 ${parsed.error})`) : pc12.dim(parsed.description.slice(0, 70));
|
|
1609
1300
|
return `${pc12.bold(sk.name)} ${pc12.dim(`[${sk.agent}/${sk.scope}]`)}
|
|
1610
1301
|
${desc}`;
|
|
@@ -1635,7 +1326,7 @@ async function runInteractive() {
|
|
|
1635
1326
|
p4.outro(pc12.dim("skillmd.com"));
|
|
1636
1327
|
}
|
|
1637
1328
|
|
|
1638
|
-
//
|
|
1329
|
+
// src/index.ts
|
|
1639
1330
|
var version = "0.0.0";
|
|
1640
1331
|
try {
|
|
1641
1332
|
version = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8")).version;
|
package/package.json
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillmds",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "SkillMD for agents: the `skillmds` MCP server + the `skillmd` CLI (lint, scan, search, install, publish Agent Skills) from skillmd.com.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SkillMD",
|
|
5
7
|
"type": "module",
|
|
8
|
+
"mcpName": "com.skillmd/skillmd",
|
|
6
9
|
"bin": {
|
|
7
10
|
"skillmds": "bin/skillmd-mcp.mjs",
|
|
8
11
|
"skillmd": "bin/skillmd.mjs"
|
|
9
12
|
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"dist",
|
|
16
|
+
"assets",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
10
23
|
"keywords": [
|
|
11
24
|
"mcp",
|
|
12
25
|
"model-context-protocol",
|
|
@@ -23,37 +36,37 @@
|
|
|
23
36
|
"ai-agents",
|
|
24
37
|
"llm"
|
|
25
38
|
],
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/skillmds/skillmd.git",
|
|
42
|
+
"directory": "packages/skillmds"
|
|
43
|
+
},
|
|
26
44
|
"homepage": "https://skillmd.com",
|
|
27
45
|
"bugs": {
|
|
28
|
-
"url": "https://
|
|
46
|
+
"url": "https://github.com/skillmds/skillmd/issues"
|
|
29
47
|
},
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"CHANGELOG.md"
|
|
39
|
-
],
|
|
40
|
-
"engines": {
|
|
41
|
-
"node": ">=18"
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"prepublishOnly": "npm run build"
|
|
42
56
|
},
|
|
43
57
|
"dependencies": {
|
|
44
58
|
"@clack/prompts": "^0.8.0",
|
|
45
59
|
"@modelcontextprotocol/sdk": "^1.13.0",
|
|
60
|
+
"@skillmds/core": "^1.0.0",
|
|
46
61
|
"commander": "^12.1.0",
|
|
47
|
-
"giget": "^
|
|
62
|
+
"giget": "^3.3.0",
|
|
48
63
|
"picocolors": "^1.1.0",
|
|
49
64
|
"yaml": "^2.6.1"
|
|
50
65
|
},
|
|
51
66
|
"devDependencies": {
|
|
52
|
-
"@
|
|
53
|
-
"tsup": "^8.3.0"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"build": "tsup",
|
|
57
|
-
"prepublishOnly": "tsup"
|
|
67
|
+
"@types/node": "^22.10.0",
|
|
68
|
+
"tsup": "^8.3.0",
|
|
69
|
+
"typescript": "^5.7.2",
|
|
70
|
+
"vitest": "^2.1.0"
|
|
58
71
|
}
|
|
59
72
|
}
|