skills 1.5.1 → 1.5.3

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