skills 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +64 -54
  2. package/dist/cli.mjs +252 -65
  3. 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 [Available Agents](#available-agents)<!-- agent-names:end --> |
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
- | Agent | `--agent` | Project Path | Global Path |
230
- | ------------------------------------- | ---------------------------------------- | ---------------------- | ------------------------------- |
231
- | Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` |
232
- | Antigravity | `antigravity` | `.agents/skills/` | `~/.gemini/antigravity/skills/` |
233
- | Augment | `augment` | `.augment/skills/` | `~/.augment/skills/` |
234
- | IBM Bob | `bob` | `.bob/skills/` | `~/.bob/skills/` |
235
- | Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
236
- | OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` |
237
- | Cline, Warp | `cline`, `warp` | `.agents/skills/` | `~/.agents/skills/` |
238
- | CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` |
239
- | Codex | `codex` | `.agents/skills/` | `~/.codex/skills/` |
240
- | Command Code | `command-code` | `.commandcode/skills/` | `~/.commandcode/skills/` |
241
- | Continue | `continue` | `.continue/skills/` | `~/.continue/skills/` |
242
- | Cortex Code | `cortex` | `.cortex/skills/` | `~/.snowflake/cortex/skills/` |
243
- | Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` |
244
- | Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` |
245
- | Deep Agents | `deepagents` | `.agents/skills/` | `~/.deepagents/agent/skills/` |
246
- | Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
247
- | Firebender | `firebender` | `.agents/skills/` | `~/.firebender/skills/` |
248
- | Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
249
- | GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
250
- | Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
251
- | Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
252
- | iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
253
- | Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` |
254
- | Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` |
255
- | Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
256
- | MCPJam | `mcpjam` | `.mcpjam/skills/` | `~/.mcpjam/skills/` |
257
- | Mistral Vibe | `mistral-vibe` | `.vibe/skills/` | `~/.vibe/skills/` |
258
- | Mux | `mux` | `.mux/skills/` | `~/.mux/skills/` |
259
- | OpenCode | `opencode` | `.agents/skills/` | `~/.config/opencode/skills/` |
260
- | OpenHands | `openhands` | `.openhands/skills/` | `~/.openhands/skills/` |
261
- | Pi | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` |
262
- | Qoder | `qoder` | `.qoder/skills/` | `~/.qoder/skills/` |
263
- | Qwen Code | `qwen-code` | `.qwen/skills/` | `~/.qwen/skills/` |
264
- | Roo Code | `roo` | `.roo/skills/` | `~/.roo/skills/` |
265
- | Trae | `trae` | `.trae/skills/` | `~/.trae/skills/` |
266
- | Trae CN | `trae-cn` | `.trae/skills/` | `~/.trae-cn/skills/` |
267
- | Windsurf | `windsurf` | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
268
- | Zencoder | `zencoder` | `.zencoder/skills/` | `~/.zencoder/skills/` |
269
- | Neovate | `neovate` | `.neovate/skills/` | `~/.neovate/skills/` |
270
- | Pochi | `pochi` | `.pochi/skills/` | `~/.pochi/skills/` |
271
- | AdaL | `adal` | `.adal/skills/` | `~/.adal/skills/` |
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:** After installing skills, manually add them to your custom agent's `resources` in
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
- - `./skills/`
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 | No | No | No | No | No | No | No | No | 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.length;
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 CLONE_TIMEOUT_MS = 6e4;
418
+ const DEFAULT_CLONE_TIMEOUT_MS = 3e5;
419
+ const CLONE_TIMEOUT_MS = (() => {
420
+ const raw = process.env.SKILLS_CLONE_TIMEOUT_MS;
421
+ if (!raw) return DEFAULT_CLONE_TIMEOUT_MS;
422
+ const parsed = Number.parseInt(raw, 10);
423
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CLONE_TIMEOUT_MS;
424
+ })();
387
425
  var GitCloneError = class extends Error {
388
426
  url;
389
427
  isTimeout;
@@ -402,8 +440,15 @@ async function cloneRepo(url, ref) {
402
440
  timeout: { block: CLONE_TIMEOUT_MS },
403
441
  env: {
404
442
  ...process.env,
405
- GIT_TERMINAL_PROMPT: "0"
406
- }
443
+ GIT_TERMINAL_PROMPT: "0",
444
+ GIT_LFS_SKIP_SMUDGE: "1"
445
+ },
446
+ config: [
447
+ "filter.lfs.required=false",
448
+ "filter.lfs.smudge=",
449
+ "filter.lfs.clean=",
450
+ "filter.lfs.process="
451
+ ]
407
452
  });
