feat(skills): add installer execution stub command
This commit is contained in:
+11
-2
@@ -1388,6 +1388,15 @@
|
||||
"src/cli/skills.test.ts"
|
||||
],
|
||||
"test_status": "pnpm typecheck + pnpm test:run src/cli/skills.test.ts + pnpm test:run + pnpm lint (warnings only, 0 errors) + pnpm build passing"
|
||||
},
|
||||
"installer_execution_stub_command": {
|
||||
"status": "completed",
|
||||
"description": "Added skills execute command that consumes installer plans and reports stub execution output without running package-manager commands",
|
||||
"files_modified": [
|
||||
"src/cli/skills.ts",
|
||||
"src/cli/skills.test.ts"
|
||||
],
|
||||
"test_status": "pnpm typecheck + pnpm test:run src/cli/skills.test.ts + pnpm test:run + pnpm lint (warnings only, 0 errors) + pnpm build passing"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1416,7 +1425,7 @@
|
||||
},
|
||||
|
||||
"overall_progress": {
|
||||
"total_test_count": 1529,
|
||||
"total_test_count": 1531,
|
||||
"all_tests_passing": true,
|
||||
"p0_completion": "3/3 (100%)",
|
||||
"p1_completion": "4/4 (100%)",
|
||||
@@ -1436,7 +1445,7 @@
|
||||
"gmail_auth_cli": "flynn gmail-auth command implemented with OAuth2 flow, doctor check, config routed to Telegram",
|
||||
"native_audio_support": "completed — smart routing for native audio (Gemini/OpenAI/GitHub) vs Whisper transcription fallback",
|
||||
"remaining_phases_completion": "Phase 1: 3/3 (100%) — context levels, command registry, memory structure. Phase 2: 2/2 (100%) — component registry, confidence routing. Phase 3: 2/2 (100%) — adaptive memory/compaction, truthfulness/autonomy hardening",
|
||||
"next_up": "Skills infrastructure Phase 3: add installer execution stub command that consumes plan output but does not run package manager commands yet"
|
||||
"next_up": "Skills infrastructure Phase 3: add shared install action modes (plan-only/stub/install) to reduce CLI duplication while keeping execution disabled"
|
||||
},
|
||||
"soul_md_and_cron_create": {
|
||||
"date": "2026-02-11",
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
renderSkillInstallerPlan,
|
||||
toSkillInstallPreflightView,
|
||||
renderSkillInstallPreflight,
|
||||
toSkillInstallerExecutionStubView,
|
||||
renderSkillInstallerExecutionStub,
|
||||
} from './skills.js';
|
||||
import type { Skill } from '../skills/index.js';
|
||||
|
||||
@@ -212,6 +214,39 @@ describe('skills CLI helpers', () => {
|
||||
expect(output).toContain('[download] download https://example.com/tool.tgz -> <default destination>');
|
||||
});
|
||||
|
||||
it('builds installer execution stub view from skill plan', () => {
|
||||
const view = toSkillInstallerExecutionStubView(
|
||||
buildSkill({
|
||||
manifest: {
|
||||
name: 'exec-stub',
|
||||
description: 'Execution stub test',
|
||||
version: '1.1.0',
|
||||
tier: 'managed',
|
||||
installers: [{ type: 'download', url: 'https://example.com/tool.tgz' }],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(view.execution).toBe('stub');
|
||||
expect(view.wouldRun.length).toBe(1);
|
||||
expect(view.wouldRun[0]).toContain('download https://example.com/tool.tgz');
|
||||
});
|
||||
|
||||
it('renders installer execution stub output text', () => {
|
||||
const output = renderSkillInstallerExecutionStub({
|
||||
skill: { name: 'exec-stub', tier: 'bundled', version: '1.0.0' },
|
||||
execution: 'stub',
|
||||
wouldRun: ['brew install jq'],
|
||||
skipped: [{ installerType: 'node', reason: 'neither pnpm nor npm available in PATH' }],
|
||||
});
|
||||
|
||||
expect(output).toContain("Installer execution stub for 'exec-stub'");
|
||||
expect(output).toContain('No installer commands were executed.');
|
||||
expect(output).toContain('Would run:');
|
||||
expect(output).toContain('- brew install jq');
|
||||
expect(output).toContain('Skipped:');
|
||||
});
|
||||
|
||||
it('summarizes refresh counts across status and tiers', () => {
|
||||
const summary = summarizeSkillsRefresh([
|
||||
buildSkill({ manifest: { name: 'a', description: 'a', version: '1.0.0', tier: 'bundled' } }),
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user