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.
- package/README.md +8 -4
- package/dist/cli.mjs +186 -67
- 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 [
|
|
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` | `.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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: ".
|
|
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
|
-
|
|
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
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
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
|
|
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
|
|
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}/${
|
|
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}/${
|
|
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
|
-
|
|
1665
|
-
|
|
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
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.
|
|
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
|
|
23
|
-
"format:check": "prettier --check
|
|
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",
|