@agentskillkit/agent-skills 3.2.2 → 3.2.5
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/.agent/skills/mobile-design/scripts/mobile_audit.js +333 -0
- package/.agent/skills/typescript-expert/scripts/ts_diagnostic.js +227 -0
- package/README.md +197 -720
- package/package.json +4 -4
- package/packages/cli/lib/audit.js +2 -2
- package/packages/cli/lib/auto-learn.js +8 -8
- package/packages/cli/lib/eslint-fix.js +1 -1
- package/packages/cli/lib/fix.js +5 -5
- package/packages/cli/lib/hooks/install-hooks.js +4 -4
- package/packages/cli/lib/hooks/lint-learn.js +4 -4
- package/packages/cli/lib/knowledge-index.js +4 -4
- package/packages/cli/lib/knowledge-metrics.js +2 -2
- package/packages/cli/lib/knowledge-retention.js +3 -3
- package/packages/cli/lib/knowledge-validator.js +3 -3
- package/packages/cli/lib/learn.js +10 -10
- package/packages/cli/lib/recall.js +1 -1
- package/packages/cli/lib/skill-learn.js +2 -2
- package/packages/cli/lib/stats.js +3 -3
- package/packages/cli/lib/ui/dashboard-ui.js +222 -0
- package/packages/cli/lib/ui/help-ui.js +41 -18
- package/packages/cli/lib/ui/index.js +57 -5
- package/packages/cli/lib/ui/settings-ui.js +292 -14
- package/packages/cli/lib/ui/stats-ui.js +93 -43
- package/packages/cli/lib/watcher.js +2 -2
- package/packages/kit/kit.js +89 -0
- package/packages/kit/lib/agents.js +208 -0
- package/packages/kit/lib/commands/analyze.js +70 -0
- package/packages/kit/lib/commands/cache.js +65 -0
- package/packages/kit/lib/commands/doctor.js +75 -0
- package/packages/kit/lib/commands/help.js +155 -0
- package/packages/kit/lib/commands/info.js +38 -0
- package/packages/kit/lib/commands/init.js +39 -0
- package/packages/kit/lib/commands/install.js +803 -0
- package/packages/kit/lib/commands/list.js +43 -0
- package/packages/kit/lib/commands/lock.js +57 -0
- package/packages/kit/lib/commands/uninstall.js +307 -0
- package/packages/kit/lib/commands/update.js +55 -0
- package/packages/kit/lib/commands/validate.js +69 -0
- package/packages/kit/lib/commands/verify.js +56 -0
- package/packages/kit/lib/config.js +81 -0
- package/packages/kit/lib/helpers.js +196 -0
- package/packages/kit/lib/helpers.test.js +60 -0
- package/packages/kit/lib/installer.js +164 -0
- package/packages/kit/lib/skills.js +119 -0
- package/packages/kit/lib/skills.test.js +109 -0
- package/packages/kit/lib/types.js +82 -0
- package/packages/kit/lib/ui.js +329 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +0 -670
- package/.agent/skills/requirements-python.txt +0 -25
- package/.agent/skills/requirements.txt +0 -96
- package/.agent/skills/typescript-expert/scripts/ts_diagnostic.py +0 -203
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Install command - Interactive skill installation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import { exec } from "child_process";
|
|
9
|
+
import util from "util";
|
|
10
|
+
const execAsync = util.promisify(exec);
|
|
11
|
+
import boxen from "boxen";
|
|
12
|
+
import { parseSkillSpec, merkleHash } from "../helpers.js";
|
|
13
|
+
import { parseSkillMdFrontmatter } from "../skills.js";
|
|
14
|
+
import { step, activeStep, stepLine, S, c, fatal, spinner, multiselect, select, confirm, isCancel, cancel } from "../ui.js";
|
|
15
|
+
import { WORKSPACE, GLOBAL_DIR, OFFLINE, GLOBAL } from "../config.js";
|
|
16
|
+
import { installSkill } from "../installer.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Install skills from repository
|
|
20
|
+
* @param {string} spec - Skill spec (org/repo or org/repo#skill)
|
|
21
|
+
*/
|
|
22
|
+
export async function run(spec) {
|
|
23
|
+
if (!spec) {
|
|
24
|
+
fatal("Missing skill spec. Usage: add-skill <org/repo>");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { org, repo, skill: singleSkill, ref } = parseSkillSpec(spec);
|
|
29
|
+
|
|
30
|
+
if (!org || !repo) {
|
|
31
|
+
fatal("Invalid spec. Format: org/repo or org/repo#skill");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
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
|
+
|
|
44
|
+
const url = `https://github.com/${org}/${repo}.git`;
|
|
45
|
+
|
|
46
|
+
stepLine();
|
|
47
|
+
step("Source: " + c.cyan(url));
|
|
48
|
+
|
|
49
|
+
const s = spinner();
|
|
50
|
+
s.start("Cloning repository");
|
|
51
|
+
|
|
52
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "add-skill-"));
|
|
53
|
+
|
|
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
|
+
|
|
98
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
s.stop("Repository cloned");
|
|
103
|
+
|
|
104
|
+
// Find skills in repo - check multiple possible locations
|
|
105
|
+
const skillsInRepo = [];
|
|
106
|
+
|
|
107
|
+
// Possible skill locations (in order of priority)
|
|
108
|
+
const possibleSkillDirs = [
|
|
109
|
+
path.join(tmp, ".agent", "skills"), // Standard location
|
|
110
|
+
path.join(tmp, "skills"), // Alternative location
|
|
111
|
+
tmp // Root level (legacy)
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
let skillsDir = null;
|
|
115
|
+
for (const dir of possibleSkillDirs) {
|
|
116
|
+
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
|
|
117
|
+
// Check if this directory contains skill folders
|
|
118
|
+
const entries = fs.readdirSync(dir);
|
|
119
|
+
for (const e of entries) {
|
|
120
|
+
const sp = path.join(dir, e);
|
|
121
|
+
if (fs.statSync(sp).isDirectory() && fs.existsSync(path.join(sp, "SKILL.md"))) {
|
|
122
|
+
skillsDir = dir;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (skillsDir) break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (skillsDir) {
|
|
131
|
+
for (const e of fs.readdirSync(skillsDir)) {
|
|
132
|
+
const sp = path.join(skillsDir, e);
|
|
133
|
+
if (fs.statSync(sp).isDirectory()) {
|
|
134
|
+
// Check if this directory has SKILL.md (top-level skill only)
|
|
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
|
+
// NOTE: Sub-folders are NOT counted as separate skills
|
|
146
|
+
// They are part of the parent skill (e.g. game-development/2d-games)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (skillsInRepo.length === 0) {
|
|
152
|
+
step(c.yellow("No valid skills found"), S.diamond, "yellow");
|
|
153
|
+
step(c.dim("Expected skills in .agent/skills/ or skills/ directory"), S.branch, "gray");
|
|
154
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
stepLine();
|
|
159
|
+
step(`Found ${skillsInRepo.length} skill${skillsInRepo.length > 1 ? "s" : ""}`);
|
|
160
|
+
|
|
161
|
+
let selectedSkills = [];
|
|
162
|
+
|
|
163
|
+
// If single skill specified via #skill_name, auto-select it
|
|
164
|
+
if (singleSkill) {
|
|
165
|
+
const found = skillsInRepo.find(s => s.value === singleSkill);
|
|
166
|
+
if (!found) {
|
|
167
|
+
stepLine();
|
|
168
|
+
step(c.red(`Skill '${singleSkill}' not found in repository`), S.cross, "red");
|
|
169
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
selectedSkills = [singleSkill];
|
|
173
|
+
stepLine();
|
|
174
|
+
step(`Auto-selected: ${c.cyan(singleSkill)}`);
|
|
175
|
+
} else {
|
|
176
|
+
// FAANG-Grade Categories (8 balanced categories)
|
|
177
|
+
// NOTE: Order matters! Specialized categories FIRST, Core is fallback
|
|
178
|
+
const CATEGORY_KEYWORDS = {
|
|
179
|
+
// Specialized domains
|
|
180
|
+
"🎨 Frontend & UI": [
|
|
181
|
+
"frontend", "nextjs", "tailwind", "css", "ui", "ux", "visual",
|
|
182
|
+
"studio", "web-core", "design-system", "react-architect", "react"
|
|
183
|
+
],
|
|
184
|
+
"🎮 Game Development": [
|
|
185
|
+
"game", "development", "engine", "unity", "unreal", "godot", "phaser"
|
|
186
|
+
],
|
|
187
|
+
"📱 Mobile": [
|
|
188
|
+
"mobile", "first", "developer", "react-native", "flutter",
|
|
189
|
+
"ios", "android", "swift", "kotlin"
|
|
190
|
+
],
|
|
191
|
+
"🔒 Security & DevOps": [
|
|
192
|
+
"security", "vulnerability", "offensive", "scanner", "red-team", "governance",
|
|
193
|
+
"cicd", "pipeline", "gitops", "docker", "deploy", "server-ops"
|
|
194
|
+
],
|
|
195
|
+
// Technical domains
|
|
196
|
+
"🧪 Testing & Quality": [
|
|
197
|
+
"test", "testing", "tdd", "e2e", "debug", "quality", "review",
|
|
198
|
+
"lint", "validate", "automation", "problem", "checker"
|
|
199
|
+
],
|
|
200
|
+
"🤖 AI & Agents": [
|
|
201
|
+
"agent", "pattern", "auto-learn", "execution", "self-evolution",
|
|
202
|
+
"lifecycle", "skill-forge", "intelligent", "routing"
|
|
203
|
+
],
|
|
204
|
+
"📚 Docs & Planning": [
|
|
205
|
+
"doc", "template", "plan", "project", "idea", "brainstorm",
|
|
206
|
+
"geo", "seo", "i18n", "writing"
|
|
207
|
+
],
|
|
208
|
+
// Fallback (core backend/infra)
|
|
209
|
+
"⚙️ Backend & Core": [
|
|
210
|
+
"backend", "api", "nodejs", "python", "server", "database",
|
|
211
|
+
"prisma", "mcp", "data", "architect", "scaffold", "system",
|
|
212
|
+
"typescript", "shell", "bash", "powershell", "git", "code-craft",
|
|
213
|
+
"code-constitution", "observability", "perf", "state", "rollback"
|
|
214
|
+
]
|
|
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 & Core"; // Default fallback (no "Other" category)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// REQUIRED SKILLS - Always installed, not shown in selection
|
|
228
|
+
const REQUIRED_SKILLS = ["auto-learner"];
|
|
229
|
+
|
|
230
|
+
// Filter out required skills from selection list
|
|
231
|
+
const selectableSkills = skillsInRepo.filter(s => !REQUIRED_SKILLS.includes(s.value));
|
|
232
|
+
|
|
233
|
+
// Group skills by category
|
|
234
|
+
const grouped = {};
|
|
235
|
+
for (const skill of selectableSkills) {
|
|
236
|
+
const cat = categorizeSkill(skill.value);
|
|
237
|
+
if (!grouped[cat]) grouped[cat] = [];
|
|
238
|
+
grouped[cat].push(skill);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Custom sort: alphabetical but "Other" always last
|
|
242
|
+
const sortedCategories = Object.keys(grouped).sort((a, b) => {
|
|
243
|
+
if (a.includes("Other")) return 1;
|
|
244
|
+
if (b.includes("Other")) return -1;
|
|
245
|
+
return a.localeCompare(b);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
stepLine();
|
|
250
|
+
activeStep("Select skill categories to install");
|
|
251
|
+
|
|
252
|
+
// Show only categories, not individual skills
|
|
253
|
+
const selectedCategories = await multiselect({
|
|
254
|
+
message: `${c.cyan("space")} select · ${c.cyan("enter")} confirm`,
|
|
255
|
+
options: sortedCategories.map(cat => ({
|
|
256
|
+
label: `${cat} (${grouped[cat].length} skills)`,
|
|
257
|
+
value: cat,
|
|
258
|
+
hint: grouped[cat].slice(0, 3).map(s => s.value).join(", ") + (grouped[cat].length > 3 ? "..." : "")
|
|
259
|
+
})),
|
|
260
|
+
initialValues: sortedCategories, // Pre-select all
|
|
261
|
+
required: true
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (isCancel(selectedCategories)) {
|
|
265
|
+
cancel("Cancelled.");
|
|
266
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Get all skills from selected categories
|
|
271
|
+
selectedSkills = selectedCategories.flatMap(cat => grouped[cat].map(s => s.value));
|
|
272
|
+
|
|
273
|
+
// Add required system skills
|
|
274
|
+
const requiredInRepo = skillsInRepo.filter(s => REQUIRED_SKILLS.includes(s.value)).map(s => s.value);
|
|
275
|
+
selectedSkills = [...new Set([...selectedSkills, ...requiredInRepo])];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Check for required skills and show info
|
|
279
|
+
const CORE_REQUIRED = ["auto-learner"];
|
|
280
|
+
const installedRequired = selectedSkills.filter(s => CORE_REQUIRED.includes(s));
|
|
281
|
+
|
|
282
|
+
stepLine();
|
|
283
|
+
step("Select skills to install");
|
|
284
|
+
console.log(`${c.gray(S.branch)} ${c.dim(selectedSkills.filter(s => !CORE_REQUIRED.includes(s)).join(", "))}`);
|
|
285
|
+
if (installedRequired.length > 0) {
|
|
286
|
+
console.log(`${c.gray(S.branch)} ${c.cyan("+ System required:")} ${c.green(installedRequired.join(", "))}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// --- Detect installed agents ---
|
|
290
|
+
stepLine();
|
|
291
|
+
const { detectInstalledAgents } = await import("../agents.js");
|
|
292
|
+
const detectedAgents = detectInstalledAgents();
|
|
293
|
+
|
|
294
|
+
if (detectedAgents.length === 0) {
|
|
295
|
+
step(c.yellow("No agents detected"), S.diamond, "yellow");
|
|
296
|
+
step(c.dim("Please install at least one AI agent (Antigravity, Claude Code, etc.)"), S.branch, "gray");
|
|
297
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
step(`Detected ${detectedAgents.length} agents`);
|
|
302
|
+
|
|
303
|
+
// --- Select agents (Vercel-style) ---
|
|
304
|
+
const { selectAgentsPrompt, selectScopePrompt, selectMethodPrompt } = await import("../ui.js");
|
|
305
|
+
|
|
306
|
+
stepLine();
|
|
307
|
+
activeStep("Install to");
|
|
308
|
+
const selectedAgents = await selectAgentsPrompt(detectedAgents);
|
|
309
|
+
|
|
310
|
+
if (!selectedAgents || selectedAgents.length === 0) {
|
|
311
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
stepLine();
|
|
316
|
+
step("Install to");
|
|
317
|
+
console.log(`${c.gray(S.branch)} ${c.dim(selectedAgents.map(a => a.displayName).join(", "))}`);
|
|
318
|
+
|
|
319
|
+
// --- Select installation scope ---
|
|
320
|
+
let isGlobal = GLOBAL;
|
|
321
|
+
|
|
322
|
+
if (!GLOBAL) {
|
|
323
|
+
stepLine();
|
|
324
|
+
activeStep("Installation scope");
|
|
325
|
+
const scope = await selectScopePrompt();
|
|
326
|
+
|
|
327
|
+
if (!scope) {
|
|
328
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
isGlobal = scope === "global";
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
stepLine();
|
|
336
|
+
step("Installation scope");
|
|
337
|
+
console.log(`${c.gray(S.branch)} ${c.dim(isGlobal ? "Global" : "Project")}`);
|
|
338
|
+
|
|
339
|
+
// --- Select installation method ---
|
|
340
|
+
stepLine();
|
|
341
|
+
activeStep("Installation method");
|
|
342
|
+
const installMethod = await selectMethodPrompt();
|
|
343
|
+
|
|
344
|
+
if (!installMethod) {
|
|
345
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
// Installation Summary Box
|
|
351
|
+
stepLine();
|
|
352
|
+
step("Installation method");
|
|
353
|
+
console.log(`${c.gray(S.branch)} ${c.dim(installMethod === "symlink" ? "Symlink" : "Copy")}`);
|
|
354
|
+
|
|
355
|
+
stepLine();
|
|
356
|
+
step("Installation Summary");
|
|
357
|
+
stepLine();
|
|
358
|
+
|
|
359
|
+
const agentsString = selectedAgents.map(a => a.displayName).join(", ");
|
|
360
|
+
|
|
361
|
+
let summaryContent = "";
|
|
362
|
+
const methodVerb = installMethod === "symlink" ? "symlink" : "copy";
|
|
363
|
+
|
|
364
|
+
for (const sn of selectedSkills) {
|
|
365
|
+
summaryContent += `${c.cyan(sn)}\n`;
|
|
366
|
+
summaryContent += ` ${c.dim(methodVerb)} ${c.gray("→")} ${c.dim(agentsString)}\n\n`;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Remove trailing newlines
|
|
370
|
+
summaryContent = summaryContent.trim();
|
|
371
|
+
|
|
372
|
+
console.log(boxen(summaryContent, {
|
|
373
|
+
padding: 1,
|
|
374
|
+
borderColor: "gray",
|
|
375
|
+
borderStyle: "round",
|
|
376
|
+
dimBorder: true,
|
|
377
|
+
title: "Installation Summary",
|
|
378
|
+
titleAlignment: "left"
|
|
379
|
+
}).split("\n").map(l => `${c.gray(S.branch)} ${l}`).join("\n"));
|
|
380
|
+
|
|
381
|
+
stepLine();
|
|
382
|
+
|
|
383
|
+
// Confirmation
|
|
384
|
+
activeStep("Proceed with installation?");
|
|
385
|
+
const shouldProceed = await confirm({ message: " ", initialValue: true });
|
|
386
|
+
|
|
387
|
+
if (isCancel(shouldProceed) || !shouldProceed) {
|
|
388
|
+
cancel("Cancelled.");
|
|
389
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Install skills to multiple agents
|
|
394
|
+
stepLine();
|
|
395
|
+
const { installSkillForAgents } = await import("../installer.js");
|
|
396
|
+
|
|
397
|
+
// Create a map for skill paths
|
|
398
|
+
const skillPathMap = Object.fromEntries(skillsInRepo.map(s => [s.value, s._path]));
|
|
399
|
+
|
|
400
|
+
const installResults = { success: [], failed: [] };
|
|
401
|
+
|
|
402
|
+
for (const sn of selectedSkills) {
|
|
403
|
+
const src = skillPathMap[sn] || path.join(skillsDir || tmp, sn);
|
|
404
|
+
|
|
405
|
+
const is = spinner();
|
|
406
|
+
is.start(`Installing ${sn} to ${selectedAgents.length} agents`);
|
|
407
|
+
|
|
408
|
+
const result = await installSkillForAgents(src, sn, selectedAgents, {
|
|
409
|
+
method: installMethod,
|
|
410
|
+
scope: isGlobal ? "global" : "project",
|
|
411
|
+
metadata: {
|
|
412
|
+
repo: `${org}/${repo}`,
|
|
413
|
+
ref: ref || null
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
installResults.success.push(...result.success);
|
|
418
|
+
installResults.failed.push(...result.failed);
|
|
419
|
+
|
|
420
|
+
if (result.failed.length === 0) {
|
|
421
|
+
is.stop(`Installed ${sn} (${result.success.length} agents)`);
|
|
422
|
+
} else {
|
|
423
|
+
is.stop(`${sn}: ${result.success.length} success, ${result.failed.length} failed`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
// Derive base .agent directory from skillsDir
|
|
429
|
+
// If skillsDir is .../skills, then baseAgentDir is parent (.agent)
|
|
430
|
+
const baseAgentDir = skillsDir ? path.dirname(skillsDir) : path.join(tmp, ".agent");
|
|
431
|
+
|
|
432
|
+
// Install workflows if they exist
|
|
433
|
+
const workflowsDir = path.join(baseAgentDir, "workflows");
|
|
434
|
+
const targetWorkflowsDir = path.join(WORKSPACE, "..", "workflows");
|
|
435
|
+
let workflowsInstalled = 0;
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
if (fs.existsSync(workflowsDir)) {
|
|
439
|
+
stepLine();
|
|
440
|
+
const ws = spinner();
|
|
441
|
+
ws.start("Installing workflows");
|
|
442
|
+
|
|
443
|
+
fs.mkdirSync(targetWorkflowsDir, { recursive: true });
|
|
444
|
+
const workflows = fs.readdirSync(workflowsDir).filter(f => f.endsWith(".md"));
|
|
445
|
+
|
|
446
|
+
for (const wf of workflows) {
|
|
447
|
+
const src = path.join(workflowsDir, wf);
|
|
448
|
+
const dest = path.join(targetWorkflowsDir, wf);
|
|
449
|
+
|
|
450
|
+
if (!fs.existsSync(dest)) {
|
|
451
|
+
fs.copyFileSync(src, dest);
|
|
452
|
+
workflowsInstalled++;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
ws.stop(`Installed ${workflowsInstalled} workflows`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Install GEMINI.md if it exists
|
|
460
|
+
const geminiSrc = path.join(baseAgentDir, "GEMINI.md");
|
|
461
|
+
const geminiDest = path.join(WORKSPACE, "..", "GEMINI.md");
|
|
462
|
+
let geminiInstalled = false;
|
|
463
|
+
|
|
464
|
+
if (fs.existsSync(geminiSrc) && !fs.existsSync(geminiDest)) {
|
|
465
|
+
stepLine();
|
|
466
|
+
fs.copyFileSync(geminiSrc, geminiDest);
|
|
467
|
+
step("Installed GEMINI.md (Agent Rules)");
|
|
468
|
+
geminiInstalled = true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Install agents if they exist
|
|
472
|
+
const agentsDir = path.join(baseAgentDir, "agents");
|
|
473
|
+
const targetAgentsDir = path.join(WORKSPACE, "..", "agents");
|
|
474
|
+
let agentsInstalled = 0;
|
|
475
|
+
|
|
476
|
+
if (fs.existsSync(agentsDir)) {
|
|
477
|
+
stepLine();
|
|
478
|
+
const as = spinner();
|
|
479
|
+
as.start("Installing agents");
|
|
480
|
+
|
|
481
|
+
fs.mkdirSync(targetAgentsDir, { recursive: true });
|
|
482
|
+
const agents = fs.readdirSync(agentsDir).filter(f => f.endsWith(".md"));
|
|
483
|
+
|
|
484
|
+
for (const agent of agents) {
|
|
485
|
+
const src = path.join(agentsDir, agent);
|
|
486
|
+
const dest = path.join(targetAgentsDir, agent);
|
|
487
|
+
|
|
488
|
+
if (!fs.existsSync(dest)) {
|
|
489
|
+
fs.copyFileSync(src, dest);
|
|
490
|
+
agentsInstalled++;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
as.stop(`Installed ${agentsInstalled} agents`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Install ARCHITECTURE.md if it exists
|
|
498
|
+
const archSrc = path.join(baseAgentDir, "ARCHITECTURE.md");
|
|
499
|
+
const archDest = path.join(WORKSPACE, "..", "ARCHITECTURE.md");
|
|
500
|
+
let archInstalled = false;
|
|
501
|
+
|
|
502
|
+
if (fs.existsSync(archSrc) && !fs.existsSync(archDest)) {
|
|
503
|
+
fs.copyFileSync(archSrc, archDest);
|
|
504
|
+
step("Installed ARCHITECTURE.md");
|
|
505
|
+
archInstalled = true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Install knowledge if it exists (required for Agent CLI)
|
|
509
|
+
const knowledgeDir = path.join(baseAgentDir, "knowledge");
|
|
510
|
+
const targetKnowledgeDir = path.join(WORKSPACE, "..", "knowledge");
|
|
511
|
+
let knowledgeInstalled = false;
|
|
512
|
+
|
|
513
|
+
if (fs.existsSync(knowledgeDir) && !fs.existsSync(targetKnowledgeDir)) {
|
|
514
|
+
fs.cpSync(knowledgeDir, targetKnowledgeDir, { recursive: true });
|
|
515
|
+
step("Installed knowledge/");
|
|
516
|
+
knowledgeInstalled = true;
|
|
517
|
+
} else if (!fs.existsSync(targetKnowledgeDir)) {
|
|
518
|
+
// Create empty knowledge folder for Agent CLI
|
|
519
|
+
fs.mkdirSync(targetKnowledgeDir, { recursive: true });
|
|
520
|
+
// Create minimal structure for Agent CLI
|
|
521
|
+
fs.writeFileSync(path.join(targetKnowledgeDir, "lessons-learned.yaml"), "# Lessons learned by AI Agent\nlessons: []\n");
|
|
522
|
+
step("Created knowledge/ (Agent CLI ready)");
|
|
523
|
+
knowledgeInstalled = true;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Install config/ if it exists (required for skill configuration)
|
|
527
|
+
const configDir = path.join(baseAgentDir, "config");
|
|
528
|
+
const targetConfigDir = path.join(WORKSPACE, "..", "config");
|
|
529
|
+
let configInstalled = false;
|
|
530
|
+
|
|
531
|
+
if (fs.existsSync(configDir) && !fs.existsSync(targetConfigDir)) {
|
|
532
|
+
fs.cpSync(configDir, targetConfigDir, { recursive: true });
|
|
533
|
+
step("Installed config/");
|
|
534
|
+
configInstalled = true;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Install scripts-js/ if it exists (required for skill operations)
|
|
538
|
+
const scriptsJsDir = path.join(baseAgentDir, "scripts-js");
|
|
539
|
+
const targetScriptsJsDir = path.join(WORKSPACE, "..", "scripts-js");
|
|
540
|
+
let scriptsJsInstalled = false;
|
|
541
|
+
|
|
542
|
+
if (fs.existsSync(scriptsJsDir) && !fs.existsSync(targetScriptsJsDir)) {
|
|
543
|
+
fs.cpSync(scriptsJsDir, targetScriptsJsDir, { recursive: true });
|
|
544
|
+
step("Installed scripts-js/");
|
|
545
|
+
scriptsJsInstalled = true;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Install metrics/ if it exists (for agent performance tracking)
|
|
549
|
+
const metricsDir = path.join(baseAgentDir, "metrics");
|
|
550
|
+
const targetMetricsDir = path.join(WORKSPACE, "..", "metrics");
|
|
551
|
+
let metricsInstalled = false;
|
|
552
|
+
|
|
553
|
+
if (fs.existsSync(metricsDir) && !fs.existsSync(targetMetricsDir)) {
|
|
554
|
+
fs.cpSync(metricsDir, targetMetricsDir, { recursive: true });
|
|
555
|
+
step("Installed metrics/");
|
|
556
|
+
metricsInstalled = true;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Install additional policy documents
|
|
560
|
+
const policyDocs = [
|
|
561
|
+
"CONTINUOUS_EXECUTION_POLICY.md",
|
|
562
|
+
"WORKFLOW_CHAINS.md"
|
|
563
|
+
];
|
|
564
|
+
let policyDocsInstalled = 0;
|
|
565
|
+
|
|
566
|
+
for (const doc of policyDocs) {
|
|
567
|
+
const docSrc = path.join(baseAgentDir, doc);
|
|
568
|
+
const docDest = path.join(WORKSPACE, "..", doc);
|
|
569
|
+
if (fs.existsSync(docSrc) && !fs.existsSync(docDest)) {
|
|
570
|
+
fs.copyFileSync(docSrc, docDest);
|
|
571
|
+
policyDocsInstalled++;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (policyDocsInstalled > 0) {
|
|
575
|
+
step(`Installed ${policyDocsInstalled} policy docs`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Install rules if they exist
|
|
579
|
+
const rulesDir = path.join(baseAgentDir, "rules");
|
|
580
|
+
const targetRulesDir = path.join(WORKSPACE, "..", "rules");
|
|
581
|
+
let rulesInstalled = 0;
|
|
582
|
+
|
|
583
|
+
if (fs.existsSync(rulesDir)) {
|
|
584
|
+
fs.mkdirSync(targetRulesDir, { recursive: true });
|
|
585
|
+
const rules = fs.readdirSync(rulesDir).filter(f => f.endsWith(".md"));
|
|
586
|
+
|
|
587
|
+
for (const rule of rules) {
|
|
588
|
+
const src = path.join(rulesDir, rule);
|
|
589
|
+
const dest = path.join(targetRulesDir, rule);
|
|
590
|
+
|
|
591
|
+
if (!fs.existsSync(dest)) {
|
|
592
|
+
fs.copyFileSync(src, dest);
|
|
593
|
+
rulesInstalled++;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (rulesInstalled > 0) {
|
|
598
|
+
step(`Installed ${rulesInstalled} rules`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Install .shared if it exists (contains shared resources like ui-ux-pro-max data)
|
|
603
|
+
const sharedDir = path.join(tmp, ".agent", ".shared");
|
|
604
|
+
const targetSharedDir = path.join(WORKSPACE, "..", ".shared");
|
|
605
|
+
let sharedInstalled = false;
|
|
606
|
+
|
|
607
|
+
if (fs.existsSync(sharedDir) && !fs.existsSync(targetSharedDir)) {
|
|
608
|
+
stepLine();
|
|
609
|
+
const ss = spinner();
|
|
610
|
+
ss.start("Installing shared resources");
|
|
611
|
+
|
|
612
|
+
fs.cpSync(sharedDir, targetSharedDir, { recursive: true });
|
|
613
|
+
sharedInstalled = true;
|
|
614
|
+
|
|
615
|
+
ss.stop("Installed .shared/ (ui-ux-pro-max data)");
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Installation complete step
|
|
619
|
+
stepLine();
|
|
620
|
+
step("Installation complete");
|
|
621
|
+
|
|
622
|
+
// Final Success Box
|
|
623
|
+
stepLine();
|
|
624
|
+
console.log(`${c.gray(S.branch)}`); // Extra spacing line
|
|
625
|
+
|
|
626
|
+
let successContent = "";
|
|
627
|
+
|
|
628
|
+
// Skills summary
|
|
629
|
+
for (const sn of selectedSkills) {
|
|
630
|
+
const mockPath = `.agent/skills/${sn}`;
|
|
631
|
+
successContent += `${c.cyan("✓")} ${c.dim(mockPath)}\n`;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Workflows summary
|
|
635
|
+
if (workflowsInstalled > 0) {
|
|
636
|
+
successContent += `${c.cyan("✓")} ${c.dim(`.agent/workflows/ (${workflowsInstalled} files)`)}\n`;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Agents summary
|
|
640
|
+
if (agentsInstalled > 0) {
|
|
641
|
+
successContent += `${c.cyan("✓")} ${c.dim(`.agent/agents/ (${agentsInstalled} files)`)}\n`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// GEMINI.md summary
|
|
645
|
+
if (geminiInstalled) {
|
|
646
|
+
successContent += `${c.cyan("✓")} ${c.dim(".agent/GEMINI.md (Agent Rules)")}\n`;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ARCHITECTURE.md summary
|
|
650
|
+
if (archInstalled) {
|
|
651
|
+
successContent += `${c.cyan("✓")} ${c.dim(".agent/ARCHITECTURE.md")}\n`;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Knowledge summary
|
|
655
|
+
if (knowledgeInstalled) {
|
|
656
|
+
successContent += `${c.cyan("✓")} ${c.dim(".agent/knowledge/")}\n`;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Rules summary
|
|
660
|
+
if (rulesInstalled > 0) {
|
|
661
|
+
successContent += `${c.cyan("✓")} ${c.dim(`.agent/rules/ (${rulesInstalled} files)`)}\n`;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Shared resources summary
|
|
665
|
+
if (sharedInstalled) {
|
|
666
|
+
successContent += `${c.cyan("✓")} ${c.dim(".agent/.shared/ (ui-ux-pro-max data)")}\n`;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Build title
|
|
670
|
+
const parts = [`${selectedSkills.length} skills`];
|
|
671
|
+
if (workflowsInstalled > 0) parts.push(`${workflowsInstalled} workflows`);
|
|
672
|
+
if (agentsInstalled > 0) parts.push(`${agentsInstalled} agents`);
|
|
673
|
+
if (geminiInstalled) parts.push("GEMINI.md");
|
|
674
|
+
|
|
675
|
+
console.log(boxen(successContent.trim(), {
|
|
676
|
+
padding: 1,
|
|
677
|
+
borderColor: "cyan",
|
|
678
|
+
borderStyle: "round",
|
|
679
|
+
title: c.cyan(`Installed ${parts.join(", ")}`),
|
|
680
|
+
titleAlignment: "left"
|
|
681
|
+
}).split("\n").map(l => `${c.gray(S.branch)} ${l}`).join("\n"));
|
|
682
|
+
|
|
683
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
684
|
+
|
|
685
|
+
// Ask user about AutoLearn installation
|
|
686
|
+
stepLine();
|
|
687
|
+
const installAutoLearn = await confirm({
|
|
688
|
+
message: "Install AutoLearn (enables 'agent' command for learning & self-improvement)?",
|
|
689
|
+
initialValue: true
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
if (isCancel(installAutoLearn)) {
|
|
693
|
+
cancel("Installation cancelled");
|
|
694
|
+
process.exit(0);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Install CLI package
|
|
698
|
+
stepLine();
|
|
699
|
+
const cliSpinner = spinner();
|
|
700
|
+
const cliPackage = "add-skill-kit";
|
|
701
|
+
|
|
702
|
+
if (isGlobal) {
|
|
703
|
+
if (installAutoLearn) {
|
|
704
|
+
cliSpinner.start(`Installing CLI globally (${cliPackage})`);
|
|
705
|
+
} else {
|
|
706
|
+
cliSpinner.start(`Installing kit CLI globally (${cliPackage})`);
|
|
707
|
+
}
|
|
708
|
+
try {
|
|
709
|
+
await execAsync(`npm install -g ${cliPackage}`, { timeout: 120000 });
|
|
710
|
+
cliSpinner.stop("CLI installed globally");
|
|
711
|
+
if (installAutoLearn) {
|
|
712
|
+
step(c.dim("Commands: agent, kit"));
|
|
713
|
+
} else {
|
|
714
|
+
step(c.dim("Command: kit"));
|
|
715
|
+
}
|
|
716
|
+
} catch (e) {
|
|
717
|
+
cliSpinner.stop(c.yellow("Global CLI installation failed"));
|
|
718
|
+
step(c.dim(`Try running manually: npm i -g ${cliPackage}`));
|
|
719
|
+
}
|
|
720
|
+
} else {
|
|
721
|
+
cliSpinner.start(`Installing Agent CLI locally (${cliPackage})`);
|
|
722
|
+
try {
|
|
723
|
+
await execAsync(`npm install -D ${cliPackage}`, { timeout: 120000 });
|
|
724
|
+
cliSpinner.stop("CLI installed locally");
|
|
725
|
+
|
|
726
|
+
// Add npm scripts to package.json
|
|
727
|
+
try {
|
|
728
|
+
const pkgPath = path.join(process.cwd(), "package.json");
|
|
729
|
+
if (fs.existsSync(pkgPath)) {
|
|
730
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
731
|
+
pkg.scripts = pkg.scripts || {};
|
|
732
|
+
|
|
733
|
+
// Always add kit script
|
|
734
|
+
if (!pkg.scripts.kit) {
|
|
735
|
+
pkg.scripts.kit = "kit";
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Add agent script only if AutoLearn enabled
|
|
739
|
+
if (installAutoLearn && !pkg.scripts.agent) {
|
|
740
|
+
pkg.scripts.agent = "agent";
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
744
|
+
if (installAutoLearn) {
|
|
745
|
+
step(c.green("✓ Added npm scripts: 'agent', 'kit'"));
|
|
746
|
+
} else {
|
|
747
|
+
step(c.green("✓ Added npm script: 'kit'"));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
} catch (scriptErr) {
|
|
751
|
+
step(c.yellow("⚠ Could not add npm scripts automatically"));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Create wrapper scripts for direct command access (Windows + Unix)
|
|
755
|
+
try {
|
|
756
|
+
const projectRoot = process.cwd();
|
|
757
|
+
|
|
758
|
+
// Always create kit wrappers
|
|
759
|
+
const kitCmd = `@echo off\nnode "%~dp0node_modules\\add-skill-kit\\bin\\kit.js" %*`;
|
|
760
|
+
const kitSh = `#!/bin/sh\nnode "$(dirname "$0")/node_modules/add-skill-kit/bin/kit.js" "$@"`;
|
|
761
|
+
fs.writeFileSync(path.join(projectRoot, "kit.cmd"), kitCmd);
|
|
762
|
+
fs.writeFileSync(path.join(projectRoot, "kit"), kitSh, { mode: 0o755 });
|
|
763
|
+
|
|
764
|
+
if (installAutoLearn) {
|
|
765
|
+
// Create agent wrappers only if AutoLearn enabled
|
|
766
|
+
const agentCmd = `@echo off\nnode "%~dp0node_modules\\add-skill-kit\\lib\\agent-cli\\bin\\agent.js" %*`;
|
|
767
|
+
const agentSh = `#!/bin/sh\nnode "$(dirname "$0")/node_modules/add-skill-kit/lib/agent-cli/bin/agent.js" "$@"`;
|
|
768
|
+
fs.writeFileSync(path.join(projectRoot, "agent.cmd"), agentCmd);
|
|
769
|
+
fs.writeFileSync(path.join(projectRoot, "agent"), agentSh, { mode: 0o755 });
|
|
770
|
+
|
|
771
|
+
step(c.green("✓ Created wrapper scripts: agent, kit"));
|
|
772
|
+
step(c.dim("Run directly: ./agent or ./kit (Unix) | agent or kit (Windows)"));
|
|
773
|
+
} else {
|
|
774
|
+
step(c.green("✓ Created wrapper script: kit"));
|
|
775
|
+
step(c.dim("Run directly: ./kit (Unix) | kit (Windows)"));
|
|
776
|
+
}
|
|
777
|
+
} catch (wrapperErr) {
|
|
778
|
+
step(c.dim("Run: npx kit"));
|
|
779
|
+
}
|
|
780
|
+
} catch (e) {
|
|
781
|
+
cliSpinner.stop(c.yellow("Local CLI installation skipped"));
|
|
782
|
+
step(c.dim(`Run manually: npm i -D ${cliPackage}`));
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Run npm install to ensure all skill dependencies are available
|
|
787
|
+
stepLine();
|
|
788
|
+
const depsSpinner = spinner();
|
|
789
|
+
depsSpinner.start("Installing skill dependencies (csv-parse, etc.)");
|
|
790
|
+
try {
|
|
791
|
+
await execAsync("npm install", { timeout: 120000 });
|
|
792
|
+
depsSpinner.stop("Skill dependencies installed");
|
|
793
|
+
} catch (e) {
|
|
794
|
+
depsSpinner.stop(c.yellow("Dependencies installation skipped"));
|
|
795
|
+
step(c.dim("Run manually: npm install"));
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Python dependencies no longer needed - all scripts migrated to JS
|
|
799
|
+
|
|
800
|
+
stepLine();
|
|
801
|
+
console.log(` ${c.cyan("Done!")}`);
|
|
802
|
+
console.log();
|
|
803
|
+
}
|