skills 1.5.21 → 1.5.22
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 +11 -8
- package/dist/cli.mjs +285 -57
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The CLI for the open agent skills ecosystem.
|
|
4
4
|
|
|
5
5
|
<!-- agent-list:start -->
|
|
6
|
-
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [
|
|
6
|
+
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [72 more](#supported-agents).
|
|
7
7
|
<!-- agent-list:end -->
|
|
8
8
|
|
|
9
9
|
[](https://skills.sh/vercel-labs/skills)
|
|
@@ -289,6 +289,7 @@ Skills can be installed to any of these agents:
|
|
|
289
289
|
| Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
|
|
290
290
|
| Lingma | `lingma` | `.lingma/skills/` | `~/.lingma/skills/` |
|
|
291
291
|
| MCPJam | `mcpjam` | `.mcpjam/skills/` | `~/.mcpjam/skills/` |
|
|
292
|
+
| MiniMax Code | `minimax-code` | `.minimax/skills/` | `~/.minimax/skills/` |
|
|
292
293
|
| Mistral Vibe | `mistral-vibe` | `.vibe/skills/` | `~/.vibe/skills/` |
|
|
293
294
|
| Moxby | `moxby` | `.moxby/skills/` | `~/.moxby/skills/` |
|
|
294
295
|
| Mux | `mux` | `.mux/skills/` | `~/.mux/skills/` |
|
|
@@ -376,12 +377,13 @@ metadata:
|
|
|
376
377
|
### Skill Discovery
|
|
377
378
|
|
|
378
379
|
The CLI searches for skills in these locations within a repository. Each
|
|
379
|
-
skill container directory is walked
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
(e.g. under
|
|
380
|
+
skill container directory is walked up to three levels deep, covering flat
|
|
381
|
+
layouts (`skills/<name>/SKILL.md`) and catalog layouts with one or two category
|
|
382
|
+
levels (`skills/<category>/<name>/SKILL.md` or
|
|
383
|
+
`skills/<category>/<category>/<name>/SKILL.md`). A `SKILL.md` discovered at a
|
|
384
|
+
shallower level shadows anything nested below it. Use `--full-depth` to also
|
|
385
|
+
discover `SKILL.md` files outside these container directories (e.g. under
|
|
386
|
+
`examples/` or `tests/`).
|
|
385
387
|
|
|
386
388
|
<!-- skill-discovery:start -->
|
|
387
389
|
- Root directory (if it contains `SKILL.md`)
|
|
@@ -421,6 +423,7 @@ to also discover `SKILL.md` files outside these container directories
|
|
|
421
423
|
- `.kode/skills/`
|
|
422
424
|
- `.lingma/skills/`
|
|
423
425
|
- `.mcpjam/skills/`
|
|
426
|
+
- `.minimax/skills/`
|
|
424
427
|
- `.vibe/skills/`
|
|
425
428
|
- `.moxby/skills/`
|
|
426
429
|
- `.mux/skills/`
|
|
@@ -462,7 +465,7 @@ If `.claude-plugin/marketplace.json` or `.claude-plugin/plugin.json` exists, ski
|
|
|
462
465
|
}
|
|
463
466
|
```
|
|
464
467
|
|
|
465
|
-
This enables compatibility with the [Claude Code plugin marketplace](https://code.claude.com/docs/en/plugin-marketplaces) ecosystem. Skill paths declared in a manifest are searched at their declared depth and are not subject to the depth-
|
|
468
|
+
This enables compatibility with the [Claude Code plugin marketplace](https://code.claude.com/docs/en/plugin-marketplaces) ecosystem. Skill paths declared in a manifest are searched at their declared depth and are not subject to the bounded depth-3 catalog walk described above.
|
|
466
469
|
|
|
467
470
|
If no skills are found in standard locations, a recursive search is performed.
|
|
468
471
|
|
package/dist/cli.mjs
CHANGED
|
@@ -902,27 +902,45 @@ function getLocalLockPath(cwd) {
|
|
|
902
902
|
return join(cwd || process.cwd(), LOCAL_LOCK_FILE);
|
|
903
903
|
}
|
|
904
904
|
async function readLocalLock(cwd) {
|
|
905
|
-
const
|
|
905
|
+
const lockDir = cwd || process.cwd();
|
|
906
|
+
const lockPath = getLocalLockPath(lockDir);
|
|
906
907
|
try {
|
|
907
908
|
const content = await readFile(lockPath, "utf-8");
|
|
908
909
|
const parsed = JSON.parse(content);
|
|
909
910
|
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
|
|
910
911
|
if (parsed.version < CURRENT_VERSION$1) return createEmptyLocalLock();
|
|
912
|
+
for (const entry of Object.values(parsed.skills)) if (entry.sourceType === "local" && !isAbsolute(entry.source)) entry.source = resolve(lockDir, entry.source);
|
|
911
913
|
return parsed;
|
|
912
914
|
} catch {
|
|
913
915
|
return createEmptyLocalLock();
|
|
914
916
|
}
|
|
915
917
|
}
|
|
916
918
|
async function writeLocalLock(lock, cwd) {
|
|
917
|
-
const
|
|
919
|
+
const lockDir = cwd || process.cwd();
|
|
920
|
+
const lockPath = getLocalLockPath(lockDir);
|
|
918
921
|
const sortedSkills = {};
|
|
919
|
-
for (const key of Object.keys(lock.skills).sort())
|
|
922
|
+
for (const key of Object.keys(lock.skills).sort()) {
|
|
923
|
+
const entry = lock.skills[key];
|
|
924
|
+
sortedSkills[key] = entry.sourceType === "local" ? {
|
|
925
|
+
...entry,
|
|
926
|
+
source: getPortableLocalSource(entry.source, lockDir)
|
|
927
|
+
} : entry;
|
|
928
|
+
}
|
|
920
929
|
const sorted = {
|
|
921
930
|
version: lock.version,
|
|
922
931
|
skills: sortedSkills
|
|
923
932
|
};
|
|
924
933
|
await writeFile(lockPath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
|
|
925
934
|
}
|
|
935
|
+
function getPortableLocalSource(source, lockDir) {
|
|
936
|
+
const absoluteSource = isAbsolute(source) ? source : resolve(lockDir, source);
|
|
937
|
+
const relativeSource = relative(lockDir, absoluteSource);
|
|
938
|
+
if (isAbsolute(relativeSource)) return absoluteSource.split(sep).join("/");
|
|
939
|
+
const portableSource = relativeSource.split(sep).join("/");
|
|
940
|
+
if (!portableSource) return ".";
|
|
941
|
+
if (portableSource === ".." || portableSource.startsWith("../")) return portableSource;
|
|
942
|
+
return `./${portableSource}`;
|
|
943
|
+
}
|
|
926
944
|
async function computeSkillFolderHash(skillDir) {
|
|
927
945
|
const files = [];
|
|
928
946
|
await collectFiles(skillDir, skillDir, files);
|
|
@@ -969,6 +987,8 @@ function createEmptyLocalLock() {
|
|
|
969
987
|
skills: {}
|
|
970
988
|
};
|
|
971
989
|
}
|
|
990
|
+
const AGENTS_DIR$1 = ".agents";
|
|
991
|
+
const SKILLS_SUBDIR = "skills";
|
|
972
992
|
const SKIP_DIRS = [
|
|
973
993
|
"node_modules",
|
|
974
994
|
".git",
|
|
@@ -992,6 +1012,7 @@ const AGENT_PROJECT_SKILL_DIRS = [
|
|
|
992
1012
|
".kilocode/skills",
|
|
993
1013
|
".kimchi/skills",
|
|
994
1014
|
".kiro/skills",
|
|
1015
|
+
".minimax/skills",
|
|
995
1016
|
".mux/skills",
|
|
996
1017
|
".neovate/skills",
|
|
997
1018
|
".opencode/skills",
|
|
@@ -1138,25 +1159,18 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
1138
1159
|
seenNames.add(skill.name);
|
|
1139
1160
|
return true;
|
|
1140
1161
|
};
|
|
1141
|
-
|
|
1142
|
-
const walkDeep = deepContainerDirs.has(dir);
|
|
1162
|
+
const walkSkillDirs = async (dir, maxDepth, depth = 1) => {
|
|
1143
1163
|
try {
|
|
1144
1164
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
1145
1165
|
for (const entry of entries) {
|
|
1146
1166
|
if (!entry.isDirectory()) continue;
|
|
1147
1167
|
const childDir = join(dir, entry.name);
|
|
1148
|
-
if (await tryAddSkillAt(childDir) ||
|
|
1149
|
-
|
|
1150
|
-
try {
|
|
1151
|
-
const grandEntries = await readdir(childDir, { withFileTypes: true });
|
|
1152
|
-
for (const grand of grandEntries) {
|
|
1153
|
-
if (!grand.isDirectory() || SKIP_DIRS.includes(grand.name)) continue;
|
|
1154
|
-
await tryAddSkillAt(join(childDir, grand.name));
|
|
1155
|
-
}
|
|
1156
|
-
} catch {}
|
|
1168
|
+
if (await tryAddSkillAt(childDir) || depth >= maxDepth || SKIP_DIRS.includes(entry.name)) continue;
|
|
1169
|
+
await walkSkillDirs(childDir, maxDepth, depth + 1);
|
|
1157
1170
|
}
|
|
1158
1171
|
} catch {}
|
|
1159
|
-
}
|
|
1172
|
+
};
|
|
1173
|
+
for (const dir of prioritySearchDirs) await walkSkillDirs(dir, deepContainerDirs.has(dir) ? 3 : 1);
|
|
1160
1174
|
if (skills.length === 0 || options?.fullDepth) {
|
|
1161
1175
|
const allSkillDirs = await findSkillDirs(searchPath);
|
|
1162
1176
|
for (const skillDir of allSkillDirs) {
|
|
@@ -1211,6 +1225,9 @@ function isZCodeInstalled(homeDir = home, pathExists = existsSync) {
|
|
|
1211
1225
|
function isKimchiInstalled(homeDir = home, pathExists = existsSync) {
|
|
1212
1226
|
return pathExists(join(homeDir, ".config", "kimchi"));
|
|
1213
1227
|
}
|
|
1228
|
+
function isMiniMaxCodeInstalled(homeDir = home, pathExists = existsSync) {
|
|
1229
|
+
return pathExists(join(homeDir, ".minimax")) || pathExists("/Applications/MiniMax Code.app");
|
|
1230
|
+
}
|
|
1214
1231
|
const agents = {
|
|
1215
1232
|
"aider-desk": {
|
|
1216
1233
|
name: "aider-desk",
|
|
@@ -1621,6 +1638,15 @@ const agents = {
|
|
|
1621
1638
|
return existsSync(join(home, ".mcpjam"));
|
|
1622
1639
|
}
|
|
1623
1640
|
},
|
|
1641
|
+
"minimax-code": {
|
|
1642
|
+
name: "minimax-code",
|
|
1643
|
+
displayName: "MiniMax Code",
|
|
1644
|
+
skillsDir: ".minimax/skills",
|
|
1645
|
+
globalSkillsDir: join(home, ".minimax/skills"),
|
|
1646
|
+
detectInstalled: async () => {
|
|
1647
|
+
return isMiniMaxCodeInstalled();
|
|
1648
|
+
}
|
|
1649
|
+
},
|
|
1624
1650
|
"mistral-vibe": {
|
|
1625
1651
|
name: "mistral-vibe",
|
|
1626
1652
|
displayName: "Mistral Vibe",
|
|
@@ -1921,8 +1947,6 @@ function getNonUniversalAgents() {
|
|
|
1921
1947
|
function isUniversalAgent(type) {
|
|
1922
1948
|
return agents[type].skillsDir === ".agents/skills";
|
|
1923
1949
|
}
|
|
1924
|
-
const AGENTS_DIR$1 = ".agents";
|
|
1925
|
-
const SKILLS_SUBDIR = "skills";
|
|
1926
1950
|
function sanitizeName(name) {
|
|
1927
1951
|
return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
|
|
1928
1952
|
}
|
|
@@ -2873,10 +2897,10 @@ var WellKnownProvider = class {
|
|
|
2873
2897
|
return { matches: false };
|
|
2874
2898
|
}
|
|
2875
2899
|
}
|
|
2876
|
-
async fetchIndex(baseUrl) {
|
|
2877
|
-
return (await this.fetchIndexCandidates(baseUrl))[0] ?? null;
|
|
2900
|
+
async fetchIndex(baseUrl, options) {
|
|
2901
|
+
return (await this.fetchIndexCandidates(baseUrl, options))[0] ?? null;
|
|
2878
2902
|
}
|
|
2879
|
-
async fetchIndexCandidates(baseUrl) {
|
|
2903
|
+
async fetchIndexCandidates(baseUrl, options) {
|
|
2880
2904
|
try {
|
|
2881
2905
|
const parsed = new URL(baseUrl);
|
|
2882
2906
|
const basePath = parsed.pathname.replace(/\/$/, "");
|
|
@@ -2896,7 +2920,10 @@ var WellKnownProvider = class {
|
|
|
2896
2920
|
}
|
|
2897
2921
|
const candidates = [];
|
|
2898
2922
|
for (const { indexUrl, baseUrl: resolvedBase, wellKnownPath } of urlsToTry) try {
|
|
2899
|
-
const response = await fetch(indexUrl, {
|
|
2923
|
+
const response = await fetch(indexUrl, {
|
|
2924
|
+
signal,
|
|
2925
|
+
...options?.updateCheck ? { headers: { "X-Skills-Update-Check": "1" } } : {}
|
|
2926
|
+
});
|
|
2900
2927
|
if (!response.ok) continue;
|
|
2901
2928
|
const rawIndex = await response.json();
|
|
2902
2929
|
const normalized = this.normalizeIndex(rawIndex, indexUrl, wellKnownPath);
|
|
@@ -3253,6 +3280,17 @@ var WellKnownProvider = class {
|
|
|
3253
3280
|
return await this.fetchIndex(url) !== null;
|
|
3254
3281
|
}
|
|
3255
3282
|
};
|
|
3283
|
+
function computeWellKnownSkillDigest(skill) {
|
|
3284
|
+
if ("digest" in skill.indexEntry && skill.indexEntry.digest) return skill.indexEntry.digest;
|
|
3285
|
+
const hash = createHash$1("sha256");
|
|
3286
|
+
for (const path of Array.from(skill.files.keys()).sort()) {
|
|
3287
|
+
hash.update(path);
|
|
3288
|
+
hash.update("\0");
|
|
3289
|
+
hash.update(skill.files.get(path));
|
|
3290
|
+
hash.update("\0");
|
|
3291
|
+
}
|
|
3292
|
+
return `sha256:${hash.digest("hex")}`;
|
|
3293
|
+
}
|
|
3256
3294
|
const wellKnownProvider = new WellKnownProvider();
|
|
3257
3295
|
const DEFAULT_DOWNLOAD_MAX_BYTES = 10 * 1024 * 1024;
|
|
3258
3296
|
const DEFAULT_EXTRACT_MAX_BYTES = 25 * 1024 * 1024;
|
|
@@ -3636,6 +3674,7 @@ const PRIORITY_PREFIXES = [
|
|
|
3636
3674
|
".kilocode/skills/",
|
|
3637
3675
|
".kimchi/skills/",
|
|
3638
3676
|
".kiro/skills/",
|
|
3677
|
+
".minimax/skills/",
|
|
3639
3678
|
".mux/skills/",
|
|
3640
3679
|
".neovate/skills/",
|
|
3641
3680
|
".opencode/skills/",
|
|
@@ -3684,9 +3723,13 @@ function findSkillMdPaths(tree, subpath) {
|
|
|
3684
3723
|
}
|
|
3685
3724
|
continue;
|
|
3686
3725
|
}
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3726
|
+
const skillDirs = parts.slice(0, -1);
|
|
3727
|
+
const hasAncestorSkill = skillDirs.slice(0, -1).some((_, index) => {
|
|
3728
|
+
const ancestorPath = skillDirs.slice(0, index + 1).join("/");
|
|
3729
|
+
return lowerSkillMdSet.has(`${fullPrefix}${ancestorPath}/SKILL.md`.toLowerCase());
|
|
3730
|
+
});
|
|
3731
|
+
if (isContainer && parts.length >= 3 && parts.length <= 4 && parts.at(-1).toLowerCase() === "skill.md" && skillDirs.every((part) => !SKIP_DIRS.has(part)) && !hasAncestorSkill) {
|
|
3732
|
+
if (!seen.has(skillMd)) {
|
|
3690
3733
|
priorityResults.push(skillMd);
|
|
3691
3734
|
seen.add(skillMd);
|
|
3692
3735
|
}
|
|
@@ -3801,7 +3844,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
3801
3844
|
tree
|
|
3802
3845
|
};
|
|
3803
3846
|
}
|
|
3804
|
-
var version$1 = "1.5.
|
|
3847
|
+
var version$1 = "1.5.22";
|
|
3805
3848
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
3806
3849
|
const EVE_AGENT_LABEL = "eve agent";
|
|
3807
3850
|
async function isSourcePrivate(source) {
|
|
@@ -4020,6 +4063,14 @@ async function selectAgentsInteractive(options) {
|
|
|
4020
4063
|
return selected;
|
|
4021
4064
|
}
|
|
4022
4065
|
setVersion(version$1);
|
|
4066
|
+
function isSkillsShPackUrl(url) {
|
|
4067
|
+
try {
|
|
4068
|
+
const parsed = new URL(url);
|
|
4069
|
+
return parsed.hostname.replace(/^www\./, "") === "skills.sh" && /^\/p\/[^/]+/.test(parsed.pathname);
|
|
4070
|
+
} catch {
|
|
4071
|
+
return false;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4023
4074
|
async function handleWellKnownSkills(source, url, options, spinner) {
|
|
4024
4075
|
spinner.start("Discovering skills from well-known endpoint...");
|
|
4025
4076
|
const skills = await wellKnownProvider.fetchAllSkills(url).catch(() => []);
|
|
@@ -4072,6 +4123,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4072
4123
|
label: s.installName,
|
|
4073
4124
|
hint: s.description.length > 60 ? s.description.slice(0, 57) + "…" : s.description
|
|
4074
4125
|
})),
|
|
4126
|
+
initialValues: isSkillsShPackUrl(url) ? skills : void 0,
|
|
4075
4127
|
required: true
|
|
4076
4128
|
});
|
|
4077
4129
|
if (isCancel(selected)) {
|
|
@@ -4241,7 +4293,9 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4241
4293
|
source: sourceIdentifier,
|
|
4242
4294
|
sourceType: "well-known",
|
|
4243
4295
|
sourceUrl: skill.sourceUrl,
|
|
4244
|
-
skillFolderHash: ""
|
|
4296
|
+
skillFolderHash: "",
|
|
4297
|
+
sourceBaseUrl: url,
|
|
4298
|
+
wellKnownDigest: computeWellKnownSkillDigest(skill)
|
|
4245
4299
|
});
|
|
4246
4300
|
} catch {}
|
|
4247
4301
|
}
|
|
@@ -4254,8 +4308,10 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4254
4308
|
const computedHash = await computeSkillFolderHash(installDir);
|
|
4255
4309
|
await addSkillToLocalLock(skill.installName, {
|
|
4256
4310
|
source: sourceIdentifier,
|
|
4311
|
+
sourceUrl: url,
|
|
4257
4312
|
sourceType: "well-known",
|
|
4258
|
-
computedHash
|
|
4313
|
+
computedHash,
|
|
4314
|
+
wellKnownDigest: computeWellKnownSkillDigest(skill)
|
|
4259
4315
|
}, cwd);
|
|
4260
4316
|
}
|
|
4261
4317
|
} catch {}
|
|
@@ -5051,6 +5107,7 @@ const DIM$3 = "\x1B[38;5;102m";
|
|
|
5051
5107
|
const TEXT$2 = "\x1B[38;5;145m";
|
|
5052
5108
|
const CYAN$1 = "\x1B[36m";
|
|
5053
5109
|
const SEARCH_API_BASE = process.env.SKILLS_API_URL || "https://skills.sh";
|
|
5110
|
+
const SEARCH_RESULT_LIMIT = "20";
|
|
5054
5111
|
function formatInstalls(count) {
|
|
5055
5112
|
if (!count || count <= 0) return "";
|
|
5056
5113
|
if (count >= 1e6) return `${(count / 1e6).toFixed(1).replace(/\.0$/, "")}M installs`;
|
|
@@ -5101,7 +5158,7 @@ async function searchSkillsAPI(query, owner) {
|
|
|
5101
5158
|
try {
|
|
5102
5159
|
const params = new URLSearchParams({
|
|
5103
5160
|
q: query,
|
|
5104
|
-
limit:
|
|
5161
|
+
limit: SEARCH_RESULT_LIMIT
|
|
5105
5162
|
});
|
|
5106
5163
|
if (owner) params.set("owner", owner);
|
|
5107
5164
|
const url = `${SEARCH_API_BASE}/api/search?${params.toString()}`;
|
|
@@ -5277,7 +5334,7 @@ ${DIM$3} 2) npx skills add <owner/repo@skill>${RESET$3}`;
|
|
|
5277
5334
|
}
|
|
5278
5335
|
console.log(`${DIM$3}Install with${RESET$3} npx skills add <owner/repo@skill>`);
|
|
5279
5336
|
console.log();
|
|
5280
|
-
for (const skill of results
|
|
5337
|
+
for (const skill of results) {
|
|
5281
5338
|
const pkg = skill.source || skill.slug;
|
|
5282
5339
|
const installs = formatInstalls(skill.installs);
|
|
5283
5340
|
console.log(`${TEXT$2}${pkg}@${skill.name}${RESET$3}${installs ? ` ${CYAN$1}${installs}${RESET$3}` : ""}`);
|
|
@@ -5653,6 +5710,12 @@ function getLocalSource(entry) {
|
|
|
5653
5710
|
if ((entry.sourceType === "git" || entry.sourceType === "gitlab") && isBareShorthand(entry.source)) return null;
|
|
5654
5711
|
return entry.source;
|
|
5655
5712
|
}
|
|
5713
|
+
function buildLocalCloneSource(entry) {
|
|
5714
|
+
const source = getLocalSource(entry);
|
|
5715
|
+
if (!source) return null;
|
|
5716
|
+
if (entry.sourceType === "github" && isBareShorthand(source)) return `https://github.com/${source.replace(/\.git$/, "")}.git`;
|
|
5717
|
+
return source;
|
|
5718
|
+
}
|
|
5656
5719
|
function shouldUseFullDepthForUpdate(entry) {
|
|
5657
5720
|
if (!entry.skillPath) return false;
|
|
5658
5721
|
const source = entry.sourceType && entry.sourceType !== "github" ? getLocalSource(entry) : entry.source;
|
|
@@ -6029,12 +6092,12 @@ async function removeCommand(skillNames, options) {
|
|
|
6029
6092
|
const lockEntry = await getSkillFromLock(skillName);
|
|
6030
6093
|
effectiveSource = lockEntry?.source || "local";
|
|
6031
6094
|
effectiveSourceType = lockEntry?.sourceType || "local";
|
|
6032
|
-
await removeSkillFromLock(skillName);
|
|
6095
|
+
if (!isStillUsed) await removeSkillFromLock(skillName);
|
|
6033
6096
|
} else {
|
|
6034
6097
|
const lockEntry = (await readLocalLock(cwd)).skills[skillName];
|
|
6035
6098
|
effectiveSource = lockEntry?.source || "local";
|
|
6036
6099
|
effectiveSourceType = lockEntry?.sourceType || "local";
|
|
6037
|
-
await removeSkillFromLocalLock(skillName, cwd);
|
|
6100
|
+
if (!isStillUsed) await removeSkillFromLocalLock(skillName, cwd);
|
|
6038
6101
|
}
|
|
6039
6102
|
results.push({
|
|
6040
6103
|
skill: skillName,
|
|
@@ -6108,6 +6171,13 @@ const RESET$1 = "\x1B[0m";
|
|
|
6108
6171
|
const BOLD$1 = "\x1B[1m";
|
|
6109
6172
|
const DIM$1 = "\x1B[38;5;102m";
|
|
6110
6173
|
const TEXT$1 = "\x1B[38;5;145m";
|
|
6174
|
+
function getUpdateChildEnv(sourceType) {
|
|
6175
|
+
if (sourceType !== "github") return;
|
|
6176
|
+
return {
|
|
6177
|
+
...process.env,
|
|
6178
|
+
GH_HOST: "github.com"
|
|
6179
|
+
};
|
|
6180
|
+
}
|
|
6111
6181
|
function parseUpdateOptions(args) {
|
|
6112
6182
|
const options = {};
|
|
6113
6183
|
const positional = [];
|
|
@@ -6224,29 +6294,143 @@ async function getProjectSkillsForUpdate(skillFilter) {
|
|
|
6224
6294
|
}
|
|
6225
6295
|
return skills;
|
|
6226
6296
|
}
|
|
6297
|
+
async function promptDeletions(source, deletedSkills, isGlobal, options) {
|
|
6298
|
+
if (deletedSkills.length === 0) return;
|
|
6299
|
+
console.log();
|
|
6300
|
+
console.log(`${DIM$1}Warning:${RESET$1} The following skills from ${DIM$1}${source}${RESET$1} appear to have been deleted upstream:`);
|
|
6301
|
+
for (const s of deletedSkills) console.log(` ${DIM$1}•${RESET$1} ${s}`);
|
|
6302
|
+
if (options.yes || !process.stdin.isTTY) {
|
|
6303
|
+
console.log(`${DIM$1}Skipping deletion in non-interactive mode.${RESET$1}`);
|
|
6304
|
+
return;
|
|
6305
|
+
}
|
|
6306
|
+
const confirmed = await confirm({ message: `Would you like to remove the local copies of these deleted skills?` });
|
|
6307
|
+
if (confirmed && !isCancel(confirmed)) for (const s of deletedSkills) {
|
|
6308
|
+
console.log(`${DIM$1}Removing${RESET$1} ${s}…`);
|
|
6309
|
+
await removeCommand([s], {
|
|
6310
|
+
yes: true,
|
|
6311
|
+
global: isGlobal
|
|
6312
|
+
});
|
|
6313
|
+
}
|
|
6314
|
+
}
|
|
6227
6315
|
async function checkAndPromptForDeletions(source, allLockedForSource, lockSkills, isGlobal, options, discoveredPaths) {
|
|
6228
6316
|
const deletedSkills = allLockedForSource.filter((name) => {
|
|
6229
6317
|
const entry = lockSkills[name];
|
|
6230
6318
|
if (!entry?.skillPath) return false;
|
|
6231
6319
|
return !discoveredPaths.includes(entry.skillPath);
|
|
6232
6320
|
});
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6321
|
+
await promptDeletions(source, deletedSkills, isGlobal, options);
|
|
6322
|
+
return deletedSkills;
|
|
6323
|
+
}
|
|
6324
|
+
async function checkWellKnownForUpdates(baseUrl, items) {
|
|
6325
|
+
let indexResult;
|
|
6326
|
+
try {
|
|
6327
|
+
indexResult = await wellKnownProvider.fetchIndex(baseUrl, { updateCheck: true });
|
|
6328
|
+
} catch {
|
|
6329
|
+
return { status: "error" };
|
|
6330
|
+
}
|
|
6331
|
+
if (!indexResult) return { status: "error" };
|
|
6332
|
+
const byName = new Map(indexResult.entries.map((entry) => [entry.name, entry]));
|
|
6333
|
+
const removedSkills = items.filter((item) => !byName.has(item.name)).map((item) => item.name);
|
|
6334
|
+
const localNames = new Set(items.map((item) => item.name));
|
|
6335
|
+
const newSkills = indexResult.entries.map((entry) => entry.name).filter((name) => !localNames.has(name));
|
|
6336
|
+
const changedSkills = [];
|
|
6337
|
+
const needsContentCheck = [];
|
|
6338
|
+
for (const item of items) {
|
|
6339
|
+
const entry = byName.get(item.name);
|
|
6340
|
+
if (!entry) continue;
|
|
6341
|
+
if (entry.version === "0.2.0") {
|
|
6342
|
+
if (!item.digest || entry.digest !== item.digest) changedSkills.push(item.name);
|
|
6343
|
+
} else needsContentCheck.push(item);
|
|
6344
|
+
}
|
|
6345
|
+
if (needsContentCheck.length > 0) {
|
|
6346
|
+
const tracked = new Set(needsContentCheck.map((item) => item.name));
|
|
6347
|
+
const skills = (await Promise.all(indexResult.entries.filter((entry) => tracked.has(entry.name)).map((entry) => wellKnownProvider.fetchSkillByEntry(entry).catch(() => null)))).filter((skill) => skill !== null);
|
|
6348
|
+
if (skills.length === 0) return { status: "error" };
|
|
6349
|
+
const digests = new Map(skills.map((skill) => [skill.installName, computeWellKnownSkillDigest(skill)]));
|
|
6350
|
+
for (const item of needsContentCheck) {
|
|
6351
|
+
const digest = digests.get(item.name);
|
|
6352
|
+
if (!digest || !item.digest || digest !== item.digest) changedSkills.push(item.name);
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
if (changedSkills.length === 0 && removedSkills.length === 0) return {
|
|
6356
|
+
status: "current",
|
|
6357
|
+
newSkills
|
|
6358
|
+
};
|
|
6359
|
+
return {
|
|
6360
|
+
status: "changed",
|
|
6361
|
+
changedSkills,
|
|
6362
|
+
removedSkills,
|
|
6363
|
+
newSkills
|
|
6364
|
+
};
|
|
6365
|
+
}
|
|
6366
|
+
function printNewSkills(baseUrl, newSkills, isGlobal) {
|
|
6367
|
+
if (newSkills.length === 0) return;
|
|
6368
|
+
const names = newSkills.map(sanitizeMetadata);
|
|
6369
|
+
console.log(` ${DIM$1}${newSkills.length} new skill(s) available from this source:${RESET$1} ${names.join(", ")}`);
|
|
6370
|
+
console.log(` ${DIM$1}To install: ${TEXT$1}npx skills add ${baseUrl} --skill ${names.join(" ")}${isGlobal ? " -g" : ""}${RESET$1}`);
|
|
6371
|
+
}
|
|
6372
|
+
async function processWellKnownUpdates(groups, isGlobal, options) {
|
|
6373
|
+
let successCount = 0;
|
|
6374
|
+
let failCount = 0;
|
|
6375
|
+
let changed = false;
|
|
6376
|
+
for (const [baseUrl, items] of groups) {
|
|
6377
|
+
process.stdout.write(`\r${DIM$1}Checking skills from source: ${baseUrl}${RESET$1}\x1b[K\n`);
|
|
6378
|
+
const result = await checkWellKnownForUpdates(baseUrl, items);
|
|
6379
|
+
if (result.status === "error") {
|
|
6380
|
+
console.log(` ${DIM$1}✗ Failed to check skills from ${baseUrl}${RESET$1}`);
|
|
6381
|
+
continue;
|
|
6382
|
+
}
|
|
6383
|
+
if (result.status === "current") {
|
|
6384
|
+
printNewSkills(baseUrl, result.newSkills, isGlobal);
|
|
6385
|
+
continue;
|
|
6386
|
+
}
|
|
6387
|
+
changed = true;
|
|
6388
|
+
await promptDeletions(baseUrl, result.removedSkills, isGlobal, options);
|
|
6389
|
+
printNewSkills(baseUrl, result.newSkills, isGlobal);
|
|
6390
|
+
if (result.changedSkills.length === 0) continue;
|
|
6391
|
+
const cliEntry = join(__dirname$1, "..", "bin", "cli.mjs");
|
|
6392
|
+
if (!existsSync(cliEntry)) {
|
|
6393
|
+
failCount += result.changedSkills.length;
|
|
6394
|
+
console.log(` ${DIM$1}✗ CLI entrypoint not found at ${cliEntry}${RESET$1}`);
|
|
6395
|
+
continue;
|
|
6396
|
+
}
|
|
6397
|
+
const itemByName = new Map(items.map((item) => [item.name, item]));
|
|
6398
|
+
for (const name of result.changedSkills) {
|
|
6399
|
+
const safeName = sanitizeMetadata(name);
|
|
6400
|
+
console.log(`${TEXT$1}Updating ${safeName}…${RESET$1}`);
|
|
6401
|
+
const subagents = itemByName.get(name)?.subagents;
|
|
6402
|
+
const subagentArgs = !isGlobal && subagents?.length ? ["--subagent", ...subagents.map((s) => s === "" ? "root" : s)] : [];
|
|
6403
|
+
if (spawnSync(process.execPath, [
|
|
6404
|
+
cliEntry,
|
|
6405
|
+
"add",
|
|
6406
|
+
baseUrl,
|
|
6407
|
+
"--skill",
|
|
6408
|
+
name,
|
|
6409
|
+
...subagentArgs,
|
|
6410
|
+
...isGlobal ? ["-g"] : [],
|
|
6411
|
+
"-y"
|
|
6412
|
+
], {
|
|
6413
|
+
stdio: [
|
|
6414
|
+
"inherit",
|
|
6415
|
+
"pipe",
|
|
6416
|
+
"pipe"
|
|
6417
|
+
],
|
|
6418
|
+
encoding: "utf-8",
|
|
6419
|
+
shell: false
|
|
6420
|
+
}).status === 0) {
|
|
6421
|
+
successCount++;
|
|
6422
|
+
console.log(` ${TEXT$1}✓${RESET$1} Updated ${safeName}`);
|
|
6423
|
+
} else {
|
|
6424
|
+
failCount++;
|
|
6425
|
+
console.log(` ${DIM$1}✗ Failed to update ${safeName}${RESET$1}`);
|
|
6246
6426
|
}
|
|
6247
6427
|
}
|
|
6248
6428
|
}
|
|
6249
|
-
return
|
|
6429
|
+
return {
|
|
6430
|
+
successCount,
|
|
6431
|
+
failCount,
|
|
6432
|
+
changed
|
|
6433
|
+
};
|
|
6250
6434
|
}
|
|
6251
6435
|
async function updateGlobalSkills(options = {}) {
|
|
6252
6436
|
const lock = await readSkillLock();
|
|
@@ -6267,10 +6451,20 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6267
6451
|
const updates = [];
|
|
6268
6452
|
const skipped = [];
|
|
6269
6453
|
const checkable = [];
|
|
6454
|
+
const wellKnownGroups = /* @__PURE__ */ new Map();
|
|
6270
6455
|
for (const skillName of skillNames) {
|
|
6271
6456
|
if (!matchesSkillFilter(skillName, options.skills)) continue;
|
|
6272
6457
|
const entry = lock.skills[skillName];
|
|
6273
6458
|
if (!entry) continue;
|
|
6459
|
+
if (entry.sourceType === "well-known" && entry.sourceBaseUrl && entry.wellKnownDigest) {
|
|
6460
|
+
const group = wellKnownGroups.get(entry.sourceBaseUrl) || [];
|
|
6461
|
+
group.push({
|
|
6462
|
+
name: skillName,
|
|
6463
|
+
digest: entry.wellKnownDigest
|
|
6464
|
+
});
|
|
6465
|
+
wellKnownGroups.set(entry.sourceBaseUrl, group);
|
|
6466
|
+
continue;
|
|
6467
|
+
}
|
|
6274
6468
|
if (!entry.skillFolderHash || !entry.skillPath) {
|
|
6275
6469
|
skipped.push({
|
|
6276
6470
|
name: skillName,
|
|
@@ -6286,6 +6480,10 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6286
6480
|
entry
|
|
6287
6481
|
});
|
|
6288
6482
|
}
|
|
6483
|
+
const wellKnownCount = Array.from(wellKnownGroups.values()).reduce((sum, items) => sum + items.length, 0);
|
|
6484
|
+
const { successCount: wkSuccessCount, failCount: wkFailCount, changed: wkChanged } = await processWellKnownUpdates(wellKnownGroups, true, options);
|
|
6485
|
+
successCount += wkSuccessCount;
|
|
6486
|
+
failCount += wkFailCount;
|
|
6289
6487
|
const bySource = /* @__PURE__ */ new Map();
|
|
6290
6488
|
for (const item of checkable) {
|
|
6291
6489
|
const source = item.entry.source;
|
|
@@ -6343,8 +6541,8 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6343
6541
|
}
|
|
6344
6542
|
}
|
|
6345
6543
|
if (checkable.length > 0) process.stdout.write("\r\x1B[K");
|
|
6346
|
-
const checkedCount = checkable.length + skipped.length;
|
|
6347
|
-
if (checkable.length === 0 && skipped.length === 0) {
|
|
6544
|
+
const checkedCount = checkable.length + skipped.length + wellKnownCount;
|
|
6545
|
+
if (checkable.length === 0 && skipped.length === 0 && wellKnownCount === 0) {
|
|
6348
6546
|
if (!options.skills) console.log(`${DIM$1}No global skills to check.${RESET$1}`);
|
|
6349
6547
|
return {
|
|
6350
6548
|
successCount,
|
|
@@ -6352,6 +6550,14 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6352
6550
|
checkedCount: 0
|
|
6353
6551
|
};
|
|
6354
6552
|
}
|
|
6553
|
+
if (checkable.length === 0 && skipped.length === 0) {
|
|
6554
|
+
if (!wkChanged) console.log(`${TEXT$1}✓ All global skills are up to date${RESET$1}`);
|
|
6555
|
+
return {
|
|
6556
|
+
successCount,
|
|
6557
|
+
failCount,
|
|
6558
|
+
checkedCount
|
|
6559
|
+
};
|
|
6560
|
+
}
|
|
6355
6561
|
if (checkable.length === 0 && skipped.length > 0) {
|
|
6356
6562
|
printSkippedSkills(skipped);
|
|
6357
6563
|
return {
|
|
@@ -6361,7 +6567,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6361
6567
|
};
|
|
6362
6568
|
}
|
|
6363
6569
|
if (updates.length === 0) {
|
|
6364
|
-
console.log(`${TEXT$1}✓ All global skills are up to date${RESET$1}`);
|
|
6570
|
+
if (!wkChanged) console.log(`${TEXT$1}✓ All global skills are up to date${RESET$1}`);
|
|
6365
6571
|
return {
|
|
6366
6572
|
successCount,
|
|
6367
6573
|
failCount,
|
|
@@ -6390,6 +6596,8 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6390
6596
|
cliEntry,
|
|
6391
6597
|
"add",
|
|
6392
6598
|
installUrl,
|
|
6599
|
+
"--skill",
|
|
6600
|
+
update.name,
|
|
6393
6601
|
...fullDepthArgs,
|
|
6394
6602
|
"-g",
|
|
6395
6603
|
"-y"
|
|
@@ -6400,6 +6608,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
6400
6608
|
"pipe"
|
|
6401
6609
|
],
|
|
6402
6610
|
encoding: "utf-8",
|
|
6611
|
+
env: getUpdateChildEnv(update.entry.sourceType),
|
|
6403
6612
|
shell: false
|
|
6404
6613
|
}).status === 0) {
|
|
6405
6614
|
successCount++;
|
|
@@ -6431,9 +6640,24 @@ async function updateProjectSkills(options = {}) {
|
|
|
6431
6640
|
foundCount: 0
|
|
6432
6641
|
};
|
|
6433
6642
|
}
|
|
6434
|
-
const
|
|
6435
|
-
const
|
|
6436
|
-
|
|
6643
|
+
const wellKnownGroups = /* @__PURE__ */ new Map();
|
|
6644
|
+
const nonWellKnown = [];
|
|
6645
|
+
for (const skill of projectSkills) {
|
|
6646
|
+
const { entry } = skill;
|
|
6647
|
+
if (entry.sourceType === "well-known" && entry.sourceUrl && entry.wellKnownDigest) {
|
|
6648
|
+
const group = wellKnownGroups.get(entry.sourceUrl) || [];
|
|
6649
|
+
group.push({
|
|
6650
|
+
name: skill.name,
|
|
6651
|
+
digest: entry.wellKnownDigest,
|
|
6652
|
+
subagents: entry.subagents
|
|
6653
|
+
});
|
|
6654
|
+
wellKnownGroups.set(entry.sourceUrl, group);
|
|
6655
|
+
} else nonWellKnown.push(skill);
|
|
6656
|
+
}
|
|
6657
|
+
const wellKnownCount = Array.from(wellKnownGroups.values()).reduce((sum, items) => sum + items.length, 0);
|
|
6658
|
+
const updatable = nonWellKnown.filter((s) => s.entry.skillPath);
|
|
6659
|
+
const legacy = nonWellKnown.filter((s) => !s.entry.skillPath);
|
|
6660
|
+
if (updatable.length === 0 && wellKnownCount === 0) {
|
|
6437
6661
|
console.log(`${DIM$1}No project skills can be updated in place.${RESET$1}`);
|
|
6438
6662
|
printLegacyProjectSkills(legacy);
|
|
6439
6663
|
return {
|
|
@@ -6455,8 +6679,11 @@ async function updateProjectSkills(options = {}) {
|
|
|
6455
6679
|
if (hasUniversal) targetParts.push("Universal");
|
|
6456
6680
|
targetParts.push(...targetAgentNames);
|
|
6457
6681
|
if (targetParts.length > 0) console.log(`${TEXT$1}Updating for: ${targetParts.join(", ")}${RESET$1}`);
|
|
6458
|
-
console.log(`${TEXT$1}Refreshing ${updatable.length} skill(s)…${RESET$1}`);
|
|
6682
|
+
console.log(`${TEXT$1}Refreshing ${updatable.length + wellKnownCount} skill(s)…${RESET$1}`);
|
|
6459
6683
|
console.log();
|
|
6684
|
+
const { successCount: wkSuccessCount, failCount: wkFailCount } = await processWellKnownUpdates(wellKnownGroups, false, options);
|
|
6685
|
+
successCount += wkSuccessCount;
|
|
6686
|
+
failCount += wkFailCount;
|
|
6460
6687
|
const bySource = /* @__PURE__ */ new Map();
|
|
6461
6688
|
for (const skill of updatable) {
|
|
6462
6689
|
const source = skill.entry.sourceUrl || skill.entry.source;
|
|
@@ -6466,28 +6693,28 @@ async function updateProjectSkills(options = {}) {
|
|
|
6466
6693
|
}
|
|
6467
6694
|
const localLock = await readLocalLock();
|
|
6468
6695
|
const cliEntry = join(__dirname$1, "..", "bin", "cli.mjs");
|
|
6469
|
-
if (!existsSync(cliEntry)) {
|
|
6696
|
+
if (updatable.length > 0 && !existsSync(cliEntry)) {
|
|
6470
6697
|
console.log(`${DIM$1}✗ CLI entrypoint not found at ${cliEntry}${RESET$1}`);
|
|
6471
6698
|
return {
|
|
6472
6699
|
successCount,
|
|
6473
|
-
failCount: updatable.length,
|
|
6700
|
+
failCount: failCount + updatable.length,
|
|
6474
6701
|
foundCount: projectSkills.length
|
|
6475
6702
|
};
|
|
6476
6703
|
}
|
|
6477
6704
|
for (const [source, skillsForSource] of bySource) {
|
|
6478
6705
|
const firstEntry = skillsForSource[0].entry;
|
|
6479
|
-
const
|
|
6706
|
+
const cloneSource = buildLocalCloneSource(firstEntry);
|
|
6480
6707
|
const ref = firstEntry.ref;
|
|
6481
6708
|
const allLockedForSource = Object.entries(localLock.skills).filter(([_, entry]) => (entry.sourceUrl || entry.source) === source).map(([name, _]) => name);
|
|
6482
6709
|
let tempDir = null;
|
|
6483
6710
|
let deletedSkills = [];
|
|
6484
|
-
if (
|
|
6711
|
+
if (cloneSource === null) {
|
|
6485
6712
|
failCount += skillsForSource.length;
|
|
6486
6713
|
console.log(`${DIM$1}✗ Cannot update ${source}: skills-lock.json is missing sourceUrl for this generic Git source${RESET$1}`);
|
|
6487
6714
|
continue;
|
|
6488
6715
|
}
|
|
6489
6716
|
try {
|
|
6490
|
-
tempDir = await cloneRepo(
|
|
6717
|
+
tempDir = await cloneRepo(cloneSource, ref);
|
|
6491
6718
|
const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((s) => {
|
|
6492
6719
|
return join(relative(tempDir, s.path), "SKILL.md").split(sep).join("/");
|
|
6493
6720
|
});
|
|
@@ -6525,6 +6752,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
6525
6752
|
"pipe"
|
|
6526
6753
|
],
|
|
6527
6754
|
encoding: "utf-8",
|
|
6755
|
+
env: getUpdateChildEnv(skill.entry.sourceType),
|
|
6528
6756
|
shell: false
|
|
6529
6757
|
}).status === 0) {
|
|
6530
6758
|
successCount++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skills",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.22",
|
|
4
4
|
"description": "The open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"lingma",
|
|
82
82
|
"loaf",
|
|
83
83
|
"mcpjam",
|
|
84
|
+
"minimax-code",
|
|
84
85
|
"mistral-vibe",
|
|
85
86
|
"moxby",
|
|
86
87
|
"mux",
|