skillmds 1.2.1 → 1.3.1

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,103 @@ 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
+ try {
689
+ await sh("claude", ["plugin", "marketplace", "update", MARKETPLACE_NAME]);
690
+ } catch {
691
+ }
692
+ let out;
693
+ try {
694
+ const r = await sh("claude", ["plugin", "install", qualified]);
695
+ out = `${r.stdout}
696
+ ${r.stderr}`;
697
+ } catch (e) {
698
+ return { ok: false, reason: msg(e) };
699
+ }
700
+ if (/Successfully installed plugin/i.test(out)) return { ok: true, name: qualified, marketplace: MARKETPLACE_NAME };
701
+ const failed = /Failed to install plugin[^:]*:\s*(.+)/i.exec(out);
702
+ return { ok: false, reason: (failed?.[1] ?? out).trim().split(/\r?\n/)[0] ?? "install did not report success" };
703
+ }
704
+ function msg(e) {
705
+ if (e && typeof e === "object" && "stderr" in e) {
706
+ const s = String(e.stderr ?? "").trim();
707
+ if (s) return s.split(/\r?\n/)[0];
708
+ }
709
+ return e instanceof Error ? e.message.split(/\r?\n/)[0] : String(e);
710
+ }
711
+
712
+ // src/commands/add.ts
629
713
  var infoNotice = (text3) => ({ text: text3, style: "info" });
630
714
  var warnNotice = (text3) => ({ text: text3, style: "warn" });
631
715
  var skipNotice = (text3) => ({ text: text3, style: "skip" });
@@ -693,6 +777,77 @@ function provenanceFor(arg) {
693
777
  return `unknown:${arg}`;
694
778
  }
695
779
  }
