Skip to content

Commit 0c375eb

Browse files
authored
Merge pull request #15 from TanStack/feat/list-table-output
Improve `intent list` output with table layout and skill hierarchy
2 parents 57fb207 + 97d7fc5 commit 0c375eb

1 file changed

Lines changed: 114 additions & 11 deletions

File tree

packages/intent/src/cli.ts

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,95 @@ function getMetaDir(): string {
2424
// Commands
2525
// ---------------------------------------------------------------------------
2626

27+
function padColumn(text: string, width: number): string {
28+
return text.length >= width ? text + ' ' : text.padEnd(width)
29+
}
30+
31+
function printTable(headers: string[], rows: string[][]): void {
32+
const widths = headers.map(
33+
(h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length)) + 2,
34+
)
35+
36+
const headerLine = headers.map((h, i) => padColumn(h, widths[i]!)).join('')
37+
const separator = widths.map((w) => '─'.repeat(w)).join('')
38+
39+
console.log(headerLine)
40+
console.log(separator)
41+
for (const row of rows) {
42+
console.log(row.map((cell, i) => padColumn(cell, widths[i]!)).join(''))
43+
}
44+
}
45+
46+
interface SkillDisplay {
47+
name: string
48+
description: string
49+
type?: string
50+
}
51+
52+
function printSkillTree(
53+
skills: SkillDisplay[],
54+
opts: { nameWidth: number; showTypes: boolean },
55+
): void {
56+
const roots: string[] = []
57+
const children = new Map<string, SkillDisplay[]>()
58+
59+
for (const skill of skills) {
60+
const slashIdx = skill.name.indexOf('/')
61+
if (slashIdx === -1) {
62+
roots.push(skill.name)
63+
} else {
64+
const parent = skill.name.slice(0, slashIdx)
65+
if (!children.has(parent)) children.set(parent, [])
66+
children.get(parent)!.push(skill)
67+
}
68+
}
69+
70+
if (roots.length === 0) {
71+
for (const skill of skills) {
72+
if (!roots.includes(skill.name)) roots.push(skill.name)
73+
}
74+
}
75+
76+
for (const rootName of roots) {
77+
const rootSkill = skills.find((s) => s.name === rootName)
78+
if (!rootSkill) continue
79+
80+
printSkillLine(rootName, rootSkill, 4, opts)
81+
82+
for (const sub of children.get(rootName) ?? []) {
83+
const childName = sub.name.slice(sub.name.indexOf('/') + 1)
84+
printSkillLine(childName, sub, 6, opts)
85+
}
86+
}
87+
}
88+
89+
function printSkillLine(
90+
displayName: string,
91+
skill: SkillDisplay,
92+
indent: number,
93+
opts: { nameWidth: number; showTypes: boolean },
94+
): void {
95+
const nameStr = ' '.repeat(indent) + displayName
96+
const padding = ' '.repeat(Math.max(2, opts.nameWidth - nameStr.length))
97+
const typeCol = opts.showTypes
98+
? (skill.type ? `[${skill.type}]` : '').padEnd(14)
99+
: ''
100+
console.log(`${nameStr}${padding}${typeCol}${skill.description}`)
101+
}
102+
103+
function computeSkillNameWidth(allPackageSkills: SkillDisplay[][]): number {
104+
let max = 0
105+
for (const skills of allPackageSkills) {
106+
for (const s of skills) {
107+
const slashIdx = s.name.indexOf('/')
108+
const displayName = slashIdx === -1 ? s.name : s.name.slice(slashIdx + 1)
109+
const indent = slashIdx === -1 ? 4 : 6
110+
max = Math.max(max, indent + displayName.length)
111+
}
112+
}
113+
return max + 2
114+
}
115+
27116
async function cmdList(args: string[]): Promise<void> {
28117
const jsonOutput = args.includes('--json')
29118

@@ -49,18 +138,32 @@ async function cmdList(args: string[]): Promise<void> {
49138
return
50139
}
51140

52-
console.log(`Intent-enabled packages (${result.packages.length} found):\n`)
53-
141+
const totalSkills = result.packages.reduce(
142+
(sum, p) => sum + p.skills.length,
143+
0,
144+
)
145+
console.log(
146+
`\n${result.packages.length} intent-enabled packages, ${totalSkills} skills (${result.packageManager})\n`,
147+
)
148+
149+
// Summary table
150+
const rows = result.packages.map((pkg) => [
151+
pkg.name,
152+
pkg.version,
153+
String(pkg.skills.length),
154+
pkg.intent.requires?.join(', ') || '–',
155+
])
156+
printTable(['PACKAGE', 'VERSION', 'SKILLS', 'REQUIRES'], rows)
157+
158+
// Skills detail
159+
const allSkills = result.packages.map((p) => p.skills)
160+
const nameWidth = computeSkillNameWidth(allSkills)
161+
const showTypes = result.packages.some((p) => p.skills.some((s) => s.type))
162+
163+
console.log(`\nSkills:\n`)
54164
for (const pkg of result.packages) {
55-
const reqStr = pkg.intent.requires?.length
56-
? ` (requires: ${pkg.intent.requires.join(', ')})`
57-
: ''
58-
console.log(`${pkg.name} v${pkg.version}${reqStr}`)
59-
60-
for (const skill of pkg.skills) {
61-
const desc = skill.description ? ` ${skill.description}` : ''
62-
console.log(` ${skill.name.padEnd(32)}${desc}`)
63-
}
165+
console.log(` ${pkg.name}`)
166+
printSkillTree(pkg.skills, { nameWidth, showTypes })
64167
console.log()
65168
}
66169

0 commit comments

Comments
 (0)