@groupby/ai-dev 0.4.1 → 0.5.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/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @groupby/ai-dev
|
|
2
2
|
|
|
3
|
-
Interactive CLI installer for Rezolve Ai development skills.
|
|
3
|
+
Interactive CLI installer for Rezolve Ai development skills, prompts, and resources.
|
|
4
4
|
|
|
5
|
-
Discover, browse, and install shared AI skills (SKILL.md files) into any project with automatic client detection for **GitHub Copilot**, **Claude Code**, and **Codex**.
|
|
5
|
+
Discover, browse, and install shared AI skills (SKILL.md files), team prompts, team resources, and other team content folders into any project with automatic client detection for **GitHub Copilot**, **Claude Code**, and **Codex**.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -28,14 +28,14 @@ npm install --save-dev @groupby/ai-dev
|
|
|
28
28
|
npx @groupby/ai-dev
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Walks you through selecting and installing
|
|
31
|
+
Walks you through selecting and installing AI content via interactive prompts.
|
|
32
32
|
|
|
33
|
-
### List available
|
|
33
|
+
### List available content
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
36
|
npx @groupby/ai-dev list # List everything
|
|
37
37
|
npx @groupby/ai-dev list skills # List skills only
|
|
38
|
-
npx @groupby/ai-dev list teams # List teams
|
|
38
|
+
npx @groupby/ai-dev list teams # List teams and content counts
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
### Install a specific skill
|
|
@@ -44,20 +44,26 @@ npx @groupby/ai-dev list teams # List teams only
|
|
|
44
44
|
npx @groupby/ai-dev install skill <skill-name>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
### Install all
|
|
47
|
+
### Install all team content
|
|
48
48
|
|
|
49
49
|
```sh
|
|
50
50
|
npx @groupby/ai-dev install team <team-name>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
This installs the team's skills, prompts, and resources. A team's `prompts/`
|
|
54
|
+
folder lands beside `skills/` under the AI directory, for example
|
|
55
|
+
`docs/ai/prompts/`. A team's `resources/` folder installs to
|
|
56
|
+
`docs/ai/resources/`. Other direct team content folders follow the same pattern.
|
|
57
|
+
Installing a specific team or toolset skill also installs resources from that
|
|
58
|
+
skill's owning team or toolset. You will also be prompted to optionally include
|
|
59
|
+
skills from the shared library.
|
|
54
60
|
|
|
55
61
|
### Options
|
|
56
62
|
|
|
57
63
|
| Flag | Description |
|
|
58
64
|
| --- | --- |
|
|
59
65
|
| `--target <dir>` | Install into a specific directory (defaults to cwd) |
|
|
60
|
-
| `--ai-dir <path>` | Override the AI
|
|
66
|
+
| `--ai-dir <path>` | Override the AI content directory (defaults to `docs/ai`) |
|
|
61
67
|
| `--dry-run` | Preview what would be installed without writing files |
|
|
62
68
|
| `--force` | Overwrite existing files without prompting |
|
|
63
69
|
| `--skip-existing` | Skip files that already exist |
|
|
@@ -66,12 +72,15 @@ When installing a team's skills, you will be prompted to optionally include skil
|
|
|
66
72
|
Parent-directory segments such as `../shared-ai` are supported so teams can keep
|
|
67
73
|
AI docs in a workspace-level or shared location while client stubs stay in the
|
|
68
74
|
target project. Use `--dry-run` first when trying a new install layout.
|
|
75
|
+
Direct `install` subcommands use these options instead of prompting for a
|
|
76
|
+
location. Interactive mode (`npx @groupby/ai-dev`) prompts for the AI content
|
|
77
|
+
directory before showing the final install summary.
|
|
69
78
|
|
|
70
79
|
## How it works
|
|
71
80
|
|
|
72
|
-
1. **Discovery** — The CLI scans bundled `skills
|
|
81
|
+
1. **Discovery** — The CLI scans bundled `skills/`, `teams/`, and `toolsets/` directories for `SKILL.md` files, team prompts, team resources, and other team content folders.
|
|
73
82
|
2. **Client detection** — It checks the target project for known config directories (`.github/`, `.claude/`, `.agents/`) to determine which LLM clients are in use.
|
|
74
|
-
3. **Installation** — Each selected skill is copied into `<ai-dir>/skills/<name>/` (default: `docs/ai/skills/<name>/`) and a stub `SKILL.md` is generated in each client's skills directory pointing back to the full skill file.
|
|
83
|
+
3. **Installation** — Each selected skill is copied into `<ai-dir>/skills/<name>/` (default: `docs/ai/skills/<name>/`) and a stub `SKILL.md` is generated in each client's skills directory pointing back to the full skill file. Team prompts, resources, and other content folders are copied beside `skills/`, such as `<ai-dir>/prompts/` and `<ai-dir>/resources/`. Installing a skill also installs resources from its owning team or toolset.
|
|
75
84
|
4. **Conflict handling** — If a file already exists and its content differs, you are prompted to overwrite or skip (unless `--force` or `--skip-existing` is set).
|
|
76
85
|
5. **Path patching** — When `--ai-dir` is set to a non-default value, any `docs/ai/` references inside installed `.md` files are replaced with the custom path so LLMs can find resource files at runtime.
|
|
77
86
|
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import fs4 from "fs";
|
|
5
|
+
import path5 from "path";
|
|
6
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4
7
|
import { Command } from "commander";
|
|
5
8
|
|
|
6
9
|
// src/commands/list.ts
|
|
7
10
|
import chalk from "chalk";
|
|
8
|
-
import
|
|
11
|
+
import fg2 from "fast-glob";
|
|
9
12
|
|
|
10
13
|
// src/lib/discovery.ts
|
|
11
14
|
import path from "path";
|
|
@@ -72,20 +75,51 @@ async function discoverSkills() {
|
|
|
72
75
|
}
|
|
73
76
|
return skills;
|
|
74
77
|
}
|
|
78
|
+
async function discoverTeamAssetFolders() {
|
|
79
|
+
const teamDirs = await fg("teams/*", {
|
|
80
|
+
cwd: PACKAGE_ROOT,
|
|
81
|
+
absolute: true,
|
|
82
|
+
onlyDirectories: true
|
|
83
|
+
});
|
|
84
|
+
const assetFolders = [];
|
|
85
|
+
for (const teamDir of teamDirs) {
|
|
86
|
+
const teamName = path.basename(teamDir);
|
|
87
|
+
const entries = await fs.readdir(teamDir, { withFileTypes: true });
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (!entry.isDirectory() || entry.name === "skills") continue;
|
|
90
|
+
assetFolders.push({
|
|
91
|
+
name: entry.name,
|
|
92
|
+
sourcePath: path.join(teamDir, entry.name),
|
|
93
|
+
teamName
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return assetFolders.sort(
|
|
98
|
+
(a, b) => a.teamName.localeCompare(b.teamName) || a.name.localeCompare(b.name)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
75
101
|
async function discoverTeams() {
|
|
76
|
-
const skills = await
|
|
102
|
+
const [skills, assetFolders] = await Promise.all([
|
|
103
|
+
discoverSkills(),
|
|
104
|
+
discoverTeamAssetFolders()
|
|
105
|
+
]);
|
|
77
106
|
const teamMap = /* @__PURE__ */ new Map();
|
|
107
|
+
const ensureTeam = (name) => {
|
|
108
|
+
const existing = teamMap.get(name);
|
|
109
|
+
if (existing) return existing;
|
|
110
|
+
const team = { name, skills: [], assetFolders: [] };
|
|
111
|
+
teamMap.set(name, team);
|
|
112
|
+
return team;
|
|
113
|
+
};
|
|
78
114
|
for (const skill of skills) {
|
|
79
115
|
if (skill.sourceType === "team" && skill.teamName) {
|
|
80
|
-
|
|
81
|
-
existing.push(skill);
|
|
82
|
-
teamMap.set(skill.teamName, existing);
|
|
116
|
+
ensureTeam(skill.teamName).skills.push(skill);
|
|
83
117
|
}
|
|
84
118
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
119
|
+
for (const folder of assetFolders) {
|
|
120
|
+
ensureTeam(folder.teamName).assetFolders.push(folder);
|
|
121
|
+
}
|
|
122
|
+
return Array.from(teamMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
89
123
|
}
|
|
90
124
|
async function discoverToolsets() {
|
|
91
125
|
const skills = await discoverSkills();
|
|
@@ -102,8 +136,11 @@ async function discoverToolsets() {
|
|
|
102
136
|
const resourcesPath = path.join(PACKAGE_ROOT, "toolsets", name, "resources");
|
|
103
137
|
let hasResources = false;
|
|
104
138
|
if (await fs.pathExists(resourcesPath)) {
|
|
105
|
-
const
|
|
106
|
-
|
|
139
|
+
const resourceFiles = await fg("**/*", {
|
|
140
|
+
cwd: resourcesPath,
|
|
141
|
+
onlyFiles: true
|
|
142
|
+
});
|
|
143
|
+
hasResources = resourceFiles.length > 0;
|
|
107
144
|
}
|
|
108
145
|
toolsets.push({ name, skills: toolsetSkills, resourcesPath, hasResources });
|
|
109
146
|
}
|
|
@@ -120,6 +157,48 @@ async function findToolset(name) {
|
|
|
120
157
|
);
|
|
121
158
|
}
|
|
122
159
|
|
|
160
|
+
// src/lib/content-types.ts
|
|
161
|
+
var PROMPTS_FOLDER = "prompts";
|
|
162
|
+
var RESOURCES_FOLDER = "resources";
|
|
163
|
+
function isPromptFolder(folderName) {
|
|
164
|
+
return folderName === PROMPTS_FOLDER;
|
|
165
|
+
}
|
|
166
|
+
function isResourceFolder(folderName) {
|
|
167
|
+
return folderName === RESOURCES_FOLDER;
|
|
168
|
+
}
|
|
169
|
+
function formatTeamContents(team) {
|
|
170
|
+
const parts = [`${team.skills.length} skill${team.skills.length !== 1 ? "s" : ""}`];
|
|
171
|
+
const promptCount = team.assetFolders.filter((folder) => isPromptFolder(folder.name)).length;
|
|
172
|
+
const resourceCount = team.assetFolders.filter((folder) => isResourceFolder(folder.name)).length;
|
|
173
|
+
const genericCount = team.assetFolders.length - promptCount - resourceCount;
|
|
174
|
+
if (promptCount > 0) {
|
|
175
|
+
parts.push(`${promptCount} prompt${promptCount !== 1 ? "s" : ""}`);
|
|
176
|
+
}
|
|
177
|
+
if (resourceCount > 0) {
|
|
178
|
+
parts.push(`${resourceCount} resource${resourceCount !== 1 ? "s" : ""}`);
|
|
179
|
+
}
|
|
180
|
+
if (genericCount > 0) {
|
|
181
|
+
parts.push(`${genericCount} content folder${genericCount !== 1 ? "s" : ""}`);
|
|
182
|
+
}
|
|
183
|
+
return parts.join(", ");
|
|
184
|
+
}
|
|
185
|
+
function partitionTeamAssetFolders(folders) {
|
|
186
|
+
const prompts = [];
|
|
187
|
+
const resources = [];
|
|
188
|
+
const generic = [];
|
|
189
|
+
for (const folder of folders) {
|
|
190
|
+
const folderName = "name" in folder ? folder.name : folder.folder;
|
|
191
|
+
if (isPromptFolder(folderName)) {
|
|
192
|
+
prompts.push(folder);
|
|
193
|
+
} else if (isResourceFolder(folderName)) {
|
|
194
|
+
resources.push(folder);
|
|
195
|
+
} else {
|
|
196
|
+
generic.push(folder);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { prompts, resources, generic };
|
|
200
|
+
}
|
|
201
|
+
|
|
123
202
|
// src/commands/list.ts
|
|
124
203
|
function formatTeamName(name) {
|
|
125
204
|
return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
@@ -128,6 +207,13 @@ function truncate(str, max) {
|
|
|
128
207
|
if (str.length <= max) return str;
|
|
129
208
|
return str.slice(0, max - 3) + "...";
|
|
130
209
|
}
|
|
210
|
+
async function countResourceFiles(resourcesPath) {
|
|
211
|
+
const files = await fg2("**/*", {
|
|
212
|
+
cwd: resourcesPath,
|
|
213
|
+
onlyFiles: true
|
|
214
|
+
});
|
|
215
|
+
return files.length;
|
|
216
|
+
}
|
|
131
217
|
async function listSkills() {
|
|
132
218
|
const skills = await discoverSkills();
|
|
133
219
|
if (skills.length === 0) {
|
|
@@ -182,7 +268,7 @@ async function listTeams() {
|
|
|
182
268
|
console.log(chalk.bold("\nTeams"));
|
|
183
269
|
for (const t of teams) {
|
|
184
270
|
console.log(
|
|
185
|
-
` ${chalk.cyan(formatTeamName(t.name).padEnd(24))} ${t
|
|
271
|
+
` ${chalk.cyan(formatTeamName(t.name).padEnd(24))} ${formatTeamContents(t)}`
|
|
186
272
|
);
|
|
187
273
|
}
|
|
188
274
|
console.log();
|
|
@@ -197,8 +283,7 @@ async function listToolsets() {
|
|
|
197
283
|
for (const t of toolsets) {
|
|
198
284
|
const parts = [`${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}`];
|
|
199
285
|
if (t.hasResources) {
|
|
200
|
-
const
|
|
201
|
-
const count = entries.filter((e) => e !== "README.md").length;
|
|
286
|
+
const count = await countResourceFiles(t.resourcesPath);
|
|
202
287
|
parts.push(`${count} resource${count !== 1 ? "s" : ""}`);
|
|
203
288
|
}
|
|
204
289
|
console.log(` ${chalk.cyan(t.name.padEnd(24))} ${parts.join(", ")}`);
|
|
@@ -212,7 +297,7 @@ async function listAll() {
|
|
|
212
297
|
console.log(chalk.bold("Teams"));
|
|
213
298
|
for (const t of teams) {
|
|
214
299
|
console.log(
|
|
215
|
-
` ${chalk.cyan(formatTeamName(t.name).padEnd(24))} ${t
|
|
300
|
+
` ${chalk.cyan(formatTeamName(t.name).padEnd(24))} ${formatTeamContents(t)}`
|
|
216
301
|
);
|
|
217
302
|
}
|
|
218
303
|
console.log();
|
|
@@ -223,8 +308,7 @@ async function listAll() {
|
|
|
223
308
|
for (const t of toolsets) {
|
|
224
309
|
const parts = [`${t.skills.length} skill${t.skills.length !== 1 ? "s" : ""}`];
|
|
225
310
|
if (t.hasResources) {
|
|
226
|
-
const
|
|
227
|
-
const count = entries.filter((e) => e !== "README.md").length;
|
|
311
|
+
const count = await countResourceFiles(t.resourcesPath);
|
|
228
312
|
parts.push(`${count} resource${count !== 1 ? "s" : ""}`);
|
|
229
313
|
}
|
|
230
314
|
console.log(` ${chalk.cyan(t.name.padEnd(24))} ${parts.join(", ")}`);
|
|
@@ -233,9 +317,9 @@ async function listAll() {
|
|
|
233
317
|
}
|
|
234
318
|
}
|
|
235
319
|
function registerListCommand(program2) {
|
|
236
|
-
const list = program2.command("list").description("List available skills and
|
|
320
|
+
const list = program2.command("list").description("List available skills, teams, and toolsets");
|
|
237
321
|
list.command("skills").description("List all available skills").action(listSkills);
|
|
238
|
-
list.command("teams").description("List teams and
|
|
322
|
+
list.command("teams").description("List teams with skill and content counts").action(listTeams);
|
|
239
323
|
list.command("toolsets").description("List toolsets with skill and resource counts").action(listToolsets);
|
|
240
324
|
list.action(listAll);
|
|
241
325
|
}
|
|
@@ -246,7 +330,7 @@ import chalk3 from "chalk";
|
|
|
246
330
|
|
|
247
331
|
// src/lib/clients.ts
|
|
248
332
|
import path2 from "path";
|
|
249
|
-
import
|
|
333
|
+
import fs2 from "fs-extra";
|
|
250
334
|
var ALL_CLIENTS = [
|
|
251
335
|
{ name: "Copilot", skillsDir: ".github/skills", detectDir: ".github" },
|
|
252
336
|
{ name: "Claude Code", skillsDir: ".claude/skills", detectDir: ".claude" },
|
|
@@ -256,7 +340,7 @@ async function detectClients(targetDir) {
|
|
|
256
340
|
const detected = [];
|
|
257
341
|
for (const client of ALL_CLIENTS) {
|
|
258
342
|
const dirPath = path2.join(targetDir, client.detectDir);
|
|
259
|
-
if (await
|
|
343
|
+
if (await fs2.pathExists(dirPath)) {
|
|
260
344
|
detected.push(client);
|
|
261
345
|
}
|
|
262
346
|
}
|
|
@@ -265,21 +349,22 @@ async function detectClients(targetDir) {
|
|
|
265
349
|
|
|
266
350
|
// src/lib/installer.ts
|
|
267
351
|
import path3 from "path";
|
|
268
|
-
import
|
|
352
|
+
import fs3 from "fs-extra";
|
|
353
|
+
import fg3 from "fast-glob";
|
|
269
354
|
var DEFAULT_AI_DIR = "docs/ai";
|
|
270
355
|
async function handleFile(srcPath, destPath, options, result, onConflict, contentOverride) {
|
|
271
356
|
const relativeDest = path3.relative(options.targetDir, destPath);
|
|
272
|
-
if (await
|
|
273
|
-
const existingContent = await
|
|
274
|
-
const newContent = contentOverride ?? await
|
|
357
|
+
if (await fs3.pathExists(destPath)) {
|
|
358
|
+
const existingContent = await fs3.readFile(destPath, "utf-8");
|
|
359
|
+
const newContent = contentOverride ?? await fs3.readFile(srcPath, "utf-8");
|
|
275
360
|
if (existingContent === newContent) {
|
|
276
361
|
result.skipped.push(relativeDest);
|
|
277
362
|
return;
|
|
278
363
|
}
|
|
279
364
|
if (options.force) {
|
|
280
365
|
if (!options.dryRun) {
|
|
281
|
-
await
|
|
282
|
-
await
|
|
366
|
+
await fs3.ensureDir(path3.dirname(destPath));
|
|
367
|
+
await fs3.writeFile(destPath, newContent);
|
|
283
368
|
}
|
|
284
369
|
result.overwritten.push(relativeDest);
|
|
285
370
|
} else if (options.skipExisting) {
|
|
@@ -288,8 +373,8 @@ async function handleFile(srcPath, destPath, options, result, onConflict, conten
|
|
|
288
373
|
const choice = await onConflict(relativeDest);
|
|
289
374
|
if (choice === "overwrite") {
|
|
290
375
|
if (!options.dryRun) {
|
|
291
|
-
await
|
|
292
|
-
await
|
|
376
|
+
await fs3.ensureDir(path3.dirname(destPath));
|
|
377
|
+
await fs3.writeFile(destPath, newContent);
|
|
293
378
|
}
|
|
294
379
|
result.overwritten.push(relativeDest);
|
|
295
380
|
} else {
|
|
@@ -300,11 +385,11 @@ async function handleFile(srcPath, destPath, options, result, onConflict, conten
|
|
|
300
385
|
}
|
|
301
386
|
} else {
|
|
302
387
|
if (!options.dryRun) {
|
|
303
|
-
await
|
|
388
|
+
await fs3.ensureDir(path3.dirname(destPath));
|
|
304
389
|
if (contentOverride) {
|
|
305
|
-
await
|
|
390
|
+
await fs3.writeFile(destPath, contentOverride);
|
|
306
391
|
} else {
|
|
307
|
-
await
|
|
392
|
+
await fs3.copy(srcPath, destPath);
|
|
308
393
|
}
|
|
309
394
|
}
|
|
310
395
|
result.created.push(relativeDest);
|
|
@@ -313,9 +398,22 @@ async function handleFile(srcPath, destPath, options, result, onConflict, conten
|
|
|
313
398
|
async function patchContent(srcPath, aiDir) {
|
|
314
399
|
if (aiDir === DEFAULT_AI_DIR) return void 0;
|
|
315
400
|
if (!srcPath.endsWith(".md")) return void 0;
|
|
316
|
-
const content = await
|
|
401
|
+
const content = await fs3.readFile(srcPath, "utf-8");
|
|
317
402
|
return content.replaceAll(`${DEFAULT_AI_DIR}/`, `${aiDir}/`);
|
|
318
403
|
}
|
|
404
|
+
async function installDirectoryFiles(sourceDir, destDir, options, result, onConflict) {
|
|
405
|
+
const files = await fg3("**/*", {
|
|
406
|
+
cwd: sourceDir,
|
|
407
|
+
onlyFiles: true,
|
|
408
|
+
dot: true
|
|
409
|
+
});
|
|
410
|
+
for (const file of files) {
|
|
411
|
+
const srcFile = path3.join(sourceDir, file);
|
|
412
|
+
const destFile = path3.join(destDir, file);
|
|
413
|
+
const contentOverride = await patchContent(srcFile, options.aiDir);
|
|
414
|
+
await handleFile(srcFile, destFile, options, result, onConflict, contentOverride);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
319
417
|
async function installSkill(skill, options, onConflict) {
|
|
320
418
|
const result = {
|
|
321
419
|
skill: skill.name,
|
|
@@ -324,10 +422,10 @@ async function installSkill(skill, options, onConflict) {
|
|
|
324
422
|
overwritten: []
|
|
325
423
|
};
|
|
326
424
|
const destFolder = path3.join(options.targetDir, options.aiDir, "skills", skill.name);
|
|
327
|
-
const sourceFiles = await
|
|
425
|
+
const sourceFiles = await fs3.readdir(skill.sourcePath);
|
|
328
426
|
for (const file of sourceFiles) {
|
|
329
427
|
const srcFile = path3.join(skill.sourcePath, file);
|
|
330
|
-
const stat = await
|
|
428
|
+
const stat = await fs3.stat(srcFile);
|
|
331
429
|
if (stat.isFile()) {
|
|
332
430
|
const destFile = path3.join(destFolder, file);
|
|
333
431
|
const contentOverride = await patchContent(srcFile, options.aiDir);
|
|
@@ -353,6 +451,22 @@ async function installSkills(skills, options, onConflict) {
|
|
|
353
451
|
}
|
|
354
452
|
return results;
|
|
355
453
|
}
|
|
454
|
+
async function installTeamAssetFolders(assetFolders, options, onConflict) {
|
|
455
|
+
const results = [];
|
|
456
|
+
for (const folder of assetFolders) {
|
|
457
|
+
const result = {
|
|
458
|
+
team: folder.teamName,
|
|
459
|
+
folder: folder.name,
|
|
460
|
+
created: [],
|
|
461
|
+
skipped: [],
|
|
462
|
+
overwritten: []
|
|
463
|
+
};
|
|
464
|
+
const destFolder = path3.join(options.targetDir, options.aiDir, folder.name);
|
|
465
|
+
await installDirectoryFiles(folder.sourcePath, destFolder, options, result, onConflict);
|
|
466
|
+
results.push(result);
|
|
467
|
+
}
|
|
468
|
+
return results;
|
|
469
|
+
}
|
|
356
470
|
async function installResources(toolset, options, onConflict) {
|
|
357
471
|
const result = {
|
|
358
472
|
toolset: toolset.name,
|
|
@@ -361,24 +475,47 @@ async function installResources(toolset, options, onConflict) {
|
|
|
361
475
|
overwritten: []
|
|
362
476
|
};
|
|
363
477
|
if (!toolset.hasResources) return result;
|
|
364
|
-
const entries = await fs4.readdir(toolset.resourcesPath);
|
|
365
|
-
const resourceFiles = entries.filter((e) => e !== "README.md");
|
|
366
478
|
const destDir = path3.join(options.targetDir, options.aiDir, "resources");
|
|
367
|
-
const fileResult = {
|
|
368
|
-
|
|
369
|
-
const srcFile = path3.join(toolset.resourcesPath, file);
|
|
370
|
-
const stat = await fs4.stat(srcFile);
|
|
371
|
-
if (!stat.isFile()) continue;
|
|
372
|
-
const destFile = path3.join(destDir, file);
|
|
373
|
-
const contentOverride = await patchContent(srcFile, options.aiDir);
|
|
374
|
-
await handleFile(srcFile, destFile, options, fileResult, onConflict, contentOverride);
|
|
375
|
-
}
|
|
479
|
+
const fileResult = { created: [], skipped: [], overwritten: [] };
|
|
480
|
+
await installDirectoryFiles(toolset.resourcesPath, destDir, options, fileResult, onConflict);
|
|
376
481
|
result.created = fileResult.created;
|
|
377
482
|
result.skipped = fileResult.skipped;
|
|
378
483
|
result.overwritten = fileResult.overwritten;
|
|
379
484
|
return result;
|
|
380
485
|
}
|
|
381
486
|
|
|
487
|
+
// src/lib/skill-resource-dependencies.ts
|
|
488
|
+
function findTeamResourceFoldersForSkills(skills, teams) {
|
|
489
|
+
const teamNames = new Set(
|
|
490
|
+
skills.filter((skill) => skill.sourceType === "team" && skill.teamName).map((skill) => skill.teamName)
|
|
491
|
+
);
|
|
492
|
+
const folders = /* @__PURE__ */ new Map();
|
|
493
|
+
for (const team of teams) {
|
|
494
|
+
if (!teamNames.has(team.name)) continue;
|
|
495
|
+
for (const folder of team.assetFolders.filter((f) => isResourceFolder(f.name))) {
|
|
496
|
+
folders.set(`${folder.teamName}/${folder.name}`, folder);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return Array.from(folders.values());
|
|
500
|
+
}
|
|
501
|
+
function findToolsetsForSkills(skills, toolsets) {
|
|
502
|
+
const toolsetNames = new Set(
|
|
503
|
+
skills.filter((skill) => skill.sourceType === "toolset" && skill.toolsetName).map((skill) => skill.toolsetName)
|
|
504
|
+
);
|
|
505
|
+
return toolsets.filter((toolset) => toolsetNames.has(toolset.name) && toolset.hasResources);
|
|
506
|
+
}
|
|
507
|
+
async function installResourceDependenciesForSkills(skills, options, onConflict) {
|
|
508
|
+
const [teams, toolsets] = await Promise.all([discoverTeams(), discoverToolsets()]);
|
|
509
|
+
const teamResourceFolders = findTeamResourceFoldersForSkills(skills, teams);
|
|
510
|
+
const matchingToolsets = findToolsetsForSkills(skills, toolsets);
|
|
511
|
+
const assetResults = await installTeamAssetFolders(teamResourceFolders, options, onConflict);
|
|
512
|
+
const resourceResults = [];
|
|
513
|
+
for (const toolset of matchingToolsets) {
|
|
514
|
+
resourceResults.push(await installResources(toolset, options, onConflict));
|
|
515
|
+
}
|
|
516
|
+
return { resourceResults, assetResults };
|
|
517
|
+
}
|
|
518
|
+
|
|
382
519
|
// src/lib/prompts.ts
|
|
383
520
|
import { select, checkbox, confirm, input } from "@inquirer/prompts";
|
|
384
521
|
import chalk2 from "chalk";
|
|
@@ -435,7 +572,7 @@ async function promptSelectTeam(teams) {
|
|
|
435
572
|
return select({
|
|
436
573
|
message: "Select a team:",
|
|
437
574
|
choices: teams.map((t) => ({
|
|
438
|
-
name: `${formatTeamName2(t.name)} (${t
|
|
575
|
+
name: `${formatTeamName2(t.name)} (${formatTeamContents(t)})`,
|
|
439
576
|
value: t
|
|
440
577
|
}))
|
|
441
578
|
});
|
|
@@ -468,20 +605,30 @@ async function promptIncludeLibrary() {
|
|
|
468
605
|
}
|
|
469
606
|
async function promptInstallDir() {
|
|
470
607
|
const raw = await input({
|
|
471
|
-
message: "Install location for AI
|
|
608
|
+
message: "Install location for AI content:",
|
|
472
609
|
default: "docs/ai"
|
|
473
610
|
});
|
|
474
611
|
return raw.replace(/\/+$/, "") || "docs/ai";
|
|
475
612
|
}
|
|
476
|
-
async function promptConfirmInstall(skills, clients, targetDir, aiDir = "docs/ai") {
|
|
613
|
+
async function promptConfirmInstall(skills, clients, targetDir, aiDir = "docs/ai", assetFolders = []) {
|
|
477
614
|
console.log();
|
|
478
615
|
console.log(chalk2.bold("Install summary:"));
|
|
479
|
-
console.log(` Skills: ${skills.map((s) => s.name).join(", ")}`);
|
|
616
|
+
console.log(` Skills: ${skills.map((s) => s.name).join(", ") || chalk2.dim("none")}`);
|
|
617
|
+
if (assetFolders.length > 0) {
|
|
618
|
+
const { prompts, resources, generic } = partitionTeamAssetFolders(assetFolders);
|
|
619
|
+
if (prompts.length > 0) {
|
|
620
|
+
console.log(` Prompts: ${prompts.map((f) => f.name).join(", ")}`);
|
|
621
|
+
}
|
|
622
|
+
if (resources.length > 0) {
|
|
623
|
+
console.log(` Resources: ${resources.map((f) => f.name).join(", ")}`);
|
|
624
|
+
}
|
|
625
|
+
if (generic.length > 0) {
|
|
626
|
+
console.log(` Content folders: ${generic.map((f) => f.name).join(", ")}`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
480
629
|
console.log(` Clients: ${clients.map((c) => c.name).join(", ") || chalk2.dim("none")}`);
|
|
481
630
|
console.log(` Target: ${targetDir}`);
|
|
482
|
-
|
|
483
|
-
console.log(` AI directory: ${aiDir}`);
|
|
484
|
-
}
|
|
631
|
+
console.log(` AI directory: ${aiDir}`);
|
|
485
632
|
console.log();
|
|
486
633
|
return confirm({ message: "Proceed with installation?", default: true });
|
|
487
634
|
}
|
|
@@ -512,35 +659,66 @@ function normalizeAiDir(raw) {
|
|
|
512
659
|
}
|
|
513
660
|
return trimmed;
|
|
514
661
|
}
|
|
515
|
-
function
|
|
516
|
-
const prefix = options.dryRun ? chalk3.yellow("[DRY RUN] ") : "";
|
|
662
|
+
function formatFileCounts(result, options) {
|
|
517
663
|
const createdWord = options.dryRun ? "would create" : "created";
|
|
518
664
|
const skippedWord = "up to date";
|
|
519
665
|
const overwrittenWord = options.dryRun ? "would overwrite" : "overwritten";
|
|
666
|
+
const parts = [];
|
|
667
|
+
if (result.created.length > 0) parts.push(chalk3.green(`${result.created.length} ${createdWord}`));
|
|
668
|
+
if (result.skipped.length > 0) parts.push(chalk3.dim(`${result.skipped.length} ${skippedWord}`));
|
|
669
|
+
if (result.overwritten.length > 0) parts.push(chalk3.yellow(`${result.overwritten.length} ${overwrittenWord}`));
|
|
670
|
+
return parts.join(", ");
|
|
671
|
+
}
|
|
672
|
+
function printResults(results, options, resourceResults = [], assetResults = []) {
|
|
673
|
+
const prefix = options.dryRun ? chalk3.yellow("[DRY RUN] ") : "";
|
|
520
674
|
console.log(`
|
|
521
675
|
${prefix}${chalk3.bold("Install complete")}
|
|
522
676
|
`);
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
677
|
+
if (resourceResults.length > 0) {
|
|
678
|
+
console.log(" Toolset resources Status");
|
|
679
|
+
console.log(" " + "\u2500".repeat(50));
|
|
680
|
+
for (const r of resourceResults) {
|
|
681
|
+
console.log(` ${chalk3.cyan(r.toolset.padEnd(24))} ${formatFileCounts(r, options)}`);
|
|
682
|
+
}
|
|
683
|
+
console.log();
|
|
684
|
+
}
|
|
685
|
+
if (assetResults.length > 0) {
|
|
686
|
+
const { prompts, resources, generic } = partitionTeamAssetFolders(assetResults);
|
|
687
|
+
if (prompts.length > 0) {
|
|
688
|
+
console.log(" Prompt Status");
|
|
689
|
+
console.log(" " + "\u2500".repeat(50));
|
|
690
|
+
for (const r of prompts) {
|
|
691
|
+
console.log(` ${chalk3.cyan(r.folder.padEnd(24))} ${formatFileCounts(r, options)}`);
|
|
692
|
+
}
|
|
693
|
+
console.log();
|
|
694
|
+
}
|
|
695
|
+
if (resources.length > 0) {
|
|
696
|
+
console.log(" Resource Status");
|
|
697
|
+
console.log(" " + "\u2500".repeat(50));
|
|
698
|
+
for (const r of resources) {
|
|
699
|
+
console.log(` ${chalk3.cyan(r.folder.padEnd(24))} ${formatFileCounts(r, options)}`);
|
|
700
|
+
}
|
|
701
|
+
console.log();
|
|
702
|
+
}
|
|
703
|
+
if (generic.length > 0) {
|
|
704
|
+
console.log(" Content folder Status");
|
|
705
|
+
console.log(" " + "\u2500".repeat(50));
|
|
706
|
+
for (const r of generic) {
|
|
707
|
+
console.log(` ${chalk3.cyan(r.folder.padEnd(24))} ${formatFileCounts(r, options)}`);
|
|
708
|
+
}
|
|
530
709
|
console.log();
|
|
531
710
|
}
|
|
532
711
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
console.log(` ${chalk3.cyan(r.skill.padEnd(24))} ${parts.join(", ")}`);
|
|
712
|
+
if (results.length > 0) {
|
|
713
|
+
console.log(" Skill Status");
|
|
714
|
+
console.log(" " + "\u2500".repeat(50));
|
|
715
|
+
for (const r of results) {
|
|
716
|
+
console.log(` ${chalk3.cyan(r.skill.padEnd(24))} ${formatFileCounts(r, options)}`);
|
|
717
|
+
}
|
|
718
|
+
console.log();
|
|
541
719
|
}
|
|
542
|
-
console.log(`
|
|
543
|
-
|
|
720
|
+
console.log(` Target: ${options.targetDir}`);
|
|
721
|
+
console.log(` AI directory: ${options.aiDir}`);
|
|
544
722
|
console.log(` Clients: ${options.clients.map((c) => c.name).join(", ") || chalk3.dim("none")}`);
|
|
545
723
|
console.log();
|
|
546
724
|
}
|
|
@@ -575,15 +753,10 @@ async function installSkillCmd(name, _opts, cmd) {
|
|
|
575
753
|
skipExisting,
|
|
576
754
|
dryRun
|
|
577
755
|
};
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (toolset && toolset.hasResources) {
|
|
583
|
-
resourceResult = await installResources(toolset, options, force || skipExisting ? void 0 : promptConflict);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
printResults([result], options, resourceResult);
|
|
756
|
+
const conflictHandler = force || skipExisting ? void 0 : promptConflict;
|
|
757
|
+
const resourceDependencies = await installResourceDependenciesForSkills([skill], options, conflictHandler);
|
|
758
|
+
const result = await installSkill(skill, options, conflictHandler);
|
|
759
|
+
printResults([result], options, resourceDependencies.resourceResults, resourceDependencies.assetResults);
|
|
587
760
|
}
|
|
588
761
|
async function installTeamCmd(name, _opts, cmd) {
|
|
589
762
|
const parentOpts = cmd.parent.opts();
|
|
@@ -625,12 +798,18 @@ async function installTeamCmd(name, _opts, cmd) {
|
|
|
625
798
|
skipExisting,
|
|
626
799
|
dryRun
|
|
627
800
|
};
|
|
801
|
+
const conflictHandler = force || skipExisting ? void 0 : promptConflict;
|
|
802
|
+
const assetResults = await installTeamAssetFolders(
|
|
803
|
+
team.assetFolders,
|
|
804
|
+
options,
|
|
805
|
+
conflictHandler
|
|
806
|
+
);
|
|
628
807
|
const results = await installSkills(
|
|
629
808
|
skillsToInstall,
|
|
630
809
|
options,
|
|
631
|
-
|
|
810
|
+
conflictHandler
|
|
632
811
|
);
|
|
633
|
-
printResults(results, options);
|
|
812
|
+
printResults(results, options, [], assetResults);
|
|
634
813
|
}
|
|
635
814
|
async function installToolsetCmd(name, _opts, cmd) {
|
|
636
815
|
const parentOpts = cmd.parent.opts();
|
|
@@ -666,12 +845,12 @@ async function installToolsetCmd(name, _opts, cmd) {
|
|
|
666
845
|
dryRun
|
|
667
846
|
};
|
|
668
847
|
const conflictHandler = force || skipExisting ? void 0 : promptConflict;
|
|
669
|
-
|
|
848
|
+
const resourceResults = [];
|
|
670
849
|
if (toolset.hasResources) {
|
|
671
|
-
|
|
850
|
+
resourceResults.push(await installResources(toolset, options, conflictHandler));
|
|
672
851
|
}
|
|
673
852
|
const results = await installSkills(toolset.skills, options, conflictHandler);
|
|
674
|
-
printResults(results, options,
|
|
853
|
+
printResults(results, options, resourceResults);
|
|
675
854
|
}
|
|
676
855
|
var INHERITED_OPTIONS_HELP = `
|
|
677
856
|
Parent options (pass before subcommand):
|
|
@@ -679,46 +858,77 @@ Parent options (pass before subcommand):
|
|
|
679
858
|
--skip-existing Skip files that already exist without prompting
|
|
680
859
|
--dry-run Show what would be installed without writing files
|
|
681
860
|
--target <dir> Install to a different directory (default: CWD)
|
|
682
|
-
--ai-dir <path> Override the AI
|
|
861
|
+
--ai-dir <path> Override the AI content directory (default: docs/ai)`;
|
|
683
862
|
function registerInstallCommand(program2) {
|
|
684
|
-
const install = program2.command("install").description("Install
|
|
863
|
+
const install = program2.command("install").description("Install AI content").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)").option("--ai-dir <path>", "Override the AI content directory (default: docs/ai)");
|
|
685
864
|
install.command("skill <name>").description("Install a specific skill").addHelpText("after", INHERITED_OPTIONS_HELP).action(installSkillCmd);
|
|
686
|
-
install.command("team <name>").description("Install all skills for a team").addHelpText("after", INHERITED_OPTIONS_HELP).action(installTeamCmd);
|
|
865
|
+
install.command("team <name>").description("Install all skills, prompts, and content folders for a team").addHelpText("after", INHERITED_OPTIONS_HELP).action(installTeamCmd);
|
|
687
866
|
install.command("toolset <name>").description("Install all skills and resources for a toolset").addHelpText("after", INHERITED_OPTIONS_HELP).action(installToolsetCmd);
|
|
688
867
|
}
|
|
689
868
|
|
|
690
869
|
// src/commands/interactive.ts
|
|
691
870
|
import { select as select2 } from "@inquirer/prompts";
|
|
692
871
|
import chalk4 from "chalk";
|
|
693
|
-
function
|
|
694
|
-
const prefix = options.dryRun ? chalk4.yellow("[DRY RUN] ") : "";
|
|
872
|
+
function formatFileCounts2(result, options) {
|
|
695
873
|
const createdWord = options.dryRun ? "would create" : "created";
|
|
696
874
|
const skippedWord = "up to date";
|
|
697
875
|
const overwrittenWord = options.dryRun ? "would overwrite" : "overwritten";
|
|
876
|
+
const parts = [];
|
|
877
|
+
if (result.created.length > 0) parts.push(chalk4.green(`${result.created.length} ${createdWord}`));
|
|
878
|
+
if (result.skipped.length > 0) parts.push(chalk4.dim(`${result.skipped.length} ${skippedWord}`));
|
|
879
|
+
if (result.overwritten.length > 0) parts.push(chalk4.yellow(`${result.overwritten.length} ${overwrittenWord}`));
|
|
880
|
+
return parts.join(", ");
|
|
881
|
+
}
|
|
882
|
+
function printResults2(results, options, resourceResults = [], assetResults = []) {
|
|
883
|
+
const prefix = options.dryRun ? chalk4.yellow("[DRY RUN] ") : "";
|
|
698
884
|
console.log(`
|
|
699
885
|
${prefix}${chalk4.bold("Install complete")}
|
|
700
886
|
`);
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
887
|
+
if (resourceResults.length > 0) {
|
|
888
|
+
console.log(" Toolset resources Status");
|
|
889
|
+
console.log(" " + "\u2500".repeat(50));
|
|
890
|
+
for (const r of resourceResults) {
|
|
891
|
+
console.log(` ${chalk4.cyan(r.toolset.padEnd(24))} ${formatFileCounts2(r, options)}`);
|
|
892
|
+
}
|
|
893
|
+
console.log();
|
|
894
|
+
}
|
|
895
|
+
if (assetResults.length > 0) {
|
|
896
|
+
const { prompts, resources, generic } = partitionTeamAssetFolders(assetResults);
|
|
897
|
+
if (prompts.length > 0) {
|
|
898
|
+
console.log(" Prompt Status");
|
|
899
|
+
console.log(" " + "\u2500".repeat(50));
|
|
900
|
+
for (const r of prompts) {
|
|
901
|
+
console.log(` ${chalk4.cyan(r.folder.padEnd(24))} ${formatFileCounts2(r, options)}`);
|
|
902
|
+
}
|
|
903
|
+
console.log();
|
|
904
|
+
}
|
|
905
|
+
if (resources.length > 0) {
|
|
906
|
+
console.log(" Resource Status");
|
|
907
|
+
console.log(" " + "\u2500".repeat(50));
|
|
908
|
+
for (const r of resources) {
|
|
909
|
+
console.log(` ${chalk4.cyan(r.folder.padEnd(24))} ${formatFileCounts2(r, options)}`);
|
|
910
|
+
}
|
|
911
|
+
console.log();
|
|
912
|
+
}
|
|
913
|
+
if (generic.length > 0) {
|
|
914
|
+
console.log(" Content folder Status");
|
|
915
|
+
console.log(" " + "\u2500".repeat(50));
|
|
916
|
+
for (const r of generic) {
|
|
917
|
+
console.log(` ${chalk4.cyan(r.folder.padEnd(24))} ${formatFileCounts2(r, options)}`);
|
|
918
|
+
}
|
|
708
919
|
console.log();
|
|
709
920
|
}
|
|
710
921
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
console.log(` ${chalk4.cyan(r.skill.padEnd(24))} ${parts.join(", ")}`);
|
|
922
|
+
if (results.length > 0) {
|
|
923
|
+
console.log(" Skill Status");
|
|
924
|
+
console.log(" " + "\u2500".repeat(50));
|
|
925
|
+
for (const r of results) {
|
|
926
|
+
console.log(` ${chalk4.cyan(r.skill.padEnd(24))} ${formatFileCounts2(r, options)}`);
|
|
927
|
+
}
|
|
928
|
+
console.log();
|
|
719
929
|
}
|
|
720
|
-
console.log(`
|
|
721
|
-
|
|
930
|
+
console.log(` Target: ${options.targetDir}`);
|
|
931
|
+
console.log(` AI directory: ${options.aiDir}`);
|
|
722
932
|
console.log(` Clients: ${options.clients.map((c) => c.name).join(", ") || chalk4.dim("none")}`);
|
|
723
933
|
console.log();
|
|
724
934
|
}
|
|
@@ -757,7 +967,7 @@ ${formatTeamName3(team)}`));
|
|
|
757
967
|
console.log(chalk4.bold("\nTeams"));
|
|
758
968
|
for (const t of teams) {
|
|
759
969
|
console.log(
|
|
760
|
-
` ${chalk4.cyan(formatTeamName3(t.name).padEnd(24))} ${t
|
|
970
|
+
` ${chalk4.cyan(formatTeamName3(t.name).padEnd(24))} ${formatTeamContents(t)}`
|
|
761
971
|
);
|
|
762
972
|
}
|
|
763
973
|
}
|
|
@@ -778,7 +988,7 @@ async function runInteractive() {
|
|
|
778
988
|
message: "What would you like to do?",
|
|
779
989
|
choices: [
|
|
780
990
|
{ name: "Install skill(s)", value: "install-skills" },
|
|
781
|
-
{ name: "Install team
|
|
991
|
+
{ name: "Install team content", value: "install-team" },
|
|
782
992
|
{ name: "Install toolset", value: "install-toolset" },
|
|
783
993
|
{ name: "List available skills & teams", value: "list" }
|
|
784
994
|
]
|
|
@@ -811,18 +1021,9 @@ async function runInteractive() {
|
|
|
811
1021
|
skipExisting: false,
|
|
812
1022
|
dryRun: false
|
|
813
1023
|
};
|
|
1024
|
+
const resourceDependencies = await installResourceDependenciesForSkills(selectedSkills, options, promptConflict);
|
|
814
1025
|
const results = await installSkills(selectedSkills, options, promptConflict);
|
|
815
|
-
|
|
816
|
-
const toolsetNames = new Set(
|
|
817
|
-
selectedSkills.filter((s) => s.sourceType === "toolset" && s.toolsetName).map((s) => s.toolsetName)
|
|
818
|
-
);
|
|
819
|
-
for (const tsName of toolsetNames) {
|
|
820
|
-
const toolset = await findToolset(tsName);
|
|
821
|
-
if (toolset && toolset.hasResources) {
|
|
822
|
-
resourceResult = await installResources(toolset, options, promptConflict);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
printResults2(results, options, resourceResult);
|
|
1026
|
+
printResults2(results, options, resourceDependencies.resourceResults, resourceDependencies.assetResults);
|
|
826
1027
|
} else if (action === "install-team") {
|
|
827
1028
|
const teams = await discoverTeams();
|
|
828
1029
|
if (teams.length === 0) {
|
|
@@ -840,7 +1041,7 @@ async function runInteractive() {
|
|
|
840
1041
|
const detected = await detectClients(targetDir);
|
|
841
1042
|
const clients = await promptSelectClients(ALL_CLIENTS, detected);
|
|
842
1043
|
const aiDir = await promptInstallDir();
|
|
843
|
-
const confirmed = await promptConfirmInstall(skillsToInstall, clients, targetDir, aiDir);
|
|
1044
|
+
const confirmed = await promptConfirmInstall(skillsToInstall, clients, targetDir, aiDir, team.assetFolders);
|
|
844
1045
|
if (!confirmed) {
|
|
845
1046
|
console.log(chalk4.dim("Cancelled."));
|
|
846
1047
|
return;
|
|
@@ -853,18 +1054,9 @@ async function runInteractive() {
|
|
|
853
1054
|
skipExisting: false,
|
|
854
1055
|
dryRun: false
|
|
855
1056
|
};
|
|
1057
|
+
const assetResults = await installTeamAssetFolders(team.assetFolders, options, promptConflict);
|
|
856
1058
|
const results = await installSkills(skillsToInstall, options, promptConflict);
|
|
857
|
-
|
|
858
|
-
const toolsetNames = new Set(
|
|
859
|
-
skillsToInstall.filter((s) => s.sourceType === "toolset" && s.toolsetName).map((s) => s.toolsetName)
|
|
860
|
-
);
|
|
861
|
-
for (const tsName of toolsetNames) {
|
|
862
|
-
const toolset = await findToolset(tsName);
|
|
863
|
-
if (toolset && toolset.hasResources) {
|
|
864
|
-
resourceResult = await installResources(toolset, options, promptConflict);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
printResults2(results, options, resourceResult);
|
|
1059
|
+
printResults2(results, options, [], assetResults);
|
|
868
1060
|
} else if (action === "install-toolset") {
|
|
869
1061
|
const toolsets = await discoverToolsets();
|
|
870
1062
|
if (toolsets.length === 0) {
|
|
@@ -888,18 +1080,24 @@ async function runInteractive() {
|
|
|
888
1080
|
skipExisting: false,
|
|
889
1081
|
dryRun: false
|
|
890
1082
|
};
|
|
891
|
-
|
|
1083
|
+
const resourceResults = [];
|
|
892
1084
|
if (toolset.hasResources) {
|
|
893
|
-
|
|
1085
|
+
resourceResults.push(await installResources(toolset, options, promptConflict));
|
|
894
1086
|
}
|
|
895
1087
|
const results = await installSkills(toolset.skills, options, promptConflict);
|
|
896
|
-
printResults2(results, options,
|
|
1088
|
+
printResults2(results, options, resourceResults);
|
|
897
1089
|
}
|
|
898
1090
|
}
|
|
899
1091
|
|
|
900
1092
|
// src/index.ts
|
|
1093
|
+
function getPackageVersion() {
|
|
1094
|
+
const packageRoot = path5.resolve(path5.dirname(fileURLToPath2(import.meta.url)), "..");
|
|
1095
|
+
const packageJsonPath = path5.join(packageRoot, "package.json");
|
|
1096
|
+
const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
|
|
1097
|
+
return packageJson.version || "0.0.0";
|
|
1098
|
+
}
|
|
901
1099
|
var program = new Command();
|
|
902
|
-
program.name("ai-dev").description("Interactive installer for GroupBy AI development
|
|
1100
|
+
program.name("ai-dev").description("Interactive installer for GroupBy AI development content").version(getPackageVersion()).action(runInteractive);
|
|
903
1101
|
registerListCommand(program);
|
|
904
1102
|
registerInstallCommand(program);
|
|
905
1103
|
program.addHelpText(
|
|
@@ -910,7 +1108,7 @@ Examples:
|
|
|
910
1108
|
$ npx @groupby/ai-dev list List skills, teams, and toolsets
|
|
911
1109
|
$ npx @groupby/ai-dev list toolsets List available toolsets
|
|
912
1110
|
$ npx @groupby/ai-dev install skill frontend-design Install a single skill
|
|
913
|
-
$ npx @groupby/ai-dev install team brain-studio Install all team
|
|
1111
|
+
$ npx @groupby/ai-dev install team brain-studio Install all team content
|
|
914
1112
|
$ npx @groupby/ai-dev install toolset rzlv-flow Install all skills + resources for a toolset
|
|
915
1113
|
$ npx @groupby/ai-dev install team brain-studio --dry-run
|
|
916
1114
|
$ npx @groupby/ai-dev install skill frontend-design --ai-dir .ai`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groupby/ai-dev",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Interactive installer for Rezolve Ai development
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Interactive installer for Rezolve Ai development content",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ai-dev": "dist/index.js"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Fix Review Findings
|
|
2
|
+
|
|
3
|
+
Apply actionable findings from `prompts/review-change.md` to the current changes in `components/`.
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Take the review findings/suggestions already produced, implement concrete fixes, run relevant verification, and report
|
|
8
|
+
what was addressed versus what still needs clarification.
|
|
9
|
+
|
|
10
|
+
## Inputs
|
|
11
|
+
|
|
12
|
+
Use the latest review output from this session. If no findings were reported, stop and state that no fix work is needed.
|
|
13
|
+
|
|
14
|
+
If findings are missing required detail (for example no file path, unclear recommendation, or conflicting guidance), ask
|
|
15
|
+
for clarification before editing.
|
|
16
|
+
|
|
17
|
+
## Prioritization Rules
|
|
18
|
+
|
|
19
|
+
Process findings in this order:
|
|
20
|
+
|
|
21
|
+
1. Critical and high-severity correctness/regression issues
|
|
22
|
+
2. Contract/schema/API mismatches
|
|
23
|
+
3. Validation and error-handling gaps
|
|
24
|
+
4. Missing or weak tests
|
|
25
|
+
5. Medium/low maintainability or style items
|
|
26
|
+
|
|
27
|
+
## Implementation Rules
|
|
28
|
+
|
|
29
|
+
- Apply minimal, targeted changes per finding.
|
|
30
|
+
- Keep scope limited to the reviewed change set.
|
|
31
|
+
- Do not perform unrelated refactors.
|
|
32
|
+
- If behavior changes, add or update focused tests.
|
|
33
|
+
- If contract/schema changes are required, update specs/contracts first, then runtime code.
|
|
34
|
+
- If a finding is intentionally not implemented, record why.
|
|
35
|
+
|
|
36
|
+
## Verification
|
|
37
|
+
|
|
38
|
+
Run relevant checks for touched components:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv run pytest test/ -v # bc-agent, bc-mcp
|
|
42
|
+
mix test # bc-api
|
|
43
|
+
mix format --check-formatted # bc-api
|
|
44
|
+
./gradlew :bc-pay-core:test # bc-pay
|
|
45
|
+
./gradlew spotlessCheck # bc-pay
|
|
46
|
+
./gradlew test --no-daemon # bc-pay-stripe
|
|
47
|
+
./gradlew spotlessCheck --no-daemon # bc-pay-stripe
|
|
48
|
+
./gradlew test # bc-magento, bc-salesforce, bc-shopify, bc-webhooks
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
If full verification is too expensive, run focused checks for touched areas and state what was not run.
|
|
52
|
+
|
|
53
|
+
## Final Response
|
|
54
|
+
|
|
55
|
+
Lead with outcome and provide:
|
|
56
|
+
|
|
57
|
+
- Findings addressed (mapped to files changed)
|
|
58
|
+
- Findings deferred or blocked (with reason)
|
|
59
|
+
- Checks run and results
|
|
60
|
+
- Remaining risks
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Review Brain Checkout Component Change
|
|
2
|
+
|
|
3
|
+
Review the current changes in `components/`.
|
|
4
|
+
|
|
5
|
+
## Review Focus
|
|
6
|
+
|
|
7
|
+
- Correctness and regressions
|
|
8
|
+
- Contract compatibility
|
|
9
|
+
- Producer/consumer alignment for events and APIs
|
|
10
|
+
- Validation and error handling
|
|
11
|
+
- Secret leakage in logs/config/examples
|
|
12
|
+
- Missing or weak tests
|
|
13
|
+
- Build/test risk
|
|
14
|
+
|
|
15
|
+
## Output Format
|
|
16
|
+
|
|
17
|
+
Lead with findings:
|
|
18
|
+
|
|
19
|
+
```markdown
|
|
20
|
+
## Findings
|
|
21
|
+
|
|
22
|
+
- Severity: {critical|high|medium|low}
|
|
23
|
+
File: `{path}:{line}`
|
|
24
|
+
Issue: {specific issue}
|
|
25
|
+
Recommendation: {specific fix}
|
|
26
|
+
|
|
27
|
+
## Open Questions
|
|
28
|
+
|
|
29
|
+
- {question or "None"}
|
|
30
|
+
|
|
31
|
+
## Checks
|
|
32
|
+
|
|
33
|
+
- {command}: {result}
|
|
34
|
+
|
|
35
|
+
## Summary
|
|
36
|
+
|
|
37
|
+
{brief summary}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If no issues are found, say that clearly and list residual risks.
|
|
41
|
+
|
|
42
|
+
## Optional Next Step
|
|
43
|
+
|
|
44
|
+
If findings or suggestions are reported and code updates are required, run `prompts/fix-review-findings.md`.
|