skills 1.5.1 → 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 +250 -64
- 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;
|
|
@@ -404,7 +442,13 @@ async function cloneRepo(url, ref) {
|
|
|
404
442
|
...process.env,
|
|
405
443
|
GIT_TERMINAL_PROMPT: "0",
|
|
406
444
|
GIT_LFS_SKIP_SMUDGE: "1"
|
|
407
|
-
}
|
|
445
|
+
},
|
|
446
|
+
config: [
|
|
447
|
+
"filter.lfs.required=false",
|
|
448
|
+
"filter.lfs.smudge=",
|
|
449
|
+
"filter.lfs.clean=",
|
|
450
|
+
"filter.lfs.process="
|
|
451
|
+
]
|
|
408
452
|
});
|
|
409
453
|
const cloneOptions = ref ? [
|
|
410
454
|
"--depth",
|
|
@@ -423,7 +467,7 @@ async function cloneRepo(url, ref) {
|
|
|
423
467
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
424
468
|
const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
|
|
425
469
|
const isAuthError = errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("Permission denied") || errorMessage.includes("Repository not found");
|
|
426
|
-
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);
|
|
427
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);
|
|
428
472
|
throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
|
|
429
473
|
}
|
|
@@ -539,8 +583,8 @@ async function parseSkillMd(skillMdPath, options) {
|
|
|
539
583
|
if (typeof data.name !== "string" || typeof data.description !== "string") return null;
|
|
540
584
|
if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
|
|
541
585
|
return {
|
|
542
|
-
name: data.name,
|
|
543
|
-
description: data.description,
|
|
586
|
+
name: sanitizeMetadata(data.name),
|
|
587
|
+
description: sanitizeMetadata(data.description),
|
|
544
588
|
path: dirname(skillMdPath),
|
|
545
589
|
rawContent: content,
|
|
546
590
|
metadata: data.metadata
|
|
@@ -658,6 +702,7 @@ const home = homedir();
|
|
|
658
702
|
const configHome = xdgConfig ?? join(home, ".config");
|
|
659
703
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
660
704
|
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
|
|
705
|
+
const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
|
|
661
706
|
function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
662
707
|
if (pathExists(join(homeDir, ".openclaw"))) return join(homeDir, ".openclaw/skills");
|
|
663
708
|
if (pathExists(join(homeDir, ".clawdbot"))) return join(homeDir, ".clawdbot/skills");
|
|
@@ -665,6 +710,15 @@ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
|
665
710
|
return join(homeDir, ".openclaw/skills");
|
|
666
711
|
}
|
|
667
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
|
+
},
|
|
668
722
|
amp: {
|
|
669
723
|
name: "amp",
|
|
670
724
|
displayName: "Amp",
|
|
@@ -728,6 +782,15 @@ const agents = {
|
|
|
728
782
|
return existsSync(join(home, ".cline"));
|
|
729
783
|
}
|
|
730
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
|
+
},
|
|
731
794
|
codebuddy: {
|
|
732
795
|
name: "codebuddy",
|
|
733
796
|
displayName: "CodeBuddy",
|
|
@@ -737,6 +800,24 @@ const agents = {
|
|
|
737
800
|
return existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"));
|
|
738
801
|
}
|
|
739
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
|
+
},
|
|
740
821
|
codex: {
|
|
741
822
|
name: "codex",
|
|
742
823
|
displayName: "Codex",
|
|
@@ -800,6 +881,15 @@ const agents = {
|
|
|
800
881
|
return existsSync(join(home, ".deepagents"));
|
|
801
882
|
}
|
|
802
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
|
+
},
|
|
803
893
|
droid: {
|
|
804
894
|
name: "droid",
|
|
805
895
|
displayName: "Droid",
|
|
@@ -818,6 +908,15 @@ const agents = {
|
|
|
818
908
|
return existsSync(join(home, ".firebender"));
|
|
819
909
|
}
|
|
820
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
|
+
},
|
|
821
920
|
"gemini-cli": {
|
|
822
921
|
name: "gemini-cli",
|
|
823
922
|
displayName: "Gemini CLI",
|
|
@@ -912,9 +1011,9 @@ const agents = {
|
|
|
912
1011
|
name: "mistral-vibe",
|
|
913
1012
|
displayName: "Mistral Vibe",
|
|
914
1013
|
skillsDir: ".vibe/skills",
|
|
915
|
-
globalSkillsDir: join(
|
|
1014
|
+
globalSkillsDir: join(vibeHome, "skills"),
|
|
916
1015
|
detectInstalled: async () => {
|
|
917
|
-
return existsSync(
|
|
1016
|
+
return existsSync(vibeHome);
|
|
918
1017
|
}
|
|
919
1018
|
},
|
|
920
1019
|
mux: {
|
|
@@ -981,6 +1080,15 @@ const agents = {
|
|
|
981
1080
|
return existsSync(join(process.cwd(), ".replit"));
|
|
982
1081
|
}
|
|
983
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
|
+
},
|
|
984
1092
|
roo: {
|
|
985
1093
|
name: "roo",
|
|
986
1094
|
displayName: "Roo Code",
|
|
@@ -990,6 +1098,15 @@ const agents = {
|
|
|
990
1098
|
return existsSync(join(home, ".roo"));
|
|
991
1099
|
}
|
|
992
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
|
+
},
|
|
993
1110
|
trae: {
|
|
994
1111
|
name: "trae",
|
|
995
1112
|
displayName: "Trae",
|
|
@@ -1096,6 +1213,15 @@ function isPathSafe(basePath, targetPath) {
|
|
|
1096
1213
|
const normalizedTarget = normalize(resolve(targetPath));
|
|
1097
1214
|
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
|
|
1098
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
|
+
}
|
|
1099
1225
|
function getCanonicalSkillsDir(global, cwd) {
|
|
1100
1226
|
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
|
|
1101
1227
|
}
|
|
@@ -1236,7 +1362,6 @@ const EXCLUDE_DIRS = new Set([
|
|
|
1236
1362
|
]);
|
|
1237
1363
|
const isExcluded = (name, isDirectory = false) => {
|
|
1238
1364
|
if (EXCLUDE_FILES.has(name)) return true;
|
|
1239
|
-
if (name.startsWith(".")) return true;
|
|
1240
1365
|
if (isDirectory && EXCLUDE_DIRS.has(name)) return true;
|
|
1241
1366
|
return false;
|
|
1242
1367
|
};
|
|
@@ -1492,8 +1617,8 @@ async function listInstalledSkills(options = {}) {
|
|
|
1492
1617
|
for (const scope of scopes) try {
|
|
1493
1618
|
const entries = await readdir(scope.path, { withFileTypes: true });
|
|
1494
1619
|
for (const entry of entries) {
|
|
1495
|
-
if (!entry.isDirectory()) continue;
|
|
1496
1620
|
const skillDir = join(scope.path, entry.name);
|
|
1621
|
+
if (!await isDirEntryOrSymlinkToDir(entry, skillDir)) continue;
|
|
1497
1622
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
1498
1623
|
try {
|
|
1499
1624
|
await stat(skillMdPath);
|
|
@@ -1542,8 +1667,8 @@ async function listInstalledSkills(options = {}) {
|
|
|
1542
1667
|
if (!found) try {
|
|
1543
1668
|
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1544
1669
|
for (const agentEntry of agentEntries) {
|
|
1545
|
-
if (!agentEntry.isDirectory()) continue;
|
|
1546
1670
|
const candidateDir = join(agentBase, agentEntry.name);
|
|
1671
|
+
if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
|
|
1547
1672
|
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
1548
1673
|
try {
|
|
1549
1674
|
const candidateSkillMd = join(candidateDir, "SKILL.md");
|
|
@@ -1602,6 +1727,7 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
|
|
|
1602
1727
|
return null;
|
|
1603
1728
|
}
|
|
1604
1729
|
}
|
|
1730
|
+
const pendingTelemetry = [];
|
|
1605
1731
|
function track(data) {
|
|
1606
1732
|
if (!isEnabled()) return;
|
|
1607
1733
|
try {
|
|
@@ -1609,9 +1735,15 @@ function track(data) {
|
|
|
1609
1735
|
if (cliVersion) params.set("v", cliVersion);
|
|
1610
1736
|
if (isCI()) params.set("ci", "1");
|
|
1611
1737
|
for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
|
|
1612
|
-
fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
|
|
1738
|
+
const p = fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {}).then(() => {});
|
|
1739
|
+
pendingTelemetry.push(p);
|
|
1613
1740
|
} catch {}
|
|
1614
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
|
+
}
|
|
1615
1747
|
var ProviderRegistryImpl = class {
|
|
1616
1748
|
providers = [];
|
|
1617
1749
|
register(provider) {
|
|
@@ -1749,8 +1881,8 @@ var WellKnownProvider = class {
|
|
|
1749
1881
|
const fileResults = await Promise.all(filePromises);
|
|
1750
1882
|
for (const result of fileResults) if (result) files.set(result.path, result.content);
|
|
1751
1883
|
return {
|
|
1752
|
-
name: data.name,
|
|
1753
|
-
description: data.description,
|
|
1884
|
+
name: sanitizeMetadata(data.name),
|
|
1885
|
+
description: sanitizeMetadata(data.description),
|
|
1754
1886
|
content,
|
|
1755
1887
|
installName: entry.name,
|
|
1756
1888
|
sourceUrl: skillMdUrl,
|
|
@@ -2123,12 +2255,14 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
2123
2255
|
if (!data.name || !data.description) continue;
|
|
2124
2256
|
if (typeof data.name !== "string" || typeof data.description !== "string") continue;
|
|
2125
2257
|
if (data.metadata?.internal === true && !options.includeInternal) continue;
|
|
2258
|
+
const safeName = sanitizeMetadata(data.name);
|
|
2259
|
+
const safeDescription = sanitizeMetadata(data.description);
|
|
2126
2260
|
parsedSkills.push({
|
|
2127
2261
|
mdPath,
|
|
2128
|
-
name:
|
|
2129
|
-
description:
|
|
2262
|
+
name: safeName,
|
|
2263
|
+
description: safeDescription,
|
|
2130
2264
|
content,
|
|
2131
|
-
slug: toSkillSlug(
|
|
2265
|
+
slug: toSkillSlug(safeName),
|
|
2132
2266
|
metadata: data.metadata
|
|
2133
2267
|
});
|
|
2134
2268
|
}
|
|
@@ -2165,7 +2299,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
2165
2299
|
tree
|
|
2166
2300
|
};
|
|
2167
2301
|
}
|
|
2168
|
-
var version$1 = "1.5.
|
|
2302
|
+
var version$1 = "1.5.2";
|
|
2169
2303
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
2170
2304
|
async function isSourcePrivate(source) {
|
|
2171
2305
|
const ownerRepo = parseOwnerRepo(source);
|
|
@@ -2191,7 +2325,7 @@ function socketLabel(audit) {
|
|
|
2191
2325
|
return count > 0 ? import_picocolors.default.red(`${count} alert${count !== 1 ? "s" : ""}`) : import_picocolors.default.green("0 alerts");
|
|
2192
2326
|
}
|
|
2193
2327
|
function padEnd(str, width) {
|
|
2194
|
-
const visible = str
|
|
2328
|
+
const visible = stripTerminalEscapes(str);
|
|
2195
2329
|
const pad = Math.max(0, width - visible.length);
|
|
2196
2330
|
return str + " ".repeat(pad);
|
|
2197
2331
|
}
|
|
@@ -2514,6 +2648,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2514
2648
|
process.exit(0);
|
|
2515
2649
|
}
|
|
2516
2650
|
}
|
|
2651
|
+
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
2652
|
+
const wellKnownPrivacyPromise = isSourcePrivate(sourceIdentifier).catch(() => null);
|
|
2517
2653
|
spinner.start("Installing skills...");
|
|
2518
2654
|
const results = [];
|
|
2519
2655
|
for (const skill of selectedSkills) for (const agent of targetAgents) {
|
|
@@ -2531,10 +2667,9 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2531
2667
|
console.log();
|
|
2532
2668
|
const successful = results.filter((r) => r.success);
|
|
2533
2669
|
const failed = results.filter((r) => !r.success);
|
|
2534
|
-
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
2535
2670
|
const skillFiles = {};
|
|
2536
2671
|
for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
|
|
2537
|
-
if (await
|
|
2672
|
+
if (await wellKnownPrivacyPromise !== true) track({
|
|
2538
2673
|
event: "install",
|
|
2539
2674
|
source: sourceIdentifier,
|
|
2540
2675
|
skills: selectedSkills.map((s) => s.installName).join(","),
|
|
@@ -2646,7 +2781,14 @@ async function runAdd(args, options = {}) {
|
|
|
2646
2781
|
spinner.start("Parsing source...");
|
|
2647
2782
|
const parsed = parseSource(source);
|
|
2648
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)}` : ""}`);
|
|
2649
|
-
|
|
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) {
|
|
2650
2792
|
console.log();
|
|
2651
2793
|
M.warn(import_picocolors.default.yellow(import_picocolors.default.bold("⚠ OpenClaw skills are unverified community submissions.")));
|
|
2652
2794
|
M.message(import_picocolors.default.yellow("This source contains user-submitted skills that have not been reviewed for safety or quality."));
|
|
@@ -2681,7 +2823,11 @@ async function runAdd(args, options = {}) {
|
|
|
2681
2823
|
fullDepth: options.fullDepth
|
|
2682
2824
|
});
|
|
2683
2825
|
} else if (parsed.type === "github" && !options.fullDepth) {
|
|
2684
|
-
const BLOB_ALLOWED_OWNERS = [
|
|
2826
|
+
const BLOB_ALLOWED_OWNERS = [
|
|
2827
|
+
"vercel",
|
|
2828
|
+
"vercel-labs",
|
|
2829
|
+
"heygen-com"
|
|
2830
|
+
];
|
|
2685
2831
|
const ownerRepo = getOwnerRepo(parsed);
|
|
2686
2832
|
const owner = ownerRepo?.split("/")[0]?.toLowerCase();
|
|
2687
2833
|
if (ownerRepo && owner && BLOB_ALLOWED_OWNERS.includes(owner)) {
|
|
@@ -3019,18 +3165,8 @@ async function runAdd(args, options = {}) {
|
|
|
3019
3165
|
else continue;
|
|
3020
3166
|
const normalizedSource = getOwnerRepo(parsed);
|
|
3021
3167
|
const lockSource = parsed.url.startsWith("git@") ? parsed.url : normalizedSource;
|
|
3022
|
-
if (normalizedSource) {
|
|
3023
|
-
|
|
3024
|
-
if (ownerRepo) {
|
|
3025
|
-
if (await isRepoPrivate(ownerRepo.owner, ownerRepo.repo) === false) track({
|
|
3026
|
-
event: "install",
|
|
3027
|
-
source: normalizedSource,
|
|
3028
|
-
skills: selectedSkills.map((s) => s.name).join(","),
|
|
3029
|
-
agents: targetAgents.join(","),
|
|
3030
|
-
...installGlobally && { global: "1" },
|
|
3031
|
-
skillFiles: JSON.stringify(skillFiles)
|
|
3032
|
-
});
|
|
3033
|
-
} else track({
|
|
3168
|
+
if (normalizedSource) if (parseOwnerRepo(normalizedSource)) {
|
|
3169
|
+
if (await repoPrivacyPromise === false) track({
|
|
3034
3170
|
event: "install",
|
|
3035
3171
|
source: normalizedSource,
|
|
3036
3172
|
skills: selectedSkills.map((s) => s.name).join(","),
|
|
@@ -3038,9 +3174,21 @@ async function runAdd(args, options = {}) {
|
|
|
3038
3174
|
...installGlobally && { global: "1" },
|
|
3039
3175
|
skillFiles: JSON.stringify(skillFiles)
|
|
3040
3176
|
});
|
|
3041
|
-
}
|
|
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
|
+
});
|
|
3042
3185
|
if (successful.length > 0 && installGlobally && normalizedSource) {
|
|
3043
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
|
+
}
|
|
3044
3192
|
for (const skill of selectedSkills) {
|
|
3045
3193
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3046
3194
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
@@ -3049,8 +3197,11 @@ async function runAdd(args, options = {}) {
|
|
|
3049
3197
|
if (blobResult && skillPathValue) {
|
|
3050
3198
|
const hash = getSkillFolderHashFromTree(blobResult.tree, skillPathValue);
|
|
3051
3199
|
if (hash) skillFolderHash = hash;
|
|
3052
|
-
} else if (parsed.type === "github" && skillPathValue) {
|
|
3053
|
-
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)));
|
|
3054
3205
|
if (hash) skillFolderHash = hash;
|
|
3055
3206
|
}
|
|
3056
3207
|
await addSkillToLock(skill.name, {
|
|
@@ -3071,10 +3222,12 @@ async function runAdd(args, options = {}) {
|
|
|
3071
3222
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3072
3223
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
3073
3224
|
const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
|
|
3225
|
+
const skillPathValue = skillFiles[skill.name];
|
|
3074
3226
|
await addSkillToLocalLock(skill.name, {
|
|
3075
3227
|
source: lockSource || parsed.url,
|
|
3076
3228
|
ref: parsed.ref,
|
|
3077
3229
|
sourceType: parsed.type,
|
|
3230
|
+
...skillPathValue && { skillPath: skillPathValue },
|
|
3078
3231
|
computedHash
|
|
3079
3232
|
}, cwd);
|
|
3080
3233
|
} catch {}
|
|
@@ -3259,9 +3412,9 @@ async function searchSkillsAPI(query) {
|
|
|
3259
3412
|
const res = await fetch(url);
|
|
3260
3413
|
if (!res.ok) return [];
|
|
3261
3414
|
return (await res.json()).skills.map((skill) => ({
|
|
3262
|
-
name: skill.name,
|
|
3263
|
-
slug: skill.id,
|
|
3264
|
-
source: skill.source || "",
|
|
3415
|
+
name: sanitizeMetadata(skill.name),
|
|
3416
|
+
slug: sanitizeMetadata(skill.id),
|
|
3417
|
+
source: sanitizeMetadata(skill.source || ""),
|
|
3265
3418
|
installs: skill.installs
|
|
3266
3419
|
})).sort((a, b) => (b.installs || 0) - (a.installs || 0));
|
|
3267
3420
|
} catch {
|
|
@@ -3874,7 +4027,7 @@ async function runList(args) {
|
|
|
3874
4027
|
const shortPath = shortenPath(skill.canonicalPath, cwd);
|
|
3875
4028
|
const agentNames = skill.agents.map((a) => agents[a].displayName);
|
|
3876
4029
|
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$1}`;
|
|
3877
|
-
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}`);
|
|
3878
4031
|
console.log(`${prefix} ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
|
|
3879
4032
|
}
|
|
3880
4033
|
console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
|
|
@@ -4102,18 +4255,25 @@ function formatSourceInput(sourceUrl, ref) {
|
|
|
4102
4255
|
if (!ref) return sourceUrl;
|
|
4103
4256
|
return `${sourceUrl}#${ref}`;
|
|
4104
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
|
+
}
|
|
4105
4270
|
function buildUpdateInstallSource(entry) {
|
|
4106
4271
|
if (!entry.skillPath) return formatSourceInput(entry.sourceUrl, entry.ref);
|
|
4107
|
-
|
|
4108
|
-
if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
|
|
4109
|
-
else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
|
|
4110
|
-
if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
|
|
4111
|
-
let installSource = skillFolder ? `${entry.source}/${skillFolder}` : entry.source;
|
|
4112
|
-
if (entry.ref) installSource = `${installSource}#${entry.ref}`;
|
|
4113
|
-
return installSource;
|
|
4272
|
+
return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
|
|
4114
4273
|
}
|
|
4115
4274
|
function buildLocalUpdateSource(entry) {
|
|
4116
|
-
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);
|
|
4117
4277
|
}
|
|
4118
4278
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4119
4279
|
function getVersion() {
|
|
@@ -4452,10 +4612,10 @@ function printSkippedSkills(skipped) {
|
|
|
4452
4612
|
for (const [source, skills] of grouped) {
|
|
4453
4613
|
if (skills.length === 1) {
|
|
4454
4614
|
const skill = skills[0];
|
|
4455
|
-
console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
|
|
4615
|
+
console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)} ${DIM}(${skill.reason})${RESET}`);
|
|
4456
4616
|
} else {
|
|
4457
4617
|
const reason = skills[0].reason;
|
|
4458
|
-
const names = skills.map((s) => s.name).join(", ");
|
|
4618
|
+
const names = skills.map((s) => sanitizeMetadata(s.name)).join(", ");
|
|
4459
4619
|
console.log(` ${TEXT}•${RESET} ${names} ${DIM}(${reason})${RESET}`);
|
|
4460
4620
|
}
|
|
4461
4621
|
console.log(` ${DIM}To update: ${TEXT}npx skills add ${source} -g -y${RESET}`);
|
|
@@ -4516,7 +4676,7 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4516
4676
|
}
|
|
4517
4677
|
for (let i = 0; i < checkable.length; i++) {
|
|
4518
4678
|
const { name: skillName, entry } = checkable[i];
|
|
4519
|
-
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`);
|
|
4520
4680
|
try {
|
|
4521
4681
|
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token, entry.ref);
|
|
4522
4682
|
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
@@ -4555,12 +4715,13 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4555
4715
|
console.log(`${TEXT}Found ${updates.length} global update(s)${RESET}`);
|
|
4556
4716
|
console.log();
|
|
4557
4717
|
for (const update of updates) {
|
|
4558
|
-
|
|
4718
|
+
const safeName = sanitizeMetadata(update.name);
|
|
4719
|
+
console.log(`${TEXT}Updating ${safeName}...${RESET}`);
|
|
4559
4720
|
const installUrl = buildUpdateInstallSource(update.entry);
|
|
4560
4721
|
const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
|
|
4561
4722
|
if (!existsSync(cliEntry)) {
|
|
4562
4723
|
failCount++;
|
|
4563
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4724
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
4564
4725
|
continue;
|
|
4565
4726
|
}
|
|
4566
4727
|
if (spawnSync(process.execPath, [
|
|
@@ -4579,10 +4740,10 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4579
4740
|
shell: process.platform === "win32"
|
|
4580
4741
|
}).status === 0) {
|
|
4581
4742
|
successCount++;
|
|
4582
|
-
console.log(` ${TEXT}✓${RESET} Updated ${
|
|
4743
|
+
console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
|
|
4583
4744
|
} else {
|
|
4584
4745
|
failCount++;
|
|
4585
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4746
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
|
|
4586
4747
|
}
|
|
4587
4748
|
}
|
|
4588
4749
|
printSkippedSkills(skipped);
|
|
@@ -4607,21 +4768,35 @@ async function updateProjectSkills(skillFilter) {
|
|
|
4607
4768
|
foundCount: 0
|
|
4608
4769
|
};
|
|
4609
4770
|
}
|
|
4610
|
-
|
|
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}`);
|
|
4611
4783
|
console.log();
|
|
4612
|
-
for (const skill of
|
|
4613
|
-
|
|
4784
|
+
for (const skill of updatable) {
|
|
4785
|
+
const safeName = sanitizeMetadata(skill.name);
|
|
4786
|
+
console.log(`${TEXT}Updating ${safeName}...${RESET}`);
|
|
4614
4787
|
const installUrl = buildLocalUpdateSource(skill.entry);
|
|
4615
4788
|
const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
|
|
4616
4789
|
if (!existsSync(cliEntry)) {
|
|
4617
4790
|
failCount++;
|
|
4618
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4791
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
4619
4792
|
continue;
|
|
4620
4793
|
}
|
|
4621
4794
|
if (spawnSync(process.execPath, [
|
|
4622
4795
|
cliEntry,
|
|
4623
4796
|
"add",
|
|
4624
4797
|
installUrl,
|
|
4798
|
+
"--skill",
|
|
4799
|
+
skill.name,
|
|
4625
4800
|
"-y"
|
|
4626
4801
|
], {
|
|
4627
4802
|
stdio: [
|
|
@@ -4633,18 +4808,29 @@ async function updateProjectSkills(skillFilter) {
|
|
|
4633
4808
|
shell: process.platform === "win32"
|
|
4634
4809
|
}).status === 0) {
|
|
4635
4810
|
successCount++;
|
|
4636
|
-
console.log(` ${TEXT}✓${RESET} Updated ${
|
|
4811
|
+
console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
|
|
4637
4812
|
} else {
|
|
4638
4813
|
failCount++;
|
|
4639
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4814
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
|
|
4640
4815
|
}
|
|
4641
4816
|
}
|
|
4817
|
+
printLegacyProjectSkills(legacy);
|
|
4642
4818
|
return {
|
|
4643
4819
|
successCount,
|
|
4644
4820
|
failCount,
|
|
4645
4821
|
foundCount: projectSkills.length
|
|
4646
4822
|
};
|
|
4647
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
|
+
}
|
|
4648
4834
|
async function runUpdate(args = []) {
|
|
4649
4835
|
const options = parseUpdateOptions(args);
|
|
4650
4836
|
const scope = await resolveUpdateScope(options);
|
|
@@ -4756,5 +4942,5 @@ async function main() {
|
|
|
4756
4942
|
console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
|
|
4757
4943
|
}
|
|
4758
4944
|
}
|
|
4759
|
-
main();
|
|
4945
|
+
main().finally(() => flushTelemetry().then(() => process.exit(0)));
|
|
4760
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",
|