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.
Files changed (3) hide show
  1. package/README.md +11 -8
  2. package/dist/cli.mjs +285 -57
  3. 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 [70 more](#supported-agents).
6
+ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [72 more](#supported-agents).
7
7
  <!-- agent-list:end -->
8
8
 
9
9
  [![skills.sh](https://skills.sh/b/vercel-labs/skills)](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 one level deep for the common flat
380
- layout (`skills/<name>/SKILL.md`) and one extra level deep for catalog
381
- layouts (`skills/<category>/<name>/SKILL.md`). A `SKILL.md` discovered at
382
- the shallower level shadows anything nested below it. Use `--full-depth`
383
- to also discover `SKILL.md` files outside these container directories
384
- (e.g. under `examples/` or `tests/`).
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-2 catalog walk described above.
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 lockPath = getLocalLockPath(cwd);
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 lockPath = getLocalLockPath(cwd);
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()) sortedSkills[key] = lock.skills[key];
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
- for (const dir of prioritySearchDirs) {
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) || !walkDeep) continue;
1149
- if (SKIP_DIRS.includes(entry.name)) continue;
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, { signal });
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
- if (isContainer && parts.length === 3 && parts[2].toLowerCase() === "skill.md" && !SKIP_DIRS.has(parts[0]) && !SKIP_DIRS.has(parts[1])) {
3688
- const parentSkillMd = `${fullPrefix}${parts[0]}/SKILL.md`.toLowerCase();
3689
- if (!lowerSkillMdSet.has(parentSkillMd) && !seen.has(skillMd)) {
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.21";
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: "10"
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.slice(0, 6)) {
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
- if (deletedSkills.length > 0) {
6234
- console.log();
6235
- console.log(`${DIM$1}Warning:${RESET$1} The following skills from ${DIM$1}${source}${RESET$1} appear to have been deleted upstream:`);
6236
- for (const s of deletedSkills) console.log(` ${DIM$1}•${RESET$1} ${s}`);
6237
- if (options.yes || !process.stdin.isTTY) console.log(`${DIM$1}Skipping deletion in non-interactive mode.${RESET$1}`);
6238
- else {
6239
- const confirmed = await confirm({ message: `Would you like to remove the local copies of these deleted skills?` });
6240
- if (confirmed && !isCancel(confirmed)) for (const s of deletedSkills) {
6241
- console.log(`${DIM$1}Removing${RESET$1} ${s}…`);
6242
- await removeCommand([s], {
6243
- yes: true,
6244
- global: isGlobal
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 deletedSkills;
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 updatable = projectSkills.filter((s) => s.entry.skillPath);
6435
- const legacy = projectSkills.filter((s) => !s.entry.skillPath);
6436
- if (updatable.length === 0) {
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 sourceUrl = firstEntry.sourceUrl || firstEntry.source;
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 (buildLocalUpdateSource(firstEntry) === null) {
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(sourceUrl, ref);
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.21",
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",