feat(skills): expose list command for skill visibility
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import type { Command } from 'commander';
|
||||
import { resolve } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import type { Skill } from '../skills/index.js';
|
||||
import { loadAllSkills } from '../skills/index.js';
|
||||
import { loadConfigSafe } from './shared.js';
|
||||
|
||||
export interface SkillListRow {
|
||||
name: string;
|
||||
tier: Skill['manifest']['tier'];
|
||||
status: 'available' | 'unavailable';
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export function toSkillListRows(skills: Skill[]): SkillListRow[] {
|
||||
return skills
|
||||
.map((skill) => ({
|
||||
name: skill.manifest.name,
|
||||
tier: skill.manifest.tier,
|
||||
status: (skill.available ? 'available' : 'unavailable') as SkillListRow['status'],
|
||||
reason: skill.unavailableReasons?.join('; '),
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
export function renderSkillsTable(rows: SkillListRow[]): string {
|
||||
if (rows.length === 0) {
|
||||
return 'No skills found.';
|
||||
}
|
||||
|
||||
const nameWidth = Math.max('NAME'.length, ...rows.map((row) => row.name.length));
|
||||
const tierWidth = Math.max('TIER'.length, ...rows.map((row) => row.tier.length));
|
||||
const statusWidth = Math.max('STATUS'.length, ...rows.map((row) => row.status.length));
|
||||
|
||||
const lines = [
|
||||
`${'NAME'.padEnd(nameWidth)} ${'TIER'.padEnd(tierWidth)} ${'STATUS'.padEnd(statusWidth)} REASON`,
|
||||
`${'-'.repeat(nameWidth)} ${'-'.repeat(tierWidth)} ${'-'.repeat(statusWidth)} ------`,
|
||||
];
|
||||
|
||||
for (const row of rows) {
|
||||
lines.push(
|
||||
`${row.name.padEnd(nameWidth)} ${row.tier.padEnd(tierWidth)} ${row.status.padEnd(statusWidth)} ${row.reason ?? ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function registerSkillsCommand(program: Command): void {
|
||||
const skills = program
|
||||
.command('skills')
|
||||
.description('Manage Flynn skills')
|
||||
.action(() => {
|
||||
skills.outputHelp();
|
||||
});
|
||||
|
||||
skills
|
||||
.command('list')
|
||||
.description('List discovered skills')
|
||||
.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');
|
||||
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);
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(rows, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(renderSkillsTable(rows));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user