skills 1.4.5 → 1.4.7

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 +8 -4
  2. package/dist/cli.mjs +186 -67
  3. package/package.json +6 -3
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 [38 more](#available-agents).
6
+ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [41 more](#available-agents).
7
7
  <!-- agent-list:end -->
8
8
 
9
9
  ## Install a Skill
@@ -39,7 +39,7 @@ npx skills add ./my-local-skills
39
39
  | Option | Description |
40
40
  | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
41
41
  | `-g, --global` | Install to user directory instead of project |
42
- | `-a, --agent <agents...>` | <!-- agent-names:start -->Target specific agents (e.g., `claude-code`, `codex`). See [Available Agents](#available-agents)<!-- agent-names:end --> |
42
+ | `-a, --agent <agents...>` | <!-- agent-names:start -->Target specific agents (e.g., `claude-code`, `codex`). See [Available Agents](#available-agents)<!-- agent-names:end --> |
43
43
  | `-s, --skill <skills...>` | Install specific skills by name (use `'*'` for all skills) |
44
44
  | `-l, --list` | List available skills without installing |
45
45
  | `--copy` | Copy files instead of symlinking to agent directories |
@@ -210,8 +210,9 @@ Skills can be installed to any of these agents:
210
210
  | Agent | `--agent` | Project Path | Global Path |
211
211
  |-------|-----------|--------------|-------------|
212
212
  | Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` |
213
- | Antigravity | `antigravity` | `.agent/skills/` | `~/.gemini/antigravity/skills/` |
213
+ | Antigravity | `antigravity` | `.agents/skills/` | `~/.gemini/antigravity/skills/` |
214
214
  | Augment | `augment` | `.augment/skills/` | `~/.augment/skills/` |
215
+ | IBM Bob | `bob` | `.bob/skills/` | `~/.bob/skills/` |
215
216
  | Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
216
217
  | OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` |
217
218
  | Cline, Warp | `cline`, `warp` | `.agents/skills/` | `~/.agents/skills/` |
@@ -222,7 +223,9 @@ Skills can be installed to any of these agents:
222
223
  | Cortex Code | `cortex` | `.cortex/skills/` | `~/.snowflake/cortex/skills/` |
223
224
  | Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` |
224
225
  | Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` |
226
+ | Deep Agents | `deepagents` | `.agents/skills/` | `~/.deepagents/agent/skills/` |
225
227
  | Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
228
+ | Firebender | `firebender` | `.agents/skills/` | `~/.firebender/skills/` |
226
229
  | Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
227
230
  | GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
228
231
  | Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
@@ -317,8 +320,8 @@ The CLI searches for skills in these locations within a repository:
317
320
  - `skills/.experimental/`
318
321
  - `skills/.system/`
319
322
  - `.agents/skills/`
320
- - `.agent/skills/`
321
323
  - `.augment/skills/`
324
+ - `.bob/skills/`
322
325
  - `.claude/skills/`
323
326
  - `./skills/`
324
327
  - `.codebuddy/skills/`
@@ -433,6 +436,7 @@ Telemetry is automatically disabled in CI environments.
433
436
  - [Command Code Skills Documentation](https://commandcode.ai/docs/skills)
434
437
  - [Crush Skills Documentation](https://github.com/charmbracelet/crush?tab=readme-ov-file#agent-skills)
435
438
  - [Cursor Skills Documentation](https://cursor.com/docs/context/skills)
439
+ - [Firebender Skills Documentation](https://docs.firebender.com/multi-agent/skills)
436
440
  - [Gemini CLI Skills Documentation](https://geminicli.com/docs/cli/skills/)
437
441
  - [GitHub Copilot Agent Skills](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills)
438
442
  - [iFlow CLI Skills Documentation](https://platform.iflow.cn/en/cli/examples/skill)
package/dist/cli.mjs CHANGED
@@ -9,15 +9,15 @@ import { t as require_gray_matter } from "./_chunks/libs/gray-matter.mjs";
9
9
  import "./_chunks/libs/extend-shallow.mjs";
10
10
  import "./_chunks/libs/esprima.mjs";
11
11
  import { t as xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
12
- import { execSync, spawn, spawnSync } from "child_process";
12
+ import { execSync, spawnSync } from "child_process";
13
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
14
14
  import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
15
15
  import { homedir, platform, tmpdir } from "os";
16
- import { createHash } from "crypto";
17
16
  import { fileURLToPath } from "url";
18
17
  import * as readline from "readline";
19
18
  import { Writable } from "stream";
20
19
  import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
20
+ import { createHash } from "crypto";
21
21
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
22
22
  function getOwnerRepo(parsed) {
23
23
  if (parsed.type === "local") return null;
@@ -62,13 +62,48 @@ function isLocalPath(input) {
62
62
  return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
63
63
  }
64
64
  const SOURCE_ALIASES = { "coinbase/agentWallet": "coinbase/agentic-wallet-skills" };
65
+ function decodeFragmentValue(value) {
66
+ try {
67
+ return decodeURIComponent(value);
68
+ } catch {
69
+ return value;
70
+ }
71
+ }
72
+ function looksLikeGitSource(input) {
73
+ if (input.startsWith("github:") || input.startsWith("gitlab:") || input.startsWith("git@")) return true;
74
+ if (input.startsWith("http://") || input.startsWith("https://")) try {
75
+ const parsed = new URL(input);
76
+ const pathname = parsed.pathname;
77
+ if (parsed.hostname === "github.com") return /^\/[^/]+\/[^/]+(?:\.git)?(?:\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
78
+ if (parsed.hostname === "gitlab.com") return /^\/.+?\/[^/]+(?:\.git)?(?:\/-\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
79
+ } catch {}
80
+ if (/^https?:\/\/.+\.git(?:$|[/?])/i.test(input)) return true;
81
+ return !input.includes(":") && !input.startsWith(".") && !input.startsWith("/") && /^([^/]+)\/([^/]+)(?:\/(.+)|@(.+))?$/.test(input);
82
+ }
83
+ function parseFragmentRef(input) {
84
+ const hashIndex = input.indexOf("#");
85
+ if (hashIndex < 0) return { inputWithoutFragment: input };
86
+ const inputWithoutFragment = input.slice(0, hashIndex);
87
+ const fragment = input.slice(hashIndex + 1);
88
+ if (!fragment || !looksLikeGitSource(inputWithoutFragment)) return { inputWithoutFragment: input };
89
+ const atIndex = fragment.indexOf("@");
90
+ if (atIndex === -1) return {
91
+ inputWithoutFragment,
92
+ ref: decodeFragmentValue(fragment)
93
+ };
94
+ const ref = fragment.slice(0, atIndex);
95
+ const skillFilter = fragment.slice(atIndex + 1);
96
+ return {
97
+ inputWithoutFragment,
98
+ ref: ref ? decodeFragmentValue(ref) : void 0,
99
+ skillFilter: skillFilter ? decodeFragmentValue(skillFilter) : void 0
100
+ };
101
+ }
102
+ function appendFragmentRef(input, ref, skillFilter) {
103
+ if (!ref) return input;
104
+ return `${input}#${ref}${skillFilter ? `@${skillFilter}` : ""}`;
105
+ }
65
106
  function parseSource(input) {
66
- const alias = SOURCE_ALIASES[input];
67
- if (alias) input = alias;
68
- const githubPrefixMatch = input.match(/^github:(.+)$/);
69
- if (githubPrefixMatch) return parseSource(githubPrefixMatch[1]);
70
- const gitlabPrefixMatch = input.match(/^gitlab:(.+)$/);
71
- if (gitlabPrefixMatch) return parseSource(`https://gitlab.com/${gitlabPrefixMatch[1]}`);
72
107
  if (isLocalPath(input)) {
73
108
  const resolvedPath = resolve(input);
74
109
  return {
@@ -77,13 +112,21 @@ function parseSource(input) {
77
112
  localPath: resolvedPath
78
113
  };
79
114
  }
115
+ const { inputWithoutFragment, ref: fragmentRef, skillFilter: fragmentSkillFilter } = parseFragmentRef(input);
116
+ input = inputWithoutFragment;
117
+ const alias = SOURCE_ALIASES[input];
118
+ if (alias) input = alias;
119
+ const githubPrefixMatch = input.match(/^github:(.+)$/);
120
+ if (githubPrefixMatch) return parseSource(appendFragmentRef(githubPrefixMatch[1], fragmentRef, fragmentSkillFilter));
121
+ const gitlabPrefixMatch = input.match(/^gitlab:(.+)$/);
122
+ if (gitlabPrefixMatch) return parseSource(appendFragmentRef(`https://gitlab.com/${gitlabPrefixMatch[1]}`, fragmentRef, fragmentSkillFilter));
80
123
  const githubTreeWithPathMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
81
124
  if (githubTreeWithPathMatch) {
82
125
  const [, owner, repo, ref, subpath] = githubTreeWithPathMatch;
83
126
  return {
84
127
  type: "github",
85
128
  url: `https://github.com/${owner}/${repo}.git`,
86
- ref,
129
+ ref: ref || fragmentRef,
87
130
  subpath: subpath ? sanitizeSubpath(subpath) : subpath
88
131
  };
89
132
  }
@@ -93,7 +136,7 @@ function parseSource(input) {
93
136
  return {
94
137
  type: "github",
95
138
  url: `https://github.com/${owner}/${repo}.git`,
96
- ref
139
+ ref: ref || fragmentRef
97
140
  };
98
141
  }
99
142
  const githubRepoMatch = input.match(/github\.com\/([^/]+)\/([^/]+)/);
@@ -101,7 +144,8 @@ function parseSource(input) {
101
144
  const [, owner, repo] = githubRepoMatch;
102
145
  return {
103
146
  type: "github",
104
- url: `https://github.com/${owner}/${repo.replace(/\.git$/, "")}.git`
147
+ url: `https://github.com/${owner}/${repo.replace(/\.git$/, "")}.git`,
148
+ ...fragmentRef ? { ref: fragmentRef } : {}
105
149
  };
106
150
  }
107
151
  const gitlabTreeWithPathMatch = input.match(/^(https?):\/\/([^/]+)\/(.+?)\/-\/tree\/([^/]+)\/(.+)/);
@@ -110,7 +154,7 @@ function parseSource(input) {
110
154
  if (hostname !== "github.com" && repoPath) return {
111
155
  type: "gitlab",
112
156
  url: `${protocol}://${hostname}/${repoPath.replace(/\.git$/, "")}.git`,
113
- ref,
157
+ ref: ref || fragmentRef,
114
158
  subpath: subpath ? sanitizeSubpath(subpath) : subpath
115
159
  };
116
160
  }
@@ -120,7 +164,7 @@ function parseSource(input) {
120
164
  if (hostname !== "github.com" && repoPath) return {
121
165
  type: "gitlab",
122
166
  url: `${protocol}://${hostname}/${repoPath.replace(/\.git$/, "")}.git`,
123
- ref
167
+ ref: ref || fragmentRef
124
168
  };
125
169
  }
126
170
  const gitlabRepoMatch = input.match(/gitlab\.com\/(.+?)(?:\.git)?\/?$/);
@@ -128,7 +172,8 @@ function parseSource(input) {
128
172
  const repoPath = gitlabRepoMatch[1];
129
173
  if (repoPath.includes("/")) return {
130
174
  type: "gitlab",
131
- url: `https://gitlab.com/${repoPath}.git`
175
+ url: `https://gitlab.com/${repoPath}.git`,
176
+ ...fragmentRef ? { ref: fragmentRef } : {}
132
177
  };
133
178
  }
134
179
  const atSkillMatch = input.match(/^([^/]+)\/([^/@]+)@(.+)$/);
@@ -137,16 +182,19 @@ function parseSource(input) {
137
182
  return {
138
183
  type: "github",
139
184
  url: `https://github.com/${owner}/${repo}.git`,
140
- skillFilter
185
+ ...fragmentRef ? { ref: fragmentRef } : {},
186
+ skillFilter: fragmentSkillFilter || skillFilter
141
187
  };
142
188
  }
143
- const shorthandMatch = input.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
189
+ const shorthandMatch = input.match(/^([^/]+)\/([^/]+)(?:\/(.+?))?\/?$/);
144
190
  if (shorthandMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
145
191
  const [, owner, repo, subpath] = shorthandMatch;
146
192
  return {
147
193
  type: "github",
148
194
  url: `https://github.com/${owner}/${repo}.git`,
149
- subpath: subpath ? sanitizeSubpath(subpath) : subpath
195
+ ...fragmentRef ? { ref: fragmentRef } : {},
196
+ subpath: subpath ? sanitizeSubpath(subpath) : subpath,
197
+ ...fragmentSkillFilter ? { skillFilter: fragmentSkillFilter } : {}
150
198
  };
151
199
  }
152
200
  if (isWellKnownUrl(input)) return {
@@ -155,7 +203,8 @@ function parseSource(input) {
155
203
  };
156
204
  return {
157
205
  type: "git",
158
- url: input
206
+ url: input,
207
+ ...fragmentRef ? { ref: fragmentRef } : {}
159
208
  };
160
209
  }
161
210
  function isWellKnownUrl(input) {
@@ -533,7 +582,6 @@ async function discoverSkills(basePath, subpath, options) {
533
582
  join(searchPath, "skills/.curated"),
534
583
  join(searchPath, "skills/.experimental"),
535
584
  join(searchPath, "skills/.system"),
536
- join(searchPath, ".agent/skills"),
537
585
  join(searchPath, ".agents/skills"),
538
586
  join(searchPath, ".claude/skills"),
539
587
  join(searchPath, ".cline/skills"),
@@ -620,7 +668,7 @@ const agents = {
620
668
  antigravity: {
621
669
  name: "antigravity",
622
670
  displayName: "Antigravity",
623
- skillsDir: ".agent/skills",
671
+ skillsDir: ".agents/skills",
624
672
  globalSkillsDir: join(home, ".gemini/antigravity/skills"),
625
673
  detectInstalled: async () => {
626
674
  return existsSync(join(home, ".gemini/antigravity"));
@@ -635,6 +683,15 @@ const agents = {
635
683
  return existsSync(join(home, ".augment"));
636
684
  }
637
685
  },
686
+ bob: {
687
+ name: "bob",
688
+ displayName: "IBM Bob",
689
+ skillsDir: ".bob/skills",
690
+ globalSkillsDir: join(home, ".bob/skills"),
691
+ detectInstalled: async () => {
692
+ return existsSync(join(home, ".bob"));
693
+ }
694
+ },
638
695
  "claude-code": {
639
696
  name: "claude-code",
640
697
  displayName: "Claude Code",
@@ -725,6 +782,15 @@ const agents = {
725
782
  return existsSync(join(home, ".cursor"));
726
783
  }
727
784
  },
785
+ deepagents: {
786
+ name: "deepagents",
787
+ displayName: "Deep Agents",
788
+ skillsDir: ".agents/skills",
789
+ globalSkillsDir: join(home, ".deepagents/agent/skills"),
790
+ detectInstalled: async () => {
791
+ return existsSync(join(home, ".deepagents"));
792
+ }
793
+ },
728
794
  droid: {
729
795
  name: "droid",
730
796
  displayName: "Droid",
@@ -734,6 +800,15 @@ const agents = {
734
800
  return existsSync(join(home, ".factory"));
735
801
  }
736
802
  },
803
+ firebender: {
804
+ name: "firebender",
805
+ displayName: "Firebender",
806
+ skillsDir: ".agents/skills",
807
+ globalSkillsDir: join(home, ".firebender/skills"),
808
+ detectInstalled: async () => {
809
+ return existsSync(join(home, ".firebender"));
810
+ }
811
+ },
737
812
  "gemini-cli": {
738
813
  name: "gemini-cli",
739
814
  displayName: "Gemini CLI",
@@ -1310,6 +1385,19 @@ async function listInstalledSkills(options = {}) {
1310
1385
  agentType
1311
1386
  });
1312
1387
  }
1388
+ const allAgentTypes = Object.keys(agents);
1389
+ for (const agentType of allAgentTypes) {
1390
+ if (agentsToCheck.includes(agentType)) continue;
1391
+ const agent = agents[agentType];
1392
+ if (isGlobal && agent.globalSkillsDir === void 0) continue;
1393
+ const agentDir = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
1394
+ if (scopes.some((s) => s.path === agentDir && s.global === isGlobal)) continue;
1395
+ if (existsSync(agentDir)) scopes.push({
1396
+ global: isGlobal,
1397
+ path: agentDir,
1398
+ agentType
1399
+ });
1400
+ }
1313
1401
  }
1314
1402
  for (const scope of scopes) try {
1315
1403
  const entries = await readdir(scope.path, { withFileTypes: true });
@@ -1452,7 +1540,7 @@ new ProviderRegistryImpl();
1452
1540
  var WellKnownProvider = class {
1453
1541
  id = "well-known";
1454
1542
  displayName = "Well-Known Skills";
1455
- WELL_KNOWN_PATH = ".well-known/skills";
1543
+ WELL_KNOWN_PATHS = [".well-known/agent-skills", ".well-known/skills"];
1456
1544
  INDEX_FILE = "index.json";
1457
1545
  match(url) {
1458
1546
  if (!url.startsWith("http://") && !url.startsWith("https://")) return { matches: false };
@@ -1475,15 +1563,20 @@ var WellKnownProvider = class {
1475
1563
  try {
1476
1564
  const parsed = new URL(baseUrl);
1477
1565
  const basePath = parsed.pathname.replace(/\/$/, "");
1478
- const urlsToTry = [{
1479
- indexUrl: `${parsed.protocol}//${parsed.host}${basePath}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`,
1480
- baseUrl: `${parsed.protocol}//${parsed.host}${basePath}`
1481
- }];
1482
- if (basePath && basePath !== "") urlsToTry.push({
1483
- indexUrl: `${parsed.protocol}//${parsed.host}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`,
1484
- baseUrl: `${parsed.protocol}//${parsed.host}`
1485
- });
1486
- for (const { indexUrl, baseUrl: resolvedBase } of urlsToTry) try {
1566
+ const urlsToTry = [];
1567
+ for (const wellKnownPath of this.WELL_KNOWN_PATHS) {
1568
+ urlsToTry.push({
1569
+ indexUrl: `${parsed.protocol}//${parsed.host}${basePath}/${wellKnownPath}/${this.INDEX_FILE}`,
1570
+ baseUrl: `${parsed.protocol}//${parsed.host}${basePath}`,
1571
+ wellKnownPath
1572
+ });
1573
+ if (basePath && basePath !== "") urlsToTry.push({
1574
+ indexUrl: `${parsed.protocol}//${parsed.host}/${wellKnownPath}/${this.INDEX_FILE}`,
1575
+ baseUrl: `${parsed.protocol}//${parsed.host}`,
1576
+ wellKnownPath
1577
+ });
1578
+ }
1579
+ for (const { indexUrl, baseUrl: resolvedBase, wellKnownPath } of urlsToTry) try {
1487
1580
  const response = await fetch(indexUrl);
1488
1581
  if (!response.ok) continue;
1489
1582
  const index = await response.json();
@@ -1495,7 +1588,8 @@ var WellKnownProvider = class {
1495
1588
  }
1496
1589
  if (allValid) return {
1497
1590
  index,
1498
- resolvedBaseUrl: resolvedBase
1591
+ resolvedBaseUrl: resolvedBase,
1592
+ resolvedWellKnownPath: wellKnownPath
1499
1593
  };
1500
1594
  } catch {
1501
1595
  continue;
@@ -1526,22 +1620,23 @@ var WellKnownProvider = class {
1526
1620
  const parsed = new URL(url);
1527
1621
  const result = await this.fetchIndex(url);
1528
1622
  if (!result) return null;
1529
- const { index, resolvedBaseUrl } = result;
1623
+ const { index, resolvedBaseUrl, resolvedWellKnownPath } = result;
1530
1624
  let skillName = null;
1531
- const pathMatch = parsed.pathname.match(/\/.well-known\/skills\/([^/]+)\/?$/);
1625
+ const pathMatch = parsed.pathname.match(/\/.well-known\/(?:agent-skills|skills)\/([^/]+)\/?$/);
1532
1626
  if (pathMatch && pathMatch[1] && pathMatch[1] !== "index.json") skillName = pathMatch[1];
1533
1627
  else if (index.skills.length === 1) skillName = index.skills[0].name;
1534
1628
  if (!skillName) return null;
1535
1629
  const skillEntry = index.skills.find((s) => s.name === skillName);
1536
1630
  if (!skillEntry) return null;
1537
- return this.fetchSkillByEntry(resolvedBaseUrl, skillEntry);
1631
+ return this.fetchSkillByEntry(resolvedBaseUrl, skillEntry, resolvedWellKnownPath);
1538
1632
  } catch {
1539
1633
  return null;
1540
1634
  }
1541
1635
  }
1542
- async fetchSkillByEntry(baseUrl, entry) {
1636
+ async fetchSkillByEntry(baseUrl, entry, wellKnownPath) {
1543
1637
  try {
1544
- const skillBaseUrl = `${baseUrl.replace(/\/$/, "")}/${this.WELL_KNOWN_PATH}/${entry.name}`;
1638
+ const resolvedPath = wellKnownPath ?? this.WELL_KNOWN_PATHS[0];
1639
+ const skillBaseUrl = `${baseUrl.replace(/\/$/, "")}/${resolvedPath}/${entry.name}`;
1545
1640
  const skillMdUrl = `${skillBaseUrl}/SKILL.md`;
1546
1641
  const response = await fetch(skillMdUrl);
1547
1642
  if (!response.ok) return null;
@@ -1581,8 +1676,8 @@ var WellKnownProvider = class {
1581
1676
  try {
1582
1677
  const result = await this.fetchIndex(url);
1583
1678
  if (!result) return [];
1584
- const { index, resolvedBaseUrl } = result;
1585
- const skillPromises = index.skills.map((entry) => this.fetchSkillByEntry(resolvedBaseUrl, entry));
1679
+ const { index, resolvedBaseUrl, resolvedWellKnownPath } = result;
1680
+ const skillPromises = index.skills.map((entry) => this.fetchSkillByEntry(resolvedBaseUrl, entry, resolvedWellKnownPath));
1586
1681
  return (await Promise.all(skillPromises)).filter((s) => s !== null);
1587
1682
  } catch {
1588
1683
  return [];
@@ -1592,13 +1687,14 @@ var WellKnownProvider = class {
1592
1687
  try {
1593
1688
  const parsed = new URL(url);
1594
1689
  if (url.toLowerCase().endsWith("/skill.md")) return url;
1595
- const pathMatch = parsed.pathname.match(/\/.well-known\/skills\/([^/]+)\/?$/);
1690
+ const primaryPath = this.WELL_KNOWN_PATHS[0];
1691
+ const pathMatch = parsed.pathname.match(/\/.well-known\/(?:agent-skills|skills)\/([^/]+)\/?$/);
1596
1692
  if (pathMatch && pathMatch[1]) {
1597
- const basePath = parsed.pathname.replace(/\/.well-known\/skills\/.*$/, "");
1598
- return `${parsed.protocol}//${parsed.host}${basePath}/${this.WELL_KNOWN_PATH}/${pathMatch[1]}/SKILL.md`;
1693
+ const basePath = parsed.pathname.replace(/\/.well-known\/(?:agent-skills|skills)\/.*$/, "");
1694
+ return `${parsed.protocol}//${parsed.host}${basePath}/${primaryPath}/${pathMatch[1]}/SKILL.md`;
1599
1695
  }
1600
1696
  const basePath = parsed.pathname.replace(/\/$/, "");
1601
- return `${parsed.protocol}//${parsed.host}${basePath}/${this.WELL_KNOWN_PATH}/${this.INDEX_FILE}`;
1697
+ return `${parsed.protocol}//${parsed.host}${basePath}/${primaryPath}/${this.INDEX_FILE}`;
1602
1698
  } catch {
1603
1699
  return url;
1604
1700
  }
@@ -1656,13 +1752,14 @@ function getGitHubToken() {
1656
1752
  } catch {}
1657
1753
  return null;
1658
1754
  }
1659
- async function fetchSkillFolderHash(ownerRepo, skillPath, token) {
1755
+ async function fetchSkillFolderHash(ownerRepo, skillPath, token, ref) {
1660
1756
  let folderPath = skillPath.replace(/\\/g, "/");
1661
1757
  if (folderPath.endsWith("/SKILL.md")) folderPath = folderPath.slice(0, -9);
1662
1758
  else if (folderPath.endsWith("SKILL.md")) folderPath = folderPath.slice(0, -8);
1663
1759
  if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
1664
- for (const branch of ["main", "master"]) try {
1665
- const url = `https://api.github.com/repos/${ownerRepo}/git/trees/${branch}?recursive=1`;
1760
+ const branches = ref ? [ref] : ["main", "master"];
1761
+ for (const branch of branches) try {
1762
+ const url = `https://api.github.com/repos/${ownerRepo}/git/trees/${encodeURIComponent(branch)}?recursive=1`;
1666
1763
  const headers = {
1667
1764
  Accept: "application/vnd.github.v3+json",
1668
1765
  "User-Agent": "skills-cli"
@@ -1793,7 +1890,7 @@ function createEmptyLocalLock() {
1793
1890
  skills: {}
1794
1891
  };
1795
1892
  }
1796
- var version$1 = "1.4.5";
1893
+ var version$1 = "1.4.7";
1797
1894
  const isCancelled$1 = (value) => typeof value === "symbol";
1798
1895
  async function isSourcePrivate(source) {
1799
1896
  const ownerRepo = parseOwnerRepo(source);
@@ -1964,7 +2061,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
1964
2061
  const skills = await wellKnownProvider.fetchAllSkills(url);
1965
2062
  if (skills.length === 0) {
1966
2063
  spinner.stop(import_picocolors.default.red("No skills found"));
1967
- Se(import_picocolors.default.red("No skills found at this URL. Make sure the server has a /.well-known/skills/index.json file."));
2064
+ Se(import_picocolors.default.red("No skills found at this URL. Make sure the server has a /.well-known/agent-skills/index.json or /.well-known/skills/index.json file."));
1968
2065
  process.exit(1);
1969
2066
  }
1970
2067
  spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
@@ -2625,13 +2722,14 @@ async function runAdd(args, options = {}) {
2625
2722
  let skillFolderHash = "";
2626
2723
  const skillPathValue = skillFiles[skill.name];
2627
2724
  if (parsed.type === "github" && skillPathValue) {
2628
- const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue, getGitHubToken());
2725
+ const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue, getGitHubToken(), parsed.ref);
2629
2726
  if (hash) skillFolderHash = hash;
2630
2727
  }
2631
2728
  await addSkillToLock(skill.name, {
2632
2729
  source: lockSource || normalizedSource,
2633
2730
  sourceType: parsed.type,
2634
2731
  sourceUrl: parsed.url,
2732
+ ref: parsed.ref,
2635
2733
  skillPath: skillPathValue,
2636
2734
  skillFolderHash,
2637
2735
  pluginName: skill.pluginName
@@ -2647,6 +2745,7 @@ async function runAdd(args, options = {}) {
2647
2745
  const computedHash = await computeSkillFolderHash(skill.path);
2648
2746
  await addSkillToLocalLock(skill.name, {
2649
2747
  source: lockSource || parsed.url,
2748
+ ref: parsed.ref,
2650
2749
  sourceType: parsed.type,
2651
2750
  computedHash
2652
2751
  }, cwd);
@@ -3337,9 +3436,10 @@ async function runInstallFromLock(args) {
3337
3436
  nodeModuleSkills.push(skillName);
3338
3437
  continue;
3339
3438
  }
3340
- const existing = bySource.get(entry.source);
3439
+ const installSource = entry.ref ? `${entry.source}#${entry.ref}` : entry.source;
3440
+ const existing = bySource.get(installSource);
3341
3441
  if (existing) existing.skills.push(skillName);
3342
- else bySource.set(entry.source, {
3442
+ else bySource.set(installSource, {
3343
3443
  sourceType: entry.sourceType,
3344
3444
  skills: [skillName]
3345
3445
  });
@@ -3669,6 +3769,20 @@ function parseRemoveOptions(args) {
3669
3769
  options
3670
3770
  };
3671
3771
  }
3772
+ function formatSourceInput(sourceUrl, ref) {
3773
+ if (!ref) return sourceUrl;
3774
+ return `${sourceUrl}#${ref}`;
3775
+ }
3776
+ function buildUpdateInstallSource(entry) {
3777
+ if (!entry.skillPath) return formatSourceInput(entry.sourceUrl, entry.ref);
3778
+ let skillFolder = entry.skillPath;
3779
+ if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
3780
+ else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
3781
+ if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
3782
+ let installSource = skillFolder ? `${entry.source}/${skillFolder}` : entry.source;
3783
+ if (entry.ref) installSource = `${installSource}#${entry.ref}`;
3784
+ return installSource;
3785
+ }
3672
3786
  const __dirname = dirname(fileURLToPath(import.meta.url));
3673
3787
  function getVersion() {
3674
3788
  try {
@@ -3922,7 +4036,7 @@ function printSkippedSkills(skipped) {
3922
4036
  console.log(`${DIM}${skipped.length} skill(s) cannot be checked automatically:${RESET}`);
3923
4037
  for (const skill of skipped) {
3924
4038
  console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
3925
- console.log(` ${DIM}To update: ${TEXT}npx skills add ${skill.sourceUrl} -g -y${RESET}`);
4039
+ console.log(` ${DIM}To update: ${TEXT}npx skills add ${formatSourceInput(skill.sourceUrl, skill.ref)} -g -y${RESET}`);
3926
4040
  }
3927
4041
  }
3928
4042
  async function runCheck(args = []) {
@@ -3945,7 +4059,8 @@ async function runCheck(args = []) {
3945
4059
  skipped.push({
3946
4060
  name: skillName,
3947
4061
  reason: getSkipReason(entry),
3948
- sourceUrl: entry.sourceUrl
4062
+ sourceUrl: entry.sourceUrl,
4063
+ ref: entry.ref
3949
4064
  });
3950
4065
  continue;
3951
4066
  }
@@ -3966,7 +4081,7 @@ async function runCheck(args = []) {
3966
4081
  const updates = [];
3967
4082
  const errors = [];
3968
4083
  for (const [source, skills] of skillsBySource) for (const { name, entry } of skills) try {
3969
- const latestHash = await fetchSkillFolderHash(source, entry.skillPath, token);
4084
+ const latestHash = await fetchSkillFolderHash(source, entry.skillPath, token, entry.ref);
3970
4085
  if (!latestHash) {
3971
4086
  errors.push({
3972
4087
  name,
@@ -4001,6 +4116,11 @@ async function runCheck(args = []) {
4001
4116
  if (errors.length > 0) {
4002
4117
  console.log();
4003
4118
  console.log(`${DIM}Could not check ${errors.length} skill(s) (may need reinstall)${RESET}`);
4119
+ console.log();
4120
+ for (const error of errors) {
4121
+ console.log(` ${DIM}✗${RESET} ${error.name}`);
4122
+ console.log(` ${DIM}source: ${error.source}${RESET}`);
4123
+ }
4004
4124
  }
4005
4125
  printSkippedSkills(skipped);
4006
4126
  track({
@@ -4030,12 +4150,13 @@ async function runUpdate() {
4030
4150
  skipped.push({
4031
4151
  name: skillName,
4032
4152
  reason: getSkipReason(entry),
4033
- sourceUrl: entry.sourceUrl
4153
+ sourceUrl: entry.sourceUrl,
4154
+ ref: entry.ref
4034
4155
  });
4035
4156
  continue;
4036
4157
  }
4037
4158
  try {
4038
- const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
4159
+ const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token, entry.ref);
4039
4160
  if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
4040
4161
  name: skillName,
4041
4162
  source: entry.source,
@@ -4059,18 +4180,15 @@ async function runUpdate() {
4059
4180
  let failCount = 0;
4060
4181
  for (const update of updates) {
4061
4182
  console.log(`${TEXT}Updating ${update.name}...${RESET}`);
4062
- let installUrl = update.entry.sourceUrl;
4063
- if (update.entry.skillPath) {
4064
- let skillFolder = update.entry.skillPath;
4065
- if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
4066
- else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
4067
- if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
4068
- installUrl = update.entry.sourceUrl.replace(/\.git$/, "").replace(/\/$/, "");
4069
- installUrl = `${installUrl}/tree/main/${skillFolder}`;
4070
- }
4071
- if (spawnSync("npx", [
4072
- "-y",
4073
- "skills",
4183
+ const installUrl = buildUpdateInstallSource(update.entry);
4184
+ const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
4185
+ if (!existsSync(cliEntry)) {
4186
+ failCount++;
4187
+ console.log(` ${DIM}✗ Failed to update ${update.name}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4188
+ continue;
4189
+ }
4190
+ if (spawnSync(process.execPath, [
4191
+ cliEntry,
4074
4192
  "add",
4075
4193
  installUrl,
4076
4194
  "-g",
@@ -4081,6 +4199,7 @@ async function runUpdate() {
4081
4199
  "pipe",
4082
4200
  "pipe"
4083
4201
  ],
4202
+ encoding: "utf-8",
4084
4203
  shell: process.platform === "win32"
4085
4204
  }).status === 0) {
4086
4205
  successCount++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,8 +19,8 @@
19
19
  "dev": "node src/cli.ts",
20
20
  "exec:test": "node scripts/execute-tests.ts",
21
21
  "prepublishOnly": "npm run build",
22
- "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.ts'",
23
- "format:check": "prettier --check 'src/**/*.ts' 'scripts/**/*.ts'",
22
+ "format": "prettier --write \"src/**/*.ts\" \"scripts/**/*.ts\"",
23
+ "format:check": "prettier --check \"src/**/*.ts\" \"scripts/**/*.ts\"",
24
24
  "prepare": "husky",
25
25
  "test": "vitest",
26
26
  "type-check": "tsc --noEmit",
@@ -39,6 +39,7 @@
39
39
  "amp",
40
40
  "antigravity",
41
41
  "augment",
42
+ "bob",
42
43
  "claude-code",
43
44
  "openclaw",
44
45
  "cline",
@@ -49,7 +50,9 @@
49
50
  "cortex",
50
51
  "crush",
51
52
  "cursor",
53
+ "deepagents",
52
54
  "droid",
55
+ "firebender",
53
56
  "gemini-cli",
54
57
  "github-copilot",
55
58
  "goose",