pi-pretty-tui 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,8 @@ A visual refinement extension for the [Pi coding agent](https://pi.dev/). It pro
9
9
  - Compact `Read`, `Bash`, `Edit`, `Write`, `Grep`, `Find`, and `List` calls
10
10
  - Gray running, green success, and red failure indicators
11
11
  - Concise result summaries with expandable output
12
+ - Native-style `Write` previews: first 10 lines when collapsed, full content when expanded
13
+ - Live `Bash` output: latest 5 lines when collapsed, all available output when expanded
12
14
  - Colored `+added` and `-removed` edit statistics and diffs
13
15
  - Correct hanging indentation for long paths and wrapped tool output
14
16
  - Bullet (`•`) markers for unordered Markdown lists
@@ -25,7 +27,7 @@ Pi may warn that built-in tools are being overridden. This is expected: pi-prett
25
27
 
26
28
  ## Expand tool output
27
29
 
28
- Use Pi's configured `app.tools.expand` keybinding (usually `Ctrl+E`) to show or hide detailed output and edit diffs.
30
+ Press `Ctrl+O` (Pi's default `app.tools.expand` keybinding) to show or hide detailed output, edit diffs, and complete `Write` content.
29
31
 
30
32
  ## Compatibility notice
31
33
 
@@ -101,20 +101,53 @@ export default function prettyTui(pi: ExtensionAPI) {
101
101
  context.state.compactToolStatus = status;
102
102
  };
103
103
 
104
+ const callRow = (theme: any, name: string, detail: string, state: any): DisplayRow => ({
105
+ prefix: () => {
106
+ const status = (state.compactToolStatus ?? "running") as ToolStatus;
107
+ const color = status === "success" ? "success" : status === "error" ? "error" : "dim";
108
+ return theme.fg(color, "● ");
109
+ },
110
+ continuation: " ",
111
+ content:
112
+ theme.fg("accent", theme.bold(name)) +
113
+ theme.fg("dim", "(") +
114
+ theme.fg("text", detail) +
115
+ theme.fg("dim", ")"),
116
+ });
117
+
104
118
  const call = (theme: any, name: string, detail: string, state: any) =>
105
- block([{
106
- prefix: () => {
107
- const status = (state.compactToolStatus ?? "running") as ToolStatus;
108
- const color = status === "success" ? "success" : status === "error" ? "error" : "dim";
109
- return theme.fg(color, "● ");
110
- },
111
- continuation: " ",
112
- content:
113
- theme.fg("accent", theme.bold(name)) +
114
- theme.fg("dim", "(") +
115
- theme.fg("text", detail) +
116
- theme.fg("dim", ")"),
117
- }]);
119
+ block([callRow(theme, name, detail, state)]);
120
+
121
+ const writeCall = (theme: any, path: string, content: string, expanded: boolean, state: any) => {
122
+ const lines = content.replace(/\t/g, " ").split("\n");
123
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
124
+
125
+ const total = lines.length;
126
+ const shown = lines.slice(0, expanded ? total : 10);
127
+ const remaining = total - shown.length;
128
+ const rows: DisplayRow[] = [
129
+ callRow(theme, "Write", `${path} · ${total} ${total === 1 ? "line" : "lines"}`, state),
130
+ ];
131
+
132
+ for (let index = 0; index < shown.length; index++) {
133
+ const isLast = index === shown.length - 1 && remaining === 0;
134
+ rows.push({
135
+ prefix: theme.fg("dim", isLast ? " └ " : " │ "),
136
+ continuation: theme.fg("dim", " │ "),
137
+ content: theme.fg("toolOutput", shown[index] || " "),
138
+ });
139
+ }
140
+
141
+ if (remaining > 0) {
142
+ rows.push({
143
+ prefix: theme.fg("muted", " └ "),
144
+ continuation: " ",
145
+ content: theme.fg("muted", `… ${remaining} more ${remaining === 1 ? "line" : "lines"}`),
146
+ });
147
+ }
148
+
149
+ return block(rows);
150
+ };
118
151
 
119
152
  const result = (
120
153
  theme: any,
@@ -161,6 +194,72 @@ export default function prettyTui(pi: ExtensionAPI) {
161
194
  return result(theme, label);
162
195
  };
163
196
 
197
+ const terminalOutputLines = (output: string): string[] => {
198
+ const lines: string[] = [];
199
+ let current = "";
200
+
201
+ for (let index = 0; index < output.length; index++) {
202
+ const char = output[index];
203
+ if (char === "\r") {
204
+ if (output[index + 1] === "\n") {
205
+ lines.push(current);
206
+ current = "";
207
+ index++;
208
+ } else {
209
+ // A bare carriage return redraws the current terminal line. Progress
210
+ // bars such as tqdm use this to update in place.
211
+ current = "";
212
+ }
213
+ } else if (char === "\n") {
214
+ lines.push(current);
215
+ current = "";
216
+ } else {
217
+ current += char;
218
+ }
219
+ }
220
+
221
+ if (current) lines.push(current);
222
+ return lines;
223
+ };
224
+
225
+ const bashResult = (
226
+ context: any,
227
+ theme: any,
228
+ summary: string,
229
+ output: string,
230
+ expanded: boolean,
231
+ status: ToolStatus,
232
+ ): Component => {
233
+ setStatus(context, status);
234
+ const lines = terminalOutputLines(output);
235
+ const shown = expanded ? lines : lines.slice(-5);
236
+ const omitted = lines.length - shown.length;
237
+ const rows: DisplayRow[] = [{
238
+ prefix: theme.fg("dim", "└ "),
239
+ continuation: " ",
240
+ content: theme.fg(status === "error" ? "error" : "toolOutput", summary),
241
+ }];
242
+
243
+ if (omitted > 0) {
244
+ rows.push({
245
+ prefix: theme.fg("muted", " │ "),
246
+ continuation: theme.fg("muted", " │ "),
247
+ content: theme.fg("muted", `… ${omitted} earlier ${omitted === 1 ? "line" : "lines"}`),
248
+ });
249
+ }
250
+
251
+ for (let index = 0; index < shown.length; index++) {
252
+ const isLast = index === shown.length - 1;
253
+ rows.push({
254
+ prefix: theme.fg("dim", isLast ? " └ " : " │ "),
255
+ continuation: theme.fg("dim", " │ "),
256
+ content: theme.fg("toolOutput", shown[index] || " "),
257
+ });
258
+ }
259
+
260
+ return block(rows);
261
+ };
262
+
164
263
  const completeStatus = (context: any, output: string): boolean => {
165
264
  const failed = isError(context, output);
166
265
  setStatus(context, failed ? "error" : "success");
@@ -200,20 +299,31 @@ export default function prettyTui(pi: ExtensionAPI) {
200
299
  ...bash,
201
300
  renderShell: "self",
202
301
  renderCall(args: any, theme: any, context: any) {
203
- const command = String(args.command).replace(/\s*\n\s*/g, " ↵ ");
302
+ const command = typeof args.command === "string" ? args.command : "";
204
303
  return call(theme, "Bash", command, context.state);
205
304
  },
206
305
  renderResult(toolResult: any, options: any, theme: any, context: any) {
207
- if (options.isPartial) return partialResult(context, theme, "Running…");
208
306
  const output = textContent(toolResult);
209
- const failed = completeStatus(context, output);
210
- const lines = nonEmptyLines(output);
307
+ if (options.isPartial) {
308
+ return bashResult(context, theme, "Running…", output, options.expanded, "running");
309
+ }
310
+
311
+ const failed = isError(context, output);
312
+ const outputLines = terminalOutputLines(output);
313
+ const lineCount = outputLines.filter((line) => line.length > 0).length;
211
314
  const summary = failed
212
- ? output.split("\n")[0] || "Command failed"
213
- : output
214
- ? `Done · ${lines} output ${lines === 1 ? "line" : "lines"}`
315
+ ? outputLines[0] || "Command failed"
316
+ : outputLines.length > 0
317
+ ? `Done · ${lineCount} output ${lineCount === 1 ? "line" : "lines"}`
215
318
  : "Done";
216
- return result(theme, summary, output, options.expanded, failed);
319
+ return bashResult(
320
+ context,
321
+ theme,
322
+ summary,
323
+ output,
324
+ options.expanded,
325
+ failed ? "error" : "success",
326
+ );
217
327
  },
218
328
  } as any);
219
329
 
@@ -261,8 +371,8 @@ export default function prettyTui(pi: ExtensionAPI) {
261
371
  ...write,
262
372
  renderShell: "self",
263
373
  renderCall(args: any, theme: any, context: any) {
264
- const content = String(args.content ?? "");
265
- return call(theme, "Write", `${args.path} · ${nonEmptyLines(content)} lines`, context.state);
374
+ const content = typeof args.content === "string" ? args.content : "";
375
+ return writeCall(theme, String(args.path ?? ""), content, context.expanded, context.state);
266
376
  },
267
377
  renderResult(toolResult: any, options: any, theme: any, context: any) {
268
378
  if (options.isPartial) return partialResult(context, theme, "Writing…");
package/package.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
2
  "name": "pi-pretty-tui",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Polished tool calls and list rendering for the Pi coding agent TUI",
5
5
  "license": "MIT",
6
6
  "author": "Kainan Yang <ykn0309@whu.edu.cn>",
7
7
  "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/ykn0309/pi-pretty-tui.git"
11
+ },
12
+ "homepage": "https://github.com/ykn0309/pi-pretty-tui#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ykn0309/pi-pretty-tui/issues"
15
+ },
8
16
  "keywords": [
9
17
  "pi-package",
10
18
  "pi-extension",
@@ -32,6 +40,6 @@
32
40
  "extensions": [
33
41
  "./extensions/index.ts"
34
42
  ],
35
- "image": "https://unpkg.com/pi-pretty-tui@0.1.1/assets/screenshot.png"
43
+ "image": "https://unpkg.com/pi-pretty-tui@0.1.3/assets/screenshot.png"
36
44
  }
37
45
  }