add-skill-kit 3.2.0 → 3.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/bin/lib/agents.js +208 -0
- package/bin/lib/commands/install.js +172 -141
- package/bin/lib/installer.js +115 -0
- package/bin/lib/ui.js +173 -6
- package/package.json +67 -64
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Agent definitions and detection
|
|
3
|
+
* Based on Vercel's agent-skills CLI structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
|
|
10
|
+
const home = homedir();
|
|
11
|
+
|
|
12
|
+
// Environment-based paths
|
|
13
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
14
|
+
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} AgentConfig
|
|
18
|
+
* @property {string} name - Internal agent ID
|
|
19
|
+
* @property {string} displayName - Display name for UI
|
|
20
|
+
* @property {string} skillsDir - Project-level skills directory
|
|
21
|
+
* @property {string} globalSkillsDir - Global skills directory
|
|
22
|
+
* @property {() => boolean} detect - Detection function
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* All supported agents with detection logic
|
|
27
|
+
* @type {Record<string, AgentConfig>}
|
|
28
|
+
*/
|
|
29
|
+
export const AGENTS = {
|
|
30
|
+
antigravity: {
|
|
31
|
+
name: "antigravity",
|
|
32
|
+
displayName: "Antigravity",
|
|
33
|
+
skillsDir: ".agent/skills",
|
|
34
|
+
globalSkillsDir: join(home, ".gemini/antigravity/global_skills"),
|
|
35
|
+
detect: () => existsSync(join(process.cwd(), ".agent")) || existsSync(join(home, ".gemini/antigravity"))
|
|
36
|
+
},
|
|
37
|
+
"claude-code": {
|
|
38
|
+
name: "claude-code",
|
|
39
|
+
displayName: "Claude Code",
|
|
40
|
+
skillsDir: ".claude/skills",
|
|
41
|
+
globalSkillsDir: join(claudeHome, "skills"),
|
|
42
|
+
detect: () => existsSync(claudeHome)
|
|
43
|
+
},
|
|
44
|
+
codex: {
|
|
45
|
+
name: "codex",
|
|
46
|
+
displayName: "Codex",
|
|
47
|
+
skillsDir: ".codex/skills",
|
|
48
|
+
globalSkillsDir: join(codexHome, "skills"),
|
|
49
|
+
detect: () => existsSync(codexHome) || existsSync("/etc/codex")
|
|
50
|
+
},
|
|
51
|
+
"gemini-cli": {
|
|
52
|
+
name: "gemini-cli",
|
|
53
|
+
displayName: "Gemini CLI",
|
|
54
|
+
skillsDir: ".gemini/skills",
|
|
55
|
+
globalSkillsDir: join(home, ".gemini/skills"),
|
|
56
|
+
detect: () => existsSync(join(home, ".gemini"))
|
|
57
|
+
},
|
|
58
|
+
"github-copilot": {
|
|
59
|
+
name: "github-copilot",
|
|
60
|
+
displayName: "GitHub Copilot",
|
|
61
|
+
skillsDir: ".github/skills",
|
|
62
|
+
globalSkillsDir: join(home, ".copilot/skills"),
|
|
63
|
+
detect: () => existsSync(join(process.cwd(), ".github")) || existsSync(join(home, ".copilot"))
|
|
64
|
+
},
|
|
65
|
+
windsurf: {
|
|
66
|
+
name: "windsurf",
|
|
67
|
+
displayName: "Windsurf",
|
|
68
|
+
skillsDir: ".windsurf/skills",
|
|
69
|
+
globalSkillsDir: join(home, ".codeium/windsurf/skills"),
|
|
70
|
+
detect: () => existsSync(join(home, ".codeium/windsurf"))
|
|
71
|
+
},
|
|
72
|
+
cursor: {
|
|
73
|
+
name: "cursor",
|
|
74
|
+
displayName: "Cursor",
|
|
75
|
+
skillsDir: ".cursor/skills",
|
|
76
|
+
globalSkillsDir: join(home, ".cursor/skills"),
|
|
77
|
+
detect: () => existsSync(join(home, ".cursor"))
|
|
78
|
+
},
|
|
79
|
+
cline: {
|
|
80
|
+
name: "cline",
|
|
81
|
+
displayName: "Cline",
|
|
82
|
+
skillsDir: ".cline/skills",
|
|
83
|
+
globalSkillsDir: join(home, ".cline/skills"),
|
|
84
|
+
detect: () => existsSync(join(home, ".cline"))
|
|
85
|
+
},
|
|
86
|
+
roo: {
|
|
87
|
+
name: "roo",
|
|
88
|
+
displayName: "Roo Code",
|
|
89
|
+
skillsDir: ".roo/skills",
|
|
90
|
+
globalSkillsDir: join(home, ".roo/skills"),
|
|
91
|
+
detect: () => existsSync(join(home, ".roo"))
|
|
92
|
+
},
|
|
93
|
+
continue: {
|
|
94
|
+
name: "continue",
|
|
95
|
+
displayName: "Continue",
|
|
96
|
+
skillsDir: ".continue/skills",
|
|
97
|
+
globalSkillsDir: join(home, ".continue/skills"),
|
|
98
|
+
detect: () => existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"))
|
|
99
|
+
},
|
|
100
|
+
goose: {
|
|
101
|
+
name: "goose",
|
|
102
|
+
displayName: "Goose",
|
|
103
|
+
skillsDir: ".goose/skills",
|
|
104
|
+
globalSkillsDir: join(home, ".config/goose/skills"),
|
|
105
|
+
detect: () => existsSync(join(home, ".config/goose"))
|
|
106
|
+
},
|
|
107
|
+
trae: {
|
|
108
|
+
name: "trae",
|
|
109
|
+
displayName: "Trae",
|
|
110
|
+
skillsDir: ".trae/skills",
|
|
111
|
+
globalSkillsDir: join(home, ".trae/skills"),
|
|
112
|
+
detect: () => existsSync(join(home, ".trae"))
|
|
113
|
+
},
|
|
114
|
+
kilo: {
|
|
115
|
+
name: "kilo",
|
|
116
|
+
displayName: "Kilo Code",
|
|
117
|
+
skillsDir: ".kilocode/skills",
|
|
118
|
+
globalSkillsDir: join(home, ".kilocode/skills"),
|
|
119
|
+
detect: () => existsSync(join(home, ".kilocode"))
|
|
120
|
+
},
|
|
121
|
+
opencode: {
|
|
122
|
+
name: "opencode",
|
|
123
|
+
displayName: "OpenCode",
|
|
124
|
+
skillsDir: ".opencode/skills",
|
|
125
|
+
globalSkillsDir: join(home, ".config/opencode/skills"),
|
|
126
|
+
detect: () => existsSync(join(home, ".config/opencode"))
|
|
127
|
+
},
|
|
128
|
+
amp: {
|
|
129
|
+
name: "amp",
|
|
130
|
+
displayName: "Amp",
|
|
131
|
+
skillsDir: ".agents/skills",
|
|
132
|
+
globalSkillsDir: join(home, ".config/agents/skills"),
|
|
133
|
+
detect: () => existsSync(join(home, ".config/amp"))
|
|
134
|
+
},
|
|
135
|
+
junie: {
|
|
136
|
+
name: "junie",
|
|
137
|
+
displayName: "Junie",
|
|
138
|
+
skillsDir: ".junie/skills",
|
|
139
|
+
globalSkillsDir: join(home, ".junie/skills"),
|
|
140
|
+
detect: () => existsSync(join(home, ".junie"))
|
|
141
|
+
},
|
|
142
|
+
"kiro-cli": {
|
|
143
|
+
name: "kiro-cli",
|
|
144
|
+
displayName: "Kiro CLI",
|
|
145
|
+
skillsDir: ".kiro/skills",
|
|
146
|
+
globalSkillsDir: join(home, ".kiro/skills"),
|
|
147
|
+
detect: () => existsSync(join(home, ".kiro"))
|
|
148
|
+
},
|
|
149
|
+
zencoder: {
|
|
150
|
+
name: "zencoder",
|
|
151
|
+
displayName: "Zencoder",
|
|
152
|
+
skillsDir: ".zencoder/skills",
|
|
153
|
+
globalSkillsDir: join(home, ".zencoder/skills"),
|
|
154
|
+
detect: () => existsSync(join(home, ".zencoder"))
|
|
155
|
+
},
|
|
156
|
+
openhands: {
|
|
157
|
+
name: "openhands",
|
|
158
|
+
displayName: "OpenHands",
|
|
159
|
+
skillsDir: ".openhands/skills",
|
|
160
|
+
globalSkillsDir: join(home, ".openhands/skills"),
|
|
161
|
+
detect: () => existsSync(join(home, ".openhands"))
|
|
162
|
+
},
|
|
163
|
+
"qwen-code": {
|
|
164
|
+
name: "qwen-code",
|
|
165
|
+
displayName: "Qwen Code",
|
|
166
|
+
skillsDir: ".qwen/skills",
|
|
167
|
+
globalSkillsDir: join(home, ".qwen/skills"),
|
|
168
|
+
detect: () => existsSync(join(home, ".qwen"))
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Detect all installed agents on the system
|
|
174
|
+
* @returns {Array<{name: string, displayName: string, skillsDir: string, globalSkillsDir: string}>}
|
|
175
|
+
*/
|
|
176
|
+
export function detectInstalledAgents() {
|
|
177
|
+
const detected = [];
|
|
178
|
+
|
|
179
|
+
for (const [key, config] of Object.entries(AGENTS)) {
|
|
180
|
+
if (config.detect()) {
|
|
181
|
+
detected.push({
|
|
182
|
+
name: config.name,
|
|
183
|
+
displayName: config.displayName,
|
|
184
|
+
skillsDir: config.skillsDir,
|
|
185
|
+
globalSkillsDir: config.globalSkillsDir
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return detected;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get agent config by name
|
|
195
|
+
* @param {string} name - Agent name
|
|
196
|
+
* @returns {AgentConfig | undefined}
|
|
197
|
+
*/
|
|
198
|
+
export function getAgentConfig(name) {
|
|
199
|
+
return AGENTS[name];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get all agent names
|
|
204
|
+
* @returns {string[]}
|
|
205
|
+
*/
|
|
206
|
+
export function getAllAgentNames() {
|
|
207
|
+
return Object.keys(AGENTS);
|
|
208
|
+
}
|
|
@@ -130,14 +130,34 @@ export async function run(spec) {
|
|
|
130
130
|
if (skillsDir) {
|
|
131
131
|
for (const e of fs.readdirSync(skillsDir)) {
|
|
132
132
|
const sp = path.join(skillsDir, e);
|
|
133
|
-
if (fs.statSync(sp).isDirectory()
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
if (fs.statSync(sp).isDirectory()) {
|
|
134
|
+
// Check if this directory has SKILL.md (top-level skill)
|
|
135
|
+
if (fs.existsSync(path.join(sp, "SKILL.md"))) {
|
|
136
|
+
const m = parseSkillMdFrontmatter(path.join(sp, "SKILL.md"));
|
|
137
|
+
skillsInRepo.push({
|
|
138
|
+
title: e, // Only show folder name
|
|
139
|
+
value: e,
|
|
140
|
+
description: m.description || "",
|
|
141
|
+
selected: singleSkill ? e === singleSkill : true,
|
|
142
|
+
_path: sp
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Also scan nested directories (2 levels deep) for sub-skills
|
|
147
|
+
for (const sub of fs.readdirSync(sp)) {
|
|
148
|
+
const subPath = path.join(sp, sub);
|
|
149
|
+
if (fs.statSync(subPath).isDirectory() && fs.existsSync(path.join(subPath, "SKILL.md"))) {
|
|
150
|
+
const m = parseSkillMdFrontmatter(path.join(subPath, "SKILL.md"));
|
|
151
|
+
const skillName = `${e}/${sub}`; // e.g. "game-development/2d-games"
|
|
152
|
+
skillsInRepo.push({
|
|
153
|
+
title: skillName,
|
|
154
|
+
value: skillName,
|
|
155
|
+
description: m.description || "",
|
|
156
|
+
selected: singleSkill ? skillName === singleSkill : true,
|
|
157
|
+
_path: subPath
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
141
161
|
}
|
|
142
162
|
}
|
|
143
163
|
}
|
|
@@ -167,177 +187,167 @@ export async function run(spec) {
|
|
|
167
187
|
stepLine();
|
|
168
188
|
step(`Auto-selected: ${c.cyan(singleSkill)}`);
|
|
169
189
|
} else {
|
|
170
|
-
//
|
|
171
|
-
|
|
190
|
+
// Group skills by category - 5 main categories (all skills covered, no Other)
|
|
191
|
+
// NOTE: Order matters! Mobile is before Security so mobile-security-coder goes to Mobile
|
|
192
|
+
const CATEGORY_KEYWORDS = {
|
|
193
|
+
"⚙️ Backend & API": [
|
|
194
|
+
// Core backend
|
|
195
|
+
"backend", "api", "nodejs", "server", "database", "prisma", "mcp", "python", "cache",
|
|
196
|
+
// Architecture & patterns
|
|
197
|
+
"architecture", "pattern", "app-builder", "performance", "profiling",
|
|
198
|
+
// Testing & quality
|
|
199
|
+
"testing", "test", "tdd", "lint", "validate", "debugging", "systematic", "debug",
|
|
200
|
+
"code-review", "code-quality", "e2e", "integration", "webapp", "checklist", "reviewer", "clean",
|
|
201
|
+
// Documentation & tools
|
|
202
|
+
"documentation", "plan", "writing", "geo", "seo", "i18n", "localization", "template",
|
|
203
|
+
// CLI & scripting
|
|
204
|
+
"bash", "linux", "powershell", "windows", "shell", "script",
|
|
205
|
+
// AI & automation
|
|
206
|
+
"agent", "routing", "brainstorm", "behavioral", "intelligent", "parallel", "modes",
|
|
207
|
+
// Other skills
|
|
208
|
+
"typescript", "problem", "workflow", "builder", "conventions", "fundamentals",
|
|
209
|
+
"best-practices", "procedures", "management"
|
|
210
|
+
],
|
|
211
|
+
"🎨 Frontend & UI": ["frontend", "nextjs", "tailwind", "css", "ui", "ux", "visual", "studio", "web-core", "design-system", "react-architect", "react"],
|
|
212
|
+
"🎮 Game Development": ["game", "development", "engine", "unity", "unreal", "godot", "phaser", "game-dev"],
|
|
213
|
+
"📱 Mobile": ["mobile", "first", "developer", "react-native", "flutter", "ios", "android", "swift", "kotlin"],
|
|
214
|
+
"🔒 Security & DevOps": ["security", "vulnerability", "deploy", "git", "docker", "red-team", "governance", "offensive", "gitops", "cicd", "pipeline", "incident", "chaos", "scanner"]
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
function categorizeSkill(skillName) {
|
|
218
|
+
const lower = skillName.toLowerCase();
|
|
219
|
+
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
|
|
220
|
+
if (keywords.some(kw => lower.includes(kw))) {
|
|
221
|
+
return category;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return "⚙️ Backend & API"; // Default fallback (no "Other" category)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Group skills by category
|
|
228
|
+
const grouped = {};
|
|
229
|
+
for (const skill of skillsInRepo) {
|
|
230
|
+
const cat = categorizeSkill(skill.value);
|
|
231
|
+
if (!grouped[cat]) grouped[cat] = [];
|
|
232
|
+
grouped[cat].push(skill);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Custom sort: alphabetical but "Other" always last
|
|
236
|
+
const sortedCategories = Object.keys(grouped).sort((a, b) => {
|
|
237
|
+
if (a.includes("Other")) return 1;
|
|
238
|
+
if (b.includes("Other")) return -1;
|
|
239
|
+
return a.localeCompare(b);
|
|
240
|
+
});
|
|
172
241
|
|
|
173
|
-
// Active Step: Selection
|
|
174
|
-
// Note: activeStep already prints the icon. Clack prompts typically print their own message.
|
|
175
|
-
// But we want to enforce the tree style.
|
|
176
|
-
// Clack's multiselect prints: "? Message"
|
|
177
|
-
// We want: "◆ Select skills to install"
|
|
178
|
-
// We can print "activeStep" line, and then let clack print below?
|
|
179
|
-
// Or we use clack's message but Clack doesn't support custom icons well in the prompt line itself easily without patches.
|
|
180
|
-
// We will print the active step line manually, and set clack message to something minimal or empty if possible, or just repeat it but we want the "◆".
|
|
181
|
-
// Let's print activeStep("Select skills to install") and then run multiselect with a simpler message like " " or hidden?
|
|
182
|
-
// Clack behaves best with a message. Let's try printing the header and then the prompt.
|
|
183
242
|
|
|
184
|
-
|
|
243
|
+
stepLine();
|
|
244
|
+
activeStep("Select skill categories to install");
|
|
185
245
|
|
|
186
|
-
|
|
246
|
+
// Show only categories, not individual skills
|
|
247
|
+
const selectedCategories = await multiselect({
|
|
187
248
|
message: `${c.cyan("space")} select · ${c.cyan("enter")} confirm`,
|
|
188
|
-
options:
|
|
189
|
-
label:
|
|
190
|
-
value:
|
|
249
|
+
options: sortedCategories.map(cat => ({
|
|
250
|
+
label: `${cat} (${grouped[cat].length} skills)`,
|
|
251
|
+
value: cat,
|
|
252
|
+
hint: grouped[cat].slice(0, 3).map(s => s.value).join(", ") + (grouped[cat].length > 3 ? "..." : "")
|
|
191
253
|
})),
|
|
192
|
-
initialValues:
|
|
254
|
+
initialValues: sortedCategories, // Pre-select all
|
|
193
255
|
required: true
|
|
194
256
|
});
|
|
195
257
|
|
|
196
|
-
if (isCancel(
|
|
258
|
+
if (isCancel(selectedCategories)) {
|
|
197
259
|
cancel("Cancelled.");
|
|
198
260
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
199
261
|
return;
|
|
200
262
|
}
|
|
201
263
|
|
|
202
|
-
|
|
264
|
+
// Get all skills from selected categories
|
|
265
|
+
selectedSkills = selectedCategories.flatMap(cat => grouped[cat].map(s => s.value));
|
|
203
266
|
}
|
|
204
267
|
|
|
205
|
-
|
|
268
|
+
|
|
269
|
+
|
|
206
270
|
stepLine();
|
|
207
271
|
step("Select skills to install");
|
|
208
272
|
console.log(`${c.gray(S.branch)} ${c.dim(selectedSkills.join(", "))}`);
|
|
209
273
|
|
|
210
|
-
// ---
|
|
274
|
+
// --- Detect installed agents ---
|
|
275
|
+
stepLine();
|
|
276
|
+
const { detectInstalledAgents } = await import("../agents.js");
|
|
277
|
+
const detectedAgents = detectInstalledAgents();
|
|
211
278
|
|
|
212
|
-
|
|
213
|
-
|
|
279
|
+
if (detectedAgents.length === 0) {
|
|
280
|
+
step(c.yellow("No agents detected"), S.diamond, "yellow");
|
|
281
|
+
step(c.dim("Please install at least one AI agent (Antigravity, Claude Code, etc.)"), S.branch, "gray");
|
|
282
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
214
285
|
|
|
215
|
-
|
|
216
|
-
isGlobal = true;
|
|
217
|
-
} else {
|
|
218
|
-
activeStep("Select installation scope");
|
|
219
|
-
|
|
220
|
-
const scopeSelection = await select({
|
|
221
|
-
message: " ",
|
|
222
|
-
options: [
|
|
223
|
-
{
|
|
224
|
-
label: "Current Project",
|
|
225
|
-
value: "local",
|
|
226
|
-
hint: "Installs to .agent/ (Best for project-specific skills)"
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
label: "Global System",
|
|
230
|
-
value: "global",
|
|
231
|
-
hint: "Installs to ~/.gemini/ (Available to all projects via 'agent' command)"
|
|
232
|
-
}
|
|
233
|
-
],
|
|
234
|
-
initialValue: "local"
|
|
235
|
-
});
|
|
286
|
+
step(`Detected ${detectedAgents.length} agents`);
|
|
236
287
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
288
|
+
// --- Select agents (Vercel-style) ---
|
|
289
|
+
const { selectAgentsPrompt, selectScopePrompt, selectMethodPrompt } = await import("../ui.js");
|
|
290
|
+
|
|
291
|
+
stepLine();
|
|
292
|
+
activeStep("Install to");
|
|
293
|
+
const selectedAgents = await selectAgentsPrompt(detectedAgents);
|
|
242
294
|
|
|
243
|
-
|
|
295
|
+
if (!selectedAgents || selectedAgents.length === 0) {
|
|
296
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
297
|
+
return;
|
|
244
298
|
}
|
|
245
|
-
const targetScope = isGlobal ? GLOBAL_DIR : WORKSPACE;
|
|
246
299
|
|
|
247
300
|
stepLine();
|
|
248
|
-
step("
|
|
249
|
-
console.log(`${c.gray(S.branch)} ${c.dim(
|
|
250
|
-
|
|
251
|
-
// Agent selection - currently only Antigravity supported
|
|
252
|
-
const availableAgents = [
|
|
253
|
-
{ label: "Antigravity (.agent/skills)", value: "antigravity", available: true },
|
|
254
|
-
{ label: "Claude Code (coming soon)", value: "claude", available: false },
|
|
255
|
-
{ label: "Codex (coming soon)", value: "codex", available: false },
|
|
256
|
-
{ label: "Gemini CLI (coming soon)", value: "gemini", available: false },
|
|
257
|
-
{ label: "Windsurf (coming soon)", value: "windsurf", available: false }
|
|
258
|
-
];
|
|
259
|
-
const activeAgentCount = availableAgents.filter(a => a.available).length;
|
|
260
|
-
stepLine();
|
|
261
|
-
step(`Detected ${availableAgents.length} agents (${activeAgentCount} available)`);
|
|
301
|
+
step("Install to");
|
|
302
|
+
console.log(`${c.gray(S.branch)} ${c.dim(selectedAgents.map(a => a.displayName).join(", "))}`);
|
|
262
303
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Active Step
|
|
266
|
-
activeStep("Select agents to install skills to (Antigravity only)");
|
|
304
|
+
// --- Select installation scope ---
|
|
305
|
+
let isGlobal = GLOBAL;
|
|
267
306
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
value: a.value
|
|
273
|
-
})),
|
|
274
|
-
initialValues: ["antigravity"],
|
|
275
|
-
required: true
|
|
276
|
-
});
|
|
307
|
+
if (!GLOBAL) {
|
|
308
|
+
stepLine();
|
|
309
|
+
activeStep("Installation scope");
|
|
310
|
+
const scope = await selectScopePrompt();
|
|
277
311
|
|
|
278
|
-
if (
|
|
279
|
-
cancel("Cancelled.");
|
|
312
|
+
if (!scope) {
|
|
280
313
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
281
314
|
return;
|
|
282
315
|
}
|
|
283
316
|
|
|
284
|
-
|
|
285
|
-
if (invalidAgents.length > 0) {
|
|
286
|
-
step(`Selection contains coming soon agents. Only Antigravity is currently supported.`, S.cross, "red");
|
|
287
|
-
stepLine();
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
break;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (!agents || agents.length === 0) {
|
|
295
|
-
console.log(`\n ${c.yellow("No agents selected.")}`);
|
|
296
|
-
fs.rmSync(tmp, { recursive: true, force: true });
|
|
297
|
-
return;
|
|
317
|
+
isGlobal = scope === "global";
|
|
298
318
|
}
|
|
299
319
|
|
|
300
|
-
// Agent summary
|
|
301
320
|
stepLine();
|
|
302
|
-
step("
|
|
303
|
-
console.log(`${c.gray(S.branch)} ${c.dim(
|
|
321
|
+
step("Installation scope");
|
|
322
|
+
console.log(`${c.gray(S.branch)} ${c.dim(isGlobal ? "Global" : "Project")}`);
|
|
304
323
|
|
|
305
|
-
//
|
|
324
|
+
// --- Select installation method ---
|
|
325
|
+
stepLine();
|
|
306
326
|
activeStep("Installation method");
|
|
327
|
+
const installMethod = await selectMethodPrompt();
|
|
307
328
|
|
|
308
|
-
|
|
309
|
-
message: " ",
|
|
310
|
-
options: [
|
|
311
|
-
{ label: "Symlink (Recommended)", value: "symlink", hint: "Single source of truth, easy updates" },
|
|
312
|
-
{ label: "Copy to all agents", value: "copy" }
|
|
313
|
-
],
|
|
314
|
-
initialValue: "symlink"
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
if (isCancel(installMethod)) {
|
|
318
|
-
cancel("Cancelled.");
|
|
329
|
+
if (!installMethod) {
|
|
319
330
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
320
331
|
return;
|
|
321
332
|
}
|
|
322
333
|
|
|
334
|
+
|
|
323
335
|
// Installation Summary Box
|
|
324
336
|
stepLine();
|
|
325
|
-
step("Installation
|
|
337
|
+
step("Installation method");
|
|
338
|
+
console.log(`${c.gray(S.branch)} ${c.dim(installMethod === "symlink" ? "Symlink" : "Copy")}`);
|
|
339
|
+
|
|
340
|
+
stepLine();
|
|
341
|
+
step("Installation Summary");
|
|
326
342
|
stepLine();
|
|
327
343
|
|
|
328
|
-
const
|
|
329
|
-
const agentsString = selectedAgentsList.map(a =>
|
|
330
|
-
a === "antigravity" ? "Antigravity" :
|
|
331
|
-
a.charAt(0).toUpperCase() + a.slice(1)
|
|
332
|
-
).join(", ");
|
|
344
|
+
const agentsString = selectedAgents.map(a => a.displayName).join(", ");
|
|
333
345
|
|
|
334
346
|
let summaryContent = "";
|
|
335
347
|
const methodVerb = installMethod === "symlink" ? "symlink" : "copy";
|
|
336
348
|
|
|
337
349
|
for (const sn of selectedSkills) {
|
|
338
|
-
|
|
339
|
-
const targetPath = path.relative(process.cwd(), path.join(targetScope, sn));
|
|
340
|
-
summaryContent += `${c.cyan(targetPath)}\n`;
|
|
350
|
+
summaryContent += `${c.cyan(sn)}\n`;
|
|
341
351
|
summaryContent += ` ${c.dim(methodVerb)} ${c.gray("→")} ${c.dim(agentsString)}\n\n`;
|
|
342
352
|
}
|
|
343
353
|
|
|
@@ -356,7 +366,6 @@ export async function run(spec) {
|
|
|
356
366
|
stepLine();
|
|
357
367
|
|
|
358
368
|
// Confirmation
|
|
359
|
-
// Active Step
|
|
360
369
|
activeStep("Proceed with installation?");
|
|
361
370
|
const shouldProceed = await confirm({ message: " ", initialValue: true });
|
|
362
371
|
|
|
@@ -366,29 +375,51 @@ export async function run(spec) {
|
|
|
366
375
|
return;
|
|
367
376
|
}
|
|
368
377
|
|
|
369
|
-
// Install
|
|
378
|
+
// Install skills to multiple agents
|
|
370
379
|
stepLine();
|
|
371
|
-
|
|
380
|
+
const { installSkillForAgents } = await import("../installer.js");
|
|
372
381
|
|
|
373
382
|
// Create a map for skill paths
|
|
374
383
|
const skillPathMap = Object.fromEntries(skillsInRepo.map(s => [s.value, s._path]));
|
|
375
384
|
|
|
385
|
+
const installResults = { success: [], failed: [] };
|
|
386
|
+
|
|
376
387
|
for (const sn of selectedSkills) {
|
|
377
388
|
const src = skillPathMap[sn] || path.join(skillsDir || tmp, sn);
|
|
378
|
-
const dest = path.join(targetScope, sn);
|
|
379
389
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
390
|
+
const is = spinner();
|
|
391
|
+
is.start(`Installing ${sn} to ${selectedAgents.length} agents`);
|
|
392
|
+
|
|
393
|
+
const result = await installSkillForAgents(src, sn, selectedAgents, {
|
|
394
|
+
method: installMethod,
|
|
395
|
+
scope: isGlobal ? "global" : "project",
|
|
396
|
+
metadata: {
|
|
397
|
+
repo: `${org}/${repo}`,
|
|
398
|
+
ref: ref || null
|
|
399
|
+
}
|
|
384
400
|
});
|
|
401
|
+
|
|
402
|
+
installResults.success.push(...result.success);
|
|
403
|
+
installResults.failed.push(...result.failed);
|
|
404
|
+
|
|
405
|
+
if (result.failed.length === 0) {
|
|
406
|
+
is.stop(`Installed ${sn} (${result.success.length} agents)`);
|
|
407
|
+
} else {
|
|
408
|
+
is.stop(`${sn}: ${result.success.length} success, ${result.failed.length} failed`);
|
|
409
|
+
}
|
|
385
410
|
}
|
|
386
411
|
|
|
412
|
+
|
|
413
|
+
// Derive base .agent directory from skillsDir
|
|
414
|
+
// If skillsDir is .../skills, then baseAgentDir is parent (.agent)
|
|
415
|
+
const baseAgentDir = skillsDir ? path.dirname(skillsDir) : path.join(tmp, ".agent");
|
|
416
|
+
|
|
387
417
|
// Install workflows if they exist
|
|
388
|
-
const workflowsDir = path.join(
|
|
418
|
+
const workflowsDir = path.join(baseAgentDir, "workflows");
|
|
389
419
|
const targetWorkflowsDir = path.join(WORKSPACE, "..", "workflows");
|
|
390
420
|
let workflowsInstalled = 0;
|
|
391
421
|
|
|
422
|
+
|
|
392
423
|
if (fs.existsSync(workflowsDir)) {
|
|
393
424
|
stepLine();
|
|
394
425
|
const ws = spinner();
|
|
@@ -411,7 +442,7 @@ export async function run(spec) {
|
|
|
411
442
|
}
|
|
412
443
|
|
|
413
444
|
// Install GEMINI.md if it exists
|
|
414
|
-
const geminiSrc = path.join(
|
|
445
|
+
const geminiSrc = path.join(baseAgentDir, "GEMINI.md");
|
|
415
446
|
const geminiDest = path.join(WORKSPACE, "..", "GEMINI.md");
|
|
416
447
|
let geminiInstalled = false;
|
|
417
448
|
|
|
@@ -423,7 +454,7 @@ export async function run(spec) {
|
|
|
423
454
|
}
|
|
424
455
|
|
|
425
456
|
// Install agents if they exist
|
|
426
|
-
const agentsDir = path.join(
|
|
457
|
+
const agentsDir = path.join(baseAgentDir, "agents");
|
|
427
458
|
const targetAgentsDir = path.join(WORKSPACE, "..", "agents");
|
|
428
459
|
let agentsInstalled = 0;
|
|
429
460
|
|
|
@@ -449,7 +480,7 @@ export async function run(spec) {
|
|
|
449
480
|
}
|
|
450
481
|
|
|
451
482
|
// Install ARCHITECTURE.md if it exists
|
|
452
|
-
const archSrc = path.join(
|
|
483
|
+
const archSrc = path.join(baseAgentDir, "ARCHITECTURE.md");
|
|
453
484
|
const archDest = path.join(WORKSPACE, "..", "ARCHITECTURE.md");
|
|
454
485
|
let archInstalled = false;
|
|
455
486
|
|
|
@@ -460,7 +491,7 @@ export async function run(spec) {
|
|
|
460
491
|
}
|
|
461
492
|
|
|
462
493
|
// Install knowledge if it exists
|
|
463
|
-
const knowledgeDir = path.join(
|
|
494
|
+
const knowledgeDir = path.join(baseAgentDir, "knowledge");
|
|
464
495
|
const targetKnowledgeDir = path.join(WORKSPACE, "..", "knowledge");
|
|
465
496
|
let knowledgeInstalled = false;
|
|
466
497
|
|
|
@@ -471,7 +502,7 @@ export async function run(spec) {
|
|
|
471
502
|
}
|
|
472
503
|
|
|
473
504
|
// Install rules if they exist
|
|
474
|
-
const rulesDir = path.join(
|
|
505
|
+
const rulesDir = path.join(baseAgentDir, "rules");
|
|
475
506
|
const targetRulesDir = path.join(WORKSPACE, "..", "rules");
|
|
476
507
|
let rulesInstalled = 0;
|
|
477
508
|
|
package/bin/lib/installer.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
3
4
|
import { GLOBAL_DIR } from "./config.js";
|
|
4
5
|
import { merkleHash } from "./helpers.js";
|
|
6
|
+
import { AGENTS } from "./agents.js";
|
|
7
|
+
|
|
8
|
+
const home = homedir();
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Install a skill to the destination using the specified method.
|
|
@@ -47,3 +51,114 @@ export async function installSkill(src, dest, method, metadata) {
|
|
|
47
51
|
method: method
|
|
48
52
|
}, null, 2));
|
|
49
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Install a skill to multiple agents
|
|
57
|
+
* @param {string} src - Source directory containing the skill
|
|
58
|
+
* @param {string} skillName - Name of the skill
|
|
59
|
+
* @param {Array<{name: string, displayName: string, skillsDir: string, globalSkillsDir: string}>} agents - Agents to install to
|
|
60
|
+
* @param {Object} options - Installation options
|
|
61
|
+
* @param {string} options.method - 'symlink' or 'copy'
|
|
62
|
+
* @param {string} options.scope - 'project' or 'global'
|
|
63
|
+
* @param {Object} options.metadata - Metadata for tracking
|
|
64
|
+
* @returns {Promise<{success: Array, failed: Array}>}
|
|
65
|
+
*/
|
|
66
|
+
export async function installSkillForAgents(src, skillName, agents, options = {}) {
|
|
67
|
+
const { method = "symlink", scope = "project", metadata = {} } = options;
|
|
68
|
+
const results = { success: [], failed: [] };
|
|
69
|
+
|
|
70
|
+
// For symlink mode: first copy to canonical location
|
|
71
|
+
let canonicalPath = null;
|
|
72
|
+
|
|
73
|
+
if (method === "symlink") {
|
|
74
|
+
// Canonical: .agents/skills/<skill-name> or ~/.agents/skills/<skill-name>
|
|
75
|
+
const baseDir = scope === "global" ? home : process.cwd();
|
|
76
|
+
canonicalPath = path.join(baseDir, ".agents", "skills", skillName);
|
|
77
|
+
|
|
78
|
+
// Ensure fresh copy in canonical
|
|
79
|
+
if (fs.existsSync(canonicalPath)) {
|
|
80
|
+
fs.rmSync(canonicalPath, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
fs.mkdirSync(path.dirname(canonicalPath), { recursive: true });
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await fs.promises.cp(src, canonicalPath, { recursive: true });
|
|
86
|
+
|
|
87
|
+
// Write metadata to canonical location
|
|
88
|
+
const hash = merkleHash(canonicalPath);
|
|
89
|
+
const metaFile = path.join(canonicalPath, ".skill-source.json");
|
|
90
|
+
fs.writeFileSync(metaFile, JSON.stringify({
|
|
91
|
+
...metadata,
|
|
92
|
+
skillName,
|
|
93
|
+
checksum: hash,
|
|
94
|
+
installedAt: new Date().toISOString(),
|
|
95
|
+
method: method,
|
|
96
|
+
scope: scope,
|
|
97
|
+
agents: agents.map(a => a.name)
|
|
98
|
+
}, null, 2));
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// If canonical copy fails, abort
|
|
101
|
+
return {
|
|
102
|
+
success: [],
|
|
103
|
+
failed: agents.map(a => ({ agent: a.displayName, error: `Canonical copy failed: ${err.message}` }))
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Install to each agent
|
|
109
|
+
for (const agent of agents) {
|
|
110
|
+
const agentConfig = AGENTS[agent.name];
|
|
111
|
+
if (!agentConfig) {
|
|
112
|
+
results.failed.push({ agent: agent.displayName, error: "Unknown agent" });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Determine destination path
|
|
117
|
+
const baseDir = scope === "global" ? agentConfig.globalSkillsDir : path.join(process.cwd(), agentConfig.skillsDir);
|
|
118
|
+
const destPath = path.join(baseDir, skillName);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// Ensure parent directory exists
|
|
122
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
123
|
+
|
|
124
|
+
// Remove existing if any
|
|
125
|
+
if (fs.existsSync(destPath)) {
|
|
126
|
+
fs.rmSync(destPath, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (method === "symlink" && canonicalPath) {
|
|
130
|
+
// Create symlink to canonical location
|
|
131
|
+
try {
|
|
132
|
+
fs.symlinkSync(canonicalPath, destPath, "junction");
|
|
133
|
+
results.success.push({ agent: agent.displayName, path: destPath, mode: "symlink" });
|
|
134
|
+
} catch (symlinkErr) {
|
|
135
|
+
// Fallback to copy if symlink fails (Windows permissions)
|
|
136
|
+
await fs.promises.cp(canonicalPath, destPath, { recursive: true });
|
|
137
|
+
results.success.push({ agent: agent.displayName, path: destPath, mode: "copy (symlink failed)" });
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// Direct copy
|
|
141
|
+
await fs.promises.cp(src, destPath, { recursive: true });
|
|
142
|
+
|
|
143
|
+
// Write metadata
|
|
144
|
+
const hash = merkleHash(destPath);
|
|
145
|
+
const metaFile = path.join(destPath, ".skill-source.json");
|
|
146
|
+
fs.writeFileSync(metaFile, JSON.stringify({
|
|
147
|
+
...metadata,
|
|
148
|
+
skillName,
|
|
149
|
+
checksum: hash,
|
|
150
|
+
installedAt: new Date().toISOString(),
|
|
151
|
+
method: "copy",
|
|
152
|
+
scope: scope
|
|
153
|
+
}, null, 2));
|
|
154
|
+
|
|
155
|
+
results.success.push({ agent: agent.displayName, path: destPath, mode: "copy" });
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
results.failed.push({ agent: agent.displayName, error: err.message });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
|
package/bin/lib/ui.js
CHANGED
|
@@ -6,9 +6,22 @@ import kleur from "kleur";
|
|
|
6
6
|
import boxen from "boxen";
|
|
7
7
|
import { intro, outro, multiselect, select, confirm, isCancel, cancel, text } from "@clack/prompts";
|
|
8
8
|
import ora from "ora";
|
|
9
|
+
import gradient from "gradient-string";
|
|
9
10
|
|
|
10
11
|
export { intro, outro, multiselect, select, confirm, isCancel, cancel, text };
|
|
11
12
|
|
|
13
|
+
// --- ASCII Art Banner ---
|
|
14
|
+
const PIKAKIT_BANNER = `
|
|
15
|
+
____ _ _ _ ___ _
|
|
16
|
+
| _ \\(_) | ____ _| |/ (_) |_
|
|
17
|
+
| |_) | | |/ / _\` | ' /| | __|
|
|
18
|
+
| __/| | < (_| | . \\| | |_
|
|
19
|
+
|_| |_|_|\\_\\__,_|_|\\_\\_|\\__|
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
// Custom gradient: white → gray (like vercel style)
|
|
23
|
+
const pikaGradient = gradient(['#ffffff', '#bbbbbb', '#888888', '#555555']);
|
|
24
|
+
|
|
12
25
|
/**
|
|
13
26
|
* Create a spinner
|
|
14
27
|
*/
|
|
@@ -148,15 +161,169 @@ export function box(message, options = {}) {
|
|
|
148
161
|
}
|
|
149
162
|
|
|
150
163
|
/**
|
|
151
|
-
* Show branded intro with version
|
|
164
|
+
* Show branded intro with version (matches agent CLI style)
|
|
152
165
|
* @param {string} version - Package version
|
|
153
166
|
* @param {string} [status] - Optional status text
|
|
154
167
|
*/
|
|
155
168
|
export function brandedIntro(version, status = "") {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
)
|
|
169
|
+
// Split banner and filter to get content lines only
|
|
170
|
+
const bannerLines = PIKAKIT_BANNER.split('\n').filter(line => line.trim() !== '');
|
|
171
|
+
|
|
172
|
+
// Print all lines except the last with gradient
|
|
173
|
+
for (let i = 0; i < bannerLines.length - 1; i++) {
|
|
174
|
+
console.log(pikaGradient(bannerLines[i]));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Last line: gradient ASCII + dim version (aligned at bottom)
|
|
178
|
+
const lastLine = bannerLines[bannerLines.length - 1];
|
|
179
|
+
console.log(pikaGradient(lastLine) + ` ${c.dim(`v${version}`)}`);
|
|
180
|
+
console.log(''); // Empty line after banner
|
|
181
|
+
|
|
182
|
+
if (status) {
|
|
183
|
+
console.log(`${c.dim(status)}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// --- Vercel-Style Installation Prompts ---
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Prompt user to select which agents to install to
|
|
191
|
+
* @param {Array<{name: string, displayName: string, skillsDir: string}>} detectedAgents
|
|
192
|
+
* @returns {Promise<Array<{name: string, displayName: string, skillsDir: string}> | null>}
|
|
193
|
+
*/
|
|
194
|
+
export async function selectAgentsPrompt(detectedAgents) {
|
|
195
|
+
if (detectedAgents.length === 0) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// First ask: All detected or select specific?
|
|
200
|
+
const installChoice = await select({
|
|
201
|
+
message: "Install to",
|
|
202
|
+
options: [
|
|
203
|
+
{
|
|
204
|
+
value: "all",
|
|
205
|
+
label: `All detected agents (Recommended)`,
|
|
206
|
+
hint: `Install to all ${detectedAgents.length} detected agents`
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
value: "select",
|
|
210
|
+
label: "Select specific agents",
|
|
211
|
+
hint: "Choose which agents to install to"
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (isCancel(installChoice)) {
|
|
217
|
+
cancel("Installation cancelled");
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (installChoice === "all") {
|
|
222
|
+
return detectedAgents;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Let user select specific agents
|
|
226
|
+
const selectedAgents = await multiselect({
|
|
227
|
+
message: "Select agents to install skills to",
|
|
228
|
+
options: detectedAgents.map(agent => ({
|
|
229
|
+
value: agent.name,
|
|
230
|
+
label: agent.displayName,
|
|
231
|
+
hint: agent.skillsDir
|
|
232
|
+
})),
|
|
233
|
+
required: true
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (isCancel(selectedAgents)) {
|
|
237
|
+
cancel("Installation cancelled");
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return detectedAgents.filter(a => selectedAgents.includes(a.name));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Prompt user to select installation scope (Project or Global)
|
|
246
|
+
* @returns {Promise<"project" | "global" | null>}
|
|
247
|
+
*/
|
|
248
|
+
export async function selectScopePrompt() {
|
|
249
|
+
const scope = await select({
|
|
250
|
+
message: "Installation scope",
|
|
251
|
+
options: [
|
|
252
|
+
{
|
|
253
|
+
value: "project",
|
|
254
|
+
label: "Project",
|
|
255
|
+
hint: "Install in current directory (committed with your project)"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
value: "global",
|
|
259
|
+
label: "Global",
|
|
260
|
+
hint: "Install globally (available across all projects)"
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (isCancel(scope)) {
|
|
266
|
+
cancel("Installation cancelled");
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return scope;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Prompt user to select installation method (Symlink or Copy)
|
|
275
|
+
* @returns {Promise<"symlink" | "copy" | null>}
|
|
276
|
+
*/
|
|
277
|
+
export async function selectMethodPrompt() {
|
|
278
|
+
const method = await select({
|
|
279
|
+
message: "Installation method",
|
|
280
|
+
options: [
|
|
281
|
+
{
|
|
282
|
+
value: "symlink",
|
|
283
|
+
label: "Symlink (Recommended)",
|
|
284
|
+
hint: "Single source of truth, easy updates"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
value: "copy",
|
|
288
|
+
label: "Copy to all agents",
|
|
289
|
+
hint: "Independent copies for each agent"
|
|
290
|
+
}
|
|
291
|
+
]
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (isCancel(method)) {
|
|
295
|
+
cancel("Installation cancelled");
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return method;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Prompt user to select skills to install (multiselect with descriptions)
|
|
304
|
+
* @param {Array<{name: string, description: string, path: string}>} skills
|
|
305
|
+
* @returns {Promise<Array<{name: string, description: string, path: string}> | null>}
|
|
306
|
+
*/
|
|
307
|
+
export async function selectSkillsPrompt(skills) {
|
|
308
|
+
if (skills.length === 0) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const selectedNames = await multiselect({
|
|
313
|
+
message: "Select skills to install",
|
|
314
|
+
options: skills.map(skill => ({
|
|
315
|
+
value: skill.name,
|
|
316
|
+
label: skill.name,
|
|
317
|
+
hint: skill.description ? skill.description.substring(0, 60) + "..." : ""
|
|
318
|
+
})),
|
|
319
|
+
required: true
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
if (isCancel(selectedNames)) {
|
|
323
|
+
cancel("Installation cancelled");
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return skills.filter(s => selectedNames.includes(s.name));
|
|
161
328
|
}
|
|
162
329
|
|
package/package.json
CHANGED
|
@@ -1,64 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "add-skill-kit",
|
|
3
|
-
"version": "3.2.
|
|
4
|
-
"description": "Enterprise-grade Agent Skill Manager with Antigravity Skills support, Progressive Disclosure detection, and semantic routing validation",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "agentskillkit <agentskillkit@gmail.com>",
|
|
7
|
-
"homepage": "https://github.com/agentskillkit/add-skill-kit",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "https://github.com/agentskillkit/add-skill-kit.git"
|
|
11
|
-
},
|
|
12
|
-
"bugs": {
|
|
13
|
-
"url": "https://github.com/agentskillkit/add-skill-kit/issues"
|
|
14
|
-
},
|
|
15
|
-
"type": "module",
|
|
16
|
-
"bin": {
|
|
17
|
-
"kit": "./bin/cli.js"
|
|
18
|
-
},
|
|
19
|
-
"files": [
|
|
20
|
-
"bin/",
|
|
21
|
-
"specs/",
|
|
22
|
-
"README.md",
|
|
23
|
-
"LICENSE"
|
|
24
|
-
],
|
|
25
|
-
"engines": {
|
|
26
|
-
"node": ">=18.0.0"
|
|
27
|
-
},
|
|
28
|
-
"keywords": [
|
|
29
|
-
"agent",
|
|
30
|
-
"ai",
|
|
31
|
-
"skills",
|
|
32
|
-
"cli",
|
|
33
|
-
"tooling",
|
|
34
|
-
"registry",
|
|
35
|
-
"security",
|
|
36
|
-
"devops",
|
|
37
|
-
"automation",
|
|
38
|
-
"antigravity"
|
|
39
|
-
],
|
|
40
|
-
"scripts": {
|
|
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"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "add-skill-kit",
|
|
3
|
+
"version": "3.2.2",
|
|
4
|
+
"description": "Enterprise-grade Agent Skill Manager with Antigravity Skills support, Progressive Disclosure detection, and semantic routing validation",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "agentskillkit <agentskillkit@gmail.com>",
|
|
7
|
+
"homepage": "https://github.com/agentskillkit/add-skill-kit",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/agentskillkit/add-skill-kit.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/agentskillkit/add-skill-kit/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"kit": "./bin/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin/",
|
|
21
|
+
"specs/",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"agent",
|
|
30
|
+
"ai",
|
|
31
|
+
"skills",
|
|
32
|
+
"cli",
|
|
33
|
+
"tooling",
|
|
34
|
+
"registry",
|
|
35
|
+
"security",
|
|
36
|
+
"devops",
|
|
37
|
+
"automation",
|
|
38
|
+
"antigravity"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
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",
|
|
47
|
+
"agent": "agent"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@clack/prompts": "^0.9.1",
|
|
54
|
+
"boxen": "^8.0.1",
|
|
55
|
+
"chalk": "^5.4.1",
|
|
56
|
+
"gradient-string": "^2.0.2",
|
|
57
|
+
"kleur": "^4.1.5",
|
|
58
|
+
"ora": "^8.1.0",
|
|
59
|
+
"prompts": "^2.4.2"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"agentskillskit-cli": "^3.2.0",
|
|
63
|
+
"eslint": "^8.57.0",
|
|
64
|
+
"prettier": "^3.2.5",
|
|
65
|
+
"vitest": "^1.6.0"
|
|
66
|
+
}
|
|
67
|
+
}
|