add-skill-kit 1.1.0 → 1.3.1
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 +146 -73
- package/bin/cli.js +68 -79
- package/bin/lib/commands/install.js +71 -18
- package/bin/lib/config.js +15 -17
- package/bin/lib/helpers.js +25 -4
- package/bin/lib/helpers.test.js +60 -0
- package/bin/lib/skills.js +7 -2
- package/bin/lib/skills.test.js +109 -0
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -1,105 +1,175 @@
|
|
|
1
|
-
|
|
2
|
-
<img src="https://raw.githubusercontent.com/agentskillkit/add-skill-kit/main/.github/logo.svg" height="96">
|
|
3
|
-
</p>
|
|
1
|
+
# add-skill-kit
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
> **The package manager for AI Agent Skills**
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<p align="center">
|
|
12
|
-
<a href="https://www.npmjs.com/package/add-skill-kit"><img src="https://img.shields.io/npm/v/add-skill-kit?style=flat&colorA=18181b&colorB=7c3aed" alt="npm"></a>
|
|
13
|
-
<a href="https://www.npmjs.com/package/add-skill-kit"><img src="https://img.shields.io/npm/dm/add-skill-kit?style=flat&colorA=18181b&colorB=7c3aed" alt="downloads"></a>
|
|
14
|
-
<a href="https://github.com/agentskillkit/add-skill-kit/blob/main/LICENSE"><img src="https://img.shields.io/github/license/agentskillkit/add-skill-kit?style=flat&colorA=18181b&colorB=7c3aed" alt="license"></a>
|
|
15
|
-
</p>
|
|
16
|
-
|
|
17
|
-
<p align="center">
|
|
18
|
-
<a href="#install">Install</a> •
|
|
19
|
-
<a href="#why">Why?</a> •
|
|
20
|
-
<a href="#what-you-get">What You Get</a> •
|
|
21
|
-
<a href="#commands">Commands</a>
|
|
22
|
-
</p>
|
|
5
|
+
[](https://www.npmjs.com/package/add-skill-kit)
|
|
6
|
+
[](https://www.npmjs.com/package/add-skill-kit)
|
|
7
|
+
[](https://github.com/agentskillkit/add-skill-kit/blob/main/LICENSE)
|
|
23
8
|
|
|
24
9
|
---
|
|
25
10
|
|
|
26
|
-
## Install
|
|
11
|
+
## Quick Install
|
|
27
12
|
|
|
28
13
|
```bash
|
|
29
14
|
npx -y add-skill-kit agentskillkit/agent-skills
|
|
30
15
|
```
|
|
31
16
|
|
|
32
|
-
|
|
17
|
+
Or install globally:
|
|
33
18
|
|
|
34
|
-
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g add-skill-kit
|
|
21
|
+
add-skill-kit agentskillkit/agent-skills
|
|
22
|
+
```
|
|
35
23
|
|
|
36
|
-
|
|
24
|
+
This installs the `.agent` folder containing all templates into your project.
|
|
37
25
|
|
|
38
|
-
|
|
26
|
+
---
|
|
39
27
|
|
|
40
|
-
|
|
28
|
+
## What's Included
|
|
41
29
|
|
|
42
|
-
|
|
|
43
|
-
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
| Manual updates | `add-skill-kit update` |
|
|
30
|
+
| Component | Count | Description |
|
|
31
|
+
|-----------|-------|-------------|
|
|
32
|
+
| **Agents** | 20 | Specialist AI personas (frontend, backend, security, PM, QA, etc.) |
|
|
33
|
+
| **Skills** | 40+ | Domain-specific knowledge modules |
|
|
34
|
+
| **Workflows** | 11 | Slash command procedures |
|
|
48
35
|
|
|
49
36
|
---
|
|
50
37
|
|
|
51
|
-
##
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### Using Agents
|
|
41
|
+
|
|
42
|
+
**No need to mention agents explicitly!** The system automatically detects and applies the right specialist(s):
|
|
52
43
|
|
|
53
44
|
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
└── GEMINI.md # Agent config
|
|
45
|
+
You: "Add JWT authentication"
|
|
46
|
+
AI: 🤖 Applying @security-auditor + @backend-specialist...
|
|
47
|
+
|
|
48
|
+
You: "Fix the dark mode button"
|
|
49
|
+
AI: 🤖 Using @frontend-specialist...
|
|
50
|
+
|
|
51
|
+
You: "Login returns 500 error"
|
|
52
|
+
AI: 🤖 Using @debugger for systematic analysis...
|
|
63
53
|
```
|
|
64
54
|
|
|
65
|
-
**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- `vulnerability-scanner` — OWASP security
|
|
55
|
+
**How it works:**
|
|
56
|
+
1. Analyzes your request silently
|
|
57
|
+
2. Detects domain(s) automatically (frontend, backend, security, etc.)
|
|
58
|
+
3. Selects the best specialist(s)
|
|
59
|
+
4. Informs you which expertise is being applied
|
|
71
60
|
|
|
72
|
-
|
|
61
|
+
**Benefits:**
|
|
62
|
+
- ✅ Zero learning curve - just describe what you need
|
|
63
|
+
- ✅ Always get expert responses
|
|
64
|
+
- ✅ Transparent - shows which agent is being used
|
|
65
|
+
- ✅ Can still override by mentioning agent explicitly
|
|
73
66
|
|
|
74
|
-
|
|
67
|
+
---
|
|
75
68
|
|
|
69
|
+
### Using Workflows
|
|
70
|
+
|
|
71
|
+
Invoke workflows with slash commands:
|
|
72
|
+
|
|
73
|
+
| Command | Description |
|
|
74
|
+
|---------|-------------|
|
|
75
|
+
| `/brainstorm` | Explore options before implementation |
|
|
76
|
+
| `/create` | Create new features or apps |
|
|
77
|
+
| `/debug` | Systematic debugging |
|
|
78
|
+
| `/deploy` | Deploy application |
|
|
79
|
+
| `/enhance` | Improve existing code |
|
|
80
|
+
| `/orchestrate` | Multi-agent coordination |
|
|
81
|
+
| `/plan` | Create task breakdown |
|
|
82
|
+
| `/preview` | Preview changes locally |
|
|
83
|
+
| `/status` | Check project status |
|
|
84
|
+
| `/test` | Generate and run tests |
|
|
85
|
+
| `/ui-ux-pro-max` | Design with 50 styles |
|
|
86
|
+
|
|
87
|
+
**Example:**
|
|
76
88
|
```
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
/brainstorm authentication system
|
|
90
|
+
/create landing page with hero section
|
|
91
|
+
/debug why login fails
|
|
80
92
|
```
|
|
81
93
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Using Skills
|
|
97
|
+
|
|
98
|
+
Skills are loaded automatically based on task context:
|
|
99
|
+
|
|
100
|
+
| Skill | Description |
|
|
101
|
+
|-------|-------------|
|
|
102
|
+
| `react-patterns` | Modern React hooks & composition |
|
|
103
|
+
| `api-patterns` | REST, GraphQL, tRPC design |
|
|
104
|
+
| `testing-patterns` | Unit, integration, E2E strategies |
|
|
105
|
+
| `clean-code` | Pragmatic coding standards |
|
|
106
|
+
| `systematic-debugging` | 4-phase debug methodology |
|
|
107
|
+
| `vulnerability-scanner` | OWASP security audit |
|
|
108
|
+
| ...and 30+ more | |
|
|
86
109
|
|
|
87
110
|
---
|
|
88
111
|
|
|
89
|
-
## Commands
|
|
112
|
+
## CLI Commands
|
|
90
113
|
|
|
91
114
|
```bash
|
|
92
|
-
# Install from repository
|
|
115
|
+
# Install skills from repository
|
|
93
116
|
npx -y add-skill-kit <org/repo>
|
|
94
117
|
|
|
95
118
|
# Install specific skill
|
|
96
119
|
npx -y add-skill-kit <org/repo>#skill-name
|
|
97
120
|
|
|
98
|
-
# List installed
|
|
121
|
+
# List installed skills
|
|
99
122
|
npx -y add-skill-kit list
|
|
100
123
|
|
|
101
|
-
# Validate
|
|
124
|
+
# Validate skill integrity
|
|
102
125
|
npx -y add-skill-kit validate
|
|
126
|
+
|
|
127
|
+
# Check workspace health
|
|
128
|
+
npx -y add-skill-kit doctor
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## How It Works
|
|
134
|
+
|
|
135
|
+
Skills are **data, not code**. They never execute on your system.
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
139
|
+
│ GitHub Repo │────▶│ add-skill-kit │────▶│ .agent/skills │
|
|
140
|
+
│ (source) │ │ (installer) │ │ (your project) │
|
|
141
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
142
|
+
│
|
|
143
|
+
▼
|
|
144
|
+
┌─────────────────┐
|
|
145
|
+
│ AI Agent │
|
|
146
|
+
│ reads skills │
|
|
147
|
+
└─────────────────┘
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Security:**
|
|
151
|
+
- ✅ Skills are read-only markdown & YAML
|
|
152
|
+
- ✅ No scripts executed during install
|
|
153
|
+
- ✅ SHA-256 integrity verification
|
|
154
|
+
- ✅ Full audit trail in `.skill-source.json`
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Project Structure After Install
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
your-project/
|
|
162
|
+
└── .agent/
|
|
163
|
+
├── skills/ # 40+ domain skills
|
|
164
|
+
│ ├── react-patterns/
|
|
165
|
+
│ ├── api-patterns/
|
|
166
|
+
│ ├── testing-patterns/
|
|
167
|
+
│ └── ...
|
|
168
|
+
├── workflows/ # Slash commands
|
|
169
|
+
│ ├── brainstorm.md
|
|
170
|
+
│ ├── debug.md
|
|
171
|
+
│ └── ...
|
|
172
|
+
└── GEMINI.md # Agent configuration
|
|
103
173
|
```
|
|
104
174
|
|
|
105
175
|
---
|
|
@@ -111,21 +181,24 @@ npx -y add-skill-kit validate
|
|
|
111
181
|
| **Antigravity** | ✅ Ready |
|
|
112
182
|
| Claude Code | 🔜 Coming |
|
|
113
183
|
| Gemini CLI | 🔜 Coming |
|
|
184
|
+
| Codex | 🔜 Coming |
|
|
114
185
|
|
|
115
186
|
---
|
|
116
187
|
|
|
117
|
-
##
|
|
188
|
+
## Requirements
|
|
118
189
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
190
|
+
- Node.js 18+
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Documentation
|
|
195
|
+
|
|
196
|
+
- [GitHub Repository](https://github.com/agentskillkit/add-skill-kit)
|
|
197
|
+
- [Issues & Feature Requests](https://github.com/agentskillkit/add-skill-kit/issues)
|
|
198
|
+
- [npm Package](https://www.npmjs.com/package/add-skill-kit)
|
|
199
|
+
|
|
200
|
+
---
|
|
122
201
|
|
|
123
|
-
|
|
124
|
-
<sub>Built by <a href="https://github.com/agentskillkit">Agent Skill Kit</a></sub>
|
|
125
|
-
</p>
|
|
202
|
+
## License
|
|
126
203
|
|
|
127
|
-
|
|
128
|
-
<a href="https://github.com/agentskillkit/add-skill-kit">GitHub</a> •
|
|
129
|
-
<a href="https://www.npmjs.com/package/add-skill-kit">npm</a> •
|
|
130
|
-
<a href="https://github.com/agentskillkit/add-skill-kit/blob/main/LICENSE">MIT</a>
|
|
131
|
-
</p>
|
|
204
|
+
MIT © [Agent Skill Kit](https://github.com/agentskillkit)
|
package/bin/cli.js
CHANGED
|
@@ -1,91 +1,80 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Install Agent Skill
|
|
3
|
+
* Install Agent Skill CLI
|
|
4
|
+
* @description Package manager for AI Agent Skills
|
|
4
5
|
*/
|
|
5
|
-
import { c,
|
|
6
|
-
import { command, params,
|
|
6
|
+
import { c, intro } from "./lib/ui.js";
|
|
7
|
+
import { command, params, VERSION } from "./lib/config.js";
|
|
8
|
+
|
|
9
|
+
// --- Command Registry ---
|
|
10
|
+
const COMMANDS = {
|
|
11
|
+
// Installation
|
|
12
|
+
install: { module: "./lib/commands/install.js", hasParam: true, aliases: ["add", "i"] },
|
|
13
|
+
uninstall: { module: "./lib/commands/uninstall.js", hasParam: true, aliases: ["remove", "rm"] },
|
|
14
|
+
update: { module: "./lib/commands/update.js", hasParam: true },
|
|
15
|
+
|
|
16
|
+
// Workspace
|
|
17
|
+
init: { module: "./lib/commands/init.js", aliases: ["list", "ls"] },
|
|
18
|
+
lock: { module: "./lib/commands/lock.js" },
|
|
19
|
+
cache: { module: "./lib/commands/cache.js", hasParam: true },
|
|
20
|
+
|
|
21
|
+
// Validation
|
|
22
|
+
verify: { module: "./lib/commands/verify.js" },
|
|
23
|
+
doctor: { module: "./lib/commands/doctor.js" },
|
|
24
|
+
validate: { module: "./lib/commands/validate.js", hasParam: true, aliases: ["check"] },
|
|
25
|
+
analyze: { module: "./lib/commands/analyze.js", hasParam: true },
|
|
26
|
+
|
|
27
|
+
// Info
|
|
28
|
+
info: { module: "./lib/commands/info.js", hasParam: true, aliases: ["show"] },
|
|
29
|
+
help: { module: "./lib/commands/help.js", aliases: ["--help", "-h"] }
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Find command config by name or alias
|
|
34
|
+
* @param {string} cmd - Command name or alias
|
|
35
|
+
* @returns {{ name: string, config: object } | null}
|
|
36
|
+
*/
|
|
37
|
+
function findCommand(cmd) {
|
|
38
|
+
// Direct match
|
|
39
|
+
if (COMMANDS[cmd]) {
|
|
40
|
+
return { name: cmd, config: COMMANDS[cmd] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Alias match
|
|
44
|
+
for (const [name, config] of Object.entries(COMMANDS)) {
|
|
45
|
+
if (config.aliases?.includes(cmd)) {
|
|
46
|
+
return { name, config };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
7
52
|
|
|
8
53
|
// --- MAIN ---
|
|
9
54
|
async function main() {
|
|
10
55
|
intro(c.bgBlue(c.white(` Add Skill Kit v${VERSION} `)));
|
|
11
56
|
|
|
12
|
-
// Basic command routing
|
|
13
|
-
let cmdModule;
|
|
14
|
-
|
|
15
57
|
try {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
cmdModule = await import("./lib/commands/update.js");
|
|
37
|
-
await cmdModule.run(params[0]);
|
|
38
|
-
break;
|
|
39
|
-
case "lock":
|
|
40
|
-
cmdModule = await import("./lib/commands/lock.js");
|
|
41
|
-
await cmdModule.run();
|
|
42
|
-
break;
|
|
43
|
-
case "verify":
|
|
44
|
-
cmdModule = await import("./lib/commands/verify.js");
|
|
45
|
-
await cmdModule.run();
|
|
46
|
-
break;
|
|
47
|
-
case "doctor":
|
|
48
|
-
cmdModule = await import("./lib/commands/doctor.js");
|
|
49
|
-
await cmdModule.run();
|
|
50
|
-
break;
|
|
51
|
-
case "cache":
|
|
52
|
-
cmdModule = await import("./lib/commands/cache.js");
|
|
53
|
-
await cmdModule.run(params[0]);
|
|
54
|
-
break;
|
|
55
|
-
case "validate":
|
|
56
|
-
case "check":
|
|
57
|
-
cmdModule = await import("./lib/commands/validate.js");
|
|
58
|
-
await cmdModule.run(params[0]);
|
|
59
|
-
break;
|
|
60
|
-
case "analyze":
|
|
61
|
-
cmdModule = await import("./lib/commands/analyze.js");
|
|
62
|
-
await cmdModule.run(params[0]);
|
|
63
|
-
break;
|
|
64
|
-
case "info":
|
|
65
|
-
case "show":
|
|
66
|
-
cmdModule = await import("./lib/commands/info.js");
|
|
67
|
-
await cmdModule.run(params[0]);
|
|
68
|
-
break;
|
|
69
|
-
case "help":
|
|
70
|
-
case "--help":
|
|
71
|
-
case "-h":
|
|
72
|
-
cmdModule = await import("./lib/commands/help.js");
|
|
73
|
-
await cmdModule.run();
|
|
74
|
-
break;
|
|
75
|
-
case "--version":
|
|
76
|
-
case "-V":
|
|
77
|
-
console.log(VERSION);
|
|
78
|
-
break;
|
|
79
|
-
default:
|
|
80
|
-
// Handle direct install via org/repo syntax
|
|
81
|
-
if (command.includes("/")) {
|
|
82
|
-
cmdModule = await import("./lib/commands/install.js");
|
|
83
|
-
await cmdModule.run(command);
|
|
84
|
-
} else {
|
|
85
|
-
console.log(`Unknown command: ${command}`);
|
|
86
|
-
cmdModule = await import("./lib/commands/help.js");
|
|
87
|
-
await cmdModule.run();
|
|
88
|
-
}
|
|
58
|
+
// Handle version flag
|
|
59
|
+
if (command === "--version" || command === "-V") {
|
|
60
|
+
console.log(VERSION);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Find command
|
|
65
|
+
const found = findCommand(command);
|
|
66
|
+
|
|
67
|
+
if (found) {
|
|
68
|
+
const cmdModule = await import(found.config.module);
|
|
69
|
+
await cmdModule.run(found.config.hasParam ? params[0] : undefined);
|
|
70
|
+
} else if (command.includes("/")) {
|
|
71
|
+
// Direct install via org/repo syntax
|
|
72
|
+
const cmdModule = await import("./lib/commands/install.js");
|
|
73
|
+
await cmdModule.run(command);
|
|
74
|
+
} else {
|
|
75
|
+
console.log(`Unknown command: ${command}`);
|
|
76
|
+
const cmdModule = await import("./lib/commands/help.js");
|
|
77
|
+
await cmdModule.run();
|
|
89
78
|
}
|
|
90
79
|
} catch (err) {
|
|
91
80
|
console.error(c.red("\nError: " + err.message));
|
|
@@ -12,7 +12,7 @@ import boxen from "boxen";
|
|
|
12
12
|
import { parseSkillSpec, merkleHash } from "../helpers.js";
|
|
13
13
|
import { parseSkillMdFrontmatter } from "../skills.js";
|
|
14
14
|
import { step, activeStep, stepLine, S, c, fatal, spinner, multiselect, select, confirm, isCancel, cancel } from "../ui.js";
|
|
15
|
-
import { WORKSPACE, GLOBAL_DIR } from "../config.js";
|
|
15
|
+
import { WORKSPACE, GLOBAL_DIR, OFFLINE } from "../config.js";
|
|
16
16
|
import { installSkill } from "../installer.js";
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -32,6 +32,15 @@ export async function run(spec) {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Check offline mode
|
|
36
|
+
if (OFFLINE) {
|
|
37
|
+
stepLine();
|
|
38
|
+
step(c.yellow("Offline mode enabled"), S.diamond, "yellow");
|
|
39
|
+
step(c.dim("Cannot install from remote repository in offline mode"), S.branch, "gray");
|
|
40
|
+
step(c.dim("Use --locked to install from lockfile instead"), S.branch, "gray");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
const url = `https://github.com/${org}/${repo}.git`;
|
|
36
45
|
|
|
37
46
|
stepLine();
|
|
@@ -42,11 +51,50 @@ export async function run(spec) {
|
|
|
42
51
|
|
|
43
52
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "add-skill-"));
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
// Retry logic with exponential backoff
|
|
55
|
+
const MAX_RETRIES = 3;
|
|
56
|
+
let lastError = null;
|
|
57
|
+
|
|
58
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
59
|
+
try {
|
|
60
|
+
await execAsync(`git clone --depth=1 ${url} "${tmp}"`, { timeout: 60000 });
|
|
61
|
+
if (ref) await execAsync(`git -C "${tmp}" checkout ${ref}`, { timeout: 30000 });
|
|
62
|
+
lastError = null;
|
|
63
|
+
break;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
lastError = err;
|
|
66
|
+
|
|
67
|
+
if (attempt < MAX_RETRIES) {
|
|
68
|
+
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s
|
|
69
|
+
s.message(`Retry ${attempt}/${MAX_RETRIES} in ${delay / 1000}s...`);
|
|
70
|
+
await new Promise(r => setTimeout(r, delay));
|
|
71
|
+
|
|
72
|
+
// Clean up failed attempt
|
|
73
|
+
try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { }
|
|
74
|
+
fs.mkdirSync(tmp, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (lastError) {
|
|
80
|
+
s.fail("Failed to clone repository");
|
|
81
|
+
stepLine();
|
|
82
|
+
|
|
83
|
+
// Provide helpful error messages
|
|
84
|
+
const errMsg = lastError.message || "";
|
|
85
|
+
if (errMsg.includes("not found") || errMsg.includes("404")) {
|
|
86
|
+
step(c.red(`Repository not found: ${org}/${repo}`), S.cross, "red");
|
|
87
|
+
step(c.dim("Check if the repository exists and is public"), S.branch, "gray");
|
|
88
|
+
} else if (errMsg.includes("timeout")) {
|
|
89
|
+
step(c.red("Connection timeout"), S.cross, "red");
|
|
90
|
+
step(c.dim("Check your internet connection and try again"), S.branch, "gray");
|
|
91
|
+
} else if (errMsg.includes("Could not resolve")) {
|
|
92
|
+
step(c.red("Network error: Unable to reach GitHub"), S.cross, "red");
|
|
93
|
+
step(c.dim("Check your internet connection"), S.branch, "gray");
|
|
94
|
+
} else {
|
|
95
|
+
step(c.red("Clone failed: " + errMsg.split("\n")[0]), S.cross, "red");
|
|
96
|
+
}
|
|
97
|
+
|
|
50
98
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
51
99
|
return;
|
|
52
100
|
}
|
|
@@ -159,9 +207,17 @@ export async function run(spec) {
|
|
|
159
207
|
step("Select skills to install");
|
|
160
208
|
console.log(`${c.gray(S.branch)} ${c.dim(selectedSkills.join(", "))}`);
|
|
161
209
|
|
|
162
|
-
// Agent selection
|
|
210
|
+
// Agent selection - currently only Antigravity supported
|
|
211
|
+
const availableAgents = [
|
|
212
|
+
{ label: "Antigravity (.agent/skills)", value: "antigravity", available: true },
|
|
213
|
+
{ label: "Claude Code (coming soon)", value: "claude", available: false },
|
|
214
|
+
{ label: "Codex (coming soon)", value: "codex", available: false },
|
|
215
|
+
{ label: "Gemini CLI (coming soon)", value: "gemini", available: false },
|
|
216
|
+
{ label: "Windsurf (coming soon)", value: "windsurf", available: false }
|
|
217
|
+
];
|
|
218
|
+
const activeAgentCount = availableAgents.filter(a => a.available).length;
|
|
163
219
|
stepLine();
|
|
164
|
-
step(
|
|
220
|
+
step(`Detected ${availableAgents.length} agents (${activeAgentCount} available)`);
|
|
165
221
|
|
|
166
222
|
let agents;
|
|
167
223
|
while (true) {
|
|
@@ -170,13 +226,10 @@ export async function run(spec) {
|
|
|
170
226
|
|
|
171
227
|
agents = await multiselect({
|
|
172
228
|
message: `${c.cyan("space")} select · ${c.cyan("enter")} confirm`,
|
|
173
|
-
options:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
{ label: c.dim("Gemini CLI (coming soon)"), value: "gemini" },
|
|
178
|
-
{ label: c.dim("Windsurf (coming soon)"), value: "windsurf" }
|
|
179
|
-
],
|
|
229
|
+
options: availableAgents.map(a => ({
|
|
230
|
+
label: a.available ? a.label : c.dim(a.label),
|
|
231
|
+
value: a.value
|
|
232
|
+
})),
|
|
180
233
|
initialValues: ["antigravity"],
|
|
181
234
|
required: true
|
|
182
235
|
});
|
|
@@ -248,9 +301,9 @@ export async function run(spec) {
|
|
|
248
301
|
const methodVerb = installMethod === "symlink" ? "symlink" : "copy";
|
|
249
302
|
|
|
250
303
|
for (const sn of selectedSkills) {
|
|
251
|
-
//
|
|
252
|
-
const
|
|
253
|
-
summaryContent += `${c.cyan(
|
|
304
|
+
// Show actual target path
|
|
305
|
+
const targetPath = path.relative(cwd, path.join(targetScope, sn));
|
|
306
|
+
summaryContent += `${c.cyan(targetPath)}\n`;
|
|
254
307
|
summaryContent += ` ${c.dim(methodVerb)} ${c.gray("→")} ${c.dim(agentsString)}\n\n`;
|
|
255
308
|
}
|
|
256
309
|
|
package/bin/lib/config.js
CHANGED
|
@@ -8,11 +8,11 @@ import os from "os";
|
|
|
8
8
|
/** Current working directory */
|
|
9
9
|
export const cwd = process.cwd();
|
|
10
10
|
|
|
11
|
-
/** Local workspace skills directory */
|
|
12
|
-
export const WORKSPACE = path.join(cwd, ".agent", "skills");
|
|
11
|
+
/** Local workspace skills directory (configurable via ADD_SKILL_WORKSPACE) */
|
|
12
|
+
export const WORKSPACE = process.env.ADD_SKILL_WORKSPACE || path.join(cwd, ".agent", "skills");
|
|
13
13
|
|
|
14
|
-
/** Global skills directory */
|
|
15
|
-
export const GLOBAL_DIR = path.join(os.homedir(), ".gemini", "antigravity", "skills");
|
|
14
|
+
/** Global skills directory (configurable via ADD_SKILL_GLOBAL_DIR) */
|
|
15
|
+
export const GLOBAL_DIR = process.env.ADD_SKILL_GLOBAL_DIR || path.join(os.homedir(), ".gemini", "antigravity", "skills");
|
|
16
16
|
|
|
17
17
|
/** Cache root directory */
|
|
18
18
|
export const CACHE_ROOT = process.env.ADD_SKILL_CACHE_DIR || path.join(os.homedir(), ".cache", "agentskillskit");
|
|
@@ -41,31 +41,28 @@ export const params = args.filter((a) => !a.startsWith("--")).slice(1);
|
|
|
41
41
|
|
|
42
42
|
// --- Flag Shortcuts ---
|
|
43
43
|
|
|
44
|
-
/** Use global scope */
|
|
44
|
+
/** @type {boolean} Use global scope */
|
|
45
45
|
export const GLOBAL = flags.has("--global") || flags.has("-g");
|
|
46
46
|
|
|
47
|
-
/** Verbose output */
|
|
47
|
+
/** @type {boolean} Verbose output */
|
|
48
48
|
export const VERBOSE = flags.has("--verbose") || flags.has("-v");
|
|
49
49
|
|
|
50
|
-
/** JSON output mode */
|
|
50
|
+
/** @type {boolean} JSON output mode */
|
|
51
51
|
export const JSON_OUTPUT = flags.has("--json");
|
|
52
52
|
|
|
53
|
-
/** Force operation */
|
|
53
|
+
/** @type {boolean} Force operation */
|
|
54
54
|
export const FORCE = flags.has("--force") || flags.has("-f");
|
|
55
55
|
|
|
56
|
-
/** Dry run mode */
|
|
56
|
+
/** @type {boolean} Dry run mode */
|
|
57
57
|
export const DRY = flags.has("--dry-run");
|
|
58
58
|
|
|
59
|
-
/** Strict mode */
|
|
59
|
+
/** @type {boolean} Strict mode */
|
|
60
60
|
export const STRICT = flags.has("--strict");
|
|
61
61
|
|
|
62
|
-
/**
|
|
63
|
-
export const FIX = flags.has("--fix");
|
|
64
|
-
|
|
65
|
-
/** Locked mode */
|
|
62
|
+
/** @type {boolean} Locked mode (install from lockfile) */
|
|
66
63
|
export const LOCKED = flags.has("--locked");
|
|
67
64
|
|
|
68
|
-
/** Offline mode */
|
|
65
|
+
/** @type {boolean} Offline mode (skip network operations) */
|
|
69
66
|
export const OFFLINE = flags.has("--offline");
|
|
70
67
|
|
|
71
68
|
// --- Package Info ---
|
|
@@ -73,8 +70,9 @@ export const OFFLINE = flags.has("--offline");
|
|
|
73
70
|
import { createRequire } from "module";
|
|
74
71
|
const require = createRequire(import.meta.url);
|
|
75
72
|
|
|
76
|
-
/** Package version */
|
|
73
|
+
/** @type {string} Package version */
|
|
77
74
|
export const VERSION = (() => {
|
|
78
75
|
try { return require("../../package.json").version; }
|
|
79
|
-
catch { return "
|
|
76
|
+
catch { return "1.2.0"; }
|
|
80
77
|
})();
|
|
78
|
+
|
package/bin/lib/helpers.js
CHANGED
|
@@ -24,7 +24,10 @@ export function getDirSize(dir) {
|
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
walk(dir);
|
|
27
|
-
} catch {
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// Directory may not exist or be inaccessible
|
|
29
|
+
if (process.env.DEBUG) console.error(`getDirSize error: ${err.message}`);
|
|
30
|
+
}
|
|
28
31
|
return size;
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -78,10 +81,24 @@ export function merkleHash(dir) {
|
|
|
78
81
|
* @returns {import('./types.js').ParsedSpec}
|
|
79
82
|
*/
|
|
80
83
|
export function parseSkillSpec(spec) {
|
|
84
|
+
if (!spec || typeof spec !== 'string') {
|
|
85
|
+
throw new Error('Skill spec is required');
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
const [repoPart, skillPart] = spec.split("#");
|
|
89
|
+
|
|
90
|
+
if (!repoPart.includes('/')) {
|
|
91
|
+
throw new Error(`Invalid spec format: "${spec}". Expected: org/repo or org/repo#skill`);
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
const [org, repo] = repoPart.split("/");
|
|
95
|
+
|
|
96
|
+
if (!org || !repo) {
|
|
97
|
+
throw new Error(`Invalid spec: missing org or repo in "${spec}"`);
|
|
98
|
+
}
|
|
99
|
+
|
|
83
100
|
const [skill, ref] = (skillPart || "").split("@");
|
|
84
|
-
return { org, repo, skill, ref };
|
|
101
|
+
return { org, repo, skill: skill || undefined, ref: ref || undefined };
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
/**
|
|
@@ -140,8 +157,12 @@ export function loadSkillLock() {
|
|
|
140
157
|
* @returns {string[]}
|
|
141
158
|
*/
|
|
142
159
|
export function loadRegistries() {
|
|
143
|
-
try {
|
|
144
|
-
|
|
160
|
+
try {
|
|
161
|
+
return fs.existsSync(REGISTRIES_FILE) ? JSON.parse(fs.readFileSync(REGISTRIES_FILE, "utf-8")) : [];
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (process.env.DEBUG) console.error(`loadRegistries error: ${err.message}`);
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
145
166
|
}
|
|
146
167
|
|
|
147
168
|
/**
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for helpers.js
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
import { formatBytes, formatDate, parseSkillSpec } from "./helpers.js";
|
|
6
|
+
|
|
7
|
+
describe("formatBytes", () => {
|
|
8
|
+
it("formats bytes correctly", () => {
|
|
9
|
+
expect(formatBytes(0)).toBe("0 B");
|
|
10
|
+
expect(formatBytes(512)).toBe("512 B");
|
|
11
|
+
expect(formatBytes(1024)).toBe("1.0 KB");
|
|
12
|
+
expect(formatBytes(1536)).toBe("1.5 KB");
|
|
13
|
+
expect(formatBytes(1048576)).toBe("1.0 MB");
|
|
14
|
+
expect(formatBytes(1572864)).toBe("1.5 MB");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("formatDate", () => {
|
|
19
|
+
it("returns 'unknown' for undefined", () => {
|
|
20
|
+
expect(formatDate()).toBe("unknown");
|
|
21
|
+
expect(formatDate(undefined)).toBe("unknown");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("formats ISO date strings", () => {
|
|
25
|
+
const result = formatDate("2026-01-25T00:00:00.000Z");
|
|
26
|
+
expect(result).toBeTruthy();
|
|
27
|
+
expect(typeof result).toBe("string");
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("parseSkillSpec", () => {
|
|
32
|
+
it("parses org/repo format", () => {
|
|
33
|
+
const result = parseSkillSpec("agentskillkit/agent-skills");
|
|
34
|
+
expect(result.org).toBe("agentskillkit");
|
|
35
|
+
expect(result.repo).toBe("agent-skills");
|
|
36
|
+
expect(result.skill).toBeUndefined();
|
|
37
|
+
expect(result.ref).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("parses org/repo#skill format", () => {
|
|
41
|
+
const result = parseSkillSpec("agentskillkit/agent-skills#react-patterns");
|
|
42
|
+
expect(result.org).toBe("agentskillkit");
|
|
43
|
+
expect(result.repo).toBe("agent-skills");
|
|
44
|
+
expect(result.skill).toBe("react-patterns");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("parses org/repo#skill@ref format", () => {
|
|
48
|
+
const result = parseSkillSpec("agentskillkit/agent-skills#react-patterns@v1.0.0");
|
|
49
|
+
expect(result.org).toBe("agentskillkit");
|
|
50
|
+
expect(result.repo).toBe("agent-skills");
|
|
51
|
+
expect(result.skill).toBe("react-patterns");
|
|
52
|
+
expect(result.ref).toBe("v1.0.0");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("throws error for invalid spec", () => {
|
|
56
|
+
expect(() => parseSkillSpec("")).toThrow("Skill spec is required");
|
|
57
|
+
expect(() => parseSkillSpec("invalid")).toThrow("Invalid spec format");
|
|
58
|
+
expect(() => parseSkillSpec(null)).toThrow("Skill spec is required");
|
|
59
|
+
});
|
|
60
|
+
});
|
package/bin/lib/skills.js
CHANGED
|
@@ -30,7 +30,10 @@ export function parseSkillMdFrontmatter(p) {
|
|
|
30
30
|
else if (key && val) meta[key] = val;
|
|
31
31
|
}
|
|
32
32
|
return meta;
|
|
33
|
-
} catch
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (process.env.DEBUG) console.error(`parseSkillMdFrontmatter error: ${err.message}`);
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
@@ -69,7 +72,9 @@ export function detectSkillStructure(dir) {
|
|
|
69
72
|
s.files.push(item);
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
|
-
} catch {
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (process.env.DEBUG) console.error(`detectSkillStructure error: ${err.message}`);
|
|
77
|
+
}
|
|
73
78
|
return s;
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for skills.js
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import { parseSkillMdFrontmatter, detectSkillStructure } from "./skills.js";
|
|
9
|
+
|
|
10
|
+
describe("parseSkillMdFrontmatter", () => {
|
|
11
|
+
let tempDir;
|
|
12
|
+
let skillMdPath;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "skill-test-"));
|
|
16
|
+
skillMdPath = path.join(tempDir, "SKILL.md");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("parses valid frontmatter", () => {
|
|
24
|
+
const content = `---
|
|
25
|
+
name: test-skill
|
|
26
|
+
description: A test skill for testing
|
|
27
|
+
version: 1.0.0
|
|
28
|
+
author: Test Author
|
|
29
|
+
tags: react, testing, patterns
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Test Skill
|
|
33
|
+
|
|
34
|
+
Some content here.
|
|
35
|
+
`;
|
|
36
|
+
fs.writeFileSync(skillMdPath, content);
|
|
37
|
+
|
|
38
|
+
const result = parseSkillMdFrontmatter(skillMdPath);
|
|
39
|
+
|
|
40
|
+
expect(result.name).toBe("test-skill");
|
|
41
|
+
expect(result.description).toBe("A test skill for testing");
|
|
42
|
+
expect(result.version).toBe("1.0.0");
|
|
43
|
+
expect(result.author).toBe("Test Author");
|
|
44
|
+
expect(result.tags).toEqual(["react", "testing", "patterns"]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns empty object for no frontmatter", () => {
|
|
48
|
+
fs.writeFileSync(skillMdPath, "# Just a heading\n\nNo frontmatter here.");
|
|
49
|
+
|
|
50
|
+
const result = parseSkillMdFrontmatter(skillMdPath);
|
|
51
|
+
|
|
52
|
+
expect(result).toEqual({});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns empty object for non-existent file", () => {
|
|
56
|
+
const result = parseSkillMdFrontmatter("/non/existent/path/SKILL.md");
|
|
57
|
+
expect(result).toEqual({});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("detectSkillStructure", () => {
|
|
62
|
+
let tempDir;
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "skill-structure-"));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("detects standard directories", () => {
|
|
73
|
+
fs.mkdirSync(path.join(tempDir, "resources"));
|
|
74
|
+
fs.mkdirSync(path.join(tempDir, "examples"));
|
|
75
|
+
fs.mkdirSync(path.join(tempDir, "scripts"));
|
|
76
|
+
fs.writeFileSync(path.join(tempDir, "SKILL.md"), "# Skill");
|
|
77
|
+
|
|
78
|
+
const result = detectSkillStructure(tempDir);
|
|
79
|
+
|
|
80
|
+
expect(result.hasResources).toBe(true);
|
|
81
|
+
expect(result.hasExamples).toBe(true);
|
|
82
|
+
expect(result.hasScripts).toBe(true);
|
|
83
|
+
expect(result.directories).toContain("resources");
|
|
84
|
+
expect(result.directories).toContain("examples");
|
|
85
|
+
expect(result.directories).toContain("scripts");
|
|
86
|
+
expect(result.files).toContain("SKILL.md");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("detects governance directories", () => {
|
|
90
|
+
fs.mkdirSync(path.join(tempDir, "constitution"));
|
|
91
|
+
fs.mkdirSync(path.join(tempDir, "doctrines"));
|
|
92
|
+
fs.mkdirSync(path.join(tempDir, "enforcement"));
|
|
93
|
+
fs.mkdirSync(path.join(tempDir, "proposals"));
|
|
94
|
+
|
|
95
|
+
const result = detectSkillStructure(tempDir);
|
|
96
|
+
|
|
97
|
+
expect(result.hasConstitution).toBe(true);
|
|
98
|
+
expect(result.hasDoctrines).toBe(true);
|
|
99
|
+
expect(result.hasEnforcement).toBe(true);
|
|
100
|
+
expect(result.hasProposals).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("returns empty structure for non-existent dir", () => {
|
|
104
|
+
const result = detectSkillStructure("/non/existent/path");
|
|
105
|
+
|
|
106
|
+
expect(result.directories).toEqual([]);
|
|
107
|
+
expect(result.files).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "add-skill-kit",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Enterprise-grade Agent Skill Manager with Antigravity Skills support, Progressive Disclosure detection, and semantic routing validation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "agentskillkit <agentskillkit@gmail.com>",
|
|
@@ -38,9 +38,12 @@
|
|
|
38
38
|
"antigravity"
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
|
-
"lint": "
|
|
42
|
-
"
|
|
43
|
-
"
|
|
41
|
+
"lint": "eslint bin/",
|
|
42
|
+
"lint:fix": "eslint bin/ --fix",
|
|
43
|
+
"format": "prettier --write bin/",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"ci": "npm run lint && npm test && node bin/cli.js verify --strict && node bin/cli.js doctor --strict"
|
|
44
47
|
},
|
|
45
48
|
"publishConfig": {
|
|
46
49
|
"access": "public"
|
|
@@ -52,5 +55,10 @@
|
|
|
52
55
|
"kleur": "^4.1.5",
|
|
53
56
|
"ora": "^8.1.0",
|
|
54
57
|
"prompts": "^2.4.2"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"eslint": "^8.57.0",
|
|
61
|
+
"prettier": "^3.2.5",
|
|
62
|
+
"vitest": "^1.6.0"
|
|
55
63
|
}
|
|
56
64
|
}
|