@bud-fe/skills 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -9
- package/dist/_chunks/libs/xdg-basedir.mjs +1 -1
- package/dist/cli.mjs +312 -189
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
# skills-x
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Forked from [vercel-labs/skills](https://github.com/vercel-labs/skills).
|
|
4
|
+
|
|
5
|
+
## Changes based on upstream
|
|
6
|
+
|
|
7
|
+
### ADDED
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
- `add-c` 命令:支持按名称从默认源的 `commands/` 目录安装 slash command 文件
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- `
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
-
|
|
11
|
+
### MODIFIED
|
|
12
|
+
|
|
13
|
+
- `add` 命令
|
|
14
|
+
- 支持在未提供源但携带标志(如 `--list` 和 `--all`)时,自动解析至默认源
|
|
15
|
+
- 将简单标识符(如 `code-review`)映射到默认源,而显式源(`owner/repo`、URL、本地路径)保持原有行为
|
|
16
|
+
- `find` 命令
|
|
17
|
+
- 无参数时默认从默认源搜索
|
|
18
|
+
- `-g` / `--global` 搜索 [skills.sh](https://skills.sh)(即原逻辑)
|
|
19
|
+
- `check` 和 `update`
|
|
20
|
+
- 支持非 GitHub 源(如 GitLab、通用 Git),采用基于内容的内容哈希(SHA-256)进行更新检测
|
|
14
21
|
|
|
15
22
|
---
|
|
16
23
|
|
|
24
|
+
The CLI for the open agent skills, commands ecosystem.
|
|
25
|
+
|
|
17
26
|
<!-- agent-list:start -->
|
|
18
27
|
|
|
19
28
|
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [37 more](#available-agents).
|
package/dist/cli.mjs
CHANGED
|
@@ -9,17 +9,17 @@ import "./_chunks/libs/esprima.mjs";
|
|
|
9
9
|
import "./_chunks/libs/@kwsites/file-exists.mjs";
|
|
10
10
|
import "./_chunks/libs/@kwsites/promise-deferred.mjs";
|
|
11
11
|
import { t as esm_default } from "./_chunks/libs/simple-git.mjs";
|
|
12
|
-
import { execSync,
|
|
12
|
+
import { execSync, spawnSync } from "child_process";
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
|
-
import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
15
14
|
import { homedir, platform, tmpdir } from "os";
|
|
16
|
-
import {
|
|
15
|
+
import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
17
16
|
import { fileURLToPath } from "url";
|
|
18
17
|
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
18
|
+
import { createHash } from "crypto";
|
|
19
19
|
import * as readline from "readline";
|
|
20
20
|
import { Writable } from "stream";
|
|
21
21
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
22
|
-
var version$1 = "0.0.
|
|
22
|
+
var version$1 = "0.0.4";
|
|
23
23
|
const home = homedir();
|
|
24
24
|
const configHome = xdgConfig ?? join(home, ".config");
|
|
25
25
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
@@ -104,6 +104,8 @@ const agents = {
|
|
|
104
104
|
displayName: "Codex",
|
|
105
105
|
skillsDir: ".agents/skills",
|
|
106
106
|
globalSkillsDir: join(codexHome, "skills"),
|
|
107
|
+
commandsDir: ".codex/prompts",
|
|
108
|
+
globalCommandsDir: join(codexHome, "prompts"),
|
|
107
109
|
detectInstalled: async () => {
|
|
108
110
|
return existsSync(codexHome) || existsSync("/etc/codex");
|
|
109
111
|
}
|
|
@@ -1295,6 +1297,72 @@ async function listInstalledSkills(options = {}) {
|
|
|
1295
1297
|
} catch {}
|
|
1296
1298
|
return Array.from(skillsMap.values());
|
|
1297
1299
|
}
|
|
1300
|
+
const LOCAL_LOCK_FILE = "skills-lock.json";
|
|
1301
|
+
const CURRENT_VERSION$1 = 1;
|
|
1302
|
+
function getLocalLockPath(cwd) {
|
|
1303
|
+
return join(cwd || process.cwd(), LOCAL_LOCK_FILE);
|
|
1304
|
+
}
|
|
1305
|
+
async function readLocalLock(cwd) {
|
|
1306
|
+
const lockPath = getLocalLockPath(cwd);
|
|
1307
|
+
try {
|
|
1308
|
+
const content = await readFile(lockPath, "utf-8");
|
|
1309
|
+
const parsed = JSON.parse(content);
|
|
1310
|
+
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
|
|
1311
|
+
if (parsed.version < CURRENT_VERSION$1) return createEmptyLocalLock();
|
|
1312
|
+
return parsed;
|
|
1313
|
+
} catch {
|
|
1314
|
+
return createEmptyLocalLock();
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function writeLocalLock(lock, cwd) {
|
|
1318
|
+
const lockPath = getLocalLockPath(cwd);
|
|
1319
|
+
const sortedSkills = {};
|
|
1320
|
+
for (const key of Object.keys(lock.skills).sort()) sortedSkills[key] = lock.skills[key];
|
|
1321
|
+
const sorted = {
|
|
1322
|
+
version: lock.version,
|
|
1323
|
+
skills: sortedSkills
|
|
1324
|
+
};
|
|
1325
|
+
await writeFile(lockPath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
|
|
1326
|
+
}
|
|
1327
|
+
async function computeSkillFolderHash(skillDir) {
|
|
1328
|
+
const files = [];
|
|
1329
|
+
await collectFiles(skillDir, skillDir, files);
|
|
1330
|
+
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
1331
|
+
const hash = createHash("sha256");
|
|
1332
|
+
for (const file of files) {
|
|
1333
|
+
hash.update(file.relativePath);
|
|
1334
|
+
hash.update(file.content);
|
|
1335
|
+
}
|
|
1336
|
+
return hash.digest("hex");
|
|
1337
|
+
}
|
|
1338
|
+
async function collectFiles(baseDir, currentDir, results) {
|
|
1339
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
1340
|
+
await Promise.all(entries.map(async (entry) => {
|
|
1341
|
+
const fullPath = join(currentDir, entry.name);
|
|
1342
|
+
if (entry.isDirectory()) {
|
|
1343
|
+
if (entry.name === ".git" || entry.name === "node_modules") return;
|
|
1344
|
+
await collectFiles(baseDir, fullPath, results);
|
|
1345
|
+
} else if (entry.isFile()) {
|
|
1346
|
+
const content = await readFile(fullPath);
|
|
1347
|
+
const relativePath = relative(baseDir, fullPath).split("\\").join("/");
|
|
1348
|
+
results.push({
|
|
1349
|
+
relativePath,
|
|
1350
|
+
content
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
}));
|
|
1354
|
+
}
|
|
1355
|
+
async function addSkillToLocalLock(skillName, entry, cwd) {
|
|
1356
|
+
const lock = await readLocalLock(cwd);
|
|
1357
|
+
lock.skills[skillName] = entry;
|
|
1358
|
+
await writeLocalLock(lock, cwd);
|
|
1359
|
+
}
|
|
1360
|
+
function createEmptyLocalLock() {
|
|
1361
|
+
return {
|
|
1362
|
+
version: CURRENT_VERSION$1,
|
|
1363
|
+
skills: {}
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1298
1366
|
async function fetchMintlifySkill(url) {
|
|
1299
1367
|
try {
|
|
1300
1368
|
const response = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
@@ -1770,7 +1838,7 @@ registerProvider(mintlifyProvider);
|
|
|
1770
1838
|
registerProvider(huggingFaceProvider);
|
|
1771
1839
|
const AGENTS_DIR$1 = ".agents";
|
|
1772
1840
|
const LOCK_FILE$1 = ".skill-lock.json";
|
|
1773
|
-
const CURRENT_VERSION
|
|
1841
|
+
const CURRENT_VERSION = 3;
|
|
1774
1842
|
function getSkillLockPath$1() {
|
|
1775
1843
|
return join(homedir(), AGENTS_DIR$1, LOCK_FILE$1);
|
|
1776
1844
|
}
|
|
@@ -1780,7 +1848,7 @@ async function readSkillLock$1() {
|
|
|
1780
1848
|
const content = await readFile(lockPath, "utf-8");
|
|
1781
1849
|
const parsed = JSON.parse(content);
|
|
1782
1850
|
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLockFile();
|
|
1783
|
-
if (parsed.version < CURRENT_VERSION
|
|
1851
|
+
if (parsed.version < CURRENT_VERSION) return createEmptyLockFile();
|
|
1784
1852
|
return parsed;
|
|
1785
1853
|
} catch (error) {
|
|
1786
1854
|
return createEmptyLockFile();
|
|
@@ -1853,7 +1921,7 @@ async function getSkillFromLock(skillName) {
|
|
|
1853
1921
|
}
|
|
1854
1922
|
function createEmptyLockFile() {
|
|
1855
1923
|
return {
|
|
1856
|
-
version: CURRENT_VERSION
|
|
1924
|
+
version: CURRENT_VERSION,
|
|
1857
1925
|
skills: {},
|
|
1858
1926
|
dismissed: {}
|
|
1859
1927
|
};
|
|
@@ -1875,72 +1943,6 @@ async function saveSelectedAgents(agents) {
|
|
|
1875
1943
|
lock.lastSelectedAgents = agents;
|
|
1876
1944
|
await writeSkillLock(lock);
|
|
1877
1945
|
}
|
|
1878
|
-
const LOCAL_LOCK_FILE = "skills-lock.json";
|
|
1879
|
-
const CURRENT_VERSION = 1;
|
|
1880
|
-
function getLocalLockPath(cwd) {
|
|
1881
|
-
return join(cwd || process.cwd(), LOCAL_LOCK_FILE);
|
|
1882
|
-
}
|
|
1883
|
-
async function readLocalLock(cwd) {
|
|
1884
|
-
const lockPath = getLocalLockPath(cwd);
|
|
1885
|
-
try {
|
|
1886
|
-
const content = await readFile(lockPath, "utf-8");
|
|
1887
|
-
const parsed = JSON.parse(content);
|
|
1888
|
-
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
|
|
1889
|
-
if (parsed.version < CURRENT_VERSION) return createEmptyLocalLock();
|
|
1890
|
-
return parsed;
|
|
1891
|
-
} catch {
|
|
1892
|
-
return createEmptyLocalLock();
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
async function writeLocalLock(lock, cwd) {
|
|
1896
|
-
const lockPath = getLocalLockPath(cwd);
|
|
1897
|
-
const sortedSkills = {};
|
|
1898
|
-
for (const key of Object.keys(lock.skills).sort()) sortedSkills[key] = lock.skills[key];
|
|
1899
|
-
const sorted = {
|
|
1900
|
-
version: lock.version,
|
|
1901
|
-
skills: sortedSkills
|
|
1902
|
-
};
|
|
1903
|
-
await writeFile(lockPath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
|
|
1904
|
-
}
|
|
1905
|
-
async function computeSkillFolderHash(skillDir) {
|
|
1906
|
-
const files = [];
|
|
1907
|
-
await collectFiles(skillDir, skillDir, files);
|
|
1908
|
-
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
1909
|
-
const hash = createHash("sha256");
|
|
1910
|
-
for (const file of files) {
|
|
1911
|
-
hash.update(file.relativePath);
|
|
1912
|
-
hash.update(file.content);
|
|
1913
|
-
}
|
|
1914
|
-
return hash.digest("hex");
|
|
1915
|
-
}
|
|
1916
|
-
async function collectFiles(baseDir, currentDir, results) {
|
|
1917
|
-
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
1918
|
-
await Promise.all(entries.map(async (entry) => {
|
|
1919
|
-
const fullPath = join(currentDir, entry.name);
|
|
1920
|
-
if (entry.isDirectory()) {
|
|
1921
|
-
if (entry.name === ".git" || entry.name === "node_modules") return;
|
|
1922
|
-
await collectFiles(baseDir, fullPath, results);
|
|
1923
|
-
} else if (entry.isFile()) {
|
|
1924
|
-
const content = await readFile(fullPath);
|
|
1925
|
-
const relativePath = relative(baseDir, fullPath).split("\\").join("/");
|
|
1926
|
-
results.push({
|
|
1927
|
-
relativePath,
|
|
1928
|
-
content
|
|
1929
|
-
});
|
|
1930
|
-
}
|
|
1931
|
-
}));
|
|
1932
|
-
}
|
|
1933
|
-
async function addSkillToLocalLock(skillName, entry, cwd) {
|
|
1934
|
-
const lock = await readLocalLock(cwd);
|
|
1935
|
-
lock.skills[skillName] = entry;
|
|
1936
|
-
await writeLocalLock(lock, cwd);
|
|
1937
|
-
}
|
|
1938
|
-
function createEmptyLocalLock() {
|
|
1939
|
-
return {
|
|
1940
|
-
version: CURRENT_VERSION,
|
|
1941
|
-
skills: {}
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
1946
|
function getOwnerRepo(parsed) {
|
|
1945
1947
|
if (parsed.type === "local") return null;
|
|
1946
1948
|
if (!parsed.url.startsWith("http://") && !parsed.url.startsWith("https://")) return null;
|
|
@@ -2073,8 +2075,7 @@ function parseSource(input) {
|
|
|
2073
2075
|
url: input
|
|
2074
2076
|
};
|
|
2075
2077
|
if (input.match(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i) && !input.includes("/") && !input.includes(":") && !input.startsWith(".")) return {
|
|
2076
|
-
|
|
2077
|
-
url: DEFAULT_REGISTRY_URL,
|
|
2078
|
+
...parseSource(DEFAULT_REGISTRY_URL),
|
|
2078
2079
|
subpath: DEFAULT_REGISTRY_SUBPATH,
|
|
2079
2080
|
skillFilter: input
|
|
2080
2081
|
};
|
|
@@ -2322,6 +2323,33 @@ async function selectAgentsInteractive(options) {
|
|
|
2322
2323
|
return selected;
|
|
2323
2324
|
}
|
|
2324
2325
|
setVersion(version$1);
|
|
2326
|
+
async function resolveGlobalLockHashData(params, deps = {
|
|
2327
|
+
fetchGitHubHash: fetchSkillFolderHash,
|
|
2328
|
+
computeFolderHash: computeSkillFolderHash
|
|
2329
|
+
}) {
|
|
2330
|
+
if (params.sourceType === "github") {
|
|
2331
|
+
let skillFolderHash = "";
|
|
2332
|
+
if (params.normalizedSource && params.skillPathValue) try {
|
|
2333
|
+
const hash = await deps.fetchGitHubHash(params.normalizedSource, params.skillPathValue);
|
|
2334
|
+
if (hash) skillFolderHash = hash;
|
|
2335
|
+
} catch {}
|
|
2336
|
+
return {
|
|
2337
|
+
skillFolderHash,
|
|
2338
|
+
hashMethod: "github-tree-sha"
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
if (params.sourceType === "git" || params.sourceType === "gitlab") {
|
|
2342
|
+
let skillFolderHash = "";
|
|
2343
|
+
if (params.installDir) try {
|
|
2344
|
+
skillFolderHash = await deps.computeFolderHash(params.installDir);
|
|
2345
|
+
} catch {}
|
|
2346
|
+
return {
|
|
2347
|
+
skillFolderHash,
|
|
2348
|
+
hashMethod: "content-sha256"
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
return { skillFolderHash: "" };
|
|
2352
|
+
}
|
|
2325
2353
|
async function handleRemoteSkill(source, url, options, spinner) {
|
|
2326
2354
|
const provider = findProvider(url);
|
|
2327
2355
|
if (!provider) {
|
|
@@ -2494,7 +2522,9 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2494
2522
|
});
|
|
2495
2523
|
if (successful.length > 0 && installGlobally) try {
|
|
2496
2524
|
let skillFolderHash = "";
|
|
2525
|
+
let hashMethod;
|
|
2497
2526
|
if (remoteSkill.providerId === "github") {
|
|
2527
|
+
hashMethod = "github-tree-sha";
|
|
2498
2528
|
const hash = await fetchSkillFolderHash(remoteSkill.sourceIdentifier, url);
|
|
2499
2529
|
if (hash) skillFolderHash = hash;
|
|
2500
2530
|
}
|
|
@@ -2502,7 +2532,8 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2502
2532
|
source: remoteSkill.sourceIdentifier,
|
|
2503
2533
|
sourceType: remoteSkill.providerId,
|
|
2504
2534
|
sourceUrl: url,
|
|
2505
|
-
skillFolderHash
|
|
2535
|
+
skillFolderHash,
|
|
2536
|
+
hashMethod
|
|
2506
2537
|
});
|
|
2507
2538
|
} catch {}
|
|
2508
2539
|
if (successful.length > 0 && !installGlobally) try {
|
|
@@ -3025,12 +3056,12 @@ async function runAdd(args, options = {}) {
|
|
|
3025
3056
|
console.log(import_picocolors.default.bgRed(import_picocolors.default.white(import_picocolors.default.bold(" ERROR "))) + " " + import_picocolors.default.red("Missing required argument: source"));
|
|
3026
3057
|
console.log();
|
|
3027
3058
|
console.log(import_picocolors.default.dim(" Usage:"));
|
|
3028
|
-
console.log(` ${import_picocolors.default.cyan("npx skills add")} ${import_picocolors.default.yellow("<source>")} ${import_picocolors.default.dim("[options]")}`);
|
|
3029
|
-
console.log(` ${import_picocolors.default.cyan("npx skills add")} ${import_picocolors.default.yellow("[options]")} ${import_picocolors.default.dim("(uses default registry)")}`);
|
|
3059
|
+
console.log(` ${import_picocolors.default.cyan("npx @bud-fe/skills add")} ${import_picocolors.default.yellow("<source>")} ${import_picocolors.default.dim("[options]")}`);
|
|
3060
|
+
console.log(` ${import_picocolors.default.cyan("npx @bud-fe/skills add")} ${import_picocolors.default.yellow("[options]")} ${import_picocolors.default.dim("(uses default registry)")}`);
|
|
3030
3061
|
console.log();
|
|
3031
3062
|
console.log(import_picocolors.default.dim(" Example:"));
|
|
3032
|
-
console.log(` ${import_picocolors.default.cyan("npx skills add")} ${import_picocolors.default.yellow("vercel-labs/agent-skills")}`);
|
|
3033
|
-
console.log(` ${import_picocolors.default.cyan("npx skills add")} ${import_picocolors.default.yellow("--all")}`);
|
|
3063
|
+
console.log(` ${import_picocolors.default.cyan("npx @bud-fe/skills add")} ${import_picocolors.default.yellow("vercel-labs/agent-skills")}`);
|
|
3064
|
+
console.log(` ${import_picocolors.default.cyan("npx @bud-fe/skills add")} ${import_picocolors.default.yellow("--all")}`);
|
|
3034
3065
|
console.log();
|
|
3035
3066
|
process.exit(1);
|
|
3036
3067
|
}
|
|
@@ -3329,18 +3360,22 @@ async function runAdd(args, options = {}) {
|
|
|
3329
3360
|
for (const skill of selectedSkills) {
|
|
3330
3361
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3331
3362
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
3332
|
-
let skillFolderHash = "";
|
|
3333
3363
|
const skillPathValue = skillFiles[skill.name];
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3364
|
+
const matchingResult = successful.find((r) => r.skill === skillDisplayName);
|
|
3365
|
+
const installDir = matchingResult?.canonicalPath || matchingResult?.path;
|
|
3366
|
+
const { skillFolderHash, hashMethod } = await resolveGlobalLockHashData({
|
|
3367
|
+
sourceType: parsed.type,
|
|
3368
|
+
normalizedSource,
|
|
3369
|
+
skillPathValue,
|
|
3370
|
+
installDir
|
|
3371
|
+
});
|
|
3338
3372
|
await addSkillToLock(skill.name, {
|
|
3339
3373
|
source: normalizedSource,
|
|
3340
3374
|
sourceType: parsed.type,
|
|
3341
3375
|
sourceUrl: parsed.url,
|
|
3342
3376
|
skillPath: skillPathValue,
|
|
3343
|
-
skillFolderHash
|
|
3377
|
+
skillFolderHash,
|
|
3378
|
+
hashMethod
|
|
3344
3379
|
});
|
|
3345
3380
|
} catch {}
|
|
3346
3381
|
}
|
|
@@ -3446,12 +3481,12 @@ async function runAddCommand(args, options = {}) {
|
|
|
3446
3481
|
console.log(import_picocolors.default.bgRed(import_picocolors.default.white(import_picocolors.default.bold(" ERROR "))) + " " + import_picocolors.default.red("Missing required argument: command"));
|
|
3447
3482
|
console.log();
|
|
3448
3483
|
console.log(import_picocolors.default.dim(" Usage:"));
|
|
3449
|
-
console.log(` ${import_picocolors.default.cyan("npx skills add-c")} ${import_picocolors.default.yellow("<command>")} ${import_picocolors.default.dim("[options]")}`);
|
|
3450
|
-
console.log(` ${import_picocolors.default.cyan("npx skills add-c")} ${import_picocolors.default.yellow("[options]")} ${import_picocolors.default.dim("(uses default registry)")}`);
|
|
3484
|
+
console.log(` ${import_picocolors.default.cyan("npx @bud-fe/skills add-c")} ${import_picocolors.default.yellow("<command>")} ${import_picocolors.default.dim("[options]")}`);
|
|
3485
|
+
console.log(` ${import_picocolors.default.cyan("npx @bud-fe/skills add-c")} ${import_picocolors.default.yellow("[options]")} ${import_picocolors.default.dim("(uses default registry)")}`);
|
|
3451
3486
|
console.log();
|
|
3452
3487
|
console.log(import_picocolors.default.dim(" Example:"));
|
|
3453
|
-
console.log(` ${import_picocolors.default.cyan("npx skills add-c")} ${import_picocolors.default.yellow("my-command")}`);
|
|
3454
|
-
console.log(` ${import_picocolors.default.cyan("npx skills add-c")} ${import_picocolors.default.yellow("--list")}`);
|
|
3488
|
+
console.log(` ${import_picocolors.default.cyan("npx @bud-fe/skills add-c")} ${import_picocolors.default.yellow("my-command")}`);
|
|
3489
|
+
console.log(` ${import_picocolors.default.cyan("npx @bud-fe/skills add-c")} ${import_picocolors.default.yellow("--list")}`);
|
|
3455
3490
|
console.log();
|
|
3456
3491
|
process.exit(1);
|
|
3457
3492
|
}
|
|
@@ -3795,11 +3830,11 @@ async function promptForFindSkills(options, targetAgents) {
|
|
|
3795
3830
|
});
|
|
3796
3831
|
} catch {
|
|
3797
3832
|
M.warn("Failed to install find-skills. You can try again with:");
|
|
3798
|
-
M.message(import_picocolors.default.dim(" npx skills add vercel-labs/skills@find-skills -g -y --all"));
|
|
3833
|
+
M.message(import_picocolors.default.dim(" npx @bud-fe/skills add vercel-labs/skills@find-skills -g -y --all"));
|
|
3799
3834
|
}
|
|
3800
3835
|
} else {
|
|
3801
3836
|
await dismissPrompt("findSkillsPrompt");
|
|
3802
|
-
M.message(import_picocolors.default.dim("You can install it later with: npx skills add vercel-labs/skills@find-skills"));
|
|
3837
|
+
M.message(import_picocolors.default.dim("You can install it later with: npx @bud-fe/skills add vercel-labs/skills@find-skills"));
|
|
3803
3838
|
}
|
|
3804
3839
|
} catch {}
|
|
3805
3840
|
}
|
|
@@ -3983,7 +4018,7 @@ function formatNonInteractiveResults(mode, defaultResults, apiResults) {
|
|
|
3983
4018
|
const hasDefault = mode === "default" && defaultResults.length > 0;
|
|
3984
4019
|
const hasApi = mode === "global" && apiResults.length > 0;
|
|
3985
4020
|
if (hasDefault) {
|
|
3986
|
-
lines.push(`${DIM$2}Install with${RESET$2} npx skills add <name>${DIM$2} (From default registry)${RESET$2}`);
|
|
4021
|
+
lines.push(`${DIM$2}Install with${RESET$2} npx @bud-fe/skills add <name>${DIM$2} (From default registry)${RESET$2}`);
|
|
3987
4022
|
lines.push("");
|
|
3988
4023
|
for (const skill of defaultResults) {
|
|
3989
4024
|
lines.push(`${TEXT$1}${skill.name}${RESET$2}`);
|
|
@@ -3992,7 +4027,7 @@ function formatNonInteractiveResults(mode, defaultResults, apiResults) {
|
|
|
3992
4027
|
}
|
|
3993
4028
|
}
|
|
3994
4029
|
if (hasApi) {
|
|
3995
|
-
lines.push(`${DIM$2}Install with${RESET$2} npx skills add <owner/repo@skill>${DIM$2} (From https://skills.sh)${RESET$2}`);
|
|
4030
|
+
lines.push(`${DIM$2}Install with${RESET$2} npx @bud-fe/skills add <owner/repo@skill>${DIM$2} (From https://skills.sh)${RESET$2}`);
|
|
3996
4031
|
lines.push("");
|
|
3997
4032
|
for (const skill of apiResults.slice(0, 6)) {
|
|
3998
4033
|
const pkg = skill.source || skill.slug;
|
|
@@ -4177,9 +4212,9 @@ async function runFind(args) {
|
|
|
4177
4212
|
const { mode, query } = parseFindArgs(args);
|
|
4178
4213
|
const isNonInteractive = !process.stdin.isTTY;
|
|
4179
4214
|
const agentTip = `${DIM$2}Tip: if running in a coding agent, follow these steps:${RESET$2}
|
|
4180
|
-
${DIM$2} 1) npx skills find [query]${RESET$2}
|
|
4181
|
-
${DIM$2} 2) npx skills find -g [query]${RESET$2}
|
|
4182
|
-
${DIM$2} 3) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
4215
|
+
${DIM$2} 1) npx @bud-fe/skills find [query]${RESET$2}
|
|
4216
|
+
${DIM$2} 2) npx @bud-fe/skills find -g [query]${RESET$2}
|
|
4217
|
+
${DIM$2} 3) npx @bud-fe/skills add <owner/repo@skill>${RESET$2}`;
|
|
4183
4218
|
if (query) {
|
|
4184
4219
|
const { defaultResults, apiResults } = await searchSkillsByMode(query, mode);
|
|
4185
4220
|
const totalResults = defaultResults.length + apiResults.length;
|
|
@@ -4526,7 +4561,7 @@ async function runInstallFromLock(args) {
|
|
|
4526
4561
|
const skillEntries = Object.entries(lock.skills);
|
|
4527
4562
|
if (skillEntries.length === 0) {
|
|
4528
4563
|
M.warn("No project skills found in skills-lock.json");
|
|
4529
|
-
M.info(`Add project-level skills with ${import_picocolors.default.cyan("npx skills add <package>")} (without ${import_picocolors.default.cyan("-g")})`);
|
|
4564
|
+
M.info(`Add project-level skills with ${import_picocolors.default.cyan("npx @bud-fe/skills add <package>")} (without ${import_picocolors.default.cyan("-g")})`);
|
|
4530
4565
|
return;
|
|
4531
4566
|
}
|
|
4532
4567
|
const universalAgentNames = getUniversalAgents();
|
|
@@ -4826,6 +4861,142 @@ function parseRemoveOptions(args) {
|
|
|
4826
4861
|
options
|
|
4827
4862
|
};
|
|
4828
4863
|
}
|
|
4864
|
+
const DEFAULT_DEPS = {
|
|
4865
|
+
fetchGitHubHash: fetchSkillFolderHash,
|
|
4866
|
+
cloneRepo,
|
|
4867
|
+
cleanupTempDir,
|
|
4868
|
+
computeFolderHash: computeSkillFolderHash,
|
|
4869
|
+
pathExists: existsSync
|
|
4870
|
+
};
|
|
4871
|
+
function normalizeSkillFolderPath(skillPath) {
|
|
4872
|
+
let folderPath = skillPath.replace(/\\/g, "/");
|
|
4873
|
+
if (folderPath.endsWith("/SKILL.md")) folderPath = folderPath.slice(0, -9);
|
|
4874
|
+
else if (folderPath.endsWith("SKILL.md")) folderPath = folderPath.slice(0, -8);
|
|
4875
|
+
if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
|
|
4876
|
+
return folderPath;
|
|
4877
|
+
}
|
|
4878
|
+
function resolveHashMethod(entry) {
|
|
4879
|
+
if (entry.hashMethod === "github-tree-sha" || entry.hashMethod === "content-sha256") return entry.hashMethod;
|
|
4880
|
+
if (entry.sourceType === "github") return "github-tree-sha";
|
|
4881
|
+
if ((entry.sourceType === "git" || entry.sourceType === "gitlab") && !!entry.skillFolderHash) return "content-sha256";
|
|
4882
|
+
return null;
|
|
4883
|
+
}
|
|
4884
|
+
async function detectSkillUpdates(lock, options = {}) {
|
|
4885
|
+
const deps = {
|
|
4886
|
+
...DEFAULT_DEPS,
|
|
4887
|
+
...options.deps || {}
|
|
4888
|
+
};
|
|
4889
|
+
const githubSkillsBySource = /* @__PURE__ */ new Map();
|
|
4890
|
+
const contentSkillsBySourceUrl = /* @__PURE__ */ new Map();
|
|
4891
|
+
let skippedMissingMetadata = 0;
|
|
4892
|
+
for (const [name, entry] of Object.entries(lock.skills)) {
|
|
4893
|
+
const hashMethod = resolveHashMethod(entry);
|
|
4894
|
+
if (!hashMethod || !entry.skillPath || !entry.skillFolderHash) {
|
|
4895
|
+
skippedMissingMetadata++;
|
|
4896
|
+
continue;
|
|
4897
|
+
}
|
|
4898
|
+
if (hashMethod === "github-tree-sha") {
|
|
4899
|
+
const bucket = githubSkillsBySource.get(entry.source) || [];
|
|
4900
|
+
bucket.push({
|
|
4901
|
+
name,
|
|
4902
|
+
entry
|
|
4903
|
+
});
|
|
4904
|
+
githubSkillsBySource.set(entry.source, bucket);
|
|
4905
|
+
continue;
|
|
4906
|
+
}
|
|
4907
|
+
if (!entry.sourceUrl) {
|
|
4908
|
+
skippedMissingMetadata++;
|
|
4909
|
+
continue;
|
|
4910
|
+
}
|
|
4911
|
+
const bucket = contentSkillsBySourceUrl.get(entry.sourceUrl) || [];
|
|
4912
|
+
bucket.push({
|
|
4913
|
+
name,
|
|
4914
|
+
entry
|
|
4915
|
+
});
|
|
4916
|
+
contentSkillsBySourceUrl.set(entry.sourceUrl, bucket);
|
|
4917
|
+
}
|
|
4918
|
+
const updates = [];
|
|
4919
|
+
const errors = [];
|
|
4920
|
+
let checkedCount = 0;
|
|
4921
|
+
for (const [source, skills] of githubSkillsBySource) for (const { name, entry } of skills) {
|
|
4922
|
+
checkedCount++;
|
|
4923
|
+
try {
|
|
4924
|
+
const latestHash = await deps.fetchGitHubHash(source, entry.skillPath, options.githubToken);
|
|
4925
|
+
if (!latestHash) {
|
|
4926
|
+
errors.push({
|
|
4927
|
+
name,
|
|
4928
|
+
source,
|
|
4929
|
+
error: "Could not fetch from GitHub"
|
|
4930
|
+
});
|
|
4931
|
+
continue;
|
|
4932
|
+
}
|
|
4933
|
+
if (latestHash !== entry.skillFolderHash) updates.push({
|
|
4934
|
+
name,
|
|
4935
|
+
source,
|
|
4936
|
+
entry
|
|
4937
|
+
});
|
|
4938
|
+
} catch (err) {
|
|
4939
|
+
errors.push({
|
|
4940
|
+
name,
|
|
4941
|
+
source,
|
|
4942
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
4943
|
+
});
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
for (const [sourceUrl, skills] of contentSkillsBySourceUrl) {
|
|
4947
|
+
let tempDir = null;
|
|
4948
|
+
try {
|
|
4949
|
+
tempDir = await deps.cloneRepo(sourceUrl);
|
|
4950
|
+
} catch (err) {
|
|
4951
|
+
const message = err instanceof Error ? err.message : "Failed to clone source repository";
|
|
4952
|
+
for (const { name, entry } of skills) {
|
|
4953
|
+
checkedCount++;
|
|
4954
|
+
errors.push({
|
|
4955
|
+
name,
|
|
4956
|
+
source: entry.source,
|
|
4957
|
+
error: message
|
|
4958
|
+
});
|
|
4959
|
+
}
|
|
4960
|
+
continue;
|
|
4961
|
+
}
|
|
4962
|
+
try {
|
|
4963
|
+
for (const { name, entry } of skills) {
|
|
4964
|
+
checkedCount++;
|
|
4965
|
+
try {
|
|
4966
|
+
const folderPath = normalizeSkillFolderPath(entry.skillPath);
|
|
4967
|
+
const targetDir = folderPath ? join(tempDir, folderPath) : tempDir;
|
|
4968
|
+
if (!deps.pathExists(targetDir)) {
|
|
4969
|
+
errors.push({
|
|
4970
|
+
name,
|
|
4971
|
+
source: entry.source,
|
|
4972
|
+
error: `Skill path not found: ${folderPath}`
|
|
4973
|
+
});
|
|
4974
|
+
continue;
|
|
4975
|
+
}
|
|
4976
|
+
if (await deps.computeFolderHash(targetDir) !== entry.skillFolderHash) updates.push({
|
|
4977
|
+
name,
|
|
4978
|
+
source: entry.source,
|
|
4979
|
+
entry
|
|
4980
|
+
});
|
|
4981
|
+
} catch (err) {
|
|
4982
|
+
errors.push({
|
|
4983
|
+
name,
|
|
4984
|
+
source: entry.source,
|
|
4985
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
} finally {
|
|
4990
|
+
await deps.cleanupTempDir(tempDir).catch(() => {});
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
return {
|
|
4994
|
+
checkedCount,
|
|
4995
|
+
skippedMissingMetadata,
|
|
4996
|
+
updates,
|
|
4997
|
+
errors
|
|
4998
|
+
};
|
|
4999
|
+
}
|
|
4829
5000
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4830
5001
|
function getVersion() {
|
|
4831
5002
|
try {
|
|
@@ -4866,20 +5037,20 @@ function showBanner() {
|
|
|
4866
5037
|
console.log();
|
|
4867
5038
|
console.log(`${DIM}The open agent skills ecosystem${RESET}`);
|
|
4868
5039
|
console.log();
|
|
4869
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills add ${DIM}<package>${RESET} ${DIM}Add a new skill${RESET}`);
|
|
4870
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills add-c ${DIM}<command>${RESET} ${DIM}Install a command${RESET}`);
|
|
4871
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
|
|
4872
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills list${RESET} ${DIM}List installed skills${RESET}`);
|
|
4873
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills find ${DIM}[query] [-g]${RESET} ${DIM}Search skills (default registry or global)${RESET}`);
|
|
5040
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills add ${DIM}<package>${RESET} ${DIM}Add a new skill${RESET}`);
|
|
5041
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills add-c ${DIM}<command>${RESET} ${DIM}Install a command${RESET}`);
|
|
5042
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
|
|
5043
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills list${RESET} ${DIM}List installed skills${RESET}`);
|
|
5044
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills find ${DIM}[query] [-g]${RESET} ${DIM}Search skills (default registry or global)${RESET}`);
|
|
4874
5045
|
console.log();
|
|
4875
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills check${RESET} ${DIM}Check for updates${RESET}`);
|
|
4876
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills update${RESET} ${DIM}Update all skills${RESET}`);
|
|
5046
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills check${RESET} ${DIM}Check for updates${RESET}`);
|
|
5047
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills update${RESET} ${DIM}Update all skills${RESET}`);
|
|
4877
5048
|
console.log();
|
|
4878
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
|
|
4879
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
|
|
4880
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
|
|
5049
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
|
|
5050
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
|
|
5051
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx @bud-fe/skills experimental_sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
|
|
4881
5052
|
console.log();
|
|
4882
|
-
console.log(`${DIM}try:${RESET} npx skills add vercel-labs/agent-skills`);
|
|
5053
|
+
console.log(`${DIM}try:${RESET} npx @bud-fe/skills add vercel-labs/agent-skills`);
|
|
4883
5054
|
console.log();
|
|
4884
5055
|
console.log(`Discover more skills at ${TEXT}https://skills.sh/${RESET}`);
|
|
4885
5056
|
console.log();
|
|
@@ -5043,8 +5214,8 @@ Describe when this skill should be used.
|
|
|
5043
5214
|
console.log(` 2. Update the ${TEXT}name${RESET} and ${TEXT}description${RESET} in the frontmatter`);
|
|
5044
5215
|
console.log();
|
|
5045
5216
|
console.log(`${DIM}Publishing:${RESET}`);
|
|
5046
|
-
console.log(` ${DIM}GitHub:${RESET} Push to a repo, then ${TEXT}npx skills add <owner>/<repo>${RESET}`);
|
|
5047
|
-
console.log(` ${DIM}URL:${RESET} Host the file, then ${TEXT}npx skills add https://example.com/${displayPath}${RESET}`);
|
|
5217
|
+
console.log(` ${DIM}GitHub:${RESET} Push to a repo, then ${TEXT}npx @bud-fe/skills add <owner>/<repo>${RESET}`);
|
|
5218
|
+
console.log(` ${DIM}URL:${RESET} Host the file, then ${TEXT}npx @bud-fe/skills add https://example.com/${displayPath}${RESET}`);
|
|
5048
5219
|
console.log();
|
|
5049
5220
|
console.log(`Browse existing skills for inspiration at ${TEXT}https://skills.sh/${RESET}`);
|
|
5050
5221
|
console.log();
|
|
@@ -5080,58 +5251,21 @@ async function runCheck(args = []) {
|
|
|
5080
5251
|
console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
5081
5252
|
console.log();
|
|
5082
5253
|
const lock = readSkillLock();
|
|
5083
|
-
|
|
5084
|
-
if (skillNames.length === 0) {
|
|
5254
|
+
if (Object.keys(lock.skills).length === 0) {
|
|
5085
5255
|
console.log(`${DIM}No skills tracked in lock file.${RESET}`);
|
|
5086
|
-
console.log(`${DIM}Install skills with${RESET} ${TEXT}npx skills add <package>${RESET}`);
|
|
5256
|
+
console.log(`${DIM}Install skills with${RESET} ${TEXT}npx @bud-fe/skills add <package>${RESET}`);
|
|
5087
5257
|
return;
|
|
5088
5258
|
}
|
|
5089
|
-
const
|
|
5090
|
-
const
|
|
5091
|
-
let skippedCount = 0;
|
|
5092
|
-
for (const skillName of skillNames) {
|
|
5093
|
-
const entry = lock.skills[skillName];
|
|
5094
|
-
if (!entry) continue;
|
|
5095
|
-
if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) {
|
|
5096
|
-
skippedCount++;
|
|
5097
|
-
continue;
|
|
5098
|
-
}
|
|
5099
|
-
const existing = skillsBySource.get(entry.source) || [];
|
|
5100
|
-
existing.push({
|
|
5101
|
-
name: skillName,
|
|
5102
|
-
entry
|
|
5103
|
-
});
|
|
5104
|
-
skillsBySource.set(entry.source, existing);
|
|
5105
|
-
}
|
|
5106
|
-
const totalSkills = skillNames.length - skippedCount;
|
|
5259
|
+
const result = await detectSkillUpdates(lock, { githubToken: getGitHubToken() });
|
|
5260
|
+
const totalSkills = result.checkedCount;
|
|
5107
5261
|
if (totalSkills === 0) {
|
|
5108
|
-
console.log(`${DIM}No
|
|
5262
|
+
console.log(`${DIM}No skills with update metadata to check.${RESET}`);
|
|
5263
|
+
if (result.skippedMissingMetadata > 0) console.log(`${DIM}${result.skippedMissingMetadata} skill(s) are missing hash metadata (run${RESET} ${TEXT}npx @bud-fe/skills add <source> -g${RESET} ${DIM}to reinstall and track updates).${RESET}`);
|
|
5109
5264
|
return;
|
|
5110
5265
|
}
|
|
5111
5266
|
console.log(`${DIM}Checking ${totalSkills} skill(s) for updates...${RESET}`);
|
|
5112
|
-
const updates =
|
|
5113
|
-
const errors =
|
|
5114
|
-
for (const [source, skills] of skillsBySource) for (const { name, entry } of skills) try {
|
|
5115
|
-
const latestHash = await fetchSkillFolderHash(source, entry.skillPath, token);
|
|
5116
|
-
if (!latestHash) {
|
|
5117
|
-
errors.push({
|
|
5118
|
-
name,
|
|
5119
|
-
source,
|
|
5120
|
-
error: "Could not fetch from GitHub"
|
|
5121
|
-
});
|
|
5122
|
-
continue;
|
|
5123
|
-
}
|
|
5124
|
-
if (latestHash !== entry.skillFolderHash) updates.push({
|
|
5125
|
-
name,
|
|
5126
|
-
source
|
|
5127
|
-
});
|
|
5128
|
-
} catch (err) {
|
|
5129
|
-
errors.push({
|
|
5130
|
-
name,
|
|
5131
|
-
source,
|
|
5132
|
-
error: err instanceof Error ? err.message : "Unknown error"
|
|
5133
|
-
});
|
|
5134
|
-
}
|
|
5267
|
+
const updates = result.updates;
|
|
5268
|
+
const errors = result.errors;
|
|
5135
5269
|
console.log();
|
|
5136
5270
|
if (updates.length === 0) console.log(`${TEXT}✓ All skills are up to date${RESET}`);
|
|
5137
5271
|
else {
|
|
@@ -5142,12 +5276,16 @@ async function runCheck(args = []) {
|
|
|
5142
5276
|
console.log(` ${DIM}source: ${update.source}${RESET}`);
|
|
5143
5277
|
}
|
|
5144
5278
|
console.log();
|
|
5145
|
-
console.log(`${DIM}Run${RESET} ${TEXT}npx skills update${RESET} ${DIM}to update all skills${RESET}`);
|
|
5279
|
+
console.log(`${DIM}Run${RESET} ${TEXT}npx @bud-fe/skills update${RESET} ${DIM}to update all skills${RESET}`);
|
|
5146
5280
|
}
|
|
5147
5281
|
if (errors.length > 0) {
|
|
5148
5282
|
console.log();
|
|
5149
5283
|
console.log(`${DIM}Could not check ${errors.length} skill(s) (may need reinstall)${RESET}`);
|
|
5150
5284
|
}
|
|
5285
|
+
if (result.skippedMissingMetadata > 0) {
|
|
5286
|
+
console.log();
|
|
5287
|
+
console.log(`${DIM}Skipped ${result.skippedMissingMetadata} skill(s) missing hash metadata (reinstall to enable update tracking).${RESET}`);
|
|
5288
|
+
}
|
|
5151
5289
|
track({
|
|
5152
5290
|
event: "check",
|
|
5153
5291
|
skillCount: String(totalSkills),
|
|
@@ -5159,31 +5297,16 @@ async function runUpdate() {
|
|
|
5159
5297
|
console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
5160
5298
|
console.log();
|
|
5161
5299
|
const lock = readSkillLock();
|
|
5162
|
-
|
|
5163
|
-
if (skillNames.length === 0) {
|
|
5300
|
+
if (Object.keys(lock.skills).length === 0) {
|
|
5164
5301
|
console.log(`${DIM}No skills tracked in lock file.${RESET}`);
|
|
5165
|
-
console.log(`${DIM}Install skills with${RESET} ${TEXT}npx skills add <package>${RESET}`);
|
|
5302
|
+
console.log(`${DIM}Install skills with${RESET} ${TEXT}npx @bud-fe/skills add <package>${RESET}`);
|
|
5166
5303
|
return;
|
|
5167
5304
|
}
|
|
5168
|
-
const
|
|
5169
|
-
const updates =
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
if (!entry) continue;
|
|
5174
|
-
if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) continue;
|
|
5175
|
-
checkedCount++;
|
|
5176
|
-
try {
|
|
5177
|
-
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
|
|
5178
|
-
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
5179
|
-
name: skillName,
|
|
5180
|
-
source: entry.source,
|
|
5181
|
-
entry
|
|
5182
|
-
});
|
|
5183
|
-
} catch {}
|
|
5184
|
-
}
|
|
5185
|
-
if (checkedCount === 0) {
|
|
5186
|
-
console.log(`${DIM}No skills to check.${RESET}`);
|
|
5305
|
+
const result = await detectSkillUpdates(lock, { githubToken: getGitHubToken() });
|
|
5306
|
+
const updates = result.updates;
|
|
5307
|
+
if (result.checkedCount === 0) {
|
|
5308
|
+
console.log(`${DIM}No skills with update metadata to check.${RESET}`);
|
|
5309
|
+
if (result.skippedMissingMetadata > 0) console.log(`${DIM}${result.skippedMissingMetadata} skill(s) are missing hash metadata (reinstall to enable update tracking).${RESET}`);
|
|
5187
5310
|
return;
|
|
5188
5311
|
}
|
|
5189
5312
|
if (updates.length === 0) {
|
|
@@ -5198,22 +5321,21 @@ async function runUpdate() {
|
|
|
5198
5321
|
for (const update of updates) {
|
|
5199
5322
|
console.log(`${TEXT}Updating ${update.name}...${RESET}`);
|
|
5200
5323
|
let installUrl = update.entry.sourceUrl;
|
|
5201
|
-
|
|
5324
|
+
const addArgs = [
|
|
5325
|
+
"-y",
|
|
5326
|
+
"skills",
|
|
5327
|
+
"add"
|
|
5328
|
+
];
|
|
5329
|
+
if (update.entry.sourceType === "github" && update.entry.skillPath) {
|
|
5202
5330
|
let skillFolder = update.entry.skillPath;
|
|
5203
5331
|
if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
|
|
5204
5332
|
else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
|
|
5205
5333
|
if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
|
|
5206
5334
|
installUrl = update.entry.sourceUrl.replace(/\.git$/, "").replace(/\/$/, "");
|
|
5207
5335
|
installUrl = `${installUrl}/tree/main/${skillFolder}`;
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
"skills",
|
|
5212
|
-
"add",
|
|
5213
|
-
installUrl,
|
|
5214
|
-
"-g",
|
|
5215
|
-
"-y"
|
|
5216
|
-
], { stdio: [
|
|
5336
|
+
addArgs.push(installUrl, "-g", "-y");
|
|
5337
|
+
} else addArgs.push(installUrl, "--skill", update.name, "-g", "-y");
|
|
5338
|
+
if (spawnSync("npx", addArgs, { stdio: [
|
|
5217
5339
|
"inherit",
|
|
5218
5340
|
"pipe",
|
|
5219
5341
|
"pipe"
|
|
@@ -5228,6 +5350,7 @@ async function runUpdate() {
|
|
|
5228
5350
|
console.log();
|
|
5229
5351
|
if (successCount > 0) console.log(`${TEXT}✓ Updated ${successCount} skill(s)${RESET}`);
|
|
5230
5352
|
if (failCount > 0) console.log(`${DIM}Failed to update ${failCount} skill(s)${RESET}`);
|
|
5353
|
+
if (result.skippedMissingMetadata > 0) console.log(`${DIM}Skipped ${result.skippedMissingMetadata} skill(s) missing hash metadata (reinstall to enable update tracking).${RESET}`);
|
|
5231
5354
|
track({
|
|
5232
5355
|
event: "update",
|
|
5233
5356
|
skillCount: String(updates.length),
|