openskill-ai 1.0.0
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/LICENSE +21 -0
- package/README.md +230 -0
- package/dist/chunk-63EFN7CX.js +450 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +286 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +391 -0
- package/package.json +70 -0
- package/skills/backend-best-practices/SKILL.md +116 -0
- package/skills/backend-best-practices/rules/api-consistent-responses.md +127 -0
- package/skills/backend-best-practices/rules/api-pagination.md +83 -0
- package/skills/backend-best-practices/rules/api-rate-limiting.md +94 -0
- package/skills/backend-best-practices/rules/api-restful-conventions.md +67 -0
- package/skills/backend-best-practices/rules/api-versioning.md +69 -0
- package/skills/backend-best-practices/rules/arch-dependency-injection.md +55 -0
- package/skills/backend-best-practices/rules/arch-dto-pattern.md +64 -0
- package/skills/backend-best-practices/rules/arch-repository-pattern.md +74 -0
- package/skills/backend-best-practices/rules/arch-separation-concerns.md +80 -0
- package/skills/backend-best-practices/rules/arch-service-layer.md +48 -0
- package/skills/backend-best-practices/rules/code-documentation.md +77 -0
- package/skills/backend-best-practices/rules/code-dry-principle.md +49 -0
- package/skills/backend-best-practices/rules/code-naming-conventions.md +47 -0
- package/skills/backend-best-practices/rules/code-single-responsibility.md +78 -0
- package/skills/backend-best-practices/rules/code-type-safety.md +64 -0
- package/skills/backend-best-practices/rules/db-connection-pooling.md +136 -0
- package/skills/backend-best-practices/rules/db-indexing.md +88 -0
- package/skills/backend-best-practices/rules/db-migrations.md +189 -0
- package/skills/backend-best-practices/rules/db-n-plus-one.md +118 -0
- package/skills/backend-best-practices/rules/db-transactions.md +178 -0
- package/skills/backend-best-practices/rules/deploy-environment-variables.md +63 -0
- package/skills/backend-best-practices/rules/deploy-graceful-shutdown.md +77 -0
- package/skills/backend-best-practices/rules/deploy-health-checks.md +70 -0
- package/skills/backend-best-practices/rules/deploy-monitoring.md +87 -0
- package/skills/backend-best-practices/rules/deploy-zero-downtime.md +85 -0
- package/skills/backend-best-practices/rules/error-global-handler.md +94 -0
- package/skills/backend-best-practices/rules/error-graceful-degradation.md +70 -0
- package/skills/backend-best-practices/rules/error-http-status-codes.md +77 -0
- package/skills/backend-best-practices/rules/error-logging.md +71 -0
- package/skills/backend-best-practices/rules/error-meaningful-messages.md +61 -0
- package/skills/backend-best-practices/rules/perf-async-operations.md +55 -0
- package/skills/backend-best-practices/rules/perf-caching.md +81 -0
- package/skills/backend-best-practices/rules/perf-compression.md +33 -0
- package/skills/backend-best-practices/rules/perf-database-queries.md +54 -0
- package/skills/backend-best-practices/rules/perf-lazy-loading.md +47 -0
- package/skills/backend-best-practices/rules/security-https-only.md +116 -0
- package/skills/backend-best-practices/rules/security-input-validation.md +96 -0
- package/skills/backend-best-practices/rules/security-jwt-best-practices.md +140 -0
- package/skills/backend-best-practices/rules/security-sql-injection.md +77 -0
- package/skills/clean-code-skills/references/solid.md +304 -0
- package/skills/clean-code-skills/skills.md +263 -0
- package/skills/flutter-skills/AGENTS.md +1265 -0
- package/skills/flutter-skills/SKILL.md +116 -0
- package/skills/flutter-skills/rules/advanced-custom-painter.md +117 -0
- package/skills/flutter-skills/rules/advanced-layer-link.md +103 -0
- package/skills/flutter-skills/rules/advanced-render-object.md +105 -0
- package/skills/flutter-skills/rules/advanced-sliver-persistent.md +111 -0
- package/skills/flutter-skills/rules/animation-animated-builder.md +118 -0
- package/skills/flutter-skills/rules/animation-cached-images.md +112 -0
- package/skills/flutter-skills/rules/animation-physics.md +105 -0
- package/skills/flutter-skills/rules/animation-reduce-overdraw.md +111 -0
- package/skills/flutter-skills/rules/animation-tween-sequence.md +112 -0
- package/skills/flutter-skills/rules/async-cancel-subscriptions.md +112 -0
- package/skills/flutter-skills/rules/async-compute.md +78 -0
- package/skills/flutter-skills/rules/async-debounce-throttle.md +104 -0
- package/skills/flutter-skills/rules/async-future-builder.md +106 -0
- package/skills/flutter-skills/rules/async-parallel.md +75 -0
- package/skills/flutter-skills/rules/build-avoid-rebuild.md +80 -0
- package/skills/flutter-skills/rules/build-const-constructors.md +56 -0
- package/skills/flutter-skills/rules/build-itemextent.md +73 -0
- package/skills/flutter-skills/rules/build-keys.md +74 -0
- package/skills/flutter-skills/rules/build-split-widgets.md +99 -0
- package/skills/flutter-skills/rules/dart-avoid-dynamic.md +86 -0
- package/skills/flutter-skills/rules/dart-cascade-notation.md +89 -0
- package/skills/flutter-skills/rules/dart-collection-if.md +92 -0
- package/skills/flutter-skills/rules/dart-final-const.md +70 -0
- package/skills/flutter-skills/rules/dart-spread-operator.md +90 -0
- package/skills/flutter-skills/rules/dart-string-buffer.md +77 -0
- package/skills/flutter-skills/rules/layout-avoid-opacity.md +110 -0
- package/skills/flutter-skills/rules/layout-clip-behavior.md +94 -0
- package/skills/flutter-skills/rules/layout-intrinsic-dimensions.md +89 -0
- package/skills/flutter-skills/rules/layout-repaint-boundary.md +117 -0
- package/skills/flutter-skills/rules/layout-slivers.md +94 -0
- package/skills/flutter-skills/rules/memory-dispose.md +90 -0
- package/skills/flutter-skills/rules/memory-image-cache.md +86 -0
- package/skills/flutter-skills/rules/memory-isolate.md +91 -0
- package/skills/flutter-skills/rules/memory-precache.md +114 -0
- package/skills/flutter-skills/rules/memory-weak-references.md +79 -0
- package/skills/flutter-skills/rules/state-late-final.md +90 -0
- package/skills/flutter-skills/rules/state-lift-state-up.md +84 -0
- package/skills/flutter-skills/rules/state-minimize-rebuilds.md +95 -0
- package/skills/flutter-skills/rules/state-selector.md +87 -0
- package/skills/flutter-skills/rules/state-valuenotifier.md +85 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/skill-writer-skills/AGENTS.md +637 -0
- package/skills/skill-writer-skills/README.md +49 -0
- package/skills/skill-writer-skills/SKILL.md +97 -0
- package/skills/skill-writer-skills/metadata.json +17 -0
- package/skills/skill-writer-skills/references/common-pitfalls.md +291 -0
- package/skills/skill-writer-skills/references/core-principles.md +147 -0
- package/skills/skill-writer-skills/references/creation-process.md +250 -0
- package/skills/skill-writer-skills/references/design-patterns.md +300 -0
- package/skills/skill-writer-skills/references/skill-anatomy.md +174 -0
- package/skills/skill-writer-skills/references/validation-checklist.md +194 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agent Skills Community
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# agent-skills
|
|
2
|
+
|
|
3
|
+
> Install AI agent skills from any Git repository
|
|
4
|
+
|
|
5
|
+
A powerful CLI tool to install and manage AI coding agent skills from GitHub, GitLab, or any Git repository. Supports 15+ popular AI agents with auto-detection and interactive prompts.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
✨ **Remote Repository Support** - Install skills from any Git repository
|
|
10
|
+
🤖 **15+ AI Agents** - Antigravity, Cursor, Claude Code, OpenCode, GitHub Copilot, and more
|
|
11
|
+
💬 **Interactive CLI** - Beautiful prompts with multi-select workflows
|
|
12
|
+
🔍 **Auto-Detection** - Automatically finds installed agents on your system
|
|
13
|
+
🌐 **Global/Project Installs** - Choose your installation scope
|
|
14
|
+
🔒 **Security** - Path sanitization and traversal protection
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install skills from a repository
|
|
20
|
+
npx agent-skills vercel-labs/agent-skills
|
|
21
|
+
|
|
22
|
+
# List available skills first
|
|
23
|
+
npx agent-skills vercel-labs/agent-skills --list
|
|
24
|
+
|
|
25
|
+
# Install specific skill to Antigravity
|
|
26
|
+
npx agent-skills vercel-labs/agent-skills \
|
|
27
|
+
--skill frontend-design \
|
|
28
|
+
--agent antigravity
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### As a CLI Tool (Recommended)
|
|
34
|
+
|
|
35
|
+
Use with `npx` without installing:
|
|
36
|
+
```bash
|
|
37
|
+
npx agent-skills <source>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or install globally:
|
|
41
|
+
```bash
|
|
42
|
+
npm install -g agent-skills
|
|
43
|
+
agent-skills <source>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### As a Library
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install agent-skills
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
import { installSkillForAgent, discoverSkills } from 'agent-skills';
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### Source Formats
|
|
59
|
+
|
|
60
|
+
The CLI accepts multiple source formats:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# GitHub shorthand
|
|
64
|
+
agent-skills vercel-labs/agent-skills
|
|
65
|
+
|
|
66
|
+
# GitHub URL
|
|
67
|
+
agent-skills https://github.com/owner/repo
|
|
68
|
+
|
|
69
|
+
# GitHub tree path (specific directory)
|
|
70
|
+
agent-skills https://github.com/owner/repo/tree/main/skills/frontend
|
|
71
|
+
|
|
72
|
+
# GitLab
|
|
73
|
+
agent-skills https://gitlab.com/owner/repo
|
|
74
|
+
|
|
75
|
+
# Direct Git URL
|
|
76
|
+
agent-skills git@github.com:owner/repo.git
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### CLI Options
|
|
80
|
+
|
|
81
|
+
| Option | Description |
|
|
82
|
+
|--------|-------------|
|
|
83
|
+
| `-g, --global` | Install globally (user-level) instead of project-level |
|
|
84
|
+
| `-a, --agent <agents...>` | Target specific agents |
|
|
85
|
+
| `-s, --skill <skills...>` | Select specific skills to install |
|
|
86
|
+
| `-l, --list` | List available skills without installing |
|
|
87
|
+
| `-y, --yes` | Skip all confirmation prompts |
|
|
88
|
+
| `-h, --help` | Show help |
|
|
89
|
+
|
|
90
|
+
### Examples
|
|
91
|
+
|
|
92
|
+
**Interactive Installation** (recommended for first-time users):
|
|
93
|
+
```bash
|
|
94
|
+
agent-skills vercel-labs/agent-skills
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**List Skills Before Installing:**
|
|
98
|
+
```bash
|
|
99
|
+
agent-skills vercel-labs/agent-skills --list
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Install Specific Skill:**
|
|
103
|
+
```bash
|
|
104
|
+
agent-skills vercel-labs/agent-skills --skill frontend-design
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Install to Multiple Agents:**
|
|
108
|
+
```bash
|
|
109
|
+
agent-skills vercel-labs/agent-skills \
|
|
110
|
+
--skill frontend-design \
|
|
111
|
+
--agent antigravity \
|
|
112
|
+
--agent cursor \
|
|
113
|
+
--agent claude-code
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Global Installation (Non-Interactive):**
|
|
117
|
+
```bash
|
|
118
|
+
agent-skills vercel-labs/agent-skills \
|
|
119
|
+
--skill frontend-design \
|
|
120
|
+
--global \
|
|
121
|
+
--yes
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**List All Supported Agents:**
|
|
125
|
+
```bash
|
|
126
|
+
agent-skills list-agents
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Supported AI Agents
|
|
130
|
+
|
|
131
|
+
The tool supports 15 AI coding agents with auto-detection:
|
|
132
|
+
|
|
133
|
+
- **Antigravity** - `.agent/skills` (project) or `~/.gemini/antigravity/skills` (global)
|
|
134
|
+
- **Cursor** - `.cursor/skills` or `~/.cursor/skills`
|
|
135
|
+
- **Claude Code** - `.claude/skills` or `~/.claude/skills`
|
|
136
|
+
- **OpenCode** - `.opencode/skill` or `~/.config/opencode/skill`
|
|
137
|
+
- **GitHub Copilot** - `.github/skills` or `~/.copilot/skills`
|
|
138
|
+
- **Codex** - `.codex/skills` or `~/.codex/skills`
|
|
139
|
+
- **Windsurf** - `.windsurf/skills` or `~/.codeium/windsurf/skills`
|
|
140
|
+
- **Roo Code** - `.roo/skills` or `~/.roo/skills`
|
|
141
|
+
- **Goose** - `.goose/skills` or `~/.config/goose/skills`
|
|
142
|
+
- **Gemini CLI** - `.gemini/skills` or `~/.gemini/skills`
|
|
143
|
+
- **Amp** - `.agents/skills` or `~/.config/agents/skills`
|
|
144
|
+
- **Kilo Code** - `.kilocode/skills` or `~/.kilocode/skills`
|
|
145
|
+
- **Clawdbot** - `skills/` or `~/.clawdbot/skills`
|
|
146
|
+
- **Droid** - `.factory/skills` or `~/.factory/skills`
|
|
147
|
+
|
|
148
|
+
## Creating Skills
|
|
149
|
+
|
|
150
|
+
Skills are defined using `SKILL.md` files with YAML frontmatter:
|
|
151
|
+
|
|
152
|
+
```markdown
|
|
153
|
+
---
|
|
154
|
+
name: my-skill
|
|
155
|
+
description: What this skill does and when to use it
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
# My Skill
|
|
159
|
+
|
|
160
|
+
Instructions for the AI agent to follow when this skill is activated.
|
|
161
|
+
|
|
162
|
+
## When to Use
|
|
163
|
+
|
|
164
|
+
Describe scenarios where this skill should be applied.
|
|
165
|
+
|
|
166
|
+
## Guidelines
|
|
167
|
+
|
|
168
|
+
1. First guideline
|
|
169
|
+
2. Second guideline
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Skill Discovery
|
|
173
|
+
|
|
174
|
+
The CLI searches for skills in these locations:
|
|
175
|
+
- Root directory (if it contains `SKILL.md`)
|
|
176
|
+
- `skills/`, `skills/.curated/`, `skills/.experimental/`
|
|
177
|
+
- `.agent/skills/`, `.claude/skills/`, `.cursor/skills/`
|
|
178
|
+
- All other agent-specific directories
|
|
179
|
+
- Recursive search as fallback (max depth 5)
|
|
180
|
+
|
|
181
|
+
## Security
|
|
182
|
+
|
|
183
|
+
The tool implements several security measures:
|
|
184
|
+
|
|
185
|
+
- **Path Sanitization** - Removes dangerous characters from skill names
|
|
186
|
+
- **Traversal Protection** - Validates all paths to prevent directory traversal
|
|
187
|
+
- **Safe Cloning** - Uses temporary directories for Git operations
|
|
188
|
+
- **File Exclusions** - Skips `README.md`, `metadata.json`, and files starting with `_`
|
|
189
|
+
|
|
190
|
+
## Programmatic API
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
import {
|
|
194
|
+
installSkillForAgent,
|
|
195
|
+
discoverSkills,
|
|
196
|
+
detectInstalledAgents,
|
|
197
|
+
parseSource,
|
|
198
|
+
cloneRepo
|
|
199
|
+
} from 'agent-skills';
|
|
200
|
+
|
|
201
|
+
// Clone and discover skills
|
|
202
|
+
const parsed = parseSource('vercel-labs/agent-skills');
|
|
203
|
+
const tempDir = await cloneRepo(parsed.url);
|
|
204
|
+
const skills = await discoverSkills(tempDir);
|
|
205
|
+
|
|
206
|
+
// Detect installed agents
|
|
207
|
+
const agents = await detectInstalledAgents();
|
|
208
|
+
|
|
209
|
+
// Install skill
|
|
210
|
+
await installSkillForAgent(skills[0], 'antigravity', { global: false });
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Requirements
|
|
214
|
+
|
|
215
|
+
- Node.js >= 18.0.0
|
|
216
|
+
- Git installed on your system
|
|
217
|
+
|
|
218
|
+
## Contributing
|
|
219
|
+
|
|
220
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT © Agent Skills Community
|
|
225
|
+
|
|
226
|
+
## Links
|
|
227
|
+
|
|
228
|
+
- [GitHub Repository](https://github.com/yourusername/agent-skills)
|
|
229
|
+
- [Report Issues](https://github.com/yourusername/agent-skills/issues)
|
|
230
|
+
- [npm Package](https://www.npmjs.com/package/agent-skills)
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
// src/utils/git.ts
|
|
2
|
+
import simpleGit from "simple-git";
|
|
3
|
+
import { join, normalize, resolve, sep } from "path";
|
|
4
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
function parseSource(input) {
|
|
7
|
+
const githubTreeMatch = input.match(
|
|
8
|
+
/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/
|
|
9
|
+
);
|
|
10
|
+
if (githubTreeMatch) {
|
|
11
|
+
const [, owner, repo, , subpath] = githubTreeMatch;
|
|
12
|
+
return {
|
|
13
|
+
type: "github",
|
|
14
|
+
url: `https://github.com/${owner}/${repo}.git`,
|
|
15
|
+
subpath
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const githubRepoMatch = input.match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
19
|
+
if (githubRepoMatch) {
|
|
20
|
+
const [, owner, repo] = githubRepoMatch;
|
|
21
|
+
const cleanRepo = repo.replace(/\.git$/, "");
|
|
22
|
+
return {
|
|
23
|
+
type: "github",
|
|
24
|
+
url: `https://github.com/${owner}/${cleanRepo}.git`
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const gitlabTreeMatch = input.match(
|
|
28
|
+
/gitlab\.com\/([^/]+)\/([^/]+)\/-\/tree\/([^/]+)\/(.+)/
|
|
29
|
+
);
|
|
30
|
+
if (gitlabTreeMatch) {
|
|
31
|
+
const [, owner, repo, , subpath] = gitlabTreeMatch;
|
|
32
|
+
return {
|
|
33
|
+
type: "gitlab",
|
|
34
|
+
url: `https://gitlab.com/${owner}/${repo}.git`,
|
|
35
|
+
subpath
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const gitlabRepoMatch = input.match(/gitlab\.com\/([^/]+)\/([^/]+)/);
|
|
39
|
+
if (gitlabRepoMatch) {
|
|
40
|
+
const [, owner, repo] = gitlabRepoMatch;
|
|
41
|
+
const cleanRepo = repo.replace(/\.git$/, "");
|
|
42
|
+
return {
|
|
43
|
+
type: "gitlab",
|
|
44
|
+
url: `https://gitlab.com/${owner}/${cleanRepo}.git`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const shorthandMatch = input.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
|
48
|
+
if (shorthandMatch && !input.includes(":")) {
|
|
49
|
+
const [, owner, repo, subpath] = shorthandMatch;
|
|
50
|
+
return {
|
|
51
|
+
type: "github",
|
|
52
|
+
url: `https://github.com/${owner}/${repo}.git`,
|
|
53
|
+
subpath
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
type: "git",
|
|
58
|
+
url: input
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function cloneRepo(url) {
|
|
62
|
+
const tempDir = await mkdtemp(join(tmpdir(), "agent-skills-"));
|
|
63
|
+
const git = simpleGit();
|
|
64
|
+
await git.clone(url, tempDir, ["--depth", "1"]);
|
|
65
|
+
return tempDir;
|
|
66
|
+
}
|
|
67
|
+
async function cleanupTempDir(dir) {
|
|
68
|
+
const normalizedDir = normalize(resolve(dir));
|
|
69
|
+
const normalizedTmpDir = normalize(resolve(tmpdir()));
|
|
70
|
+
if (!normalizedDir.startsWith(normalizedTmpDir + sep) && normalizedDir !== normalizedTmpDir) {
|
|
71
|
+
throw new Error("Attempted to clean up directory outside of temp directory");
|
|
72
|
+
}
|
|
73
|
+
await rm(dir, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/utils/skills.ts
|
|
77
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
78
|
+
import { join as join2, basename, dirname } from "path";
|
|
79
|
+
import matter from "gray-matter";
|
|
80
|
+
var SKIP_DIRS = ["node_modules", ".git", "dist", "build", "__pycache__"];
|
|
81
|
+
async function hasSkillMd(dir) {
|
|
82
|
+
try {
|
|
83
|
+
const skillPath = join2(dir, "SKILL.md");
|
|
84
|
+
const stats = await stat(skillPath);
|
|
85
|
+
return stats.isFile();
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function parseSkillMd(skillMdPath) {
|
|
91
|
+
try {
|
|
92
|
+
const content = await readFile(skillMdPath, "utf-8");
|
|
93
|
+
const { data } = matter(content);
|
|
94
|
+
if (!data.name || !data.description) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
name: data.name,
|
|
99
|
+
description: data.description,
|
|
100
|
+
path: dirname(skillMdPath),
|
|
101
|
+
metadata: data.metadata
|
|
102
|
+
};
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
|
|
108
|
+
const skillDirs = [];
|
|
109
|
+
if (depth > maxDepth) return skillDirs;
|
|
110
|
+
try {
|
|
111
|
+
if (await hasSkillMd(dir)) {
|
|
112
|
+
skillDirs.push(dir);
|
|
113
|
+
}
|
|
114
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
if (entry.isDirectory() && !SKIP_DIRS.includes(entry.name)) {
|
|
117
|
+
const subDirs = await findSkillDirs(join2(dir, entry.name), depth + 1, maxDepth);
|
|
118
|
+
skillDirs.push(...subDirs);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
return skillDirs;
|
|
124
|
+
}
|
|
125
|
+
async function discoverSkills(basePath, subpath) {
|
|
126
|
+
const skills = [];
|
|
127
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
128
|
+
const searchPath = subpath ? join2(basePath, subpath) : basePath;
|
|
129
|
+
if (await hasSkillMd(searchPath)) {
|
|
130
|
+
const skill = await parseSkillMd(join2(searchPath, "SKILL.md"));
|
|
131
|
+
if (skill) {
|
|
132
|
+
skills.push(skill);
|
|
133
|
+
return skills;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const prioritySearchDirs = [
|
|
137
|
+
searchPath,
|
|
138
|
+
join2(searchPath, "skills"),
|
|
139
|
+
join2(searchPath, "skills/.curated"),
|
|
140
|
+
join2(searchPath, "skills/.experimental"),
|
|
141
|
+
join2(searchPath, "skills/.system"),
|
|
142
|
+
join2(searchPath, ".codex/skills"),
|
|
143
|
+
join2(searchPath, ".claude/skills"),
|
|
144
|
+
join2(searchPath, ".opencode/skill"),
|
|
145
|
+
join2(searchPath, ".cursor/skills"),
|
|
146
|
+
join2(searchPath, ".agents/skills"),
|
|
147
|
+
join2(searchPath, ".kilocode/skills"),
|
|
148
|
+
join2(searchPath, ".roo/skills"),
|
|
149
|
+
join2(searchPath, ".goose/skills"),
|
|
150
|
+
join2(searchPath, ".agent/skills"),
|
|
151
|
+
join2(searchPath, ".github/skills")
|
|
152
|
+
];
|
|
153
|
+
for (const dir of prioritySearchDirs) {
|
|
154
|
+
try {
|
|
155
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (entry.isDirectory()) {
|
|
158
|
+
const skillDir = join2(dir, entry.name);
|
|
159
|
+
if (await hasSkillMd(skillDir)) {
|
|
160
|
+
const skill = await parseSkillMd(join2(skillDir, "SKILL.md"));
|
|
161
|
+
if (skill && !seenNames.has(skill.name)) {
|
|
162
|
+
skills.push(skill);
|
|
163
|
+
seenNames.add(skill.name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (skills.length === 0) {
|
|
172
|
+
const allSkillDirs = await findSkillDirs(searchPath);
|
|
173
|
+
for (const skillDir of allSkillDirs) {
|
|
174
|
+
const skill = await parseSkillMd(join2(skillDir, "SKILL.md"));
|
|
175
|
+
if (skill && !seenNames.has(skill.name)) {
|
|
176
|
+
skills.push(skill);
|
|
177
|
+
seenNames.add(skill.name);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return skills;
|
|
182
|
+
}
|
|
183
|
+
function getSkillDisplayName(skill) {
|
|
184
|
+
return skill.name || basename(skill.path);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/utils/agents.ts
|
|
188
|
+
import { homedir } from "os";
|
|
189
|
+
import { join as join3 } from "path";
|
|
190
|
+
import { existsSync } from "fs";
|
|
191
|
+
var home = homedir();
|
|
192
|
+
var agents = {
|
|
193
|
+
opencode: {
|
|
194
|
+
name: "opencode",
|
|
195
|
+
displayName: "OpenCode",
|
|
196
|
+
skillsDir: ".opencode/skill",
|
|
197
|
+
globalSkillsDir: join3(home, ".config/opencode/skill"),
|
|
198
|
+
detectInstalled: async () => {
|
|
199
|
+
return existsSync(join3(home, ".config/opencode")) || existsSync(join3(home, ".claude/skills"));
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"claude-code": {
|
|
203
|
+
name: "claude-code",
|
|
204
|
+
displayName: "Claude Code",
|
|
205
|
+
skillsDir: ".claude/skills",
|
|
206
|
+
globalSkillsDir: join3(home, ".claude/skills"),
|
|
207
|
+
detectInstalled: async () => {
|
|
208
|
+
return existsSync(join3(home, ".claude"));
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
codex: {
|
|
212
|
+
name: "codex",
|
|
213
|
+
displayName: "Codex",
|
|
214
|
+
skillsDir: ".codex/skills",
|
|
215
|
+
globalSkillsDir: join3(home, ".codex/skills"),
|
|
216
|
+
detectInstalled: async () => {
|
|
217
|
+
return existsSync(join3(home, ".codex"));
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
cursor: {
|
|
221
|
+
name: "cursor",
|
|
222
|
+
displayName: "Cursor",
|
|
223
|
+
skillsDir: ".cursor/skills",
|
|
224
|
+
globalSkillsDir: join3(home, ".cursor/skills"),
|
|
225
|
+
detectInstalled: async () => {
|
|
226
|
+
return existsSync(join3(home, ".cursor"));
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
amp: {
|
|
230
|
+
name: "amp",
|
|
231
|
+
displayName: "Amp",
|
|
232
|
+
skillsDir: ".agents/skills",
|
|
233
|
+
globalSkillsDir: join3(home, ".config/agents/skills"),
|
|
234
|
+
detectInstalled: async () => {
|
|
235
|
+
return existsSync(join3(home, ".config/amp"));
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
kilo: {
|
|
239
|
+
name: "kilo",
|
|
240
|
+
displayName: "Kilo Code",
|
|
241
|
+
skillsDir: ".kilocode/skills",
|
|
242
|
+
globalSkillsDir: join3(home, ".kilocode/skills"),
|
|
243
|
+
detectInstalled: async () => {
|
|
244
|
+
return existsSync(join3(home, ".kilocode"));
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
roo: {
|
|
248
|
+
name: "roo",
|
|
249
|
+
displayName: "Roo Code",
|
|
250
|
+
skillsDir: ".roo/skills",
|
|
251
|
+
globalSkillsDir: join3(home, ".roo/skills"),
|
|
252
|
+
detectInstalled: async () => {
|
|
253
|
+
return existsSync(join3(home, ".roo"));
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
goose: {
|
|
257
|
+
name: "goose",
|
|
258
|
+
displayName: "Goose",
|
|
259
|
+
skillsDir: ".goose/skills",
|
|
260
|
+
globalSkillsDir: join3(home, ".config/goose/skills"),
|
|
261
|
+
detectInstalled: async () => {
|
|
262
|
+
return existsSync(join3(home, ".config/goose"));
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
"gemini-cli": {
|
|
266
|
+
name: "gemini-cli",
|
|
267
|
+
displayName: "Gemini CLI",
|
|
268
|
+
skillsDir: ".gemini/skills",
|
|
269
|
+
globalSkillsDir: join3(home, ".gemini/skills"),
|
|
270
|
+
detectInstalled: async () => {
|
|
271
|
+
return existsSync(join3(home, ".gemini"));
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
antigravity: {
|
|
275
|
+
name: "antigravity",
|
|
276
|
+
displayName: "Antigravity",
|
|
277
|
+
skillsDir: ".agent/skills",
|
|
278
|
+
globalSkillsDir: join3(home, ".gemini/antigravity/skills"),
|
|
279
|
+
detectInstalled: async () => {
|
|
280
|
+
return existsSync(join3(process.cwd(), ".agent")) || existsSync(join3(home, ".gemini/antigravity"));
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"github-copilot": {
|
|
284
|
+
name: "github-copilot",
|
|
285
|
+
displayName: "GitHub Copilot",
|
|
286
|
+
skillsDir: ".github/skills",
|
|
287
|
+
globalSkillsDir: join3(home, ".copilot/skills"),
|
|
288
|
+
detectInstalled: async () => {
|
|
289
|
+
return existsSync(join3(process.cwd(), ".github")) || existsSync(join3(home, ".copilot"));
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
clawdbot: {
|
|
293
|
+
name: "clawdbot",
|
|
294
|
+
displayName: "Clawdbot",
|
|
295
|
+
skillsDir: "skills",
|
|
296
|
+
globalSkillsDir: join3(home, ".clawdbot/skills"),
|
|
297
|
+
detectInstalled: async () => {
|
|
298
|
+
return existsSync(join3(home, ".clawdbot"));
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
droid: {
|
|
302
|
+
name: "droid",
|
|
303
|
+
displayName: "Droid",
|
|
304
|
+
skillsDir: ".factory/skills",
|
|
305
|
+
globalSkillsDir: join3(home, ".factory/skills"),
|
|
306
|
+
detectInstalled: async () => {
|
|
307
|
+
return existsSync(join3(home, ".factory/skills"));
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
gemini: {
|
|
311
|
+
name: "gemini",
|
|
312
|
+
displayName: "Gemini CLI",
|
|
313
|
+
skillsDir: ".gemini/skills",
|
|
314
|
+
globalSkillsDir: join3(home, ".gemini/skills"),
|
|
315
|
+
detectInstalled: async () => {
|
|
316
|
+
return existsSync(join3(home, ".gemini"));
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
windsurf: {
|
|
320
|
+
name: "windsurf",
|
|
321
|
+
displayName: "Windsurf",
|
|
322
|
+
skillsDir: ".windsurf/skills",
|
|
323
|
+
globalSkillsDir: join3(home, ".codeium/windsurf/skills"),
|
|
324
|
+
detectInstalled: async () => {
|
|
325
|
+
return existsSync(join3(home, ".codeium/windsurf"));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
async function detectInstalledAgents() {
|
|
330
|
+
const installed = [];
|
|
331
|
+
for (const [type, config] of Object.entries(agents)) {
|
|
332
|
+
if (await config.detectInstalled()) {
|
|
333
|
+
installed.push(type);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return installed;
|
|
337
|
+
}
|
|
338
|
+
function getAgentConfig(type) {
|
|
339
|
+
return agents[type];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/utils/installer.ts
|
|
343
|
+
import { mkdir, cp, access, readdir as readdir2 } from "fs/promises";
|
|
344
|
+
import { join as join4, basename as basename2, normalize as normalize2, resolve as resolve2, sep as sep2 } from "path";
|
|
345
|
+
function sanitizeName(name) {
|
|
346
|
+
let sanitized = name.replace(/[\/\\:\0]/g, "");
|
|
347
|
+
sanitized = sanitized.replace(/^[.\s]+|[.\s]+$/g, "");
|
|
348
|
+
sanitized = sanitized.replace(/^\.+/, "");
|
|
349
|
+
if (!sanitized || sanitized.length === 0) {
|
|
350
|
+
sanitized = "unnamed-skill";
|
|
351
|
+
}
|
|
352
|
+
if (sanitized.length > 255) {
|
|
353
|
+
sanitized = sanitized.substring(0, 255);
|
|
354
|
+
}
|
|
355
|
+
return sanitized;
|
|
356
|
+
}
|
|
357
|
+
function isPathSafe(basePath, targetPath) {
|
|
358
|
+
const normalizedBase = normalize2(resolve2(basePath));
|
|
359
|
+
const normalizedTarget = normalize2(resolve2(targetPath));
|
|
360
|
+
return normalizedTarget.startsWith(normalizedBase + sep2) || normalizedTarget === normalizedBase;
|
|
361
|
+
}
|
|
362
|
+
async function installSkillForAgent(skill, agentType, options = {}) {
|
|
363
|
+
const agent = agents[agentType];
|
|
364
|
+
const rawSkillName = skill.name || basename2(skill.path);
|
|
365
|
+
const skillName = sanitizeName(rawSkillName);
|
|
366
|
+
const targetBase = options.global ? agent.globalSkillsDir : join4(options.cwd || process.cwd(), agent.skillsDir);
|
|
367
|
+
const targetDir = join4(targetBase, skillName);
|
|
368
|
+
if (!isPathSafe(targetBase, targetDir)) {
|
|
369
|
+
return {
|
|
370
|
+
success: false,
|
|
371
|
+
path: targetDir,
|
|
372
|
+
error: "Invalid skill name: potential path traversal detected"
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
await mkdir(targetDir, { recursive: true });
|
|
377
|
+
await copyDirectory(skill.path, targetDir);
|
|
378
|
+
return { success: true, path: targetDir };
|
|
379
|
+
} catch (error) {
|
|
380
|
+
return {
|
|
381
|
+
success: false,
|
|
382
|
+
path: targetDir,
|
|
383
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
var EXCLUDE_FILES = /* @__PURE__ */ new Set([
|
|
388
|
+
"README.md",
|
|
389
|
+
"metadata.json"
|
|
390
|
+
]);
|
|
391
|
+
var isExcluded = (name) => {
|
|
392
|
+
if (EXCLUDE_FILES.has(name)) return true;
|
|
393
|
+
if (name.startsWith("_")) return true;
|
|
394
|
+
return false;
|
|
395
|
+
};
|
|
396
|
+
async function copyDirectory(src, dest) {
|
|
397
|
+
await mkdir(dest, { recursive: true });
|
|
398
|
+
const entries = await readdir2(src, { withFileTypes: true });
|
|
399
|
+
for (const entry of entries) {
|
|
400
|
+
if (isExcluded(entry.name)) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const srcPath = join4(src, entry.name);
|
|
404
|
+
const destPath = join4(dest, entry.name);
|
|
405
|
+
if (entry.isDirectory()) {
|
|
406
|
+
await copyDirectory(srcPath, destPath);
|
|
407
|
+
} else {
|
|
408
|
+
await cp(srcPath, destPath);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
413
|
+
const agent = agents[agentType];
|
|
414
|
+
const sanitized = sanitizeName(skillName);
|
|
415
|
+
const targetBase = options.global ? agent.globalSkillsDir : join4(options.cwd || process.cwd(), agent.skillsDir);
|
|
416
|
+
const skillDir = join4(targetBase, sanitized);
|
|
417
|
+
if (!isPathSafe(targetBase, skillDir)) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
await access(skillDir);
|
|
422
|
+
return true;
|
|
423
|
+
} catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function getInstallPath(skillName, agentType, options = {}) {
|
|
428
|
+
const agent = agents[agentType];
|
|
429
|
+
const sanitized = sanitizeName(skillName);
|
|
430
|
+
const targetBase = options.global ? agent.globalSkillsDir : join4(options.cwd || process.cwd(), agent.skillsDir);
|
|
431
|
+
const installPath = join4(targetBase, sanitized);
|
|
432
|
+
if (!isPathSafe(targetBase, installPath)) {
|
|
433
|
+
throw new Error("Invalid skill name: potential path traversal detected");
|
|
434
|
+
}
|
|
435
|
+
return installPath;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export {
|
|
439
|
+
parseSource,
|
|
440
|
+
cloneRepo,
|
|
441
|
+
cleanupTempDir,
|
|
442
|
+
discoverSkills,
|
|
443
|
+
getSkillDisplayName,
|
|
444
|
+
agents,
|
|
445
|
+
detectInstalledAgents,
|
|
446
|
+
getAgentConfig,
|
|
447
|
+
installSkillForAgent,
|
|
448
|
+
isSkillInstalled,
|
|
449
|
+
getInstallPath
|
|
450
|
+
};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|