feat(skills): add installer execution stub command

This commit is contained in:
William Valentin
2026-02-12 18:26:09 -08:00
parent 1bb791c7dd
commit e8d5d01d4d
3 changed files with 117 additions and 2 deletions
+71
View File
@@ -38,6 +38,13 @@ export interface SkillInstallPreflightView {
skipped: SkillInstallerPlanView['skipped'];
}
export interface SkillInstallerExecutionStubView {
skill: SkillInstallerPlanView['skill'];
execution: 'stub';
wouldRun: string[];
skipped: SkillInstallerPlanView['skipped'];
}
export function toSkillListRows(skills: Skill[]): SkillListRow[] {
return skills
.map((skill) => ({
@@ -196,6 +203,41 @@ export function renderSkillInstallPreflight(view: SkillInstallPreflightView): st
return lines.join('\n');
}
export function toSkillInstallerExecutionStubView(skill: Skill): SkillInstallerExecutionStubView {
const plan = toSkillInstallerPlanView(skill);
return {
skill: plan.skill,
execution: 'stub',
wouldRun: plan.steps.map((step) => step.command),
skipped: plan.skipped,
};
}
export function renderSkillInstallerExecutionStub(view: SkillInstallerExecutionStubView): string {
const lines: string[] = [
`Installer execution stub for '${view.skill.name}' (${view.skill.tier}, v${view.skill.version})`,
'No installer commands were executed.',
];
if (view.wouldRun.length === 0) {
lines.push('Would run: none');
} else {
lines.push('Would run:');
for (const command of view.wouldRun) {
lines.push(`- ${command}`);
}
}
if (view.skipped.length > 0) {
lines.push('Skipped:');
for (const skip of view.skipped) {
lines.push(`- [${skip.installerType}] ${skip.reason}`);
}
}
return lines.join('\n');
}
export function summarizeSkillsRefresh(skills: Skill[]): SkillRefreshSummary {
const summary: SkillRefreshSummary = {
total: skills.length,
@@ -495,4 +537,33 @@ export function registerSkillsCommand(program: Command): void {
console.log(renderSkillInstallerPlan(view));
});
skills
.command('execute <name>')
.description('Preview installer execution steps (stub only; no commands run)')
.option('--json', 'Output as JSON')
.option('-c, --config <path>', 'Config file path')
.action((name: string, opts: { json?: boolean; config?: string }) => {
const loaded = loadSkillsFromConfig(opts.config);
if (loaded.error || !loaded.skills) {
console.error(loaded.error ?? 'Failed to load skills');
process.exitCode = 1;
return;
}
const skill = loaded.skills.find((item) => item.manifest.name === name);
if (!skill) {
console.error(`Skill '${name}' not found.`);
process.exitCode = 1;
return;
}
const view = toSkillInstallerExecutionStubView(skill);
if (opts.json) {
console.log(JSON.stringify(view, null, 2));
return;
}
console.log(renderSkillInstallerExecutionStub(view));
});
}