Skip to content

Commit 835c827

Browse files
committed
feat(pgpm): install skills from .boilerplate.json after scaffold
Add BoilerplateSkill type and skills field to BoilerplateConfig so templates can declare agent skills to install post-scaffold. After scaffoldTemplate completes, inspectTemplate reads the config and runs 'npx skills add' for each declared skill. On failure, prints manual install commands instead of aborting. Wired into both workspace init and module init flows.
1 parent d6a34a5 commit 835c827

2 files changed

Lines changed: 76 additions & 2 deletions

File tree

pgpm/cli/src/commands/init/index.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { execSync } from 'child_process';
12
import fs from 'fs';
23
import path from 'path';
34

45
import {
6+
BoilerplateSkill,
57
DEFAULT_TEMPLATE_REPO,
68
DEFAULT_TEMPLATE_TOOL_NAME,
79
inspectTemplate,
@@ -275,6 +277,38 @@ interface InitContext {
275277
createWorkspace?: boolean;
276278
}
277279

280+
function installSkills(skills: BoilerplateSkill[], cwd: string): void {
281+
const failed: string[] = [];
282+
283+
for (const entry of skills) {
284+
const source = entry.source.includes('://')
285+
? entry.source
286+
: `https://github.com/${entry.source}`;
287+
288+
for (const skill of entry.skills) {
289+
const cmd = `npx --yes skills add ${source} --skill ${skill} --yes`;
290+
try {
291+
execSync(cmd, {
292+
cwd,
293+
stdio: ['pipe', 'inherit', 'inherit'],
294+
timeout: 120_000,
295+
});
296+
} catch {
297+
failed.push(` npx skills add ${source} --skill ${skill}`);
298+
}
299+
}
300+
}
301+
302+
if (failed.length > 0) {
303+
process.stdout.write('\n⚠️ Some skills could not be installed automatically.\n');
304+
process.stdout.write('Run the following commands manually:\n\n');
305+
for (const cmd of failed) {
306+
process.stdout.write(`${cmd}\n`);
307+
}
308+
process.stdout.write('\n');
309+
}
310+
}
311+
278312
async function handleWorkspaceInit(
279313
argv: Partial<Record<string, any>>,
280314
prompter: Inquirerer,
@@ -329,6 +363,19 @@ async function handleWorkspaceInit(
329363
process.stdout.write('\n');
330364
}
331365

366+
// Install skills declared in .boilerplate.json
367+
const templateInfo = inspectTemplate({
368+
fromPath: ctx.fromPath,
369+
templateRepo: ctx.templateRepo,
370+
branch: ctx.branch,
371+
dir: ctx.dir,
372+
cwd: ctx.cwd,
373+
});
374+
if (templateInfo.config?.skills?.length) {
375+
process.stdout.write('\n📦 Installing skills...\n\n');
376+
installSkills(templateInfo.config.skills, targetPath);
377+
}
378+
332379
const relPath = path.relative(process.cwd(), targetPath);
333380
process.stdout.write(`\n✨ Enjoy!\n\ncd ./${relPath}\n`);
334381

@@ -620,6 +667,20 @@ async function handleModuleInit(
620667
process.stdout.write('\n');
621668
}
622669

670+
// Install skills declared in .boilerplate.json
671+
const moduleTemplateInfo = inspectTemplate({
672+
fromPath: ctx.fromPath,
673+
templateRepo: ctx.templateRepo,
674+
branch: ctx.branch,
675+
dir: ctx.dir,
676+
cwd: ctx.cwd,
677+
});
678+
if (moduleTemplateInfo.config?.skills?.length) {
679+
const skillsCwd = project.workspacePath || modulePath;
680+
process.stdout.write('\n📦 Installing skills...\n\n');
681+
installSkills(moduleTemplateInfo.config.skills, skillsCwd);
682+
}
683+
623684
const relPath = path.relative(process.cwd(), modulePath);
624685
process.stdout.write(`\n✨ Enjoy!\n\ncd ./${relPath}\n`);
625686

pgpm/core/src/core/template-scaffold.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@ import type { Inquirerer, Question } from 'inquirerer';
1414
export type WorkspaceType = 'pgpm' | 'pnpm' | 'lerna' | 'npm' | false;
1515

1616
/**
17-
* Extended BoilerplateConfig that adds workspace requirement field.
18-
* This field controls both workspace detection and whether pgpm-specific files are created.
17+
* Declares a skill to install after scaffolding completes.
1918
*/
19+
export interface BoilerplateSkill {
20+
/** GitHub repository (org/repo format) */
21+
source: string;
22+
/** Skill name(s) to install from the source */
23+
skills: string[];
24+
}
25+
2026
export interface BoilerplateConfig extends GenomicBoilerplateConfig {
2127
/**
2228
* Specifies what type of workspace this template requires.
@@ -29,6 +35,13 @@ export interface BoilerplateConfig extends GenomicBoilerplateConfig {
2935
* Defaults to 'pgpm' for 'module' type (backward compatibility), false for others.
3036
*/
3137
requiresWorkspace?: WorkspaceType;
38+
/**
39+
* Skills to install after scaffolding completes.
40+
* Each entry specifies a source repository and skill names to install.
41+
* Runs `npx skills add <source> --skill <name>` for each entry.
42+
* Non-fatal: prints manual install commands on failure.
43+
*/
44+
skills?: BoilerplateSkill[];
3245
}
3346

3447
export interface InspectTemplateOptions {

0 commit comments

Comments
 (0)