skills 1.5.19 → 1.5.20

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 +5 -1
  2. package/dist/cli.mjs +206 -62
  3. package/package.json +3 -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 [69 more](#supported-agents).
6
+ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [70 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)
@@ -272,12 +272,14 @@ Skills can be installed to any of these agents:
272
272
  | Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
273
273
  | GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
274
274
  | Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
275
+ | Grok Build | `grok` | `.grok/skills/` | `~/.grok/skills/` |
275
276
  | Hermes Agent | `hermes-agent` | `.hermes/skills/` | `~/.hermes/skills/` |
276
277
  | inference.sh | `inference-sh` | `.inferencesh/skills/` | `~/.inferencesh/skills/` |
277
278
  | Jazz | `jazz` | `.jazz/skills/` | `~/.jazz/skills/` |
278
279
  | Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
279
280
  | iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
280
281
  | Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` |
282
+ | Kimchi | `kimchi` | `.kimchi/skills/` | `~/.config/kimchi/harness/skills/` |
281
283
  | Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` |
282
284
  | Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
283
285
  | Lingma | `lingma` | `.lingma/skills/` | `~/.lingma/skills/` |
@@ -402,12 +404,14 @@ to also discover `SKILL.md` files outside these container directories
402
404
  - `agent/skills/`
403
405
  - `.forge/skills/`
404
406
  - `.goose/skills/`
407
+ - `.grok/skills/`
405
408
  - `.hermes/skills/`
406
409
  - `.inferencesh/skills/`
407
410
  - `.jazz/skills/`
408
411
  - `.junie/skills/`
409
412
  - `.iflow/skills/`
410
413
  - `.kilocode/skills/`
414
+ - `.kimchi/skills/`
411
415
  - `.kiro/skills/`
412
416
  - `.kode/skills/`
413
417
  - `.lingma/skills/`
package/dist/cli.mjs CHANGED
@@ -21,6 +21,22 @@ import { createHash } from "crypto";
21
21
  import { createHash as createHash$1 } from "node:crypto";
22
22
  import { gunzipSync, inflateRawSync } from "node:zlib";
23
23
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
24
+ const DEFAULT_GITHUB_HOST = "github.com";
25
+ function getGitHubHost() {
26
+ const configuredHost = process.env.GH_HOST?.trim();
27
+ if (!configuredHost) return DEFAULT_GITHUB_HOST;
28
+ try {
29
+ const parsed = new URL(`https://${configuredHost}`);
30
+ if (parsed.username || parsed.password || parsed.port || parsed.pathname !== "/" || parsed.search || parsed.hash) return DEFAULT_GITHUB_HOST;
31
+ return parsed.hostname;
32
+ } catch {
33
+ return DEFAULT_GITHUB_HOST;
34
+ }
35
+ }
36
+ function isGitHubHost(host) {
37
+ const normalizedHost = host.toLowerCase();
38
+ return normalizedHost === DEFAULT_GITHUB_HOST || normalizedHost === getGitHubHost().toLowerCase();
39
+ }
24
40
  function getOwnerRepo(parsed) {
25
41
  if (parsed.type === "local") return null;
26
42
  const sshMatch = parsed.url.match(/^git@[^:]+:(.+)$/);
@@ -88,7 +104,7 @@ function looksLikeGitSource(input) {
88
104
  if (input.startsWith("http://") || input.startsWith("https://")) try {
89
105
  const parsed = new URL(input);
90
106
  const pathname = parsed.pathname;
91
- if (parsed.hostname === "github.com") return /^\/[^/]+\/[^/]+(?:\.git)?(?:\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
107
+ if (isGitHubHost(parsed.host)) return /^\/[^/]+\/[^/]+(?:\.git)?(?:\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
92
108
  if (parsed.hostname === "gitlab.com") return /^\/.+?\/[^/]+(?:\.git)?(?:\/-\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
93
109
  } catch {}
94
110
  if (/^https?:\/\/.+\.git(?:$|[/?])/i.test(input)) return true;
@@ -134,6 +150,22 @@ function parseSource(input) {
134
150
  if (githubPrefixMatch) return parseSource(appendFragmentRef(githubPrefixMatch[1], fragmentRef, fragmentSkillFilter));
135
151
  const gitlabPrefixMatch = input.match(/^gitlab:(.+)$/);
136
152
  if (gitlabPrefixMatch) return parseSource(appendFragmentRef(`https://gitlab.com/${gitlabPrefixMatch[1]}`, fragmentRef, fragmentSkillFilter));
153
+ if (getGitHubHost() !== "github.com" && /^https?:\/\//.test(input)) try {
154
+ const parsedUrl = new URL(input);
155
+ if (isGitHubHost(parsedUrl.host) && parsedUrl.host !== "github.com") {
156
+ const [owner, rawRepo, marker, ref, ...subpathSegments] = parsedUrl.pathname.split("/").filter(Boolean);
157
+ if (owner && rawRepo) {
158
+ const repo = rawRepo.replace(/\.git$/, "");
159
+ const isTreeUrl = marker === "tree" && ref;
160
+ return {
161
+ type: "git",
162
+ url: `${parsedUrl.protocol}//${parsedUrl.host}/${owner}/${repo}.git`,
163
+ ...isTreeUrl ? { ref } : fragmentRef ? { ref: fragmentRef } : {},
164
+ ...isTreeUrl && subpathSegments.length > 0 ? { subpath: sanitizeSubpath(subpathSegments.join("/")) } : {}
165
+ };
166
+ }
167
+ }
168
+ } catch {}
137
169
  const githubTreeWithPathMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
138
170
  if (githubTreeWithPathMatch) {
139
171
  const [, owner, repo, ref, subpath] = githubTreeWithPathMatch;
@@ -190,12 +222,14 @@ function parseSource(input) {
190
222
  ...fragmentRef ? { ref: fragmentRef } : {}
191
223
  };
192
224
  }
225
+ const githubHost = getGitHubHost();
226
+ const shorthandSourceType = githubHost === "github.com" ? "github" : "git";
193
227
  const atSkillMatch = input.match(/^([^/]+)\/([^/@]+)@(.+)$/);
194
228
  if (atSkillMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
195
229
  const [, owner, repo, skillFilter] = atSkillMatch;
196
230
  return {
197
- type: "github",
198
- url: `https://github.com/${owner}/${repo}.git`,
231
+ type: shorthandSourceType,
232
+ url: `https://${githubHost}/${owner}/${repo}.git`,
199
233
  ...fragmentRef ? { ref: fragmentRef } : {},
200
234
  skillFilter: fragmentSkillFilter || skillFilter
201
235
  };
@@ -204,8 +238,8 @@ function parseSource(input) {
204
238
  if (shorthandMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
205
239
  const [, owner, repo, subpath] = shorthandMatch;
206
240
  return {
207
- type: "github",
208
- url: `https://github.com/${owner}/${repo}.git`,
241
+ type: shorthandSourceType,
242
+ url: `https://${githubHost}/${owner}/${repo}.git`,
209
243
  ...fragmentRef ? { ref: fragmentRef } : {},
210
244
  subpath: subpath ? sanitizeSubpath(subpath) : subpath,
211
245
  ...fragmentSkillFilter ? { skillFilter: fragmentSkillFilter } : {}
@@ -573,20 +607,21 @@ var GitCloneError = class extends Error {
573
607
  }
574
608
  };
575
609
  function parseGitHubRepoUrl(url) {
576
- const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
577
- if (sshMatch) {
578
- const owner = sshMatch[1];
579
- const repo = sshMatch[2];
610
+ const sshMatch = url.match(/^git@([^:]+):([^/]+)\/([^/]+?)(?:\.git)?$/i);
611
+ if (sshMatch && isGitHubHost(sshMatch[1])) {
612
+ const host = sshMatch[1];
613
+ const owner = sshMatch[2];
614
+ const repo = sshMatch[3];
580
615
  return {
581
616
  owner,
582
617
  repo,
583
618
  slug: `${owner}/${repo}`,
584
- sshUrl: `git@github.com:${owner}/${repo}.git`
619
+ sshUrl: `git@${host}:${owner}/${repo}.git`
585
620
  };
586
621
  }
587
622
  try {
588
623
  const parsed = new URL(url);
589
- if (parsed.hostname !== "github.com") return null;
624
+ if (!isGitHubHost(parsed.host)) return null;
590
625
  const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
591
626
  if (!match) return null;
592
627
  const owner = match[1];
@@ -595,7 +630,7 @@ function parseGitHubRepoUrl(url) {
595
630
  owner,
596
631
  repo,
597
632
  slug: `${owner}/${repo}`,
598
- sshUrl: `git@github.com:${owner}/${repo}.git`
633
+ sshUrl: `git@${parsed.host}:${owner}/${repo}.git`
599
634
  };
600
635
  } catch {
601
636
  return null;
@@ -604,7 +639,7 @@ function parseGitHubRepoUrl(url) {
604
639
  function isGitHubHttpsCloneUrl(url) {
605
640
  try {
606
641
  const parsed = new URL(url);
607
- return parsed.protocol === "https:" && parsed.hostname === "github.com";
642
+ return parsed.protocol === "https:" && isGitHubHost(parsed.host);
608
643
  } catch {
609
644
  return false;
610
645
  }
@@ -665,12 +700,13 @@ async function resetTempDir(dir) {
665
700
  }
666
701
  async function tryGhClone(repo, tempDir, ref) {
667
702
  let cloneTarget = repo.slug;
703
+ const host = repo.sshUrl.match(/^git@([^:]+):/)?.[1] || "github.com";
668
704
  try {
669
705
  const { stdout, stderr } = await execFileAsync("gh", [
670
706
  "auth",
671
707
  "status",
672
708
  "-h",
673
- "github.com"
709
+ host
674
710
  ], {
675
711
  timeout: 5e3,
676
712
  env: {
@@ -705,8 +741,9 @@ async function tryGhClone(repo, tempDir, ref) {
705
741
  return true;
706
742
  }
707
743
  function buildGitHubAuthError(url, repo, message) {
708
- if (repo && isGitHubSsoAuthError(message)) return `GitHub blocked HTTPS access to ${url} because the organization enforces SAML SSO.\n skills tried your existing git credentials and available fallbacks, but none succeeded.\n - Re-authorize your GitHub credentials/app for that org's SSO policy\n - Or rerun with SSH: npx skills add ${repo.sshUrl}\n - Verify access with: gh auth status -h github.com or ssh -T git@github.com`;
709
- if (repo) return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - Retry with SSH: npx skills add ${repo.sshUrl}\n - Check access with: gh auth status -h github.com or ssh -T git@github.com`;
744
+ const host = repo?.sshUrl.match(/^git@([^:]+):/)?.[1] || "github.com";
745
+ if (repo && isGitHubSsoAuthError(message)) return `GitHub blocked HTTPS access to ${url} because the organization enforces SAML SSO.\n skills tried your existing git credentials and available fallbacks, but none succeeded.\n - Re-authorize your GitHub credentials/app for that org's SSO policy\n - Or rerun with SSH: npx skills add ${repo.sshUrl}\n - Verify access with: gh auth status -h ${host} or ssh -T git@${host}`;
746
+ if (repo) return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - Retry with SSH: npx skills add ${repo.sshUrl}\n - Check access with: gh auth status -h ${host} or ssh -T git@${host}`;
710
747
  return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`;
711
748
  }
712
749
  async function cloneRepo(url, ref) {
@@ -897,6 +934,13 @@ async function addSkillToLocalLock(skillName, entry, cwd) {
897
934
  lock.skills[skillName] = entry;
898
935
  await writeLocalLock(lock, cwd);
899
936
  }
937
+ async function removeSkillFromLocalLock(skillName, cwd) {
938
+ const lock = await readLocalLock(cwd);
939
+ if (!(skillName in lock.skills)) return false;
940
+ delete lock.skills[skillName];
941
+ await writeLocalLock(lock, cwd);
942
+ return true;
943
+ }
900
944
  function createEmptyLocalLock() {
901
945
  return {
902
946
  version: CURRENT_VERSION$1,
@@ -920,9 +964,11 @@ const AGENT_PROJECT_SKILL_DIRS = [
920
964
  ".continue/skills",
921
965
  ".github/skills",
922
966
  ".goose/skills",
967
+ ".grok/skills",
923
968
  ".iflow/skills",
924
969
  ".junie/skills",
925
970
  ".kilocode/skills",
971
+ ".kimchi/skills",
926
972
  ".kiro/skills",
927
973
  ".mux/skills",
928
974
  ".neovate/skills",
@@ -956,24 +1002,44 @@ async function hasSkillMd(dir) {
956
1002
  return false;
957
1003
  }
958
1004
  }
1005
+ function warnSkippedSkill(skillMdPath, reason) {
1006
+ console.warn(`⚠ Skipped ${sanitizeMetadata(skillMdPath)} — ${stripTerminalEscapes(reason)}`);
1007
+ }
959
1008
  async function parseSkillMd(skillMdPath, options) {
1009
+ let content;
960
1010
  try {
961
- const content = await readFile(skillMdPath, "utf-8");
962
- const { data } = parseFrontmatter(content);
963
- if (!data.name || !data.description) return null;
964
- if (typeof data.name !== "string" || typeof data.description !== "string") return null;
965
- const metadata = isRecord(data.metadata) ? data.metadata : void 0;
966
- if (metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
967
- return {
968
- name: sanitizeMetadata(data.name),
969
- description: sanitizeMetadata(data.description),
970
- path: dirname(skillMdPath),
971
- rawContent: content,
972
- metadata
973
- };
974
- } catch {
1011
+ content = await readFile(skillMdPath, "utf-8");
1012
+ } catch (err) {
1013
+ warnSkippedSkill(skillMdPath, `failed to read file: ${err.message}`);
1014
+ return null;
1015
+ }
1016
+ let data;
1017
+ try {
1018
+ ({data} = parseFrontmatter(content));
1019
+ } catch (err) {
1020
+ warnSkippedSkill(skillMdPath, `YAML parse error: ${err.message}`);
1021
+ return null;
1022
+ }
1023
+ if (!data.name || !data.description) {
1024
+ const missing = [];
1025
+ if (!data.name) missing.push("name");
1026
+ if (!data.description) missing.push("description");
1027
+ warnSkippedSkill(skillMdPath, `missing required frontmatter field(s): ${missing.join(", ")}`);
1028
+ return null;
1029
+ }
1030
+ if (typeof data.name !== "string" || typeof data.description !== "string") {
1031
+ warnSkippedSkill(skillMdPath, `frontmatter "name" and "description" must be strings (got ${typeof data.name} and ${typeof data.description})`);
975
1032
  return null;
976
1033
  }
1034
+ const metadata = isRecord(data.metadata) ? data.metadata : void 0;
1035
+ if (metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
1036
+ return {
1037
+ name: sanitizeMetadata(data.name),
1038
+ description: sanitizeMetadata(data.description),
1039
+ path: dirname(skillMdPath),
1040
+ rawContent: content,
1041
+ metadata
1042
+ };
977
1043
  }
978
1044
  async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
979
1045
  if (depth > maxDepth) return [];
@@ -994,6 +1060,7 @@ function isSubpathSafe(basePath, subpath) {
994
1060
  async function discoverSkills(basePath, subpath, options) {
995
1061
  const skills = [];
996
1062
  const seenNames = /* @__PURE__ */ new Set();
1063
+ const parsedSkillPaths = /* @__PURE__ */ new Set();
997
1064
  const localLock = await readLocalLock(basePath);
998
1065
  const lockedSkillNames = new Set(Object.keys(localLock.skills).map(normalizeSkillName));
999
1066
  if (subpath && !isSubpathSafe(basePath, subpath)) throw new Error(`Invalid subpath: "${subpath}" resolves outside the repository directory. Subpath must not contain ".." segments that escape the base path.`);
@@ -1012,8 +1079,14 @@ async function discoverSkills(basePath, subpath, options) {
1012
1079
  const directoryName = normalizeSkillName(basename(skill.path));
1013
1080
  return lockedSkillNames.has(skillName) || lockedSkillNames.has(directoryName);
1014
1081
  };
1082
+ const parseSkillAt = async (skillDir) => {
1083
+ const skillMdPath = resolve(skillDir, "SKILL.md");
1084
+ if (parsedSkillPaths.has(skillMdPath)) return null;
1085
+ parsedSkillPaths.add(skillMdPath);
1086
+ return parseSkillMd(skillMdPath, options);
1087
+ };
1015
1088
  if (await hasSkillMd(searchPath)) {
1016
- let skill = await parseSkillMd(join(searchPath, "SKILL.md"), options);
1089
+ let skill = await parseSkillAt(searchPath);
1017
1090
  if (skill) {
1018
1091
  if (!isInstalledProjectSkill(skill)) {
1019
1092
  skill = enhanceSkill(skill);
@@ -1035,7 +1108,7 @@ async function discoverSkills(basePath, subpath, options) {
1035
1108
  prioritySearchDirs.push(...await getPluginSkillPaths(searchPath));
1036
1109
  const tryAddSkillAt = async (skillDir) => {
1037
1110
  if (!await hasSkillMd(skillDir)) return false;
1038
- let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
1111
+ let skill = await parseSkillAt(skillDir);
1039
1112
  if (!skill || seenNames.has(skill.name)) return true;
1040
1113
  if (isInstalledProjectSkill(skill)) return true;
1041
1114
  skill = enhanceSkill(skill);
@@ -1065,7 +1138,7 @@ async function discoverSkills(basePath, subpath, options) {
1065
1138
  if (skills.length === 0 || options?.fullDepth) {
1066
1139
  const allSkillDirs = await findSkillDirs(searchPath);
1067
1140
  for (const skillDir of allSkillDirs) {
1068
- let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
1141
+ let skill = await parseSkillAt(skillDir);
1069
1142
  if (skill && !seenNames.has(skill.name) && !isInstalledProjectSkill(skill)) {
1070
1143
  skill = enhanceSkill(skill);
1071
1144
  skills.push(skill);
@@ -1093,6 +1166,7 @@ const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude"
1093
1166
  const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
1094
1167
  const hermesHome = process.env.HERMES_HOME?.trim() || join(home, ".hermes");
1095
1168
  const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home, ".autohand");
1169
+ const grokHome = process.env.GROK_HOME?.trim() || join(home, ".grok");
1096
1170
  const zedAppDataHome = process.env.APPDATA?.trim();
1097
1171
  const zedFlatpakConfigHome = process.env.FLATPAK_XDG_CONFIG_HOME?.trim();
1098
1172
  function packageJsonHasDependency(packageJsonPath, dependencyName) {
@@ -1112,6 +1186,9 @@ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
1112
1186
  function isZCodeInstalled(homeDir = home, pathExists = existsSync) {
1113
1187
  return pathExists(join(homeDir, ".zcode")) || pathExists("/Applications/ZCode.app");
1114
1188
  }
1189
+ function isKimchiInstalled(homeDir = home, pathExists = existsSync) {
1190
+ return pathExists(join(homeDir, ".config", "kimchi"));
1191
+ }
1115
1192
  const agents = {
1116
1193
  "aider-desk": {
1117
1194
  name: "aider-desk",
@@ -1395,6 +1472,15 @@ const agents = {
1395
1472
  return existsSync(join(configHome, "goose"));
1396
1473
  }
1397
1474
  },
1475
+ grok: {
1476
+ name: "grok",
1477
+ displayName: "Grok Build",
1478
+ skillsDir: ".grok/skills",
1479
+ globalSkillsDir: join(grokHome, "skills"),
1480
+ detectInstalled: async () => {
1481
+ return existsSync(grokHome);
1482
+ }
1483
+ },
1398
1484
  "hermes-agent": {
1399
1485
  name: "hermes-agent",
1400
1486
  displayName: "Hermes Agent",
@@ -1449,6 +1535,15 @@ const agents = {
1449
1535
  return existsSync(join(home, ".kilocode"));
1450
1536
  }
1451
1537
  },
1538
+ kimchi: {
1539
+ name: "kimchi",
1540
+ displayName: "Kimchi",
1541
+ skillsDir: ".kimchi/skills",
1542
+ globalSkillsDir: join(home, ".config", "kimchi", "harness", "skills"),
1543
+ detectInstalled: async () => {
1544
+ return isKimchiInstalled();
1545
+ }
1546
+ },
1452
1547
  "kimi-code-cli": {
1453
1548
  name: "kimi-code-cli",
1454
1549
  displayName: "Kimi Code CLI",
@@ -2244,7 +2339,7 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
2244
2339
  mode: "symlink"
2245
2340
  };
2246
2341
  if (!isGlobal && !isUniversalAgent(agentType)) {
2247
- if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0]))) return {
2342
+ if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0])) && agentType !== "claude-code") return {
2248
2343
  success: true,
2249
2344
  path: canonicalDir,
2250
2345
  canonicalPath: canonicalDir,
@@ -3165,9 +3260,11 @@ const PRIORITY_PREFIXES = [
3165
3260
  ".continue/skills/",
3166
3261
  ".github/skills/",
3167
3262
  ".goose/skills/",
3263
+ ".grok/skills/",
3168
3264
  ".iflow/skills/",
3169
3265
  ".junie/skills/",
3170
3266
  ".kilocode/skills/",
3267
+ ".kimchi/skills/",
3171
3268
  ".kiro/skills/",
3172
3269
  ".mux/skills/",
3173
3270
  ".neovate/skills/",
@@ -3334,7 +3431,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
3334
3431
  tree
3335
3432
  };
3336
3433
  }
3337
- var version$1 = "1.5.19";
3434
+ var version$1 = "1.5.20";
3338
3435
  const isCancelled$1 = (value) => typeof value === "symbol";
3339
3436
  const EVE_AGENT_LABEL = "eve agent";
3340
3437
  async function isSourcePrivate(source) {
@@ -3885,6 +3982,7 @@ async function runAdd(args, options = {}) {
3885
3982
  spinner$3.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
3886
3983
  const ownerRepoRaw = getOwnerRepo(parsed);
3887
3984
  const repoPrivacyPromise = (() => {
3985
+ if (parsed.type !== "github") return Promise.resolve(null);
3888
3986
  if (!ownerRepoRaw) return Promise.resolve(null);
3889
3987
  const ownerRepo = parseOwnerRepo(ownerRepoRaw);
3890
3988
  if (!ownerRepo) return Promise.resolve(null);
@@ -5155,7 +5253,7 @@ function deriveSkillFolder(skillPath) {
5155
5253
  return folder;
5156
5254
  }
5157
5255
  function supportsAppendedSubpath(source) {
5158
- if (source.startsWith("git@")) return false;
5256
+ if (source.startsWith("git@") || source.startsWith("ssh://")) return false;
5159
5257
  if (source.endsWith(".git")) return false;
5160
5258
  if (source.startsWith("http://") || source.startsWith("https://")) try {
5161
5259
  const host = new URL(source).hostname;
@@ -5173,6 +5271,11 @@ function getLocalSource(entry) {
5173
5271
  if ((entry.sourceType === "git" || entry.sourceType === "gitlab") && isBareShorthand(entry.source)) return null;
5174
5272
  return entry.source;
5175
5273
  }
5274
+ function shouldUseFullDepthForUpdate(entry) {
5275
+ if (!entry.skillPath) return false;
5276
+ const source = entry.sourceType && entry.sourceType !== "github" ? getLocalSource(entry) : entry.source;
5277
+ return source !== null && !supportsAppendedSubpath(source);
5278
+ }
5176
5279
  function appendFolderAndRef(source, skillPath, ref) {
5177
5280
  if (!supportsAppendedSubpath(source)) return formatSourceInput(source, ref);
5178
5281
  const folder = deriveSkillFolder(skillPath);
@@ -5296,18 +5399,26 @@ async function runList(args) {
5296
5399
  global: scope,
5297
5400
  agentFilter
5298
5401
  });
5402
+ const cwd = process.cwd();
5403
+ const lockedSkills = scope ? await getAllLockedSkills() : (await readLocalLock(cwd)).skills;
5404
+ const lockEntriesBySanitizedName = new Map(Object.entries(lockedSkills).map(([name, entry]) => [sanitizeName(name), entry]));
5405
+ const getLockEntry = (skillName) => lockedSkills[skillName] ?? lockEntriesBySanitizedName.get(sanitizeName(skillName));
5299
5406
  if (options.json) {
5300
- const jsonOutput = installedSkills.map((skill) => ({
5301
- name: skill.name,
5302
- path: skill.canonicalPath,
5303
- scope: skill.scope,
5304
- agents: skill.agents.map((a) => agents[a].displayName)
5305
- }));
5407
+ const jsonOutput = installedSkills.map((skill) => {
5408
+ const lockEntry = getLockEntry(skill.name);
5409
+ return {
5410
+ name: skill.name,
5411
+ path: skill.canonicalPath,
5412
+ scope: skill.scope,
5413
+ agents: skill.agents.map((a) => agents[a].displayName),
5414
+ source: lockEntry?.source ?? null,
5415
+ sourceUrl: lockEntry?.sourceUrl ?? null,
5416
+ sourceType: lockEntry?.sourceType ?? null
5417
+ };
5418
+ });
5306
5419
  console.log(JSON.stringify(jsonOutput, null, 2));
5307
5420
  return;
5308
5421
  }
5309
- const lockedSkills = await getAllLockedSkills();
5310
- const cwd = process.cwd();
5311
5422
  const scopeLabel = scope ? "Global" : "Project";
5312
5423
  if (installedSkills.length === 0) {
5313
5424
  if (options.json) {
@@ -5326,14 +5437,17 @@ async function runList(args) {
5326
5437
  const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$2}`;
5327
5438
  const paddedName = sanitizeMetadata(skill.name).padEnd(maxNameLength);
5328
5439
  const paddedPath = shortPath.padEnd(maxPathLength);
5329
- console.log(`${prefix}${CYAN}${paddedName}${RESET$2} ${DIM$2}${paddedPath}${RESET$2} ${DIM$2}Agents:${RESET$2} ${agentInfo}`);
5440
+ const source = getLockEntry(skill.name)?.source ?? null;
5441
+ const sourceLabel = source ? sanitizeMetadata(source) : "local";
5442
+ console.log(`${prefix}${CYAN}${paddedName}${RESET$2} ${DIM$2}${paddedPath}${RESET$2}`);
5443
+ console.log(`${prefix} ${DIM$2}Agents:${RESET$2} ${agentInfo} ${DIM$2}Source:${RESET$2} ${sourceLabel}`);
5330
5444
  }
5331
5445
  console.log(`${BOLD$2}${scopeLabel} Skills${RESET$2}`);
5332
5446
  console.log();
5333
5447
  const groupedSkills = {};
5334
5448
  const ungroupedSkills = [];
5335
5449
  for (const skill of installedSkills) {
5336
- const lockEntry = lockedSkills[skill.name];
5450
+ const lockEntry = getLockEntry(skill.name);
5337
5451
  if (lockEntry?.pluginName) {
5338
5452
  const group = lockEntry.pluginName;
5339
5453
  if (!groupedSkills[group]) groupedSkills[group] = [];
@@ -5385,6 +5499,17 @@ async function runList(args) {
5385
5499
  console.log();
5386
5500
  }
5387
5501
  }
5502
+ function resolveSkillsToRemove(requested, folderNames, lockKeys = []) {
5503
+ const identityBySanitized = /* @__PURE__ */ new Map();
5504
+ for (const folder of folderNames) identityBySanitized.set(sanitizeName(folder), folder);
5505
+ for (const key of lockKeys) identityBySanitized.set(sanitizeName(key), key);
5506
+ const matched = /* @__PURE__ */ new Set();
5507
+ for (const name of requested) {
5508
+ const hit = identityBySanitized.get(sanitizeName(name));
5509
+ if (hit) matched.add(hit);
5510
+ }
5511
+ return Array.from(matched);
5512
+ }
5388
5513
  async function removeCommand(skillNames, options) {
5389
5514
  const agentResult = await detectAgent();
5390
5515
  if (agentResult.isAgent) {
@@ -5414,7 +5539,10 @@ async function removeCommand(skillNames, options) {
5414
5539
  }
5415
5540
  const installedSkills = Array.from(skillNamesSet).sort();
5416
5541
  spinner$1.stop(`Found ${installedSkills.length} unique installed skill(s)`);
5417
- if (installedSkills.length === 0) {
5542
+ const lockSkillsKeys = isGlobal ? Object.keys((await readSkillLock()).skills) : Object.keys((await readLocalLock(cwd)).skills);
5543
+ const requestedSkills = options.all ? [...installedSkills, ...lockSkillsKeys] : skillNames;
5544
+ const resolvedRequestedSkills = options.all || skillNames.length > 0 ? resolveSkillsToRemove(requestedSkills, installedSkills, lockSkillsKeys) : [];
5545
+ if (installedSkills.length === 0 && resolvedRequestedSkills.length === 0) {
5418
5546
  outro(import_picocolors.default.yellow("No skills found to remove."));
5419
5547
  return;
5420
5548
  }
@@ -5428,9 +5556,9 @@ async function removeCommand(skillNames, options) {
5428
5556
  }
5429
5557
  }
5430
5558
  let selectedSkills = [];
5431
- if (options.all) selectedSkills = installedSkills;
5559
+ if (options.all) selectedSkills = resolvedRequestedSkills;
5432
5560
  else if (skillNames.length > 0) {
5433
- selectedSkills = installedSkills.filter((s) => skillNames.some((name) => name.toLowerCase() === s.toLowerCase()));
5561
+ selectedSkills = resolvedRequestedSkills;
5434
5562
  if (selectedSkills.length === 0) {
5435
5563
  log.error(`No matching skills found for: ${skillNames.join(", ")}`);
5436
5564
  return;
@@ -5449,7 +5577,7 @@ async function removeCommand(skillNames, options) {
5449
5577
  cancel("Removal cancelled");
5450
5578
  process.exit(0);
5451
5579
  }
5452
- selectedSkills = selected;
5580
+ selectedSkills = resolveSkillsToRemove(selected, installedSkills, lockSkillsKeys);
5453
5581
  }
5454
5582
  let targetAgents;
5455
5583
  if (options.agent && options.agent.length > 0) targetAgents = options.agent;
@@ -5513,10 +5641,19 @@ async function removeCommand(skillNames, options) {
5513
5641
  recursive: true,
5514
5642
  force: true
5515
5643
  });
5516
- const lockEntry = isGlobal ? await getSkillFromLock(skillName) : null;
5517
- const effectiveSource = lockEntry?.source || "local";
5518
- const effectiveSourceType = lockEntry?.sourceType || "local";
5519
- if (isGlobal) await removeSkillFromLock(skillName);
5644
+ let effectiveSource = "local";
5645
+ let effectiveSourceType = "local";
5646
+ if (isGlobal) {
5647
+ const lockEntry = await getSkillFromLock(skillName);
5648
+ effectiveSource = lockEntry?.source || "local";
5649
+ effectiveSourceType = lockEntry?.sourceType || "local";
5650
+ await removeSkillFromLock(skillName);
5651
+ } else {
5652
+ const lockEntry = (await readLocalLock(cwd)).skills[skillName];
5653
+ effectiveSource = lockEntry?.source || "local";
5654
+ effectiveSourceType = lockEntry?.sourceType || "local";
5655
+ await removeSkillFromLocalLock(skillName, cwd);
5656
+ }
5520
5657
  results.push({
5521
5658
  skill: skillName,
5522
5659
  success: true,
@@ -5786,7 +5923,7 @@ async function updateGlobalSkills(options = {}) {
5786
5923
  console.log(` ${DIM$1}✗ Failed to fetch tree for ${source}${RESET$1}`);
5787
5924
  continue;
5788
5925
  }
5789
- const discoveredPaths = findSkillMdPaths(tree);
5926
+ const discoveredPaths = tree.tree.filter((entry) => entry.type === "blob").map((entry) => entry.path);
5790
5927
  const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
5791
5928
  const deletedSkillSet = new Set(deletedSkills);
5792
5929
  for (const { name: skillName, entry } of itemsForSource) {
@@ -5801,7 +5938,7 @@ async function updateGlobalSkills(options = {}) {
5801
5938
  continue;
5802
5939
  }
5803
5940
  tempDir = await cloneRepo(sourceUrl, firstEntry.ref);
5804
- const discoveredPaths = (await discoverSkills(tempDir)).map((skill) => {
5941
+ const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((skill) => {
5805
5942
  return join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/");
5806
5943
  });
5807
5944
  const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
@@ -5866,10 +6003,12 @@ async function updateGlobalSkills(options = {}) {
5866
6003
  console.log(` ${DIM$1}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET$1}`);
5867
6004
  continue;
5868
6005
  }
6006
+ const fullDepthArgs = shouldUseFullDepthForUpdate(update.entry) ? ["--full-depth"] : [];
5869
6007
  if (spawnSync(process.execPath, [
5870
6008
  cliEntry,
5871
6009
  "add",
5872
6010
  installUrl,
6011
+ ...fullDepthArgs,
5873
6012
  "-g",
5874
6013
  "-y"
5875
6014
  ], {
@@ -5967,7 +6106,7 @@ async function updateProjectSkills(options = {}) {
5967
6106
  }
5968
6107
  try {
5969
6108
  tempDir = await cloneRepo(sourceUrl, ref);
5970
- const discoveredPaths = (await discoverSkills(tempDir)).map((s) => {
6109
+ const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((s) => {
5971
6110
  return join(relative(tempDir, s.path), "SKILL.md").split(sep).join("/");
5972
6111
  });
5973
6112
  deletedSkills = await checkAndPromptForDeletions(source, allLockedForSource, localLock.skills, false, options, discoveredPaths);
@@ -5987,6 +6126,7 @@ async function updateProjectSkills(options = {}) {
5987
6126
  continue;
5988
6127
  }
5989
6128
  const subagentArgs = skill.entry.subagents?.length ? ["--subagent", ...skill.entry.subagents.map((s) => s === "" ? "root" : s)] : [];
6129
+ const fullDepthArgs = shouldUseFullDepthForUpdate(skill.entry) ? ["--full-depth"] : [];
5990
6130
  if (spawnSync(process.execPath, [
5991
6131
  cliEntry,
5992
6132
  "add",
@@ -5994,6 +6134,7 @@ async function updateProjectSkills(options = {}) {
5994
6134
  "--skill",
5995
6135
  skill.name,
5996
6136
  ...subagentArgs,
6137
+ ...fullDepthArgs,
5997
6138
  "-y"
5998
6139
  ], {
5999
6140
  stdio: [
@@ -6674,6 +6815,11 @@ async function main() {
6674
6815
  }
6675
6816
  const command = args[0];
6676
6817
  const restArgs = args.slice(1);
6818
+ if (command !== "--help" && command !== "-h" && command !== "--version" && command !== "-v" && (restArgs.includes("--help") || restArgs.includes("-h"))) {
6819
+ if (command === "remove" || command === "rm" || command === "r") showRemoveHelp();
6820
+ else showHelp();
6821
+ return;
6822
+ }
6677
6823
  switch (command) {
6678
6824
  case "find":
6679
6825
  case "search":
@@ -6713,14 +6859,11 @@ async function main() {
6713
6859
  }
6714
6860
  case "remove":
6715
6861
  case "rm":
6716
- case "r":
6717
- if (restArgs.includes("--help") || restArgs.includes("-h")) {
6718
- showRemoveHelp();
6719
- break;
6720
- }
6862
+ case "r": {
6721
6863
  const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
6722
6864
  await removeCommand(skills, removeOptions);
6723
6865
  break;
6866
+ }
6724
6867
  case "experimental_sync": {
6725
6868
  if (!inAgent) showLogo();
6726
6869
  const { options: syncOptions } = parseSyncOptions(restArgs);
@@ -6747,6 +6890,7 @@ async function main() {
6747
6890
  default:
6748
6891
  console.log(`Unknown command: ${command}`);
6749
6892
  console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
6893
+ process.exitCode = 1;
6750
6894
  }
6751
6895
  }
6752
6896
  main().finally(() => flushTelemetry().then(() => process.exit(process.exitCode ?? 0)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.5.19",
3
+ "version": "1.5.20",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -67,12 +67,14 @@
67
67
  "gemini-cli",
68
68
  "github-copilot",
69
69
  "goose",
70
+ "grok",
70
71
  "hermes-agent",
71
72
  "inference-sh",
72
73
  "jazz",
73
74
  "junie",
74
75
  "iflow-cli",
75
76
  "kilo",
77
+ "kimchi",
76
78
  "kimi-code-cli",
77
79
  "kiro-cli",
78
80
  "kode",