408
453
  const cloneOptions = ref ? [
409
454
  "--depth",
@@ -422,7 +467,7 @@ async function cloneRepo(url, ref) {
422
467
  const errorMessage = error instanceof Error ? error.message : String(error);
423
468
  const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
424
469
  const isAuthError = errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("Permission denied") || errorMessage.includes("Repository not found");
425
- if (isTimeout) throw new GitCloneError("Clone timed out after 60s. This often happens with private repos that require authentication.\n Ensure you have access and your SSH keys or credentials are configured:\n - For SSH: ssh-add -l (to check loaded keys)\n - For HTTPS: gh auth status (if using GitHub CLI)", url, true, false);
470
+ if (isTimeout) throw new GitCloneError(`Clone timed out after ${Math.round(CLONE_TIMEOUT_MS / 1e3)}s. Common causes:\n - Large repository: raise the timeout with SKILLS_CLONE_TIMEOUT_MS=600000 (10m)\n - Slow network: retry, or clone manually and pass the local path to 'skills add'\n - Private repo without credentials: ensure auth is configured\n - For SSH: ssh-add -l (to check loaded keys)\n - For HTTPS: gh auth status (if using GitHub CLI)`, url, true, false);
426
471
  if (isAuthError) throw new GitCloneError(`Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`, url, false, true);
427
472
  throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
428
473
  }
@@ -538,8 +583,8 @@ async function parseSkillMd(skillMdPath, options) {
538
583
  if (typeof data.name !== "string" || typeof data.description !== "string") return null;
539
584
  if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
540
585
  return {
541
- name: data.name,
542
- description: data.description,
586
+ name: sanitizeMetadata(data.name),
587
+ description: sanitizeMetadata(data.description),
543
588
  path: dirname(skillMdPath),
544
589
  rawContent: content,
545
590
  metadata: data.metadata
@@ -657,6 +702,7 @@ const home = homedir();
657
702
  const configHome = xdgConfig ?? join(home, ".config");
658
703
  const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
659
704
  const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
705
+ const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
660
706
  function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
661
707
  if (pathExists(join(homeDir, ".openclaw"))) return join(homeDir, ".openclaw/skills");
662
708
  if (pathExists(join(homeDir, ".clawdbot"))) return join(homeDir, ".clawdbot/skills");
@@ -664,6 +710,15 @@ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
664
710
  return join(homeDir, ".openclaw/skills");
665
711
  }
666
712
  const agents = {
713
+ "aider-desk": {
714
+ name: "aider-desk",
715
+ displayName: "AiderDesk",
716
+ skillsDir: ".aider-desk/skills",
717
+ globalSkillsDir: join(home, ".aider-desk/skills"),
718
+ detectInstalled: async () => {
719
+ return existsSync(join(home, ".aider-desk"));
720
+ }
721
+ },
667
722
  amp: {
668
723
  name: "amp",
669
724
  displayName: "Amp",
@@ -727,6 +782,15 @@ const agents = {
727
782
  return existsSync(join(home, ".cline"));
728
783
  }
729
784
  },
785
+ "codearts-agent": {
786
+ name: "codearts-agent",
787
+ displayName: "CodeArts Agent",
788
+ skillsDir: ".codeartsdoer/skills",
789
+ globalSkillsDir: join(home, ".codeartsdoer/skills"),
790
+ detectInstalled: async () => {
791
+ return existsSync(join(home, ".codeartsdoer"));
792
+ }
793
+ },
730
794
  codebuddy: {
731
795
  name: "codebuddy",
732
796
  displayName: "CodeBuddy",
@@ -736,6 +800,24 @@ const agents = {
736
800
  return existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"));
737
801
  }
738
802
  },
803
+ codemaker: {
804
+ name: "codemaker",
805
+ displayName: "Codemaker",
806
+ skillsDir: ".codemaker/skills",
807
+ globalSkillsDir: join(home, ".codemaker/skills"),
808
+ detectInstalled: async () => {
809
+ return existsSync(join(home, ".codemaker"));
810
+ }
811
+ },
812
+ codestudio: {
813
+ name: "codestudio",
814
+ displayName: "Code Studio",
815
+ skillsDir: ".codestudio/skills",
816
+ globalSkillsDir: join(home, ".codestudio/skills"),
817
+ detectInstalled: async () => {
818
+ return existsSync(join(home, ".codestudio"));
819
+ }
820
+ },
739
821
  codex: {
740
822
  name: "codex",
741
823
  displayName: "Codex",
@@ -799,6 +881,15 @@ const agents = {
799
881
  return existsSync(join(home, ".deepagents"));
800
882
  }
801
883
  },
884
+ devin: {
885
+ name: "devin",
886
+ displayName: "Devin for Terminal",
887
+ skillsDir: ".devin/skills",
888
+ globalSkillsDir: join(configHome, "devin/skills"),
889
+ detectInstalled: async () => {
890
+ return existsSync(join(configHome, "devin"));
891
+ }
892
+ },
802
893
  droid: {
803
894
  name: "droid",
804
895
  displayName: "Droid",
@@ -817,6 +908,15 @@ const agents = {
817
908
  return existsSync(join(home, ".firebender"));
818
909
  }
819
910
  },
911
+ forgecode: {
912
+ name: "forgecode",
913
+ displayName: "ForgeCode",
914
+ skillsDir: ".forge/skills",
915
+ globalSkillsDir: join(home, ".forge/skills"),
916
+ detectInstalled: async () => {
917
+ return existsSync(join(home, ".forge"));
918
+ }
919
+ },
820
920
  "gemini-cli": {
821
921
  name: "gemini-cli",
822
922
  displayName: "Gemini CLI",
@@ -911,9 +1011,9 @@ const agents = {
911
1011
  name: "mistral-vibe",
912
1012
  displayName: "Mistral Vibe",
913
1013
  skillsDir: ".vibe/skills",
914
- globalSkillsDir: join(home, ".vibe/skills"),
1014
+ globalSkillsDir: join(vibeHome, "skills"),
915
1015
  detectInstalled: async () => {
916
- return existsSync(join(home, ".vibe"));
1016
+ return existsSync(vibeHome);
917
1017
  }
918
1018
  },
919
1019
  mux: {
@@ -980,6 +1080,15 @@ const agents = {
980
1080
  return existsSync(join(process.cwd(), ".replit"));
981
1081
  }
982
1082
  },
1083
+ rovodev: {
1084
+ name: "rovodev",
1085
+ displayName: "Rovo Dev",
1086
+ skillsDir: ".rovodev/skills",
1087
+ globalSkillsDir: join(home, ".rovodev/skills"),
1088
+ detectInstalled: async () => {
1089
+ return existsSync(join(home, ".rovodev"));
1090
+ }
1091
+ },
983
1092
  roo: {
984
1093
  name: "roo",
985
1094
  displayName: "Roo Code",
@@ -989,6 +1098,15 @@ const agents = {
989
1098
  return existsSync(join(home, ".roo"));
990
1099
  }
991
1100
  },
1101
+ "tabnine-cli": {
1102
+ name: "tabnine-cli",
1103
+ displayName: "Tabnine CLI",
1104
+ skillsDir: ".tabnine/agent/skills",
1105
+ globalSkillsDir: join(home, ".tabnine/agent/skills"),
1106
+ detectInstalled: async () => {
1107
+ return existsSync(join(home, ".tabnine"));
1108
+ }
1109
+ },
992
1110
  trae: {
993
1111
  name: "trae",
994
1112
  displayName: "Trae",
@@ -1095,6 +1213,15 @@ function isPathSafe(basePath, targetPath) {
1095
1213
  const normalizedTarget = normalize(resolve(targetPath));
1096
1214
  return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
1097
1215
  }
1216
+ async function isDirEntryOrSymlinkToDir(entry, entryPath) {
1217
+ if (entry.isDirectory()) return true;
1218
+ if (!entry.isSymbolicLink()) return false;
1219
+ try {
1220
+ return (await stat(entryPath)).isDirectory();
1221
+ } catch {
1222
+ return false;
1223
+ }
1224
+ }
1098
1225
  function getCanonicalSkillsDir(global, cwd) {
1099
1226
  return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
1100
1227
  }
@@ -1235,7 +1362,6 @@ const EXCLUDE_DIRS = new Set([
1235
1362
  ]);
1236
1363
  const isExcluded = (name, isDirectory = false) => {
1237
1364
  if (EXCLUDE_FILES.has(name)) return true;
1238
- if (name.startsWith(".")) return true;
1239
1365
  if (isDirectory && EXCLUDE_DIRS.has(name)) return true;
1240
1366
  return false;
1241
1367
  };
@@ -1491,8 +1617,8 @@ async function listInstalledSkills(options = {}) {
1491
1617
  for (const scope of scopes) try {
1492
1618
  const entries = await readdir(scope.path, { withFileTypes: true });
1493
1619
  for (const entry of entries) {
1494
- if (!entry.isDirectory()) continue;
1495
1620
  const skillDir = join(scope.path, entry.name);
1621
+ if (!await isDirEntryOrSymlinkToDir(entry, skillDir)) continue;
1496
1622
  const skillMdPath = join(skillDir, "SKILL.md");
1497
1623
  try {
1498
1624
  await stat(skillMdPath);
@@ -1541,8 +1667,8 @@ async function listInstalledSkills(options = {}) {
1541
1667
  if (!found) try {
1542
1668
  const agentEntries = await readdir(agentBase, { withFileTypes: true });
1543
1669
  for (const agentEntry of agentEntries) {
1544
- if (!agentEntry.isDirectory()) continue;
1545
1670
  const candidateDir = join(agentBase, agentEntry.name);
1671
+ if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
1546
1672
  if (!isPathSafe(agentBase, candidateDir)) continue;
1547
1673
  try {
1548
1674
  const candidateSkillMd = join(candidateDir, "SKILL.md");
@@ -1601,6 +1727,7 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
1601
1727
  return null;
1602
1728
  }
1603
1729
  }
1730
+ const pendingTelemetry = [];
1604
1731
  function track(data) {
1605
1732
  if (!isEnabled()) return;
1606
1733
  try {
@@ -1608,9 +1735,15 @@ function track(data) {
1608
1735
  if (cliVersion) params.set("v", cliVersion);
1609
1736
  if (isCI()) params.set("ci", "1");
1610
1737
  for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
1611
- fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
1738
+ const p = fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {}).then(() => {});
1739
+ pendingTelemetry.push(p);
1612
1740
  } catch {}
1613
1741
  }
1742
+ async function flushTelemetry(timeoutMs = 5e3) {
1743
+ if (pendingTelemetry.length === 0) return;
1744
+ const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
1745
+ await Promise.race([Promise.all(pendingTelemetry), timeout]);
1746
+ }
1614
1747
  var ProviderRegistryImpl = class {
1615
1748
  providers = [];
1616
1749
  register(provider) {
@@ -1748,8 +1881,8 @@ var WellKnownProvider = class {
1748
1881
  const fileResults = await Promise.all(filePromises);
1749
1882
  for (const result of fileResults) if (result) files.set(result.path, result.content);
1750
1883
  return {
1751
- name: data.name,
1752
- description: data.description,
1884
+ name: sanitizeMetadata(data.name),
1885
+ description: sanitizeMetadata(data.description),
1753
1886
  content,
1754
1887
  installName: entry.name,
1755
1888
  sourceUrl: skillMdUrl,
@@ -2122,12 +2255,14 @@ async function tryBlobInstall(ownerRepo, options = {}) {
2122
2255
  if (!data.name || !data.description) continue;
2123
2256
  if (typeof data.name !== "string" || typeof data.description !== "string") continue;
2124
2257
  if (data.metadata?.internal === true && !options.includeInternal) continue;
2258
+ const safeName = sanitizeMetadata(data.name);
2259
+ const safeDescription = sanitizeMetadata(data.description);
2125
2260
  parsedSkills.push({
2126
2261
  mdPath,
2127
- name: data.name,
2128
- description: data.description,
2262
+ name: safeName,
2263
+ description: safeDescription,
2129
2264
  content,
2130
- slug: toSkillSlug(data.name),
2265
+ slug: toSkillSlug(safeName),
2131
2266
  metadata: data.metadata
2132
2267
  });
2133
2268
  }
@@ -2164,7 +2299,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
2164
2299
  tree
2165
2300
  };
2166
2301
  }
2167
- var version$1 = "1.5.0";
2302
+ var version$1 = "1.5.2";
2168
2303
  const isCancelled$1 = (value) => typeof value === "symbol";
2169
2304
  async function isSourcePrivate(source) {
2170
2305
  const ownerRepo = parseOwnerRepo(source);
@@ -2190,7 +2325,7 @@ function socketLabel(audit) {
2190
2325
  return count > 0 ? import_picocolors.default.red(`${count} alert${count !== 1 ? "s" : ""}`) : import_picocolors.default.green("0 alerts");
2191
2326
  }
2192
2327
  function padEnd(str, width) {
2193
- const visible = str.replace(/\x1b\[[0-9;]*m/g, "");
2328
+ const visible = stripTerminalEscapes(str);
2194
2329
  const pad = Math.max(0, width - visible.length);
2195
2330
  return str + " ".repeat(pad);
2196
2331
  }
@@ -2513,6 +2648,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2513
2648
  process.exit(0);
2514
2649
  }
2515
2650
  }
2651
+ const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
2652
+ const wellKnownPrivacyPromise = isSourcePrivate(sourceIdentifier).catch(() => null);
2516
2653
  spinner.start("Installing skills...");
2517
2654
  const results = [];
2518
2655
  for (const skill of selectedSkills) for (const agent of targetAgents) {
@@ -2530,10 +2667,9 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2530
2667
  console.log();
2531
2668
  const successful = results.filter((r) => r.success);
2532
2669
  const failed = results.filter((r) => !r.success);
2533
- const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
2534
2670
  const skillFiles = {};
2535
2671
  for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
2536
- if (await isSourcePrivate(sourceIdentifier) !== true) track({
2672
+ if (await wellKnownPrivacyPromise !== true) track({
2537
2673
  event: "install",
2538
2674
  source: sourceIdentifier,
2539
2675
  skills: selectedSkills.map((s) => s.installName).join(","),
@@ -2645,7 +2781,14 @@ async function runAdd(args, options = {}) {
2645
2781
  spinner.start("Parsing source...");
2646
2782
  const parsed = parseSource(source);
2647
2783
  spinner.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
2648
- if (getOwnerRepo(parsed)?.split("/")[0]?.toLowerCase() === "openclaw" && !options.dangerouslyAcceptOpenclawRisks) {
2784
+ const ownerRepoRaw = getOwnerRepo(parsed);
2785
+ const repoPrivacyPromise = (() => {
2786
+ if (!ownerRepoRaw) return Promise.resolve(null);
2787
+ const ownerRepo = parseOwnerRepo(ownerRepoRaw);
2788
+ if (!ownerRepo) return Promise.resolve(null);
2789
+ return isRepoPrivate(ownerRepo.owner, ownerRepo.repo).catch(() => null);
2790
+ })();
2791
+ if (ownerRepoRaw?.split("/")[0]?.toLowerCase() === "openclaw" && !options.dangerouslyAcceptOpenclawRisks) {
2649
2792
  console.log();
2650
2793
  M.warn(import_picocolors.default.yellow(import_picocolors.default.bold("⚠ OpenClaw skills are unverified community submissions.")));
2651
2794
  M.message(import_picocolors.default.yellow("This source contains user-submitted skills that have not been reviewed for safety or quality."));
@@ -2680,7 +2823,11 @@ async function runAdd(args, options = {}) {
2680
2823
  fullDepth: options.fullDepth
2681
2824
  });
2682
2825
  } else if (parsed.type === "github" && !options.fullDepth) {
2683
- const BLOB_ALLOWED_OWNERS = ["vercel", "vercel-labs"];
2826
+ const BLOB_ALLOWED_OWNERS = [
2827
+ "vercel",
2828
+ "vercel-labs",
2829
+ "heygen-com"
2830
+ ];
2684
2831
  const ownerRepo = getOwnerRepo(parsed);
2685
2832
  const owner = ownerRepo?.split("/")[0]?.toLowerCase();
2686
2833
  if (ownerRepo && owner && BLOB_ALLOWED_OWNERS.includes(owner)) {
@@ -3018,18 +3165,8 @@ async function runAdd(args, options = {}) {
3018
3165
  else continue;
3019
3166
  const normalizedSource = getOwnerRepo(parsed);
3020
3167
  const lockSource = parsed.url.startsWith("git@") ? parsed.url : normalizedSource;
3021
- if (normalizedSource) {
3022
- const ownerRepo = parseOwnerRepo(normalizedSource);
3023
- if (ownerRepo) {
3024
- if (await isRepoPrivate(ownerRepo.owner, ownerRepo.repo) === false) track({
3025
- event: "install",
3026
- source: normalizedSource,
3027
- skills: selectedSkills.map((s) => s.name).join(","),
3028
- agents: targetAgents.join(","),
3029
- ...installGlobally && { global: "1" },
3030
- skillFiles: JSON.stringify(skillFiles)
3031
- });
3032
- } else track({
3168
+ if (normalizedSource) if (parseOwnerRepo(normalizedSource)) {
3169
+ if (await repoPrivacyPromise === false) track({
3033
3170
  event: "install",
3034
3171
  source: normalizedSource,
3035
3172
  skills: selectedSkills.map((s) => s.name).join(","),
@@ -3037,9 +3174,21 @@ async function runAdd(args, options = {}) {
3037
3174
  ...installGlobally && { global: "1" },
3038
3175
  skillFiles: JSON.stringify(skillFiles)
3039
3176
  });
3040
- }
3177
+ } else track({
3178
+ event: "install",
3179
+ source: normalizedSource,
3180
+ skills: selectedSkills.map((s) => s.name).join(","),
3181
+ agents: targetAgents.join(","),
3182
+ ...installGlobally && { global: "1" },
3183
+ skillFiles: JSON.stringify(skillFiles)
3184
+ });
3041
3185
  if (successful.length > 0 && installGlobally && normalizedSource) {
3042
3186
  const successfulSkillNames = new Set(successful.map((r) => r.skill));
3187
+ let cachedTree;
3188
+ if (parsed.type === "github" && !blobResult) {
3189
+ const token = getGitHubToken();
3190
+ cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, token);
3191
+ }
3043
3192
  for (const skill of selectedSkills) {
3044
3193
  const skillDisplayName = getSkillDisplayName(skill);
3045
3194
  if (successfulSkillNames.has(skillDisplayName)) try {
@@ -3048,8 +3197,11 @@ async function runAdd(args, options = {}) {
3048
3197
  if (blobResult && skillPathValue) {
3049
3198
  const hash = getSkillFolderHashFromTree(blobResult.tree, skillPathValue);
3050
3199
  if (hash) skillFolderHash = hash;
3051
- } else if (parsed.type === "github" && skillPathValue) {
3052
- const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue, getGitHubToken(), parsed.ref);
3200
+ } else if (parsed.type === "github" && skillPathValue && cachedTree) {
3201
+ const hash = getSkillFolderHashFromTree(cachedTree, skillPathValue);
3202
+ if (hash) skillFolderHash = hash;
3203
+ } else if (skillPathValue && tempDir) {
3204
+ const hash = await computeSkillFolderHash(join(tempDir, dirname(skillPathValue)));
3053
3205
  if (hash) skillFolderHash = hash;
3054
3206
  }
3055
3207
  await addSkillToLock(skill.name, {
@@ -3070,10 +3222,12 @@ async function runAdd(args, options = {}) {
3070
3222
  const skillDisplayName = getSkillDisplayName(skill);
3071
3223
  if (successfulSkillNames.has(skillDisplayName)) try {
3072
3224
  const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
3225
+ const skillPathValue = skillFiles[skill.name];
3073
3226
  await addSkillToLocalLock(skill.name, {
3074
3227
  source: lockSource || parsed.url,
3075
3228
  ref: parsed.ref,
3076
3229
  sourceType: parsed.type,
3230
+ ...skillPathValue && { skillPath: skillPathValue },
3077
3231
  computedHash
3078
3232
  }, cwd);
3079
3233
  } catch {}
@@ -3258,9 +3412,9 @@ async function searchSkillsAPI(query) {
3258
3412
  const res = await fetch(url);
3259
3413
  if (!res.ok) return [];
3260
3414
  return (await res.json()).skills.map((skill) => ({
3261
- name: skill.name,
3262
- slug: skill.id,
3263
- source: skill.source || "",
3415
+ name: sanitizeMetadata(skill.name),
3416
+ slug: sanitizeMetadata(skill.id),
3417
+ source: sanitizeMetadata(skill.source || ""),
3264
3418
  installs: skill.installs
3265
3419
  })).sort((a, b) => (b.installs || 0) - (a.installs || 0));
3266
3420
  } catch {
@@ -3873,7 +4027,7 @@ async function runList(args) {
3873
4027
  const shortPath = shortenPath(skill.canonicalPath, cwd);
3874
4028
  const agentNames = skill.agents.map((a) => agents[a].displayName);
3875
4029
  const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$1}`;
3876
- console.log(`${prefix}${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
4030
+ console.log(`${prefix}${CYAN}${sanitizeMetadata(skill.name)}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
3877
4031
  console.log(`${prefix} ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
3878
4032
  }
3879
4033
  console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
@@ -4101,18 +4255,25 @@ function formatSourceInput(sourceUrl, ref) {
4101
4255
  if (!ref) return sourceUrl;
4102
4256
  return `${sourceUrl}#${ref}`;
4103
4257
  }
4258
+ function deriveSkillFolder(skillPath) {
4259
+ let folder = skillPath;
4260
+ if (folder.endsWith("/SKILL.md")) folder = folder.slice(0, -9);
4261
+ else if (folder.endsWith("SKILL.md")) folder = folder.slice(0, -8);
4262
+ if (folder.endsWith("/")) folder = folder.slice(0, -1);
4263
+ return folder;
4264
+ }
4265
+ function appendFolderAndRef(source, skillPath, ref) {
4266
+ const folder = deriveSkillFolder(skillPath);
4267
+ const withFolder = folder ? `${source}/${folder}` : source;
4268
+ return ref ? `${withFolder}#${ref}` : withFolder;
4269
+ }
4104
4270
  function buildUpdateInstallSource(entry) {
4105
4271
  if (!entry.skillPath) return formatSourceInput(entry.sourceUrl, entry.ref);
4106
- let skillFolder = entry.skillPath;
4107
- if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
4108
- else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
4109
- if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
4110
- let installSource = skillFolder ? `${entry.source}/${skillFolder}` : entry.source;
4111
- if (entry.ref) installSource = `${installSource}#${entry.ref}`;
4112
- return installSource;
4272
+ return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
4113
4273
  }
4114
4274
  function buildLocalUpdateSource(entry) {
4115
- return formatSourceInput(entry.source, entry.ref);
4275
+ if (!entry.skillPath) return formatSourceInput(entry.source, entry.ref);
4276
+ return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
4116
4277
  }
4117
4278
  const __dirname = dirname(fileURLToPath(import.meta.url));
4118
4279
  function getVersion() {
@@ -4451,10 +4612,10 @@ function printSkippedSkills(skipped) {
4451
4612
  for (const [source, skills] of grouped) {
4452
4613
  if (skills.length === 1) {
4453
4614
  const skill = skills[0];
4454
- console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
4615
+ console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)} ${DIM}(${skill.reason})${RESET}`);
4455
4616
  } else {
4456
4617
  const reason = skills[0].reason;
4457
- const names = skills.map((s) => s.name).join(", ");
4618
+ const names = skills.map((s) => sanitizeMetadata(s.name)).join(", ");
4458
4619
  console.log(` ${TEXT}•${RESET} ${names} ${DIM}(${reason})${RESET}`);
4459
4620
  }
4460
4621
  console.log(` ${DIM}To update: ${TEXT}npx skills add ${source} -g -y${RESET}`);
@@ -4515,7 +4676,7 @@ async function updateGlobalSkills(skillFilter) {
4515
4676
  }
4516
4677
  for (let i = 0; i < checkable.length; i++) {
4517
4678
  const { name: skillName, entry } = checkable[i];
4518
- process.stdout.write(`\r${DIM}Checking global skill ${i + 1}/${checkable.length}: ${skillName}${RESET}\x1b[K`);
4679
+ process.stdout.write(`\r${DIM}Checking global skill ${i + 1}/${checkable.length}: ${sanitizeMetadata(skillName)}${RESET}\x1b[K`);
4519
4680
  try {
4520
4681
  const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token, entry.ref);
4521
4682
  if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
@@ -4554,12 +4715,13 @@ async function updateGlobalSkills(skillFilter) {
4554
4715
  console.log(`${TEXT}Found ${updates.length} global update(s)${RESET}`);
4555
4716
  console.log();
4556
4717
  for (const update of updates) {
4557
- console.log(`${TEXT}Updating ${update.name}...${RESET}`);
4718
+ const safeName = sanitizeMetadata(update.name);
4719
+ console.log(`${TEXT}Updating ${safeName}...${RESET}`);
4558
4720
  const installUrl = buildUpdateInstallSource(update.entry);
4559
4721
  const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
4560
4722
  if (!existsSync(cliEntry)) {
4561
4723
  failCount++;
4562
- console.log(` ${DIM}✗ Failed to update ${update.name}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4724
+ console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4563
4725
  continue;
4564
4726
  }
4565
4727
  if (spawnSync(process.execPath, [
@@ -4578,10 +4740,10 @@ async function updateGlobalSkills(skillFilter) {
4578
4740
  shell: process.platform === "win32"
4579
4741
  }).status === 0) {
4580
4742
  successCount++;
4581
- console.log(` ${TEXT}✓${RESET} Updated ${update.name}`);
4743
+ console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
4582
4744
  } else {
4583
4745
  failCount++;
4584
- console.log(` ${DIM}✗ Failed to update ${update.name}${RESET}`);
4746
+ console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
4585
4747
  }
4586
4748
  }
4587
4749
  printSkippedSkills(skipped);
@@ -4606,21 +4768,35 @@ async function updateProjectSkills(skillFilter) {
4606
4768
  foundCount: 0
4607
4769
  };
4608
4770
  }
4609
- console.log(`${TEXT}Refreshing ${projectSkills.length} project skill(s)...${RESET}`);
4771
+ const updatable = projectSkills.filter((s) => s.entry.skillPath);
4772
+ const legacy = projectSkills.filter((s) => !s.entry.skillPath);
4773
+ if (updatable.length === 0) {
4774
+ console.log(`${DIM}No project skills can be updated in place.${RESET}`);
4775
+ printLegacyProjectSkills(legacy);
4776
+ return {
4777
+ successCount,
4778
+ failCount,
4779
+ foundCount: projectSkills.length
4780
+ };
4781
+ }
4782
+ console.log(`${TEXT}Refreshing ${updatable.length} project skill(s)...${RESET}`);
4610
4783
  console.log();
4611
- for (const skill of projectSkills) {
4612
- console.log(`${TEXT}Updating ${skill.name}...${RESET}`);
4784
+ for (const skill of updatable) {
4785
+ const safeName = sanitizeMetadata(skill.name);
4786
+ console.log(`${TEXT}Updating ${safeName}...${RESET}`);
4613
4787
  const installUrl = buildLocalUpdateSource(skill.entry);
4614
4788
  const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
4615
4789
  if (!existsSync(cliEntry)) {
4616
4790
  failCount++;
4617
- console.log(` ${DIM}✗ Failed to update ${skill.name}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4791
+ console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4618
4792
  continue;
4619
4793
  }
4620
4794
  if (spawnSync(process.execPath, [
4621
4795
  cliEntry,
4622
4796
  "add",
4623
4797
  installUrl,
4798
+ "--skill",
4799
+ skill.name,
4624
4800
  "-y"
4625
4801
  ], {
4626
4802
  stdio: [
@@ -4632,18 +4808,29 @@ async function updateProjectSkills(skillFilter) {
4632
4808
  shell: process.platform === "win32"
4633
4809
  }).status === 0) {
4634
4810
  successCount++;
4635
- console.log(` ${TEXT}✓${RESET} Updated ${skill.name}`);
4811
+ console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
4636
4812
  } else {
4637
4813
  failCount++;
4638
- console.log(` ${DIM}✗ Failed to update ${skill.name}${RESET}`);
4814
+ console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
4639
4815
  }
4640
4816
  }
4817
+ printLegacyProjectSkills(legacy);
4641
4818
  return {
4642
4819
  successCount,
4643
4820
  failCount,
4644
4821
  foundCount: projectSkills.length
4645
4822
  };
4646
4823
  }
4824
+ function printLegacyProjectSkills(legacy) {
4825
+ if (legacy.length === 0) return;
4826
+ console.log();
4827
+ console.log(`${DIM}${legacy.length} project skill(s) cannot be updated automatically (installed before skillPath tracking):${RESET}`);
4828
+ for (const skill of legacy) {
4829
+ const reinstall = formatSourceInput(skill.entry.source, skill.entry.ref);
4830
+ console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)}`);
4831
+ console.log(` ${DIM}To refresh: ${TEXT}npx skills add ${reinstall} -y${RESET}`);
4832
+ }
4833
+ }
4647
4834
  async function runUpdate(args = []) {
4648
4835
  const options = parseUpdateOptions(args);
4649
4836
  const scope = await resolveUpdateScope(options);
@@ -4755,5 +4942,5 @@ async function main() {
4755
4942
  console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
4756
4943
  }
4757
4944
  }
4758
- main();
4945
+ main().finally(() => flushTelemetry().then(() => process.exit(0)));
4759
4946
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.5.0",
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",