add-skill 1.0.10 → 1.0.12

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 +51 -37
  2. package/dist/index.js +126 -11
  3. package/package.json +5 -2
package/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  Install agent skills onto your coding agents from any git repository.
4
4
 
5
- Supports [OpenCode](https://opencode.ai), [Claude Code](https://claude.ai/code), [Codex](https://developers.openai.com/codex), [Cursor](https://cursor.com), and [Antigravity](https://antigravity.google).
5
+ <!-- agent-list:start -->
6
+ Supports **Opencode**, **Claude Code**, **Codex**, **Cursor**, and [11 more](#available-agents).
7
+ <!-- agent-list:end -->
6
8
 
7
9
  ## Quick Start
8
10
 
@@ -47,7 +49,7 @@ npx add-skill git@github.com:vercel-labs/agent-skills.git
47
49
  | Option | Description |
48
50
  |--------|-------------|
49
51
  | `-g, --global` | Install to user directory instead of project |
50
- | `-a, --agent <agents...>` | Target specific agents: `opencode`, `claude-code`, `codex`, `cursor`, `antigravity` |
52
+ | `-a, --agent <agents...>` | <!-- agent-names:start -->Target specific agents (e.g., `claude-code`, `codex`). See [Available Agents](#available-agents)<!-- agent-names:end --> |
51
53
  | `-s, --skill <skills...>` | Install specific skills by name |
52
54
  | `-l, --list` | List available skills without installing |
53
55
  | `-y, --yes` | Skip all confirmation prompts |
@@ -73,33 +75,29 @@ npx add-skill vercel-labs/agent-skills --skill frontend-design -g -a claude-code
73
75
  npx add-skill vercel-labs/agent-skills -y -g
74
76
  ```
75
77
 
76
- ## Installation Paths
77
-
78
- Skills are installed to different locations depending on the agent and scope:
79
-
80
- ### Project-level (default)
81
-
82
- Installed in your current working directory. Commit these to share with your team.
83
-
84
- | Agent | Path |
85
- |-------|------|
86
- | OpenCode | `.opencode/skill/<name>/` |
87
- | Claude Code | `.claude/skills/<name>/` |
88
- | Codex | `.codex/skills/<name>/` |
89
- | Cursor | `.cursor/skills/<name>/` |
90
- | Antigravity | `.agent/skills/<name>/` |
91
-
92
- ### Global (`--global`)
93
-
94
- Installed in your home directory. Available across all projects.
95
-
96
- | Agent | Path |
97
- |-------|------|
98
- | OpenCode | `~/.config/opencode/skill/<name>/` |
99
- | Claude Code | `~/.claude/skills/<name>/` |
100
- | Codex | `~/.codex/skills/<name>/` |
101
- | Cursor | `~/.cursor/skills/<name>/` |
102
- | Antigravity | `~/.gemini/antigravity/skills/<name>/` |
78
+ ## Available Agents
79
+
80
+ Skills can be installed to any of these supported agents. Use `-g, --global` to install to the global path instead of project-level.
81
+
82
+ <!-- available-agents:start -->
83
+ | Agent | Project Path | Global Path |
84
+ |-------|--------------|-------------|
85
+ | OpenCode | `.opencode/skill/` | `~/.config/opencode/skill/` |
86
+ | Claude Code | `.claude/skills/` | `~/.claude/skills/` |
87
+ | Codex | `.codex/skills/` | `~/.codex/skills/` |
88
+ | Cursor | `.cursor/skills/` | `~/.cursor/skills/` |
89
+ | Amp | `.agents/skills/` | `~/.config/agents/skills/` |
90
+ | Kilo Code | `.kilocode/skills/` | `~/.kilocode/skills/` |
91
+ | Roo Code | `.roo/skills/` | `~/.roo/skills/` |
92
+ | Goose | `.goose/skills/` | `~/.config/goose/skills/` |
93
+ | Gemini CLI | `.gemini/skills/` | `~/.gemini/skills/` |
94
+ | Antigravity | `.agent/skills/` | `~/.gemini/antigravity/skills/` |
95
+ | GitHub Copilot | `.github/skills/` | `~/.copilot/skills/` |
96
+ | Clawdbot | `skills/` | `~/.clawdbot/skills/` |
97
+ | Droid | `.factory/skills/` | `~/.factory/skills/` |
98
+ | Gemini CLI | `.gemini/skills/` | `~/.gemini/skills/` |
99
+ | Windsurf | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
100
+ <!-- available-agents:end -->
103
101
 
104
102
  ## Agent Detection
105
103
 
@@ -138,16 +136,27 @@ Describe the scenarios where this skill should be used.
138
136
 
139
137
  The CLI searches for skills in these locations within a repository:
140
138
 
139
+ <!-- skill-discovery:start -->
141
140
  - Root directory (if it contains `SKILL.md`)
142
141
  - `skills/`
143
142
  - `skills/.curated/`
144
143
  - `skills/.experimental/`
145
144
  - `skills/.system/`
146
- - `.codex/skills/`
147
- - `.claude/skills/`
148
145
  - `.opencode/skill/`
146
+ - `.claude/skills/`
147
+ - `.codex/skills/`
149
148
  - `.cursor/skills/`
149
+ - `.agents/skills/`
150
+ - `.kilocode/skills/`
151
+ - `.roo/skills/`
152
+ - `.goose/skills/`
153
+ - `.gemini/skills/`
150
154
  - `.agent/skills/`
155
+ - `.github/skills/`
156
+ - `./skills/`
157
+ - `.factory/skills/`
158
+ - `.windsurf/skills/`
159
+ <!-- skill-discovery:end -->
151
160
 
152
161
  If no skills are found in standard locations, a recursive search is performed.
153
162
 
@@ -155,12 +164,12 @@ If no skills are found in standard locations, a recursive search is performed.
155
164
 
156
165
  Skills are generally compatible across agents since they follow a shared [Agent Skills specification](https://agentskills.io). However, some features may be agent-specific:
157
166
 
158
- | Feature | OpenCode | Claude Code | Codex | Cursor | Antigravity |
159
- |---------|----------|-------------|-------|--------|-------------|
160
- | Basic skills | Yes | Yes | Yes | Yes | Yes |
161
- | `allowed-tools` | Yes | Yes | Yes | Yes | Yes |
162
- | `context: fork` | No | Yes | No | No | No |
163
- | Hooks | No | Yes | No | No | No |
167
+ | Feature | OpenCode | Claude Code | Codex | Cursor | Antigravity | Roo Code | Github Copilot | Amp | Clawdbot |
168
+ |---------|----------|-------------|-------|--------|-------------|----------|----------------|-----|----------|
169
+ | Basic skills | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
170
+ | `allowed-tools` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
171
+ | `context: fork` | No | Yes | No | No | No | No | No | No | No |
172
+ | Hooks | No | Yes | No | No | No | No | No | No | No |
164
173
 
165
174
  ## Troubleshooting
166
175
 
@@ -186,7 +195,12 @@ Ensure you have write access to the target directory.
186
195
  - [Claude Code Skills Documentation](https://code.claude.com/docs/en/skills)
187
196
  - [Codex Skills Documentation](https://developers.openai.com/codex/skills)
188
197
  - [Cursor Skills Documentation](https://cursor.com/docs/context/skills)
198
+ - [Gemini CLI Skills Documentation](https://geminicli.com/docs/cli/skills/)
199
+ - [Amp Skills Documentation](https://ampcode.com/manual#agent-skills)
189
200
  - [Antigravity Skills Documentation](https://antigravity.google/docs/skills)
201
+ - [GitHub Copilot Agent Skills](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills)
202
+ - [Roo Code Skills Documentation](https://docs.roocode.com/features/skills)
203
+ - [Clawdbot Skills Documentation](https://docs.clawd.bot/tools/skills)
190
204
 
191
205
  ## License
192
206
 
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import chalk from "chalk";
7
7
 
8
8
  // src/git.ts
9
9
  import simpleGit from "simple-git";
10
- import { join } from "path";
10
+ import { join, normalize, resolve, sep } from "path";
11
11
  import { mkdtemp, rm } from "fs/promises";
12
12
  import { tmpdir } from "os";
13
13
  function parseSource(input) {
@@ -72,6 +72,11 @@ async function cloneRepo(url) {
72
72
  return tempDir;
73
73
  }
74
74
  async function cleanupTempDir(dir) {
75
+ const normalizedDir = normalize(resolve(dir));
76
+ const normalizedTmpDir = normalize(resolve(tmpdir()));
77
+ if (!normalizedDir.startsWith(normalizedTmpDir + sep) && normalizedDir !== normalizedTmpDir) {
78
+ throw new Error("Attempted to clean up directory outside of temp directory");
79
+ }
75
80
  await rm(dir, { recursive: true, force: true });
76
81
  }
77
82
 
@@ -149,7 +154,8 @@ async function discoverSkills(basePath, subpath) {
149
154
  join2(searchPath, ".kilocode/skills"),
150
155
  join2(searchPath, ".roo/skills"),
151
156
  join2(searchPath, ".goose/skills"),
152
- join2(searchPath, ".agent/skills")
157
+ join2(searchPath, ".agent/skills"),
158
+ join2(searchPath, ".github/skills")
153
159
  ];
154
160
  for (const dir of prioritySearchDirs) {
155
161
  try {
@@ -187,7 +193,7 @@ function getSkillDisplayName(skill) {
187
193
 
188
194
  // src/installer.ts
189
195
  import { mkdir, cp, access, readdir as readdir2 } from "fs/promises";
190
- import { join as join4, basename as basename2 } from "path";
196
+ import { join as join4, basename as basename2, normalize as normalize2, resolve as resolve2, sep as sep2 } from "path";
191
197
 
192
198
  // src/agents.ts
193
199
  import { homedir } from "os";
@@ -267,6 +273,15 @@ var agents = {
267
273
  return existsSync(join3(home, ".config/goose"));
268
274
  }
269
275
  },
276
+ "gemini-cli": {
277
+ name: "gemini-cli",
278
+ displayName: "Gemini CLI",
279
+ skillsDir: ".gemini/skills",
280
+ globalSkillsDir: join3(home, ".gemini/skills"),
281
+ detectInstalled: async () => {
282
+ return existsSync(join3(home, ".gemini"));
283
+ }
284
+ },
270
285
  antigravity: {
271
286
  name: "antigravity",
272
287
  displayName: "Antigravity",
@@ -275,6 +290,51 @@ var agents = {
275
290
  detectInstalled: async () => {
276
291
  return existsSync(join3(process.cwd(), ".agent")) || existsSync(join3(home, ".gemini/antigravity"));
277
292
  }
293
+ },
294
+ "github-copilot": {
295
+ name: "github-copilot",
296
+ displayName: "GitHub Copilot",
297
+ skillsDir: ".github/skills",
298
+ globalSkillsDir: join3(home, ".copilot/skills"),
299
+ detectInstalled: async () => {
300
+ return existsSync(join3(process.cwd(), ".github")) || existsSync(join3(home, ".copilot"));
301
+ }
302
+ },
303
+ clawdbot: {
304
+ name: "clawdbot",
305
+ displayName: "Clawdbot",
306
+ skillsDir: "skills",
307
+ globalSkillsDir: join3(home, ".clawdbot/skills"),
308
+ detectInstalled: async () => {
309
+ return existsSync(join3(home, ".clawdbot"));
310
+ }
311
+ },
312
+ droid: {
313
+ name: "droid",
314
+ displayName: "Droid",
315
+ skillsDir: ".factory/skills",
316
+ globalSkillsDir: join3(home, ".factory/skills"),
317
+ detectInstalled: async () => {
318
+ return existsSync(join3(home, ".factory/skills"));
319
+ }
320
+ },
321
+ gemini: {
322
+ name: "gemini",
323
+ displayName: "Gemini CLI",
324
+ skillsDir: ".gemini/skills",
325
+ globalSkillsDir: join3(home, ".gemini/skills"),
326
+ detectInstalled: async () => {
327
+ return existsSync(join3(home, ".gemini"));
328
+ }
329
+ },
330
+ windsurf: {
331
+ name: "windsurf",
332
+ displayName: "Windsurf",
333
+ skillsDir: ".windsurf/skills",
334
+ globalSkillsDir: join3(home, ".codeium/windsurf/skills"),
335
+ detectInstalled: async () => {
336
+ return existsSync(join3(home, ".codeium/windsurf"));
337
+ }
278
338
  }
279
339
  };
280
340
  async function detectInstalledAgents() {
@@ -288,11 +348,36 @@ async function detectInstalledAgents() {
288
348
  }
289
349
 
290
350
  // src/installer.ts
351
+ function sanitizeName(name) {
352
+ let sanitized = name.replace(/[\/\\:\0]/g, "");
353
+ sanitized = sanitized.replace(/^[.\s]+|[.\s]+$/g, "");
354
+ sanitized = sanitized.replace(/^\.+/, "");
355
+ if (!sanitized || sanitized.length === 0) {
356
+ sanitized = "unnamed-skill";
357
+ }
358
+ if (sanitized.length > 255) {
359
+ sanitized = sanitized.substring(0, 255);
360
+ }
361
+ return sanitized;
362
+ }
363
+ function isPathSafe(basePath, targetPath) {
364
+ const normalizedBase = normalize2(resolve2(basePath));
365
+ const normalizedTarget = normalize2(resolve2(targetPath));
366
+ return normalizedTarget.startsWith(normalizedBase + sep2) || normalizedTarget === normalizedBase;
367
+ }
291
368
  async function installSkillForAgent(skill, agentType, options = {}) {
292
369
  const agent = agents[agentType];
293
- const skillName = skill.name || basename2(skill.path);
370
+ const rawSkillName = skill.name || basename2(skill.path);
371
+ const skillName = sanitizeName(rawSkillName);
294
372
  const targetBase = options.global ? agent.globalSkillsDir : join4(options.cwd || process.cwd(), agent.skillsDir);
295
373
  const targetDir = join4(targetBase, skillName);
374
+ if (!isPathSafe(targetBase, targetDir)) {
375
+ return {
376
+ success: false,
377
+ path: targetDir,
378
+ error: "Invalid skill name: potential path traversal detected"
379
+ };
380
+ }
296
381
  try {
297
382
  await mkdir(targetDir, { recursive: true });
298
383
  await copyDirectory(skill.path, targetDir);
@@ -332,8 +417,12 @@ async function copyDirectory(src, dest) {
332
417
  }
333
418
  async function isSkillInstalled(skillName, agentType, options = {}) {
334
419
  const agent = agents[agentType];
420
+ const sanitized = sanitizeName(skillName);
335
421
  const targetBase = options.global ? agent.globalSkillsDir : join4(options.cwd || process.cwd(), agent.skillsDir);
336
- const skillDir = join4(targetBase, skillName);
422
+ const skillDir = join4(targetBase, sanitized);
423
+ if (!isPathSafe(targetBase, skillDir)) {
424
+ return false;
425
+ }
337
426
  try {
338
427
  await access(skillDir);
339
428
  return true;
@@ -343,8 +432,13 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
343
432
  }
344
433
  function getInstallPath(skillName, agentType, options = {}) {
345
434
  const agent = agents[agentType];
435
+ const sanitized = sanitizeName(skillName);
346
436
  const targetBase = options.global ? agent.globalSkillsDir : join4(options.cwd || process.cwd(), agent.skillsDir);
347
- return join4(targetBase, skillName);
437
+ const installPath = join4(targetBase, sanitized);
438
+ if (!isPathSafe(targetBase, installPath)) {
439
+ throw new Error("Invalid skill name: potential path traversal detected");
440
+ }
441
+ return installPath;
348
442
  }
349
443
 
350
444
  // src/telemetry.ts
@@ -383,7 +477,7 @@ function track(data) {
383
477
  // package.json
384
478
  var package_default = {
385
479
  name: "add-skill",
386
- version: "1.0.10",
480
+ version: "1.0.12",
387
481
  description: "Install agent skills onto coding agents (OpenCode, Claude Code, Codex, Cursor)",
388
482
  type: "module",
389
483
  bin: {
@@ -396,7 +490,8 @@ var package_default = {
396
490
  scripts: {
397
491
  build: "tsup src/index.ts --format esm --dts --clean",
398
492
  dev: "tsx src/index.ts",
399
- prepublishOnly: "npm run build"
493
+ prepublishOnly: "npm run build",
494
+ "update-readme": "tsx scripts/update-readme.ts"
400
495
  },
401
496
  keywords: [
402
497
  "cli",
@@ -405,7 +500,9 @@ var package_default = {
405
500
  "claude-code",
406
501
  "codex",
407
502
  "cursor",
503
+ "amp",
408
504
  "antigravity",
505
+ "roo-code",
409
506
  "ai-agents"
410
507
  ],
411
508
  repository: {
@@ -439,7 +536,25 @@ var package_default = {
439
536
  // src/index.ts
440
537
  var version = package_default.version;
441
538
  setVersion(version);
442
- program.name("add-skill").description("Install skills onto coding agents (OpenCode, Claude Code, Codex, Cursor, Antigravity)").version(version).argument("<source>", "Git repo URL, GitHub shorthand (owner/repo), or direct path to skill").option("-g, --global", "Install skill globally (user-level) instead of project-level").option("-a, --agent <agents...>", "Specify agents to install to (opencode, claude-code, codex, cursor)").option("-s, --skill <skills...>", "Specify skill names to install (skip selection prompt)").option("-l, --list", "List available skills in the repository without installing").option("-y, --yes", "Skip confirmation prompts").action(async (source, options) => {
539
+ program.name("add-skill").description("Install skills onto coding agents (OpenCode, Claude Code, Codex, Cursor, Antigravity, Github Copilot, Roo Code)").version(version).argument("<source>", "Git repo URL, GitHub shorthand (owner/repo), or direct path to skill").option("-g, --global", "Install skill globally (user-level) instead of project-level").option("-a, --agent <agents...>", "Specify agents to install to (opencode, claude-code, codex, cursor, antigravity, gitub-copilot, roo)").option("-s, --skill <skills...>", "Specify skill names to install (skip selection prompt)").option("-l, --list", "List available skills in the repository without installing").option("-y, --yes", "Skip confirmation prompts").configureOutput({
540
+ outputError: (str, write) => {
541
+ if (str.includes("missing required argument")) {
542
+ console.log();
543
+ console.log(chalk.bgRed.white.bold(" ERROR ") + " " + chalk.red("Missing required argument: source"));
544
+ console.log();
545
+ console.log(chalk.dim(" Usage:"));
546
+ console.log(` ${chalk.cyan("npx add-skill")} ${chalk.yellow("<source>")} ${chalk.dim("[options]")}`);
547
+ console.log();
548
+ console.log(chalk.dim(" Example:"));
549
+ console.log(` ${chalk.cyan("npx add-skill")} ${chalk.yellow("vercel-labs/agent-skills")}`);
550
+ console.log();
551
+ console.log(chalk.dim(" Run") + ` ${chalk.cyan("npx add-skill --help")} ` + chalk.dim("for more information."));
552
+ console.log();
553
+ } else {
554
+ write(str);
555
+ }
556
+ }
557
+ }).action(async (source, options) => {
443
558
  await main(source, options);
444
559
  });
445
560
  program.parse();
@@ -520,8 +635,8 @@ async function main(source, options) {
520
635
  selectedSkills = selected;
521
636
  }
522
637
  let targetAgents;
638
+ const validAgents = Object.keys(agents);
523
639
  if (options.agent && options.agent.length > 0) {
524
- const validAgents = ["opencode", "claude-code", "codex", "cursor", "antigravity"];
525
640
  const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
526
641
  if (invalidAgents.length > 0) {
527
642
  p.log.error(`Invalid agents: ${invalidAgents.join(", ")}`);
@@ -536,7 +651,7 @@ async function main(source, options) {
536
651
  spinner2.stop(`Detected ${installedAgents.length} agent${installedAgents.length !== 1 ? "s" : ""}`);
537
652
  if (installedAgents.length === 0) {
538
653
  if (options.yes) {
539
- targetAgents = ["opencode", "claude-code", "codex", "cursor", "antigravity"];
654
+ targetAgents = validAgents;
540
655
  p.log.info("Installing to all agents (none detected)");
541
656
  } else {
542
657
  p.log.warn("No coding agents detected. You can still install skills.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-skill",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Install agent skills onto coding agents (OpenCode, Claude Code, Codex, Cursor)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,8 @@
13
13
  "scripts": {
14
14
  "build": "tsup src/index.ts --format esm --dts --clean",
15
15
  "dev": "tsx src/index.ts",
16
- "prepublishOnly": "npm run build"
16
+ "prepublishOnly": "npm run build",
17
+ "update-readme": "tsx scripts/update-readme.ts"
17
18
  },
18
19
  "keywords": [
19
20
  "cli",
@@ -22,7 +23,9 @@
22
23
  "claude-code",
23
24
  "codex",
24
25
  "cursor",
26
+ "amp",
25
27
  "antigravity",
28
+ "roo-code",
26
29
  "ai-agents"
27
30
  ],
28
31
  "repository": {