skills 1.4.7 → 1.4.8

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.
@@ -25,35 +25,6 @@ The above copyright notice and this permission notice shall be included in all c
25
25
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
26
 
27
27
 
28
- ================================================================================
29
- Package: gray-matter@4.0.3
30
- License: MIT
31
- Repository: https://github.com/jonschlinkert/gray-matter
32
- --------------------------------------------------------------------------------
33
-
34
- The MIT License (MIT)
35
-
36
- Copyright (c) 2014-2018, Jon Schlinkert.
37
-
38
- Permission is hereby granted, free of charge, to any person obtaining a copy
39
- of this software and associated documentation files (the "Software"), to deal
40
- in the Software without restriction, including without limitation the rights
41
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42
- copies of the Software, and to permit persons to whom the Software is
43
- furnished to do so, subject to the following conditions:
44
-
45
- The above copyright notice and this permission notice shall be included in
46
- all copies or substantial portions of the Software.
47
-
48
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
54
- THE SOFTWARE.
55
-
56
-
57
28
  ================================================================================
58
29
  Package: picocolors@1.1.1
59
30
  License: ISC
@@ -121,5 +92,26 @@ The above copyright notice and this permission notice shall be included in all c
121
92
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
122
93
 
123
94
 
95
+ ================================================================================
96
+ Package: yaml@2.8.3
97
+ License: ISC
98
+ Repository: https://github.com/eemeli/yaml
99
+ --------------------------------------------------------------------------------
100
+
101
+ Copyright Eemeli Aro <eemeli@gmail.com>
102
+
103
+ Permission to use, copy, modify, and/or distribute this software for any purpose
104
+ with or without fee is hereby granted, provided that the above copyright notice
105
+ and this permission notice appear in all copies.
106
+
107
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
108
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
109
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
110
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
111
+ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
112
+ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
113
+ THIS SOFTWARE.
114
+
115
+
124
116
  ================================================================================
125
117
  */
@@ -1,4 +1,4 @@
1
- import { r as __toESM, t as __commonJSMin } from "../../rolldown-runtime.mjs";
1
+ import { i as __toESM, t as __commonJSMin } from "../../rolldown-runtime.mjs";
2
2
  import { stdin, stdout } from "node:process";
3
3
  import * as g from "node:readline";
4
4
  import O from "node:readline";
@@ -1,4 +1,4 @@
1
- import { r as __toESM } from "../../rolldown-runtime.mjs";
1
+ import { i as __toESM } from "../../rolldown-runtime.mjs";
2
2
  import { a as SD, c as fD, d as require_src, i as RD, l as pD, n as LD, o as _D, r as MD, s as dD, t as ID, u as require_picocolors } from "./core.mjs";
3
3
  import { stripVTControlCharacters } from "node:util";
4
4
  import y from "node:process";
