add-skill 1.0.11 → 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 +50 -39
  2. package/dist/index.js +125 -11
  3. package/package.json +4 -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), [Antigravity](https://antigravity.google), and [Roo Code](https://roocode.com).
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,35 +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
- | Roo Code | `.roo/skills/<name>/` |
92
-
93
- ### Global (`--global`)
94
-
95
- Installed in your home directory. Available across all projects.
96
-
97
- | Agent | Path |
98
- |-------|------|
99
- | OpenCode | `~/.config/opencode/skill/<name>/` |
100
- | Claude Code | `~/.claude/skills/<name>/` |
101
- | Codex | `~/.codex/skills/<name>/` |
102
- | Cursor | `~/.cursor/skills/<name>/` |
103
- | Antigravity | `~/.gemini/antigravity/skills/<name>/` |
104
- | Roo Code | `~/.roo/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 -->
105
101
 
106
102
  ## Agent Detection
107
103
 
@@ -140,16 +136,27 @@ Describe the scenarios where this skill should be used.
140
136
 
141
137
  The CLI searches for skills in these locations within a repository:
142
138
 
139
+ <!-- skill-discovery:start -->
143
140
  - Root directory (if it contains `SKILL.md`)
144
141
  - `skills/`
145
142
  - `skills/.curated/`
146
143
  - `skills/.experimental/`
147
144
  - `skills/.system/`
148
- - `.codex/skills/`
149
- - `.claude/skills/`
150
145
  - `.opencode/skill/`
146
+ - `.claude/skills/`
147
+ - `.codex/skills/`
151
148
  - `.cursor/skills/`
149
+ - `.agents/skills/`
150
+ - `.kilocode/skills/`
151
+ - `.roo/skills/`
152
+ - `.goose/skills/`
153
+ - `.gemini/skills/`
152
154
  - `.agent/skills/`
155
+ - `.github/skills/`
156
+ - `./skills/`
157
+ - `.factory/skills/`
158
+ - `.windsurf/skills/`
159
+ <!-- skill-discovery:end -->
153
160
 
154
161
  If no skills are found in standard locations, a recursive search is performed.
155
162
 
@@ -157,12 +164,12 @@ If no skills are found in standard locations, a recursive search is performed.
157
164
 
158
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:
159
166
 
160
- | Feature | OpenCode | Claude Code | Codex | Cursor | Antigravity | Roo Code |
161
- |---------|----------|-------------|-------|--------|-------------|----------|
162
- | Basic skills | Yes | Yes | Yes | Yes | Yes | Yes |
163
- | `allowed-tools` | Yes | Yes | Yes | Yes | Yes | Yes |
164
- | `context: fork` | No | Yes | No | No | No | No |
165
- | Hooks | No | Yes | No | 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 |
166
173
 
167
174
  ## Troubleshooting
168
175
 
@@ -188,8 +195,12 @@ Ensure you have write access to the target directory.
188
195
  - [Claude Code Skills Documentation](https://code.claude.com/docs/en/skills)
189
196
  - [Codex Skills Documentation](https://developers.openai.com/codex/skills)
190
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)
191
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)
192
202
  - [Roo Code Skills Documentation](https://docs.roocode.com/features/skills)
203
+ - [Clawdbot Skills Documentation](https://docs.clawd.bot/tools/skills)
193
204
 
194
205
  ## License
195
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.11",
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,6 +500,7 @@ var package_default = {
405
500
  "claude-code",
406
501
  "codex",
407
502
  "cursor",
503
+ "amp",
408
504
  "antigravity",
409
505
  "roo-code",
410
506
  "ai-agents"
@@ -440,7 +536,25 @@ var package_default = {
440
536
  // src/index.ts
441
537
  var version = package_default.version;
442
538
  setVersion(version);
443
- program.name("add-skill").description("Install skills onto coding agents (OpenCode, Claude Code, Codex, Cursor, Antigravity, 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, 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").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) => {
444
558
  await main(source, options);
445
559
  });
446
560
  program.parse();
@@ -521,8 +635,8 @@ async function main(source, options) {
521
635
  selectedSkills = selected;
522
636
  }
523
637
  let targetAgents;
638
+ const validAgents = Object.keys(agents);
524
639
  if (options.agent && options.agent.length > 0) {
525
- const validAgents = ["opencode", "claude-code", "codex", "cursor", "antigravity", "roo"];
526
640
  const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
527
641
  if (invalidAgents.length > 0) {
528
642
  p.log.error(`Invalid agents: ${invalidAgents.join(", ")}`);
@@ -537,7 +651,7 @@ async function main(source, options) {
537
651
  spinner2.stop(`Detected ${installedAgents.length} agent${installedAgents.length !== 1 ? "s" : ""}`);
538
652
  if (installedAgents.length === 0) {
539
653
  if (options.yes) {
540
- targetAgents = ["opencode", "claude-code", "codex", "cursor", "antigravity", "roo"];
654
+ targetAgents = validAgents;
541
655
  p.log.info("Installing to all agents (none detected)");
542
656
  } else {
543
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.11",
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,6 +23,7 @@
22
23
  "claude-code",
23
24
  "codex",
24
25
  "cursor",
26
+ "amp",
25
27
  "antigravity",
26
28
  "roo-code",
27
29
  "ai-agents"