skillmds 1.2.1 → 1.3.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.
@@ -19,6 +19,12 @@ function parseSource(arg, opts = {}) {
19
19
  const exists = opts.exists ?? existsSync;
20
20
  const a = arg.trim();
21
21
  if (!a) throw new Error("a source is required: owner/name, a GitHub URL, or a local path");
22
+ if (/^(plugin|pack):/i.test(a)) {
23
+ const body2 = a.slice(a.indexOf(":") + 1);
24
+ const m2 = body2.match(/^([^/#@\s]+)\/([^/#@\s]+)$/);
25
+ if (!m2) throw new Error(`"${a}" is not a valid plugin source \u2014 expected plugin:owner/slug`);
26
+ return { kind: "plugin", owner: m2[1], slug: m2[2], display: a };
27
+ }
22
28
  if (a === "~" || a.startsWith("~/") || a.startsWith("~\\")) {
23
29
  return { kind: "local", path: join(homedir(), a.slice(1)), display: a };
24
30
  }
@@ -54,6 +60,10 @@ function sourceId(spec) {
54
60
  switch (spec.kind) {
55
61
  case "slug":
56
62
  return `registry:${spec.owner}/${spec.name}`;
63
+ // Members are recorded under their own registry slug, not this one — the
64
+ // lock file tracks what was written, and a plugin is only how they arrived.
65
+ case "plugin":
66
+ return `plugin:${spec.owner}/${spec.slug}`;
57
67
  case "github":
58
68
  return `github:${spec.owner}/${spec.repo}${spec.subpath ? `/${spec.subpath}` : ""}${spec.ref ? `#${spec.ref}` : ""}${spec.skill ? `@${spec.skill}` : ""}`;
59
69
  case "gist":
@@ -314,6 +324,9 @@ async function resolveSource(arg, opts = {}) {
314
324
  if (raw.length > MAX_PACK_BYTES) throw tooBig(raw.length);
315
325
  return [{ path: spec.display, raw, slug: `gist-${spec.id.slice(0, 8)}` }];
316
326
  }
327
+ if (spec.kind === "plugin") {
328
+ throw new Error(`"${spec.display}" is a plugin, not a single skill \u2014 install it with \`skillmd add ${spec.display}\`.`);
329
+ }
317
330
  const gh = asGithub(spec);
318
331
  const { dir, cleanup } = await downloadGithub(gh, "skillmd-src-", opts, Boolean(gh.subpath));
319
332
  try {
package/dist/cli.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  uninstallSkill,
31
31
  upsertEntry,
32
32
  writeConfig
33
- } from "./chunk-4BFQJ3YZ.js";
33
+ } from "./chunk-FVWCI37S.js";
34
34
 
35
35
  // src/index.ts
36
36
  import { Command as Command13 } from "commander";
@@ -319,12 +319,12 @@ function lintCommand() {
319
319
  const s = p.spinner();
320
320
  s.start(`Scanning ${target}`);
321
321
  try {
322
- const run2 = await runLint(target, opts);
323
- s.stop(`Scanned ${run2.results.length} skill${run2.results.length === 1 ? "" : "s"}`);
324
- console.log(run2.output);
325
- const failed = run2.results.filter((r) => !r.result.ok).length;
322
+ const run3 = await runLint(target, opts);
323
+ s.stop(`Scanned ${run3.results.length} skill${run3.results.length === 1 ? "" : "s"}`);
324
+ console.log(run3.output);
325
+ const failed = run3.results.filter((r) => !r.result.ok).length;
326
326
  p.outro(failed === 0 ? pc2.green("All good") : pc2.red(`${failed} skill${failed === 1 ? "" : "s"} failed \u2014 details above`));
327
- process.exitCode = run2.exitCode;
327
+ process.exitCode = run3.exitCode;
328
328
  } catch (err) {
329
329
  s.stop(pc2.red("No skills found"));
330
330
  p.outro(err instanceof Error ? err.message : String(err));
@@ -334,9 +334,9 @@ function lintCommand() {
334
334
  }
335
335
  process.stderr.write(`Looking for SKILL.md under ${target} \u2026
336
336
  `);
337
- const run = await runLint(target, opts);
338
- console.log(run.output);
339
- process.exitCode = run.exitCode;
337
+ const run2 = await runLint(target, opts);
338
+ console.log(run2.output);
339
+ process.exitCode = run2.exitCode;
340
340
  });
341
341
  }
342
342
 
@@ -375,9 +375,9 @@ async function runScan(target, flags) {
375
375
  }
376
376
  function scanCommand() {
377
377
  return new Command2("scan").description("Security-scan SKILL.md file(s) for scripts, network calls and secret access").argument("[target]", "local path or GitHub source", ".").option("--deny <flags...>", "exit non-zero if any of these flags are present (e.g. executes_scripts)").option("--format <fmt>", "text | json", "text").action(async (target, opts) => {
378
- const run = await runScan(target, opts);
379
- console.log(run.output);
380
- process.exitCode = run.exitCode;
378
+ const run2 = await runScan(target, opts);
379
+ console.log(run2.output);
380
+ process.exitCode = run2.exitCode;
381
381
  });
382
382
  }
383
383
 
@@ -504,8 +504,8 @@ async function runInit(nameArg, flags, deps = defaultInitDeps(flags)) {
504
504
  const file = initSkill(name, { dir: flags.dir, description: flags.description, license: flags.license });
505
505
  return { exitCode: 0, file, output: flags.json ? JSON.stringify({ ok: true, file }, null, 2) : `Created ${file}` };
506
506
  } catch (e) {
507
- const msg = e instanceof Error ? e.message : String(e);
508
- return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg }, null, 2) : pc5.red(msg) };
507
+ const msg2 = e instanceof Error ? e.message : String(e);
508
+ return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg2 }, null, 2) : pc5.red(msg2) };
509
509
  }
