skills 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +64 -54
  2. package/dist/cli.mjs +250 -64
  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;
@@ -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("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);
427
471
  if (isAuthError) throw new GitCloneError(`Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`, url, false, true);
428
472
  throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
429
473
  }
@@ -539,8 +583,8 @@ async function parseSkillMd(skillMdPath, options) {
539
583
  if (typeof data.name !== "string" || typeof data.description !== "string") return null;
540
584
  if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
541
585
  return {
542
- name: data.name,
543
- description: data.description,
586
+ name: sanitizeMetadata(data.name),
587
+ description: sanitizeMetadata(data.description),
544
588
  path: dirname(skillMdPath),
545
589
  rawContent: content,
546
590
  metadata: data.metadata
@@ -658,6 +702,7 @@ const home = homedir();
658
702
  const configHome = xdgConfig ?? join(home, ".config");
659
703
  const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
660
704
  const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
705
+ const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
661
706
  function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
662
707
  if (pathExists(join(homeDir, ".openclaw"))) return join(homeDir, ".openclaw/skills");
663
708
  if (pathExists(join(homeDir, ".clawdbot"))) return join(homeDir, ".clawdbot/skills");
@@ -665,6 +710,15 @@ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
665
710
  return join(homeDir, ".openclaw/skills");
666
711
  }
667
712
  const agents = {
713
+ "aider-desk": {
714
+ name: "aider-desk",
715
+ displayName: "AiderDesk",
716
+ skillsDir: ".aider-desk/skills",
717
+ globalSkillsDir: join(home, ".aider-desk/skills"),
718
+ detectInstalled: async () => {
719
+ return existsSync(join(home, ".aider-desk"));
720
+ }
721
+ },
668
722
  amp: {
669
723
  name: "amp",
670
724
  displayName: "Amp",
@@ -728,6 +782,15 @@ const agents = {
728
782
  return existsSync(join(home, ".cline"));
729
783
  }
730
784
  },
785
+ "codearts-agent": {
786
+ name: "codearts-agent",
787
+ displayName: "CodeArts Agent",
788
+ skillsDir: ".codeartsdoer/skills",
789
+ globalSkillsDir: join(home, ".codeartsdoer/skills"),
790
+ detectInstalled: async () => {
791
+ return existsSync(join(home, ".codeartsdoer"));
792
+ }
793
+ },
731
794
  codebuddy: {
732
795
  name: "codebuddy",
733
796
  displayName: "CodeBuddy",
@@ -737,6 +800,24 @@ const agents = {
737
800
  return existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"));
738
801
  }
739
802
  },
803
+ codemaker: {
804
+ name: "codemaker",
805
+ displayName: "Codemaker",
806
+ skillsDir: ".codemaker/skills",
807
+ globalSkillsDir: join(home, ".codemaker/skills"),
808
+ detectInstalled: async () => {
809
+ return existsSync(join(home, ".codemaker"));
810
+ }
811
+ },
812
+ codestudio: {
813
+ name: "codestudio",
814
+ displayName: "Code Studio",
815
+ skillsDir: ".codestudio/skills",
816
+ globalSkillsDir: join(home, ".codestudio/skills"),
817
+ detectInstalled: async () => {
818
+ return existsSync(join(home, ".codestudio"));
819
+ }
820
+ },
740
821
  codex: {
741
822
  name: "codex",
742
823
  displayName: "Codex",
@@ -800,6 +881,15 @@ const agents = {
800
881
  return existsSync(join(home, ".deepagents"));
801
882
  }
802
883
  },
884
+ devin: {
885
+ name: "devin",
886
+ displayName: "Devin for Terminal",
887
+ skillsDir: ".devin/skills",
888
+ globalSkillsDir: join(configHome, "devin/skills"),
889
+ detectInstalled: async () => {
890
+ return existsSync(join(configHome, "devin"));
891
+ }
892
+ },
803
893
  droid: {
804
894
  name: "droid",
805
895
  displayName: "Droid",
@@ -818,6 +908,15 @@ const agents = {
818
908
  return existsSync(join(home, ".firebender"));
819
909
  }
820
910
  },
911
+ forgecode: {
912
+ name: "forgecode",
913
+ displayName: "ForgeCode",
914
+ skillsDir: ".forge/skills",
915
+ globalSkillsDir: join(home, ".forge/skills"),
916
+ detectInstalled: async () => {
917
+ return existsSync(join(home, ".forge"));
918
+ }
919
+ },
821
920
  "gemini-cli": {
822
921
  name: "gemini-cli",
823
922
  displayName: "Gemini CLI",
@@ -912,9 +1011,9 @@ const agents = {
912
1011
  name: "mistral-vibe",
913
1012
  displayName: "Mistral Vibe",
914
1013
  skillsDir: ".vibe/skills",
915
- globalSkillsDir: join(home, ".vibe/skills"),
1014
+ globalSkillsDir: join(vibeHome, "skills"),
916
1015
  detectInstalled: async () => {
917
- return existsSync(join(home, ".vibe"));
1016
+ return existsSync(vibeHome);
918
1017
  }
919
1018
  },
920
1019
  mux: {
@@ -981,6 +1080,15 @@ const agents = {
981
1080
  return existsSync(join(process.cwd(), ".replit"));
982
1081
  }
983
1082
  },
1083
+ rovodev: {
1084
+ name: "rovodev",
1085
+ displayName: "Rovo Dev",
1086
+ skillsDir: ".rovodev/skills",
1087
+ globalSkillsDir: join(home, ".rovodev/skills"),
1088
+ detectInstalled: async () => {
1089
+ return existsSync(join(home, ".rovodev"));
1090
+ }
1091
+ },
984
1092
  roo: {
985
1093
  name: "roo",
986
1094
  displayName: "Roo Code",
@@ -990,6 +1098,15 @@ const agents = {
990
1098
  return existsSync(join(home, ".roo"));
991
1099
  }
992
1100
  },
1101
+ "tabnine-cli": {
1102
+ name: "tabnine-cli",
1103
+ displayName: "Tabnine CLI",
1104
+ skillsDir: ".tabnine/agent/skills",
1105
+ globalSkillsDir: join(home, ".tabnine/agent/skills"),
1106
+ detectInstalled: async () => {
1107
+ return existsSync(join(home, ".tabnine"));
1108
+ }
1109
+ },
993
1110
  trae: {
994
1111
  name: "trae",
995
1112
  displayName: "Trae",
@@ -1096,6 +1213,15 @@ function isPathSafe(basePath, targetPath) {
1096
1213
  const normalizedTarget = normalize(resolve(targetPath));
1097
1214
  return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
1098
1215
  }
1216
+ async function isDirEntryOrSymlinkToDir(entry, entryPath) {
1217
+ if (entry.isDirectory()) return true;
1218
+ if (!entry.isSymbolicLink()) return false;
1219
+ try {
1220
+ return (await stat(entryPath)).isDirectory();
1221
+ } catch {
1222
+ return false;
1223
+ }
1224
+ }
1099
1225
  function getCanonicalSkillsDir(global, cwd) {
1100
1226
  return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
1101
1227
  }
@@ -1236,7 +1362,6 @@ const EXCLUDE_DIRS = new Set([
1236
1362
  ]);
1237
1363
  const isExcluded = (name, isDirectory = false) => {
1238
1364
  if (EXCLUDE_FILES.has(name)) return true;
1239
- if (name.startsWith(".")) return true;
1240
1365
  if (isDirectory && EXCLUDE_DIRS.has(name)) return true;
1241
1366
  return false;
1242
1367
  };
@@ -1492,8 +1617,8 @@ async function listInstalledSkills(options = {}) {
1492
1617
  for (const scope of scopes) try {
1493
1618
  const entries = await readdir(scope.path, { withFileTypes: true });
1494
1619
  for (const entry of entries) {
1495
- if (!entry.isDirectory()) continue;
1496
1620
  const skillDir = join(scope.path, entry.name);
1621
+ if (!await isDirEntryOrSymlinkToDir(entry, skillDir)) continue;
1497
1622
  const skillMdPath = join(skillDir, "SKILL.md");
1498
1623
  try {
1499
1624
  await stat(skillMdPath);
@@ -1542,8 +1667,8 @@ async function listInstalledSkills(options = {}) {
1542
1667
  if (!found) try {
1543
1668
  const agentEntries = await readdir(agentBase, { withFileTypes: true });
1544
1669
  for (const agentEntry of agentEntries) {
1545
- if (!agentEntry.isDirectory()) continue;
1546
1670
  const candidateDir = join(agentBase, agentEntry.name);
1671
+ if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
1547
1672
  if (!isPathSafe(agentBase, candidateDir)) continue;
1548
1673
  try {
1549
1674
  const candidateSkillMd = join(candidateDir, "SKILL.md");
@@ -1602,6 +1727,7 @@ async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
1602
1727
  return null;
1603
1728
  }
1604
1729
  }
1730
+ const pendingTelemetry = [];
1605
1731
  function track(data) {
1606
1732
  if (!isEnabled()) return;
1607
1733
  try {
@@ -1609,9 +1735,15 @@ function track(data) {
1609
1735
  if (cliVersion) params.set("v", cliVersion);
1610
1736
  if (isCI()) params.set("ci", "1");
1611
1737
  for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
1612
- fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
1738
+ const p = fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {}).then(() => {});
1739
+ pendingTelemetry.push(p);
1613
1740
  } catch {}
1614
1741
  }
1742
+ async function flushTelemetry(timeoutMs = 5e3) {
1743
+ if (pendingTelemetry.length === 0) return;
1744
+ const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
1745
+ await Promise.race([Promise.all(pendingTelemetry), timeout]);
1746
+ }
1615
1747
  var ProviderRegistryImpl = class {
1616
1748
  providers = [];
1617
1749
  register(provider) {
@@ -1749,8 +1881,8 @@ var WellKnownProvider = class {
1749
1881
  const fileResults = await Promise.all(filePromises);
1750
1882
  for (const result of fileResults) if (result) files.set(result.path, result.content);
1751
1883
  return {
1752
- name: data.name,
1753
- description: data.description,
1884
+ name: sanitizeMetadata(data.name),
1885
+ description: sanitizeMetadata(data.description),
1754
1886
  content,
1755
1887
  installName: entry.name,
1756
1888
  sourceUrl: skillMdUrl,
@@ -2123,12 +2255,14 @@ async function tryBlobInstall(ownerRepo, options = {}) {
2123
2255
  if (!data.name || !data.description) continue;
2124
2256
  if (typeof data.name !== "string" || typeof data.description !== "string") continue;
2125
2257
  if (data.metadata?.internal === true && !options.includeInternal) continue;
2258
+ const safeName = sanitizeMetadata(data.name);
2259
+ const safeDescription = sanitizeMetadata(data.description);
2126
2260
  parsedSkills.push({
2127
2261
  mdPath,
2128
- name: data.name,
2129
- description: data.description,
2262
+ name: safeName,
2263
+ description: safeDescription,
2130
2264
  content,
2131
- slug: toSkillSlug(data.name),
2265
+ slug: toSkillSlug(safeName),
2132
2266
  metadata: data.metadata
2133
2267
  });
2134
2268
  }
@@ -2165,7 +2299,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
2165
2299
  tree
2166
2300
  };
2167
2301
  }
2168
- var version$1 = "1.5.1";
2302
+ var version$1 = "1.5.2";
2169
2303
  const isCancelled$1 = (value) => typeof value === "symbol";
2170
2304
  async function isSourcePrivate(source) {
2171
2305
  const ownerRepo = parseOwnerRepo(source);
@@ -2191,7 +2325,7 @@ function socketLabel(audit) {
2191
2325
  return count > 0 ? import_picocolors.default.red(`${count} alert${count !== 1 ? "s" : ""}`) : import_picocolors.default.green("0 alerts");
2192
2326
  }
2193
2327
  function padEnd(str, width) {
2194
- const visible = str.replace(/\x1b\[[0-9;]*m/g, "");
2328
+ const visible = stripTerminalEscapes(str);
2195
2329
  const pad = Math.max(0, width - visible.length);
2196
2330
  return str + " ".repeat(pad);
2197
2331
  }
@@ -2514,6 +2648,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2514
2648
  process.exit(0);
2515
2649
  }
2516
2650
  }
2651
+ const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
2652
+ const wellKnownPrivacyPromise = isSourcePrivate(sourceIdentifier).catch(() => null);
2517
2653
  spinner.start("Installing skills...");
2518
2654
  const results = [];
2519
2655
  for (const skill of selectedSkills) for (const agent of targetAgents) {
@@ -2531,10 +2667,9 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2531
2667
  console.log();
2532
2668
  const successful = results.filter((r) => r.success);
2533
2669
  const failed = results.filter((r) => !r.success);
2534
- const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
2535
2670
  const skillFiles = {};
2536
2671
  for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
2537
- if (await isSourcePrivate(sourceIdentifier) !== true) track({
2672
+ if (await wellKnownPrivacyPromise !== true) track({
2538
2673
  event: "install",
2539
2674
  source: sourceIdentifier,
2540
2675
  skills: selectedSkills.map((s) => s.installName).join(","),
@@ -2646,7 +2781,14 @@ async function runAdd(args, options = {}) {
2646
2781
  spinner.start("Parsing source...");
2647
2782
  const parsed = parseSource(source);
2648
2783
  spinner.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
2649
- 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) {
2650
2792
  console.log();
2651
2793
  M.warn(import_picocolors.default.yellow(import_picocolors.default.bold("⚠ OpenClaw skills are unverified community submissions.")));
2652
2794
  M.message(import_picocolors.default.yellow("This source contains user-submitted skills that have not been reviewed for safety or quality."));
@@ -2681,7 +2823,11 @@ async function runAdd(args, options = {}) {
2681
2823
  fullDepth: options.fullDepth
2682
2824
  });
2683
2825
  } else if (parsed.type === "github" && !options.fullDepth) {
2684
- const BLOB_ALLOWED_OWNERS = ["vercel", "vercel-labs"];
2826
+ const BLOB_ALLOWED_OWNERS = [
2827
+ "vercel",
2828
+ "vercel-labs",
2829
+ "heygen-com"
2830
+ ];
2685
2831
  const ownerRepo = getOwnerRepo(parsed);
2686
2832
  const owner = ownerRepo?.split("/")[0]?.toLowerCase();
2687
2833
  if (ownerRepo && owner && BLOB_ALLOWED_OWNERS.includes(owner)) {
@@ -3019,18 +3165,8 @@ async function runAdd(args, options = {}) {
3019
3165
  else continue;
3020
3166
  const normalizedSource = getOwnerRepo(parsed);
3021
3167
  const lockSource = parsed.url.startsWith("git@") ? parsed.url : normalizedSource;
3022
- if (normalizedSource) {
3023
- const ownerRepo = parseOwnerRepo(normalizedSource);
3024
- if (ownerRepo) {
3025
- if (await isRepoPrivate(ownerRepo.owner, ownerRepo.repo) === false) track({
3026
- event: "install",
3027
- source: normalizedSource,
3028
- skills: selectedSkills.map((s) => s.name).join(","),
3029
- agents: targetAgents.join(","),
3030
- ...installGlobally && { global: "1" },
3031
- skillFiles: JSON.stringify(skillFiles)
3032
- });
3033
- } else track({
3168
+ if (normalizedSource) if (parseOwnerRepo(normalizedSource)) {
3169
+ if (await repoPrivacyPromise === false) track({
3034
3170
  event: "install",
3035
3171
  source: normalizedSource,
3036
3172
  skills: selectedSkills.map((s) => s.name).join(","),
@@ -3038,9 +3174,21 @@ async function runAdd(args, options = {}) {
3038
3174
  ...installGlobally && { global: "1" },
3039
3175
  skillFiles: JSON.stringify(skillFiles)
3040
3176
  });
3041
- }
3177
+ } else track({
3178
+ event: "install",
3179
+ source: normalizedSource,
3180
+ skills: selectedSkills.map((s) => s.name).join(","),
3181
+ agents: targetAgents.join(","),
3182
+ ...installGlobally && { global: "1" },
3183
+ skillFiles: JSON.stringify(skillFiles)
3184
+ });
3042
3185
  if (successful.length > 0 && installGlobally && normalizedSource) {
3043
3186
  const successfulSkillNames = new Set(successful.map((r) => r.skill));
3187
+ let cachedTree;
3188
+ if (parsed.type === "github" && !blobResult) {
3189
+ const token = getGitHubToken();
3190
+ cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, token);
3191
+ }
3044
3192
  for (const skill of selectedSkills) {
3045
3193
  const skillDisplayName = getSkillDisplayName(skill);
3046
3194
  if (successfulSkillNames.has(skillDisplayName)) try {
@@ -3049,8 +3197,11 @@ async function runAdd(args, options = {}) {
3049
3197
  if (blobResult && skillPathValue) {
3050
3198
  const hash = getSkillFolderHashFromTree(blobResult.tree, skillPathValue);
3051
3199
  if (hash) skillFolderHash = hash;
3052
- } else if (parsed.type === "github" && skillPathValue) {
3053
- const hash = 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)));
3054
3205
  if (hash) skillFolderHash = hash;
3055
3206
  }
3056
3207
  await addSkillToLock(skill.name, {
@@ -3071,10 +3222,12 @@ async function runAdd(args, options = {}) {
3071
3222
  const skillDisplayName = getSkillDisplayName(skill);
3072
3223
  if (successfulSkillNames.has(skillDisplayName)) try {
3073
3224
  const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
3225
+ const skillPathValue = skillFiles[skill.name];
3074
3226
  await addSkillToLocalLock(skill.name, {
3075
3227
  source: lockSource || parsed.url,
3076
3228
  ref: parsed.ref,
3077
3229
  sourceType: parsed.type,
3230
+ ...skillPathValue && { skillPath: skillPathValue },
3078
3231
  computedHash
3079
3232
  }, cwd);
3080
3233
  } catch {}
@@ -3259,9 +3412,9 @@ async function searchSkillsAPI(query) {
3259
3412
  const res = await fetch(url);
3260
3413
  if (!res.ok) return [];
3261
3414
  return (await res.json()).skills.map((skill) => ({
3262
- name: skill.name,
3263
- slug: skill.id,
3264
- source: skill.source || "",
3415
+ name: sanitizeMetadata(skill.name),
3416
+ slug: sanitizeMetadata(skill.id),
3417
+ source: sanitizeMetadata(skill.source || ""),
3265
3418
  installs: skill.installs
3266
3419
  })).sort((a, b) => (b.installs || 0) - (a.installs || 0));
3267
3420
  } catch {
@@ -3874,7 +4027,7 @@ async function runList(args) {
3874
4027
  const shortPath = shortenPath(skill.canonicalPath, cwd);
3875
4028
  const agentNames = skill.agents.map((a) => agents[a].displayName);
3876
4029
  const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$1}`;
3877
- console.log(`${prefix}${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
4030
+ console.log(`${prefix}${CYAN}${sanitizeMetadata(skill.name)}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
3878
4031
  console.log(`${prefix} ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
3879
4032
  }
3880
4033
  console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
@@ -4102,18 +4255,25 @@ function formatSourceInput(sourceUrl, ref) {
4102
4255
  if (!ref) return sourceUrl;
4103
4256
  return `${sourceUrl}#${ref}`;
4104
4257
  }
4258
+ function deriveSkillFolder(skillPath) {
4259
+ let folder = skillPath;
4260
+ if (folder.endsWith("/SKILL.md")) folder = folder.slice(0, -9);
4261
+ else if (folder.endsWith("SKILL.md")) folder = folder.slice(0, -8);
4262
+ if (folder.endsWith("/")) folder = folder.slice(0, -1);
4263
+ return folder;
4264
+ }
4265
+ function appendFolderAndRef(source, skillPath, ref) {
4266
+ const folder = deriveSkillFolder(skillPath);
4267
+ const withFolder = folder ? `${source}/${folder}` : source;
4268
+ return ref ? `${withFolder}#${ref}` : withFolder;
4269
+ }
4105
4270
  function buildUpdateInstallSource(entry) {
4106
4271
  if (!entry.skillPath) return formatSourceInput(entry.sourceUrl, entry.ref);
4107
- let skillFolder = entry.skillPath;
4108
- if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
4109
- else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
4110
- if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
4111
- let installSource = skillFolder ? `${entry.source}/${skillFolder}` : entry.source;
4112
- if (entry.ref) installSource = `${installSource}#${entry.ref}`;
4113
- return installSource;
4272
+ return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
4114
4273
  }
4115
4274
  function buildLocalUpdateSource(entry) {
4116
- return formatSourceInput(entry.source, entry.ref);
4275
+ if (!entry.skillPath) return formatSourceInput(entry.source, entry.ref);
4276
+ return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
4117
4277
  }
4118
4278
  const __dirname = dirname(fileURLToPath(import.meta.url));
4119
4279
  function getVersion() {
@@ -4452,10 +4612,10 @@ function printSkippedSkills(skipped) {
4452
4612
  for (const [source, skills] of grouped) {
4453
4613
  if (skills.length === 1) {
4454
4614
  const skill = skills[0];
4455
- console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
4615
+ console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)} ${DIM}(${skill.reason})${RESET}`);
4456
4616
  } else {
4457
4617
  const reason = skills[0].reason;
4458
- const names = skills.map((s) => s.name).join(", ");
4618
+ const names = skills.map((s) => sanitizeMetadata(s.name)).join(", ");
4459
4619
  console.log(` ${TEXT}•${RESET} ${names} ${DIM}(${reason})${RESET}`);
4460
4620
  }
4461
4621
  console.log(` ${DIM}To update: ${TEXT}npx skills add ${source} -g -y${RESET}`);
@@ -4516,7 +4676,7 @@ async function updateGlobalSkills(skillFilter) {
4516
4676
  }
4517
4677
  for (let i = 0; i < checkable.length; i++) {
4518
4678
  const { name: skillName, entry } = checkable[i];
4519
- process.stdout.write(`\r${DIM}Checking global skill ${i + 1}/${checkable.length}: ${skillName}${RESET}\x1b[K`);
4679
+ process.stdout.write(`\r${DIM}Checking global skill ${i + 1}/${checkable.length}: ${sanitizeMetadata(skillName)}${RESET}\x1b[K`);
4520
4680
  try {
4521
4681
  const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token, entry.ref);
4522
4682
  if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
@@ -4555,12 +4715,13 @@ async function updateGlobalSkills(skillFilter) {
4555
4715
  console.log(`${TEXT}Found ${updates.length} global update(s)${RESET}`);
4556
4716
  console.log();
4557
4717
  for (const update of updates) {
4558
- console.log(`${TEXT}Updating ${update.name}...${RESET}`);
4718
+ const safeName = sanitizeMetadata(update.name);
4719
+ console.log(`${TEXT}Updating ${safeName}...${RESET}`);
4559
4720
  const installUrl = buildUpdateInstallSource(update.entry);
4560
4721
  const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
4561
4722
  if (!existsSync(cliEntry)) {
4562
4723
  failCount++;
4563
- console.log(` ${DIM}✗ Failed to update ${update.name}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4724
+ console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4564
4725
  continue;
4565
4726
  }
4566
4727
  if (spawnSync(process.execPath, [
@@ -4579,10 +4740,10 @@ async function updateGlobalSkills(skillFilter) {
4579
4740
  shell: process.platform === "win32"
4580
4741
  }).status === 0) {
4581
4742
  successCount++;
4582
- console.log(` ${TEXT}✓${RESET} Updated ${update.name}`);
4743
+ console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
4583
4744
  } else {
4584
4745
  failCount++;
4585
- console.log(` ${DIM}✗ Failed to update ${update.name}${RESET}`);
4746
+ console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
4586
4747
  }
4587
4748
  }
4588
4749
  printSkippedSkills(skipped);
@@ -4607,21 +4768,35 @@ async function updateProjectSkills(skillFilter) {
4607
4768
  foundCount: 0
4608
4769
  };
4609
4770
  }
4610
- 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}`);
4611
4783
  console.log();
4612
- for (const skill of projectSkills) {
4613
- 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}`);
4614
4787
  const installUrl = buildLocalUpdateSource(skill.entry);
4615
4788
  const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
4616
4789
  if (!existsSync(cliEntry)) {
4617
4790
  failCount++;
4618
- console.log(` ${DIM}✗ Failed to update ${skill.name}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4791
+ console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
4619
4792
  continue;
4620
4793
  }
4621
4794
  if (spawnSync(process.execPath, [
4622
4795
  cliEntry,
4623
4796
  "add",
4624
4797
  installUrl,
4798
+ "--skill",
4799
+ skill.name,
4625
4800
  "-y"
4626
4801
  ], {
4627
4802
  stdio: [
@@ -4633,18 +4808,29 @@ async function updateProjectSkills(skillFilter) {
4633
4808
  shell: process.platform === "win32"
4634
4809
  }).status === 0) {
4635
4810
  successCount++;
4636
- console.log(` ${TEXT}✓${RESET} Updated ${skill.name}`);
4811
+ console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
4637
4812
  } else {
4638
4813
  failCount++;
4639
- console.log(` ${DIM}✗ Failed to update ${skill.name}${RESET}`);
4814
+ console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
4640
4815
  }
4641
4816
  }
4817
+ printLegacyProjectSkills(legacy);
4642
4818
  return {
4643
4819
  successCount,
4644
4820
  failCount,
4645
4821
  foundCount: projectSkills.length
4646
4822
  };
4647
4823
  }
4824
+ function printLegacyProjectSkills(legacy) {
4825
+ if (legacy.length === 0) return;
4826
+ console.log();
4827
+ console.log(`${DIM}${legacy.length} project skill(s) cannot be updated automatically (installed before skillPath tracking):${RESET}`);
4828
+ for (const skill of legacy) {
4829
+ const reinstall = formatSourceInput(skill.entry.source, skill.entry.ref);
4830
+ console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)}`);
4831
+ console.log(` ${DIM}To refresh: ${TEXT}npx skills add ${reinstall} -y${RESET}`);
4832
+ }
4833
+ }
4648
4834
  async function runUpdate(args = []) {
4649
4835
  const options = parseUpdateOptions(args);
4650
4836
  const scope = await resolveUpdateScope(options);
@@ -4756,5 +4942,5 @@ async function main() {
4756
4942
  console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
4757
4943
  }
4758
4944
  }
4759
- main();
4945
+ main().finally(() => flushTelemetry().then(() => process.exit(0)));
4760
4946
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills",
3
- "version": "1.5.1",
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",