skills 1.4.2 → 1.4.4

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +75 -649
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -21,6 +21,13 @@ import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpat
21
21
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
22
22
  function getOwnerRepo(parsed) {
23
23
  if (parsed.type === "local") return null;
24
+ const sshMatch = parsed.url.match(/^git@[^:]+:(.+)$/);
25
+ if (sshMatch) {
26
+ let path = sshMatch[1];
27
+ path = path.replace(/\.git$/, "");
28
+ if (path.includes("/")) return path;
29
+ return null;
30
+ }
24
31
  if (!parsed.url.startsWith("http://") && !parsed.url.startsWith("https://")) return null;
25
32
  try {
26
33
  let path = new URL(parsed.url).pathname.slice(1);
@@ -46,22 +53,22 @@ async function isRepoPrivate(owner, repo) {
46
53
  return null;
47
54
  }
48
55
  }
56
+ function sanitizeSubpath(subpath) {
57
+ const segments = subpath.replace(/\\/g, "/").split("/");
58
+ for (const segment of segments) if (segment === "..") throw new Error(`Unsafe subpath: "${subpath}" contains path traversal segments. Subpaths must not contain ".." components.`);
59
+ return subpath;
60
+ }
49
61
  function isLocalPath(input) {
50
62
  return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
51
63
  }
52
- function isDirectSkillUrl(input) {
53
- if (!input.startsWith("http://") && !input.startsWith("https://")) return false;
54
- if (!input.toLowerCase().endsWith("/skill.md")) return false;
55
- if (input.includes("github.com/") && !input.includes("raw.githubusercontent.com")) {
56
- if (!input.includes("/blob/") && !input.includes("/raw/")) return false;
57
- }
58
- if (input.includes("gitlab.com/") && !input.includes("/-/raw/")) return false;
59
- return true;
60
- }
61
64
  const SOURCE_ALIASES = { "coinbase/agentWallet": "coinbase/agentic-wallet-skills" };
62
65
  function parseSource(input) {
63
66
  const alias = SOURCE_ALIASES[input];
64
67
  if (alias) input = alias;
68
+ const githubPrefixMatch = input.match(/^github:(.+)$/);
69
+ if (githubPrefixMatch) return parseSource(githubPrefixMatch[1]);
70
+ const gitlabPrefixMatch = input.match(/^gitlab:(.+)$/);
71
+ if (gitlabPrefixMatch) return parseSource(`https://gitlab.com/${gitlabPrefixMatch[1]}`);
65
72
  if (isLocalPath(input)) {
66
73
  const resolvedPath = resolve(input);
67
74
  return {
@@ -70,10 +77,6 @@ function parseSource(input) {
70
77
  localPath: resolvedPath
71
78
  };
72
79
  }
73
- if (isDirectSkillUrl(input)) return {
74
- type: "direct-url",
75
- url: input
76
- };
77
80
  const githubTreeWithPathMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
78
81
  if (githubTreeWithPathMatch) {
79
82
  const [, owner, repo, ref, subpath] = githubTreeWithPathMatch;
@@ -81,7 +84,7 @@ function parseSource(input) {
81
84
  type: "github",
82
85
  url: `https://github.com/${owner}/${repo}.git`,
83
86
  ref,
84
- subpath
87
+ subpath: subpath ? sanitizeSubpath(subpath) : subpath
85
88
  };
86
89
  }
87
90
  const githubTreeMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)$/);
@@ -108,7 +111,7 @@ function parseSource(input) {
108
111
  type: "gitlab",
109
112
  url: `${protocol}://${hostname}/${repoPath.replace(/\.git$/, "")}.git`,
110
113
  ref,
111
- subpath
114
+ subpath: subpath ? sanitizeSubpath(subpath) : subpath
112
115
  };
113
116
  }
114
117
  const gitlabTreeMatch = input.match(/^(https?):\/\/([^/]+)\/(.+?)\/-\/tree\/([^/]+)$/);
@@ -143,7 +146,7 @@ function parseSource(input) {
143
146
  return {
144
147
  type: "github",
145
148
  url: `https://github.com/${owner}/${repo}.git`,
146
- subpath
149
+ subpath: subpath ? sanitizeSubpath(subpath) : subpath
147
150
  };
148
151
  }
149
152
  if (isWellKnownUrl(input)) return {
@@ -162,10 +165,8 @@ function isWellKnownUrl(input) {
162
165
  if ([
163
166
  "github.com",
164
167
  "gitlab.com",
165
- "huggingface.co",
166
168
  "raw.githubusercontent.com"
167
169
  ].includes(parsed.hostname)) return false;
168
- if (input.toLowerCase().endsWith("/skill.md")) return false;
169
170
  if (input.endsWith(".git")) return false;
170
171
  return true;
171
172
  } catch {
@@ -501,9 +502,15 @@ async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
501
502
  return [];
502
503
  }
503
504
  }