780
+ async function registryCandidate(owner, name, source, client) {
781
+ const registrySlug = `${owner}/${name}`;
782
+ const skill = await client.api(`/api/skills/${owner}/${name}`);
783
+ const skillName = skill.slug.split("/")[1] || name;
784
+ const meta = {
785
+ name: skillName,
786
+ slug: skillName,
787
+ registrySlug,
788
+ source,
789
+ commit_sha: skill.commit_sha ?? void 0,
790
+ verified: skill.verified,
791
+ type: skill.type,
792
+ securityFlags: skill.security_flags
793
+ };
794
+ if (skill.type === "pack") {
795
+ const bundle = await fetchBundle(client.base, registrySlug, client.token);
796
+ if (bundle && bundle.length) {
797
+ const sk = bundle.find((file) => file.path === "SKILL.md");
798
+ const unverified = bundle.filter((file) => file.unverified).length;
799
+ return {
800
+ ...meta,
801
+ raw: sk ? sk.contents.toString("utf8") : skillMdFor(skill),
802
+ files: bundle,
803
+ ...unverified ? { note: unverifiedFilesNote(unverified) } : {}
804
+ };
805
+ }
806
+ const pack = await resolvePackFiles(skill);
807
+ if (pack) return { ...meta, ...pack };
808
+ }
809
+ return { ...meta, raw: skillMdFor(skill) };
810
+ }
811
+ async function fetchPackMembers(api, owner, slug) {
812
+ const PAGE = 200;
813
+ const members = [];
814
+ let name = slug;
815
+ let total = Infinity;
816
+ for (let offset = 0; members.length < total; offset += PAGE) {
817
+ const page = await api(`/api/packs/${owner}/${slug}?limit=${PAGE}&offset=${offset}`);
818
+ name = page.name || name;
819
+ total = Number(page.item_total ?? 0);
820
+ const got = (page.items ?? []).map((i) => ({ slug: String(i.slug ?? ""), type: i.type, verified: Boolean(i.verified) })).filter((i) => i.slug);
821
+ if (!got.length) break;
822
+ members.push(...got);
823
+ }
824
+ return { name, members };
825
+ }
826
+ function packDirNames(slugs) {
827
+ const used = /* @__PURE__ */ new Set();
828
+ return slugs.map((slug) => {
829
+ const [owner, name] = slug.split("/");
830
+ let dir = name ?? slug;
831
+ if (used.has(dir)) dir = `${owner}-${name}`;
832
+ used.add(dir);
833
+ return dir;
834
+ });
835
+ }
836
+ async function fetchPackArchive(base, owner, slug, token, doFetch = fetch) {
837
+ const headers = { accept: "application/zip" };
838
+ if (token) headers.authorization = `Bearer ${token}`;
839
+ const res = await doFetch(`${base}/api/packs/${owner}/${slug}/download`, { headers });
840
+ if (!res.ok) throw new RegistryError(`SkillMD API ${res.status} on /api/packs/${owner}/${slug}/download`, res.status);
841
+ const bytes = new Uint8Array(await res.arrayBuffer());
842
+ const { unzipSync, strFromU8 } = await import("fflate");
843
+ const entries = unzipSync(bytes);
844
+ const out = /* @__PURE__ */ new Map();
845
+ for (const [path, data] of Object.entries(entries)) {
846
+ const m = /^(.+)\/SKILL\.md$/.exec(path);
847
+ if (m) out.set(m[1], strFromU8(data));
848
+ }
849
+ return out;
850
+ }
696
851
  function makeDefaultDeps(flags) {
697
852
  const canPrompt = () => isInteractive(flags, {
698
853
  stdinTTY: Boolean(process.stdin.isTTY),
@@ -703,6 +858,55 @@ function makeDefaultDeps(flags) {
703
858
  async resolve(arg, f) {
704
859
  const spec = parseSource(arg, { ref: f.ref });
705
860
  const source = sourceId(spec);
861
+ if (spec.kind === "plugin") {
862
+ const { api: api2, base: apiBase2, token: token2, fetch: doFetch } = createClient(f);
863
+ const { name: packName, members } = await fetchPackMembers(api2, spec.owner, spec.slug);
864
+ if (!members.length) throw new Error(`plugin ${spec.owner}/${spec.slug} has no installable skills`);
865
+ const archive = await fetchPackArchive(apiBase2, spec.owner, spec.slug, token2, doFetch);
866
+ const dirs = packDirNames(members.map((m) => m.slug));
867
+ const out = [];
868
+ const missing = [];
869
+ members.forEach((m, i) => {
870
+ const raw = archive.get(dirs[i]);
871
+ if (!raw) {
872
+ missing.push(m.slug);
873
+ return;
874
+ }
875
+ const name = dirs[i];
876
+ out.push({
877
+ name,
878
+ slug: name,
879
+ registrySlug: m.slug,
880
+ source,
881
+ raw,
882
+ verified: m.verified,
883
+ type: m.type
884
+ });
885
+ });
886
+ const multi = out.filter((c) => c.type === "pack");
887
+ if (multi.length) {
888
+ let n = 0;
889
+ await Promise.all(Array.from({ length: Math.min(4, multi.length) }, async () => {
890
+ for (let i = n++; i < multi.length; i = n++) {
891
+ const c = multi[i];
892
+ try {
893
+ const bundle = await fetchBundle(apiBase2, c.registrySlug, token2);
894
+ if (!bundle?.length) continue;
895
+ const sk = bundle.find((file) => file.path === "SKILL.md");
896
+ const unverified = bundle.filter((file) => file.unverified).length;
897
+ c.files = bundle;
898
+ if (sk) c.raw = sk.contents.toString("utf8");
899
+ if (unverified) c.note = unverifiedFilesNote(unverified);
900
+ } catch {
901
+ }
902
+ }
903
+ }));
904
+ }
905
+ if (!out.length) throw new Error(`plugin ${spec.owner}/${spec.slug} returned no readable skills`);
906
+ const first = out[0];
907
+ 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)` : "");
908
+ return out;
909
+ }
706
910
  if (spec.kind !== "slug") {
707
911
  const rs = await resolveSource(argWithRef(arg, f.ref));
708
912
  return rs.map((r) => ({
@@ -724,34 +928,7 @@ function makeDefaultDeps(flags) {
724
928
  })();
725
929
  const registrySlug = `${spec.owner}/${spec.name}`;
726
930
  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) }];
931
+ return [await registryCandidate(spec.owner, spec.name, source, { api, base: apiBase, token })];
755
932
  } catch (e) {
756
933
  if (e instanceof IntegrityError) throw e;
757
934
  if (e instanceof RegistryError && e.status !== 404) registryDown = e;
@@ -785,7 +962,7 @@ function makeDefaultDeps(flags) {
785
962
  };
786
963
  }
787
964
  async function resolveScope(flags, deps) {
788
- const home = resolve3(flags.home ?? homedir3());
965
+ const home = resolve3(flags.home ?? homedir4());
789
966
  const cwd = resolve3(flags.cwd ?? process.cwd());
790
967
  const base = { cwd: flags.cwd, home: flags.home, env: flags.env };
791
968
  if (flags.global) return { scope: { global: true, ...base } };
@@ -808,7 +985,7 @@ function defaultNeedsPrompt(flags) {
808
985
  const env = flags.env ?? process.env;
809
986
  if (flags.yes || flags.json || flags.global || flags.project || flags.list) return false;
810
987
  if (detectHostAgent(env)) return false;
811
- if (resolve3(flags.cwd ?? process.cwd()) === resolve3(flags.home ?? homedir3())) return false;
988
+ if (resolve3(flags.cwd ?? process.cwd()) === resolve3(flags.home ?? homedir4())) return false;
812
989
  return !isInteractive(flags, { stdinTTY: Boolean(process.stdin.isTTY), stdoutTTY: Boolean(process.stdout.isTTY), env });
813
990
  }
814
991
  var CANCELLED_TEXT = "installation cancelled \u2014 nothing was installed";
@@ -823,7 +1000,7 @@ function finish2(flags, outcome) {
823
1000
  ...outcome.notices,
824
1001
  ...outcome.records.flatMap((r) => r.kind === "installed" ? r.notices : [])
825
1002
  ].map((n) => n.text);
826
- const exitCode = blocked.length > 0 || installed.length === 0 ? 1 : 0;
1003
+ const exitCode = blocked.length > 0 || installed.length === 0 && !outcome.managed ? 1 : 0;
827
1004
  const doc = { ok: exitCode === 0, scope: outcome.scope, installed, blocked, warnings };
828
1005
  return {
829
1006
  written,
@@ -985,9 +1162,49 @@ async function installOne(c, ctx) {
985
1162
  ...out.replaced ? { replaced: out.replaced } : {}
986
1163
  };
987
1164
  }
1165
+ async function tryPluginInstall(arg, flags) {
1166
+ let spec;
1167
+ try {
1168
+ spec = parseSource(arg, { ref: flags.ref });
1169
+ } catch {
1170
+ return null;
1171
+ }
1172
+ if (spec.kind !== "plugin") return null;
1173
+ if (flags.agent?.length || flags.skill?.length || flags.list || flags.mode === "copy") return null;
1174
+ if (!await claudeCliAvailable()) return null;
1175
+ const res = await installClaudePlugin(spec.owner, spec.slug);
1176
+ if (!res.ok) return { ok: false, reason: res.reason };
1177
+ return {
1178
+ ok: true,
1179
+ agent: "claude-code",
1180
+ name: res.name,
1181
+ note: `installed ${res.name} as a Claude Code plugin \u2014 manage it with \`claude plugin\` or /plugin`
1182
+ };
1183
+ }
988
1184
  async function runAdd(arg, flags, deps) {
989
1185
  const d = deps ?? makeDefaultDeps(flags);
990
1186
  const env = flags.env ?? process.env;
1187
+ const attempt = await tryPluginInstall(arg, flags);
1188
+ const managed = attempt?.ok ? attempt : null;
1189
+ const managedNotices = !attempt ? [] : attempt.ok ? [infoNotice(attempt.note)] : [warnNotice(`could not install this as a Claude Code plugin (${attempt.reason}) \u2014 installing its skills instead`)];
1190
+ let scopeOnce;
1191
+ const scopeOf = async () => scopeOnce ??= await resolveScope(flags, d);
1192
+ if (managed) {
1193
+ const pre = await scopeOf();
1194
+ if (!pre) return finish2(flags, { kind: "cancel" });
1195
+ const others = detectAgents(pre.scope).filter((id) => id !== managed.agent);
1196
+ if (!others.length) {
1197
+ const paths2 = { home: flags.home ?? homedir4(), cwd: flags.cwd ?? process.cwd() };
1198
+ return finish2(flags, {
1199
+ kind: "install",
1200
+ scope: pre.scope.global ? "global" : "project",
1201
+ records: [],
1202
+ notices: [...pre.note ? [infoNotice(pre.note)] : [], ...managedNotices],
1203
+ paths: paths2,
1204
+ managed: managed.name
1205
+ });
1206
+ }
1207
+ }
991
1208
  let candidates;
992
1209
  try {
993
1210
  candidates = await resolveCandidates(arg, flags, d);
@@ -1000,18 +1217,20 @@ async function runAdd(arg, flags, deps) {
1000
1217
  }
1001
1218
  const early = gate(flags, d, candidates);
1002
1219
  if (early) return early;
1003
- const resolved = await resolveScope(flags, d);
1220
+ const resolved = await scopeOf();
1004
1221
  if (!resolved) return finish2(flags, { kind: "cancel" });
1005
1222
  const scope = resolved.scope;
1006
1223
  const notices = resolved.note ? [infoNotice(resolved.note)] : [];
1007
- const chosen = await chooseTargets(flags, d, scope, detectAgents(scope));
1224
+ notices.push(...managedNotices);
1225
+ const detected = detectAgents(scope).filter((id) => id !== managed?.agent);
1226
+ const chosen = await chooseTargets(flags, d, scope, detected);
1008
1227
  if (!chosen) return finish2(flags, { kind: "cancel" });
1009
1228
  notices.push(...chosen.notices);
1010
1229
  const ctx = { arg, flags, deny: new Set(flags.deny ?? []), scope, targets: chosen.targets, env, deps: d };
1011
1230
  const records = [];
1012
1231
  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 });
1232
+ const paths = { home: flags.home ?? homedir4(), cwd: flags.cwd ?? process.cwd() };
1233
+ return finish2(flags, { kind: "install", scope: scope.global ? "global" : "project", records, notices, paths, ...managed ? { managed: managed.name } : {} });
1015
1234
  }
1016
1235
  function addFlags(opts, parent) {
1017
1236
  const merged = { ...parent, ...opts };
@@ -1021,12 +1240,12 @@ function addFlags(opts, parent) {
1021
1240
  function addCommand() {
1022
1241
  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
1242
  const merged = addFlags(opts, cmd.parent?.opts());
1024
- const { run, printed } = await runAddWithUI(source, merged);
1243
+ const { run: run2, printed } = await runAddWithUI(source, merged);
1025
1244
  if (!printed) {
1026
- if (run.exitCode === 1 && !merged.json) console.error(run.output);
1027
- else console.log(run.output);
1245
+ if (run2.exitCode === 1 && !merged.json) console.error(run2.output);
1246
+ else console.log(run2.output);
1028
1247
  }
1029
- process.exitCode = run.exitCode;
1248
+ process.exitCode = run2.exitCode;
1030
1249
  });
1031
1250
  }
1032
1251
  var clackUI = {
@@ -1043,7 +1262,7 @@ async function runAddWithUI(source, flags, opts = {}) {
1043
1262
  const ui = opts.ui ?? clackUI;
1044
1263
  ui.intro(banner());
1045
1264
  try {
1046
- const run = await runAdd(source, flags, {
1265
+ const run2 = await runAdd(source, flags, {
1047
1266
  // NOTE: spreading `base` reads makeDefaultDeps()'s promptScope/promptAgents
1048
1267
  // getters right here, freezing their values for this run. That is exactly
1049
1268
  // what we want (interactivity is decided once, up front), but it does mean
@@ -1063,13 +1282,13 @@ async function runAddWithUI(source, flags, opts = {}) {
1063
1282
  }
1064
1283
  }
1065
1284
  });
1066
- if (run.cancelled) {
1285
+ if (run2.cancelled) {
1067
1286
  ui.cancel("Installation cancelled \u2014 nothing was installed.");
1068
- return { run, printed: true };
1287
+ return { run: run2, printed: true };
1069
1288
  }
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 };
1289
+ ui.message(run2.output);
1290
+ ui.outro(run2.exitCode === 0 ? `Done. Run ${pc8.cyan("skillmd list")} to see everything installed.` : pc8.red("Some skills were not installed."));
1291
+ return { run: run2, printed: true };
1073
1292
  } catch (e) {
1074
1293
  ui.outro(pc8.red("Nothing was installed."));
1075
1294
  throw e;
@@ -1078,8 +1297,8 @@ async function runAddWithUI(source, flags, opts = {}) {
1078
1297
 
1079
1298
  // src/commands/list.ts
1080
1299
  import { Command as Command8 } from "commander";
1081
- import { existsSync as existsSync2, readFileSync } from "fs";
1082
- import { join as join3 } from "path";
1300
+ import { existsSync as existsSync3, readFileSync } from "fs";
1301
+ import { join as join4 } from "path";
1083
1302
  import pc9 from "picocolors";
1084
1303
  import { parseSkillMd as parseSkillMd3 } from "@skillmds/core";
1085
1304
  function runList(flags = {}) {
@@ -1087,9 +1306,9 @@ function runList(flags = {}) {
1087
1306
  let items = [];
1088
1307
  for (const global of scopes) {
1089
1308
  for (const s of listInstalled({ global, cwd: flags.cwd, home: flags.home })) {
1090
- const md = join3(s.path, "SKILL.md");
1309
+ const md = join4(s.path, "SKILL.md");
1091
1310
  let description = "";
1092
- if (existsSync2(md)) {
1311
+ if (existsSync3(md)) {
1093
1312
  const parsed = parseSkillMd3(readFileSync(md, "utf8"));
1094
1313
  description = "error" in parsed ? `(invalid SKILL.md \u2014 ${parsed.error})` : parsed.description;
1095
1314
  }
@@ -1120,12 +1339,12 @@ function listCommand() {
1120
1339
  // src/commands/remove.ts
1121
1340
  import { Command as Command9 } from "commander";
1122
1341
  import { rmSync } from "fs";
1123
- import { join as join4, resolve as resolve4 } from "path";
1342
+ import { join as join5, resolve as resolve4 } from "path";
1124
1343
  import * as p4 from "@clack/prompts";
1125
1344
  import pc10 from "picocolors";
1126
1345
  var defaultDeps = (flags) => isInteractive(flags) ? {
1127
- confirm: async (msg) => {
1128
- const a = await p4.confirm({ message: msg, initialValue: false });
1346
+ confirm: async (msg2) => {
1347
+ const a = await p4.confirm({ message: msg2, initialValue: false });
1129
1348
  return !p4.isCancel(a) && a;
1130
1349
  },
1131
1350
  pick: async ({ names }) => {
@@ -1208,7 +1427,7 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
1208
1427
  acted = true;
1209
1428
  continue;
1210
1429
  }
1211
- const pth = join4(dir, name);
1430
+ const pth = join5(dir, name);
1212
1431
  if (isSkillDirOrLink(pth)) {
1213
1432
  rmSync(pth, { recursive: true, force: true });
1214
1433
  removed.push(pth);
@@ -1242,9 +1461,9 @@ async function runRemove(names, flags, deps = defaultDeps(flags)) {
1242
1461
  }
1243
1462
  function removeCommand() {
1244
1463
  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;
1464
+ const run2 = await runRemove(names, { ...cmd.parent?.opts(), ...opts });
1465
+ console.log(run2.output);
1466
+ process.exitCode = run2.exitCode;
1248
1467
  });
1249
1468
  }
1250
1469
 
@@ -1378,16 +1597,16 @@ function scopeOptions(cmd) {
1378
1597
  }
1379
1598
  function updateCommand() {
1380
1599
  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;
1600
+ const run2 = await runUpdate(names, { ...cmd.parent?.opts(), ...opts });
1601
+ console.log(run2.output);
1602
+ process.exitCode = run2.exitCode;
1384
1603
  });
1385
1604
  }
1386
1605
  function checkCommand() {
1387
1606
  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;
1607
+ const run2 = await runUpdate(names, { ...cmd.parent?.opts(), ...opts, check: true });
1608
+ console.log(run2.output);
1609
+ process.exitCode = run2.exitCode;
1391
1610
  });
1392
1611
  }
1393
1612
 
@@ -1435,9 +1654,9 @@ ${pc12.green(`\u2713 Published ${res.slug ?? src.slug} (status: ${res.status ??
1435
1654
  function publishCommand() {
1436
1655
  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
1656
  const merged = { ...cmd.parent?.opts(), ...opts };
1438
- const run = await runPublish(target, merged);
1439
- console.log(run.output);
1440
- process.exitCode = run.exitCode;
1657
+ const run2 = await runPublish(target, merged);
1658
+ console.log(run2.output);
1659
+ process.exitCode = run2.exitCode;
1441
1660
  });
1442
1661
  }
1443
1662
 
@@ -1473,8 +1692,8 @@ async function runLogin(flags, deps = defaultLoginDeps(flags)) {
1473
1692
  try {
1474
1693
  host = hostOf(resolveApi({ api: flags.api, insecureHttp: flags.insecureHttp }, { env, config: { ...cfg, api: flags.api ?? cfg.api } }));
1475
1694
  } 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) };
1695
+ const msg2 = e instanceof Error ? e.message : String(e);
1696
+ return { exitCode: 1, output: flags.json ? JSON.stringify({ ok: false, error: msg2 }, null, 2) : pc13.red(msg2) };
1478
1697
  }
1479
1698
  cfg.token = token;
1480
1699
  if (flags.api) cfg.api = flags.api;
@@ -1576,9 +1795,9 @@ async function browseOneResult(item) {
1576
1795
  }
1577
1796
  p6.log.step(`Installing ${slugTxt}\u2026`);
1578
1797
  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.");
1798
+ const run2 = await runAdd(item.slug, action === "global" ? { global: true } : { project: true });
1799
+ console.log(run2.output);
1800
+ if (run2.exitCode === 0) p6.log.success("Installed.");
1582
1801
  else p6.log.warn("Not installed.");
1583
1802
  } catch (err) {
1584
1803
  p6.log.error(err instanceof Error ? err.message : String(err));
@@ -1613,10 +1832,10 @@ async function runInteractive() {
1613
1832
  const s = p6.spinner();
1614
1833
  s.start(action === "lint" ? "Linting" : "Scanning");
1615
1834
  try {
1616
- const run = action === "lint" ? await runLint(target, { format: "text" }) : await runScan(target, { format: "text" });
1835
+ const run2 = action === "lint" ? await runLint(target, { format: "text" }) : await runScan(target, { format: "text" });
1617
1836
  s.stop("Done");
1618
- console.log(run.output);
1619
- process.exitCode = run.exitCode;
1837
+ console.log(run2.output);
1838
+ process.exitCode = run2.exitCode;
1620
1839
  } catch (err) {
1621
1840
  s.stop(pc14.red(err instanceof Error ? err.message : String(err)));
1622
1841
  process.exitCode = 1;
@@ -1631,13 +1850,13 @@ async function runInteractive() {
1631
1850
  const s = p6.spinner();
1632
1851
  s.start("Searching");
1633
1852
  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) {
1853
+ const run2 = await runSearch(String(query), { verified: Boolean(verified), limit: "20" });
1854
+ s.stop(`${run2.items.length} result${run2.items.length === 1 ? "" : "s"}`);
1855
+ if (run2.items.length === 0) {
1637
1856
  p6.log.info("No results.");
1638
1857
  break;
1639
1858
  }
1640
- await browseSearchResults(run.items);
1859
+ await browseSearchResults(run2.items);
1641
1860
  } catch (err) {
1642
1861
  s.stop(pc14.red(err instanceof Error ? err.message : String(err)));
1643
1862
  process.exitCode = 1;
@@ -1662,9 +1881,9 @@ async function runInteractive() {
1662
1881
  const src = await p6.text({ message: "Skill source", placeholder: "owner/name, https://github.com/\u2026, or ./path" });
1663
1882
  if (bail(src)) break;
1664
1883
  try {
1665
- const run = await runAdd(String(src), {});
1666
- console.log(run.output);
1667
- process.exitCode = run.exitCode;
1884
+ const run2 = await runAdd(String(src), {});
1885
+ console.log(run2.output);
1886
+ process.exitCode = run2.exitCode;
1668
1887
  } catch (err) {
1669
1888
  p6.log.error(err instanceof Error ? err.message : String(err));
1670
1889
  process.exitCode = 1;
@@ -1677,9 +1896,9 @@ async function runInteractive() {
1677
1896
  }
1678
1897
  case "update": {
1679
1898
  try {
1680
- const run = await runUpdate([], {});
1681
- console.log(run.output);
1682
- process.exitCode = run.exitCode;
1899
+ const run2 = await runUpdate([], {});
1900
+ console.log(run2.output);
1901
+ process.exitCode = run2.exitCode;
1683
1902
  } catch (err) {
1684
1903
  p6.log.error(err instanceof Error ? err.message : String(err));
1685
1904
  process.exitCode = 1;
@@ -1688,9 +1907,9 @@ async function runInteractive() {
1688
1907
  }
1689
1908
  case "remove": {
1690
1909
  try {
1691
- const run = await runRemove([], {});
1692
- console.log(run.output);
1693
- process.exitCode = run.exitCode;
1910
+ const run2 = await runRemove([], {});
1911
+ console.log(run2.output);
1912
+ process.exitCode = run2.exitCode;
1694
1913
  } catch (err) {
1695
1914
  p6.log.error(err instanceof Error ? err.message : String(err));
1696
1915
  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.1",
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
  },