From 835c8277393d8e00fe87a5f019fcac46d746b68d Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 6 Jun 2026 18:08:41 +0000 Subject: [PATCH] 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. --- pgpm/cli/src/commands/init/index.ts | 61 +++++++++++++++++++++++++ pgpm/core/src/core/template-scaffold.ts | 17 ++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/pgpm/cli/src/commands/init/index.ts b/pgpm/cli/src/commands/init/index.ts index 0800782699..1e904f2a5b 100644 --- a/pgpm/cli/src/commands/init/index.ts +++ b/pgpm/cli/src/commands/init/index.ts @@ -1,7 +1,9 @@ +import { execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; import { + BoilerplateSkill, DEFAULT_TEMPLATE_REPO, DEFAULT_TEMPLATE_TOOL_NAME, inspectTemplate, @@ -275,6 +277,38 @@ interface InitContext { createWorkspace?: boolean; } +function installSkills(skills: BoilerplateSkill[], cwd: string): void { + const failed: string[] = []; + + for (const entry of skills) { + const source = entry.source.includes('://') + ? entry.source + : `https://github.com/${entry.source}`; + + for (const skill of entry.skills) { + const cmd = `npx --yes skills add ${source} --skill ${skill} --yes`; + try { + execSync(cmd, { + cwd, + stdio: ['pipe', 'inherit', 'inherit'], + timeout: 120_000, + }); + } catch { + failed.push(` npx skills add ${source} --skill ${skill}`); + } + } + } + + if (failed.length > 0) { + process.stdout.write('\n⚠️ Some skills could not be installed automatically.\n'); + process.stdout.write('Run the following commands manually:\n\n'); + for (const cmd of failed) { + process.stdout.write(`${cmd}\n`); + } + process.stdout.write('\n'); + } +} + async function handleWorkspaceInit( argv: Partial>, prompter: Inquirerer, @@ -329,6 +363,19 @@ async function handleWorkspaceInit( process.stdout.write('\n'); } + // Install skills declared in .boilerplate.json + const templateInfo = inspectTemplate({ + fromPath: ctx.fromPath, + templateRepo: ctx.templateRepo, + branch: ctx.branch, + dir: ctx.dir, + cwd: ctx.cwd, + }); + if (templateInfo.config?.skills?.length) { + process.stdout.write('\n📦 Installing skills...\n\n'); + installSkills(templateInfo.config.skills, targetPath); + } + const relPath = path.relative(process.cwd(), targetPath); process.stdout.write(`\n✨ Enjoy!\n\ncd ./${relPath}\n`); @@ -620,6 +667,20 @@ async function handleModuleInit( process.stdout.write('\n'); } + // Install skills declared in .boilerplate.json + const moduleTemplateInfo = inspectTemplate({ + fromPath: ctx.fromPath, + templateRepo: ctx.templateRepo, + branch: ctx.branch, + dir: ctx.dir, + cwd: ctx.cwd, + }); + if (moduleTemplateInfo.config?.skills?.length) { + const skillsCwd = project.workspacePath || modulePath; + process.stdout.write('\n📦 Installing skills...\n\n'); + installSkills(moduleTemplateInfo.config.skills, skillsCwd); + } + const relPath = path.relative(process.cwd(), modulePath); process.stdout.write(`\n✨ Enjoy!\n\ncd ./${relPath}\n`); diff --git a/pgpm/core/src/core/template-scaffold.ts b/pgpm/core/src/core/template-scaffold.ts index 79a6195128..e6fa208e95 100644 --- a/pgpm/core/src/core/template-scaffold.ts +++ b/pgpm/core/src/core/template-scaffold.ts @@ -14,9 +14,15 @@ import type { Inquirerer, Question } from 'inquirerer'; export type WorkspaceType = 'pgpm' | 'pnpm' | 'lerna' | 'npm' | false; /** - * Extended BoilerplateConfig that adds workspace requirement field. - * This field controls both workspace detection and whether pgpm-specific files are created. + * Declares a skill to install after scaffolding completes. */ +export interface BoilerplateSkill { + /** GitHub repository (org/repo format) */ + source: string; + /** Skill name(s) to install from the source */ + skills: string[]; +} + export interface BoilerplateConfig extends GenomicBoilerplateConfig { /** * Specifies what type of workspace this template requires. @@ -29,6 +35,13 @@ export interface BoilerplateConfig extends GenomicBoilerplateConfig { * Defaults to 'pgpm' for 'module' type (backward compatibility), false for others. */ requiresWorkspace?: WorkspaceType; + /** + * Skills to install after scaffolding completes. + * Each entry specifies a source repository and skill names to install. + * Runs `npx skills add --skill ` for each entry. + * Non-fatal: prints manual install commands on failure. + */ + skills?: BoilerplateSkill[]; } export interface InspectTemplateOptions {