505
+ function isSubpathSafe(basePath, subpath) {
506
+ const normalizedBase = normalize(resolve(basePath));
507
+ const normalizedTarget = normalize(resolve(join(basePath, subpath)));
508
+ return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
509
+ }
504
510
  async function discoverSkills(basePath, subpath, options) {
505
511
  const skills = [];
506
512
  const seenNames = /* @__PURE__ */ new Set();
513
+ if (subpath && !isSubpathSafe(basePath, subpath)) throw new Error(`Invalid subpath: "${subpath}" resolves outside the repository directory. Subpath must not contain ".." segments that escape the base path.`);
507
514
  const searchPath = subpath ? join(basePath, subpath) : basePath;
508
515
  const pluginGroupings = await getPluginGroupings(searchPath);
509
516
  const enhanceSkill = (skill) => {
@@ -1179,78 +1186,6 @@ function getCanonicalPath(skillName, options = {}) {
1179
1186
  if (!isPathSafe(canonicalBase, canonicalPath)) throw new Error("Invalid skill name: potential path traversal detected");
1180
1187
  return canonicalPath;
1181
1188
  }
1182
- async function installRemoteSkillForAgent(skill, agentType, options = {}) {
1183
- const agent = agents[agentType];
1184
- const isGlobal = options.global ?? false;
1185
- const cwd = options.cwd || process.cwd();
1186
- const installMode = options.mode ?? "symlink";
1187
- if (isGlobal && agent.globalSkillsDir === void 0) return {
1188
- success: false,
1189
- path: "",
1190
- mode: installMode,
1191
- error: `${agent.displayName} does not support global skill installation`
1192
- };
1193
- const skillName = sanitizeName(skill.installName);
1194
- const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
1195
- const canonicalDir = join(canonicalBase, skillName);
1196
- const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1197
- const agentDir = join(agentBase, skillName);
1198
- if (!isPathSafe(canonicalBase, canonicalDir)) return {
1199
- success: false,
1200
- path: agentDir,
1201
- mode: installMode,
1202
- error: "Invalid skill name: potential path traversal detected"
1203
- };
1204
- if (!isPathSafe(agentBase, agentDir)) return {
1205
- success: false,
1206
- path: agentDir,
1207
- mode: installMode,
1208
- error: "Invalid skill name: potential path traversal detected"
1209
- };
1210
- try {
1211
- if (installMode === "copy") {
1212
- await cleanAndCreateDirectory(agentDir);
1213
- await writeFile(join(agentDir, "SKILL.md"), skill.content, "utf-8");
1214
- return {
1215
- success: true,
1216
- path: agentDir,
1217
- mode: "copy"
1218
- };
1219
- }
1220
- await cleanAndCreateDirectory(canonicalDir);
1221
- await writeFile(join(canonicalDir, "SKILL.md"), skill.content, "utf-8");
1222
- if (isGlobal && isUniversalAgent(agentType)) return {
1223
- success: true,
1224
- path: canonicalDir,
1225
- canonicalPath: canonicalDir,
1226
- mode: "symlink"
1227
- };
1228
- if (!await createSymlink(canonicalDir, agentDir)) {
1229
- await cleanAndCreateDirectory(agentDir);
1230
- await writeFile(join(agentDir, "SKILL.md"), skill.content, "utf-8");
1231
- return {
1232
- success: true,
1233
- path: agentDir,
1234
- canonicalPath: canonicalDir,
1235
- mode: "symlink",
1236
- symlinkFailed: true
1237
- };
1238
- }
1239
- return {
1240
- success: true,
1241
- path: agentDir,
1242
- canonicalPath: canonicalDir,
1243
- mode: "symlink"
1244
- };
1245
- } catch (error) {
1246
- return {
1247
- success: false,
1248
- path: agentDir,
1249
- mode: installMode,
1250
- error: error instanceof Error ? error.message : "Unknown error"
1251
- };
1252
- }
1253
- }
1254
1189
  async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1255
1190
  const agent = agents[agentType];
1256
1191
  const isGlobal = options.global ?? false;
@@ -1495,108 +1430,7 @@ var ProviderRegistryImpl = class {
1495
1430
  return [...this.providers];
1496
1431
  }
1497
1432
  };
1498
- const registry = new ProviderRegistryImpl();
1499
- function registerProvider(provider) {
1500
- registry.register(provider);
1501
- }
1502
- function findProvider(url) {
1503
- return registry.findProvider(url);
1504
- }
1505
- var MintlifyProvider = class {
1506
- id = "mintlify";
1507
- displayName = "Mintlify";
1508
- match(url) {
1509
- if (!url.startsWith("http://") && !url.startsWith("https://")) return { matches: false };
1510
- if (!url.toLowerCase().endsWith("/skill.md")) return { matches: false };
1511
- if (url.includes("github.com") || url.includes("gitlab.com")) return { matches: false };
1512
- if (url.includes("huggingface.co")) return { matches: false };
1513
- return { matches: true };
1514
- }
1515
- async fetchSkill(url) {
1516
- try {
1517
- const response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
1518
- if (!response.ok) return null;
1519
- const content = await response.text();
1520
- const { data } = (0, import_gray_matter.default)(content);
1521
- const mintlifySite = data.metadata?.["mintlify-proj"];
1522
- if (!mintlifySite) return null;
1523
- if (!data.name || !data.description) return null;
1524
- return {
1525
- name: data.name,
1526
- description: data.description,
1527
- content,
1528
- installName: mintlifySite,
1529
- sourceUrl: url,
1530
- metadata: data.metadata
1531
- };
1532
- } catch {
1533
- return null;
1534
- }
1535
- }
1536
- toRawUrl(url) {
1537
- return url;
1538
- }
1539
- getSourceIdentifier(url) {
1540
- return "mintlify/com";
1541
- }
1542
- };
1543
- const mintlifyProvider = new MintlifyProvider();
1544
- var HuggingFaceProvider = class {
1545
- id = "huggingface";
1546
- displayName = "HuggingFace";
1547
- HOST = "huggingface.co";
1548
- match(url) {
1549
- if (!url.startsWith("http://") && !url.startsWith("https://")) return { matches: false };
1550
- try {
1551
- if (new URL(url).hostname !== this.HOST) return { matches: false };
1552
- } catch {
1553
- return { matches: false };
1554
- }
1555
- if (!url.toLowerCase().endsWith("/skill.md")) return { matches: false };
1556
- if (!url.includes("/spaces/")) return { matches: false };
1557
- return { matches: true };
1558
- }
1559
- async fetchSkill(url) {
1560
- try {
1561
- const rawUrl = this.toRawUrl(url);
1562
- const response = await fetch(rawUrl, { signal: AbortSignal.timeout(3e4) });
1563
- if (!response.ok) return null;
1564
- const content = await response.text();
1565
- const { data } = (0, import_gray_matter.default)(content);
1566
- if (!data.name || !data.description) return null;
1567
- const parsed = this.parseUrl(url);
1568
- if (!parsed) return null;
1569
- const installName = data.metadata?.["install-name"] || parsed.repo;
1570
- return {
1571
- name: data.name,
1572
- description: data.description,
1573
- content,
1574
- installName,
1575
- sourceUrl: url,
1576
- metadata: data.metadata
1577
- };
1578
- } catch {
1579
- return null;
1580
- }
1581
- }
1582
- toRawUrl(url) {
1583
- return url.replace("/blob/", "/raw/");
1584
- }
1585
- getSourceIdentifier(url) {
1586
- const parsed = this.parseUrl(url);
1587
- if (!parsed) return "huggingface/unknown";
1588
- return `huggingface/${parsed.owner}/${parsed.repo}`;
1589
- }
1590
- parseUrl(url) {
1591
- const match = url.match(/\/spaces\/([^/]+)\/([^/]+)/);
1592
- if (!match || !match[1] || !match[2]) return null;
1593
- return {
1594
- owner: match[1],
1595
- repo: match[2]
1596
- };
1597
- }
1598
- };
1599
- const huggingFaceProvider = new HuggingFaceProvider();
1433
+ new ProviderRegistryImpl();
1600
1434
  var WellKnownProvider = class {
1601
1435
  id = "well-known";
1602
1436
  displayName = "Well-Known Skills";
@@ -1753,15 +1587,9 @@ var WellKnownProvider = class {
1753
1587
  }
1754
1588
  getSourceIdentifier(url) {
1755
1589
  try {
1756
- const parsed = new URL(url);
1757
- const hostParts = parsed.hostname.split(".");
1758
- if (hostParts.length >= 2) {
1759
- const tld = hostParts[hostParts.length - 1];
1760
- return `${hostParts[hostParts.length - 2]}/${tld}`;
1761
- }
1762
- return parsed.hostname.replace(".", "/");
1590
+ return new URL(url).hostname.replace(/^www\./, "");
1763
1591
  } catch {
1764
- return "unknown/unknown";
1592
+ return "unknown";
1765
1593
  }
1766
1594
  }
1767
1595
  async hasSkillsIndex(url) {
@@ -1769,28 +1597,6 @@ var WellKnownProvider = class {
1769
1597
  }
1770
1598
  };
1771
1599
  const wellKnownProvider = new WellKnownProvider();
1772
- registerProvider(mintlifyProvider);
1773
- registerProvider(huggingFaceProvider);
1774
- async function fetchMintlifySkill(url) {
1775
- try {
1776
- const response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
1777
- if (!response.ok) return null;
1778
- const content = await response.text();
1779
- const { data } = (0, import_gray_matter.default)(content);
1780
- const mintlifySite = data.metadata?.["mintlify-proj"];
1781
- if (!mintlifySite) return null;
1782
- if (!data.name || !data.description) return null;
1783
- return {
1784
- name: data.name,
1785
- description: data.description,
1786
- content,
1787
- mintlifySite,
1788
- sourceUrl: url
1789
- };
1790
- } catch {
1791
- return null;
1792
- }
1793
- }
1794
1600
  const AGENTS_DIR$1 = ".agents";
1795
1601
  const LOCK_FILE$1 = ".skill-lock.json";
1796
1602
  const CURRENT_VERSION$1 = 3;
@@ -1967,7 +1773,7 @@ function createEmptyLocalLock() {
1967
1773
  skills: {}
1968
1774
  };
1969
1775
  }
1970
- var version$1 = "1.4.2";
1776
+ var version$1 = "1.4.4";
1971
1777
  const isCancelled$1 = (value) => typeof value === "symbol";
1972
1778
  async function isSourcePrivate(source) {
1973
1779
  const ownerRepo = parseOwnerRepo(source);
@@ -2133,232 +1939,6 @@ async function selectAgentsInteractive(options) {
2133
1939
  return selected;
2134
1940
  }
2135
1941
  setVersion(version$1);
2136
- async function handleRemoteSkill(source, url, options, spinner) {
2137
- const provider = findProvider(url);
2138
- if (!provider) {
2139
- await handleDirectUrlSkillLegacy(source, url, options, spinner);
2140
- return;
2141
- }
2142
- spinner.start(`Fetching skill.md from ${provider.displayName}...`);
2143
- const providerSkill = await provider.fetchSkill(url);
2144
- if (!providerSkill) {
2145
- spinner.stop(import_picocolors.default.red("Invalid skill"));
2146
- Se(import_picocolors.default.red("Could not fetch skill.md or missing required frontmatter (name, description)."));
2147
- process.exit(1);
2148
- }
2149
- const remoteSkill = {
2150
- name: providerSkill.name,
2151
- description: providerSkill.description,
2152
- content: providerSkill.content,
2153
- installName: providerSkill.installName,
2154
- sourceUrl: providerSkill.sourceUrl,
2155
- providerId: provider.id,
2156
- sourceIdentifier: provider.getSourceIdentifier(url),
2157
- metadata: providerSkill.metadata
2158
- };
2159
- spinner.stop(`Found skill: ${import_picocolors.default.cyan(remoteSkill.installName)}`);
2160
- M.info(`Skill: ${import_picocolors.default.cyan(remoteSkill.name)}`);
2161
- M.message(import_picocolors.default.dim(remoteSkill.description));
2162
- M.message(import_picocolors.default.dim(`Source: ${remoteSkill.sourceIdentifier}`));
2163
- if (options.list) {
2164
- console.log();
2165
- M.step(import_picocolors.default.bold("Skill Details"));
2166
- M.message(` ${import_picocolors.default.cyan("Name:")} ${remoteSkill.name}`);
2167
- M.message(` ${import_picocolors.default.cyan("Install as:")} ${remoteSkill.installName}`);
2168
- M.message(` ${import_picocolors.default.cyan("Provider:")} ${provider.displayName}`);
2169
- M.message(` ${import_picocolors.default.cyan("Description:")} ${remoteSkill.description}`);
2170
- console.log();
2171
- Se("Run without --list to install");
2172
- process.exit(0);
2173
- }
2174
- let targetAgents;
2175
- const validAgents = Object.keys(agents);
2176
- const universalAgents = getUniversalAgents();
2177
- if (options.agent?.includes("*")) {
2178
- targetAgents = validAgents;
2179
- M.info(`Installing to all ${targetAgents.length} agents`);
2180
- } else if (options.agent && options.agent.length > 0) {
2181
- const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
2182
- if (invalidAgents.length > 0) {
2183
- M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
2184
- M.info(`Valid agents: ${validAgents.join(", ")}`);
2185
- process.exit(1);
2186
- }
2187
- targetAgents = options.agent;
2188
- } else {
2189
- spinner.start("Loading agents...");
2190
- const installedAgents = await detectInstalledAgents();
2191
- const totalAgents = Object.keys(agents).length;
2192
- spinner.stop(`${totalAgents} agents`);
2193
- if (installedAgents.length === 0) if (options.yes) {
2194
- targetAgents = universalAgents;
2195
- M.info(`Installing to universal agents`);
2196
- } else {
2197
- const selected = await selectAgentsInteractive({ global: options.global });
2198
- if (pD(selected)) {
2199
- xe("Installation cancelled");
2200
- process.exit(0);
2201
- }
2202
- targetAgents = selected;
2203
- }
2204
- else if (installedAgents.length === 1 || options.yes) {
2205
- targetAgents = ensureUniversalAgents(installedAgents);
2206
- const { universal, symlinked } = splitAgentsByType(targetAgents);
2207
- if (symlinked.length > 0) M.info(`Installing to: ${import_picocolors.default.green("universal")} + ${symlinked.map((a) => import_picocolors.default.cyan(a)).join(", ")}`);
2208
- else M.info(`Installing to: ${import_picocolors.default.green("universal agents")}`);
2209
- } else {
2210
- const selected = await selectAgentsInteractive({ global: options.global });
2211
- if (pD(selected)) {
2212
- xe("Installation cancelled");
2213
- process.exit(0);
2214
- }
2215
- targetAgents = selected;
2216
- }
2217
- }
2218
- let installGlobally = options.global ?? false;
2219
- const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2220
- if (options.global === void 0 && !options.yes && supportsGlobal) {
2221
- const scope = await ve({
2222
- message: "Installation scope",
2223
- options: [{
2224
- value: false,
2225
- label: "Project",
2226
- hint: "Install in current directory (committed with your project)"
2227
- }, {
2228
- value: true,
2229
- label: "Global",
2230
- hint: "Install in home directory (available across all projects)"
2231
- }]
2232
- });
2233
- if (pD(scope)) {
2234
- xe("Installation cancelled");
2235
- process.exit(0);
2236
- }
2237
- installGlobally = scope;
2238
- }
2239
- let installMode = options.copy ? "copy" : "symlink";
2240
- if (!options.copy && !options.yes) {
2241
- const modeChoice = await ve({
2242
- message: "Installation method",
2243
- options: [{
2244
- value: "symlink",
2245
- label: "Symlink (Recommended)",
2246
- hint: "Single source of truth, easy updates"
2247
- }, {
2248
- value: "copy",
2249
- label: "Copy to all agents",
2250
- hint: "Independent copies for each agent"
2251
- }]
2252
- });
2253
- if (pD(modeChoice)) {
2254
- xe("Installation cancelled");
2255
- process.exit(0);
2256
- }
2257
- installMode = modeChoice;
2258
- }
2259
- const cwd = process.cwd();
2260
- const overwriteChecks = await Promise.all(targetAgents.map(async (agent) => ({
2261
- agent,
2262
- installed: await isSkillInstalled(remoteSkill.installName, agent, { global: installGlobally })
2263
- })));
2264
- const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
2265
- const summaryLines = [];
2266
- const shortCanonical = shortenPath$2(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
2267
- summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2268
- summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2269
- const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
2270
- if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
2271
- console.log();
2272
- Me(summaryLines.join("\n"), "Installation Summary");
2273
- if (!options.yes) {
2274
- const confirmed = await ye({ message: "Proceed with installation?" });
2275
- if (pD(confirmed) || !confirmed) {
2276
- xe("Installation cancelled");
2277
- process.exit(0);
2278
- }
2279
- }
2280
- spinner.start("Installing skill...");
2281
- const results = [];
2282
- for (const agent of targetAgents) {
2283
- const result = await installRemoteSkillForAgent(remoteSkill, agent, {
2284
- global: installGlobally,
2285
- mode: installMode
2286
- });
2287
- results.push({
2288
- skill: remoteSkill.installName,
2289
- agent: agents[agent].displayName,
2290
- ...result
2291
- });
2292
- }
2293
- spinner.stop("Installation complete");
2294
- console.log();
2295
- const successful = results.filter((r) => r.success);
2296
- const failed = results.filter((r) => !r.success);
2297
- if (await isSourcePrivate(remoteSkill.sourceIdentifier) !== true) track({
2298
- event: "install",
2299
- source: remoteSkill.sourceIdentifier,
2300
- skills: remoteSkill.installName,
2301
- agents: targetAgents.join(","),
2302
- ...installGlobally && { global: "1" },
2303
- skillFiles: JSON.stringify({ [remoteSkill.installName]: url }),
2304
- sourceType: remoteSkill.providerId
2305
- });
2306
- if (successful.length > 0 && installGlobally) try {
2307
- let skillFolderHash = "";
2308
- if (remoteSkill.providerId === "github") {
2309
- const hash = await fetchSkillFolderHash(remoteSkill.sourceIdentifier, url);
2310
- if (hash) skillFolderHash = hash;
2311
- }
2312
- await addSkillToLock(remoteSkill.installName, {
2313
- source: remoteSkill.sourceIdentifier,
2314
- sourceType: remoteSkill.providerId,
2315
- sourceUrl: url,
2316
- skillFolderHash
2317
- });
2318
- } catch {}
2319
- if (successful.length > 0 && !installGlobally) try {
2320
- const firstResult = successful[0];
2321
- const computedHash = await computeSkillFolderHash(firstResult.canonicalPath || firstResult.path);
2322
- await addSkillToLocalLock(remoteSkill.installName, {
2323
- source: remoteSkill.sourceIdentifier,
2324
- sourceType: remoteSkill.providerId,
2325
- computedHash
2326
- }, cwd);
2327
- } catch {}
2328
- if (successful.length > 0) {
2329
- const resultLines = [];
2330
- const firstResult = successful[0];
2331
- if (firstResult.mode === "copy") {
2332
- resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName} ${import_picocolors.default.dim("(copied)")}`);
2333
- for (const r of successful) {
2334
- const shortPath = shortenPath$2(r.path, cwd);
2335
- resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
2336
- }
2337
- } else {
2338
- if (firstResult.canonicalPath) {
2339
- const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2340
- resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2341
- } else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
2342
- resultLines.push(...buildResultLines(successful, targetAgents));
2343
- }
2344
- const title = import_picocolors.default.green("Installed 1 skill");
2345
- Me(resultLines.join("\n"), title);
2346
- const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2347
- if (symlinkFailures.length > 0) {
2348
- const copiedAgentNames = symlinkFailures.map((r) => r.agent);
2349
- M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgentNames)}`));
2350
- M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
2351
- }
2352
- }
2353
- if (failed.length > 0) {
2354
- console.log();
2355
- M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
2356
- for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
2357
- }
2358
- console.log();
2359
- Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2360
- await promptForFindSkills(options, targetAgents);
2361
- }
2362
1942
  async function handleWellKnownSkills(source, url, options, spinner) {
2363
1943
  spinner.start("Discovering skills from well-known endpoint...");
2364
1944
  const skills = await wellKnownProvider.fetchAllSkills(url);
@@ -2639,188 +2219,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2639
2219
  Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2640
2220
  await promptForFindSkills(options, targetAgents);
2641
2221
  }
