feat(skills): add info command for skill inspection

This commit is contained in:
William Valentin
2026-02-12 16:44:46 -08:00
parent b3e5aee333
commit 0d84a6bccc
3 changed files with 118 additions and 14 deletions
+73 -11
View File
@@ -46,6 +46,47 @@ export function renderSkillsTable(rows: SkillListRow[]): string {
return lines.join('\n');
}
export function renderSkillInfo(skill: Skill): string {
const lines: string[] = [
`Name: ${skill.manifest.name}`,
`Description: ${skill.manifest.description}`,
`Version: ${skill.manifest.version}`,
`Tier: ${skill.manifest.tier}`,
`Status: ${skill.available ? 'available' : 'unavailable'}`,
`Directory: ${skill.directory}`,
];
if (skill.manifest.author) {
lines.push(`Author: ${skill.manifest.author}`);
}
if (skill.manifest.tools && skill.manifest.tools.length > 0) {
lines.push(`Tools: ${skill.manifest.tools.join(', ')}`);
}
if (skill.unavailableReasons && skill.unavailableReasons.length > 0) {
lines.push(`Unavailable reasons: ${skill.unavailableReasons.join('; ')}`);
}
return lines.join('\n');
}
function loadSkillsFromConfig(configPath?: string): { skills?: Skill[]; error?: string } {
const loaded = loadConfigSafe(configPath);
if (loaded.error || !loaded.config) {
return { error: loaded.error ?? 'Failed to load config' };
}
const defaultManagedDir = resolve(homedir(), '.flynn/workspace/skills');
const skills = loadAllSkills({
bundledDir: loaded.config.skills.bundled_dir,
managedDir: loaded.config.skills.managed_dir ?? defaultManagedDir,
workspaceDir: loaded.config.skills.workspace_dir,
});
return { skills };
}
export function registerSkillsCommand(program: Command): void {
const skills = program
.command('skills')
@@ -60,21 +101,14 @@ export function registerSkillsCommand(program: Command): void {
.option('--json', 'Output as JSON')
.option('-c, --config <path>', 'Config file path')
.action((opts: { json?: boolean; config?: string }) => {
const loaded = loadConfigSafe(opts.config);
if (loaded.error || !loaded.config) {
console.error(loaded.error ?? 'Failed to load config');
const loaded = loadSkillsFromConfig(opts.config);
if (loaded.error || !loaded.skills) {
console.error(loaded.error ?? 'Failed to load skills');
process.exitCode = 1;
return;
}
const defaultManagedDir = resolve(homedir(), '.flynn/workspace/skills');
const skills = loadAllSkills({
bundledDir: loaded.config.skills.bundled_dir,
managedDir: loaded.config.skills.managed_dir ?? defaultManagedDir,
workspaceDir: loaded.config.skills.workspace_dir,
});
const rows = toSkillListRows(skills);
const rows = toSkillListRows(loaded.skills);
if (opts.json) {
console.log(JSON.stringify(rows, null, 2));
return;
@@ -82,4 +116,32 @@ export function registerSkillsCommand(program: Command): void {
console.log(renderSkillsTable(rows));
});
skills
.command('info <name>')
.description('Show details 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;
}
if (opts.json) {
console.log(JSON.stringify(skill, null, 2));
return;
}
console.log(renderSkillInfo(skill));
});
}