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.
- 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 +371 -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.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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
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 (
|
|
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.
|
|
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
|
}
|