@@ -1,4 +1,4 @@
1
- import { n as __require, t as __commonJSMin } from "../../rolldown-runtime.mjs";
1
+ import { r as __require, t as __commonJSMin } from "../../rolldown-runtime.mjs";
2
2
  var require_ms = /* @__PURE__ */ __commonJSMin(((exports, module) => {
3
3
  var s = 1e3;
4
4
  var m = s * 60;
@@ -1,4 +1,4 @@
1
- import { r as __toESM } from "../rolldown-runtime.mjs";
1
+ import { i as __toESM } from "../rolldown-runtime.mjs";
2
2
  import { n as require_src, t as require_dist } from "./@kwsites/file-exists.mjs";
3
3
  import { t as require_dist$1 } from "./@kwsites/promise-deferred.mjs";
4
4
  import { spawn } from "child_process";
@@ -6,6 +6,15 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
9
+ var __exportAll = (all, symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
9
18
  var __copyProps = (to, from, except, desc) => {
10
19
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
20
  key = keys[i];
@@ -21,4 +30,4 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
30
  enumerable: true
22
31
  }) : target, mod));
23
32
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
24
- export { __require as n, __toESM as r, __commonJSMin as t };
33
+ export { __toESM as i, __exportAll as n, __require as r, __commonJSMin as t };
package/dist/cli.mjs CHANGED
@@ -1,13 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { r as __toESM } from "./_chunks/rolldown-runtime.mjs";
2
+ import { i as __toESM, n as __exportAll } from "./_chunks/rolldown-runtime.mjs";
3
3
  import { l as pD, u as require_picocolors } from "./_chunks/libs/@clack/core.mjs";
4
4
  import { a as Y, c as ve, i as Se, l as xe, n as M, o as be, r as Me, s as fe, t as Ie, u as ye } from "./_chunks/libs/@clack/prompts.mjs";
5
5
  import "./_chunks/libs/@kwsites/file-exists.mjs";
6
6
  import "./_chunks/libs/@kwsites/promise-deferred.mjs";
7
7
  import { t as esm_default } from "./_chunks/libs/simple-git.mjs";
8
- import { t as require_gray_matter } from "./_chunks/libs/gray-matter.mjs";
9
- import "./_chunks/libs/extend-shallow.mjs";
10
- import "./_chunks/libs/esprima.mjs";
11
8
  import { t as xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
12
9
  import { execSync, spawnSync } from "child_process";
13
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -17,6 +14,7 @@ import { fileURLToPath } from "url";
17
14
  import * as readline from "readline";
18
15
  import { Writable } from "stream";
19
16
  import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
17
+ import { parse } from "yaml";
20
18
  import { createHash } from "crypto";
21
19
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
22
20
  function getOwnerRepo(parsed) {
@@ -438,7 +436,17 @@ async function cleanupTempDir(dir) {
438
436
  force: true
439
437
  });
440
438
  }
441
- var import_gray_matter = /* @__PURE__ */ __toESM(require_gray_matter(), 1);
439
+ function parseFrontmatter(raw) {
440
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
441
+ if (!match) return {
442
+ data: {},
443
+ content: raw
444
+ };
445
+ return {
446
+ data: parse(match[1]) ?? {},
447
+ content: match[2] ?? ""
448
+ };
449
+ }
442
450
  function isContainedIn(targetPath, basePath) {
443
451
  const normalizedBase = normalize(resolve(basePath));
444
452
  const normalizedTarget = normalize(resolve(targetPath));
@@ -525,7 +533,7 @@ async function hasSkillMd(dir) {
525
533
  async function parseSkillMd(skillMdPath, options) {
526
534
  try {
527
535
  const content = await readFile(skillMdPath, "utf-8");
528
- const { data } = (0, import_gray_matter.default)(content);
536
+ const { data } = parseFrontmatter(content);
529
537
  if (!data.name || !data.description) return null;
530
538
  if (typeof data.name !== "string" || typeof data.description !== "string") return null;
531
539
  if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
@@ -1360,6 +1368,87 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
1360
1368
  };
1361
1369
  }
1362
1370
  }
1371
+ async function installBlobSkillForAgent(skill, agentType, options = {}) {
1372
+ const agent = agents[agentType];
1373
+ const isGlobal = options.global ?? false;
1374
+ const cwd = options.cwd || process.cwd();
1375
+ const installMode = options.mode ?? "symlink";
1376
+ if (isGlobal && agent.globalSkillsDir === void 0) return {
1377
+ success: false,
1378
+ path: "",
1379
+ mode: installMode,
1380
+ error: `${agent.displayName} does not support global skill installation`
1381
+ };
1382
+ const skillName = sanitizeName(skill.installName);
1383
+ const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
1384
+ const canonicalDir = join(canonicalBase, skillName);
1385
+ const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
1386
+ const agentDir = join(agentBase, skillName);
1387
+ if (!isPathSafe(canonicalBase, canonicalDir)) return {
1388
+ success: false,
1389
+ path: agentDir,
1390
+ mode: installMode,
1391
+ error: "Invalid skill name: potential path traversal detected"
1392
+ };
1393
+ if (!isPathSafe(agentBase, agentDir)) return {
1394
+ success: false,
1395
+ path: agentDir,
1396
+ mode: installMode,
1397
+ error: "Invalid skill name: potential path traversal detected"
1398
+ };
1399
+ async function writeSkillFiles(targetDir) {
1400
+ for (const file of skill.files) {
1401
+ const fullPath = join(targetDir, file.path);
1402
+ if (!isPathSafe(targetDir, fullPath)) continue;
1403
+ const parentDir = dirname(fullPath);
1404
+ if (parentDir !== targetDir) await mkdir(parentDir, { recursive: true });
1405
+ await writeFile(fullPath, file.contents, "utf-8");
1406
+ }
1407
+ }
1408
+ try {
1409
+ if (installMode === "copy") {
1410
+ await cleanAndCreateDirectory(agentDir);
1411
+ await writeSkillFiles(agentDir);
1412
+ return {
1413
+ success: true,
1414
+ path: agentDir,
1415
+ mode: "copy"
1416
+ };
1417
+ }
1418
+ await cleanAndCreateDirectory(canonicalDir);
1419
+ await writeSkillFiles(canonicalDir);
1420
+ if (isGlobal && isUniversalAgent(agentType)) return {
1421
+ success: true,
1422
+ path: canonicalDir,
1423
+ canonicalPath: canonicalDir,
1424
+ mode: "symlink"
1425
+ };
1426
+ if (!await createSymlink(canonicalDir, agentDir)) {
1427
+ await cleanAndCreateDirectory(agentDir);
1428
+ await writeSkillFiles(agentDir);
1429
+ return {
1430
+ success: true,
1431
+ path: agentDir,
1432
+ canonicalPath: canonicalDir,
1433
+ mode: "symlink",
1434
+ symlinkFailed: true
1435
+ };
1436
+ }
1437
+ return {
1438
+ success: true,
1439
+ path: agentDir,
1440
+ canonicalPath: canonicalDir,
1441
+ mode: "symlink"
1442
+ };
1443
+ } catch (error) {
1444
+ return {
1445
+ success: false,
1446
+ path: agentDir,
1447
+ mode: installMode,
1448
+ error: error instanceof Error ? error.message : "Unknown error"
1449
+ };
1450
+ }
1451
+ }
1363
1452
  async function listInstalledSkills(options = {}) {
1364
1453
  const cwd = options.cwd || process.cwd();
1365
1454
  const skillsMap = /* @__PURE__ */ new Map();
@@ -1641,7 +1730,7 @@ var WellKnownProvider = class {
1641
1730
  const response = await fetch(skillMdUrl);
1642
1731
  if (!response.ok) return null;
1643
1732
  const content = await response.text();
1644
- const { data } = (0, import_gray_matter.default)(content);
1733
+ const { data } = parseFrontmatter(content);
1645
1734
  if (!data.name || !data.description) return null;
1646
1735
  const files = /* @__PURE__ */ new Map();
1647
1736
  files.set("SKILL.md", content);
@@ -1753,28 +1842,10 @@ function getGitHubToken() {
1753
1842
  return null;
1754
1843
  }
1755
1844
  async function fetchSkillFolderHash(ownerRepo, skillPath, token, ref) {
1756
- let folderPath = skillPath.replace(/\\/g, "/");
1757
- if (folderPath.endsWith("/SKILL.md")) folderPath = folderPath.slice(0, -9);
1758
- else if (folderPath.endsWith("SKILL.md")) folderPath = folderPath.slice(0, -8);
1759
- if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
1760
- const branches = ref ? [ref] : ["main", "master"];
1761
- for (const branch of branches) try {
1762
- const url = `https://api.github.com/repos/${ownerRepo}/git/trees/${encodeURIComponent(branch)}?recursive=1`;
1763
- const headers = {
1764
- Accept: "application/vnd.github.v3+json",
1765
- "User-Agent": "skills-cli"
1766
- };
1767
- if (token) headers["Authorization"] = `Bearer ${token}`;
1768
- const response = await fetch(url, { headers });
1769
- if (!response.ok) continue;
1770
- const data = await response.json();
1771
- if (!folderPath) return data.sha;
1772
- const folderEntry = data.tree.find((entry) => entry.type === "tree" && entry.path === folderPath);
1773
- if (folderEntry) return folderEntry.sha;
1774
- } catch {
1775
- continue;
1776
- }
1777
- return null;
1845
+ const { fetchRepoTree, getSkillFolderHashFromTree } = await Promise.resolve().then(() => blob_exports);
1846
+ const tree = await fetchRepoTree(ownerRepo, ref, token);
1847
+ if (!tree) return null;
1848
+ return getSkillFolderHashFromTree(tree, skillPath);
1778
1849
  }
1779
1850
  async function addSkillToLock(skillName, entry) {
1780
1851
  const lock = await readSkillLock$1();
@@ -1890,7 +1961,210 @@ function createEmptyLocalLock() {
1890
1961
  skills: {}
1891
1962
  };
1892
1963
  }
1893
- var version$1 = "1.4.7";
1964
+ var blob_exports = /* @__PURE__ */ __exportAll({
1965
+ fetchRepoTree: () => fetchRepoTree,
1966
+ findSkillMdPaths: () => findSkillMdPaths,
1967
+ getSkillFolderHashFromTree: () => getSkillFolderHashFromTree,
1968
+ toSkillSlug: () => toSkillSlug,
1969
+ tryBlobInstall: () => tryBlobInstall
1970
+ });
1971
+ const DOWNLOAD_BASE_URL = process.env.SKILLS_DOWNLOAD_URL || "https://skills.sh";
1972
+ const FETCH_TIMEOUT = 1e4;
1973
+ function toSkillSlug(name) {
1974
+ return name.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
1975
+ }
1976
+ async function fetchRepoTree(ownerRepo, ref, token) {
1977
+ const branches = ref ? [ref] : [
1978
+ "HEAD",
1979
+ "main",
1980
+ "master"
1981
+ ];
1982
+ for (const branch of branches) try {
1983
+ const url = `https://api.github.com/repos/${ownerRepo}/git/trees/${encodeURIComponent(branch)}?recursive=1`;
1984
+ const headers = {
1985
+ Accept: "application/vnd.github.v3+json",
1986
+ "User-Agent": "skills-cli"
1987
+ };
1988
+ if (token) headers["Authorization"] = `Bearer ${token}`;
1989
+ const response = await fetch(url, {
1990
+ headers,
1991
+ signal: AbortSignal.timeout(FETCH_TIMEOUT)
1992
+ });
1993
+ if (!response.ok) continue;
1994
+ const data = await response.json();
1995
+ return {
1996
+ sha: data.sha,
1997
+ branch,
1998
+ tree: data.tree
1999
+ };
2000
+ } catch {
2001
+ continue;
2002
+ }
2003
+ return null;
2004
+ }
2005
+ function getSkillFolderHashFromTree(tree, skillPath) {
2006
+ let folderPath = skillPath.replace(/\\/g, "/");
2007
+ if (folderPath.endsWith("/SKILL.md")) folderPath = folderPath.slice(0, -9);
2008
+ else if (folderPath.endsWith("SKILL.md")) folderPath = folderPath.slice(0, -8);
2009
+ if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
2010
+ if (!folderPath) return tree.sha;
2011
+ return tree.tree.find((e) => e.type === "tree" && e.path === folderPath)?.sha ?? null;
2012
+ }
2013
+ const PRIORITY_PREFIXES = [
2014
+ "",
2015
+ "skills/",
2016
+ "skills/.curated/",
2017
+ "skills/.experimental/",
2018
+ "skills/.system/",
2019
+ ".agents/skills/",
2020
+ ".claude/skills/",
2021
+ ".cline/skills/",
2022
+ ".codebuddy/skills/",
2023
+ ".codex/skills/",
2024
+ ".commandcode/skills/",
2025
+ ".continue/skills/",
2026
+ ".github/skills/",
2027
+ ".goose/skills/",
2028
+ ".iflow/skills/",
2029
+ ".junie/skills/",
2030
+ ".kilocode/skills/",
2031
+ ".kiro/skills/",
2032
+ ".mux/skills/",
2033
+ ".neovate/skills/",
2034
+ ".opencode/skills/",
2035
+ ".openhands/skills/",
2036
+ ".pi/skills/",
2037
+ ".qoder/skills/",
2038
+ ".roo/skills/",
2039
+ ".trae/skills/",
2040
+ ".windsurf/skills/",
2041
+ ".zencoder/skills/"
2042
+ ];
2043
+ function findSkillMdPaths(tree, subpath) {
2044
+ const allSkillMds = tree.tree.filter((e) => e.type === "blob" && e.path.endsWith("SKILL.md")).map((e) => e.path);
2045
+ const prefix = subpath ? subpath.endsWith("/") ? subpath : subpath + "/" : "";
2046
+ const filtered = prefix ? allSkillMds.filter((p) => p.startsWith(prefix) || p === prefix + "SKILL.md") : allSkillMds;
2047
+ if (filtered.length === 0) return [];
2048
+ const priorityResults = [];
2049
+ const seen = /* @__PURE__ */ new Set();
2050
+ for (const priorityPrefix of PRIORITY_PREFIXES) {
2051
+ const fullPrefix = prefix + priorityPrefix;
2052
+ for (const skillMd of filtered) {
2053
+ if (!skillMd.startsWith(fullPrefix)) continue;
2054
+ const rest = skillMd.slice(fullPrefix.length);
2055
+ if (rest === "SKILL.md") {
2056
+ if (!seen.has(skillMd)) {
2057
+ priorityResults.push(skillMd);
2058
+ seen.add(skillMd);
2059
+ }
2060
+ continue;
2061
+ }
2062
+ const parts = rest.split("/");
2063
+ if (parts.length === 2 && parts[1] === "SKILL.md") {
2064
+ if (!seen.has(skillMd)) {
2065
+ priorityResults.push(skillMd);
2066
+ seen.add(skillMd);
2067
+ }
2068
+ }
2069
+ }
2070
+ }
2071
+ if (priorityResults.length > 0) return priorityResults;
2072
+ return filtered.filter((p) => {
2073
+ return p.split("/").length <= 6;
2074
+ });
2075
+ }
2076
+ async function fetchSkillMdContent(ownerRepo, branch, skillMdPath) {
2077
+ try {
2078
+ const url = `https://raw.githubusercontent.com/${ownerRepo}/${branch}/${skillMdPath}`;
2079
+ const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT) });
2080
+ if (!response.ok) return null;
2081
+ return await response.text();
2082
+ } catch {
2083
+ return null;
2084
+ }
2085
+ }
2086
+ async function fetchSkillDownload(source, slug) {
2087
+ try {
2088
+ const [owner, repo] = source.split("/");
2089
+ const url = `${DOWNLOAD_BASE_URL}/api/download/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${encodeURIComponent(slug)}`;
2090
+ const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT) });
2091
+ if (!response.ok) return null;
2092
+ return await response.json();
2093
+ } catch {
2094
+ return null;
2095
+ }
2096
+ }
2097
+ async function tryBlobInstall(ownerRepo, options = {}) {
2098
+ const tree = await fetchRepoTree(ownerRepo, options.ref, options.token);
2099
+ if (!tree) return null;
2100
+ let skillMdPaths = findSkillMdPaths(tree, options.subpath);
2101
+ if (skillMdPaths.length === 0) return null;
2102
+ if (options.skillFilter) {
2103
+ const filterSlug = toSkillSlug(options.skillFilter);
2104
+ const filtered = skillMdPaths.filter((p) => {
2105
+ const parts = p.split("/");
2106
+ if (parts.length < 2) return false;
2107
+ const folderName = parts[parts.length - 2];
2108
+ return toSkillSlug(folderName) === filterSlug;
2109
+ });
2110
+ if (filtered.length > 0) skillMdPaths = filtered;
2111
+ }
2112
+ const mdFetches = await Promise.all(skillMdPaths.map(async (mdPath) => {
2113
+ return {
2114
+ mdPath,
2115
+ content: await fetchSkillMdContent(ownerRepo, tree.branch, mdPath)
2116
+ };
2117
+ }));
2118
+ const parsedSkills = [];
2119
+ for (const { mdPath, content } of mdFetches) {
2120
+ if (!content) continue;
2121
+ const { data } = parseFrontmatter(content);
2122
+ if (!data.name || !data.description) continue;
2123
+ if (typeof data.name !== "string" || typeof data.description !== "string") continue;
2124
+ if (data.metadata?.internal === true && !options.includeInternal) continue;
2125
+ parsedSkills.push({
2126
+ mdPath,
2127
+ name: data.name,
2128
+ description: data.description,
2129
+ content,
2130
+ slug: toSkillSlug(data.name),
2131
+ metadata: data.metadata
2132
+ });
2133
+ }
2134
+ if (parsedSkills.length === 0) return null;
2135
+ let filteredSkills = parsedSkills;
2136
+ if (options.skillFilter) {
2137
+ const filterSlug = toSkillSlug(options.skillFilter);
2138
+ const nameFiltered = parsedSkills.filter((s) => s.slug === filterSlug);
2139
+ if (nameFiltered.length > 0) filteredSkills = nameFiltered;
2140
+ if (filteredSkills.length === 0) return null;
2141
+ }
2142
+ const source = ownerRepo.toLowerCase();
2143
+ const downloads = await Promise.all(filteredSkills.map(async (skill) => {
2144
+ return {
2145
+ skill,
2146
+ download: await fetchSkillDownload(source, skill.slug)
2147
+ };
2148
+ }));
2149
+ if (!downloads.every((d) => d.download !== null)) return null;
2150
+ return {
2151
+ skills: downloads.map(({ skill, download }) => {
2152
+ skill.mdPath.endsWith("/SKILL.md") ? skill.mdPath.slice(0, -9) : skill.mdPath === "SKILL.md" || skill.mdPath.slice(0, -9);
2153
+ return {
2154
+ name: skill.name,
2155
+ description: skill.description,
2156
+ path: "",
2157
+ rawContent: skill.content,
2158
+ metadata: skill.metadata,
2159
+ files: download.files,
2160
+ snapshotHash: download.hash,
2161
+ repoPath: skill.mdPath
2162
+ };
2163
+ }),
2164
+ tree
2165
+ };
2166
+ }
2167
+ var version$1 = "1.4.8";
1894
2168
  const isCancelled$1 = (value) => typeof value === "symbol";
