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.
- package/ThirdPartyNoticeText.txt +21 -29
- package/dist/_chunks/libs/@clack/core.mjs +1 -1
- package/dist/_chunks/libs/@clack/prompts.mjs +1 -1
- package/dist/_chunks/libs/@kwsites/file-exists.mjs +1 -1
- package/dist/_chunks/libs/simple-git.mjs +1 -1
- package/dist/_chunks/rolldown-runtime.mjs +10 -1
- package/dist/cli.mjs +382 -54
- package/package.json +5 -3
- package/dist/_chunks/libs/esprima.mjs +0 -5338
- package/dist/_chunks/libs/extend-shallow.mjs +0 -31
- package/dist/_chunks/libs/gray-matter.mjs +0 -2596
package/ThirdPartyNoticeText.txt
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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 } = (
|
|
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 } = (
|
|
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
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
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 (
|
|
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 {
|