skills 1.5.0 → 1.5.2
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 +64 -54
- package/dist/cli.mjs +252 -65
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
The CLI for the open agent skills ecosystem.
|
|
4
4
|
|
|
5
5
|
<!-- agent-list:start -->
|
|
6
|
-
|
|
7
|
-
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [41 more](#available-agents).
|
|
8
|
-
|
|
6
|
+
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [49 more](#supported-agents).
|
|
9
7
|
<!-- agent-list:end -->
|
|
10
8
|
|
|
11
9
|
## Install a Skill
|
|
@@ -41,7 +39,7 @@ npx skills add ./my-local-skills
|
|
|
41
39
|
| Option | Description |
|
|
42
40
|
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
41
|
| `-g, --global` | Install to user directory instead of project |
|
|
44
|
-
| `-a, --agent <agents...>` | <!-- agent-names:start -->Target specific agents (e.g., `claude-code`, `codex`). See [
|
|
42
|
+
| `-a, --agent <agents...>` | <!-- agent-names:start -->Target specific agents (e.g., `claude-code`, `codex`). See [Supported Agents](#supported-agents)<!-- agent-names:end --> |
|
|
45
43
|
| `-s, --skill <skills...>` | Install specific skills by name (use `'*'` for all skills) |
|
|
46
44
|
| `-l, --list` | List available skills without installing |
|
|
47
45
|
| `--copy` | Copy files instead of symlinking to agent directories |
|
|
@@ -225,56 +223,62 @@ Discover skills at **[skills.sh](https://skills.sh)**
|
|
|
225
223
|
Skills can be installed to any of these agents:
|
|
226
224
|
|
|
227
225
|
<!-- supported-agents:start -->
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
|
231
|
-
| Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/`
|
|
232
|
-
| Antigravity
|
|
233
|
-
| Augment
|
|
234
|
-
| IBM Bob
|
|
235
|
-
| Claude Code
|
|
236
|
-
| OpenClaw
|
|
237
|
-
| Cline, Warp
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
|
|
|
253
|
-
|
|
|
254
|
-
|
|
|
255
|
-
|
|
|
256
|
-
|
|
|
257
|
-
|
|
|
258
|
-
|
|
|
259
|
-
|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
|
263
|
-
|
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
|
|
|
267
|
-
|
|
|
268
|
-
|
|
|
269
|
-
|
|
|
270
|
-
|
|
|
271
|
-
|
|
|
272
|
-
|
|
226
|
+
| Agent | `--agent` | Project Path | Global Path |
|
|
227
|
+
|-------|-----------|--------------|-------------|
|
|
228
|
+
| AiderDesk | `aider-desk` | `.aider-desk/skills/` | `~/.aider-desk/skills/` |
|
|
229
|
+
| Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` |
|
|
230
|
+
| Antigravity | `antigravity` | `.agents/skills/` | `~/.gemini/antigravity/skills/` |
|
|
231
|
+
| Augment | `augment` | `.augment/skills/` | `~/.augment/skills/` |
|
|
232
|
+
| IBM Bob | `bob` | `.bob/skills/` | `~/.bob/skills/` |
|
|
233
|
+
| Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
|
|
234
|
+
| OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` |
|
|
235
|
+
| Cline, Warp | `cline`, `warp` | `.agents/skills/` | `~/.agents/skills/` |
|
|
236
|
+
| CodeArts Agent | `codearts-agent` | `.codeartsdoer/skills/` | `~/.codeartsdoer/skills/` |
|
|
237
|
+
| CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` |
|
|
238
|
+
| Codemaker | `codemaker` | `.codemaker/skills/` | `~/.codemaker/skills/` |
|
|
239
|
+
| Code Studio | `codestudio` | `.codestudio/skills/` | `~/.codestudio/skills/` |
|
|
240
|
+
| Codex | `codex` | `.agents/skills/` | `~/.codex/skills/` |
|
|
241
|
+
| Command Code | `command-code` | `.commandcode/skills/` | `~/.commandcode/skills/` |
|
|
242
|
+
| Continue | `continue` | `.continue/skills/` | `~/.continue/skills/` |
|
|
243
|
+
| Cortex Code | `cortex` | `.cortex/skills/` | `~/.snowflake/cortex/skills/` |
|
|
244
|
+
| Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` |
|
|
245
|
+
| Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` |
|
|
246
|
+
| Deep Agents | `deepagents` | `.agents/skills/` | `~/.deepagents/agent/skills/` |
|
|
247
|
+
| Devin for Terminal | `devin` | `.devin/skills/` | `~/.config/devin/skills/` |
|
|
248
|
+
| Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
|
|
249
|
+
| Firebender | `firebender` | `.agents/skills/` | `~/.firebender/skills/` |
|
|
250
|
+
| ForgeCode | `forgecode` | `.forge/skills/` | `~/.forge/skills/` |
|
|
251
|
+
| Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
|
|
252
|
+
| GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
|
|
253
|
+
| Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
|
|
254
|
+
| Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
|
|
255
|
+
| iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
|
|
256
|
+
| Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` |
|
|
257
|
+
| Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` |
|
|
258
|
+
| Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
|
|
259
|
+
| MCPJam | `mcpjam` | `.mcpjam/skills/` | `~/.mcpjam/skills/` |
|
|
260
|
+
| Mistral Vibe | `mistral-vibe` | `.vibe/skills/` | `~/.vibe/skills/` |
|
|
261
|
+
| Mux | `mux` | `.mux/skills/` | `~/.mux/skills/` |
|
|
262
|
+
| OpenCode | `opencode` | `.agents/skills/` | `~/.config/opencode/skills/` |
|
|
263
|
+
| OpenHands | `openhands` | `.openhands/skills/` | `~/.openhands/skills/` |
|
|
264
|
+
| Pi | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` |
|
|
265
|
+
| Qoder | `qoder` | `.qoder/skills/` | `~/.qoder/skills/` |
|
|
266
|
+
| Qwen Code | `qwen-code` | `.qwen/skills/` | `~/.qwen/skills/` |
|
|
267
|
+
| Rovo Dev | `rovodev` | `.rovodev/skills/` | `~/.rovodev/skills/` |
|
|
268
|
+
| Roo Code | `roo` | `.roo/skills/` | `~/.roo/skills/` |
|
|
269
|
+
| Tabnine CLI | `tabnine-cli` | `.tabnine/agent/skills/` | `~/.tabnine/agent/skills/` |
|
|
270
|
+
| Trae | `trae` | `.trae/skills/` | `~/.trae/skills/` |
|
|
271
|
+
| Trae CN | `trae-cn` | `.trae/skills/` | `~/.trae-cn/skills/` |
|
|
272
|
+
| Windsurf | `windsurf` | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
|
|
273
|
+
| Zencoder | `zencoder` | `.zencoder/skills/` | `~/.zencoder/skills/` |
|
|
274
|
+
| Neovate | `neovate` | `.neovate/skills/` | `~/.neovate/skills/` |
|
|
275
|
+
| Pochi | `pochi` | `.pochi/skills/` | `~/.pochi/skills/` |
|
|
276
|
+
| AdaL | `adal` | `.adal/skills/` | `~/.adal/skills/` |
|
|
273
277
|
<!-- supported-agents:end -->
|
|
274
278
|
|
|
275
279
|
> [!NOTE]
|
|
276
|
-
> **Kiro CLI users:**
|
|
277
|
-
> `.kiro/agents/<agent>.json`:
|
|
280
|
+
> **Kiro CLI users:** The default agent automatically loads skills from `.kiro/skills/` and `~/.kiro/skills/` — no
|
|
281
|
+
> configuration needed. If you use a **custom agent**, add skills to its `resources` in `.kiro/agents/<agent>.json`:
|
|
278
282
|
>
|
|
279
283
|
> ```json
|
|
280
284
|
> {
|
|
@@ -334,23 +338,27 @@ metadata:
|
|
|
334
338
|
The CLI searches for skills in these locations within a repository:
|
|
335
339
|
|
|
336
340
|
<!-- skill-discovery:start -->
|
|
337
|
-
|
|
338
341
|
- Root directory (if it contains `SKILL.md`)
|
|
339
342
|
- `skills/`
|
|
340
343
|
- `skills/.curated/`
|
|
341
344
|
- `skills/.experimental/`
|
|
342
345
|
- `skills/.system/`
|
|
346
|
+
- `.aider-desk/skills/`
|
|
343
347
|
- `.agents/skills/`
|
|
344
348
|
- `.augment/skills/`
|
|
345
349
|
- `.bob/skills/`
|
|
346
350
|
- `.claude/skills/`
|
|
347
|
-
-
|
|
351
|
+
- `.codeartsdoer/skills/`
|
|
348
352
|
- `.codebuddy/skills/`
|
|
353
|
+
- `.codemaker/skills/`
|
|
354
|
+
- `.codestudio/skills/`
|
|
349
355
|
- `.commandcode/skills/`
|
|
350
356
|
- `.continue/skills/`
|
|
351
357
|
- `.cortex/skills/`
|
|
352
358
|
- `.crush/skills/`
|
|
359
|
+
- `.devin/skills/`
|
|
353
360
|
- `.factory/skills/`
|
|
361
|
+
- `.forge/skills/`
|
|
354
362
|
- `.goose/skills/`
|
|
355
363
|
- `.junie/skills/`
|
|
356
364
|
- `.iflow/skills/`
|
|
@@ -364,7 +372,9 @@ The CLI searches for skills in these locations within a repository:
|
|
|
364
372
|
- `.pi/skills/`
|
|
365
373
|
- `.qoder/skills/`
|
|
366
374
|
- `.qwen/skills/`
|
|
375
|
+
- `.rovodev/skills/`
|
|
367
376
|
- `.roo/skills/`
|
|
377
|
+
- `.tabnine/agent/skills/`
|
|
368
378
|
- `.trae/skills/`
|
|
369
379
|
- `.windsurf/skills/`
|
|
370
380
|
- `.zencoder/skills/`
|
|
@@ -405,7 +415,7 @@ shared [Agent Skills specification](https://agentskills.io). However, some featu
|
|
|
405
415
|
| Basic skills | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
|
406
416
|
| `allowed-tools` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No |
|
|
407
417
|
| `context: fork` | No | No | Yes | No | No | No | No | No | No | No | No | No | No | No | No | No | No | No |
|
|
408
|
-
| Hooks | No | No | Yes | Yes | No | No | No |
|
|
418
|
+
| Hooks | No | No | Yes | Yes | No | No | No | Yes | No | No | No | No | No | No | No | No | No | No |
|
|
409
419
|
|
|
410
420
|
## Troubleshooting
|
|
411
421
|
|
package/dist/cli.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from
|
|
|
11
11
|
import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
12
12
|
import { homedir, platform, tmpdir } from "os";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
|
+
import { stripVTControlCharacters } from "node:util";
|
|
14
15
|
import * as readline from "readline";
|
|
15
16
|
import { Writable } from "stream";
|
|
16
17
|
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
@@ -220,6 +221,18 @@ function isWellKnownUrl(input) {
|
|
|
220
221
|
return false;
|
|
221
222
|
}
|
|
222
223
|
}
|
|
224
|
+
const CSI_RE = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
|
|
225
|
+
const OSC_RE = /\x1b\][\s\S]*?(?:\x07|\x1b\\)/g;
|
|
226
|
+
const DCS_PM_APC_RE = /\x1b[P^_][\s\S]*?(?:\x1b\\)/g;
|
|
227
|
+
const SIMPLE_ESC_RE = /\x1b[\x20-\x7e]/g;
|
|
228
|
+
const C1_RE = /[\x80-\x9f]/g;
|
|
229
|
+
const CONTROL_RE = /[\x00-\x06\x07\x08\x0b\x0c\x0d-\x1a\x1c-\x1f\x7f]/g;
|
|
230
|
+
function stripTerminalEscapes(str) {
|
|
231
|
+
return str.replace(OSC_RE, "").replace(DCS_PM_APC_RE, "").replace(CSI_RE, "").replace(SIMPLE_ESC_RE, "").replace(C1_RE, "").replace(CONTROL_RE, "");
|
|
232
|
+
}
|
|
233
|
+
function sanitizeMetadata(str) {
|
|
234
|
+
return stripTerminalEscapes(str).replace(/[\r\n]+/g, " ").trim();
|
|
235
|
+
}
|
|
223
236
|
const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
|
|
224
237
|
callback();
|
|
225
238
|
} });
|
|
@@ -233,6 +246,25 @@ const S_BULLET = import_picocolors.default.green("•");
|
|
|
233
246
|
const S_BAR = import_picocolors.default.dim("│");
|
|
234
247
|
const S_BAR_H = import_picocolors.default.dim("─");
|
|
235
248
|
const cancelSymbol = Symbol("cancel");
|
|
249
|
+
function approxStringWidth(plain) {
|
|
250
|
+
let width = 0;
|
|
251
|
+
for (const ch of plain) {
|
|
252
|
+
const code = ch.codePointAt(0);
|
|
253
|
+
if (code === 0) continue;
|
|
254
|
+
width += code >= 4352 && code <= 4447 || code >= 8986 && code <= 8987 || code >= 9001 && code <= 9002 || code >= 9193 && code <= 9196 || code === 9200 || code === 9203 || code >= 9725 && code <= 9726 || code >= 9748 && code <= 9749 || code >= 9800 && code <= 9811 || code >= 9855 && code <= 9855 || code >= 9875 && code <= 9875 || code >= 9889 && code <= 9889 || code >= 9898 && code <= 9899 || code >= 9917 && code <= 9918 || code >= 9924 && code <= 9925 || code >= 9934 && code <= 9934 || code >= 9940 && code <= 9940 || code >= 9962 && code <= 9962 || code >= 9970 && code <= 9971 || code >= 9973 && code <= 9973 || code >= 9978 && code <= 9978 || code >= 9981 && code <= 9981 || code >= 9989 && code <= 9989 || code >= 9994 && code <= 9995 || code >= 10024 && code <= 10024 || code >= 10060 && code <= 10060 || code >= 10062 && code <= 10062 || code >= 10067 && code <= 10069 || code >= 10071 && code <= 10071 || code >= 10133 && code <= 10135 || code >= 10160 && code <= 10160 || code >= 10175 && code <= 10175 || code >= 11035 && code <= 11036 || code >= 11088 && code <= 11088 || code >= 11093 && code <= 11093 || code >= 11904 && code <= 42191 && code !== 12351 || code >= 43360 && code <= 43388 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65049 || code >= 65072 && code <= 65135 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 126976 && code <= 129535 ? 2 : 1;
|
|
255
|
+
}
|
|
256
|
+
return width;
|
|
257
|
+
}
|
|
258
|
+
function visualRowsForLine(line, columns) {
|
|
259
|
+
const plain = stripVTControlCharacters(line);
|
|
260
|
+
const cols = Math.max(1, columns);
|
|
261
|
+
const w = approxStringWidth(plain);
|
|
262
|
+
return Math.max(1, Math.ceil(w / cols));
|
|
263
|
+
}
|
|
264
|
+
function countVisualRowsForLines(lines, columns) {
|
|
265
|
+
const cols = columns !== void 0 && columns > 0 ? columns : process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
266
|
+
return lines.reduce((sum, line) => sum + visualRowsForLine(line, cols), 0);
|
|
267
|
+
}
|
|
236
268
|
async function searchMultiselect(options) {
|
|
237
269
|
const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection } = options;
|
|
238
270
|
return new Promise((resolve) => {
|
|
@@ -320,7 +352,7 @@ async function searchMultiselect(options) {
|
|
|
320
352
|
lines.push(`${S_BAR} ${import_picocolors.default.dim(allSelectedLabels.join(", "))}`);
|
|
321
353
|
} else if (state === "cancel") lines.push(`${S_BAR} ${import_picocolors.default.strikethrough(import_picocolors.default.dim("Cancelled"))}`);
|
|
322
354
|
process.stdout.write(lines.join("\n") + "\n");
|
|
323
|
-
lastRenderHeight = lines.
|
|
355
|
+
lastRenderHeight = countVisualRowsForLines(lines, process.stdout.columns);
|
|
324
356
|
};
|
|
325
357
|
const cleanup = () => {
|
|
326
358
|
process.stdin.removeListener("keypress", keypressHandler);
|
|
@@ -383,7 +415,13 @@ async function searchMultiselect(options) {
|
|
|
383
415
|
render();
|
|
384
416
|
});
|
|
385
417
|
}
|
|
386
|
-
const
|
|
418
|
+
const DEFAULT_CLONE_TIMEOUT_MS = 3e5;
|
|
419
|
+
const CLONE_TIMEOUT_MS = (() => {
|
|
420
|
+
const raw = process.env.SKILLS_CLONE_TIMEOUT_MS;
|
|
421
|
+
if (!raw) return DEFAULT_CLONE_TIMEOUT_MS;
|
|
422
|
+
const parsed = Number.parseInt(raw, 10);
|
|
423
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CLONE_TIMEOUT_MS;
|
|
424
|
+
})();
|
|
387
425
|
var GitCloneError = class extends Error {
|
|
388
426
|
url;
|
|
389
427
|
isTimeout;
|
|
@@ -402,8 +440,15 @@ async function cloneRepo(url, ref) {
|
|
|
402
440
|
timeout: { block: CLONE_TIMEOUT_MS },
|
|
403
441
|
env: {
|
|
404
442
|
...process.env,
|
|
405
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
406
|
-
|
|
443
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
444
|
+
GIT_LFS_SKIP_SMUDGE: "1"
|
|
445
|
+
},
|
|
446
|
+
config: [
|
|
447
|
+
"filter.lfs.required=false",
|
|
448
|
+
"filter.lfs.smudge=",
|
|
449
|
+
"filter.lfs.clean=",
|
|
450
|
+
"filter.lfs.process="
|
|
451
|
+
]
|
|
407
452
|
});
|
|
408
453
|
const cloneOptions = ref ? [
|
|
409
454
|
"--depth",
|
|
@@ -422,7 +467,7 @@ async function cloneRepo(url, ref) {
|
|
|
422
467
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
423
468
|
const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
|
|
424
469
|
const isAuthError = errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("Permission denied") || errorMessage.includes("Repository not found");
|
|
425
|
-
if (isTimeout) throw new GitCloneError(
|
|
470
|
+
if (isTimeout) throw new GitCloneError(`Clone timed out after ${Math.round(CLONE_TIMEOUT_MS / 1e3)}s. Common causes:\n - Large repository: raise the timeout with SKILLS_CLONE_TIMEOUT_MS=600000 (10m)\n - Slow network: retry, or clone manually and pass the local path to 'skills add'\n - Private repo without credentials: ensure auth is configured\n - For SSH: ssh-add -l (to check loaded keys)\n - For HTTPS: gh auth status (if using GitHub CLI)`, url, true, false);
|
|
426
471
|
if (isAuthError) throw new GitCloneError(`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`, url, false, true);
|
|
427
472
|
throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
|
|
428
473
|
}
|
|
@@ -538,8 +583,8 @@ async function parseSkillMd(skillMdPath, options) {
|
|
|
538
583
|
if (typeof data.name !== "string" || typeof data.description !== "string") return null;
|
|
539
584
|
if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
|
|
540
585
|
return {
|
|
541
|
-
name: data.name,
|
|
542
|
-
description: data.description,
|
|
586
|
+
name: sanitizeMetadata(data.name),
|
|
587
|
+
description: sanitizeMetadata(data.description),
|
|
543
588
|
path: dirname(skillMdPath),
|
|
544
589
|
rawContent: content,
|
|
545
590
|
metadata: data.metadata
|
|
@@ -657,6 +702,7 @@ const home = homedir();
|
|
|
657
702
|
const configHome = xdgConfig ?? join(home, ".config");
|
|
658
703
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
659
704
|
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
|
|
705
|
+
const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
|
|
660
706
|
function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
661
707
|
if (pathExists(join(homeDir, ".openclaw"))) return join(homeDir, ".openclaw/skills");
|
|
662
708
|
if (pathExists(join(homeDir, ".clawdbot"))) return join(homeDir, ".clawdbot/skills");
|
|
@@ -664,6 +710,15 @@ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
|
664
710
|
return join(homeDir, ".openclaw/skills");
|
|
665
711
|
}
|
|
666
712
|
const agents = {
|
|
713
|
+
"aider-desk": {
|
|
714
|
+
name: "aider-desk",
|
|
715
|
+
displayName: "AiderDesk",
|
|
716
|
+
skillsDir: ".aider-desk/skills",
|
|
717
|
+
globalSkillsDir: join(home, ".aider-desk/skills"),
|
|
718
|
+
detectInstalled: async () => {
|
|
719
|
+
return existsSync(join(home, ".aider-desk"));
|
|
720
|
+
}
|
|
721
|
+
},
|
|
667
722
|
amp: {
|
|
668
723
|
name: "amp",
|
|
669
724
|
displayName: "Amp",
|
|
@@ -727,6 +782,15 @@ const agents = {
|
|
|
727
782
|
return existsSync(join(home, ".cline"));
|
|
728
783
|
}
|
|
729
784
|
},
|
|
785
|
+
"codearts-agent": {
|
|
786
|
+
name: "codearts-agent",
|
|
787
|
+
displayName: "CodeArts Agent",
|
|
788
|
+
skillsDir: ".codeartsdoer/skills",
|
|
789
|
+
globalSkillsDir: join(home, ".codeartsdoer/skills"),
|
|
790
|
+
detectInstalled: async () => {
|
|
791
|
+
return existsSync(join(home, ".codeartsdoer"));
|
|
792
|
+
}
|
|
793
|
+
},
|
|
730
794
|
codebuddy: {
|
|
731
795
|
name: "codebuddy",
|
|
732
796
|
displayName: "CodeBuddy",
|
|
@@ -736,6 +800,24 @@ const agents = {
|
|
|
736
800
|
return existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"));
|
|
737
801
|
}
|
|
738
802
|
},
|
|
803
|
+
codemaker: {
|
|
804
|
+
name: "codemaker",
|
|
805
|
+
displayName: "Codemaker",
|
|
806
|
+
skillsDir: ".codemaker/skills",
|
|
807
|
+
globalSkillsDir: join(home, ".codemaker/skills"),
|
|
808
|
+
detectInstalled: async () => {
|
|
809
|
+
return existsSync(join(home, ".codemaker"));
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
codestudio: {
|
|
813
|
+
name: "codestudio",
|
|
814
|
+
displayName: "Code Studio",
|
|
815
|
+
skillsDir: ".codestudio/skills",
|
|
816
|
+
globalSkillsDir: join(home, ".codestudio/skills"),
|
|
817
|
+
detectInstalled: async () => {
|
|
818
|
+
return existsSync(join(home, ".codestudio"));
|
|
819
|
+
}
|
|
820
|
+
},
|
|
739
821
|
codex: {
|
|
740
822
|
name: "codex",
|
|
741
823
|
displayName: "Codex",
|
|
@@ -799,6 +881,15 @@ const agents = {
|
|
|
799
881
|
return existsSync(join(home, ".deepagents"));
|
|
800
882
|
}
|
|
801
883
|
},
|
|
884
|
+
devin: {
|
|
885
|
+
name: "devin",
|
|
886
|
+
displayName: "Devin for Terminal",
|
|
887
|
+
skillsDir: ".devin/skills",
|
|
888
|
+
globalSkillsDir: join(configHome, "devin/skills"),
|
|
889
|
+
detectInstalled: async () => {
|
|
890
|
+
return existsSync(join(configHome, "devin"));
|
|
891
|
+
}
|
|
892
|
+
},
|
|
802
893
|
droid: {
|
|
803
894
|
name: "droid",
|
|
804
895
|
displayName: "Droid",
|
|
@@ -817,6 +908,15 @@ const agents = {
|
|
|
817
908
|
return existsSync(join(home, ".firebender"));
|
|
818
909
|
}
|
|
819
910
|
},
|
|
911
|
+
forgecode: {
|
|
912
|
+
name: "forgecode",
|
|
913
|
+
displayName: "ForgeCode",
|
|
914
|
+
skillsDir: ".forge/skills",
|
|
915
|
+
globalSkillsDir: join(home, ".forge/skills"),
|
|
916
|
+
detectInstalled: async () => {
|
|
917
|
+
return existsSync(join(home, ".forge"));
|
|
918
|
+
}
|
|
919
|
+
},
|
|
820
920
|
"gemini-cli": {
|
|
821
921
|
name: "gemini-cli",
|
|
822
922
|
displayName: "Gemini CLI",
|
|
@@ -911,9 +1011,9 @@ const agents = {
|
|
|
911
1011
|
name: "mistral-vibe",
|
|
912
1012
|
displayName: "Mistral Vibe",
|
|
913
1013
|
skillsDir: ".vibe/skills",
|
|
914
|
-
globalSkillsDir: join(
|
|
1014
|
+
globalSkillsDir: join(vibeHome, "skills"),
|
|
915
1015
|
detectInstalled: async () => {
|
|
916
|
-
return existsSync(
|
|
1016
|
+
return existsSync(vibeHome);
|
|
917
1017
|
}
|
|
918
1018
|
},
|
|
919
1019
|
mux: {
|
|
@@ -980,6 +1080,15 @@ const agents = {
|
|
|
980
1080
|
return existsSync(join(process.cwd(), ".replit"));
|
|
981
1081
|
}
|
|
982
1082
|
},
|
|
1083
|
+
rovodev: {
|
|
1084
|
+
name: "rovodev",
|
|
1085
|
+
displayName: "Rovo Dev",
|
|
1086
|
+
skillsDir: ".rovodev/skills",
|
|
1087
|
+
globalSkillsDir: join(home, ".rovodev/skills"),
|
|
1088
|
+
detectInstalled: async () => {
|
|
1089
|
+
return existsSync(join(home, ".rovodev"));
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
983
1092
|
roo: {
|
|
984
1093
|
name: "roo",
|
|
985
1094
|
displayName: "Roo Code",
|
|
@@ -989,6 +1098,15 @@ const agents = {
|
|
|
989
1098
|
return existsSync(join(home, ".roo"));
|
|
990
1099
|
}
|
|
991
1100
|
},
|
|
1101
|
+
"tabnine-cli": {
|
|
1102
|
+
name: "tabnine-cli",
|
|
1103
|
+
displayName: "Tabnine CLI",
|
|
1104
|
+
skillsDir: ".tabnine/agent/skills",
|
|
1105
|
+
globalSkillsDir: join(home, ".tabnine/agent/skills"),
|
|
1106
|
+
detectInstalled: async () => {
|
|
1107
|
+
return existsSync(join(home, ".tabnine"));
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
992
1110
|
trae: {
|
|
993
1111
|
name: "trae",
|
|
994
1112
|
displayName: "Trae",
|
|
@@ -1095,6 +1213,15 @@ function isPathSafe(basePath, targetPath) {
|
|
|
1095
1213
|
const normalizedTarget = normalize(resolve(targetPath));
|
|
1096
1214
|
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
|
|
1097
1215
|
}
|
|
1216
|
+
async function isDirEntryOrSymlinkToDir(entry, entryPath) {
|
|
1217
|
+
if (entry.isDirectory()) return true;
|
|
1218
|
+
if (!entry.isSymbolicLink()) return false;
|
|
1219
|
+
try {
|
|
1220
|
+
return (await stat(entryPath)).isDirectory();
|
|
1221
|
+
} catch {
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1098
1225
|
function getCanonicalSkillsDir(global, cwd) {
|
|
1099
1226
|
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
|
|
1100
1227
|
}
|
|
@@ -1235,7 +1362,6 @@ const EXCLUDE_DIRS = new Set([
|
|
|
1235
1362
|
]);
|
|
1236
1363
|
const isExcluded = (name, isDirectory = false) => {
|
|
1237
1364
|
if (EXCLUDE_FILES.has(name)) return true;
|
|
1238
|
-
if (name.startsWith(".")) return true;
|
|
1239
1365
|
if (isDirectory && EXCLUDE_DIRS.has(name)) return true;
|
|
1240
1366
|
return false;
|
|
1241
1367
|
};
|
|
@@ -1491,8 +1617,8 @@ async function listInstalledSkills(options = {}) {
|
|
|
1491
1617
|
for (const scope of scopes) try {
|
|
1492
1618
|
const entries = await readdir(scope.path, { withFileTypes: true });
|
|
1493
1619
|
for (const entry of entries) {
|
|
1494
|
-
if (!entry.isDirectory()) continue;
|
|
1495
1620
|
const skillDir = join(scope.path, entry.name);
|
|
1621
|
+
if (!await isDirEntryOrSymlinkToDir(entry, skillDir)) continue;
|
|
1496
1622
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
1497
1623
|
try {
|
|
1498
1624
|
await stat(skillMdPath);
|
|
@@ -1541,8 +1667,8 @@ async function listInstalledSkills(options = {}) {
|
|
|
1541
1667
|
if (!found) try {
|
|
1542
1668
|
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1543
1669
|
for (const agentEntry of agentEntries) {
|
|
1544
|
-
if (!agentEntry.isDirectory()) continue;
|
|
1545
1670
|
const candidateDir = join(agentBase, agentEntry.name);
|
|
1671
|
+
if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
|
|
1546
1672
|
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
1547
1673
|
try {
|
|
1548
1674
|
const candidateSkillMd = join(candidateDir, "SKILL.md");
|
|
@@ -1601,6 +1727,7 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
|
|
|
1601
1727
|
return null;
|
|
1602
1728
|
}
|
|
1603
1729
|
}
|
|
1730
|
+
const pendingTelemetry = [];
|
|
1604
1731
|
function track(data) {
|
|
1605
1732
|
if (!isEnabled()) return;
|
|
1606
1733
|
try {
|
|
@@ -1608,9 +1735,15 @@ function track(data) {
|
|
|
1608
1735
|
if (cliVersion) params.set("v", cliVersion);
|
|
1609
1736
|
if (isCI()) params.set("ci", "1");
|
|
1610
1737
|
for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
|
|
1611
|
-
fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
|
|
1738
|
+
const p = fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {}).then(() => {});
|
|
1739
|
+
pendingTelemetry.push(p);
|
|
1612
1740
|
} catch {}
|
|
1613
1741
|
}
|
|
1742
|
+
async function flushTelemetry(timeoutMs = 5e3) {
|
|
1743
|
+
if (pendingTelemetry.length === 0) return;
|
|
1744
|
+
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
1745
|
+
await Promise.race([Promise.all(pendingTelemetry), timeout]);
|
|
1746
|
+
}
|
|
1614
1747
|
var ProviderRegistryImpl = class {
|
|
1615
1748
|
providers = [];
|
|
1616
1749
|
register(provider) {
|
|
@@ -1748,8 +1881,8 @@ var WellKnownProvider = class {
|
|
|
1748
1881
|
const fileResults = await Promise.all(filePromises);
|
|
1749
1882
|
for (const result of fileResults) if (result) files.set(result.path, result.content);
|
|
1750
1883
|
return {
|
|
1751
|
-
name: data.name,
|
|
1752
|
-
description: data.description,
|
|
1884
|
+
name: sanitizeMetadata(data.name),
|
|
1885
|
+
description: sanitizeMetadata(data.description),
|
|
1753
1886
|
content,
|
|
1754
1887
|
installName: entry.name,
|
|
1755
1888
|
sourceUrl: skillMdUrl,
|
|
@@ -2122,12 +2255,14 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
2122
2255
|
if (!data.name || !data.description) continue;
|
|
2123
2256
|
if (typeof data.name !== "string" || typeof data.description !== "string") continue;
|
|
2124
2257
|
if (data.metadata?.internal === true && !options.includeInternal) continue;
|
|
2258
|
+
const safeName = sanitizeMetadata(data.name);
|
|
2259
|
+
const safeDescription = sanitizeMetadata(data.description);
|
|
2125
2260
|
parsedSkills.push({
|
|
2126
2261
|
mdPath,
|
|
2127
|
-
name:
|
|
2128
|
-
description:
|
|
2262
|
+
name: safeName,
|
|
2263
|
+
description: safeDescription,
|
|
2129
2264
|
content,
|
|
2130
|
-
slug: toSkillSlug(
|
|
2265
|
+
slug: toSkillSlug(safeName),
|
|
2131
2266
|
metadata: data.metadata
|
|
2132
2267
|
});
|
|
2133
2268
|
}
|
|
@@ -2164,7 +2299,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
2164
2299
|
tree
|
|
2165
2300
|
};
|
|
2166
2301
|
}
|
|
2167
|
-
var version$1 = "1.5.
|
|
2302
|
+
var version$1 = "1.5.2";
|
|
2168
2303
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
2169
2304
|
async function isSourcePrivate(source) {
|
|
2170
2305
|
const ownerRepo = parseOwnerRepo(source);
|
|
@@ -2190,7 +2325,7 @@ function socketLabel(audit) {
|
|
|
2190
2325
|
return count > 0 ? import_picocolors.default.red(`${count} alert${count !== 1 ? "s" : ""}`) : import_picocolors.default.green("0 alerts");
|
|
2191
2326
|
}
|
|
2192
2327
|
function padEnd(str, width) {
|
|
2193
|
-
const visible = str
|
|
2328
|
+
const visible = stripTerminalEscapes(str);
|
|
2194
2329
|
const pad = Math.max(0, width - visible.length);
|
|
2195
2330
|
return str + " ".repeat(pad);
|
|
2196
2331
|
}
|
|
@@ -2513,6 +2648,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2513
2648
|
process.exit(0);
|
|
2514
2649
|
}
|
|
2515
2650
|
}
|
|
2651
|
+
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
2652
|
+
const wellKnownPrivacyPromise = isSourcePrivate(sourceIdentifier).catch(() => null);
|
|
2516
2653
|
spinner.start("Installing skills...");
|
|
2517
2654
|
const results = [];
|
|
2518
2655
|
for (const skill of selectedSkills) for (const agent of targetAgents) {
|
|
@@ -2530,10 +2667,9 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2530
2667
|
console.log();
|
|
2531
2668
|
const successful = results.filter((r) => r.success);
|
|
2532
2669
|
const failed = results.filter((r) => !r.success);
|
|
2533
|
-
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
2534
2670
|
const skillFiles = {};
|
|
2535
2671
|
for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
|
|
2536
|
-
if (await
|
|
2672
|
+
if (await wellKnownPrivacyPromise !== true) track({
|
|
2537
2673
|
event: "install",
|
|
2538
2674
|
source: sourceIdentifier,
|
|
2539
2675
|
skills: selectedSkills.map((s) => s.installName).join(","),
|
|
@@ -2645,7 +2781,14 @@ async function runAdd(args, options = {}) {
|
|
|
2645
2781
|
spinner.start("Parsing source...");
|
|
2646
2782
|
const parsed = parseSource(source);
|
|
2647
2783
|
spinner.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)}` : ""}`);
|
|
2648
|
-
|
|
2784
|
+
const ownerRepoRaw = getOwnerRepo(parsed);
|
|
2785
|
+
const repoPrivacyPromise = (() => {
|
|
2786
|
+
if (!ownerRepoRaw) return Promise.resolve(null);
|
|
2787
|
+
const ownerRepo = parseOwnerRepo(ownerRepoRaw);
|
|
2788
|
+
if (!ownerRepo) return Promise.resolve(null);
|
|
2789
|
+
return isRepoPrivate(ownerRepo.owner, ownerRepo.repo).catch(() => null);
|
|
2790
|
+
})();
|
|
2791
|
+
if (ownerRepoRaw?.split("/")[0]?.toLowerCase() === "openclaw" && !options.dangerouslyAcceptOpenclawRisks) {
|
|
2649
2792
|
console.log();
|
|
2650
2793
|
M.warn(import_picocolors.default.yellow(import_picocolors.default.bold("⚠ OpenClaw skills are unverified community submissions.")));
|
|
2651
2794
|
M.message(import_picocolors.default.yellow("This source contains user-submitted skills that have not been reviewed for safety or quality."));
|
|
@@ -2680,7 +2823,11 @@ async function runAdd(args, options = {}) {
|
|
|
2680
2823
|
fullDepth: options.fullDepth
|
|
2681
2824
|
});
|
|
2682
2825
|
} else if (parsed.type === "github" && !options.fullDepth) {
|
|
2683
|
-
const BLOB_ALLOWED_OWNERS = [
|
|
2826
|
+
const BLOB_ALLOWED_OWNERS = [
|
|
2827
|
+
"vercel",
|
|
2828
|
+
"vercel-labs",
|
|
2829
|
+
"heygen-com"
|
|
2830
|
+
];
|
|
2684
2831
|
const ownerRepo = getOwnerRepo(parsed);
|
|
2685
2832
|
const owner = ownerRepo?.split("/")[0]?.toLowerCase();
|
|
2686
2833
|
if (ownerRepo && owner && BLOB_ALLOWED_OWNERS.includes(owner)) {
|
|
@@ -3018,18 +3165,8 @@ async function runAdd(args, options = {}) {
|
|
|
3018
3165
|
else continue;
|
|
3019
3166
|
const normalizedSource = getOwnerRepo(parsed);
|
|
3020
3167
|
const lockSource = parsed.url.startsWith("git@") ? parsed.url : normalizedSource;
|
|
3021
|
-
if (normalizedSource) {
|
|
3022
|
-
|
|
3023
|
-
if (ownerRepo) {
|
|
3024
|
-
if (await isRepoPrivate(ownerRepo.owner, ownerRepo.repo) === false) track({
|
|
3025
|
-
event: "install",
|
|
3026
|
-
source: normalizedSource,
|
|
3027
|
-
skills: selectedSkills.map((s) => s.name).join(","),
|
|
3028
|
-
agents: targetAgents.join(","),
|
|
3029
|
-
...installGlobally && { global: "1" },
|
|
3030
|
-
skillFiles: JSON.stringify(skillFiles)
|
|
3031
|
-
});
|
|
3032
|
-
} else track({
|
|
3168
|
+
if (normalizedSource) if (parseOwnerRepo(normalizedSource)) {
|
|
3169
|
+
if (await repoPrivacyPromise === false) track({
|
|
3033
3170
|
event: "install",
|
|
3034
3171
|
source: normalizedSource,
|
|
3035
3172
|
skills: selectedSkills.map((s) => s.name).join(","),
|
|
@@ -3037,9 +3174,21 @@ async function runAdd(args, options = {}) {
|
|
|
3037
3174
|
...installGlobally && { global: "1" },
|
|
3038
3175
|
skillFiles: JSON.stringify(skillFiles)
|
|
3039
3176
|
});
|
|
3040
|
-
}
|
|
3177
|
+
} else track({
|
|
3178
|
+
event: "install",
|
|
3179
|
+
source: normalizedSource,
|
|
3180
|
+
skills: selectedSkills.map((s) => s.name).join(","),
|
|
3181
|
+
agents: targetAgents.join(","),
|
|
3182
|
+
...installGlobally && { global: "1" },
|
|
3183
|
+
skillFiles: JSON.stringify(skillFiles)
|
|
3184
|
+
});
|
|
3041
3185
|
if (successful.length > 0 && installGlobally && normalizedSource) {
|
|
3042
3186
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3187
|
+
let cachedTree;
|
|
3188
|
+
if (parsed.type === "github" && !blobResult) {
|
|
3189
|
+
const token = getGitHubToken();
|
|
3190
|
+
cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, token);
|
|
3191
|
+
}
|
|
3043
3192
|
for (const skill of selectedSkills) {
|
|
3044
3193
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3045
3194
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
@@ -3048,8 +3197,11 @@ async function runAdd(args, options = {}) {
|
|
|
3048
3197
|
if (blobResult && skillPathValue) {
|
|
3049
3198
|
const hash = getSkillFolderHashFromTree(blobResult.tree, skillPathValue);
|
|
3050
3199
|
if (hash) skillFolderHash = hash;
|
|
3051
|
-
} else if (parsed.type === "github" && skillPathValue) {
|
|
3052
|
-
const hash =
|
|
3200
|
+
} else if (parsed.type === "github" && skillPathValue && cachedTree) {
|
|
3201
|
+
const hash = getSkillFolderHashFromTree(cachedTree, skillPathValue);
|
|
3202
|
+
if (hash) skillFolderHash = hash;
|
|
3203
|
+
} else if (skillPathValue && tempDir) {
|
|
3204
|
+
const hash = await computeSkillFolderHash(join(tempDir, dirname(skillPathValue)));
|
|
3053
3205
|
if (hash) skillFolderHash = hash;
|
|
3054
3206
|
}
|
|
3055
3207
|
await addSkillToLock(skill.name, {
|
|
@@ -3070,10 +3222,12 @@ async function runAdd(args, options = {}) {
|
|
|
3070
3222
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3071
3223
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
3072
3224
|
const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
|
|
3225
|
+
const skillPathValue = skillFiles[skill.name];
|
|
3073
3226
|
await addSkillToLocalLock(skill.name, {
|
|
3074
3227
|
source: lockSource || parsed.url,
|
|
3075
3228
|
ref: parsed.ref,
|
|
3076
3229
|
sourceType: parsed.type,
|
|
3230
|
+
...skillPathValue && { skillPath: skillPathValue },
|
|
3077
3231
|
computedHash
|
|
3078
3232
|
}, cwd);
|
|
3079
3233
|
} catch {}
|
|
@@ -3258,9 +3412,9 @@ async function searchSkillsAPI(query) {
|
|
|
3258
3412
|
const res = await fetch(url);
|
|
3259
3413
|
if (!res.ok) return [];
|
|
3260
3414
|
return (await res.json()).skills.map((skill) => ({
|
|
3261
|
-
name: skill.name,
|
|
3262
|
-
slug: skill.id,
|
|
3263
|
-
source: skill.source || "",
|
|
3415
|
+
name: sanitizeMetadata(skill.name),
|
|
3416
|
+
slug: sanitizeMetadata(skill.id),
|
|
3417
|
+
source: sanitizeMetadata(skill.source || ""),
|
|
3264
3418
|
installs: skill.installs
|
|
3265
3419
|
})).sort((a, b) => (b.installs || 0) - (a.installs || 0));
|
|
3266
3420
|
} catch {
|
|
@@ -3873,7 +4027,7 @@ async function runList(args) {
|
|
|
3873
4027
|
const shortPath = shortenPath(skill.canonicalPath, cwd);
|
|
3874
4028
|
const agentNames = skill.agents.map((a) => agents[a].displayName);
|
|
3875
4029
|
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$1}`;
|
|
3876
|
-
console.log(`${prefix}${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
|
|
4030
|
+
console.log(`${prefix}${CYAN}${sanitizeMetadata(skill.name)}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
|
|
3877
4031
|
console.log(`${prefix} ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
|
|
3878
4032
|
}
|
|
3879
4033
|
console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
|
|
@@ -4101,18 +4255,25 @@ function formatSourceInput(sourceUrl, ref) {
|
|
|
4101
4255
|
if (!ref) return sourceUrl;
|
|
4102
4256
|
return `${sourceUrl}#${ref}`;
|
|
4103
4257
|
}
|
|
4258
|
+
function deriveSkillFolder(skillPath) {
|
|
4259
|
+
let folder = skillPath;
|
|
4260
|
+
if (folder.endsWith("/SKILL.md")) folder = folder.slice(0, -9);
|
|
4261
|
+
else if (folder.endsWith("SKILL.md")) folder = folder.slice(0, -8);
|
|
4262
|
+
if (folder.endsWith("/")) folder = folder.slice(0, -1);
|
|
4263
|
+
return folder;
|
|
4264
|
+
}
|
|
4265
|
+
function appendFolderAndRef(source, skillPath, ref) {
|
|
4266
|
+
const folder = deriveSkillFolder(skillPath);
|
|
4267
|
+
const withFolder = folder ? `${source}/${folder}` : source;
|
|
4268
|
+
return ref ? `${withFolder}#${ref}` : withFolder;
|
|
4269
|
+
}
|
|
4104
4270
|
function buildUpdateInstallSource(entry) {
|
|
4105
4271
|
if (!entry.skillPath) return formatSourceInput(entry.sourceUrl, entry.ref);
|
|
4106
|
-
|
|
4107
|
-
if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
|
|
4108
|
-
else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
|
|
4109
|
-
if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
|
|
4110
|
-
let installSource = skillFolder ? `${entry.source}/${skillFolder}` : entry.source;
|
|
4111
|
-
if (entry.ref) installSource = `${installSource}#${entry.ref}`;
|
|
4112
|
-
return installSource;
|
|
4272
|
+
return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
|
|
4113
4273
|
}
|
|
4114
4274
|
function buildLocalUpdateSource(entry) {
|
|
4115
|
-
return formatSourceInput(entry.source, entry.ref);
|
|
4275
|
+
if (!entry.skillPath) return formatSourceInput(entry.source, entry.ref);
|
|
4276
|
+
return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
|
|
4116
4277
|
}
|
|
4117
4278
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4118
4279
|
function getVersion() {
|
|
@@ -4451,10 +4612,10 @@ function printSkippedSkills(skipped) {
|
|
|
4451
4612
|
for (const [source, skills] of grouped) {
|
|
4452
4613
|
if (skills.length === 1) {
|
|
4453
4614
|
const skill = skills[0];
|
|
4454
|
-
console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
|
|
4615
|
+
console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)} ${DIM}(${skill.reason})${RESET}`);
|
|
4455
4616
|
} else {
|
|
4456
4617
|
const reason = skills[0].reason;
|
|
4457
|
-
const names = skills.map((s) => s.name).join(", ");
|
|
4618
|
+
const names = skills.map((s) => sanitizeMetadata(s.name)).join(", ");
|
|
4458
4619
|
console.log(` ${TEXT}•${RESET} ${names} ${DIM}(${reason})${RESET}`);
|
|
4459
4620
|
}
|
|
4460
4621
|
console.log(` ${DIM}To update: ${TEXT}npx skills add ${source} -g -y${RESET}`);
|
|
@@ -4515,7 +4676,7 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4515
4676
|
}
|
|
4516
4677
|
for (let i = 0; i < checkable.length; i++) {
|
|
4517
4678
|
const { name: skillName, entry } = checkable[i];
|
|
4518
|
-
process.stdout.write(`\r${DIM}Checking global skill ${i + 1}/${checkable.length}: ${skillName}${RESET}\x1b[K`);
|
|
4679
|
+
process.stdout.write(`\r${DIM}Checking global skill ${i + 1}/${checkable.length}: ${sanitizeMetadata(skillName)}${RESET}\x1b[K`);
|
|
4519
4680
|
try {
|
|
4520
4681
|
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token, entry.ref);
|
|
4521
4682
|
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
@@ -4554,12 +4715,13 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4554
4715
|
console.log(`${TEXT}Found ${updates.length} global update(s)${RESET}`);
|
|
4555
4716
|
console.log();
|
|
4556
4717
|
for (const update of updates) {
|
|
4557
|
-
|
|
4718
|
+
const safeName = sanitizeMetadata(update.name);
|
|
4719
|
+
console.log(`${TEXT}Updating ${safeName}...${RESET}`);
|
|
4558
4720
|
const installUrl = buildUpdateInstallSource(update.entry);
|
|
4559
4721
|
const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
|
|
4560
4722
|
if (!existsSync(cliEntry)) {
|
|
4561
4723
|
failCount++;
|
|
4562
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4724
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
4563
4725
|
continue;
|
|
4564
4726
|
}
|
|
4565
4727
|
if (spawnSync(process.execPath, [
|
|
@@ -4578,10 +4740,10 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4578
4740
|
shell: process.platform === "win32"
|
|
4579
4741
|
}).status === 0) {
|
|
4580
4742
|
successCount++;
|
|
4581
|
-
console.log(` ${TEXT}✓${RESET} Updated ${
|
|
4743
|
+
console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
|
|
4582
4744
|
} else {
|
|
4583
4745
|
failCount++;
|
|
4584
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4746
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
|
|
4585
4747
|
}
|
|
4586
4748
|
}
|
|
4587
4749
|
printSkippedSkills(skipped);
|
|
@@ -4606,21 +4768,35 @@ async function updateProjectSkills(skillFilter) {
|
|
|
4606
4768
|
foundCount: 0
|
|
4607
4769
|
};
|
|
4608
4770
|
}
|
|
4609
|
-
|
|
4771
|
+
const updatable = projectSkills.filter((s) => s.entry.skillPath);
|
|
4772
|
+
const legacy = projectSkills.filter((s) => !s.entry.skillPath);
|
|
4773
|
+
if (updatable.length === 0) {
|
|
4774
|
+
console.log(`${DIM}No project skills can be updated in place.${RESET}`);
|
|
4775
|
+
printLegacyProjectSkills(legacy);
|
|
4776
|
+
return {
|
|
4777
|
+
successCount,
|
|
4778
|
+
failCount,
|
|
4779
|
+
foundCount: projectSkills.length
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4782
|
+
console.log(`${TEXT}Refreshing ${updatable.length} project skill(s)...${RESET}`);
|
|
4610
4783
|
console.log();
|
|
4611
|
-
for (const skill of
|
|
4612
|
-
|
|
4784
|
+
for (const skill of updatable) {
|
|
4785
|
+
const safeName = sanitizeMetadata(skill.name);
|
|
4786
|
+
console.log(`${TEXT}Updating ${safeName}...${RESET}`);
|
|
4613
4787
|
const installUrl = buildLocalUpdateSource(skill.entry);
|
|
4614
4788
|
const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
|
|
4615
4789
|
if (!existsSync(cliEntry)) {
|
|
4616
4790
|
failCount++;
|
|
4617
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4791
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
4618
4792
|
continue;
|
|
4619
4793
|
}
|
|
4620
4794
|
if (spawnSync(process.execPath, [
|
|
4621
4795
|
cliEntry,
|
|
4622
4796
|
"add",
|
|
4623
4797
|
installUrl,
|
|
4798
|
+
"--skill",
|
|
4799
|
+
skill.name,
|
|
4624
4800
|
"-y"
|
|
4625
4801
|
], {
|
|
4626
4802
|
stdio: [
|
|
@@ -4632,18 +4808,29 @@ async function updateProjectSkills(skillFilter) {
|
|
|
4632
4808
|
shell: process.platform === "win32"
|
|
4633
4809
|
}).status === 0) {
|
|
4634
4810
|
successCount++;
|
|
4635
|
-
console.log(` ${TEXT}✓${RESET} Updated ${
|
|
4811
|
+
console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
|
|
4636
4812
|
} else {
|
|
4637
4813
|
failCount++;
|
|
4638
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4814
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
|
|
4639
4815
|
}
|
|
4640
4816
|
}
|
|
4817
|
+
printLegacyProjectSkills(legacy);
|
|
4641
4818
|
return {
|
|
4642
4819
|
successCount,
|
|
4643
4820
|
failCount,
|
|
4644
4821
|
foundCount: projectSkills.length
|
|
4645
4822
|
};
|
|
4646
4823
|
}
|
|
4824
|
+
function printLegacyProjectSkills(legacy) {
|
|
4825
|
+
if (legacy.length === 0) return;
|
|
4826
|
+
console.log();
|
|
4827
|
+
console.log(`${DIM}${legacy.length} project skill(s) cannot be updated automatically (installed before skillPath tracking):${RESET}`);
|
|
4828
|
+
for (const skill of legacy) {
|
|
4829
|
+
const reinstall = formatSourceInput(skill.entry.source, skill.entry.ref);
|
|
4830
|
+
console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)}`);
|
|
4831
|
+
console.log(` ${DIM}To refresh: ${TEXT}npx skills add ${reinstall} -y${RESET}`);
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4647
4834
|
async function runUpdate(args = []) {
|
|
4648
4835
|
const options = parseUpdateOptions(args);
|
|
4649
4836
|
const scope = await resolveUpdateScope(options);
|
|
@@ -4755,5 +4942,5 @@ async function main() {
|
|
|
4755
4942
|
console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
|
|
4756
4943
|
}
|
|
4757
4944
|
}
|
|
4758
|
-
main();
|
|
4945
|
+
main().finally(() => flushTelemetry().then(() => process.exit(0)));
|
|
4759
4946
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skills",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "The open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"agent-skills",
|
|
37
37
|
"skills",
|
|
38
38
|
"ai-agents",
|
|
39
|
+
"aider-desk",
|
|
39
40
|
"amp",
|
|
40
41
|
"antigravity",
|
|
41
42
|
"augment",
|
|
@@ -43,7 +44,10 @@
|
|
|
43
44
|
"claude-code",
|
|
44
45
|
"openclaw",
|
|
45
46
|
"cline",
|
|
47
|
+
"codearts-agent",
|
|
46
48
|
"codebuddy",
|
|
49
|
+
"codemaker",
|
|
50
|
+
"codestudio",
|
|
47
51
|
"codex",
|
|
48
52
|
"command-code",
|
|
49
53
|
"continue",
|
|
@@ -51,8 +55,10 @@
|
|
|
51
55
|
"crush",
|
|
52
56
|
"cursor",
|
|
53
57
|
"deepagents",
|
|
58
|
+
"devin",
|
|
54
59
|
"droid",
|
|
55
60
|
"firebender",
|
|
61
|
+
"forgecode",
|
|
56
62
|
"gemini-cli",
|
|
57
63
|
"github-copilot",
|
|
58
64
|
"goose",
|
|
@@ -71,7 +77,9 @@
|
|
|
71
77
|
"qoder",
|
|
72
78
|
"qwen-code",
|
|
73
79
|
"replit",
|
|
80
|
+
"rovodev",
|
|
74
81
|
"roo",
|
|
82
|
+
"tabnine-cli",
|
|
75
83
|
"trae",
|
|
76
84
|
"trae-cn",
|
|
77
85
|
"warp",
|