@groupby/ai-dev 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +612 -0
- package/package.json +44 -0
- package/skills/README.md +61 -0
- package/skills/archived/README.md +3 -0
- package/skills/library/README.md +3 -0
- package/skills/library/frontend-design/LICENSE.txt +177 -0
- package/skills/library/frontend-design/SKILL.md +42 -0
- package/skills/skills/README.md +61 -0
- package/skills/skills/archived/README.md +3 -0
- package/skills/skills/library/README.md +3 -0
- package/skills/skills/library/frontend-design/LICENSE.txt +177 -0
- package/skills/skills/library/frontend-design/SKILL.md +42 -0
- package/teams/brain-studio/skills/code-review/SKILL.md +46 -0
- package/teams/teams/brain-studio/skills/code-review/SKILL.md +46 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/list.ts
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
|
|
9
|
+
// src/lib/discovery.ts
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import fg from "fast-glob";
|
|
13
|
+
import fs from "fs-extra";
|
|
14
|
+
|
|
15
|
+
// src/lib/frontmatter.ts
|
|
16
|
+
import matter from "gray-matter";
|
|
17
|
+
function parseFrontmatter(content) {
|
|
18
|
+
const parsed = matter(content);
|
|
19
|
+
return {
|
|
20
|
+
data: parsed.data,
|
|
21
|
+
content: parsed.content
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function generateStub(frontmatter, skillName) {
|
|
25
|
+
const body = `
|
|
26
|
+
See \`docs/ai/skills/${skillName}/SKILL.md\` and follow closely.
|
|
27
|
+
`;
|
|
28
|
+
return matter.stringify(body, frontmatter);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/lib/discovery.ts
|
|
32
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
var PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
34
|
+
async function discoverSkills() {
|
|
35
|
+
const libraryPattern = "skills/library/*/SKILL.md";
|
|
36
|
+
const teamPattern = "teams/*/skills/*/SKILL.md";
|
|
37
|
+
const matches = await fg([libraryPattern, teamPattern], {
|
|
38
|
+
cwd: PACKAGE_ROOT,
|
|
39
|
+
absolute: true
|
|
40
|
+
});
|
|
41
|
+
const skills = [];
|
|
42
|
+
for (const match of matches) {
|
|
43
|
+
const content = await fs.readFile(match, "utf-8");
|
|
44
|
+
const { data } = parseFrontmatter(content);
|
|
45
|
+
const skillFolder = path.dirname(match);
|
|
46
|
+
const relativePath = path.relative(PACKAGE_ROOT, match);
|
|
47
|
+
let sourceType;
|
|
48
|
+
let teamName;
|
|
49
|
+
if (relativePath.startsWith("skills/library/")) {
|
|
50
|
+
sourceType = "library";
|
|
51
|
+
} else {
|
|
52
|
+
sourceType = "team";
|
|
53
|
+
const parts = relativePath.split(path.sep);
|
|
54
|
+
teamName = parts[1];
|
|
55
|
+
}
|
|
56
|
+
skills.push({
|
|
57
|
+
name: data.name || path.basename(skillFolder),
|
|
58
|
+
description: data.description || "",
|
|
59
|
+
sourcePath: skillFolder,
|
|
60
|
+
sourceType,
|
|
61
|
+
teamName,
|
|
62
|
+
frontmatter: data
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return skills;
|
|
66
|
+
}
|
|
67
|
+
async function discoverTeams() {
|
|
68
|
+
const skills = await discoverSkills();
|
|
69
|
+
const teamMap = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const skill of skills) {
|
|
71
|
+
if (skill.sourceType === "team" && skill.teamName) {
|
|
72
|
+
const existing = teamMap.get(skill.teamName) || [];
|
|
73
|
+
existing.push(skill);
|
|
74
|
+
teamMap.set(skill.teamName, existing);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return Array.from(teamMap.entries()).map(([name, skills2]) => ({
|
|
78
|
+
name,
|
|
79
|
+
skills: skills2
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
async function findSkill(name) {
|
|
83
|
+
const skills = await discoverSkills();
|
|
84
|
+
return skills.find((s) => s.name === name && s.sourceType === "library") || skills.find((s) => s.name === name);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/commands/list.ts
|
|
88
|
+
function formatTeamName(name) {
|
|
89
|
+
return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
90
|
+
}
|
|
91
|
+
function truncate(str, max) {
|
|
92
|
+
if (str.length <= max) return str;
|
|
93
|
+
return str.slice(0, max - 3) + "...";
|
|
94
|
+
}
|
|
95
|
+
async function listSkills() {
|
|
96
|
+
const skills = await discoverSkills();
|
|
97
|
+
if (skills.length === 0) {
|
|
98
|
+
console.log(chalk.yellow("No skills found in package. This may indicate a build issue."));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const librarySkills = skills.filter((s) => s.sourceType === "library");
|
|
102
|
+
const teamSkills = skills.filter((s) => s.sourceType === "team");
|
|
103
|
+
if (librarySkills.length > 0) {
|
|
104
|
+
console.log(chalk.bold("\nLibrary"));
|
|
105
|
+
for (const s of librarySkills) {
|
|
106
|
+
console.log(` ${chalk.cyan(s.name.padEnd(24))} ${truncate(s.description, 60)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const teamMap = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const s of teamSkills) {
|
|
111
|
+
const team = s.teamName || "unknown";
|
|
112
|
+
const existing = teamMap.get(team) || [];
|
|
113
|
+
existing.push(s);
|
|
114
|
+
teamMap.set(team, existing);
|
|
115
|
+
}
|
|
116
|
+
for (const [team, skillList] of teamMap) {
|
|
117
|
+
console.log(chalk.bold(`
|
|
118
|
+
${formatTeamName(team)}`));
|
|
119
|
+
for (const s of skillList) {
|
|
120
|
+
console.log(` ${chalk.cyan(s.name.padEnd(24))} ${truncate(s.description, 60)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.log();
|
|
124
|
+
}
|
|
125
|
+
async function listTeams() {
|
|
126
|
+
const teams = await discoverTeams();
|
|
127
|
+
if (teams.length === 0) {
|
|
128
|
+
console.log(chalk.yellow("No teams found."));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
console.log(chalk.bold("\nTeams"));
|
|
132
|
+
for (const t of teams) {
|
|
133
|
+
console.log(
|
|
134
|
+
` ${chalk.cyan(formatTeamName(t.name).padEnd(24))} ${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
console.log();
|
|
138
|
+
}
|
|
139
|
+
async function listAll() {
|
|
140
|
+
await listSkills();
|
|
141
|
+
const teams = await discoverTeams();
|
|
142
|
+
if (teams.length > 0) {
|
|
143
|
+
console.log(chalk.bold("Teams"));
|
|
144
|
+
for (const t of teams) {
|
|
145
|
+
console.log(
|
|
146
|
+
` ${chalk.cyan(formatTeamName(t.name).padEnd(24))} ${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
console.log();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function registerListCommand(program2) {
|
|
153
|
+
const list = program2.command("list").description("List available skills and teams");
|
|
154
|
+
list.command("skills").description("List all available skills").action(listSkills);
|
|
155
|
+
list.command("teams").description("List teams and their skill counts").action(listTeams);
|
|
156
|
+
list.action(listAll);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/commands/install.ts
|
|
160
|
+
import path4 from "path";
|
|
161
|
+
import chalk3 from "chalk";
|
|
162
|
+
|
|
163
|
+
// src/lib/clients.ts
|
|
164
|
+
import path2 from "path";
|
|
165
|
+
import fs2 from "fs-extra";
|
|
166
|
+
var ALL_CLIENTS = [
|
|
167
|
+
{ name: "Copilot", skillsDir: ".github/skills", detectDir: ".github" },
|
|
168
|
+
{ name: "Claude Code", skillsDir: ".claude/skills", detectDir: ".claude" },
|
|
169
|
+
{ name: "Codex", skillsDir: ".agents/skills", detectDir: ".agents" }
|
|
170
|
+
];
|
|
171
|
+
async function detectClients(targetDir) {
|
|
172
|
+
const detected = [];
|
|
173
|
+
for (const client of ALL_CLIENTS) {
|
|
174
|
+
const dirPath = path2.join(targetDir, client.detectDir);
|
|
175
|
+
if (await fs2.pathExists(dirPath)) {
|
|
176
|
+
detected.push(client);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return detected;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/lib/installer.ts
|
|
183
|
+
import path3 from "path";
|
|
184
|
+
import fs3 from "fs-extra";
|
|
185
|
+
async function handleFile(srcPath, destPath, options, result, onConflict, contentOverride) {
|
|
186
|
+
const relativeDest = path3.relative(options.targetDir, destPath);
|
|
187
|
+
if (await fs3.pathExists(destPath)) {
|
|
188
|
+
const existingContent = await fs3.readFile(destPath, "utf-8");
|
|
189
|
+
const newContent = contentOverride ?? await fs3.readFile(srcPath, "utf-8");
|
|
190
|
+
if (existingContent === newContent) {
|
|
191
|
+
result.skipped.push(relativeDest);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (options.force) {
|
|
195
|
+
if (!options.dryRun) {
|
|
196
|
+
await fs3.ensureDir(path3.dirname(destPath));
|
|
197
|
+
await fs3.writeFile(destPath, newContent);
|
|
198
|
+
}
|
|
199
|
+
result.overwritten.push(relativeDest);
|
|
200
|
+
} else if (options.skipExisting) {
|
|
201
|
+
result.skipped.push(relativeDest);
|
|
202
|
+
} else if (onConflict) {
|
|
203
|
+
const choice = await onConflict(relativeDest);
|
|
204
|
+
if (choice === "overwrite") {
|
|
205
|
+
if (!options.dryRun) {
|
|
206
|
+
await fs3.ensureDir(path3.dirname(destPath));
|
|
207
|
+
await fs3.writeFile(destPath, newContent);
|
|
208
|
+
}
|
|
209
|
+
result.overwritten.push(relativeDest);
|
|
210
|
+
} else {
|
|
211
|
+
result.skipped.push(relativeDest);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
result.skipped.push(relativeDest);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
if (!options.dryRun) {
|
|
218
|
+
await fs3.ensureDir(path3.dirname(destPath));
|
|
219
|
+
if (contentOverride) {
|
|
220
|
+
await fs3.writeFile(destPath, contentOverride);
|
|
221
|
+
} else {
|
|
222
|
+
await fs3.copy(srcPath, destPath);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
result.created.push(relativeDest);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function installSkill(skill, options, onConflict) {
|
|
229
|
+
const result = {
|
|
230
|
+
skill: skill.name,
|
|
231
|
+
created: [],
|
|
232
|
+
skipped: [],
|
|
233
|
+
overwritten: []
|
|
234
|
+
};
|
|
235
|
+
const destFolder = path3.join(options.targetDir, "docs", "ai", "skills", skill.name);
|
|
236
|
+
const sourceFiles = await fs3.readdir(skill.sourcePath);
|
|
237
|
+
for (const file of sourceFiles) {
|
|
238
|
+
const srcFile = path3.join(skill.sourcePath, file);
|
|
239
|
+
const stat = await fs3.stat(srcFile);
|
|
240
|
+
if (stat.isFile()) {
|
|
241
|
+
const destFile = path3.join(destFolder, file);
|
|
242
|
+
await handleFile(srcFile, destFile, options, result, onConflict);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
for (const client of options.clients) {
|
|
246
|
+
const stubPath = path3.join(
|
|
247
|
+
options.targetDir,
|
|
248
|
+
client.skillsDir,
|
|
249
|
+
skill.name,
|
|
250
|
+
"SKILL.md"
|
|
251
|
+
);
|
|
252
|
+
const stubContent = generateStub(skill.frontmatter, skill.name);
|
|
253
|
+
await handleFile("", stubPath, options, result, onConflict, stubContent);
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
async function installSkills(skills, options, onConflict) {
|
|
258
|
+
const results = [];
|
|
259
|
+
for (const skill of skills) {
|
|
260
|
+
results.push(await installSkill(skill, options, onConflict));
|
|
261
|
+
}
|
|
262
|
+
return results;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/lib/prompts.ts
|
|
266
|
+
import { select, checkbox, confirm } from "@inquirer/prompts";
|
|
267
|
+
import chalk2 from "chalk";
|
|
268
|
+
async function promptSelectSkills(skills) {
|
|
269
|
+
const librarySkills = skills.filter((s) => s.sourceType === "library");
|
|
270
|
+
const teamSkills = skills.filter((s) => s.sourceType === "team");
|
|
271
|
+
const choices = [];
|
|
272
|
+
if (librarySkills.length > 0) {
|
|
273
|
+
choices.push(
|
|
274
|
+
...librarySkills.map((s) => ({
|
|
275
|
+
name: `${chalk2.dim("[Library]")} ${s.name} \u2014 ${truncate2(s.description, 60)}`,
|
|
276
|
+
value: s
|
|
277
|
+
}))
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const teamMap = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const s of teamSkills) {
|
|
282
|
+
const team = s.teamName || "unknown";
|
|
283
|
+
const existing = teamMap.get(team) || [];
|
|
284
|
+
existing.push(s);
|
|
285
|
+
teamMap.set(team, existing);
|
|
286
|
+
}
|
|
287
|
+
for (const [team, teamSkillList] of teamMap) {
|
|
288
|
+
choices.push(
|
|
289
|
+
...teamSkillList.map((s) => ({
|
|
290
|
+
name: `${chalk2.dim(`[${formatTeamName2(team)}]`)} ${s.name} \u2014 ${truncate2(s.description, 60)}`,
|
|
291
|
+
value: s
|
|
292
|
+
}))
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return checkbox({
|
|
296
|
+
message: "Select skills to install:",
|
|
297
|
+
choices,
|
|
298
|
+
required: true
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
async function promptSelectTeam(teams) {
|
|
302
|
+
return select({
|
|
303
|
+
message: "Select a team:",
|
|
304
|
+
choices: teams.map((t) => ({
|
|
305
|
+
name: `${formatTeamName2(t.name)} (${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""})`,
|
|
306
|
+
value: t
|
|
307
|
+
}))
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
async function promptSelectClients(allClients, detectedClients) {
|
|
311
|
+
const detectedNames = new Set(detectedClients.map((c) => c.name));
|
|
312
|
+
return checkbox({
|
|
313
|
+
message: "Select LLM clients for stub installation:",
|
|
314
|
+
choices: allClients.map((c) => ({
|
|
315
|
+
name: detectedNames.has(c.name) ? `${c.name} ${chalk2.green("(detected)")}` : c.name,
|
|
316
|
+
value: c,
|
|
317
|
+
checked: detectedNames.has(c.name)
|
|
318
|
+
}))
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function promptIncludeLibrary() {
|
|
322
|
+
return confirm({
|
|
323
|
+
message: "Also install library skills?",
|
|
324
|
+
default: false
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
async function promptConfirmInstall(skills, clients, targetDir) {
|
|
328
|
+
console.log();
|
|
329
|
+
console.log(chalk2.bold("Install summary:"));
|
|
330
|
+
console.log(` Skills: ${skills.map((s) => s.name).join(", ")}`);
|
|
331
|
+
console.log(` Clients: ${clients.map((c) => c.name).join(", ") || chalk2.dim("none")}`);
|
|
332
|
+
console.log(` Target: ${targetDir}`);
|
|
333
|
+
console.log();
|
|
334
|
+
return confirm({ message: "Proceed with installation?", default: true });
|
|
335
|
+
}
|
|
336
|
+
async function promptConflict(filePath) {
|
|
337
|
+
const answer = await select({
|
|
338
|
+
message: `File already exists with different content: ${chalk2.yellow(filePath)}`,
|
|
339
|
+
choices: [
|
|
340
|
+
{ name: "Overwrite", value: "overwrite" },
|
|
341
|
+
{ name: "Skip", value: "skip" }
|
|
342
|
+
]
|
|
343
|
+
});
|
|
344
|
+
return answer;
|
|
345
|
+
}
|
|
346
|
+
function truncate2(str, max) {
|
|
347
|
+
if (str.length <= max) return str;
|
|
348
|
+
return str.slice(0, max - 3) + "...";
|
|
349
|
+
}
|
|
350
|
+
function formatTeamName2(name) {
|
|
351
|
+
return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/commands/install.ts
|
|
355
|
+
function printResults(results, options) {
|
|
356
|
+
const prefix = options.dryRun ? chalk3.yellow("[DRY RUN] ") : "";
|
|
357
|
+
const createdWord = options.dryRun ? "would create" : "created";
|
|
358
|
+
const skippedWord = "up to date";
|
|
359
|
+
const overwrittenWord = options.dryRun ? "would overwrite" : "overwritten";
|
|
360
|
+
console.log(`
|
|
361
|
+
${prefix}${chalk3.bold("Install complete")}
|
|
362
|
+
`);
|
|
363
|
+
console.log(" Skill Status");
|
|
364
|
+
console.log(" " + "\u2500".repeat(50));
|
|
365
|
+
for (const r of results) {
|
|
366
|
+
const parts = [];
|
|
367
|
+
if (r.created.length > 0) parts.push(chalk3.green(`${r.created.length} ${createdWord}`));
|
|
368
|
+
if (r.skipped.length > 0) parts.push(chalk3.dim(`${r.skipped.length} ${skippedWord}`));
|
|
369
|
+
if (r.overwritten.length > 0) parts.push(chalk3.yellow(`${r.overwritten.length} ${overwrittenWord}`));
|
|
370
|
+
console.log(` ${chalk3.cyan(r.skill.padEnd(24))} ${parts.join(", ")}`);
|
|
371
|
+
}
|
|
372
|
+
console.log(`
|
|
373
|
+
Target: ${options.targetDir}`);
|
|
374
|
+
console.log(` Clients: ${options.clients.map((c) => c.name).join(", ") || chalk3.dim("none")}`);
|
|
375
|
+
console.log();
|
|
376
|
+
}
|
|
377
|
+
async function installSkillCmd(name, _opts, cmd) {
|
|
378
|
+
const parentOpts = cmd.parent.opts();
|
|
379
|
+
const force = Boolean(parentOpts.force);
|
|
380
|
+
const skipExisting = Boolean(parentOpts.skipExisting);
|
|
381
|
+
const dryRun = Boolean(parentOpts.dryRun);
|
|
382
|
+
const targetDir = path4.resolve(
|
|
383
|
+
parentOpts.target || process.cwd()
|
|
384
|
+
);
|
|
385
|
+
const skill = await findSkill(name);
|
|
386
|
+
if (!skill) {
|
|
387
|
+
const all = await discoverSkills();
|
|
388
|
+
console.log(chalk3.red(`Skill "${name}" not found.`));
|
|
389
|
+
console.log(`Available skills: ${all.map((s) => s.name).join(", ")}`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
const detected = await detectClients(targetDir);
|
|
393
|
+
let clients;
|
|
394
|
+
if (force) {
|
|
395
|
+
clients = detected.length > 0 ? detected : ALL_CLIENTS;
|
|
396
|
+
} else {
|
|
397
|
+
clients = await promptSelectClients(ALL_CLIENTS, detected);
|
|
398
|
+
}
|
|
399
|
+
const options = {
|
|
400
|
+
targetDir,
|
|
401
|
+
clients,
|
|
402
|
+
force,
|
|
403
|
+
skipExisting,
|
|
404
|
+
dryRun
|
|
405
|
+
};
|
|
406
|
+
const result = await installSkill(skill, options, force || skipExisting ? void 0 : promptConflict);
|
|
407
|
+
printResults([result], options);
|
|
408
|
+
}
|
|
409
|
+
async function installTeamCmd(name, _opts, cmd) {
|
|
410
|
+
const parentOpts = cmd.parent.opts();
|
|
411
|
+
const force = Boolean(parentOpts.force);
|
|
412
|
+
const skipExisting = Boolean(parentOpts.skipExisting);
|
|
413
|
+
const dryRun = Boolean(parentOpts.dryRun);
|
|
414
|
+
const targetDir = path4.resolve(
|
|
415
|
+
parentOpts.target || process.cwd()
|
|
416
|
+
);
|
|
417
|
+
const teams = await discoverTeams();
|
|
418
|
+
const team = teams.find(
|
|
419
|
+
(t) => t.name === name || t.name.toLowerCase() === name.toLowerCase()
|
|
420
|
+
);
|
|
421
|
+
if (!team) {
|
|
422
|
+
console.log(chalk3.red(`Team "${name}" not found.`));
|
|
423
|
+
console.log(`Available teams: ${teams.map((t) => t.name).join(", ")}`);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
let skillsToInstall = [...team.skills];
|
|
427
|
+
const includeLibrary = await promptIncludeLibrary();
|
|
428
|
+
if (includeLibrary) {
|
|
429
|
+
const allSkills = await discoverSkills();
|
|
430
|
+
const librarySkills = allSkills.filter((s) => s.sourceType === "library");
|
|
431
|
+
skillsToInstall.push(...librarySkills);
|
|
432
|
+
}
|
|
433
|
+
const detected = await detectClients(targetDir);
|
|
434
|
+
let clients;
|
|
435
|
+
if (force) {
|
|
436
|
+
clients = detected.length > 0 ? detected : ALL_CLIENTS;
|
|
437
|
+
} else {
|
|
438
|
+
clients = await promptSelectClients(ALL_CLIENTS, detected);
|
|
439
|
+
}
|
|
440
|
+
const options = {
|
|
441
|
+
targetDir,
|
|
442
|
+
clients,
|
|
443
|
+
force,
|
|
444
|
+
skipExisting,
|
|
445
|
+
dryRun
|
|
446
|
+
};
|
|
447
|
+
const results = await installSkills(
|
|
448
|
+
skillsToInstall,
|
|
449
|
+
options,
|
|
450
|
+
force || skipExisting ? void 0 : promptConflict
|
|
451
|
+
);
|
|
452
|
+
printResults(results, options);
|
|
453
|
+
}
|
|
454
|
+
function registerInstallCommand(program2) {
|
|
455
|
+
const install = program2.command("install").description("Install skills").option("--force", "Overwrite existing files without prompting").option("--skip-existing", "Skip files that already exist without prompting").option("--dry-run", "Show what would be installed without writing files").option("--target <dir>", "Install to a different directory (default: CWD)");
|
|
456
|
+
install.command("skill <name>").description("Install a specific skill").action(installSkillCmd);
|
|
457
|
+
install.command("team <name>").description("Install all skills for a team").action(installTeamCmd);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/commands/interactive.ts
|
|
461
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
462
|
+
import chalk4 from "chalk";
|
|
463
|
+
function printResults2(results, options) {
|
|
464
|
+
const prefix = options.dryRun ? chalk4.yellow("[DRY RUN] ") : "";
|
|
465
|
+
const createdWord = options.dryRun ? "would create" : "created";
|
|
466
|
+
const skippedWord = "up to date";
|
|
467
|
+
const overwrittenWord = options.dryRun ? "would overwrite" : "overwritten";
|
|
468
|
+
console.log(`
|
|
469
|
+
${prefix}${chalk4.bold("Install complete")}
|
|
470
|
+
`);
|
|
471
|
+
console.log(" Skill Status");
|
|
472
|
+
console.log(" " + "\u2500".repeat(50));
|
|
473
|
+
for (const r of results) {
|
|
474
|
+
const parts = [];
|
|
475
|
+
if (r.created.length > 0) parts.push(chalk4.green(`${r.created.length} ${createdWord}`));
|
|
476
|
+
if (r.skipped.length > 0) parts.push(chalk4.dim(`${r.skipped.length} ${skippedWord}`));
|
|
477
|
+
if (r.overwritten.length > 0) parts.push(chalk4.yellow(`${r.overwritten.length} ${overwrittenWord}`));
|
|
478
|
+
console.log(` ${chalk4.cyan(r.skill.padEnd(24))} ${parts.join(", ")}`);
|
|
479
|
+
}
|
|
480
|
+
console.log(`
|
|
481
|
+
Target: ${options.targetDir}`);
|
|
482
|
+
console.log(` Clients: ${options.clients.map((c) => c.name).join(", ") || chalk4.dim("none")}`);
|
|
483
|
+
console.log();
|
|
484
|
+
}
|
|
485
|
+
async function listAll2() {
|
|
486
|
+
const skills = await discoverSkills();
|
|
487
|
+
const teams = await discoverTeams();
|
|
488
|
+
if (skills.length === 0) {
|
|
489
|
+
console.log(chalk4.yellow("No skills found in package. This may indicate a build issue."));
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const librarySkills = skills.filter((s) => s.sourceType === "library");
|
|
493
|
+
const teamSkills = skills.filter((s) => s.sourceType === "team");
|
|
494
|
+
const formatTeamName3 = (name) => name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
495
|
+
const truncate3 = (str, max) => str.length <= max ? str : str.slice(0, max - 3) + "...";
|
|
496
|
+
if (librarySkills.length > 0) {
|
|
497
|
+
console.log(chalk4.bold("\nLibrary"));
|
|
498
|
+
for (const s of librarySkills) {
|
|
499
|
+
console.log(` ${chalk4.cyan(s.name.padEnd(24))} ${truncate3(s.description, 60)}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const teamMap = /* @__PURE__ */ new Map();
|
|
503
|
+
for (const s of teamSkills) {
|
|
504
|
+
const team = s.teamName || "unknown";
|
|
505
|
+
const existing = teamMap.get(team) || [];
|
|
506
|
+
existing.push(s);
|
|
507
|
+
teamMap.set(team, existing);
|
|
508
|
+
}
|
|
509
|
+
for (const [team, skillList] of teamMap) {
|
|
510
|
+
console.log(chalk4.bold(`
|
|
511
|
+
${formatTeamName3(team)}`));
|
|
512
|
+
for (const s of skillList) {
|
|
513
|
+
console.log(` ${chalk4.cyan(s.name.padEnd(24))} ${truncate3(s.description, 60)}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (teams.length > 0) {
|
|
517
|
+
console.log(chalk4.bold("\nTeams"));
|
|
518
|
+
for (const t of teams) {
|
|
519
|
+
console.log(
|
|
520
|
+
` ${chalk4.cyan(formatTeamName3(t.name).padEnd(24))} ${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
console.log();
|
|
525
|
+
}
|
|
526
|
+
async function runInteractive() {
|
|
527
|
+
console.log(chalk4.bold("\n@groupby/ai-dev \u2014 AI Skills Installer\n"));
|
|
528
|
+
const action = await select2({
|
|
529
|
+
message: "What would you like to do?",
|
|
530
|
+
choices: [
|
|
531
|
+
{ name: "Install skill(s)", value: "install-skills" },
|
|
532
|
+
{ name: "Install team skills", value: "install-team" },
|
|
533
|
+
{ name: "List available skills & teams", value: "list" }
|
|
534
|
+
]
|
|
535
|
+
});
|
|
536
|
+
if (action === "list") {
|
|
537
|
+
await listAll2();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const targetDir = process.cwd();
|
|
541
|
+
if (action === "install-skills") {
|
|
542
|
+
const allSkills = await discoverSkills();
|
|
543
|
+
if (allSkills.length === 0) {
|
|
544
|
+
console.log(chalk4.yellow("No skills found in package. This may indicate a build issue."));
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
const selectedSkills = await promptSelectSkills(allSkills);
|
|
548
|
+
const detected = await detectClients(targetDir);
|
|
549
|
+
const clients = await promptSelectClients(ALL_CLIENTS, detected);
|
|
550
|
+
const confirmed = await promptConfirmInstall(selectedSkills, clients, targetDir);
|
|
551
|
+
if (!confirmed) {
|
|
552
|
+
console.log(chalk4.dim("Cancelled."));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const options = {
|
|
556
|
+
targetDir,
|
|
557
|
+
clients,
|
|
558
|
+
force: false,
|
|
559
|
+
skipExisting: false,
|
|
560
|
+
dryRun: false
|
|
561
|
+
};
|
|
562
|
+
const results = await installSkills(selectedSkills, options, promptConflict);
|
|
563
|
+
printResults2(results, options);
|
|
564
|
+
} else if (action === "install-team") {
|
|
565
|
+
const teams = await discoverTeams();
|
|
566
|
+
if (teams.length === 0) {
|
|
567
|
+
console.log(chalk4.yellow("No teams found."));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const team = await promptSelectTeam(teams);
|
|
571
|
+
let skillsToInstall = [...team.skills];
|
|
572
|
+
const includeLibrary = await promptIncludeLibrary();
|
|
573
|
+
if (includeLibrary) {
|
|
574
|
+
const allSkills = await discoverSkills();
|
|
575
|
+
const librarySkills = allSkills.filter((s) => s.sourceType === "library");
|
|
576
|
+
skillsToInstall.push(...librarySkills);
|
|
577
|
+
}
|
|
578
|
+
const detected = await detectClients(targetDir);
|
|
579
|
+
const clients = await promptSelectClients(ALL_CLIENTS, detected);
|
|
580
|
+
const confirmed = await promptConfirmInstall(skillsToInstall, clients, targetDir);
|
|
581
|
+
if (!confirmed) {
|
|
582
|
+
console.log(chalk4.dim("Cancelled."));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const options = {
|
|
586
|
+
targetDir,
|
|
587
|
+
clients,
|
|
588
|
+
force: false,
|
|
589
|
+
skipExisting: false,
|
|
590
|
+
dryRun: false
|
|
591
|
+
};
|
|
592
|
+
const results = await installSkills(skillsToInstall, options, promptConflict);
|
|
593
|
+
printResults2(results, options);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/index.ts
|
|
598
|
+
var program = new Command();
|
|
599
|
+
program.name("ai-dev").description("Interactive installer for GroupBy AI development skills").version("0.1.0").action(runInteractive);
|
|
600
|
+
registerListCommand(program);
|
|
601
|
+
registerInstallCommand(program);
|
|
602
|
+
program.addHelpText(
|
|
603
|
+
"after",
|
|
604
|
+
`
|
|
605
|
+
Examples:
|
|
606
|
+
$ npx @groupby/ai-dev Interactive mode
|
|
607
|
+
$ npx @groupby/ai-dev list List skills and teams
|
|
608
|
+
$ npx @groupby/ai-dev install skill frontend-design Install a single skill
|
|
609
|
+
$ npx @groupby/ai-dev install team brain-studio Install all team skills
|
|
610
|
+
$ npx @groupby/ai-dev install team brain-studio --dry-run`
|
|
611
|
+
);
|
|
612
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@groupby/ai-dev",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive installer for Rezolve Ai development skills",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-dev": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"skills/",
|
|
12
|
+
"teams/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"prebuild": "cp -r ../skills ./skills && cp -r ../teams ./teams",
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/rezolved/ai-dev-shared.git",
|
|
22
|
+
"directory": "cli"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "restricted"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"commander": "^13.0.0",
|
|
32
|
+
"@inquirer/prompts": "^7.0.0",
|
|
33
|
+
"chalk": "^5.3.0",
|
|
34
|
+
"gray-matter": "^4.0.3",
|
|
35
|
+
"fast-glob": "^3.3.0",
|
|
36
|
+
"fs-extra": "^11.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"typescript": "^5.5.0",
|
|
40
|
+
"@types/node": "^20.0.0",
|
|
41
|
+
"@types/fs-extra": "^11.0.0",
|
|
42
|
+
"tsup": "^8.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|