ai-agent-skills 1.2.0 → 1.2.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
  <strong>Homebrew for AI agent skills.</strong><br>
9
- One command. Ten agents. Skills that follow you.
9
+ Universal Skills installer for any agent that follows the open standard spec
10
10
  </p>
11
11
 
12
12
  <p align="center">
@@ -179,10 +179,8 @@ cp -r Ai-Agent-Skills/skills/pdf ~/.claude/skills/
179
179
 
180
180
  ## Create Your Own
181
181
 
182
- Two options:
183
182
 
184
- 1. **Generate in 30 seconds**: [skillcreator.ai/build](https://skillcreator.ai/build)
185
- 2. **Build manually**: Follow the [Agent Skills spec](https://agentskills.io/specification)
183
+ 1. **Build manually**: Follow the [Agent Skills spec](https://agentskills.io/specification)
186
184
 
187
185
  ## What Are Agent Skills?
188
186
 
@@ -209,19 +207,17 @@ We review all contributions for quality and spec compliance.
209
207
 
210
208
  ## Links
211
209
 
212
- - [Awesome Agent Skills](https://github.com/skillcreatorai/Awesome-Agent-Skills) - Curated list of 50+ skills
210
+
213
211
  - [Agent Skills Spec](https://agentskills.io) - Official format documentation
214
- - [Browse Skills](https://skillcreator.ai/discover) - Visual skill gallery with one-click install
215
- - [Create Skills](https://skillcreator.ai/build) - Generate skills from plain English
212
+ - [Browse Skills](https://skillcreator.ai/explore) - Visual skill gallery with one-click install
213
+ - [Create Skills](https://skillcreator.ai/build) - Generate skills (waitlist)
216
214
  - [Anthropic Skills](https://github.com/anthropics/skills) - Official example skills
217
215
 
218
216
  ## See Also
219
217
 
220
- **[openskills](https://github.com/numman-ali/openskills)** - Universal skills loader that inspired parts of this project. openskills focuses on flexibility: install from any GitHub repo, sync to AGENTS.md, works with any agent via CLI calls. Great if you need maximum customization.
221
-
222
- **We took a different approach**: Homebrew-style simplicity. No global install, no sync step, no AGENTS.md. Just `npx ai-agent-skills install pdf` and it lands in the right folder for your agent. Curated catalog, native paths, zero config.
218
+ **[openskills](https://github.com/numman-ali/openskills)** - another universal skills loader that inspired parts of this project (created pre the open agent skills standard) & Requires global install, AGENTS.md sync, and Bash calls. Great for flexibility.
223
219
 
224
- Different tools for different needs.
220
+ **ai-agent-skills** - Just `npx`, installs to native agent folders. Homebrew for skills.
225
221
 
226
222
  ---
227
223
 
package/cli.js CHANGED
@@ -202,7 +202,10 @@ function parseArgs(args) {
202
202
 
203
203
  // ============ SAFE FILE OPERATIONS ============
204
204
 
205
- function copyDir(src, dest, currentSize = { total: 0 }) {
205
+ function copyDir(src, dest, currentSize = { total: 0 }, rootSrc = null) {
206
+ // Track root source to prevent path escape attacks
207
+ if (rootSrc === null) rootSrc = src;
208
+
206
209
  try {
207
210
  if (fs.existsSync(dest)) {
208
211
  fs.rmSync(dest, { recursive: true });
@@ -211,13 +214,32 @@ function copyDir(src, dest, currentSize = { total: 0 }) {
211
214
 
212
215
  const entries = fs.readdirSync(src, { withFileTypes: true });
213
216
 
217
+ // Files/folders to skip during copy
218
+ const skipList = ['.git', '.github', 'node_modules', '.DS_Store'];
219
+
214
220
  for (const entry of entries) {
221
+ // Skip unnecessary files/folders
222
+ if (skipList.includes(entry.name)) continue;
223
+
224
+ // Skip symlinks to prevent path escape attacks
225
+ if (entry.isSymbolicLink()) {
226
+ warn(`Skipping symlink: ${entry.name}`);
227
+ continue;
228
+ }
229
+
215
230
  const srcPath = path.join(src, entry.name);
216
231
  const destPath = path.join(dest, entry.name);
217
232
 
233
+ // Verify resolved path stays within source directory (prevent path traversal)
234
+ const resolvedSrc = fs.realpathSync(srcPath);
235
+ if (!resolvedSrc.startsWith(fs.realpathSync(rootSrc))) {
236
+ warn(`Skipping file outside source directory: ${entry.name}`);
237
+ continue;
238
+ }
239
+
218
240
  if (entry.isDirectory()) {
219
- copyDir(srcPath, destPath, currentSize);
220
- } else {
241
+ copyDir(srcPath, destPath, currentSize, rootSrc);
242
+ } else if (entry.isFile()) {
221
243
  const stat = fs.statSync(srcPath);
222
244
  currentSize.total += stat.size;
223
245
 
@@ -227,6 +249,7 @@ function copyDir(src, dest, currentSize = { total: 0 }) {
227
249
 
228
250
  fs.copyFileSync(srcPath, destPath);
229
251
  }
252
+ // Skip any other special file types (sockets, devices, etc.)
230
253
  }
231
254
  } catch (e) {
232
255
  // Clean up partial install on failure
@@ -395,7 +418,6 @@ function getInstalledSkills(agent = 'claude') {
395
418
  function listInstalledSkills(agent = 'claude') {
396
419
  const installed = getInstalledSkills(agent);
397
420
  const destDir = AGENT_PATHS[agent] || AGENT_PATHS.claude;
398
- const data = loadSkillsJson();
399
421
 
400
422
  if (installed.length === 0) {
401
423
  warn(`No skills installed for ${agent}`);
@@ -406,43 +428,12 @@ function listInstalledSkills(agent = 'claude') {
406
428
  log(`\n${colors.bold}Installed Skills${colors.reset} (${installed.length} for ${agent})\n`);
407
429
  log(`${colors.dim}Location: ${destDir}${colors.reset}\n`);
408
430
 
409
- // Check for updates
410
- let updatesAvailable = 0;
411
-
412
431
  installed.forEach(name => {
413
- const skill = data.skills.find(s => s.name === name);
414
- const hasUpdate = skill && skill.lastUpdated && isUpdateAvailable(name, agent, skill.lastUpdated);
415
-
416
- if (hasUpdate) {
417
- updatesAvailable++;
418
- log(` ${colors.green}${name}${colors.reset} ${colors.yellow}(update available)${colors.reset}`);
419
- } else {
420
- log(` ${colors.green}${name}${colors.reset}`);
421
- }
432
+ log(` ${colors.green}${name}${colors.reset}`);
422
433
  });
423
434
 
424
- if (updatesAvailable > 0) {
425
- log(`\n${colors.yellow}${updatesAvailable} update(s) available.${colors.reset}`);
426
- log(`${colors.dim}Run: npx ai-agent-skills update <name> --agent ${agent}${colors.reset}`);
427
- }
428
-
429
- log(`\n${colors.dim}Uninstall: npx ai-agent-skills uninstall <name> --agent ${agent}${colors.reset}`);
430
- }
431
-
432
- function isUpdateAvailable(skillName, agent, repoLastUpdated) {
433
- // Simple check based on file modification time
434
- const destDir = AGENT_PATHS[agent] || AGENT_PATHS.claude;
435
- const skillPath = path.join(destDir, skillName, 'SKILL.md');
436
-
437
- try {
438
- if (!fs.existsSync(skillPath)) return false;
439
- const stat = fs.statSync(skillPath);
440
- const installedTime = stat.mtime.getTime();
441
- const repoTime = new Date(repoLastUpdated).getTime();
442
- return repoTime > installedTime;
443
- } catch {
444
- return false;
445
- }
435
+ log(`\n${colors.dim}Update: npx ai-agent-skills update <name> --agent ${agent}${colors.reset}`);
436
+ log(`${colors.dim}Uninstall: npx ai-agent-skills uninstall <name> --agent ${agent}${colors.reset}`);
446
437
  }
447
438
 
448
439
  function updateSkill(skillName, agent = 'claude', dryRun = false) {
@@ -768,11 +759,28 @@ async function browseSkills(agent = 'claude') {
768
759
  // ============ EXTERNAL INSTALL (GitHub/Local) ============
769
760
 
770
761
  function isGitHubUrl(source) {
771
- return source.includes('/') && !source.startsWith('.') && !source.startsWith('/') && !source.startsWith('~');
762
+ // Must have owner/repo format, not start with path indicators
763
+ return source.includes('/') &&
764
+ !source.startsWith('./') &&
765
+ !source.startsWith('../') &&
766
+ !source.startsWith('/') &&
767
+ !source.startsWith('~') &&
768
+ !isWindowsPath(source);
769
+ }
770
+
771
+ function isWindowsPath(source) {
772
+ // Match Windows absolute paths like C:\, D:\, etc.
773
+ return /^[a-zA-Z]:[\\\/]/.test(source);
772
774
  }
773
775
 
774
776
  function isLocalPath(source) {
775
- return source.startsWith('.') || source.startsWith('/') || source.startsWith('~');
777
+ // Explicit local paths: ./ or / or ~/ or Windows paths like C:\
778
+ // Also accept ../ as local path (will be resolved)
779
+ return source.startsWith('./') ||
780
+ source.startsWith('../') ||
781
+ source.startsWith('/') ||
782
+ source.startsWith('~/') ||
783
+ isWindowsPath(source);
776
784
  }
777
785
 
778
786
  function expandPath(p) {
@@ -782,8 +790,23 @@ function expandPath(p) {
782
790
  return path.resolve(p);
783
791
  }
784
792
 
793
+ // Validate GitHub owner/repo names (alphanumeric, hyphens, underscores, dots)
794
+ function validateGitHubName(name, type = 'name') {
795
+ if (!name || typeof name !== 'string') {
796
+ throw new Error(`Invalid GitHub ${type}`);
797
+ }
798
+ // GitHub allows: alphanumeric, hyphens, underscores, dots (no leading/trailing dots for repos)
799
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
800
+ throw new Error(`Invalid GitHub ${type}: "${name}" contains invalid characters`);
801
+ }
802
+ if (name.length > 100) {
803
+ throw new Error(`GitHub ${type} too long: ${name.length} > 100 characters`);
804
+ }
805
+ return true;
806
+ }
807
+
785
808
  async function installFromGitHub(source, agent = 'claude', dryRun = false) {
786
- const { execSync } = require('child_process');
809
+ const { execFileSync } = require('child_process');
787
810
 
788
811
  // Parse owner/repo format
789
812
  const parts = source.split('/');
@@ -796,6 +819,18 @@ async function installFromGitHub(source, agent = 'claude', dryRun = false) {
796
819
  const repo = parts[1];
797
820
  const skillName = parts[2]; // Optional specific skill
798
821
 
822
+ // Validate owner and repo to prevent injection attacks
823
+ try {
824
+ validateGitHubName(owner, 'owner');
825
+ validateGitHubName(repo, 'repository');
826
+ if (skillName) {
827
+ validateSkillName(skillName);
828
+ }
829
+ } catch (e) {
830
+ error(e.message);
831
+ return false;
832
+ }
833
+
799
834
  const repoUrl = `https://github.com/${owner}/${repo}.git`;
800
835
  const tempDir = path.join(os.tmpdir(), `ai-skills-${Date.now()}`);
801
836
 
@@ -809,13 +844,17 @@ async function installFromGitHub(source, agent = 'claude', dryRun = false) {
809
844
 
810
845
  try {
811
846
  info(`Cloning ${owner}/${repo}...`);
812
- execSync(`git clone --depth 1 ${repoUrl} ${tempDir}`, { stdio: 'pipe' });
847
+ // Use execFileSync with args array to prevent shell injection
848
+ execFileSync('git', ['clone', '--depth', '1', repoUrl, tempDir], { stdio: 'pipe' });
813
849
 
814
850
  // Find skills in the cloned repo
815
851
  const skillsDir = fs.existsSync(path.join(tempDir, 'skills'))
816
852
  ? path.join(tempDir, 'skills')
817
853
  : tempDir;
818
854
 
855
+ // Check if repo root IS a skill (has SKILL.md at root)
856
+ const isRootSkill = fs.existsSync(path.join(tempDir, 'SKILL.md'));
857
+
819
858
  if (skillName) {
820
859
  // Install specific skill
821
860
  const skillPath = path.join(skillsDir, skillName);
@@ -835,6 +874,29 @@ async function installFromGitHub(source, agent = 'claude', dryRun = false) {
835
874
  copyDir(skillPath, destPath);
836
875
  success(`\nInstalled: ${skillName} from ${owner}/${repo}`);
837
876
  info(`Location: ${destPath}`);
877
+ } else if (isRootSkill) {
878
+ // Repo itself is a single skill
879
+ // Sanitize repo name to valid skill name (lowercase, alphanumeric + hyphens)
880
+ const skillName = repo.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
881
+
882
+ try {
883
+ validateSkillName(skillName);
884
+ } catch (e) {
885
+ error(`Cannot install: repo name "${repo}" cannot be converted to valid skill name`);
886
+ fs.rmSync(tempDir, { recursive: true });
887
+ return false;
888
+ }
889
+
890
+ const destDir = AGENT_PATHS[agent] || AGENT_PATHS.claude;
891
+ const destPath = path.join(destDir, skillName);
892
+
893
+ if (!fs.existsSync(destDir)) {
894
+ fs.mkdirSync(destDir, { recursive: true });
895
+ }
896
+
897
+ copyDir(tempDir, destPath);
898
+ success(`\nInstalled: ${skillName} from ${owner}/${repo}`);
899
+ info(`Location: ${destPath}`);
838
900
  } else {
839
901
  // Install all skills from repo
840
902
  const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
@@ -970,6 +1032,7 @@ ${colors.bold}Commands:${colors.reset}
970
1032
  ${colors.green}search <query>${colors.reset} Search skills by name, description, or tags
971
1033
  ${colors.green}info <name>${colors.reset} Show skill details
972
1034
  ${colors.green}config${colors.reset} Show/edit configuration
1035
+ ${colors.green}version${colors.reset} Show version number
973
1036
  ${colors.green}help${colors.reset} Show this help
974
1037
 
975
1038
  ${colors.bold}Options:${colors.reset}
@@ -978,6 +1041,7 @@ ${colors.bold}Options:${colors.reset}
978
1041
  ${colors.cyan}--dry-run, -n${colors.reset} Preview changes without applying
979
1042
  ${colors.cyan}--category <c>${colors.reset} Filter by category
980
1043
  ${colors.cyan}--all${colors.reset} Apply to all (with update)
1044
+ ${colors.cyan}--version, -v${colors.reset} Show version number
981
1045
 
982
1046
  ${colors.bold}Agents:${colors.reset}
983
1047
  ${colors.cyan}claude${colors.reset} (default) ~/.claude/skills/
@@ -1202,6 +1266,13 @@ switch (command || 'help') {
1202
1266
  showHelp();
1203
1267
  break;
1204
1268
 
1269
+ case 'version':
1270
+ case '--version':
1271
+ case '-v':
1272
+ const pkg = require('./package.json');
1273
+ log(`ai-agent-skills v${pkg.version}`);
1274
+ break;
1275
+
1205
1276
  default:
1206
1277
  // If command looks like a skill name, try to install it
1207
1278
  if (getAvailableSkills().includes(command)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-agent-skills",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Install curated AI agent skills with one command. Works with Claude Code, Cursor, Amp, VS Code, and all Agent Skills compatible tools.",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  "skills": "./cli.js"
9
9
  },
10
10
  "engines": {
11
- "node": ">=14.0.0"
11
+ "node": ">=14.14.0"
12
12
  },
13
13
  "scripts": {
14
14
  "test": "node test.js",
package/skills.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.2.0",
3
- "updated": "2025-12-20T00:00:00Z",
2
+ "version": "1.2.2",
3
+ "updated": "2025-12-25T00:00:00Z",
4
4
  "total": 39,
5
5
  "skills": [
6
6
  {
@@ -516,7 +516,7 @@
516
516
  "id": "development",
517
517
  "name": "Development",
518
518
  "description": "Coding and software development skills",
519
- "count": 12
519
+ "count": 13
520
520
  },
521
521
  {
522
522
  "id": "document",
@@ -528,19 +528,19 @@
528
528
  "id": "creative",
529
529
  "name": "Creative",
530
530
  "description": "Design and creative skills",
531
- "count": 2
531
+ "count": 6
532
532
  },
533
533
  {
534
534
  "id": "business",
535
535
  "name": "Business",
536
536
  "description": "Business and communication skills",
537
- "count": 2
537
+ "count": 5
538
538
  },
539
539
  {
540
540
  "id": "productivity",
541
541
  "name": "Productivity",
542
542
  "description": "Productivity and workflow skills",
543
- "count": 3
543
+ "count": 11
544
544
  }
545
545
  ]
546
546
  }
@@ -1,2 +0,0 @@
1
- github: skillcreatorai
2
- custom: ["https://skillcreator.ai"]
@@ -1,41 +0,0 @@
1
- name: Bug Report
2
- description: Report an issue with a skill
3
- title: "[Bug] "
4
- labels: ["bug"]
5
- body:
6
- - type: input
7
- id: skill
8
- attributes:
9
- label: Skill Name
10
- description: Which skill has the issue?
11
- placeholder: e.g., frontend-design
12
- validations:
13
- required: true
14
-
15
- - type: textarea
16
- id: description
17
- attributes:
18
- label: What happened?
19
- description: Describe the bug
20
- validations:
21
- required: true
22
-
23
- - type: textarea
24
- id: expected
25
- attributes:
26
- label: Expected behavior
27
- description: What should have happened?
28
-
29
- - type: input
30
- id: agent
31
- attributes:
32
- label: Agent
33
- description: Which AI agent are you using?
34
- placeholder: e.g., Claude Code, Cursor, Amp
35
-
36
- - type: textarea
37
- id: logs
38
- attributes:
39
- label: Relevant logs
40
- description: Any error messages or output
41
- render: shell
@@ -1,11 +0,0 @@
1
- blank_issues_enabled: false
2
- contact_links:
3
- - name: Browse Skills
4
- url: https://skillcreator.ai/discover
5
- about: Find and install skills from our gallery
6
- - name: Create a Skill
7
- url: https://skillcreator.ai/build
8
- about: Generate a custom skill in 30 seconds
9
- - name: Follow Updates
10
- url: https://x.com/skillcreatorai
11
- about: Get notified about new skills and features
@@ -1,54 +0,0 @@
1
- name: Skill Request
2
- description: Request a new skill to be added
3
- title: "[Skill Request] "
4
- labels: ["skill-request"]
5
- body:
6
- - type: markdown
7
- attributes:
8
- value: |
9
- Thanks for suggesting a skill! We review all requests.
10
-
11
- - type: input
12
- id: skill-name
13
- attributes:
14
- label: Skill Name
15
- description: What should this skill be called?
16
- placeholder: e.g., slack-automation
17
- validations:
18
- required: true
19
-
20
- - type: textarea
21
- id: description
22
- attributes:
23
- label: What should this skill do?
24
- description: Describe the use case and functionality
25
- placeholder: |
26
- I want a skill that can...
27
-
28
- Use cases:
29
- - ...
30
- - ...
31
- validations:
32
- required: true
33
-
34
- - type: dropdown
35
- id: category
36
- attributes:
37
- label: Category
38
- options:
39
- - Development
40
- - Documents
41
- - Creative
42
- - Productivity
43
- - Other
44
- validations:
45
- required: true
46
-
47
- - type: textarea
48
- id: examples
49
- attributes:
50
- label: Example prompts
51
- description: How would you use this skill?
52
- placeholder: |
53
- "Create a Slack message to #engineering about the deployment"
54
- "Summarize this channel's messages from today"
@@ -1,23 +0,0 @@
1
- ## Summary
2
-
3
- Brief description of the changes.
4
-
5
- ## Type
6
-
7
- - [ ] New skill
8
- - [ ] Skill update/fix
9
- - [ ] Documentation
10
- - [ ] Other
11
-
12
- ## Checklist
13
-
14
- - [ ] SKILL.md has valid YAML frontmatter with `name` and `description`
15
- - [ ] Skill name is lowercase with hyphens only
16
- - [ ] Added entry to `skills.json`
17
- - [ ] Tested the skill works as expected
18
-
19
- ## Skill Details (if adding new skill)
20
-
21
- **Name:**
22
- **Category:**
23
- **Description:**
@@ -1,188 +0,0 @@
1
- name: Validate Skills
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
- branches: [main]
8
-
9
- jobs:
10
- validate:
11
- runs-on: ubuntu-latest
12
- steps:
13
- - uses: actions/checkout@v4
14
-
15
- - name: Setup Node.js
16
- uses: actions/setup-node@v4
17
- with:
18
- node-version: '18'
19
-
20
- - name: Validate skills.json schema
21
- run: |
22
- echo "Validating skills.json..."
23
- node -e "
24
- const fs = require('fs');
25
- const data = JSON.parse(fs.readFileSync('skills.json', 'utf8'));
26
-
27
- // Check structure
28
- if (!Array.isArray(data.skills)) {
29
- console.error('ERROR: skills must be an array');
30
- process.exit(1);
31
- }
32
-
33
- const required = ['name', 'description', 'category', 'author', 'license'];
34
- const validCategories = ['development', 'document', 'creative', 'business', 'productivity'];
35
- const names = new Set();
36
- let errors = 0;
37
-
38
- data.skills.forEach((skill, i) => {
39
- // Check required fields
40
- required.forEach(field => {
41
- if (!skill[field]) {
42
- console.error('ERROR: Skill ' + skill.name + ' missing ' + field);
43
- errors++;
44
- }
45
- });
46
-
47
- // Check category
48
- if (skill.category && !validCategories.includes(skill.category)) {
49
- console.error('ERROR: Invalid category ' + skill.category + ' for ' + skill.name);
50
- errors++;
51
- }
52
-
53
- // Check for duplicates
54
- if (names.has(skill.name)) {
55
- console.error('ERROR: Duplicate skill name: ' + skill.name);
56
- errors++;
57
- }
58
- names.add(skill.name);
59
-
60
- // Check name format
61
- if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(skill.name)) {
62
- console.error('ERROR: Invalid skill name format: ' + skill.name);
63
- errors++;
64
- }
65
-
66
- // Check description length
67
- if (skill.description && skill.description.length < 10) {
68
- console.error('WARNING: Short description for ' + skill.name);
69
- }
70
- });
71
-
72
- if (errors > 0) {
73
- console.error('Found ' + errors + ' errors');
74
- process.exit(1);
75
- }
76
-
77
- console.log('✓ skills.json is valid (' + data.skills.length + ' skills)');
78
- "
79
-
80
- - name: Check skill folders match JSON
81
- run: |
82
- echo "Checking skill folders..."
83
- node -e "
84
- const fs = require('fs');
85
- const path = require('path');
86
-
87
- const data = JSON.parse(fs.readFileSync('skills.json', 'utf8'));
88
- const jsonSkills = new Set(data.skills.map(s => s.name));
89
-
90
- const skillsDir = 'skills';
91
- const folders = fs.readdirSync(skillsDir).filter(f =>
92
- fs.statSync(path.join(skillsDir, f)).isDirectory()
93
- );
94
-
95
- let errors = 0;
96
-
97
- // Check each folder has an entry in JSON
98
- folders.forEach(folder => {
99
- if (!jsonSkills.has(folder)) {
100
- console.error('ERROR: Folder ' + folder + ' not in skills.json');
101
- errors++;
102
- }
103
-
104
- // Check SKILL.md exists
105
- const skillMd = path.join(skillsDir, folder, 'SKILL.md');
106
- if (!fs.existsSync(skillMd)) {
107
- console.error('ERROR: Missing SKILL.md in ' + folder);
108
- errors++;
109
- }
110
- });
111
-
112
- // Check each JSON entry has a folder
113
- jsonSkills.forEach(name => {
114
- if (!folders.includes(name)) {
115
- console.error('ERROR: skills.json has ' + name + ' but folder missing');
116
- errors++;
117
- }
118
- });
119
-
120
- if (errors > 0) {
121
- process.exit(1);
122
- }
123
-
124
- console.log('✓ All ' + folders.length + ' skill folders match skills.json');
125
- "
126
-
127
- - name: Validate SKILL.md frontmatter
128
- run: |
129
- echo "Validating frontmatter..."
130
- node -e "
131
- const fs = require('fs');
132
- const path = require('path');
133
-
134
- const skillsDir = 'skills';
135
- const folders = fs.readdirSync(skillsDir).filter(f =>
136
- fs.statSync(path.join(skillsDir, f)).isDirectory()
137
- );
138
-
139
- let errors = 0;
140
-
141
- folders.forEach(folder => {
142
- const skillMd = path.join(skillsDir, folder, 'SKILL.md');
143
- if (!fs.existsSync(skillMd)) return;
144
-
145
- const content = fs.readFileSync(skillMd, 'utf8');
146
-
147
- // Check for frontmatter
148
- if (!content.startsWith('---')) {
149
- console.error('ERROR: ' + folder + '/SKILL.md missing frontmatter');
150
- errors++;
151
- return;
152
- }
153
-
154
- // Extract frontmatter
155
- const endIndex = content.indexOf('---', 3);
156
- if (endIndex === -1) {
157
- console.error('ERROR: ' + folder + '/SKILL.md has unclosed frontmatter');
158
- errors++;
159
- return;
160
- }
161
-
162
- const frontmatter = content.slice(3, endIndex);
163
-
164
- // Check for name field
165
- if (!frontmatter.includes('name:')) {
166
- console.error('ERROR: ' + folder + '/SKILL.md missing name in frontmatter');
167
- errors++;
168
- }
169
-
170
- // Check for description field
171
- if (!frontmatter.includes('description:')) {
172
- console.error('ERROR: ' + folder + '/SKILL.md missing description in frontmatter');
173
- errors++;
174
- }
175
- });
176
-
177
- if (errors > 0) {
178
- process.exit(1);
179
- }
180
-
181
- console.log('✓ All SKILL.md files have valid frontmatter');
182
- "
183
-
184
- - name: Run tests
185
- run: node test.js
186
-
187
- - name: Validate CLI loads
188
- run: node cli.js list > /dev/null && echo "✓ CLI loads successfully"
package/CHANGELOG.md DELETED
@@ -1,120 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- ## [1.1.0] - 2024-12-20
6
-
7
- ### Added
8
- - **Dry-run mode**: Preview installations with `--dry-run` flag
9
- - **Config file support**: `~/.agent-skills.json` for default settings
10
- - **Update notifications**: See available updates when listing installed skills
11
- - **Update all**: `update --all` to update all installed skills at once
12
- - **Category filter**: `list --category development` to filter skills
13
- - **Tag support**: Search now includes tags
14
- - **"Did you mean" suggestions**: Typo-tolerant skill name matching
15
- - **Config command**: `config --default-agent cursor` to set defaults
16
- - **Security validation**: Block path traversal attacks in skill names
17
- - **Size limit**: 50MB max skill size to prevent abuse
18
- - **Proper error handling**: Graceful failures with helpful messages
19
- - **Test suite**: `npm test` runs validation tests
20
- - **Enhanced CI**: Schema validation, duplicate detection, frontmatter checks
21
-
22
- ### Changed
23
- - Bump to version 1.1.0 (semver: new features)
24
- - Node.js 14+ now required (was implicit, now explicit)
25
- - CLI shows skill size on install
26
- - Better help output with categories section
27
-
28
- ### Fixed
29
- - JSON parsing errors now show helpful messages instead of crashing
30
- - File operation errors properly caught and reported
31
- - Partial installs cleaned up on failure
32
-
33
- ### Security
34
- - Skill names validated against path traversal patterns (`../`, `\`)
35
- - Max file size enforced during copy operations
36
-
37
- ## [1.0.8] - 2024-12-20
38
-
39
- ### Added
40
- - `uninstall` command to remove installed skills
41
- - `update` command to update skills to latest version
42
- - `list --installed` flag to show installed skills per agent
43
- - Letta agent support (`--agent letta`)
44
- - Command aliases: `add`, `remove`, `rm`, `find`, `show`, `upgrade`
45
-
46
- ### Fixed
47
- - Description truncation now only adds "..." when actually truncated
48
-
49
- ## [1.0.7] - 2024-12-19
50
-
51
- ### Added
52
- - Credits & Attribution section in README
53
- - npm downloads badge
54
- - Full skill listing in README (all 38 skills now documented)
55
-
56
- ### Fixed
57
- - `--agent` flag parsing improvements
58
- - Codex agent support
59
-
60
- ## [1.0.6] - 2024-12-18
61
-
62
- ### Added
63
- - 15 new skills from ComposioHQ ecosystem:
64
- - `artifacts-builder` - Interactive React/Tailwind components
65
- - `changelog-generator` - Generate changelogs from git commits
66
- - `competitive-ads-extractor` - Analyze competitor ads
67
- - `content-research-writer` - Research and write with citations
68
- - `developer-growth-analysis` - Track developer metrics
69
- - `domain-name-brainstormer` - Generate domain name ideas
70
- - `file-organizer` - Organize files and find duplicates
71
- - `image-enhancer` - Improve image quality
72
- - `invoice-organizer` - Organize invoices for tax prep
73
- - `lead-research-assistant` - Identify and qualify leads
74
- - `meeting-insights-analyzer` - Analyze meeting transcripts
75
- - `raffle-winner-picker` - Select contest winners
76
- - `slack-gif-creator` - Create animated GIFs for Slack
77
- - `theme-factory` - Professional font and color themes
78
- - `video-downloader` - Download videos from platforms
79
- - Cross-link to Awesome Agent Skills repository
80
-
81
- ## [1.0.5] - 2024-12-17
82
-
83
- ### Fixed
84
- - VS Code install message now correctly shows `.github/skills/`
85
-
86
- ## [1.0.4] - 2024-12-17
87
-
88
- ### Fixed
89
- - VS Code path corrected to `.github/skills/` (was `.vscode/`)
90
-
91
- ## [1.0.3] - 2024-12-17
92
-
93
- ### Added
94
- - `job-application` skill for cover letters and applications
95
-
96
- ## [1.0.2] - 2024-12-17
97
-
98
- ### Added
99
- - Multi-agent support with `--agent` flag
100
- - Support for Claude Code, Cursor, Amp, VS Code, Goose, OpenCode
101
- - Portable install option with `--agent project`
102
-
103
- ## [1.0.1] - 2024-12-16
104
-
105
- ### Added
106
- - `qa-regression` skill for automated Playwright testing
107
- - `jira-issues` skill for Jira integration
108
- - GitHub issue templates and PR templates
109
- - CI validation workflow
110
- - Funding configuration
111
-
112
- ## [1.0.0] - 2024-12-16
113
-
114
- ### Added
115
- - Initial release with 20 curated skills
116
- - NPX installer (`npx ai-agent-skills install <name>`)
117
- - Skills from Anthropic's official examples
118
- - Core document skills: `pdf`, `xlsx`, `docx`, `pptx`
119
- - Development skills: `frontend-design`, `mcp-builder`, `skill-creator`
120
- - Creative skills: `canvas-design`, `algorithmic-art`
package/CONTRIBUTING.md DELETED
@@ -1,84 +0,0 @@
1
- # Contributing to Agent Skills
2
-
3
- We welcome contributions to the universal skill repository.
4
-
5
- ## Adding a Skill
6
-
7
- ### Requirements
8
-
9
- 1. Your skill must follow the [Agent Skills specification](https://agentskills.io/specification)
10
- 2. The `SKILL.md` file must include valid YAML frontmatter with `name` and `description`
11
- 3. The skill name must be lowercase with hyphens only (e.g., `my-skill`)
12
- 4. The skill must actually work and provide value
13
-
14
- ### Process
15
-
16
- 1. Fork this repository
17
- 2. Create your skill folder: `skills/<skill-name>/`
18
- 3. Add your `SKILL.md` file with proper frontmatter:
19
-
20
- ```yaml
21
- ---
22
- name: my-skill
23
- description: What this skill does and when to use it.
24
- license: MIT
25
- ---
26
-
27
- # My Skill
28
-
29
- Instructions for the agent...
30
- ```
31
-
32
- 4. Add an entry to `skills.json`:
33
-
34
- ```json
35
- {
36
- "name": "my-skill",
37
- "description": "What this skill does and when to use it.",
38
- "category": "development",
39
- "author": "your-username",
40
- "source": "your-username/your-repo",
41
- "license": "MIT",
42
- "path": "skills/my-skill",
43
- "stars": 0,
44
- "downloads": 0,
45
- "featured": false,
46
- "verified": false
47
- }
48
- ```
49
-
50
- 5. Submit a pull request
51
-
52
- ### Categories
53
-
54
- Use one of these categories:
55
-
56
- - `development` - Coding, debugging, testing, tooling
57
- - `document` - PDF, Word, Excel, presentations
58
- - `creative` - Art, design, visuals
59
- - `business` - Communication, research, productivity
60
- - `data` - Analytics, visualization, databases
61
- - `content` - Writing, research, media
62
- - `lifestyle` - Personal productivity
63
-
64
- ### Review Process
65
-
66
- We review all submissions for:
67
-
68
- - **Spec compliance**: Valid SKILL.md format
69
- - **Quality**: Clear instructions, actually useful
70
- - **Safety**: No malicious or harmful content
71
- - **Uniqueness**: Not duplicating existing skills
72
-
73
- ## Updating Existing Skills
74
-
75
- If you find a bug or want to improve an existing skill:
76
-
77
- 1. Fork the repo
78
- 2. Make your changes
79
- 3. Test that the skill still works
80
- 4. Submit a PR with a clear description of the change
81
-
82
- ## Questions?
83
-
84
- Open an issue or visit [skillcreator.ai](https://skillcreator.ai).
package/test.js DELETED
@@ -1,180 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Test suite for ai-agent-skills CLI
5
- * Run with: node test.js
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
- const { execSync } = require('child_process');
11
-
12
- const colors = {
13
- reset: '\x1b[0m',
14
- green: '\x1b[32m',
15
- red: '\x1b[31m',
16
- yellow: '\x1b[33m',
17
- dim: '\x1b[2m'
18
- };
19
-
20
- let passed = 0;
21
- let failed = 0;
22
-
23
- function test(name, fn) {
24
- try {
25
- fn();
26
- console.log(`${colors.green}✓${colors.reset} ${name}`);
27
- passed++;
28
- } catch (e) {
29
- console.log(`${colors.red}✗${colors.reset} ${name}`);
30
- console.log(` ${colors.dim}${e.message}${colors.reset}`);
31
- failed++;
32
- }
33
- }
34
-
35
- function assert(condition, message) {
36
- if (!condition) throw new Error(message || 'Assertion failed');
37
- }
38
-
39
- function assertEqual(a, b, message) {
40
- if (a !== b) throw new Error(message || `Expected ${b}, got ${a}`);
41
- }
42
-
43
- function assertContains(str, substr, message) {
44
- if (!str.includes(substr)) throw new Error(message || `Expected "${str}" to contain "${substr}"`);
45
- }
46
-
47
- function run(cmd) {
48
- try {
49
- return execSync(`node cli.js ${cmd}`, { encoding: 'utf8', cwd: __dirname });
50
- } catch (e) {
51
- return e.stdout || e.message;
52
- }
53
- }
54
-
55
- console.log('\n🧪 Running tests...\n');
56
-
57
- // ============ SKILLS.JSON TESTS ============
58
-
59
- test('skills.json exists and is valid JSON', () => {
60
- const content = fs.readFileSync(path.join(__dirname, 'skills.json'), 'utf8');
61
- const data = JSON.parse(content);
62
- assert(Array.isArray(data.skills), 'skills should be an array');
63
- });
64
-
65
- test('skills.json has skills with required fields', () => {
66
- const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'skills.json'), 'utf8'));
67
- const required = ['name', 'description', 'category', 'author', 'license'];
68
-
69
- data.skills.forEach(skill => {
70
- required.forEach(field => {
71
- assert(skill[field], `Skill ${skill.name} missing ${field}`);
72
- });
73
- });
74
- });
75
-
76
- test('skill names match folder names', () => {
77
- const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'skills.json'), 'utf8'));
78
- const skillsDir = path.join(__dirname, 'skills');
79
-
80
- data.skills.forEach(skill => {
81
- const skillPath = path.join(skillsDir, skill.name);
82
- assert(fs.existsSync(skillPath), `Folder missing for skill: ${skill.name}`);
83
- assert(fs.existsSync(path.join(skillPath, 'SKILL.md')), `SKILL.md missing for: ${skill.name}`);
84
- });
85
- });
86
-
87
- test('no duplicate skill names', () => {
88
- const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'skills.json'), 'utf8'));
89
- const names = data.skills.map(s => s.name);
90
- const unique = [...new Set(names)];
91
- assertEqual(names.length, unique.length, 'Duplicate skill names found');
92
- });
93
-
94
- test('all categories are valid', () => {
95
- const validCategories = ['development', 'document', 'creative', 'business', 'productivity'];
96
- const data = JSON.parse(fs.readFileSync(path.join(__dirname, 'skills.json'), 'utf8'));
97
-
98
- data.skills.forEach(skill => {
99
- assert(
100
- validCategories.includes(skill.category),
101
- `Invalid category "${skill.category}" for skill ${skill.name}`
102
- );
103
- });
104
- });
105
-
106
- // ============ CLI TESTS ============
107
-
108
- test('help command works', () => {
109
- const output = run('help');
110
- assertContains(output, 'AI Agent Skills');
111
- assertContains(output, 'install');
112
- assertContains(output, 'uninstall');
113
- });
114
-
115
- test('list command works', () => {
116
- const output = run('list');
117
- assertContains(output, 'Available Skills');
118
- assertContains(output, 'DEVELOPMENT');
119
- });
120
-
121
- test('search command works', () => {
122
- const output = run('search pdf');
123
- assertContains(output, 'pdf');
124
- });
125
-
126
- test('info command works', () => {
127
- const output = run('info pdf');
128
- assertContains(output, 'pdf');
129
- assertContains(output, 'Category:');
130
- });
131
-
132
- test('invalid skill name rejected', () => {
133
- const output = run('install ../etc/passwd');
134
- assertContains(output, 'Invalid skill name');
135
- });
136
-
137
- test('dry-run shows preview', () => {
138
- const output = run('install pdf --dry-run');
139
- assertContains(output, 'Dry Run');
140
- assertContains(output, 'Would install');
141
- });
142
-
143
- test('config command works', () => {
144
- const output = run('config');
145
- assertContains(output, 'Configuration');
146
- assertContains(output, 'defaultAgent');
147
- });
148
-
149
- test('unknown command shows error', () => {
150
- const output = run('notacommand');
151
- assertContains(output, 'Unknown command');
152
- });
153
-
154
- test('category filter works', () => {
155
- const output = run('list --category document');
156
- assertContains(output, 'DOCUMENT');
157
- });
158
-
159
- // ============ SECURITY TESTS ============
160
-
161
- test('path traversal blocked in skill names', () => {
162
- const output = run('install ../../etc');
163
- assertContains(output, 'Invalid skill name');
164
- });
165
-
166
- test('backslash path traversal blocked', () => {
167
- const output = run('install ..\\..\\etc');
168
- assertContains(output, 'Invalid skill name');
169
- });
170
-
171
- // ============ SUMMARY ============
172
-
173
- console.log('\n' + '─'.repeat(40));
174
- console.log(`${colors.green}Passed: ${passed}${colors.reset}`);
175
- if (failed > 0) {
176
- console.log(`${colors.red}Failed: ${failed}${colors.reset}`);
177
- }
178
- console.log('─'.repeat(40) + '\n');
179
-
180
- process.exit(failed > 0 ? 1 : 0);