skillmds 1.0.12 → 1.0.14
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 +21 -0
- package/bin/skillmd-mcp.mjs +253 -232
- package/dist/cli.js +295 -149
- package/package.json +15 -4
- package/vendor/index.js +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ../cli/src/index.ts
|
|
2
|
-
import { Command as
|
|
2
|
+
import { Command as Command13 } from "commander";
|
|
3
3
|
import { readFileSync as readFileSync4 } from "fs";
|
|
4
4
|
|
|
5
5
|
// ../cli/src/commands/lint.ts
|
|
@@ -13,7 +13,7 @@ import * as p from "@clack/prompts";
|
|
|
13
13
|
// ../../packages/core/src/parse.ts
|
|
14
14
|
import { parse as parseYaml } from "yaml";
|
|
15
15
|
var MAX_SKILL_BYTES = 256 * 1024;
|
|
16
|
-
var MAX_DESCRIPTION =
|
|
16
|
+
var MAX_DESCRIPTION = 1024;
|
|
17
17
|
var MAX_NAME = 120;
|
|
18
18
|
var slugify = (s) => String(s).toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "skill";
|
|
19
19
|
function parseSkillMd(raw, type = "single") {
|
|
@@ -30,7 +30,7 @@ function parseSkillMd(raw, type = "single") {
|
|
|
30
30
|
const description = String(fm.description ?? "").replace(/\s+/g, " ").trim();
|
|
31
31
|
if (!name) return { error: "frontmatter 'name' is required" };
|
|
32
32
|
if (!description) return { error: "frontmatter 'description' is required" };
|
|
33
|
-
if (description.length > MAX_DESCRIPTION) return { error:
|
|
33
|
+
if (description.length > MAX_DESCRIPTION) return { error: `description must be <= ${MAX_DESCRIPTION} chars` };
|
|
34
34
|
return {
|
|
35
35
|
name: name.slice(0, MAX_NAME),
|
|
36
36
|
description: description.slice(0, MAX_DESCRIPTION),
|
|
@@ -92,7 +92,7 @@ var RULES2 = [
|
|
|
92
92
|
{
|
|
93
93
|
id: "SK003",
|
|
94
94
|
severity: "error",
|
|
95
|
-
description: "description must be present and \u2264
|
|
95
|
+
description: "description must be present and \u2264 1024 chars",
|
|
96
96
|
check(s, _ctx) {
|
|
97
97
|
const out = [];
|
|
98
98
|
if (!s.description.trim()) out.push(diag(this, "frontmatter 'description' is required"));
|
|
@@ -284,9 +284,9 @@ function toGithub(results) {
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
// ../cli/src/source.ts
|
|
287
|
-
import { existsSync, statSync, readFileSync, readdirSync, mkdtempSync } from "fs";
|
|
287
|
+
import { existsSync, statSync, lstatSync, readFileSync, readdirSync, mkdtempSync, rmSync } from "fs";
|
|
288
288
|
import { tmpdir } from "os";
|
|
289
|
-
import { join, basename, dirname } from "path";
|
|
289
|
+
import { join, basename, dirname, relative, sep } from "path";
|
|
290
290
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
291
291
|
// build/dependency dirs
|
|
292
292
|
"node_modules",
|
|
@@ -357,6 +357,44 @@ async function resolveSource(arg) {
|
|
|
357
357
|
if (files.length === 0) throw new Error(`No SKILL.md found in ${arg}`);
|
|
358
358
|
return files.map(loadLocal);
|
|
359
359
|
}
|
|
360
|
+
var MAX_PACK_FILES = 200;
|
|
361
|
+
var MAX_PACK_BYTES = 20 * 1024 * 1024;
|
|
362
|
+
function collectFiles(root, dir, limits = {}) {
|
|
363
|
+
const maxFiles = limits.maxFiles ?? MAX_PACK_FILES;
|
|
364
|
+
const maxBytes = limits.maxBytes ?? MAX_PACK_BYTES;
|
|
365
|
+
const out = [];
|
|
366
|
+
let bytes = 0;
|
|
367
|
+
const walk = (d, depth) => {
|
|
368
|
+
if (depth > MAX_DEPTH) return;
|
|
369
|
+
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
370
|
+
const fp = join(d, entry.name);
|
|
371
|
+
const st = lstatSync(fp);
|
|
372
|
+
if (st.isSymbolicLink()) continue;
|
|
373
|
+
if (st.isDirectory()) {
|
|
374
|
+
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
375
|
+
walk(fp, depth + 1);
|
|
376
|
+
} else if (st.isFile()) {
|
|
377
|
+
if (out.length >= maxFiles) throw new Error(`pack too large: > ${maxFiles} files`);
|
|
378
|
+
bytes += st.size;
|
|
379
|
+
if (bytes > maxBytes) throw new Error(`pack too large: > ${Math.round(maxBytes / (1024 * 1024))}MB`);
|
|
380
|
+
out.push({ path: relative(root, fp).split(sep).join("/"), contents: readFileSync(fp) });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
walk(dir, 0);
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
387
|
+
async function resolveTree(sourceUrl, ref) {
|
|
388
|
+
const { downloadTemplate } = await import("giget");
|
|
389
|
+
const input = normalizeGiget(sourceUrl) + (ref ? `#${ref}` : "");
|
|
390
|
+
const dir = mkdtempSync(join(tmpdir(), "skillmd-pack-"));
|
|
391
|
+
try {
|
|
392
|
+
const { dir: out } = await downloadTemplate(input, { dir, forceClean: true });
|
|
393
|
+
return collectFiles(out, out);
|
|
394
|
+
} finally {
|
|
395
|
+
rmSync(dir, { recursive: true, force: true });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
360
398
|
function normalizeGiget(arg) {
|
|
361
399
|
const m = arg.match(/github\.com\/([^/]+)\/([^/]+)(?:\/tree\/[^/]+\/(.+))?/);
|
|
362
400
|
if (m) return `github:${m[1]}/${m[2]}${m[3] ? "/" + m[3] : ""}`;
|
|
@@ -697,6 +735,9 @@ function initCommand() {
|
|
|
697
735
|
import { Command as Command5 } from "commander";
|
|
698
736
|
import pc5 from "picocolors";
|
|
699
737
|
|
|
738
|
+
// ../cli/src/api.ts
|
|
739
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
740
|
+
|
|
700
741
|
// ../cli/src/config.ts
|
|
701
742
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
702
743
|
import { homedir as homedir2 } from "os";
|
|
@@ -740,12 +781,17 @@ function createClient(flags = {}) {
|
|
|
740
781
|
}
|
|
741
782
|
return { api, base, hasToken: Boolean(token) };
|
|
742
783
|
}
|
|
784
|
+
function skillMdFor(skill) {
|
|
785
|
+
if (skill.raw_md) return skill.raw_md;
|
|
786
|
+
return reconstructSkillMd(skill);
|
|
787
|
+
}
|
|
743
788
|
function reconstructSkillMd(skill) {
|
|
744
789
|
const name = skill.slug.split("/")[1] || skill.title;
|
|
745
|
-
const fm =
|
|
746
|
-
if (skill.license) fm.
|
|
747
|
-
|
|
748
|
-
|
|
790
|
+
const fm = { name, description: String(skill.description).replace(/\n/g, " ") };
|
|
791
|
+
if (skill.license) fm.license = skill.license;
|
|
792
|
+
return `---
|
|
793
|
+
${stringifyYaml(fm)}---
|
|
794
|
+
${skill.body_md || ""}`;
|
|
749
795
|
}
|
|
750
796
|
|
|
751
797
|
// ../cli/src/commands/search.ts
|
|
@@ -775,13 +821,62 @@ function searchCommand() {
|
|
|
775
821
|
});
|
|
776
822
|
}
|
|
777
823
|
|
|
778
|
-
// ../cli/src/commands/
|
|
824
|
+
// ../cli/src/commands/info.ts
|
|
779
825
|
import { Command as Command6 } from "commander";
|
|
780
|
-
import { existsSync as existsSync4 } from "fs";
|
|
781
826
|
import pc6 from "picocolors";
|
|
827
|
+
async function runInfo(slug, flags) {
|
|
828
|
+
const [owner, name] = slug.split("/");
|
|
829
|
+
if (!owner || !name || slug.split("/").length !== 2) {
|
|
830
|
+
return { ok: false, exitCode: 1, output: pc6.red("Expected owner/name, e.g. anthropic/pdf") };
|
|
831
|
+
}
|
|
832
|
+
const { api } = createClient(flags);
|
|
833
|
+
let s;
|
|
834
|
+
try {
|
|
835
|
+
s = await api(`/api/skills/${owner}/${name}`);
|
|
836
|
+
} catch (err) {
|
|
837
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
838
|
+
if (/^SkillMD API 404 /.test(message)) {
|
|
839
|
+
return { ok: false, exitCode: 1, output: pc6.red(`Skill not found on the registry: ${slug}`) };
|
|
840
|
+
}
|
|
841
|
+
return { ok: false, exitCode: 1, output: pc6.red(`Could not reach the SkillMD registry: ${message}`) };
|
|
842
|
+
}
|
|
843
|
+
if (flags.json) return { ok: true, exitCode: 0, output: JSON.stringify(s, null, 2), skill: s };
|
|
844
|
+
const lines = [];
|
|
845
|
+
lines.push(
|
|
846
|
+
`${pc6.bold(s.slug)} ${pc6.dim(`[${s.type ?? "single"}]`)} ${s.verified ? pc6.green("\u2713 verified") : pc6.yellow("unverified")}`
|
|
847
|
+
);
|
|
848
|
+
lines.push(` ${s.description}`);
|
|
849
|
+
lines.push(
|
|
850
|
+
pc6.dim(
|
|
851
|
+
` category: ${s.category ?? "-"} \xB7 rating: ${s.avg_rating != null ? s.avg_rating.toFixed(1) : "-"} (${s.rating_count ?? 0}) \xB7 installs: ${s.install_count ?? 0}`
|
|
852
|
+
)
|
|
853
|
+
);
|
|
854
|
+
lines.push(
|
|
855
|
+
pc6.dim(` security: ${(s.security_flags ?? []).join(", ") || "unscanned"} \xB7 license: ${s.provenance?.license ?? s.license ?? "unknown"}`)
|
|
856
|
+
);
|
|
857
|
+
const sourceRepo = s.provenance?.source_repo ?? s.source_repo;
|
|
858
|
+
const commitSha = s.provenance?.commit_sha ?? s.commit_sha;
|
|
859
|
+
if (sourceRepo) lines.push(pc6.dim(` source: ${sourceRepo}${commitSha ? ` @ ${commitSha.slice(0, 8)}` : ""}`));
|
|
860
|
+
lines.push(pc6.dim(` page: https://skillmd.com/skills/${s.slug}`));
|
|
861
|
+
lines.push(pc6.dim(` install: ${s.install?.cli ?? `npx skillmds add ${s.slug}`}`));
|
|
862
|
+
return { ok: true, exitCode: 0, output: lines.join("\n"), skill: s };
|
|
863
|
+
}
|
|
864
|
+
function infoCommand() {
|
|
865
|
+
return new Command6("info").description("Show registry details for a skill: verification, security flags, provenance, rating").argument("<slug>", "owner/name, e.g. anthropic/pdf").action(async (slug, _opts, cmd) => {
|
|
866
|
+
const flags = { ...cmd.parent?.opts() };
|
|
867
|
+
const run = await runInfo(slug, flags);
|
|
868
|
+
if (run.output) (run.ok ? console.log : console.error)(run.output);
|
|
869
|
+
process.exitCode = run.exitCode;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// ../cli/src/commands/add.ts
|
|
874
|
+
import { Command as Command7 } from "commander";
|
|
875
|
+
import { existsSync as existsSync4 } from "fs";
|
|
876
|
+
import pc7 from "picocolors";
|
|
782
877
|
|
|
783
878
|
// ../cli/src/agents.ts
|
|
784
|
-
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, rmSync, symlinkSync, existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync3 } from "fs";
|
|
879
|
+
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";
|
|
785
880
|
import { homedir as homedir3 } from "os";
|
|
786
881
|
import { join as join4, resolve as resolve2, dirname as dirname2 } from "path";
|
|
787
882
|
var AGENTS = [
|
|
@@ -804,7 +899,7 @@ function writeSkill(skillName, files, targetDir, opts = {}) {
|
|
|
804
899
|
const dest = join4(targetDir, skillName);
|
|
805
900
|
if (opts.mode === "symlink" && opts.canonicalDir) {
|
|
806
901
|
mkdirSync3(dirname2(dest), { recursive: true });
|
|
807
|
-
|
|
902
|
+
rmSync2(dest, { recursive: true, force: true });
|
|
808
903
|
symlinkSync(resolve2(opts.canonicalDir), dest, "dir");
|
|
809
904
|
return dest;
|
|
810
905
|
}
|
|
@@ -812,7 +907,7 @@ function writeSkill(skillName, files, targetDir, opts = {}) {
|
|
|
812
907
|
for (const f of files) {
|
|
813
908
|
const fp = join4(dest, f.path);
|
|
814
909
|
mkdirSync3(dirname2(fp), { recursive: true });
|
|
815
|
-
writeFileSync4(fp, f.contents
|
|
910
|
+
writeFileSync4(fp, f.contents);
|
|
816
911
|
}
|
|
817
912
|
return dest;
|
|
818
913
|
}
|
|
@@ -835,6 +930,20 @@ function installedSkills(opts = {}) {
|
|
|
835
930
|
function isLocalish(arg) {
|
|
836
931
|
return arg === "." || arg.startsWith("./") || arg.startsWith("../") || arg.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(arg) || existsSync4(arg);
|
|
837
932
|
}
|
|
933
|
+
async function resolvePackFiles(skill, fetchTree = resolveTree) {
|
|
934
|
+
if (!skill.source_repo) return null;
|
|
935
|
+
const attempts = [{ ref: skill.commit_sha ?? void 0, pinMiss: false }];
|
|
936
|
+
if (skill.commit_sha) attempts.push({ ref: void 0, pinMiss: true });
|
|
937
|
+
for (const attempt of attempts) {
|
|
938
|
+
try {
|
|
939
|
+
const files = await fetchTree(skill.source_repo, attempt.ref);
|
|
940
|
+
const sk = files.find((f) => f.path === "SKILL.md");
|
|
941
|
+
if (sk) return { raw: sk.contents.toString("utf8"), files, pinMiss: attempt.pinMiss };
|
|
942
|
+
} catch {
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
838
947
|
var defaultDeps = {
|
|
839
948
|
async resolve(arg, flags) {
|
|
840
949
|
if (isLocalish(arg)) {
|
|
@@ -847,7 +956,19 @@ var defaultDeps = {
|
|
|
847
956
|
try {
|
|
848
957
|
const skill = await api(`/api/skills/${owner}/${name}`);
|
|
849
958
|
const skillName = skill.slug.split("/")[1] || name;
|
|
850
|
-
|
|
959
|
+
const base = {
|
|
960
|
+
name: skillName,
|
|
961
|
+
slug: skillName,
|
|
962
|
+
registrySlug: arg,
|
|
963
|
+
verified: skill.verified,
|
|
964
|
+
type: skill.type,
|
|
965
|
+
securityFlags: skill.security_flags
|
|
966
|
+
};
|
|
967
|
+
if (skill.type === "pack") {
|
|
968
|
+
const pack = await resolvePackFiles(skill);
|
|
969
|
+
if (pack) return [{ ...base, ...pack }];
|
|
970
|
+
}
|
|
971
|
+
return [{ ...base, raw: skillMdFor(skill) }];
|
|
851
972
|
} catch {
|
|
852
973
|
}
|
|
853
974
|
}
|
|
@@ -863,6 +984,16 @@ var defaultDeps = {
|
|
|
863
984
|
body: JSON.stringify({ via: "cli" })
|
|
864
985
|
}).catch(() => {
|
|
865
986
|
});
|
|
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;
|
|
866
997
|
}
|
|
867
998
|
};
|
|
868
999
|
async function runAdd(arg, flags, deps = defaultDeps) {
|
|
@@ -876,27 +1007,39 @@ async function runAdd(arg, flags, deps = defaultDeps) {
|
|
|
876
1007
|
const targets = flags.agent?.length ? flags.agent : detectAgents(scope).length ? detectAgents(scope) : ["claude-code"];
|
|
877
1008
|
for (const c of candidates) {
|
|
878
1009
|
const result = lint(c.raw, { slug: c.slug });
|
|
879
|
-
const
|
|
1010
|
+
const allFlags = [.../* @__PURE__ */ new Set([...result.security.flags, ...c.securityFlags ?? []])];
|
|
1011
|
+
const deniedFlags = allFlags.filter((f) => deny.has(f));
|
|
880
1012
|
if (!flags.skipLint && !result.ok) {
|
|
881
1013
|
blocked.push({ name: c.name, reason: `lint errors: ${result.diagnostics.filter((d) => d.severity === "error").map((d) => d.id).join(", ")}` });
|
|
882
|
-
lines.push(
|
|
1014
|
+
lines.push(pc7.red(`\u2717 ${c.name} blocked \u2014 failed lint (use --skip-lint to override)`));
|
|
883
1015
|
continue;
|
|
884
1016
|
}
|
|
885
1017
|
if (deniedFlags.length) {
|
|
886
1018
|
blocked.push({ name: c.name, reason: `denied security flags: ${deniedFlags.join(", ")}` });
|
|
887
|
-
lines.push(
|
|
1019
|
+
lines.push(pc7.red(`\u2717 ${c.name} blocked \u2014 ${deniedFlags.join(", ")}`));
|
|
888
1020
|
continue;
|
|
889
1021
|
}
|
|
890
1022
|
if (c.registrySlug && c.verified === false && !flags.allowUnverified) {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
+
}
|
|
894
1030
|
}
|
|
1031
|
+
const files = c.files ?? [{ path: "SKILL.md", contents: c.raw }];
|
|
895
1032
|
for (const agentId of targets) {
|
|
896
1033
|
const dir = agentDir(agentId, scope);
|
|
897
|
-
const out = writeSkill(c.name,
|
|
1034
|
+
const out = writeSkill(c.name, files, dir, { mode: "copy" });
|
|
898
1035
|
written.push({ name: c.name, dir: out });
|
|
899
|
-
lines.push(
|
|
1036
|
+
lines.push(pc7.green(`\u2713 ${c.name} \u2192 ${out}`) + pc7.dim(` (score ${result.score}${allFlags.length ? `; ${allFlags.join(", ")}` : ""})`));
|
|
1037
|
+
}
|
|
1038
|
+
if (c.pinMiss) {
|
|
1039
|
+
lines.push(pc7.yellow(`\u26A0 ${c.name}: pinned commit unavailable upstream \u2014 fetched the source repo's default branch instead`));
|
|
1040
|
+
}
|
|
1041
|
+
if (c.type === "pack" && !c.files) {
|
|
1042
|
+
lines.push(pc7.yellow(`\u26A0 ${c.name} is a pack but its asset files could not be fetched \u2014 only SKILL.md was installed. It may reference missing files.`));
|
|
900
1043
|
}
|
|
901
1044
|
if (c.registrySlug) deps.fireInstall(c.registrySlug, flags);
|
|
902
1045
|
}
|
|
@@ -904,7 +1047,7 @@ async function runAdd(arg, flags, deps = defaultDeps) {
|
|
|
904
1047
|
return { written, blocked, exitCode, output: lines.join("\n") };
|
|
905
1048
|
}
|
|
906
1049
|
function addCommand() {
|
|
907
|
-
return new
|
|
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").option("--allow-unverified", "allow installing unverified registry skills").option("--skip-lint", "install even if the skill fails lint").option("--deny <flags...>", "refuse skills with these security flags").action(async (source, opts, cmd) => {
|
|
908
1051
|
const merged = { ...cmd.parent?.opts(), ...opts };
|
|
909
1052
|
const run = await runAdd(source, merged);
|
|
910
1053
|
console.log(run.output);
|
|
@@ -913,10 +1056,10 @@ function addCommand() {
|
|
|
913
1056
|
}
|
|
914
1057
|
|
|
915
1058
|
// ../cli/src/commands/list.ts
|
|
916
|
-
import { Command as
|
|
917
|
-
import
|
|
1059
|
+
import { Command as Command8 } from "commander";
|
|
1060
|
+
import pc8 from "picocolors";
|
|
918
1061
|
function listCommand() {
|
|
919
|
-
return new
|
|
1062
|
+
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) => {
|
|
920
1063
|
const scope = { global: opts.global };
|
|
921
1064
|
const skills = installedSkills(scope);
|
|
922
1065
|
if (opts.json) {
|
|
@@ -929,44 +1072,44 @@ function listCommand() {
|
|
|
929
1072
|
}
|
|
930
1073
|
for (const s of skills) {
|
|
931
1074
|
const parsed = parseSkillMd(s.raw);
|
|
932
|
-
const desc = "error" in parsed ?
|
|
933
|
-
console.log(`${
|
|
1075
|
+
const desc = "error" in parsed ? pc8.red(`(invalid SKILL.md \u2014 ${parsed.error})`) : pc8.dim(parsed.description.slice(0, 80));
|
|
1076
|
+
console.log(`${pc8.bold(s.name)} ${pc8.dim(`[${s.agent}/${s.scope}]`)}
|
|
934
1077
|
${desc}`);
|
|
935
1078
|
}
|
|
936
1079
|
});
|
|
937
1080
|
}
|
|
938
1081
|
|
|
939
1082
|
// ../cli/src/commands/remove.ts
|
|
940
|
-
import { Command as
|
|
941
|
-
import { rmSync as
|
|
942
|
-
import
|
|
1083
|
+
import { Command as Command9 } from "commander";
|
|
1084
|
+
import { rmSync as rmSync3 } from "fs";
|
|
1085
|
+
import pc9 from "picocolors";
|
|
943
1086
|
function runRemove(names, scope = {}) {
|
|
944
1087
|
const installed = installedSkills(scope);
|
|
945
1088
|
const targets = installed.filter((s) => names.includes(s.name));
|
|
946
1089
|
const removed = [];
|
|
947
1090
|
const lines = [];
|
|
948
1091
|
for (const t of targets) {
|
|
949
|
-
|
|
1092
|
+
rmSync3(t.path, { recursive: true, force: true });
|
|
950
1093
|
removed.push(t.path);
|
|
951
|
-
lines.push(
|
|
1094
|
+
lines.push(pc9.green(`\u2713 removed ${t.name} (${t.agent})`));
|
|
952
1095
|
}
|
|
953
1096
|
for (const n of names) {
|
|
954
|
-
if (!targets.some((t) => t.name === n)) lines.push(
|
|
1097
|
+
if (!targets.some((t) => t.name === n)) lines.push(pc9.yellow(`- ${n} not installed`));
|
|
955
1098
|
}
|
|
956
1099
|
return { removed, output: lines.join("\n") };
|
|
957
1100
|
}
|
|
958
1101
|
function removeCommand() {
|
|
959
|
-
return new
|
|
1102
|
+
return new Command9("remove").alias("rm").description("Remove installed skills").argument("<names...>", "skill names to remove").option("-g, --global", "only global skills").action((names, opts) => {
|
|
960
1103
|
const run = runRemove(names, { global: opts.global });
|
|
961
1104
|
console.log(run.output);
|
|
962
1105
|
});
|
|
963
1106
|
}
|
|
964
1107
|
|
|
965
1108
|
// ../cli/src/commands/update.ts
|
|
966
|
-
import { Command as
|
|
1109
|
+
import { Command as Command10 } from "commander";
|
|
967
1110
|
import { writeFileSync as writeFileSync5 } from "fs";
|
|
968
1111
|
import { join as join5 } from "path";
|
|
969
|
-
import
|
|
1112
|
+
import pc10 from "picocolors";
|
|
970
1113
|
async function runUpdate(names, flags, scope = {}) {
|
|
971
1114
|
const { api } = createClient(flags);
|
|
972
1115
|
const installed = installedSkills(scope).filter((s) => names.length === 0 || names.includes(s.name));
|
|
@@ -975,27 +1118,27 @@ async function runUpdate(names, flags, scope = {}) {
|
|
|
975
1118
|
for (const s of installed) {
|
|
976
1119
|
const slug = await findRegistrySlug(api, s);
|
|
977
1120
|
if (!slug) {
|
|
978
|
-
lines.push(
|
|
1121
|
+
lines.push(pc10.dim(`- ${s.name} skipped (no registry match)`));
|
|
979
1122
|
continue;
|
|
980
1123
|
}
|
|
981
1124
|
try {
|
|
982
1125
|
const [owner, name] = slug.split("/");
|
|
983
1126
|
const skill = await api(`/api/skills/${owner}/${name}`);
|
|
984
|
-
const next =
|
|
1127
|
+
const next = skillMdFor(skill);
|
|
985
1128
|
if (next.trim() === s.raw.trim()) {
|
|
986
|
-
lines.push(
|
|
1129
|
+
lines.push(pc10.dim(`- ${s.name} already up to date`));
|
|
987
1130
|
continue;
|
|
988
1131
|
}
|
|
989
1132
|
const result = lint(next, { slug: s.name });
|
|
990
1133
|
if (!result.ok) {
|
|
991
|
-
lines.push(
|
|
1134
|
+
lines.push(pc10.red(`\u2717 ${s.name} update failed lint \u2014 skipped`));
|
|
992
1135
|
continue;
|
|
993
1136
|
}
|
|
994
1137
|
writeFileSync5(join5(s.path, "SKILL.md"), next, "utf8");
|
|
995
1138
|
updated.push(s.name);
|
|
996
|
-
lines.push(
|
|
1139
|
+
lines.push(pc10.green(`\u2713 updated ${s.name}`));
|
|
997
1140
|
} catch {
|
|
998
|
-
lines.push(
|
|
1141
|
+
lines.push(pc10.red(`\u2717 ${s.name} update error`));
|
|
999
1142
|
}
|
|
1000
1143
|
}
|
|
1001
1144
|
return { updated, output: lines.join("\n") || "Nothing to update." };
|
|
@@ -1006,7 +1149,7 @@ async function findRegistrySlug(api, s) {
|
|
|
1006
1149
|
return hit?.slug ?? null;
|
|
1007
1150
|
}
|
|
1008
1151
|
function updateCommand() {
|
|
1009
|
-
return new
|
|
1152
|
+
return new Command10("update").description("Update installed skills from the registry (re-lints before writing)").argument("[names...]", "specific skills to update (default: all)").option("-g, --global", "only global skills").action(async (names, opts, cmd) => {
|
|
1010
1153
|
const merged = { ...cmd.parent?.opts(), ...opts };
|
|
1011
1154
|
const run = await runUpdate(names, merged, { global: opts.global });
|
|
1012
1155
|
console.log(run.output);
|
|
@@ -1014,8 +1157,8 @@ function updateCommand() {
|
|
|
1014
1157
|
}
|
|
1015
1158
|
|
|
1016
1159
|
// ../cli/src/commands/publish.ts
|
|
1017
|
-
import { Command as
|
|
1018
|
-
import
|
|
1160
|
+
import { Command as Command11 } from "commander";
|
|
1161
|
+
import pc11 from "picocolors";
|
|
1019
1162
|
var defaultDeps2 = {
|
|
1020
1163
|
resolve: resolveSource,
|
|
1021
1164
|
async submit(payload, flags) {
|
|
@@ -1038,23 +1181,23 @@ async function runPublish(target, flags, deps = defaultDeps2) {
|
|
|
1038
1181
|
const report = toText([{ file: src.path, result }], { decorate });
|
|
1039
1182
|
if (!result.ok) {
|
|
1040
1183
|
return { result, published: false, exitCode: 1, output: `${report}
|
|
1041
|
-
${
|
|
1184
|
+
${pc11.red("\u2717 Refusing to publish: fix the errors above.")}` };
|
|
1042
1185
|
}
|
|
1043
1186
|
const hasWarn = result.diagnostics.some((d) => d.severity === "warn");
|
|
1044
1187
|
if (hasWarn && !flags.force) {
|
|
1045
1188
|
return { result, published: false, exitCode: 1, output: `${report}
|
|
1046
|
-
${
|
|
1189
|
+
${pc11.yellow("\u2717 Warnings present. Re-run with --force to publish anyway.")}` };
|
|
1047
1190
|
}
|
|
1048
1191
|
if (flags.dryRun) {
|
|
1049
1192
|
return { result, published: false, exitCode: 0, output: `${report}
|
|
1050
|
-
${
|
|
1193
|
+
${pc11.green("\u2713 Dry run: would publish.")}` };
|
|
1051
1194
|
}
|
|
1052
1195
|
const res = await deps.submit({ content: src.raw, type: flags.type ?? "single" }, flags);
|
|
1053
1196
|
return { result, published: true, slug: res.slug, exitCode: 0, output: `${report}
|
|
1054
|
-
${
|
|
1197
|
+
${pc11.green(`\u2713 Published ${res.slug ?? src.slug} (status: ${res.status ?? "pending"})`)}` };
|
|
1055
1198
|
}
|
|
1056
1199
|
function publishCommand() {
|
|
1057
|
-
return new
|
|
1200
|
+
return new Command11("publish").alias("submit").description("Publish a skill to the registry (refuses to publish on lint errors)").argument("[target]", "local path to the skill", ".").option("--type <type>", "single | pack", "single").option("--force", "publish despite warnings (never bypasses errors)").option("--dry-run", "lint only; do not publish").action(async (target, opts, cmd) => {
|
|
1058
1201
|
const merged = { ...cmd.parent?.opts(), ...opts };
|
|
1059
1202
|
const run = await runPublish(target, merged);
|
|
1060
1203
|
console.log(run.output);
|
|
@@ -1063,10 +1206,10 @@ function publishCommand() {
|
|
|
1063
1206
|
}
|
|
1064
1207
|
|
|
1065
1208
|
// ../cli/src/commands/login.ts
|
|
1066
|
-
import { Command as
|
|
1209
|
+
import { Command as Command12 } from "commander";
|
|
1067
1210
|
import * as p3 from "@clack/prompts";
|
|
1068
1211
|
function loginCommand() {
|
|
1069
|
-
return new
|
|
1212
|
+
return new Command12("login").description("Store a SkillMD personal access token for publishing").option("--token <token>", "token to store (otherwise prompt)").option("--api <url>", "registry API base to store").action(async (opts) => {
|
|
1070
1213
|
let token = opts.token;
|
|
1071
1214
|
if (!token) {
|
|
1072
1215
|
const answer = await p3.password({ message: "Paste your SkillMD token (from your account page)" });
|
|
@@ -1089,7 +1232,7 @@ function loginCommand() {
|
|
|
1089
1232
|
});
|
|
1090
1233
|
}
|
|
1091
1234
|
function logoutCommand() {
|
|
1092
|
-
return new
|
|
1235
|
+
return new Command12("logout").description("Remove the stored SkillMD token").action(() => {
|
|
1093
1236
|
const cfg = readConfig();
|
|
1094
1237
|
delete cfg.token;
|
|
1095
1238
|
writeConfig(cfg);
|
|
@@ -1099,7 +1242,7 @@ function logoutCommand() {
|
|
|
1099
1242
|
|
|
1100
1243
|
// ../cli/src/commands/interactive.ts
|
|
1101
1244
|
import * as p4 from "@clack/prompts";
|
|
1102
|
-
import
|
|
1245
|
+
import pc12 from "picocolors";
|
|
1103
1246
|
function bail(v) {
|
|
1104
1247
|
if (p4.isCancel(v)) {
|
|
1105
1248
|
p4.cancel("Cancelled.");
|
|
@@ -1108,105 +1251,107 @@ function bail(v) {
|
|
|
1108
1251
|
return false;
|
|
1109
1252
|
}
|
|
1110
1253
|
async function runInteractive() {
|
|
1111
|
-
p4.intro(
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1254
|
+
p4.intro(pc12.bgCyan(pc12.black(" skillmd ")));
|
|
1255
|
+
for (; ; ) {
|
|
1256
|
+
const action = await p4.select({
|
|
1257
|
+
message: "What would you like to do?",
|
|
1258
|
+
options: [
|
|
1259
|
+
{ value: "lint", label: "Lint a skill", hint: "validate a SKILL.md" },
|
|
1260
|
+
{ value: "scan", label: "Security scan", hint: "scripts, network, secrets" },
|
|
1261
|
+
{ value: "search", label: "Search the registry" },
|
|
1262
|
+
{ value: "init", label: "Create a new skill" },
|
|
1263
|
+
{ value: "list", label: "List installed skills" },
|
|
1264
|
+
{ value: "help", label: "Show all commands" },
|
|
1265
|
+
{ value: "quit", label: "Quit" }
|
|
1266
|
+
]
|
|
1267
|
+
});
|
|
1268
|
+
if (bail(action) || action === "quit") break;
|
|
1269
|
+
switch (action) {
|
|
1270
|
+
case "lint":
|
|
1271
|
+
case "scan": {
|
|
1272
|
+
const path = await p4.text({ message: "Path to a skill or folder", placeholder: ".", defaultValue: "." });
|
|
1273
|
+
if (bail(path)) break;
|
|
1274
|
+
const target = String(path || ".");
|
|
1275
|
+
const s = p4.spinner();
|
|
1276
|
+
s.start(action === "lint" ? "Linting" : "Scanning");
|
|
1277
|
+
try {
|
|
1278
|
+
const run = action === "lint" ? await runLint(target, { format: "text" }) : await runScan(target, { format: "text" });
|
|
1279
|
+
s.stop("Done");
|
|
1280
|
+
console.log(run.output);
|
|
1281
|
+
process.exitCode = run.exitCode;
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
s.stop(pc12.red(err instanceof Error ? err.message : String(err)));
|
|
1284
|
+
process.exitCode = 1;
|
|
1285
|
+
}
|
|
1286
|
+
break;
|
|
1140
1287
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1288
|
+
case "search": {
|
|
1289
|
+
const query = await p4.text({ message: "Search the registry for" });
|
|
1290
|
+
if (bail(query)) break;
|
|
1291
|
+
const verified = await p4.confirm({ message: "Verified skills only?", initialValue: false });
|
|
1292
|
+
if (bail(verified)) break;
|
|
1293
|
+
const s = p4.spinner();
|
|
1294
|
+
s.start("Searching");
|
|
1295
|
+
try {
|
|
1296
|
+
const run = await runSearch(String(query), { verified: Boolean(verified), limit: "20" });
|
|
1297
|
+
s.stop(`${run.items.length} result${run.items.length === 1 ? "" : "s"}`);
|
|
1298
|
+
console.log(run.output);
|
|
1299
|
+
} catch (err) {
|
|
1300
|
+
s.stop(pc12.red(err instanceof Error ? err.message : String(err)));
|
|
1301
|
+
process.exitCode = 1;
|
|
1302
|
+
}
|
|
1303
|
+
break;
|
|
1157
1304
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1305
|
+
case "init": {
|
|
1306
|
+
const name = await p4.text({ message: "Skill name", placeholder: "my-skill" });
|
|
1307
|
+
if (bail(name)) break;
|
|
1308
|
+
const description = await p4.text({ message: "One-line description", placeholder: "What this skill helps an agent do" });
|
|
1309
|
+
if (bail(description)) break;
|
|
1310
|
+
try {
|
|
1311
|
+
const file = initSkill(String(name), { description: String(description || "") });
|
|
1312
|
+
p4.log.success(`Created ${pc12.bold(file)}`);
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
p4.log.error(err instanceof Error ? err.message : String(err));
|
|
1315
|
+
process.exitCode = 1;
|
|
1316
|
+
}
|
|
1317
|
+
break;
|
|
1171
1318
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
skills.map((sk) => {
|
|
1183
|
-
const parsed = parseSkillMd(sk.raw);
|
|
1184
|
-
const desc = "error" in parsed ? pc11.red("(invalid)") : pc11.dim(parsed.description.slice(0, 70));
|
|
1185
|
-
return `${pc11.bold(sk.name)} ${pc11.dim(`[${sk.agent}]`)}
|
|
1319
|
+
case "list": {
|
|
1320
|
+
const skills = [...installedSkills({}), ...installedSkills({ global: true })];
|
|
1321
|
+
if (skills.length === 0) {
|
|
1322
|
+
p4.log.info("No installed skills found (checked this folder and your user directory).");
|
|
1323
|
+
} else {
|
|
1324
|
+
p4.log.message(
|
|
1325
|
+
skills.map((sk) => {
|
|
1326
|
+
const parsed = parseSkillMd(sk.raw);
|
|
1327
|
+
const desc = "error" in parsed ? pc12.red(`(invalid SKILL.md \u2014 ${parsed.error})`) : pc12.dim(parsed.description.slice(0, 70));
|
|
1328
|
+
return `${pc12.bold(sk.name)} ${pc12.dim(`[${sk.agent}/${sk.scope}]`)}
|
|
1186
1329
|
${desc}`;
|
|
1187
|
-
|
|
1330
|
+
}).join("\n")
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
break;
|
|
1334
|
+
}
|
|
1335
|
+
case "help": {
|
|
1336
|
+
p4.note(
|
|
1337
|
+
[
|
|
1338
|
+
`${pc12.bold("lint")} [path] validate SKILL.md + quality score`,
|
|
1339
|
+
`${pc12.bold("scan")} [path] security scan`,
|
|
1340
|
+
`${pc12.bold("search")} <query> search the registry`,
|
|
1341
|
+
`${pc12.bold("add")} <source> install a skill (lints first)`,
|
|
1342
|
+
`${pc12.bold("info")} <slug> registry details for a skill`,
|
|
1343
|
+
`${pc12.bold("init")} [name] scaffold a new skill`,
|
|
1344
|
+
`${pc12.bold("list / remove / update")} manage installed skills`,
|
|
1345
|
+
`${pc12.bold("publish")} [path] publish (fails on lint errors)`,
|
|
1346
|
+
`${pc12.bold("rules / login")}`
|
|
1347
|
+
].join("\n"),
|
|
1348
|
+
"Commands (run: skillmd <command>)"
|
|
1188
1349
|
);
|
|
1350
|
+
break;
|
|
1189
1351
|
}
|
|
1190
|
-
break;
|
|
1191
|
-
}
|
|
1192
|
-
case "help": {
|
|
1193
|
-
p4.note(
|
|
1194
|
-
[
|
|
1195
|
-
`${pc11.bold("lint")} [path] validate SKILL.md + quality score`,
|
|
1196
|
-
`${pc11.bold("scan")} [path] security scan`,
|
|
1197
|
-
`${pc11.bold("search")} <query> search the registry`,
|
|
1198
|
-
`${pc11.bold("add")} <source> install a skill (lints first)`,
|
|
1199
|
-
`${pc11.bold("init")} [name] scaffold a new skill`,
|
|
1200
|
-
`${pc11.bold("list / remove / update")} manage installed skills`,
|
|
1201
|
-
`${pc11.bold("publish")} [path] publish (fails on lint errors)`,
|
|
1202
|
-
`${pc11.bold("rules / login")}`
|
|
1203
|
-
].join("\n"),
|
|
1204
|
-
"Commands (run: skillmd <command>)"
|
|
1205
|
-
);
|
|
1206
|
-
break;
|
|
1207
1352
|
}
|
|
1208
1353
|
}
|
|
1209
|
-
p4.outro(
|
|
1354
|
+
p4.outro(pc12.dim("skillmd.com"));
|
|
1210
1355
|
}
|
|
1211
1356
|
|
|
1212
1357
|
// ../cli/src/index.ts
|
|
@@ -1215,12 +1360,13 @@ try {
|
|
|
1215
1360
|
version = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
1216
1361
|
} catch {
|
|
1217
1362
|
}
|
|
1218
|
-
var program = new
|
|
1363
|
+
var program = new Command13("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");
|
|
1219
1364
|
program.addCommand(lintCommand());
|
|
1220
1365
|
program.addCommand(scanCommand());
|
|
1221
1366
|
program.addCommand(rulesCommand());
|
|
1222
1367
|
program.addCommand(initCommand());
|
|
1223
1368
|
program.addCommand(searchCommand());
|
|
1369
|
+
program.addCommand(infoCommand());
|
|
1224
1370
|
program.addCommand(addCommand());
|
|
1225
1371
|
program.addCommand(listCommand());
|
|
1226
1372
|
program.addCommand(removeCommand());
|