skills 1.5.1 → 1.5.3
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 +259 -64
- package/package.json +10 -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 [50 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, Dexto, Warp | `cline`, `dexto`, `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,24 @@ 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
|
+
},
|
|
893
|
+
dexto: {
|
|
894
|
+
name: "dexto",
|
|
895
|
+
displayName: "Dexto",
|
|
896
|
+
skillsDir: ".agents/skills",
|
|
897
|
+
globalSkillsDir: join(home, ".agents/skills"),
|
|
898
|
+
detectInstalled: async () => {
|
|
899
|
+
return existsSync(join(home, ".dexto"));
|
|
900
|
+
}
|
|
901
|
+
},
|
|
803
902
|
droid: {
|
|
804
903
|
name: "droid",
|
|
805
904
|
displayName: "Droid",
|
|
@@ -818,6 +917,15 @@ const agents = {
|
|
|
818
917
|
return existsSync(join(home, ".firebender"));
|
|
819
918
|
}
|
|
820
919
|
},
|
|
920
|
+
forgecode: {
|
|
921
|
+
name: "forgecode",
|
|
922
|
+
displayName: "ForgeCode",
|
|
923
|
+
skillsDir: ".forge/skills",
|
|
924
|
+
globalSkillsDir: join(home, ".forge/skills"),
|
|
925
|
+
detectInstalled: async () => {
|
|
926
|
+
return existsSync(join(home, ".forge"));
|
|
927
|
+
}
|
|
928
|
+
},
|
|
821
929
|
"gemini-cli": {
|
|
822
930
|
name: "gemini-cli",
|
|
823
931
|
displayName: "Gemini CLI",
|
|
@@ -912,9 +1020,9 @@ const agents = {
|
|
|
912
1020
|
name: "mistral-vibe",
|
|
913
1021
|
displayName: "Mistral Vibe",
|
|
914
1022
|
skillsDir: ".vibe/skills",
|
|
915
|
-
globalSkillsDir: join(
|
|
1023
|
+
globalSkillsDir: join(vibeHome, "skills"),
|
|
916
1024
|
detectInstalled: async () => {
|
|
917
|
-
return existsSync(
|
|
1025
|
+
return existsSync(vibeHome);
|
|
918
1026
|
}
|
|
919
1027
|
},
|
|
920
1028
|
mux: {
|
|
@@ -981,6 +1089,15 @@ const agents = {
|
|
|
981
1089
|
return existsSync(join(process.cwd(), ".replit"));
|
|
982
1090
|
}
|
|
983
1091
|
},
|
|
1092
|
+
rovodev: {
|
|
1093
|
+
name: "rovodev",
|
|
1094
|
+
displayName: "Rovo Dev",
|
|
1095
|
+
skillsDir: ".rovodev/skills",
|
|
1096
|
+
globalSkillsDir: join(home, ".rovodev/skills"),
|
|
1097
|
+
detectInstalled: async () => {
|
|
1098
|
+
return existsSync(join(home, ".rovodev"));
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
984
1101
|
roo: {
|
|
985
1102
|
name: "roo",
|
|
986
1103
|
displayName: "Roo Code",
|
|
@@ -990,6 +1107,15 @@ const agents = {
|
|
|
990
1107
|
return existsSync(join(home, ".roo"));
|
|
991
1108
|
}
|
|
992
1109
|
},
|
|
1110
|
+
"tabnine-cli": {
|
|
1111
|
+
name: "tabnine-cli",
|
|
1112
|
+
displayName: "Tabnine CLI",
|
|
1113
|
+
skillsDir: ".tabnine/agent/skills",
|
|
1114
|
+
globalSkillsDir: join(home, ".tabnine/agent/skills"),
|
|
1115
|
+
detectInstalled: async () => {
|
|
1116
|
+
return existsSync(join(home, ".tabnine"));
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
993
1119
|
trae: {
|
|
994
1120
|
name: "trae",
|
|
995
1121
|
displayName: "Trae",
|
|
@@ -1096,6 +1222,15 @@ function isPathSafe(basePath, targetPath) {
|
|
|
1096
1222
|
const normalizedTarget = normalize(resolve(targetPath));
|
|
1097
1223
|
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
|
|
1098
1224
|
}
|
|
1225
|
+
async function isDirEntryOrSymlinkToDir(entry, entryPath) {
|
|
1226
|
+
if (entry.isDirectory()) return true;
|
|
1227
|
+
if (!entry.isSymbolicLink()) return false;
|
|
1228
|
+
try {
|
|
1229
|
+
return (await stat(entryPath)).isDirectory();
|
|
1230
|
+
} catch {
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1099
1234
|
function getCanonicalSkillsDir(global, cwd) {
|
|
1100
1235
|
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
|
|
1101
1236
|
}
|
|
@@ -1236,7 +1371,6 @@ const EXCLUDE_DIRS = new Set([
|
|
|
1236
1371
|
]);
|
|
1237
1372
|
const isExcluded = (name, isDirectory = false) => {
|
|
1238
1373
|
if (EXCLUDE_FILES.has(name)) return true;
|
|
1239
|
-
if (name.startsWith(".")) return true;
|
|
1240
1374
|
if (isDirectory && EXCLUDE_DIRS.has(name)) return true;
|
|
1241
1375
|
return false;
|
|
1242
1376
|
};
|
|
@@ -1492,8 +1626,8 @@ async function listInstalledSkills(options = {}) {
|
|
|
1492
1626
|
for (const scope of scopes) try {
|
|
1493
1627
|
const entries = await readdir(scope.path, { withFileTypes: true });
|
|
1494
1628
|
for (const entry of entries) {
|
|
1495
|
-
if (!entry.isDirectory()) continue;
|
|
1496
1629
|
const skillDir = join(scope.path, entry.name);
|
|
1630
|
+
if (!await isDirEntryOrSymlinkToDir(entry, skillDir)) continue;
|
|
1497
1631
|
const skillMdPath = join(skillDir, "SKILL.md");
|
|
1498
1632
|
try {
|
|
1499
1633
|
await stat(skillMdPath);
|
|
@@ -1542,8 +1676,8 @@ async function listInstalledSkills(options = {}) {
|
|
|
1542
1676
|
if (!found) try {
|
|
1543
1677
|
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1544
1678
|
for (const agentEntry of agentEntries) {
|
|
1545
|
-
if (!agentEntry.isDirectory()) continue;
|
|
1546
1679
|
const candidateDir = join(agentBase, agentEntry.name);
|
|
1680
|
+
if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
|
|
1547
1681
|
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
1548
1682
|
try {
|
|
1549
1683
|
const candidateSkillMd = join(candidateDir, "SKILL.md");
|
|
@@ -1602,6 +1736,7 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
|
|
|
1602
1736
|
return null;
|
|
1603
1737
|
}
|
|
1604
1738
|
}
|
|
1739
|
+
const pendingTelemetry = [];
|
|
1605
1740
|
function track(data) {
|
|
1606
1741
|
if (!isEnabled()) return;
|
|
1607
1742
|
try {
|
|
@@ -1609,9 +1744,15 @@ function track(data) {
|
|
|
1609
1744
|
if (cliVersion) params.set("v", cliVersion);
|
|
1610
1745
|
if (isCI()) params.set("ci", "1");
|
|
1611
1746
|
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(() => {});
|
|
1747
|
+
const p = fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {}).then(() => {});
|
|
1748
|
+
pendingTelemetry.push(p);
|
|
1613
1749
|
} catch {}
|
|
1614
1750
|
}
|
|
1751
|
+
async function flushTelemetry(timeoutMs = 5e3) {
|
|
1752
|
+
if (pendingTelemetry.length === 0) return;
|
|
1753
|
+
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
1754
|
+
await Promise.race([Promise.all(pendingTelemetry), timeout]);
|
|
1755
|
+
}
|
|
1615
1756
|
var ProviderRegistryImpl = class {
|
|
1616
1757
|
providers = [];
|
|
1617
1758
|
register(provider) {
|
|
@@ -1749,8 +1890,8 @@ var WellKnownProvider = class {
|
|
|
1749
1890
|
const fileResults = await Promise.all(filePromises);
|
|
1750
1891
|
for (const result of fileResults) if (result) files.set(result.path, result.content);
|
|
1751
1892
|
return {
|
|
1752
|
-
name: data.name,
|
|
1753
|
-
description: data.description,
|
|
1893
|
+
name: sanitizeMetadata(data.name),
|
|
1894
|
+
description: sanitizeMetadata(data.description),
|
|
1754
1895
|
content,
|
|
1755
1896
|
installName: entry.name,
|
|
1756
1897
|
sourceUrl: skillMdUrl,
|
|
@@ -2123,12 +2264,14 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
2123
2264
|
if (!data.name || !data.description) continue;
|
|
2124
2265
|
if (typeof data.name !== "string" || typeof data.description !== "string") continue;
|
|
2125
2266
|
if (data.metadata?.internal === true && !options.includeInternal) continue;
|
|
2267
|
+
const safeName = sanitizeMetadata(data.name);
|
|
2268
|
+
const safeDescription = sanitizeMetadata(data.description);
|
|
2126
2269
|
parsedSkills.push({
|
|
2127
2270
|
mdPath,
|
|
2128
|
-
name:
|
|
2129
|
-
description:
|
|
2271
|
+
name: safeName,
|
|
2272
|
+
description: safeDescription,
|
|
2130
2273
|
content,
|
|
2131
|
-
slug: toSkillSlug(
|
|
2274
|
+
slug: toSkillSlug(safeName),
|
|
2132
2275
|
metadata: data.metadata
|
|
2133
2276
|
});
|
|
2134
2277
|
}
|
|
@@ -2165,7 +2308,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
2165
2308
|
tree
|
|
2166
2309
|
};
|
|
2167
2310
|
}
|
|
2168
|
-
var version$1 = "1.5.
|
|
2311
|
+
var version$1 = "1.5.3";
|
|
2169
2312
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
2170
2313
|
async function isSourcePrivate(source) {
|
|
2171
2314
|
const ownerRepo = parseOwnerRepo(source);
|
|
@@ -2191,7 +2334,7 @@ function socketLabel(audit) {
|
|
|
2191
2334
|
return count > 0 ? import_picocolors.default.red(`${count} alert${count !== 1 ? "s" : ""}`) : import_picocolors.default.green("0 alerts");
|
|
2192
2335
|
}
|
|
2193
2336
|
function padEnd(str, width) {
|
|
2194
|
-
const visible = str
|
|
2337
|
+
const visible = stripTerminalEscapes(str);
|
|
2195
2338
|
const pad = Math.max(0, width - visible.length);
|
|
2196
2339
|
return str + " ".repeat(pad);
|
|
2197
2340
|
}
|
|
@@ -2514,6 +2657,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2514
2657
|
process.exit(0);
|
|
2515
2658
|
}
|
|
2516
2659
|
}
|
|
2660
|
+
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
2661
|
+
const wellKnownPrivacyPromise = isSourcePrivate(sourceIdentifier).catch(() => null);
|
|
2517
2662
|
spinner.start("Installing skills...");
|
|
2518
2663
|
const results = [];
|
|
2519
2664
|
for (const skill of selectedSkills) for (const agent of targetAgents) {
|
|
@@ -2531,10 +2676,9 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2531
2676
|
console.log();
|
|
2532
2677
|
const successful = results.filter((r) => r.success);
|
|
2533
2678
|
const failed = results.filter((r) => !r.success);
|
|
2534
|
-
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
2535
2679
|
const skillFiles = {};
|
|
2536
2680
|
for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
|
|
2537
|
-
if (await
|
|
2681
|
+
if (await wellKnownPrivacyPromise !== true) track({
|
|
2538
2682
|
event: "install",
|
|
2539
2683
|
source: sourceIdentifier,
|
|
2540
2684
|
skills: selectedSkills.map((s) => s.installName).join(","),
|
|
@@ -2646,7 +2790,14 @@ async function runAdd(args, options = {}) {
|
|
|
2646
2790
|
spinner.start("Parsing source...");
|
|
2647
2791
|
const parsed = parseSource(source);
|
|
2648
2792
|
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
|
-
|
|
2793
|
+
const ownerRepoRaw = getOwnerRepo(parsed);
|
|
2794
|
+
const repoPrivacyPromise = (() => {
|
|
2795
|
+
if (!ownerRepoRaw) return Promise.resolve(null);
|
|
2796
|
+
const ownerRepo = parseOwnerRepo(ownerRepoRaw);
|
|
2797
|
+
if (!ownerRepo) return Promise.resolve(null);
|
|
2798
|
+
return isRepoPrivate(ownerRepo.owner, ownerRepo.repo).catch(() => null);
|
|
2799
|
+
})();
|
|
2800
|
+
if (ownerRepoRaw?.split("/")[0]?.toLowerCase() === "openclaw" && !options.dangerouslyAcceptOpenclawRisks) {
|
|
2650
2801
|
console.log();
|
|
2651
2802
|
M.warn(import_picocolors.default.yellow(import_picocolors.default.bold("⚠ OpenClaw skills are unverified community submissions.")));
|
|
2652
2803
|
M.message(import_picocolors.default.yellow("This source contains user-submitted skills that have not been reviewed for safety or quality."));
|
|
@@ -2681,7 +2832,11 @@ async function runAdd(args, options = {}) {
|
|
|
2681
2832
|
fullDepth: options.fullDepth
|
|
2682
2833
|
});
|
|
2683
2834
|
} else if (parsed.type === "github" && !options.fullDepth) {
|
|
2684
|
-
const BLOB_ALLOWED_OWNERS = [
|
|
2835
|
+
const BLOB_ALLOWED_OWNERS = [
|
|
2836
|
+
"vercel",
|
|
2837
|
+
"vercel-labs",
|
|
2838
|
+
"heygen-com"
|
|
2839
|
+
];
|
|
2685
2840
|
const ownerRepo = getOwnerRepo(parsed);
|
|
2686
2841
|
const owner = ownerRepo?.split("/")[0]?.toLowerCase();
|
|
2687
2842
|
if (ownerRepo && owner && BLOB_ALLOWED_OWNERS.includes(owner)) {
|
|
@@ -3019,18 +3174,8 @@ async function runAdd(args, options = {}) {
|
|
|
3019
3174
|
else continue;
|
|
3020
3175
|
const normalizedSource = getOwnerRepo(parsed);
|
|
3021
3176
|
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({
|
|
3177
|
+
if (normalizedSource) if (parseOwnerRepo(normalizedSource)) {
|
|
3178
|
+
if (await repoPrivacyPromise === false) track({
|
|
3034
3179
|
event: "install",
|
|
3035
3180
|
source: normalizedSource,
|
|
3036
3181
|
skills: selectedSkills.map((s) => s.name).join(","),
|
|
@@ -3038,9 +3183,21 @@ async function runAdd(args, options = {}) {
|
|
|
3038
3183
|
...installGlobally && { global: "1" },
|
|
3039
3184
|
skillFiles: JSON.stringify(skillFiles)
|
|
3040
3185
|
});
|
|
3041
|
-
}
|
|
3186
|
+
} else track({
|
|
3187
|
+
event: "install",
|
|
3188
|
+
source: normalizedSource,
|
|
3189
|
+
skills: selectedSkills.map((s) => s.name).join(","),
|
|
3190
|
+
agents: targetAgents.join(","),
|
|
3191
|
+
...installGlobally && { global: "1" },
|
|
3192
|
+
skillFiles: JSON.stringify(skillFiles)
|
|
3193
|
+
});
|
|
3042
3194
|
if (successful.length > 0 && installGlobally && normalizedSource) {
|
|
3043
3195
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3196
|
+
let cachedTree;
|
|
3197
|
+
if (parsed.type === "github" && !blobResult) {
|
|
3198
|
+
const token = getGitHubToken();
|
|
3199
|
+
cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, token);
|
|
3200
|
+
}
|
|
3044
3201
|
for (const skill of selectedSkills) {
|
|
3045
3202
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3046
3203
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
@@ -3049,8 +3206,11 @@ async function runAdd(args, options = {}) {
|
|
|
3049
3206
|
if (blobResult && skillPathValue) {
|
|
3050
3207
|
const hash = getSkillFolderHashFromTree(blobResult.tree, skillPathValue);
|
|
3051
3208
|
if (hash) skillFolderHash = hash;
|
|
3052
|
-
} else if (parsed.type === "github" && skillPathValue) {
|
|
3053
|
-
const hash =
|
|
3209
|
+
} else if (parsed.type === "github" && skillPathValue && cachedTree) {
|
|
3210
|
+
const hash = getSkillFolderHashFromTree(cachedTree, skillPathValue);
|
|
3211
|
+
if (hash) skillFolderHash = hash;
|
|
3212
|
+
} else if (skillPathValue && tempDir) {
|
|
3213
|
+
const hash = await computeSkillFolderHash(join(tempDir, dirname(skillPathValue)));
|
|
3054
3214
|
if (hash) skillFolderHash = hash;
|
|
3055
3215
|
}
|
|
3056
3216
|
await addSkillToLock(skill.name, {
|
|
@@ -3071,10 +3231,12 @@ async function runAdd(args, options = {}) {
|
|
|
3071
3231
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3072
3232
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
3073
3233
|
const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
|
|
3234
|
+
const skillPathValue = skillFiles[skill.name];
|
|
3074
3235
|
await addSkillToLocalLock(skill.name, {
|
|
3075
3236
|
source: lockSource || parsed.url,
|
|
3076
3237
|
ref: parsed.ref,
|
|
3077
3238
|
sourceType: parsed.type,
|
|
3239
|
+
...skillPathValue && { skillPath: skillPathValue },
|
|
3078
3240
|
computedHash
|
|
3079
3241
|
}, cwd);
|
|
3080
3242
|
} catch {}
|
|
@@ -3259,9 +3421,9 @@ async function searchSkillsAPI(query) {
|
|
|
3259
3421
|
const res = await fetch(url);
|
|
3260
3422
|
if (!res.ok) return [];
|
|
3261
3423
|
return (await res.json()).skills.map((skill) => ({
|
|
3262
|
-
name: skill.name,
|
|
3263
|
-
slug: skill.id,
|
|
3264
|
-
source: skill.source || "",
|
|
3424
|
+
name: sanitizeMetadata(skill.name),
|
|
3425
|
+
slug: sanitizeMetadata(skill.id),
|
|
3426
|
+
source: sanitizeMetadata(skill.source || ""),
|
|
3265
3427
|
installs: skill.installs
|
|
3266
3428
|
})).sort((a, b) => (b.installs || 0) - (a.installs || 0));
|
|
3267
3429
|
} catch {
|
|
@@ -3874,7 +4036,7 @@ async function runList(args) {
|
|
|
3874
4036
|
const shortPath = shortenPath(skill.canonicalPath, cwd);
|
|
3875
4037
|
const agentNames = skill.agents.map((a) => agents[a].displayName);
|
|
3876
4038
|
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}`);
|
|
4039
|
+
console.log(`${prefix}${CYAN}${sanitizeMetadata(skill.name)}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
|
|
3878
4040
|
console.log(`${prefix} ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
|
|
3879
4041
|
}
|
|
3880
4042
|
console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
|
|
@@ -4102,18 +4264,25 @@ function formatSourceInput(sourceUrl, ref) {
|
|
|
4102
4264
|
if (!ref) return sourceUrl;
|
|
4103
4265
|
return `${sourceUrl}#${ref}`;
|
|
4104
4266
|
}
|
|
4267
|
+
function deriveSkillFolder(skillPath) {
|
|
4268
|
+
let folder = skillPath;
|
|
4269
|
+
if (folder.endsWith("/SKILL.md")) folder = folder.slice(0, -9);
|
|
4270
|
+
else if (folder.endsWith("SKILL.md")) folder = folder.slice(0, -8);
|
|
4271
|
+
if (folder.endsWith("/")) folder = folder.slice(0, -1);
|
|
4272
|
+
return folder;
|
|
4273
|
+
}
|
|
4274
|
+
function appendFolderAndRef(source, skillPath, ref) {
|
|
4275
|
+
const folder = deriveSkillFolder(skillPath);
|
|
4276
|
+
const withFolder = folder ? `${source}/${folder}` : source;
|
|
4277
|
+
return ref ? `${withFolder}#${ref}` : withFolder;
|
|
4278
|
+
}
|
|
4105
4279
|
function buildUpdateInstallSource(entry) {
|
|
4106
4280
|
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;
|
|
4281
|
+
return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
|
|
4114
4282
|
}
|
|
4115
4283
|
function buildLocalUpdateSource(entry) {
|
|
4116
|
-
return formatSourceInput(entry.source, entry.ref);
|
|
4284
|
+
if (!entry.skillPath) return formatSourceInput(entry.source, entry.ref);
|
|
4285
|
+
return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
|
|
4117
4286
|
}
|
|
4118
4287
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4119
4288
|
function getVersion() {
|
|
@@ -4452,10 +4621,10 @@ function printSkippedSkills(skipped) {
|
|
|
4452
4621
|
for (const [source, skills] of grouped) {
|
|
4453
4622
|
if (skills.length === 1) {
|
|
4454
4623
|
const skill = skills[0];
|
|
4455
|
-
console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
|
|
4624
|
+
console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)} ${DIM}(${skill.reason})${RESET}`);
|
|
4456
4625
|
} else {
|
|
4457
4626
|
const reason = skills[0].reason;
|
|
4458
|
-
const names = skills.map((s) => s.name).join(", ");
|
|
4627
|
+
const names = skills.map((s) => sanitizeMetadata(s.name)).join(", ");
|
|
4459
4628
|
console.log(` ${TEXT}•${RESET} ${names} ${DIM}(${reason})${RESET}`);
|
|
4460
4629
|
}
|
|
4461
4630
|
console.log(` ${DIM}To update: ${TEXT}npx skills add ${source} -g -y${RESET}`);
|
|
@@ -4516,7 +4685,7 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4516
4685
|
}
|
|
4517
4686
|
for (let i = 0; i < checkable.length; i++) {
|
|
4518
4687
|
const { name: skillName, entry } = checkable[i];
|
|
4519
|
-
process.stdout.write(`\r${DIM}Checking global skill ${i + 1}/${checkable.length}: ${skillName}${RESET}\x1b[K`);
|
|
4688
|
+
process.stdout.write(`\r${DIM}Checking global skill ${i + 1}/${checkable.length}: ${sanitizeMetadata(skillName)}${RESET}\x1b[K`);
|
|
4520
4689
|
try {
|
|
4521
4690
|
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token, entry.ref);
|
|
4522
4691
|
if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
|
|
@@ -4555,12 +4724,13 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4555
4724
|
console.log(`${TEXT}Found ${updates.length} global update(s)${RESET}`);
|
|
4556
4725
|
console.log();
|
|
4557
4726
|
for (const update of updates) {
|
|
4558
|
-
|
|
4727
|
+
const safeName = sanitizeMetadata(update.name);
|
|
4728
|
+
console.log(`${TEXT}Updating ${safeName}...${RESET}`);
|
|
4559
4729
|
const installUrl = buildUpdateInstallSource(update.entry);
|
|
4560
4730
|
const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
|
|
4561
4731
|
if (!existsSync(cliEntry)) {
|
|
4562
4732
|
failCount++;
|
|
4563
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4733
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
4564
4734
|
continue;
|
|
4565
4735
|
}
|
|
4566
4736
|
if (spawnSync(process.execPath, [
|
|
@@ -4579,10 +4749,10 @@ async function updateGlobalSkills(skillFilter) {
|
|
|
4579
4749
|
shell: process.platform === "win32"
|
|
4580
4750
|
}).status === 0) {
|
|
4581
4751
|
successCount++;
|
|
4582
|
-
console.log(` ${TEXT}✓${RESET} Updated ${
|
|
4752
|
+
console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
|
|
4583
4753
|
} else {
|
|
4584
4754
|
failCount++;
|
|
4585
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4755
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
|
|
4586
4756
|
}
|
|
4587
4757
|
}
|
|
4588
4758
|
printSkippedSkills(skipped);
|
|
@@ -4607,21 +4777,35 @@ async function updateProjectSkills(skillFilter) {
|
|
|
4607
4777
|
foundCount: 0
|
|
4608
4778
|
};
|
|
4609
4779
|
}
|
|
4610
|
-
|
|
4780
|
+
const updatable = projectSkills.filter((s) => s.entry.skillPath);
|
|
4781
|
+
const legacy = projectSkills.filter((s) => !s.entry.skillPath);
|
|
4782
|
+
if (updatable.length === 0) {
|
|
4783
|
+
console.log(`${DIM}No project skills can be updated in place.${RESET}`);
|
|
4784
|
+
printLegacyProjectSkills(legacy);
|
|
4785
|
+
return {
|
|
4786
|
+
successCount,
|
|
4787
|
+
failCount,
|
|
4788
|
+
foundCount: projectSkills.length
|
|
4789
|
+
};
|
|
4790
|
+
}
|
|
4791
|
+
console.log(`${TEXT}Refreshing ${updatable.length} project skill(s)...${RESET}`);
|
|
4611
4792
|
console.log();
|
|
4612
|
-
for (const skill of
|
|
4613
|
-
|
|
4793
|
+
for (const skill of updatable) {
|
|
4794
|
+
const safeName = sanitizeMetadata(skill.name);
|
|
4795
|
+
console.log(`${TEXT}Updating ${safeName}...${RESET}`);
|
|
4614
4796
|
const installUrl = buildLocalUpdateSource(skill.entry);
|
|
4615
4797
|
const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
|
|
4616
4798
|
if (!existsSync(cliEntry)) {
|
|
4617
4799
|
failCount++;
|
|
4618
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4800
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
4619
4801
|
continue;
|
|
4620
4802
|
}
|
|
4621
4803
|
if (spawnSync(process.execPath, [
|
|
4622
4804
|
cliEntry,
|
|
4623
4805
|
"add",
|
|
4624
4806
|
installUrl,
|
|
4807
|
+
"--skill",
|
|
4808
|
+
skill.name,
|
|
4625
4809
|
"-y"
|
|
4626
4810
|
], {
|
|
4627
4811
|
stdio: [
|
|
@@ -4633,18 +4817,29 @@ async function updateProjectSkills(skillFilter) {
|
|
|
4633
4817
|
shell: process.platform === "win32"
|
|
4634
4818
|
}).status === 0) {
|
|
4635
4819
|
successCount++;
|
|
4636
|
-
console.log(` ${TEXT}✓${RESET} Updated ${
|
|
4820
|
+
console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
|
|
4637
4821
|
} else {
|
|
4638
4822
|
failCount++;
|
|
4639
|
-
console.log(` ${DIM}✗ Failed to update ${
|
|
4823
|
+
console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
|
|
4640
4824
|
}
|
|
4641
4825
|
}
|
|
4826
|
+
printLegacyProjectSkills(legacy);
|
|
4642
4827
|
return {
|
|
4643
4828
|
successCount,
|
|
4644
4829
|
failCount,
|
|
4645
4830
|
foundCount: projectSkills.length
|
|
4646
4831
|
};
|
|
4647
4832
|
}
|
|
4833
|
+
function printLegacyProjectSkills(legacy) {
|
|
4834
|
+
if (legacy.length === 0) return;
|
|
4835
|
+
console.log();
|
|
4836
|
+
console.log(`${DIM}${legacy.length} project skill(s) cannot be updated automatically (installed before skillPath tracking):${RESET}`);
|
|
4837
|
+
for (const skill of legacy) {
|
|
4838
|
+
const reinstall = formatSourceInput(skill.entry.source, skill.entry.ref);
|
|
4839
|
+
console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)}`);
|
|
4840
|
+
console.log(` ${DIM}To refresh: ${TEXT}npx skills add ${reinstall} -y${RESET}`);
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4648
4843
|
async function runUpdate(args = []) {
|
|
4649
4844
|
const options = parseUpdateOptions(args);
|
|
4650
4845
|
const scope = await resolveUpdateScope(options);
|
|
@@ -4756,5 +4951,5 @@ async function main() {
|
|
|
4756
4951
|
console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
|
|
4757
4952
|
}
|
|
4758
4953
|
}
|
|
4759
|
-
main();
|
|
4954
|
+
main().finally(() => flushTelemetry().then(() => process.exit(0)));
|
|
4760
4955
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skills",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
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,11 @@
|
|
|
51
55
|
"crush",
|
|
52
56
|
"cursor",
|
|
53
57
|
"deepagents",
|
|
58
|
+
"devin",
|
|
59
|
+
"dexto",
|
|
54
60
|
"droid",
|
|
55
61
|
"firebender",
|
|
62
|
+
"forgecode",
|
|
56
63
|
"gemini-cli",
|
|
57
64
|
"github-copilot",
|
|
58
65
|
"goose",
|
|
@@ -71,7 +78,9 @@
|
|
|
71
78
|
"qoder",
|
|
72
79
|
"qwen-code",
|
|
73
80
|
"replit",
|
|
81
|
+
"rovodev",
|
|
74
82
|
"roo",
|
|
83
|
+
"tabnine-cli",
|
|
75
84
|
"trae",
|
|
76
85
|
"trae-cn",
|
|
77
86
|
"warp",
|