skillmds 1.3.0 → 1.3.2
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/dist/cli.js +138 -16
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
|
|
35
35
|
// src/index.ts
|
|
36
36
|
import { Command as Command13 } from "commander";
|
|
37
|
-
import { readFileSync as
|
|
37
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
38
38
|
import pc15 from "picocolors";
|
|
39
39
|
|
|
40
40
|
// src/commands/lint.ts
|
|
@@ -623,7 +623,7 @@ function infoCommand() {
|
|
|
623
623
|
import { Command as Command7, Option } from "commander";
|
|
624
624
|
import { homedir as homedir4 } from "os";
|
|
625
625
|
import * as p3 from "@clack/prompts";
|
|
626
|
-
import { dirname, resolve as resolve3 } from "path";
|
|
626
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
627
627
|
import pc8 from "picocolors";
|
|
628
628
|
import { lint as lint2, parseSkillMd as parseSkillMd2 } from "@skillmds/core";
|
|
629
629
|
|
|
@@ -633,6 +633,8 @@ import { existsSync as existsSync2 } from "fs";
|
|
|
633
633
|
import { homedir as homedir3 } from "os";
|
|
634
634
|
import { delimiter, join as join2 } from "path";
|
|
635
635
|
import { promisify } from "util";
|
|
636
|
+
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync as writeFileSync3 } from "fs";
|
|
637
|
+
import { dirname } from "path";
|
|
636
638
|
var run = promisify(execFile);
|
|
637
639
|
function resolveClaudeBin(env = process.env) {
|
|
638
640
|
const override = env.SKILLMD_CLAUDE_BIN;
|
|
@@ -685,6 +687,10 @@ async function installClaudePlugin(owner, slug, deps = {}) {
|
|
|
685
687
|
} catch (e) {
|
|
686
688
|
return { ok: false, reason: `could not add the SkillMD marketplace: ${msg(e)}` };
|
|
687
689
|
}
|
|
690
|
+
try {
|
|
691
|
+
await sh("claude", ["plugin", "marketplace", "update", MARKETPLACE_NAME]);
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
688
694
|
let out;
|
|
689
695
|
try {
|
|
690
696
|
const r = await sh("claude", ["plugin", "install", qualified]);
|
|
@@ -704,6 +710,102 @@ function msg(e) {
|
|
|
704
710
|
}
|
|
705
711
|
return e instanceof Error ? e.message.split(/\r?\n/)[0] : String(e);
|
|
706
712
|
}
|
|
713
|
+
var SITE_URL = "https://skillmd.com";
|
|
714
|
+
function readJson(path) {
|
|
715
|
+
let text3;
|
|
716
|
+
try {
|
|
717
|
+
text3 = readFileSync(path, "utf8");
|
|
718
|
+
} catch {
|
|
719
|
+
return "absent";
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
const v = JSON.parse(text3);
|
|
723
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : "unparsable";
|
|
724
|
+
} catch {
|
|
725
|
+
return "unparsable";
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
function writeJson(path, value) {
|
|
729
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
730
|
+
writeFileSync3(path, `${JSON.stringify(value, null, 2)}
|
|
731
|
+
`);
|
|
732
|
+
}
|
|
733
|
+
var obj = (v) => v && typeof v === "object" && !Array.isArray(v) ? { ...v } : {};
|
|
734
|
+
async function installPluginNatively(owner, slug, deps = {}) {
|
|
735
|
+
const home = deps.home ?? (process.env.USERPROFILE || process.env.HOME || homedir3());
|
|
736
|
+
const claudeDir = join2(home, ".claude");
|
|
737
|
+
if (!existsSync2(claudeDir)) return { ok: false, reason: "no Claude Code directory on this machine" };
|
|
738
|
+
const name = marketplacePluginName(owner, slug);
|
|
739
|
+
const qualified = `${name}@${MARKETPLACE_NAME}`;
|
|
740
|
+
const doFetch = deps.fetch ?? fetch;
|
|
741
|
+
let entries;
|
|
742
|
+
try {
|
|
743
|
+
const res = await doFetch(`${SITE_URL}/plugins/${owner}/${slug}/plugin.zip`, { headers: { accept: "application/zip" } });
|
|
744
|
+
if (!res.ok) return { ok: false, reason: `the plugin archive answered ${res.status}` };
|
|
745
|
+
const { unzipSync } = await import("fflate");
|
|
746
|
+
entries = unzipSync(new Uint8Array(await res.arrayBuffer()));
|
|
747
|
+
} catch (e) {
|
|
748
|
+
return { ok: false, reason: msg(e) };
|
|
749
|
+
}
|
|
750
|
+
let version2 = "1.0.0";
|
|
751
|
+
const manifest = entries[".claude-plugin/plugin.json"];
|
|
752
|
+
if (manifest) {
|
|
753
|
+
try {
|
|
754
|
+
const v = JSON.parse(new TextDecoder().decode(manifest));
|
|
755
|
+
if (typeof v.version === "string" && v.version) version2 = v.version;
|
|
756
|
+
} catch {
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (!manifest) return { ok: false, reason: "the plugin archive has no .claude-plugin/plugin.json" };
|
|
760
|
+
const pluginsDir = join2(claudeDir, "plugins");
|
|
761
|
+
const installPath = join2(pluginsDir, "cache", MARKETPLACE_NAME, name, version2);
|
|
762
|
+
const knownPath = join2(pluginsDir, "known_marketplaces.json");
|
|
763
|
+
const installedPath = join2(pluginsDir, "installed_plugins.json");
|
|
764
|
+
const settingsPath = join2(claudeDir, "settings.json");
|
|
765
|
+
const known = readJson(knownPath);
|
|
766
|
+
if (known === "unparsable") return { ok: false, reason: "known_marketplaces.json is not readable JSON \u2014 left untouched" };
|
|
767
|
+
const installed = readJson(installedPath);
|
|
768
|
+
if (installed === "unparsable") return { ok: false, reason: "installed_plugins.json is not readable JSON \u2014 left untouched" };
|
|
769
|
+
const settings = readJson(settingsPath);
|
|
770
|
+
if (settings === "unparsable") return { ok: false, reason: "settings.json is not readable JSON \u2014 left untouched" };
|
|
771
|
+
try {
|
|
772
|
+
for (const [path, data] of Object.entries(entries)) {
|
|
773
|
+
if (path.endsWith("/")) continue;
|
|
774
|
+
const dest = join2(installPath, ...path.split("/"));
|
|
775
|
+
mkdirSync2(dirname(dest), { recursive: true });
|
|
776
|
+
writeFileSync3(dest, data);
|
|
777
|
+
}
|
|
778
|
+
writeFileSync3(join2(installPath, ".in_use"), "");
|
|
779
|
+
mkdirSync2(join2(pluginsDir, "marketplaces", MARKETPLACE_NAME), { recursive: true });
|
|
780
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
781
|
+
const source = { source: "url", url: MARKETPLACE_URL };
|
|
782
|
+
const knownDoc = known === "absent" ? {} : known;
|
|
783
|
+
knownDoc[MARKETPLACE_NAME] = {
|
|
784
|
+
...obj(knownDoc[MARKETPLACE_NAME]),
|
|
785
|
+
source,
|
|
786
|
+
installLocation: join2(pluginsDir, "marketplaces", MARKETPLACE_NAME),
|
|
787
|
+
lastUpdated: now
|
|
788
|
+
};
|
|
789
|
+
writeJson(knownPath, knownDoc);
|
|
790
|
+
const installedDoc = installed === "absent" ? { version: 2, plugins: {} } : installed;
|
|
791
|
+
const plugins = obj(installedDoc.plugins);
|
|
792
|
+
const prior = Array.isArray(plugins[qualified]) ? plugins[qualified] : [];
|
|
793
|
+
plugins[qualified] = [
|
|
794
|
+
...prior.filter((p7) => p7?.scope !== "user"),
|
|
795
|
+
{ scope: "user", installPath, version: version2, installedAt: now, lastUpdated: now }
|
|
796
|
+
];
|
|
797
|
+
installedDoc.plugins = plugins;
|
|
798
|
+
if (typeof installedDoc.version !== "number") installedDoc.version = 2;
|
|
799
|
+
writeJson(installedPath, installedDoc);
|
|
800
|
+
const settingsDoc = settings === "absent" ? {} : settings;
|
|
801
|
+
settingsDoc.extraKnownMarketplaces = { ...obj(settingsDoc.extraKnownMarketplaces), [MARKETPLACE_NAME]: { source } };
|
|
802
|
+
settingsDoc.enabledPlugins = { ...obj(settingsDoc.enabledPlugins), [qualified]: true };
|
|
803
|
+
writeJson(settingsPath, settingsDoc);
|
|
804
|
+
} catch (e) {
|
|
805
|
+
return { ok: false, reason: msg(e) };
|
|
806
|
+
}
|
|
807
|
+
return { ok: true, name: qualified, marketplace: MARKETPLACE_NAME };
|
|
808
|
+
}
|
|
707
809
|
|
|
708
810
|
// src/commands/add.ts
|
|
709
811
|
var infoNotice = (text3) => ({ text: text3, style: "info" });
|
|
@@ -910,7 +1012,7 @@ function makeDefaultDeps(flags) {
|
|
|
910
1012
|
raw: r.raw,
|
|
911
1013
|
slug: r.slug,
|
|
912
1014
|
source,
|
|
913
|
-
...spec.kind === "local" && r.file ? { files: collectFiles(
|
|
1015
|
+
...spec.kind === "local" && r.file ? { files: collectFiles(dirname2(r.file), dirname2(r.file)) } : {}
|
|
914
1016
|
}));
|
|
915
1017
|
}
|
|
916
1018
|
let registryDown = null;
|
|
@@ -1167,10 +1269,14 @@ async function tryPluginInstall(arg, flags) {
|
|
|
1167
1269
|
}
|
|
1168
1270
|
if (spec.kind !== "plugin") return null;
|
|
1169
1271
|
if (flags.agent?.length || flags.skill?.length || flags.list || flags.mode === "copy") return null;
|
|
1170
|
-
|
|
1171
|
-
const res = await
|
|
1172
|
-
if (!res.ok)
|
|
1272
|
+
const viaCli = await claudeCliAvailable() ? await installClaudePlugin(spec.owner, spec.slug) : null;
|
|
1273
|
+
const res = viaCli?.ok ? viaCli : await installPluginNatively(spec.owner, spec.slug, { home: flags.home });
|
|
1274
|
+
if (!res.ok) {
|
|
1275
|
+
if (!viaCli && /no Claude Code directory/.test(res.reason)) return null;
|
|
1276
|
+
return { ok: false, reason: viaCli && !viaCli.ok ? viaCli.reason : res.reason };
|
|
1277
|
+
}
|
|
1173
1278
|
return {
|
|
1279
|
+
ok: true,
|
|
1174
1280
|
agent: "claude-code",
|
|
1175
1281
|
name: res.name,
|
|
1176
1282
|
note: `installed ${res.name} as a Claude Code plugin \u2014 manage it with \`claude plugin\` or /plugin`
|
|
@@ -1179,7 +1285,27 @@ async function tryPluginInstall(arg, flags) {
|
|
|
1179
1285
|
async function runAdd(arg, flags, deps) {
|
|
1180
1286
|
const d = deps ?? makeDefaultDeps(flags);
|
|
1181
1287
|
const env = flags.env ?? process.env;
|
|
1182
|
-
const
|
|
1288
|
+
const attempt = await tryPluginInstall(arg, flags);
|
|
1289
|
+
const managed = attempt?.ok ? attempt : null;
|
|
1290
|
+
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`)];
|
|
1291
|
+
let scopeOnce;
|
|
1292
|
+
const scopeOf = async () => scopeOnce ??= await resolveScope(flags, d);
|
|
1293
|
+
if (managed) {
|
|
1294
|
+
const pre = await scopeOf();
|
|
1295
|
+
if (!pre) return finish2(flags, { kind: "cancel" });
|
|
1296
|
+
const others = detectAgents(pre.scope).filter((id) => id !== managed.agent);
|
|
1297
|
+
if (!others.length) {
|
|
1298
|
+
const paths2 = { home: flags.home ?? homedir4(), cwd: flags.cwd ?? process.cwd() };
|
|
1299
|
+
return finish2(flags, {
|
|
1300
|
+
kind: "install",
|
|
1301
|
+
scope: pre.scope.global ? "global" : "project",
|
|
1302
|
+
records: [],
|
|
1303
|
+
notices: [...pre.note ? [infoNotice(pre.note)] : [], ...managedNotices],
|
|
1304
|
+
paths: paths2,
|
|
1305
|
+
managed: managed.name
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1183
1309
|
let candidates;
|
|
1184
1310
|
try {
|
|
1185
1311
|
candidates = await resolveCandidates(arg, flags, d);
|
|
@@ -1192,16 +1318,12 @@ async function runAdd(arg, flags, deps) {
|
|
|
1192
1318
|
}
|
|
1193
1319
|
const early = gate(flags, d, candidates);
|
|
1194
1320
|
if (early) return early;
|
|
1195
|
-
const resolved = await
|
|
1321
|
+
const resolved = await scopeOf();
|
|
1196
1322
|
if (!resolved) return finish2(flags, { kind: "cancel" });
|
|
1197
1323
|
const scope = resolved.scope;
|
|
1198
1324
|
const notices = resolved.note ? [infoNotice(resolved.note)] : [];
|
|
1199
|
-
|
|
1325
|
+
notices.push(...managedNotices);
|
|
1200
1326
|
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
1327
|
const chosen = await chooseTargets(flags, d, scope, detected);
|
|
1206
1328
|
if (!chosen) return finish2(flags, { kind: "cancel" });
|
|
1207
1329
|
notices.push(...chosen.notices);
|
|
@@ -1276,7 +1398,7 @@ async function runAddWithUI(source, flags, opts = {}) {
|
|
|
1276
1398
|
|
|
1277
1399
|
// src/commands/list.ts
|
|
1278
1400
|
import { Command as Command8 } from "commander";
|
|
1279
|
-
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
1401
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1280
1402
|
import { join as join4 } from "path";
|
|
1281
1403
|
import pc9 from "picocolors";
|
|
1282
1404
|
import { parseSkillMd as parseSkillMd3 } from "@skillmds/core";
|
|
@@ -1288,7 +1410,7 @@ function runList(flags = {}) {
|
|
|
1288
1410
|
const md = join4(s.path, "SKILL.md");
|
|
1289
1411
|
let description = "";
|
|
1290
1412
|
if (existsSync3(md)) {
|
|
1291
|
-
const parsed = parseSkillMd3(
|
|
1413
|
+
const parsed = parseSkillMd3(readFileSync2(md, "utf8"));
|
|
1292
1414
|
description = "error" in parsed ? `(invalid SKILL.md \u2014 ${parsed.error})` : parsed.description;
|
|
1293
1415
|
}
|
|
1294
1416
|
items.push({ ...s, description: safeText(description, 80) });
|
|
@@ -1920,7 +2042,7 @@ async function runInteractive() {
|
|
|
1920
2042
|
// src/index.ts
|
|
1921
2043
|
var version = "0.0.0";
|
|
1922
2044
|
try {
|
|
1923
|
-
version = JSON.parse(
|
|
2045
|
+
version = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
1924
2046
|
} catch {
|
|
1925
2047
|
}
|
|
1926
2048
|
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>", "personal access token (prefer `skillmd login` or SKILLMD_TOKEN \u2014 command-line tokens show up in process lists)").option("--api <url>", "override the registry API base URL").option("--insecure-http", "allow an http:// --api base (local development only)");
|
package/package.json
CHANGED