@slamb2k/mad-skills 2.0.7 → 2.0.9
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/.claude-plugin/marketplace.json +11 -5
- package/.claude-plugin/plugin.json +9 -7
- package/README.md +101 -25
- package/hooks/hooks.json +26 -0
- package/hooks/session-guard-prompt.sh +59 -0
- package/hooks/session-guard.sh +155 -151
- package/package.json +2 -7
- package/skills/brace/SKILL.md +240 -14
- package/skills/brace/assets/gitignore-template +0 -3
- package/skills/brace/references/phase-prompts.md +37 -0
- package/skills/brace/references/report-template.md +5 -1
- package/skills/build/SKILL.md +292 -13
- package/skills/distil/SKILL.md +258 -7
- package/skills/manifest.json +26 -16
- package/skills/prime/SKILL.md +76 -8
- package/skills/rig/SKILL.md +165 -7
- package/skills/ship/SKILL.md +190 -19
- package/skills/speccy/SKILL.md +165 -0
- package/skills/speccy/references/interview-guide.md +96 -0
- package/skills/speccy/tests/evals.json +34 -0
- package/skills/sync/SKILL.md +175 -17
- package/commands/brace.md +0 -9
- package/commands/build.md +0 -9
- package/commands/distil.md +0 -9
- package/commands/prime.md +0 -9
- package/commands/rig.md +0 -9
- package/commands/ship.md +0 -9
- package/commands/sync.md +0 -9
- package/skills/brace/instructions.md +0 -229
- package/skills/build/instructions.md +0 -293
- package/skills/distil/instructions.md +0 -255
- package/skills/prime/instructions.md +0 -73
- package/skills/rig/instructions.md +0 -162
- package/skills/ship/instructions.md +0 -192
- package/skills/sync/instructions.md +0 -178
- package/src/cli.js +0 -482
package/src/cli.js
DELETED
|
@@ -1,482 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Claude Skills Installer CLI
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx @your-scope/claude-skills # Install all skills
|
|
8
|
-
* npx @your-scope/claude-skills --list # List available skills
|
|
9
|
-
* npx @your-scope/claude-skills --skill foo,bar # Install specific skills
|
|
10
|
-
* npx @your-scope/claude-skills --target ./path # Custom install path
|
|
11
|
-
* npx @your-scope/claude-skills --upgrade # Upgrade existing skills
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { readdir, readFile, mkdir, access, stat, chmod } from "node:fs/promises";
|
|
15
|
-
import { existsSync } from "node:fs";
|
|
16
|
-
import { execSync } from "node:child_process";
|
|
17
|
-
import { resolve, join, dirname } from "node:path";
|
|
18
|
-
import { fileURLToPath } from "node:url";
|
|
19
|
-
import { parseArgs } from "node:util";
|
|
20
|
-
|
|
21
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const SKILLS_SRC = resolve(__dirname, "..", "skills");
|
|
23
|
-
const SUPPLEMENTARY_DIRS = ["commands", "agents", "hooks"];
|
|
24
|
-
|
|
25
|
-
const DEFAULT_TARGETS = [
|
|
26
|
-
".claude/skills", // Project-level (preferred)
|
|
27
|
-
join(process.env.HOME ?? "~", ".claude", "skills"), // User-level fallback
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const { values: args } = parseArgs({
|
|
31
|
-
options: {
|
|
32
|
-
list: { type: "boolean", default: false },
|
|
33
|
-
skill: { type: "string", default: "" },
|
|
34
|
-
target: { type: "string", default: "" },
|
|
35
|
-
upgrade: { type: "boolean", default: false },
|
|
36
|
-
force: { type: "boolean", short: "f", default: false },
|
|
37
|
-
help: { type: "boolean", short: "h", default: false },
|
|
38
|
-
},
|
|
39
|
-
strict: true,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (args.help) {
|
|
43
|
-
console.log(`
|
|
44
|
-
Claude Skills Installer
|
|
45
|
-
|
|
46
|
-
Installs skills, slash commands, agents, and hooks to your .claude directory.
|
|
47
|
-
|
|
48
|
-
Usage:
|
|
49
|
-
npx @slamb2k/mad-skills [options]
|
|
50
|
-
|
|
51
|
-
Options:
|
|
52
|
-
--list List available skills with descriptions
|
|
53
|
-
--skill <names> Comma-separated skill names to install (default: all)
|
|
54
|
-
--target <path> Installation directory (default: .claude/skills)
|
|
55
|
-
--upgrade Overwrite existing skills and supplementary files
|
|
56
|
-
--force, -f Skip confirmation prompts
|
|
57
|
-
--help, -h Show this help
|
|
58
|
-
|
|
59
|
-
Examples:
|
|
60
|
-
npx @slamb2k/mad-skills --list
|
|
61
|
-
npx @slamb2k/mad-skills --skill build,ship
|
|
62
|
-
npx @slamb2k/mad-skills --target ./my-project/.claude/skills --upgrade
|
|
63
|
-
`);
|
|
64
|
-
process.exit(0);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function discoverSkills() {
|
|
68
|
-
const entries = await readdir(SKILLS_SRC, { withFileTypes: true });
|
|
69
|
-
const skills = [];
|
|
70
|
-
|
|
71
|
-
for (const entry of entries) {
|
|
72
|
-
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
73
|
-
|
|
74
|
-
const skillMd = join(SKILLS_SRC, entry.name, "SKILL.md");
|
|
75
|
-
try {
|
|
76
|
-
await access(skillMd);
|
|
77
|
-
const content = await readFile(skillMd, "utf-8");
|
|
78
|
-
const frontmatter = parseFrontmatter(content);
|
|
79
|
-
|
|
80
|
-
skills.push({
|
|
81
|
-
name: entry.name,
|
|
82
|
-
displayName: frontmatter.name ?? entry.name,
|
|
83
|
-
description: frontmatter.description ?? "(no description)",
|
|
84
|
-
path: join(SKILLS_SRC, entry.name),
|
|
85
|
-
});
|
|
86
|
-
} catch {
|
|
87
|
-
// Skip directories without SKILL.md
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return skills;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function parseFrontmatter(content) {
|
|
95
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
96
|
-
if (!match) return {};
|
|
97
|
-
|
|
98
|
-
const result = {};
|
|
99
|
-
for (const line of match[1].split("\n")) {
|
|
100
|
-
const colonIdx = line.indexOf(":");
|
|
101
|
-
if (colonIdx === -1) continue;
|
|
102
|
-
const key = line.slice(0, colonIdx).trim();
|
|
103
|
-
const value = line.slice(colonIdx + 1).trim();
|
|
104
|
-
result[key] = value;
|
|
105
|
-
}
|
|
106
|
-
return result;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function resolveTarget() {
|
|
110
|
-
if (args.target) return resolve(args.target);
|
|
111
|
-
|
|
112
|
-
// Prefer project-level if we're in a git repo or have .claude dir
|
|
113
|
-
for (const candidate of DEFAULT_TARGETS) {
|
|
114
|
-
const resolved = resolve(candidate);
|
|
115
|
-
try {
|
|
116
|
-
await access(dirname(resolved));
|
|
117
|
-
return resolved;
|
|
118
|
-
} catch {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return resolve(DEFAULT_TARGETS[0]);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function exists(path) {
|
|
127
|
-
try {
|
|
128
|
-
await access(path);
|
|
129
|
-
return true;
|
|
130
|
-
} catch {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
// Dependency management
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
const VALID_DEP_TYPES = new Set(["cli", "npm", "agent", "skill", "plugin"]);
|
|
140
|
-
const VALID_DEP_RESOLUTIONS = new Set(["url", "install", "ask", "fallback", "stop"]);
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Parse a markdown dependency table from instructions.md content.
|
|
144
|
-
* Returns an array of { name, type, check, required, resolution, detail }.
|
|
145
|
-
*/
|
|
146
|
-
function parseDependencyTable(content) {
|
|
147
|
-
const lines = content.split("\n");
|
|
148
|
-
const deps = [];
|
|
149
|
-
|
|
150
|
-
const headerIdx = lines.findIndex((l) =>
|
|
151
|
-
/^\|\s*Dependency\s*\|/i.test(l)
|
|
152
|
-
);
|
|
153
|
-
if (headerIdx === -1) return deps;
|
|
154
|
-
|
|
155
|
-
for (let i = headerIdx + 2; i < lines.length; i++) {
|
|
156
|
-
const line = lines[i].trim();
|
|
157
|
-
if (!line.startsWith("|")) break;
|
|
158
|
-
|
|
159
|
-
const cells = line
|
|
160
|
-
.split("|")
|
|
161
|
-
.slice(1, -1)
|
|
162
|
-
.map((c) => c.trim());
|
|
163
|
-
if (cells.length < 6) continue;
|
|
164
|
-
|
|
165
|
-
const check = cells[2].replace(/^`|`$/g, "");
|
|
166
|
-
deps.push({
|
|
167
|
-
name: cells[0],
|
|
168
|
-
type: cells[1],
|
|
169
|
-
check: check === "—" || check === "-" || check === "" ? null : check,
|
|
170
|
-
required: cells[3].toLowerCase() === "yes",
|
|
171
|
-
resolution: cells[4],
|
|
172
|
-
detail: cells[5].replace(/^`|`$/g, ""),
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return deps;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Check whether a single dependency is available.
|
|
181
|
-
* Returns true if found, false if missing.
|
|
182
|
-
*/
|
|
183
|
-
function checkDependency(dep, targetDir) {
|
|
184
|
-
if (!dep.check) return true;
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
switch (dep.type) {
|
|
188
|
-
case "cli":
|
|
189
|
-
case "npm":
|
|
190
|
-
execSync(dep.check, { stdio: "ignore", timeout: 15000 });
|
|
191
|
-
return true;
|
|
192
|
-
case "agent":
|
|
193
|
-
case "skill": {
|
|
194
|
-
const p = dep.check.replace(/^~/, process.env.HOME ?? "~");
|
|
195
|
-
if (existsSync(resolve(p))) return true;
|
|
196
|
-
// For skill deps, also check inside the install target directory
|
|
197
|
-
if (targetDir && dep.type === "skill") {
|
|
198
|
-
const parts = dep.check.split("/");
|
|
199
|
-
const idx = parts.indexOf("skills");
|
|
200
|
-
if (idx !== -1 && idx + 1 < parts.length) {
|
|
201
|
-
const targetPath = join(targetDir, parts[idx + 1], "SKILL.md");
|
|
202
|
-
if (existsSync(targetPath)) return true;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
case "plugin":
|
|
208
|
-
return false;
|
|
209
|
-
default:
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
} catch {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Apply the resolution strategy for a missing dependency.
|
|
219
|
-
* Returns "installed" | "warning" | "info".
|
|
220
|
-
*/
|
|
221
|
-
function resolveDependency(dep) {
|
|
222
|
-
switch (dep.resolution) {
|
|
223
|
-
case "stop":
|
|
224
|
-
case "url": {
|
|
225
|
-
const icon = dep.required ? "⚠" : "ℹ";
|
|
226
|
-
console.log(` ${icon} ${dep.name} not found — ${dep.detail}`);
|
|
227
|
-
return "warning";
|
|
228
|
-
}
|
|
229
|
-
case "install": {
|
|
230
|
-
try {
|
|
231
|
-
execSync(dep.detail, { stdio: "pipe", timeout: 60000 });
|
|
232
|
-
console.log(` ✅ ${dep.name} installed (ran: ${dep.detail})`);
|
|
233
|
-
return "installed";
|
|
234
|
-
} catch {
|
|
235
|
-
console.log(
|
|
236
|
-
` ⚠ ${dep.name} auto-install failed — run manually: ${dep.detail}`
|
|
237
|
-
);
|
|
238
|
-
return "warning";
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
case "ask": {
|
|
242
|
-
console.log(
|
|
243
|
-
` ℹ ${dep.name} not found — optional, install with: ${dep.detail}`
|
|
244
|
-
);
|
|
245
|
-
return "info";
|
|
246
|
-
}
|
|
247
|
-
case "fallback": {
|
|
248
|
-
console.log(` ℹ ${dep.name} not found — ${dep.detail}`);
|
|
249
|
-
return "info";
|
|
250
|
-
}
|
|
251
|
-
default:
|
|
252
|
-
return "ok";
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Check all dependencies declared in a skill's instructions.md.
|
|
258
|
-
* Returns { installed, warnings } counts.
|
|
259
|
-
*/
|
|
260
|
-
async function checkSkillDependencies(skillName, targetDir) {
|
|
261
|
-
const instructionsPath = join(targetDir, skillName, "instructions.md");
|
|
262
|
-
let content;
|
|
263
|
-
try {
|
|
264
|
-
content = await readFile(instructionsPath, "utf-8");
|
|
265
|
-
} catch {
|
|
266
|
-
return { installed: 0, warnings: 0 };
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const deps = parseDependencyTable(content);
|
|
270
|
-
if (deps.length === 0) return { installed: 0, warnings: 0 };
|
|
271
|
-
|
|
272
|
-
let installed = 0;
|
|
273
|
-
let warnCount = 0;
|
|
274
|
-
|
|
275
|
-
for (const dep of deps) {
|
|
276
|
-
if (checkDependency(dep, targetDir)) continue;
|
|
277
|
-
const result = resolveDependency(dep);
|
|
278
|
-
if (result === "installed") installed++;
|
|
279
|
-
if (result === "warning") warnCount++;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return { installed, warnings: warnCount };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async function installSkill(skill, targetDir) {
|
|
286
|
-
const dest = join(targetDir, skill.name);
|
|
287
|
-
const destExists = await exists(dest);
|
|
288
|
-
|
|
289
|
-
if (destExists && !args.upgrade) {
|
|
290
|
-
return { name: skill.name, status: "skipped" };
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Copy skill, filtering out CI-only directories
|
|
294
|
-
await cpFiltered(skill.path, dest);
|
|
295
|
-
|
|
296
|
-
return { name: skill.name, status: destExists ? "upgraded" : "installed" };
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Recursively copies a skill directory, excluding CI/test artefacts
|
|
301
|
-
* that consumers don't need. Matches the .skill package exclusions.
|
|
302
|
-
*/
|
|
303
|
-
const INSTALL_EXCLUDE_DIRS = new Set([
|
|
304
|
-
"tests",
|
|
305
|
-
"evals",
|
|
306
|
-
"__pycache__",
|
|
307
|
-
"node_modules",
|
|
308
|
-
".git",
|
|
309
|
-
]);
|
|
310
|
-
const INSTALL_EXCLUDE_FILES = new Set([".DS_Store", ".gitkeep"]);
|
|
311
|
-
|
|
312
|
-
async function cpFiltered(src, dest) {
|
|
313
|
-
await mkdir(dest, { recursive: true });
|
|
314
|
-
const entries = await readdir(src, { withFileTypes: true });
|
|
315
|
-
|
|
316
|
-
for (const entry of entries) {
|
|
317
|
-
const srcPath = join(src, entry.name);
|
|
318
|
-
const destPath = join(dest, entry.name);
|
|
319
|
-
|
|
320
|
-
if (entry.isDirectory()) {
|
|
321
|
-
if (INSTALL_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
322
|
-
await cpFiltered(srcPath, destPath);
|
|
323
|
-
} else if (entry.isFile()) {
|
|
324
|
-
if (INSTALL_EXCLUDE_FILES.has(entry.name)) continue;
|
|
325
|
-
if (entry.name.endsWith(".pyc")) continue;
|
|
326
|
-
const { copyFile } = await import("node:fs/promises");
|
|
327
|
-
await copyFile(srcPath, destPath);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Install supplementary directories (commands, agents, hooks) alongside skills.
|
|
334
|
-
* Target dirs are siblings of the skills target dir (e.g. ~/.claude/commands).
|
|
335
|
-
*/
|
|
336
|
-
async function installSupplementary(targetDir) {
|
|
337
|
-
const baseDir = dirname(targetDir);
|
|
338
|
-
const results = { installed: 0, upgraded: 0, skipped: 0 };
|
|
339
|
-
|
|
340
|
-
for (const dir of SUPPLEMENTARY_DIRS) {
|
|
341
|
-
const src = resolve(__dirname, "..", dir);
|
|
342
|
-
const dest = join(baseDir, dir);
|
|
343
|
-
|
|
344
|
-
if (!existsSync(src)) continue;
|
|
345
|
-
|
|
346
|
-
const entries = await readdir(src, { withFileTypes: true });
|
|
347
|
-
await mkdir(dest, { recursive: true });
|
|
348
|
-
|
|
349
|
-
for (const entry of entries) {
|
|
350
|
-
if (!entry.isFile()) continue;
|
|
351
|
-
if (entry.name.startsWith(".")) continue;
|
|
352
|
-
|
|
353
|
-
const srcPath = join(src, entry.name);
|
|
354
|
-
const destPath = join(dest, entry.name);
|
|
355
|
-
const destExists = await exists(destPath);
|
|
356
|
-
|
|
357
|
-
if (destExists && !args.upgrade) {
|
|
358
|
-
results.skipped++;
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const { copyFile } = await import("node:fs/promises");
|
|
363
|
-
await copyFile(srcPath, destPath);
|
|
364
|
-
|
|
365
|
-
// Make shell scripts executable
|
|
366
|
-
if (entry.name.endsWith(".sh")) {
|
|
367
|
-
await chmod(destPath, 0o755);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (destExists) {
|
|
371
|
-
results.upgraded++;
|
|
372
|
-
} else {
|
|
373
|
-
results.installed++;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return results;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
async function main() {
|
|
382
|
-
const skills = await discoverSkills();
|
|
383
|
-
|
|
384
|
-
if (skills.length === 0) {
|
|
385
|
-
console.error("No skills found in package. This is likely a packaging error.");
|
|
386
|
-
process.exit(1);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// --list mode
|
|
390
|
-
if (args.list) {
|
|
391
|
-
console.log("\nAvailable skills:\n");
|
|
392
|
-
const maxName = Math.max(...skills.map((s) => s.name.length));
|
|
393
|
-
for (const skill of skills) {
|
|
394
|
-
const desc =
|
|
395
|
-
skill.description.length > 80
|
|
396
|
-
? skill.description.slice(0, 77) + "..."
|
|
397
|
-
: skill.description;
|
|
398
|
-
console.log(` ${skill.name.padEnd(maxName + 2)} ${desc}`);
|
|
399
|
-
}
|
|
400
|
-
console.log(`\n${skills.length} skill(s) available\n`);
|
|
401
|
-
process.exit(0);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Filter skills if --skill specified
|
|
405
|
-
let toInstall = skills;
|
|
406
|
-
if (args.skill) {
|
|
407
|
-
const requested = new Set(args.skill.split(",").map((s) => s.trim()));
|
|
408
|
-
toInstall = skills.filter((s) => requested.has(s.name));
|
|
409
|
-
const found = new Set(toInstall.map((s) => s.name));
|
|
410
|
-
const missing = [...requested].filter((r) => !found.has(r));
|
|
411
|
-
if (missing.length > 0) {
|
|
412
|
-
console.error(`Unknown skills: ${missing.join(", ")}`);
|
|
413
|
-
console.error(`Use --list to see available skills`);
|
|
414
|
-
process.exit(1);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const targetDir = await resolveTarget();
|
|
419
|
-
await mkdir(targetDir, { recursive: true });
|
|
420
|
-
|
|
421
|
-
console.log(`\nInstalling ${toInstall.length} skill(s) to ${targetDir}\n`);
|
|
422
|
-
|
|
423
|
-
// Pass 1: Install all skills (file copy)
|
|
424
|
-
const results = [];
|
|
425
|
-
for (const skill of toInstall) {
|
|
426
|
-
results.push(await installSkill(skill, targetDir));
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Pass 2: Print results and check dependencies
|
|
430
|
-
let totalDepsInstalled = 0;
|
|
431
|
-
let totalDepsWarnings = 0;
|
|
432
|
-
|
|
433
|
-
for (const result of results) {
|
|
434
|
-
if (result.status === "skipped") {
|
|
435
|
-
console.log(` ⏭ ${result.name} (exists, use --upgrade to overwrite)`);
|
|
436
|
-
} else {
|
|
437
|
-
console.log(` ✅ ${result.name} (${result.status})`);
|
|
438
|
-
const depResult = await checkSkillDependencies(result.name, targetDir);
|
|
439
|
-
totalDepsInstalled += depResult.installed;
|
|
440
|
-
totalDepsWarnings += depResult.warnings;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const installed = results.filter((r) => r.status === "installed").length;
|
|
445
|
-
const upgraded = results.filter((r) => r.status === "upgraded").length;
|
|
446
|
-
const skipped = results.filter((r) => r.status === "skipped").length;
|
|
447
|
-
|
|
448
|
-
console.log(
|
|
449
|
-
`\nDone: ${installed} installed, ${upgraded} upgraded, ${skipped} skipped`
|
|
450
|
-
);
|
|
451
|
-
if (totalDepsInstalled > 0 || totalDepsWarnings > 0) {
|
|
452
|
-
const parts = [];
|
|
453
|
-
if (totalDepsInstalled > 0) {
|
|
454
|
-
parts.push(
|
|
455
|
-
`${totalDepsInstalled} ${totalDepsInstalled === 1 ? "dependency" : "dependencies"} installed`
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
if (totalDepsWarnings > 0) {
|
|
459
|
-
parts.push(
|
|
460
|
-
`${totalDepsWarnings} ${totalDepsWarnings === 1 ? "warning" : "warnings"}`
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
console.log(` ${parts.join(", ")}`);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Pass 3: Install supplementary files (commands, agents, hooks)
|
|
467
|
-
const suppResults = await installSupplementary(targetDir);
|
|
468
|
-
const suppTotal = suppResults.installed + suppResults.upgraded + suppResults.skipped;
|
|
469
|
-
|
|
470
|
-
if (suppTotal > 0) {
|
|
471
|
-
console.log(
|
|
472
|
-
`\nSupplementary files: ${suppResults.installed} installed, ${suppResults.upgraded} upgraded, ${suppResults.skipped} skipped`
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
console.log();
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
main().catch((err) => {
|
|
480
|
-
console.error("Fatal:", err.message);
|
|
481
|
-
process.exit(1);
|
|
482
|
-
});
|