510
510
  }
511
511
  function initCommand() {
@@ -563,8 +563,8 @@ async function runSearch(query, flags) {
563
563
  function searchCommand() {
564
564
  return new Command5("search").description("Search the SkillMD registry").argument("<query>", "search terms").option("--category <slug>", "filter by category").option("--verified", "only verified skills").option("--type <type>", "single | pack").option("--min-rating <n>", "minimum average rating (0-5)").option("--limit <n>", "max results", "20").option("--json", "machine-readable output").action(async (query, opts, cmd) => {
565
565
  const merged = { ...cmd.parent?.opts(), ...opts };
566
- const run = await runSearch(query, merged);
567
- console.log(run.output);
566
+ const run2 = await runSearch(query, merged);
567
+ console.log(run2.output);
568
568
  });
569
569
  }
570
570
 
@@ -613,19 +613,99 @@ async function runInfo(slug, flags, deps = {}) {
613
613
  function infoCommand() {
614
614
  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) => {
615
615
  const flags = { ...cmd.parent?.opts() };
616
- const run = await runInfo(slug, flags);
617
- if (run.output) (run.ok ? console.log : console.error)(run.output);
618
- process.exitCode = run.exitCode;
616
+ const run2 = await runInfo(slug, flags);
617
+ if (run2.output) (run2.ok ? console.log : console.error)(run2.output);
618
+ process.exitCode = run2.exitCode;
619
619
  });
620
620
  }
621
621
 
622
622
  // src/commands/add.ts
623
623
  import { Command as Command7, Option } from "commander";
624
- import { homedir as homedir3 } from "os";
624
+ import { homedir as homedir4 } from "os";
625
625
  import * as p3 from "@clack/prompts";
626
626
  import { dirname, resolve as resolve3 } from "path";
627
627
  import pc8 from "picocolors";
628
628
  import { lint as lint2, parseSkillMd as parseSkillMd2 } from "@skillmds/core";
629
+
630
+ // src/claudePlugin.ts
631
+ import { execFile } from "child_process";
632
+ import { existsSync as existsSync2 } from "fs";
633
+ import { homedir as homedir3 } from "os";
634
+ import { delimiter, join as join2 } from "path";
635
+ import { promisify } from "util";
636
+ var run = promisify(execFile);
637
+ function resolveClaudeBin(env = process.env) {
638
+ const override = env.SKILLMD_CLAUDE_BIN;
639
+ if (override) return existsSync2(override) ? override : null;
640
+ const win = process.platform === "win32";
641
+ const exts = win ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
642
+ const dirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
643
+ const home = env.USERPROFILE || env.HOME || homedir3();
644
+ dirs.push(join2(home, ".local", "bin"), join2(home, ".claude", "local"));
645
+ for (const dir of dirs) {
646
+ for (const ext of exts) {
647
+ const candidate = join2(dir, `claude${ext.toLowerCase()}`);
648
+ if (existsSync2(candidate)) return candidate;
649
+ }
650
+ }
651
+ return null;
652
+ }
653
+ var needsShell = (bin) => /\.(cmd|bat)$/i.test(bin);
654
+ var MARKETPLACE_URL = "https://skillmd.com/.claude-plugin/marketplace.json";
655
+ var MARKETPLACE_NAME = "skillmd";
656
+ var TIMEOUT_MS = 12e4;
657
+ function exec(deps) {
658
+ return deps.exec ?? ((file, args) => {
659
+ const bin = file === "claude" ? resolveClaudeBin() : file;
660
+ if (!bin) return Promise.reject(new Error("claude not found on PATH"));
661
+ return run(bin, args, { timeout: TIMEOUT_MS, windowsHide: true, shell: needsShell(bin) }).then((r) => ({
662
+ stdout: String(r.stdout ?? ""),
663
+ stderr: String(r.stderr ?? "")
664
+ }));
665
+ });
666
+ }
667
+ function marketplacePluginName(owner, slug) {
668
+ const clean = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
669
+ const base = clean(slug) || "plugin";
670
+ return owner.toLowerCase() === "skillmd" ? base : `${clean(owner)}-${base}`;
671
+ }
672
+ async function claudeCliAvailable(deps = {}) {
673
+ try {
674
+ await exec(deps)("claude", ["--version"]);
675
+ return true;
676
+ } catch {
677
+ return false;
678
+ }
679
+ }
680
+ async function installClaudePlugin(owner, slug, deps = {}) {
681
+ const sh = exec(deps);
682
+ const qualified = `${marketplacePluginName(owner, slug)}@${MARKETPLACE_NAME}`;
683
+ try {
684
+ await sh("claude", ["plugin", "marketplace", "add", MARKETPLACE_URL]);
685
+ } catch (e) {
686
+ return { ok: false, reason: `could not add the SkillMD marketplace: ${msg(e)}` };
687
+ }
688
+ let out;
689
+ try {
690
+ const r = await sh("claude", ["plugin", "install", qualified]);
691
+ out = `${r.stdout}
692
+ ${r.stderr}`;
693
+ } catch (e) {
694
+ return { ok: false, reason: msg(e) };
695
+ }
696
+ if (/Successfully installed plugin/i.test(out)) return { ok: true, name: qualified, marketplace: MARKETPLACE_NAME };
697
+ const failed = /Failed to install plugin[^:]*:\s*(.+)/i.exec(out);
698
+ return { ok: false, reason: (failed?.[1] ?? out).trim().split(/\r?\n/)[0] ?? "install did not report success" };
699
+ }
700
+ function msg(e) {
701
+ if (e && typeof e === "object" && "stderr" in e) {
702
+ const s = String(e.stderr ?? "").trim();
703
+ if (s) return s.split(/\r?\n/)[0];
704
+ }
705
+ return e instanceof Error ? e.message.split(/\r?\n/)[0] : String(e);
706
+ }
707
+
708
+ // src/commands/add.ts
629
709
  var infoNotice = (text3) => ({ text: text3, style: "info" });
630
710
  var warnNotice = (text3) => ({ text: text3, style: "warn" });
631
711
  var skipNotice = (text3) => ({ text: text3, style: "skip" });
@@ -693,6 +773,77 @@ function provenanceFor(arg) {
693
773
  return `unknown:${arg}`;
694
774
  }
695
775
  }
