skills 1.4.7 → 1.4.9

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.9";
1894
2168
  const isCancelled$1 = (value) => typeof value === "symbol";
1895
2169
  async function isSourcePrivate(source) {
1896
2170
  const ownerRepo = parseOwnerRepo(source);
@@ -2371,11 +2645,27 @@ async function runAdd(args, options = {}) {
2371
2645
  spinner.start("Parsing source...");
2372
2646
  const parsed = parseSource(source);
2373
2647
  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)}` : ""}`);
2648
+ if (getOwnerRepo(parsed)?.split("/")[0]?.toLowerCase() === "openclaw" && !options.dangerouslyAcceptOpenclawRisks) {
2649
+ console.log();
2650
+ M.warn(import_picocolors.default.yellow(import_picocolors.default.bold("⚠ OpenClaw skills are unverified community submissions.")));
2651
+ M.message(import_picocolors.default.yellow("This source contains user-submitted skills that have not been reviewed for safety or quality."));
2652
+ M.message(import_picocolors.default.yellow("Skills run with full agent permissions and could be malicious."));
2653
+ console.log();
2654
+ M.message(`If you understand the risks, re-run with:\n\n ${import_picocolors.default.cyan(`npx skills add ${source} --dangerously-accept-openclaw-risks`)}\n`);
2655
+ Se(import_picocolors.default.red("Installation blocked"));
2656
+ process.exit(1);
2657
+ }
2374
2658
  if (parsed.type === "well-known") {
2375
2659
  await handleWellKnownSkills(source, parsed.url, options, spinner);
2376
2660
  return;
2377
2661
  }
