From b6cf4b79e782902cde7671ae4d34cdf2bbd64ba2 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Tue, 3 Mar 2026 10:14:31 -0700 Subject: [PATCH] improve `intent-library list` output with table layout and skill hierarchy Port the table + skill tree display from `intent list` (PR #15) to the `intent-library` CLI so both commands share a consistent output format. Co-Authored-By: Claude Opus 4.6 --- packages/intent/src/intent-library.ts | 129 +++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 12 deletions(-) diff --git a/packages/intent/src/intent-library.ts b/packages/intent/src/intent-library.ts index 4fd48292..1e270be6 100644 --- a/packages/intent/src/intent-library.ts +++ b/packages/intent/src/intent-library.ts @@ -2,6 +2,98 @@ import type { LibraryScanResult } from './library-scanner.js' import { scanLibrary } from './library-scanner.js' +// --------------------------------------------------------------------------- +// Display helpers +// --------------------------------------------------------------------------- + +function padColumn(text: string, width: number): string { + return text.length >= width ? text + ' ' : text.padEnd(width) +} + +function printTable(headers: string[], rows: string[][]): void { + const widths = headers.map( + (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length)) + 2, + ) + + const headerLine = headers.map((h, i) => padColumn(h, widths[i]!)).join('') + const separator = widths.map((w) => '─'.repeat(w)).join('') + + console.log(headerLine) + console.log(separator) + for (const row of rows) { + console.log(row.map((cell, i) => padColumn(cell, widths[i]!)).join('')) + } +} + +interface SkillDisplay { + name: string + description: string + type?: string +} + +function printSkillTree( + skills: SkillDisplay[], + opts: { nameWidth: number; showTypes: boolean }, +): void { + const roots: string[] = [] + const children = new Map() + + for (const skill of skills) { + const slashIdx = skill.name.indexOf('/') + if (slashIdx === -1) { + roots.push(skill.name) + } else { + const parent = skill.name.slice(0, slashIdx) + if (!children.has(parent)) children.set(parent, []) + children.get(parent)!.push(skill) + } + } + + if (roots.length === 0) { + for (const skill of skills) { + if (!roots.includes(skill.name)) roots.push(skill.name) + } + } + + for (const rootName of roots) { + const rootSkill = skills.find((s) => s.name === rootName) + if (!rootSkill) continue + + printSkillLine(rootName, rootSkill, 4, opts) + + for (const sub of children.get(rootName) ?? []) { + const childName = sub.name.slice(sub.name.indexOf('/') + 1) + printSkillLine(childName, sub, 6, opts) + } + } +} + +function printSkillLine( + displayName: string, + skill: SkillDisplay, + indent: number, + opts: { nameWidth: number; showTypes: boolean }, +): void { + const nameStr = ' '.repeat(indent) + displayName + const padding = ' '.repeat(Math.max(2, opts.nameWidth - nameStr.length)) + const typeCol = opts.showTypes + ? (skill.type ? `[${skill.type}]` : '').padEnd(14) + : '' + console.log(`${nameStr}${padding}${typeCol}${skill.description}`) +} + +function computeSkillNameWidth(allPackageSkills: SkillDisplay[][]): number { + let max = 0 + for (const skills of allPackageSkills) { + for (const s of skills) { + const slashIdx = s.name.indexOf('/') + const displayName = slashIdx === -1 ? s.name : s.name.slice(slashIdx + 1) + const indent = slashIdx === -1 ? 4 : 6 + max = Math.max(max, indent + displayName.length) + } + } + return max + 2 +} // --------------------------------------------------------------------------- // Commands @@ -25,23 +117,36 @@ async function cmdList(): Promise { return } + const totalSkills = result.packages.reduce( + (sum, p) => sum + p.skills.length, + 0, + ) + console.log( + `\n${result.packages.length} intent-enabled packages, ${totalSkills} skills\n`, + ) + + // Summary table + const rows = result.packages.map((pkg) => [ + pkg.name, + pkg.version, + String(pkg.skills.length), + ]) + printTable(['PACKAGE', 'VERSION', 'SKILLS'], rows) + + // Skills detail + const allSkills = result.packages.map((p) => p.skills) + const nameWidth = computeSkillNameWidth(allSkills) + const showTypes = result.packages.some((p) => p.skills.some((s) => s.type)) + + console.log(`\nSkills:\n`) for (const pkg of result.packages) { - const header = pkg.description - ? `${pkg.name} v${pkg.version} — ${pkg.description}` - : `${pkg.name} v${pkg.version}` - console.log(header) - - for (const skill of pkg.skills) { - const name = skill.name.padEnd(28) - const desc = (skill.description || '').padEnd(52) - console.log(` ${name}${desc}${skill.path}`) - } - + console.log(` ${pkg.name}`) + printSkillTree(pkg.skills, { nameWidth, showTypes }) console.log() } if (result.warnings.length > 0) { - console.log('Warnings:') + console.log(`Warnings:`) for (const w of result.warnings) console.log(` ⚠ ${w}`) } }