Skip to content

Commit d4aa73f

Browse files
authored
fix: improve column width handling for rli agent list (#199)
The previous code had fixed column widths that were sometimes too narrow, leading to some odd wrapping behavior and difficult-to-read output. This change calculates column widths dynamically to ensure that there is enough space. No columns are truncated, so if the terminal is too small, output will just wrap. This is ugly, but at least doesn't lose information.
1 parent 7a0796d commit d4aa73f

1 file changed

Lines changed: 98 additions & 37 deletions

File tree

src/commands/agent/list.ts

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,56 +16,117 @@ interface ListOptions {
1616
output?: string;
1717
}
1818

19-
// Column widths (NAME is dynamic, takes remaining space)
20-
const COL_VERSION = 18;
21-
const COL_VISIBILITY = 10;
22-
const COL_ID = 30;
23-
const COL_CREATED = 10;
24-
const FIXED_WIDTH = COL_VERSION + COL_VISIBILITY + COL_ID + COL_CREATED + 4; // 4 for spacing
25-
26-
function truncate(str: string, maxLen: number): string {
27-
if (str.length <= maxLen) return str;
28-
return str.slice(0, maxLen - 1) + "…";
19+
interface ColumnDef {
20+
header: string;
21+
raw: (agent: Agent) => string;
22+
styled: (agent: Agent) => string;
2923
}
3024

31-
function printTable(agents: Agent[]): void {
25+
const columns: ColumnDef[] = [
26+
{
27+
header: "NAME",
28+
raw: (a) => a.name,
29+
styled(a) {
30+
return this.raw(a);
31+
},
32+
},
33+
{
34+
header: "SOURCE",
35+
raw: (a) => (a as any).source?.type || "-",
36+
styled(a) {
37+
return this.raw(a);
38+
},
39+
},
40+
{
41+
header: "VERSION",
42+
raw: (a) => {
43+
const pkg =
44+
(a as any).source?.npm?.package_name ||
45+
(a as any).source?.pip?.package_name;
46+
return pkg ? `${pkg}@${a.version}` : a.version;
47+
},
48+
styled(a) {
49+
const pkg =
50+
(a as any).source?.npm?.package_name ||
51+
(a as any).source?.pip?.package_name;
52+
return pkg ? chalk.dim(pkg + "@") + a.version : a.version;
53+
},
54+
},
55+
{
56+
header: "ID",
57+
raw: (a) => a.id,
58+
styled(a) {
59+
return chalk.dim(a.id);
60+
},
61+
},
62+
{
63+
header: "CREATED",
64+
raw: (a) => formatTimeAgo(a.create_time_ms),
65+
styled(a) {
66+
return chalk.dim(this.raw(a));
67+
},
68+
},
69+
];
70+
71+
function computeColumnWidths(agents: Agent[]): number[] {
72+
const minPad = 2;
73+
const maxPad = 4;
74+
const termWidth = process.stdout.columns || 120;
75+
76+
// Min width per column: max of header and all row values, plus minimum padding
77+
const minWidths = columns.map((col) => {
78+
const maxContent = agents.reduce(
79+
(w, a) => Math.max(w, col.raw(a).length),
80+
col.header.length,
81+
);
82+
return maxContent + minPad;
83+
});
84+
85+
const totalMin = minWidths.reduce((s, w) => s + w, 0);
86+
const slack = termWidth - totalMin;
87+
const extraPerCol = Math.min(
88+
maxPad - minPad,
89+
Math.max(0, Math.floor(slack / columns.length)),
90+
);
91+
92+
return minWidths.map((w) => w + extraPerCol);
93+
}
94+
95+
function padStyled(raw: string, styled: string, width: number): string {
96+
return styled + " ".repeat(Math.max(0, width - raw.length));
97+
}
98+
99+
function printTable(agents: Agent[], isPublic: boolean): void {
100+
if (isPublic) {
101+
console.log(
102+
chalk.dim("Showing PUBLIC agents. Use --private to see private agents"),
103+
);
104+
} else {
105+
console.log(
106+
chalk.dim("Showing PRIVATE agents. Use --public to see public agents"),
107+
);
108+
}
109+
console.log();
110+
32111
if (agents.length === 0) {
33112
console.log(chalk.dim("No agents found"));
34113
return;
35114
}
36115

116+
const widths = computeColumnWidths(agents);
37117
const termWidth = process.stdout.columns || 120;
38-
const nameWidth = Math.max(10, termWidth - FIXED_WIDTH);
39118

40119
// Header
41-
const header =
42-
"NAME".padEnd(nameWidth) +
43-
" " +
44-
"VERSION".padEnd(COL_VERSION) +
45-
" " +
46-
"VISIBILITY".padEnd(COL_VISIBILITY) +
47-
" " +
48-
"ID".padEnd(COL_ID) +
49-
" " +
50-
"CREATED".padEnd(COL_CREATED);
120+
const header = columns.map((col, i) => col.header.padEnd(widths[i])).join("");
51121
console.log(chalk.bold(header));
52122
console.log(chalk.dim("─".repeat(Math.min(header.length, termWidth))));
53123

124+
// Rows
54125
for (const agent of agents) {
55-
const name = truncate(agent.name, nameWidth).padEnd(nameWidth);
56-
const version = truncate(agent.version, COL_VERSION).padEnd(COL_VERSION);
57-
const visibility = (agent.is_public ? "public" : "private").padEnd(
58-
COL_VISIBILITY,
59-
);
60-
const visibilityColored = agent.is_public
61-
? chalk.green(visibility)
62-
: chalk.dim(visibility);
63-
const id = truncate(agent.id, COL_ID).padEnd(COL_ID);
64-
const created = formatTimeAgo(agent.create_time_ms).padEnd(COL_CREATED);
65-
66-
console.log(
67-
`${name} ${version} ${visibilityColored} ${chalk.dim(id)} ${chalk.dim(created)}`,
68-
);
126+
const line = columns
127+
.map((col, i) => padStyled(col.raw(agent), col.styled(agent), widths[i]))
128+
.join("");
129+
console.log(line);
69130
}
70131

71132
console.log();
@@ -105,7 +166,7 @@ export async function listAgentsCommand(options: ListOptions): Promise<void> {
105166
if (format !== "text") {
106167
output(agents, { format, defaultFormat: "json" });
107168
} else {
108-
printTable(agents);
169+
printTable(agents, !!options.public);
109170
}
110171
} catch (error) {
111172
outputError("Failed to list agents", error);

0 commit comments

Comments
 (0)