2378
- let skillsDir;
2662
+ if (parsed.skillFilter) {
2663
+ options.skill = options.skill || [];
2664
+ if (!options.skill.includes(parsed.skillFilter)) options.skill.push(parsed.skillFilter);
2665
+ }
2666
+ const includeInternal = !!(options.skill && options.skill.length > 0);
2667
+ let skills;
2668
+ let blobResult = null;
2379
2669
  if (parsed.type === "local") {
2380
2670
  spinner.start("Validating local path...");
2381
2671
  if (!existsSync(parsed.localPath)) {
@@ -2383,31 +2673,58 @@ async function runAdd(args, options = {}) {
2383
2673
  Se(import_picocolors.default.red(`Local path does not exist: ${parsed.localPath}`));
2384
2674
  process.exit(1);
2385
2675
  }
2386
- skillsDir = parsed.localPath;
2387
2676
  spinner.stop("Local path validated");
2677
+ spinner.start("Discovering skills...");
2678
+ skills = await discoverSkills(parsed.localPath, parsed.subpath, {
2679
+ includeInternal,
2680
+ fullDepth: options.fullDepth
2681
+ });
2682
+ } else if (parsed.type === "github" && !options.fullDepth) {
2683
+ const BLOB_ALLOWED_OWNERS = ["vercel", "vercel-labs"];
2684
+ const ownerRepo = getOwnerRepo(parsed);
2685
+ const owner = ownerRepo?.split("/")[0]?.toLowerCase();
2686
+ if (ownerRepo && owner && BLOB_ALLOWED_OWNERS.includes(owner)) {
2687
+ spinner.start("Fetching skills...");
2688
+ const token = getGitHubToken();
2689
+ blobResult = await tryBlobInstall(ownerRepo, {
2690
+ subpath: parsed.subpath,
2691
+ skillFilter: parsed.skillFilter,
2692
+ ref: parsed.ref,
2693
+ token,
2694
+ includeInternal
2695
+ });
2696
+ if (!blobResult) spinner.stop(import_picocolors.default.dim("Falling back to clone..."));
2697
+ }
2698
+ if (blobResult) {
2699
+ skills = blobResult.skills;
2700
+ spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2701
+ } else {
2702
+ spinner.start("Cloning repository...");
2703
+ tempDir = await cloneRepo(parsed.url, parsed.ref);
2704
+ spinner.stop("Repository cloned");
2705
+ spinner.start("Discovering skills...");
2706
+ skills = await discoverSkills(tempDir, parsed.subpath, {
2707
+ includeInternal,
2708
+ fullDepth: options.fullDepth
2709
+ });
2710
+ }
2388
2711
  } else {
2389
2712
  spinner.start("Cloning repository...");
2390
2713
  tempDir = await cloneRepo(parsed.url, parsed.ref);
2391
- skillsDir = tempDir;
2392
2714
  spinner.stop("Repository cloned");
2715
+ spinner.start("Discovering skills...");
2716
+ skills = await discoverSkills(tempDir, parsed.subpath, {
2717
+ includeInternal,
2718
+ fullDepth: options.fullDepth
2719
+ });
2393
2720
  }
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
2721
  if (skills.length === 0) {
2405
2722
  spinner.stop(import_picocolors.default.red("No skills found"));
2406
2723
  Se(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
2407
2724
  await cleanup(tempDir);
2408
2725
  process.exit(1);
2409
2726
  }
2410
- spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2727
+ if (!blobResult) spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2411
2728
  if (options.list) {
2412
2729
  console.log();
2413
2730
  M.step(import_picocolors.default.bold("Available Skills"));
@@ -2669,7 +2986,17 @@ async function runAdd(args, options = {}) {
2669
2986
  spinner.start("Installing skills...");
2670
2987
  const results = [];
2671
2988
  for (const skill of selectedSkills) for (const agent of targetAgents) {
2672
- const result = await installSkillForAgent(skill, agent, {
2989
+ let result;
2990
+ if (blobResult && "files" in skill) {
2991
+ const blobSkill = skill;
2992
+ result = await installBlobSkillForAgent({
2993
+ installName: blobSkill.name,
2994
+ files: blobSkill.files
2995
+ }, agent, {
2996
+ global: installGlobally,
2997
+ mode: installMode
2998
+ });
2999
+ } else result = await installSkillForAgent(skill, agent, {
2673
3000
  global: installGlobally,
2674
3001
  mode: installMode
2675
3002
  });
@@ -2685,13 +3012,10 @@ async function runAdd(args, options = {}) {
2685
3012
  const successful = results.filter((r) => r.success);
2686
3013
  const failed = results.filter((r) => !r.success);
2687
3014
  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
- }
3015
+ for (const skill of selectedSkills) if (blobResult && "repoPath" in skill) skillFiles[skill.name] = skill.repoPath;
3016
+ else if (tempDir && skill.path === tempDir) skillFiles[skill.name] = "SKILL.md";
3017
+ else if (tempDir && skill.path.startsWith(tempDir + sep)) skillFiles[skill.name] = skill.path.slice(tempDir.length + 1).split(sep).join("/") + "/SKILL.md";
3018
+ else continue;
2695
3019
  const normalizedSource = getOwnerRepo(parsed);
2696
3020
  const lockSource = parsed.url.startsWith("git@") ? parsed.url : normalizedSource;
2697
3021
  if (normalizedSource) {
@@ -2721,7 +3045,10 @@ async function runAdd(args, options = {}) {
2721
3045
  if (successfulSkillNames.has(skillDisplayName)) try {
2722
3046
  let skillFolderHash = "";
2723
3047
  const skillPathValue = skillFiles[skill.name];
2724
- if (parsed.type === "github" && skillPathValue) {
3048
+ if (blobResult && skillPathValue) {
3049
+ const hash = getSkillFolderHashFromTree(blobResult.tree, skillPathValue);
3050
+ if (hash) skillFolderHash = hash;
3051
+ } else if (parsed.type === "github" && skillPathValue) {
2725
3052
  const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue, getGitHubToken(), parsed.ref);
2726
3053
  if (hash) skillFolderHash = hash;
2727
3054
  }
@@ -2742,7 +3069,7 @@ async function runAdd(args, options = {}) {
2742
3069
  for (const skill of selectedSkills) {
2743
3070
  const skillDisplayName = getSkillDisplayName(skill);
2744
3071
  if (successfulSkillNames.has(skillDisplayName)) try {
2745
- const computedHash = await computeSkillFolderHash(skill.path);
3072
+ const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
2746
3073
  await addSkillToLocalLock(skill.name, {
2747
3074
  source: lockSource || parsed.url,
2748
3075
  ref: parsed.ref,
@@ -2905,6 +3232,7 @@ function parseAddOptions(args) {
2905
3232
  i--;
2906
3233
  } else if (arg === "--full-depth") options.fullDepth = true;
2907
3234
  else if (arg === "--copy") options.copy = true;
3235
+ else if (arg === "--dangerously-accept-openclaw-risks") options.dangerouslyAcceptOpenclawRisks = true;
2908
3236
  else if (arg && !arg.startsWith("-")) source.push(arg);
2909
3237
  }
2910
3238
  return {