feat(skills): add installer plan command output

This commit is contained in:
William Valentin
2026-02-12 18:11:38 -08:00
parent bd754d520e
commit d3ba1328f2
3 changed files with 128 additions and 2 deletions
+81
View File
@@ -19,6 +19,17 @@ export interface SkillRefreshSummary {
tiers: Record<Skill['manifest']['tier'], number>;
}
export interface SkillInstallerPlanView {
skill: {
name: string;
tier: Skill['manifest']['tier'];
version: string;
};
mode: 'dry-run';
steps: Array<{ installerType: string; command: string }>;
skipped: Array<{ installerType: string; reason: string }>;
}
export function toSkillListRows(skills: Skill[]): SkillListRow[] {
return skills
.map((skill) => ({
@@ -95,6 +106,47 @@ export function renderSkillInfo(skill: Skill): string {
return lines.join('\n');
}
export function toSkillInstallerPlanView(skill: Skill): SkillInstallerPlanView {
const plan = buildInstallerPlan(skill.manifest.installers);
return {
skill: {
name: skill.manifest.name,
tier: skill.manifest.tier,
version: skill.manifest.version,
},
mode: plan.mode,
steps: plan.steps,
skipped: plan.skipped,
};
}
export function renderSkillInstallerPlan(view: SkillInstallerPlanView): string {
const lines: string[] = [
`Installer plan for '${view.skill.name}' (${view.skill.tier}, v${view.skill.version})`,
`Mode: ${view.mode}`,
];
if (view.steps.length === 0) {
lines.push('Planned steps: none');
} else {
lines.push('Planned steps:');
for (const step of view.steps) {
lines.push(`- [${step.installerType}] ${step.command}`);
}
}
if (view.skipped.length === 0) {
lines.push('Skipped steps: none');
} else {
lines.push('Skipped steps:');
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,
@@ -319,4 +371,33 @@ export function registerSkillsCommand(program: Command): void {
console.log(renderSkillsRefreshSummary(summary));
});
skills
.command('plan <name>')
.description('Print dry-run installer plan for a skill')
.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 = toSkillInstallerPlanView(skill);
if (opts.json) {
console.log(JSON.stringify(view, null, 2));
return;
}
console.log(renderSkillInstallerPlan(view));
});
}