776
+ async function registryCandidate(owner, name, source, client) {
777
+ const registrySlug = `${owner}/${name}`;
778
+ const skill = await client.api(`/api/skills/${owner}/${name}`);
779
+ const skillName = skill.slug.split("/")[1] || name;
780
+ const meta = {
781
+ name: skillName,
782
+ slug: skillName,
783
+ registrySlug,
784
+ source,
785
+ commit_sha: skill.commit_sha ?? void 0,
786
+ verified: skill.verified,
787
+ type: skill.type,
788
+ securityFlags: skill.security_flags
789
+ };
790
+ if (skill.type === "pack") {
791
+ const bundle = await fetchBundle(client.base, registrySlug, client.token);
792
+ if (bundle && bundle.length) {
793
+ const sk = bundle.find((file) => file.path === "SKILL.md");
794
+ const unverified = bundle.filter((file) => file.unverified).length;
795
+ return {
796
+ ...meta,
797
+ raw: sk ? sk.contents.toString("utf8") : skillMdFor(skill),
798
+ files: bundle,
799
+ ...unverified ? { note: unverifiedFilesNote(unverified) } : {}
800
+ };
801
+ }
802
+ const pack = await resolvePackFiles(skill);
803
+ if (pack) return { ...meta, ...pack };
804
+ }
805
+ return { ...meta, raw: skillMdFor(skill) };
806
+ }
807
+ async function fetchPackMembers(api, owner, slug) {
808
+ const PAGE = 200;
809
+ const members = [];
810
+ let name = slug;
811
+ let total = Infinity;
812
+ for (let offset = 0; members.length < total; offset += PAGE) {
813
+ const page = await api(`/api/packs/${owner}/${slug}?limit=${PAGE}&offset=${offset}`);
814
+ name = page.name || name;
815
+ total = Number(page.item_total ?? 0);
816
+ const got = (page.items ?? []).map((i) => ({ slug: String(i.slug ?? ""), type: i.type, verified: Boolean(i.verified) })).filter((i) => i.slug);
817
+ if (!got.length) break;
818
+ members.push(...got);
819
+ }
820
+ return { name, members };
821
+ }
822
+ function packDirNames(slugs) {
823
+ const used = /* @__PURE__ */ new Set();
824
+ return slugs.map((slug) => {
825
+ const [owner, name] = slug.split("/");
826
+ let dir = name ?? slug;
827
+ if (used.has(dir)) dir = `${owner}-${name}`;
828
+ used.add(dir);
829
+ return dir;
830
+ });
831
+ }
832
+ async function fetchPackArchive(base, owner, slug, token, doFetch = fetch) {
833
+ const headers = { accept: "application/zip" };
834
+ if (token) headers.authorization = `Bearer ${token}`;
835
+ const res = await doFetch(`${base}/api/packs/${owner}/${slug}/download`, { headers });
836
+ if (!res.ok) throw new RegistryError(`SkillMD API ${res.status} on /api/packs/${owner}/${slug}/download`, res.status);
837
+ const bytes = new Uint8Array(await res.arrayBuffer());
838
+ const { unzipSync, strFromU8 } = await import("fflate");
839
+ const entries = unzipSync(bytes);
840
+ const out = /* @__PURE__ */ new Map();
841
+ for (const [path, data] of Object.entries(entries)) {
842
+ const m = /^(.+)\/SKILL\.md$/.exec(path);
843
+ if (m) out.set(m[1], strFromU8(data));
844
+ }
845
+ return out;
846
+ }
696
847
  function makeDefaultDeps(flags) {
697
848
  const canPrompt = () => isInteractive(flags, {
698
849
  stdinTTY: Boolean(process.stdin.isTTY),
@@ -703,6 +854,55 @@ function makeDefaultDeps(flags) {
703
854
  async resolve(arg, f) {
704
855
  const spec = parseSource(arg, { ref: f.ref });
705
856
  const source = sourceId(spec);
857
+ if (spec.kind === "plugin") {
858
+ const { api: api2, base: apiBase2, token: token2, fetch: doFetch } = createClient(f);
859
+ const { name: packName, members } = await fetchPackMembers(api2, spec.owner, spec.slug);
860
+ if (!members.length) throw new Error(`plugin ${spec.owner}/${spec.slug} has no installable skills`);
861
+ const archive = await fetchPackArchive(apiBase2, spec.owner, spec.slug, token2, doFetch);
862
+ const dirs = packDirNames(members.map((m) => m.slug));
863
+ const out = [];
864
+ const missing = [];
865
+ members.forEach((m, i) => {
866
+ const raw = archive.get(dirs[i]);
867
+ if (!raw) {
868
+ missing.push(m.slug);
869
+ return;
870
+ }
871
+ const name = dirs[i];
872
+ out.push({
873
+ name,
874
+ slug: name,
875
+ registrySlug: m.slug,
876
+ source,
877
+ raw,
878
+ verified: m.verified,
879
+ type: m.type
880
+ });
881
+ });
882
+ const multi = out.filter((c) => c.type === "pack");
883
+ if (multi.length) {
884
+ let n = 0;
885
+ await Promise.all(Array.from({ length: Math.min(4, multi.length) }, async () => {
886
+ for (let i = n++; i < multi.length; i = n++) {
887
+ const c = multi[i];
888
+ try {
889
+ const bundle = await fetchBundle(apiBase2, c.registrySlug, token2);
890
+ if (!bundle?.length) continue;
891
+ const sk = bundle.find((file) => file.path === "SKILL.md");
892
+ const unverified = bundle.filter((file) => file.unverified).length;
893
+ c.files = bundle;
894
+ if (sk) c.raw = sk.contents.toString("utf8");
895
+ if (unverified) c.note = unverifiedFilesNote(unverified);
896
+ } catch {
897
+ }
898
+ }
899
+ }));
900
+ }
901
+ if (!out.length) throw new Error(`plugin ${spec.owner}/${spec.slug} returned no readable skills`);
902
+ const first = out[0];
903
+ first.note = `${out.length} skill${out.length === 1 ? "" : "s"} from the ${packName} plugin` + (missing.length ? ` (${missing.length} member${missing.length === 1 ? "" : "s"} were not in the archive)` : "");
904
+ return out;
905
+ }
706
906
  if (spec.kind !== "slug") {
707
907
  const rs = await resolveSource(argWithRef(arg, f.ref));
708
908
  return rs.map((r) => ({
@@ -724,34 +924,7 @@ function makeDefaultDeps(flags) {
724
924
  })();
725
925
  const registrySlug = `${spec.owner}/${spec.name}`;
726
926
  try {
727
- const skill = await api(`/api/skills/${spec.owner}/${spec.name}`);
728
- const skillName = skill.slug.split("/")[1] || spec.name;
729
- const meta = {
730
- name: skillName,
731
- slug: skillName,
732
- registrySlug,
733
- source,
734
- commit_sha: skill.commit_sha ?? void 0,
735
- verified: skill.verified,
736
- type: skill.type,
737
- securityFlags: skill.security_flags
738
- };
739
- if (skill.type === "pack") {
740
- const bundle = await fetchBundle(apiBase, registrySlug, token);
741
- if (bundle && bundle.length) {
742
- const sk = bundle.find((file) => file.path === "SKILL.md");
743
- const unverified = bundle.filter((file) => file.unverified).length;
744
- return [{
745
- ...meta,
746
- raw: sk ? sk.contents.toString("utf8") : skillMdFor(skill),
747
- files: bundle,
748
- ...unverified ? { note: unverifiedFilesNote(unverified) } : {}
749
- }];
750
- }
751
- const pack = await resolvePackFiles(skill);
752
- if (pack) return [{ ...meta, ...pack }];
753
- }
754
- return [{ ...meta, raw: skillMdFor(skill) }];
927
+ return [await registryCandidate(spec.owner, spec.name, source, { api, base: apiBase, token })];
755
928
  } catch (e) {
756
929
  if (e instanceof IntegrityError) throw e;
757
930
  if (e instanceof RegistryError && e.status !== 404) registryDown = e;
@@ -785,7 +958,7 @@ function makeDefaultDeps(flags) {
785
958
  };
786
959
  }
787
960
  async function resolveScope(flags, deps) {
788
- const home = resolve3(flags.home ?? homedir3());
961
+ const home = resolve3(flags.home ?? homedir4());
789
962
  const cwd = resolve3(flags.cwd ?? process.cwd());
790
963
  const base = { cwd: flags.cwd, home: flags.home, env: flags.env };
791
964
  if (flags.global) return { scope: { global: true, ...base } };
@@ -808,7 +981,7 @@ function defaultNeedsPrompt(flags) {
808
981
  const env = flags.env ?? process.env;
809
982
  if (flags.yes || flags.json || flags.global || flags.project || flags.list) return false;
810
983
  if (detectHostAgent(env)) return false;
811
- if (resolve3(flags.cwd ?? process.cwd()) === resolve3(flags.home ?? homedir3())) return false;
984
+ if (resolve3(flags.cwd ?? process.cwd()) === resolve3(flags.home ?? homedir4())) return false;
812
985
  return !isInteractive(flags, { stdinTTY: Boolean(process.stdin.isTTY), stdoutTTY: Boolean(process.stdout.isTTY), env });
813
986
  }
814
987
  var CANCELLED_TEXT = "installation cancelled \u2014 nothing was installed";
@@ -823,7 +996,7 @@ function finish2(flags, outcome) {
823
996
  ...outcome.notices,
824
997
  ...outcome.records.flatMap((r) => r.kind === "installed" ? r.notices : [])
825
998
  ].map((n) => n.text);
826
- const exitCode = blocked.length > 0 || installed.length === 0 ? 1 : 0;
999
+ const exitCode = blocked.length > 0 || installed.length === 0 && !outcome.managed ? 1 : 0;
827
1000
  const doc = { ok: exitCode === 0, scope: outcome.scope, installed, blocked, warnings };
828
1001
  return {
829
1002
  written,
@@ -985,9 +1158,28 @@ async function installOne(c, ctx) {
985
1158
  ...out.replaced ? { replaced: out.replaced } : {}
986
1159
  };
987
1160
  }
1161
+ async function tryPluginInstall(arg, flags) {
1162
+ let spec;
1163
+ try {
1164
+ spec = parseSource(arg, { ref: flags.ref });
1165
+ } catch {
1166
+ return null;
1167
+ }
1168
+ if (spec.kind !== "plugin") return null;
1169
+ if (flags.agent?.length || flags.skill?.length || flags.list || flags.mode === "copy") return null;
1170
+ if (!await claudeCliAvailable()) return null;
1171
+ const res = await installClaudePlugin(spec.owner, spec.slug);
1172
+ if (!res.ok) return null;
1173
+ return {
1174
+ agent: "claude-code",
1175
+ name: res.name,
1176
+ note: `installed ${res.name} as a Claude Code plugin \u2014 manage it with \`claude plugin\` or /plugin`
1177
+ };
1178
+ }
988
1179
  async function runAdd(arg, flags, deps) {
989
1180
  const d = deps ?? makeDefaultDeps(flags);
990
1181
  const env = flags.env ?? process.env;
1182
+ const managed = await tryPluginInstall(arg, flags);
991
1183
  let candidates;
992
1184
  try {
993
1185
  candidates = await resolveCandidates(arg, flags, d);
@@ -1004,14 +1196,20 @@ async function runAdd(arg, flags, deps) {
1004
1196
  if (!resolved) return finish2(flags, { kind: "cancel" });
1005
1197
  const scope = resolved.scope;
1006
1198
  const notices = resolved.note ? [infoNotice(resolved.note)] : [];
1007
- const chosen = await chooseTargets(flags, d, scope, detectAgents(scope));
1199
+ if (managed) notices.push(infoNotice(managed.note));
1200
+ const detected = detectAgents(scope).filter((id) => id !== managed?.agent);
1201
+ if (managed && !detected.length) {
1202
+ const paths2 = { home: flags.home ?? homedir4(), cwd: flags.cwd ?? process.cwd() };
1203
+ return finish2(flags, { kind: "install", scope: scope.global ? "global" : "project", records: [], notices, paths: paths2, managed: managed.name });
1204
+ }
1205
+ const chosen = await chooseTargets(flags, d, scope, detected);
1008
1206
  if (!chosen) return finish2(flags, { kind: "cancel" });
1009
1207
  notices.push(...chosen.notices);
1010
1208
  const ctx = { arg, flags, deny: new Set(flags.deny ?? []), scope, targets: chosen.targets, env, deps: d };
1011
1209
  const records = [];
1012
1210
  for (const c of candidates) records.push(await installOne(c, ctx));
1013
- const paths = { home: flags.home ?? homedir3(), cwd: flags.cwd ?? process.cwd() };
1014
- return finish2(flags, { kind: "install", scope: scope.global ? "global" : "project", records, notices, paths });
1211
+ const paths = { home: flags.home ?? homedir4(), cwd: flags.cwd ?? process.cwd() };
1212
+ return finish2(flags, { kind: "install", scope: scope.global ? "global" : "project", records, notices, paths, ...managed ? { managed: managed.name } : {} });
1015
1213
  }
1016
1214
  function addFlags(opts, parent) {
1017
1215
  const merged = { ...parent, ...opts };
@@ -1021,12 +1219,12 @@ function addFlags(opts, parent) {
1021
1219
  function addCommand() {
1022
1220
  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 your user-level agent folders (available in every project)").option("-p, --project", "install into the current directory's agent folders (committed with the project)").option("-a, --agent <agents...>", "target specific agents (default: every agent detected on this machine)").option("-s, --skill <names...>", "install only specific skills by name ('*' for all)").option("--ref <ref>", "git ref (branch, tag or commit) for GitHub sources").option("-l, --list", "list the skills a source contains without installing").addOption(new Option("--mode <mode>", "link (junction/symlink into each agent dir, default) or copy").choices(["link", "copy"]).default("link")).option("--force", "replace a skill installed from a different source, or an untracked directory").addOption(new Option("--copy", "alias of --mode copy").hideHelp()).option("-y, --yes", "skip prompts; scope is auto-detected (project if this folder looks like a project, else global)").addOption(new Option("--allow-unverified").hideHelp()).option("--skip-lint", "install even if the skill fails lint").option("--deny <flags...>", "refuse skills carrying any of these security flags (opt-in)").action(async (source, opts, cmd) => {
1023
1221
  const merged = addFlags(opts, cmd.parent?.opts());
1024
- const { run, printed } = await runAddWithUI(source, merged);
1222
+ const { run: run2, printed } = await runAddWithUI(source, merged);
1025
1223
  if (!printed) {
1026
- if (run.exitCode === 1 && !merged.json) console.error(run.output);
1027
- else console.log(run.output);
1224
+ if (run2.exitCode === 1 && !merged.json) console.error(run2.output);
1225
+ else console.log(run2.output);
1028
1226
  }
1029
- process.exitCode = run.exitCode;
1227
+ process.exitCode = run2.exitCode;
1030
1228
  });
1031
1229
  }
1032
1230
  var clackUI = {
@@ -1043,7 +1241,7 @@ async function runAddWithUI(source, flags, opts = {}) {
1043
1241
  const ui = opts.ui ?? clackUI;
1044
1242
  ui.intro(banner());
1045
1243
  try {
1046
- const run = await runAdd(source, flags, {
1244
+ const run2 = await runAdd(source, flags, {
1047
1245
  // NOTE: spreading `base` reads makeDefaultDeps()'s promptScope/promptAgents
1048
1246
  // getters right here, freezing their values for this run. That is exactly
1049
1247
  // what we want (interactivity is decided once, up front), but it does mean
@@ -1063,13 +1261,13 @@ async function runAddWithUI(source, flags, opts = {}) {
1063
1261
  }
1064
1262
  }
1065
1263
  });
1066
- if (run.cancelled) {
1264
+ if (run2.cancelled) {
1067
1265
  ui.cancel("Installation cancelled \u2014 nothing was installed.");
1068
- return { run, printed: true };
1266
+ return { run: run2, printed: true };
1069
1267
  }
1070
- ui.message(run.output);
1071
- ui.outro(run.exitCode === 0 ? `Done. Run ${pc8.cyan("skillmd list")} to see everything installed.` : pc8.red("Some skills were not installed."));
1072
- return { run, printed: true };
1268
+ ui.message(run2.output);
1269
+ ui.outro(run2.exitCode === 0 ? `Done. Run ${pc8.cyan("skillmd list")} to see everything installed.` : pc8.red("Some skills were not installed."));
1270
+ return { run: run2, printed: true };
1073
1271
  } catch (e) {
1074
1272
  ui.outro(pc8.red("Nothing was installed."));
1075
1273
  throw e;
@@ -1078,8 +1276,8 @@ async function runAddWithUI(source, flags, opts = {}) {
1078
1276
 
1079
1277
  // src/commands/list.ts
1080
1278
  import { Command as Command8 } from "commander";
1081
- import { existsSync as existsSync2, readFileSync } from "fs";
1082
- import { join as join3 } from "path";
1279
+ import { existsSync as existsSync3, readFileSync } from "fs";
1280
+ import { join as join4 } from "path";
1083
1281
  import pc9 from "picocolors";
1084
1282
  import { parseSkillMd as parseSkillMd3 } from "@skillmds/core";
1085
1283
  function runList(flags = {}) {
@@ -1087,9 +1285,9 @@ function runList(flags = {}) {
1087
1285
  let items = [];
1088
1286
  for (const global of scopes) {
1089
1287
  for (const s of listInstalled({ global, cwd: flags.cwd, home: flags.home })) {
1090
- const md = join3(s.path, "SKILL.md");
1288
+ const md = join4(s.path, "SKILL.md");
1091
1289
  let description = "";
1092
- if (existsSync2(md)) {
1290
+ if (existsSync3(md)) {
1093
1291
  const parsed = parseSkillMd3(readFileSync(md, "utf8"));
1094
1292
  description = "error" in parsed ? `(invalid SKILL.md \u2014 ${parsed.error})` : parsed.description;
1095
1293
  }
@@ -1120,12 +1318,12 @@ function listCommand() {
1120
1318
  // src/commands/remove.ts
1121
1319
  import { Command as Command9 } from "commander";
1122
1320
  import { rmSync } from "fs";
1123
- import { join as join4, resolve as resolve4 } from "path";
1321
+ import { join as join5, resolve as resolve4 } from "path";
1124
1322
  import * as p4 from "@clack/prompts";
1125
1323
  import pc10 from "picocolors";
1126
1324
  var defaultDeps = (flags) => isInteractive(flags) ? {
1127
- confirm: async (msg) => {
1128
- const a = await p4.confirm({ message: msg, initialValue: false });
1325
+ confirm: async (msg2) => {
1326
+ const a = await p4.confirm({ message: msg2, initialValue: false });
1129
1327
  return !p4.isCancel(a) && a;
1130
1328
  },
1131
1329
  pick: async ({ names }) => {
@@ -1208,7 +1406,7 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
1208
1406
  acted = true;
1209
1407
  continue;
1210
1408
  }
1211
- const pth = join4(dir, name);
1409
+ const pth = join5(dir, name);
1212
1410
  if (isSkillDirOrLink(pth)) {
1213
1411
  rmSync(pth, { recursive: true, force: true });
1214
1412
  removed.push(pth);
@@ -1242,9 +1440,9 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
1242
1440
  }
1243
1441
  function removeCommand() {
1244
1442
  return new Command9("remove").alias("rm").description("Remove installed skills (links, canonical copy and lock entry)").argument("[names...]", "skill names (omit to pick interactively)").option("-g, --global", "only user-level skills").option("-p, --project", "only this project's skills").option("-a, --agent <agents...>", "remove only from these agents").option("--all", "remove every skill in the scope").option("-y, --yes", "skip the confirmation").option("--json", "machine-readable output").action(async (names, opts, cmd) => {
1245
- const run = await runRemove(names, { ...cmd.parent?.opts(), ...opts });
1246
- console.log(run.output);
1247
- process.exitCode = run.exitCode;
1443
+ const run2 = await runRemove(names, { ...cmd.parent?.opts(), ...opts });
1444
+ console.log(run2.output);
1445
+ process.exitCode = run2.exitCode;
1248
1446
  });
1249
1447
  }
1250
1448
 
@@ -1378,16 +1576,16 @@ function scopeOptions(cmd) {
1378
1576
  }
1379
1577
  function updateCommand() {
1380
1578
  return scopeOptions(new Command10("update").description("Update installed skills from the source recorded in the lock file (re-lints before writing)").argument("[names...]", "specific skills (default: all tracked)").option("--check", "only report which skills have updates")).action(async (names, opts, cmd) => {
1381
- const run = await runUpdate(names, { ...cmd.parent?.opts(), ...opts });
1382
- console.log(run.output);
1383
- process.exitCode = run.exitCode;
1579
+ const run2 = await runUpdate(names, { ...cmd.parent?.opts(), ...opts });
1580
+ console.log(run2.output);
1581
+ process.exitCode = run2.exitCode;
1384
1582
  });
1385
1583
  }
1386
1584
  function checkCommand() {
1387
1585
  return scopeOptions(new Command10("check").description("Report installed skills that have updates (alias of `update --check`)").argument("[names...]", "specific skills (default: all tracked)")).action(async (names, opts, cmd) => {
1388
- const run = await runUpdate(names, { ...cmd.parent?.opts(), ...opts, check: true });
1389
- console.log(run.output);
1390
- process.exitCode = run.exitCode;
1586
+ const run2 = await runUpdate(names, { ...cmd.parent?.opts(), ...opts, check: true });
1587
+ console.log(run2.output);
1588
+ process.exitCode = run2.exitCode;
1391
1589
  });
1392
1590
  }
1393
1591
 
@@ -1435,9 +1633,9 @@ ${pc12.green(`\u2713 Published ${res.slug ?? src.slug} (status: ${res.status ??
1435
1633
  function publishCommand() {
1436
1634
  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) => {
1437
1635
  const merged = { ...cmd.parent?.opts(), ...opts };
1438
- const run = await runPublish(target, merged);
1439
- console.log(run.output);
1440
- process.exitCode = run.exitCode;
1636
+ const run2 = await runPublish(target, merged);
1637
+ console.log(run2.output);
1638
+ process.exitCode = run2.exitCode;
1441
1639
  });
1442
1640
  }
1443
1641
 
@@ -1473,8 +1671,8 @@ async function runLogin(flags, deps = defaultLoginDeps(flags)) {
1473
1671
  try {
1474
1672
  host = hostOf(resolveApi({ api: flags.api, insecureHttp: flags.insecureHttp }, { env, config: { ...cfg, api: flags.api ?? cfg.api } }));
1475
1673
  } catch (e) {
1476
- const msg = e instanceof Error ? e.message : String(e);
1477
- return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg }, null, 2) : pc13.red(msg) };
1674
+ const msg2 = e instanceof Error ? e.message : String(e);
1675
+ return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg2 }, null, 2) : pc13.red(msg2) };
1478
1676
  }
1479
1677
  cfg.token = token;
1480
1678
  if (flags.api) cfg.api = flags.api;
@@ -1576,9 +1774,9 @@ async function browseOneResult(item) {
1576
1774
  }
1577
1775
  p6.log.step(`Installing ${slugTxt}\u2026`);
1578
1776
  try {
1579
- const run = await runAdd(item.slug, action === "global" ? { global: true } : { project: true });
1580
- console.log(run.output);
1581
- if (run.exitCode === 0) p6.log.success("Installed.");
1777
+ const run2 = await runAdd(item.slug, action === "global" ? { global: true } : { project: true });
1778
+ console.log(run2.output);
1779
+ if (run2.exitCode === 0) p6.log.success("Installed.");
1582
1780
  else p6.log.warn("Not installed.");
1583
1781
  } catch (err) {
1584
1782
  p6.log.error(err instanceof Error ? err.message : String(err));
@@ -1613,10 +1811,10 @@ async function runInteractive() {
1613
1811
  const s = p6.spinner();
1614
1812
  s.start(action === "lint" ? "Linting" : "Scanning");
1615
1813
  try {
1616
- const run = action === "lint" ? await runLint(target, { format: "text" }) : await runScan(target, { format: "text" });
1814
+ const run2 = action === "lint" ? await runLint(target, { format: "text" }) : await runScan(target, { format: "text" });
1617
1815
  s.stop("Done");
1618
- console.log(run.output);
1619
- process.exitCode = run.exitCode;
1816
+ console.log(run2.output);
1817
+ process.exitCode = run2.exitCode;
1620
1818
  } catch (err) {
1621
1819
  s.stop(pc14.red(err instanceof Error ? err.message : String(err)));
1622
1820
  process.exitCode = 1;
@@ -1631,13 +1829,13 @@ async function runInteractive() {
1631
1829
  const s = p6.spinner();
1632
1830
  s.start("Searching");
1633
1831
  try {
1634
- const run = await runSearch(String(query), { verified: Boolean(verified), limit: "20" });
1635
- s.stop(`${run.items.length} result${run.items.length === 1 ? "" : "s"}`);
1636
- if (run.items.length === 0) {
1832
+ const run2 = await runSearch(String(query), { verified: Boolean(verified), limit: "20" });
1833
+ s.stop(`${run2.items.length} result${run2.items.length === 1 ? "" : "s"}`);
1834
+ if (run2.items.length === 0) {
1637
1835
  p6.log.info("No results.");
1638
1836
  break;
1639
1837
  }
1640
- await browseSearchResults(run.items);
1838
+ await browseSearchResults(run2.items);
1641
1839
  } catch (err) {
1642
1840
  s.stop(pc14.red(err instanceof Error ? err.message : String(err)));
1643
1841
  process.exitCode = 1;
@@ -1662,9 +1860,9 @@ async function runInteractive() {
1662
1860
  const src = await p6.text({ message: "Skill source", placeholder: "owner/name, https://github.com/\u2026, or ./path" });
1663
1861
  if (bail(src)) break;
1664
1862
  try {
1665
- const run = await runAdd(String(src), {});
1666
- console.log(run.output);
1667
- process.exitCode = run.exitCode;
1863
+ const run2 = await runAdd(String(src), {});
1864
+ console.log(run2.output);
1865
+ process.exitCode = run2.exitCode;
1668
1866
  } catch (err) {
1669
1867
  p6.log.error(err instanceof Error ? err.message : String(err));
1670
1868
  process.exitCode = 1;
@@ -1677,9 +1875,9 @@ async function runInteractive() {
1677
1875
  }
1678
1876
  case "update": {
1679
1877
  try {
1680
- const run = await runUpdate([], {});
1681
- console.log(run.output);
1682
- process.exitCode = run.exitCode;
1878
+ const run2 = await runUpdate([], {});
1879
+ console.log(run2.output);
1880
+ process.exitCode = run2.exitCode;
1683
1881
  } catch (err) {
1684
1882
  p6.log.error(err instanceof Error ? err.message : String(err));
1685
1883
  process.exitCode = 1;
@@ -1688,9 +1886,9 @@ async function runInteractive() {
1688
1886
  }
1689
1887
  case "remove": {
1690
1888
  try {
1691
- const run = await runRemove([], {});
1692
- console.log(run.output);
1693
- process.exitCode = run.exitCode;
1889
+ const run2 = await runRemove([], {});
1890
+ console.log(run2.output);
1891
+ process.exitCode = run2.exitCode;
1694
1892
  } catch (err) {
1695
1893
  p6.log.error(err instanceof Error ? err.message : String(err));
1696
1894
  process.exitCode = 1;
package/dist/mcp.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  safeText,
13
13
  stripTerminalEscapes,
14
14
  telemetryDisabled
15
- } from "./chunk-4BFQJ3YZ.js";
15
+ } from "./chunk-FVWCI37S.js";
16
16
 
17
17
  // src/mcp/server.ts
18
18
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillmds",
3
- "version": "1.2.1",
3
+ "version": "1.3.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
5
  "license": "MIT",
6
6
  "author": "SkillMD",
@@ -59,6 +59,7 @@
59
59
  "@modelcontextprotocol/sdk": "^1.13.0",
60
60
  "@skillmds/core": "^1.0.0",
61
61
  "commander": "^12.1.0",
62
+ "fflate": "^0.8.3",
62
63
  "giget": "^3.3.0",
63
64
  "picocolors": "^1.1.0"
64
65
  },