1895
2169
  async function isSourcePrivate(source) {
1896
2170
  const ownerRepo = parseOwnerRepo(source);
@@ -2375,7 +2649,13 @@ async function runAdd(args, options = {}) {
2375
2649
  await handleWellKnownSkills(source, parsed.url, options, spinner);
2376
2650
  return;
2377
2651
  }
2378
- let skillsDir;
2652
+ if (parsed.skillFilter) {
2653
+ options.skill = options.skill || [];
2654
+ if (!options.skill.includes(parsed.skillFilter)) options.skill.push(parsed.skillFilter);
2655
+ }
2656
+ const includeInternal = !!(options.skill && options.skill.length > 0);
2657
+ let skills;
2658
+ let blobResult = null;
2379
2659
  if (parsed.type === "local") {
2380
2660
  spinner.start("Validating local path...");
2381
2661
  if (!existsSync(parsed.localPath)) {
@@ -2383,31 +2663,58 @@ async function runAdd(args, options = {}) {
2383
2663
  Se(import_picocolors.default.red(`Local path does not exist: ${parsed.localPath}`));
2384
2664
  process.exit(1);
2385
2665
  }
2386
- skillsDir = parsed.localPath;
2387
2666
  spinner.stop("Local path validated");
2667
+ spinner.start("Discovering skills...");
2668
+ skills = await discoverSkills(parsed.localPath, parsed.subpath, {
2669
+ includeInternal,
2670
+ fullDepth: options.fullDepth
2671
+ });
2672
+ } else if (parsed.type === "github" && !options.fullDepth) {
2673
+ const BLOB_ALLOWED_OWNERS = ["vercel", "vercel-labs"];
2674
+ const ownerRepo = getOwnerRepo(parsed);
2675
+ const owner = ownerRepo?.split("/")[0]?.toLowerCase();
2676
+ if (ownerRepo && owner && BLOB_ALLOWED_OWNERS.includes(owner)) {
2677
+ spinner.start("Fetching skills...");
2678
+ const token = getGitHubToken();
2679
+ blobResult = await tryBlobInstall(ownerRepo, {
2680
+ subpath: parsed.subpath,
2681
+ skillFilter: parsed.skillFilter,
2682
+ ref: parsed.ref,
2683
+ token,
2684
+ includeInternal
2685
+ });
2686
+ if (!blobResult) spinner.stop(import_picocolors.default.dim("Falling back to clone..."));
2687
+ }
2688
+ if (blobResult) {
2689
+ skills = blobResult.skills;
2690
+ spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2691
+ } else {
2692
+ spinner.start("Cloning repository...");
2693
+ tempDir = await cloneRepo(parsed.url, parsed.ref);
2694
+ spinner.stop("Repository cloned");
2695
+ spinner.start("Discovering skills...");
2696
+ skills = await discoverSkills(tempDir, parsed.subpath, {
2697
+ includeInternal,
2698
+ fullDepth: options.fullDepth
2699
+ });
2700
+ }
2388
2701
  } else {
2389
2702
  spinner.start("Cloning repository...");
2390
2703
  tempDir = await cloneRepo(parsed.url, parsed.ref);
2391
- skillsDir = tempDir;
2392
2704
  spinner.stop("Repository cloned");
2705
+ spinner.start("Discovering skills...");
2706
+ skills = await discoverSkills(tempDir, parsed.subpath, {
2707
+ includeInternal,
2708
+ fullDepth: options.fullDepth
2709
+ });
2393
2710
  }
2394
- if (parsed.skillFilter) {
2395
- options.skill = options.skill || [];
2396
- if (!options.skill.includes(parsed.skillFilter)) options.skill.push(parsed.skillFilter);
2397
- }
2398
- const includeInternal = !!(options.skill && options.skill.length > 0);
2399
- spinner.start("Discovering skills...");
2400
- const skills = await discoverSkills(skillsDir, parsed.subpath, {
2401
- includeInternal,
2402
- fullDepth: options.fullDepth
2403
- });
2404
2711
  if (skills.length === 0) {
2405
2712
  spinner.stop(import_picocolors.default.red("No skills found"));
2406
2713
  Se(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
2407
2714
  await cleanup(tempDir);
2408
2715
  process.exit(1);
2409
2716
  }
2410
- spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2717
+ if (!blobResult) spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2411
2718
  if (options.list) {
2412
2719
  console.log();
2413
2720
  M.step(import_picocolors.default.bold("Available Skills"));
@@ -2669,7 +2976,17 @@ async function runAdd(args, options = {}) {
2669
2976
  spinner.start("Installing skills...");
2670
2977
  const results = [];
2671
2978
  for (const skill of selectedSkills) for (const agent of targetAgents) {
2672
- const result = await installSkillForAgent(skill, agent, {
2979
+ let result;
2980
+ if (blobResult && "files" in skill) {
2981
+ const blobSkill = skill;
2982
+ result = await installBlobSkillForAgent({
2983
+ installName: blobSkill.name,
2984
+ files: blobSkill.files
2985
+ }, agent, {
2986
+ global: installGlobally,
2987
+ mode: installMode
2988
+ });
2989
+ } else result = await installSkillForAgent(skill, agent, {
2673
2990
  global: installGlobally,
2674
2991
  mode: installMode
2675
2992
  });
@@ -2685,13 +3002,10 @@ async function runAdd(args, options = {}) {
2685
3002
  const successful = results.filter((r) => r.success);
2686
3003
  const failed = results.filter((r) => !r.success);
2687
3004
  const skillFiles = {};
2688
- for (const skill of selectedSkills) {
2689
- let relativePath;
2690
- if (tempDir && skill.path === tempDir) relativePath = "SKILL.md";
2691
- else if (tempDir && skill.path.startsWith(tempDir + sep)) relativePath = skill.path.slice(tempDir.length + 1).split(sep).join("/") + "/SKILL.md";
2692
- else continue;
2693
- skillFiles[skill.name] = relativePath;
2694
- }
3005
+ for (const skill of selectedSkills) if (blobResult && "repoPath" in skill) skillFiles[skill.name] = skill.repoPath;
3006
+ else if (tempDir && skill.path === tempDir) skillFiles[skill.name] = "SKILL.md";
3007
+ else if (tempDir && skill.path.startsWith(tempDir + sep)) skillFiles[skill.name] = skill.path.slice(tempDir.length + 1).split(sep).join("/") + "/SKILL.md";
3008
+ else continue;
2695
3009
  const normalizedSource = getOwnerRepo(parsed);
2696
3010
  const lockSource = parsed.url.startsWith("git@") ? parsed.url : normalizedSource;
2697
3011
  if (normalizedSource) {
@@ -2721,7 +3035,10 @@ async function runAdd(args, options = {}) {
2721
3035
  if (successfulSkillNames.has(skillDisplayName)) try {
2722
3036
  let skillFolderHash = "";
2723
3037
  const skillPathValue = skillFiles[skill.name];
2724
- if (parsed.type === "github" && skillPathValue) {
3038
+ if (blobResult && skillPathValue) {
3039
+ const hash = getSkillFolderHashFromTree(blobResult.tree, skillPathValue);
3040
+ if (hash) skillFolderHash = hash;
3041
+ } else if (parsed.type === "github" && skillPathValue) {
2725
3042
  const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue, getGitHubToken(), parsed.ref);
2726
3043
  if (hash) skillFolderHash = hash;
2727
3044
  }
@@ -2742,7 +3059,7 @@ async function runAdd(args, options = {}) {
2742
3059
  for (const skill of selectedSkills) {
2743
3060
  const skillDisplayName = getSkillDisplayName(skill);
2744
3061
  if (successfulSkillNames.has(skillDisplayName)) try {
2745
- const computedHash = await computeSkillFolderHash(skill.path);
3062
+ const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
2746
3063
  await addSkillToLocalLock(skill.name, {
2747
3064
  source: lockSource || parsed.url,
2748
3065
  ref: parsed.ref,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.4.7",
3
+ "version": "1.4.8",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -96,7 +96,6 @@
96
96
  "@clack/prompts": "^0.11.0",
97
97
  "@types/bun": "latest",
98
98
  "@types/node": "^22.10.0",
99
- "gray-matter": "^4.0.3",
100
99
  "husky": "^9.1.7",
101
100
  "lint-staged": "^16.2.7",
102
101
  "obuild": "^0.4.22",
@@ -110,5 +109,8 @@
110
109
  "engines": {
111
110
  "node": ">=18"
112
111
  },
113
- "packageManager": "pnpm@10.17.1"
112
+ "packageManager": "pnpm@10.17.1",
113
+ "dependencies": {
114
+ "yaml": "^2.8.3"
115
+ }
114
116
  }