2642
- async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
2643
- spinner.start("Fetching skill.md...");
2644
- const mintlifySkill = await fetchMintlifySkill(url);
2645
- if (!mintlifySkill) {
2646
- spinner.stop(import_picocolors.default.red("Invalid skill"));
2647
- Se(import_picocolors.default.red("Could not fetch skill.md or missing required frontmatter (name, description, mintlify-proj)."));
2648
- process.exit(1);
2649
- }
2650
- const remoteSkill = {
2651
- name: mintlifySkill.name,
2652
- description: mintlifySkill.description,
2653
- content: mintlifySkill.content,
2654
- installName: mintlifySkill.mintlifySite,
2655
- sourceUrl: mintlifySkill.sourceUrl,
2656
- providerId: "mintlify",
2657
- sourceIdentifier: "mintlify/com"
2658
- };
2659
- spinner.stop(`Found skill: ${import_picocolors.default.cyan(remoteSkill.installName)}`);
2660
- M.info(`Skill: ${import_picocolors.default.cyan(remoteSkill.name)}`);
2661
- M.message(import_picocolors.default.dim(remoteSkill.description));
2662
- if (options.list) {
2663
- console.log();
2664
- M.step(import_picocolors.default.bold("Skill Details"));
2665
- M.message(` ${import_picocolors.default.cyan("Name:")} ${remoteSkill.name}`);
2666
- M.message(` ${import_picocolors.default.cyan("Site:")} ${remoteSkill.installName}`);
2667
- M.message(` ${import_picocolors.default.cyan("Description:")} ${remoteSkill.description}`);
2668
- console.log();
2669
- Se("Run without --list to install");
2670
- process.exit(0);
2671
- }
2672
- let targetAgents;
2673
- const validAgents = Object.keys(agents);
2674
- if (options.agent?.includes("*")) {
2675
- targetAgents = validAgents;
2676
- M.info(`Installing to all ${targetAgents.length} agents`);
2677
- } else if (options.agent && options.agent.length > 0) {
2678
- const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
2679
- if (invalidAgents.length > 0) {
2680
- M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
2681
- M.info(`Valid agents: ${validAgents.join(", ")}`);
2682
- process.exit(1);
2683
- }
2684
- targetAgents = options.agent;
2685
- } else {
2686
- spinner.start("Loading agents...");
2687
- const installedAgents = await detectInstalledAgents();
2688
- const totalAgents = Object.keys(agents).length;
2689
- spinner.stop(`${totalAgents} agents`);
2690
- if (installedAgents.length === 0) if (options.yes) {
2691
- targetAgents = validAgents;
2692
- M.info("Installing to all agents");
2693
- } else {
2694
- M.info("Select agents to install skills to");
2695
- const selected = await promptForAgents("Which agents do you want to install to?", Object.entries(agents).map(([key, config]) => ({
2696
- value: key,
2697
- label: config.displayName
2698
- })));
2699
- if (pD(selected)) {
2700
- xe("Installation cancelled");
2701
- process.exit(0);
2702
- }
2703
- targetAgents = selected;
2704
- }
2705
- else if (installedAgents.length === 1 || options.yes) {
2706
- targetAgents = ensureUniversalAgents(installedAgents);
2707
- if (installedAgents.length === 1) {
2708
- const firstAgent = installedAgents[0];
2709
- M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
2710
- } else M.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
2711
- } else {
2712
- const selected = await selectAgentsInteractive({ global: options.global });
2713
- if (pD(selected)) {
2714
- xe("Installation cancelled");
2715
- process.exit(0);
2716
- }
2717
- targetAgents = selected;
2718
- }
2719
- }
2720
- let installGlobally = options.global ?? false;
2721
- const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2722
- if (options.global === void 0 && !options.yes && supportsGlobal) {
2723
- const scope = await ve({
2724
- message: "Installation scope",
2725
- options: [{
2726
- value: false,
2727
- label: "Project",
2728
- hint: "Install in current directory (committed with your project)"
2729
- }, {
2730
- value: true,
2731
- label: "Global",
2732
- hint: "Install in home directory (available across all projects)"
2733
- }]
2734
- });
2735
- if (pD(scope)) {
2736
- xe("Installation cancelled");
2737
- process.exit(0);
2738
- }
2739
- installGlobally = scope;
2740
- }
2741
- const installMode = "symlink";
2742
- const cwd = process.cwd();
2743
- const overwriteChecks = await Promise.all(targetAgents.map(async (agent) => ({
2744
- agent,
2745
- installed: await isSkillInstalled(remoteSkill.installName, agent, { global: installGlobally })
2746
- })));
2747
- const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
2748
- const summaryLines = [];
2749
- targetAgents.map((a) => agents[a].displayName);
2750
- const shortCanonical = shortenPath$2(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
2751
- summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2752
- summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2753
- const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
2754
- if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
2755
- console.log();
2756
- Me(summaryLines.join("\n"), "Installation Summary");
2757
- if (!options.yes) {
2758
- const confirmed = await ye({ message: "Proceed with installation?" });
2759
- if (pD(confirmed) || !confirmed) {
2760
- xe("Installation cancelled");
2761
- process.exit(0);
2762
- }
2763
- }
2764
- spinner.start("Installing skill...");
2765
- const results = [];
2766
- for (const agent of targetAgents) {
2767
- const result = await installRemoteSkillForAgent(remoteSkill, agent, {
2768
- global: installGlobally,
2769
- mode: installMode
2770
- });
2771
- results.push({
2772
- skill: remoteSkill.installName,
2773
- agent: agents[agent].displayName,
2774
- ...result
2775
- });
2776
- }
2777
- spinner.stop("Installation complete");
2778
- console.log();
2779
- const successful = results.filter((r) => r.success);
2780
- const failed = results.filter((r) => !r.success);
2781
- track({
2782
- event: "install",
2783
- source: "mintlify/com",
2784
- skills: remoteSkill.installName,
2785
- agents: targetAgents.join(","),
2786
- ...installGlobally && { global: "1" },
2787
- skillFiles: JSON.stringify({ [remoteSkill.installName]: url }),
2788
- sourceType: "mintlify"
2789
- });
2790
- if (successful.length > 0 && installGlobally) try {
2791
- await addSkillToLock(remoteSkill.installName, {
2792
- source: `mintlify/${remoteSkill.installName}`,
2793
- sourceType: "mintlify",
2794
- sourceUrl: url,
2795
- skillFolderHash: ""
2796
- });
2797
- } catch {}
2798
- if (successful.length > 0) {
2799
- const resultLines = [];
2800
- const firstResult = successful[0];
2801
- if (firstResult.canonicalPath) {
2802
- const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
2803
- resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
2804
- } else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
2805
- resultLines.push(...buildResultLines(successful, targetAgents));
2806
- const title = import_picocolors.default.green("Installed 1 skill");
2807
- Me(resultLines.join("\n"), title);
2808
- const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
2809
- if (symlinkFailures.length > 0) {
2810
- const copiedAgentNames = symlinkFailures.map((r) => r.agent);
2811
- M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList$1(copiedAgentNames)}`));
2812
- M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
2813
- }
2814
- }
2815
- if (failed.length > 0) {
2816
- console.log();
2817
- M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
2818
- for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
2819
- }
2820
- console.log();
2821
- Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
2822
- await promptForFindSkills(options, targetAgents);
2823
- }
2824
2222
  async function runAdd(args, options = {}) {
2825
2223
  const source = args[0];
2826
2224
  let installTipShown = false;
@@ -2855,10 +2253,6 @@ async function runAdd(args, options = {}) {
2855
2253
  spinner.start("Parsing source...");
2856
2254
  const parsed = parseSource(source);
2857
2255
  spinner.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
2858
- if (parsed.type === "direct-url") {
2859
- await handleRemoteSkill(source, parsed.url, options, spinner);
2860
- return;
2861
- }
2862
2256
  if (parsed.type === "well-known") {
2863
2257
  await handleWellKnownSkills(source, parsed.url, options, spinner);
2864
2258
  return;
@@ -3208,7 +2602,7 @@ async function runAdd(args, options = {}) {
3208
2602
  let skillFolderHash = "";
3209
2603
  const skillPathValue = skillFiles[skill.name];
3210
2604
  if (parsed.type === "github" && skillPathValue) {
3211
- const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue);
2605
+ const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue, getGitHubToken());
3212
2606
  if (hash) skillFolderHash = hash;
3213
2607
  }
3214
2608
  await addSkillToLock(skill.name, {
@@ -4473,6 +3867,22 @@ function readSkillLock() {
4473
3867
  };
4474
3868
  }
4475
3869
  }
3870
+ function getSkipReason(entry) {
3871
+ if (entry.sourceType === "local") return "Local path";
3872
+ if (entry.sourceType === "git") return "Git URL (hash tracking not supported)";
3873
+ if (!entry.skillFolderHash) return "No version hash available";
3874
+ if (!entry.skillPath) return "No skill path recorded";
3875
+ return "No version tracking";
3876
+ }
3877
+ function printSkippedSkills(skipped) {
3878
+ if (skipped.length === 0) return;
3879
+ console.log();
3880
+ console.log(`${DIM}${skipped.length} skill(s) cannot be checked automatically:${RESET}`);
3881
+ for (const skill of skipped) {
3882
+ console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
3883
+ console.log(` ${DIM}To update: ${TEXT}npx skills add ${skill.sourceUrl} -g -y${RESET}`);
3884
+ }
3885
+ }
4476
3886
  async function runCheck(args = []) {
4477
3887
  console.log(`${TEXT}Checking for skill updates...${RESET}`);
4478
3888
  console.log();
@@ -4485,12 +3895,16 @@ async function runCheck(args = []) {
4485
3895
  }
4486
3896
  const token = getGitHubToken();
4487
3897
  const skillsBySource = /* @__PURE__ */ new Map();
4488
- let skippedCount = 0;
3898
+ const skipped = [];
4489
3899
  for (const skillName of skillNames) {
4490
3900
  const entry = lock.skills[skillName];
4491
3901
  if (!entry) continue;
4492
- if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) {
4493
- skippedCount++;
3902
+ if (!entry.skillFolderHash || !entry.skillPath) {
3903
+ skipped.push({
3904
+ name: skillName,
3905
+ reason: getSkipReason(entry),
3906
+ sourceUrl: entry.sourceUrl
3907
+ });
4494
3908
  continue;
4495
3909
  }
4496
3910
  const existing = skillsBySource.get(entry.source) || [];
@@ -4500,9 +3914,10 @@ async function runCheck(args = []) {
4500
3914
  });
4501
3915
  skillsBySource.set(entry.source, existing);
4502
3916
  }
4503
- const totalSkills = skillNames.length - skippedCount;
3917
+ const totalSkills = skillNames.length - skipped.length;
4504
3918
  if (totalSkills === 0) {
4505
3919
  console.log(`${DIM}No GitHub skills to check.${RESET}`);
3920
+ printSkippedSkills(skipped);
4506
3921
  return;
4507
3922
  }
4508
3923
  console.log(`${DIM}Checking ${totalSkills} skill(s) for updates...${RESET}`);
@@ -4545,6 +3960,7 @@ async function runCheck(args = []) {
4545
3960
  console.log();
4546
3961
  console.log(`${DIM}Could not check ${errors.length} skill(s) (may need reinstall)${RESET}`);
4547
3962
  }
3963
+ printSkippedSkills(skipped);
4548
3964
  track({
4549
3965
  event: "check",
4550
3966
  skillCount: String(totalSkills),
@@ -4564,12 +3980,18 @@ async function runUpdate() {
4564
3980
  }
4565
3981
  const token = getGitHubToken();
4566
3982
  const updates = [];
4567
- let checkedCount = 0;
3983
+ const skipped = [];
4568
3984
  for (const skillName of skillNames) {
4569
3985
  const entry = lock.skills[skillName];
4570
3986
  if (!entry) continue;
4571
- if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) continue;
4572
- checkedCount++;
3987
+ if (!entry.skillFolderHash || !entry.skillPath) {
3988
+ skipped.push({
3989
+ name: skillName,
3990
+ reason: getSkipReason(entry),
3991
+ sourceUrl: entry.sourceUrl
3992
+ });
3993
+ continue;
3994
+ }
4573
3995
  try {
4574
3996
  const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
4575
3997
  if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
@@ -4579,8 +4001,9 @@ async function runUpdate() {
4579
4001
  });
4580
4002
  } catch {}
4581
4003
  }
4582
- if (checkedCount === 0) {
4004
+ if (skillNames.length - skipped.length === 0) {
4583
4005
  console.log(`${DIM}No skills to check.${RESET}`);
4006
+ printSkippedSkills(skipped);
4584
4007
  return;
4585
4008
  }
4586
4009
  if (updates.length === 0) {
@@ -4610,11 +4033,14 @@ async function runUpdate() {
4610
4033
  installUrl,
4611
4034
  "-g",
4612
4035
  "-y"
4613
- ], { stdio: [
4614
- "inherit",
4615
- "pipe",
4616
- "pipe"
4617
- ] }).status === 0) {
4036
+ ], {
4037
+ stdio: [
4038
+ "inherit",
4039
+ "pipe",
4040
+ "pipe"
4041
+ ],
4042
+ shell: process.platform === "win32"
4043
+ }).status === 0) {
4618
4044
  successCount++;
4619
4045
  console.log(` ${TEXT}✓${RESET} Updated ${update.name}`);
4620
4046
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {