@heyhuynhgiabuu/pi-pretty 0.6.30 → 0.7.0
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/CHANGELOG.md +33 -0
- package/README.md +21 -0
- package/dist/claude-style.d.ts +45 -0
- package/dist/claude-style.d.ts.map +1 -0
- package/dist/claude-style.js +582 -0
- package/dist/claude-style.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -12
- package/dist/index.js.map +1 -1
- package/dist/prompt-editor.d.ts +4 -1
- package/dist/prompt-editor.d.ts.map +1 -1
- package/dist/prompt-editor.js +27 -12
- package/dist/prompt-editor.js.map +1 -1
- package/dist/render.d.ts +10 -7
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +41 -25
- package/dist/render.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +17 -13
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/find.d.ts.map +1 -1
- package/dist/tools/find.js +15 -17
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +10 -8
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +11 -9
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +25 -21
- package/dist/tools/read.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/working-indicator.d.ts +2 -0
- package/dist/working-indicator.d.ts.map +1 -1
- package/dist/working-indicator.js +20 -8
- package/dist/working-indicator.js.map +1 -1
- package/package.json +5 -5
- package/src/claude-style.ts +650 -0
- package/src/config.ts +3 -0
- package/src/index.ts +35 -15
- package/src/prompt-editor.ts +26 -11
- package/src/render.ts +54 -24
- package/src/tools/bash.ts +28 -23
- package/src/tools/find.ts +20 -16
- package/src/tools/grep.ts +10 -6
- package/src/tools/ls.ts +17 -8
- package/src/tools/read.ts +29 -30
- package/src/types.ts +9 -0
- package/src/working-indicator.ts +20 -8
package/src/tools/ls.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* pi-pretty: ls tool -- directory listing with styled output. */
|
|
2
2
|
|
|
3
3
|
import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { BG_ERROR, FG_DIM, RST, resolveBaseBackground
|
|
4
|
+
import { BG_ERROR, FG_DIM, RST, resolveBaseBackground } from "../config.js";
|
|
5
5
|
import { shortPath } from "../helpers.js";
|
|
6
6
|
import {
|
|
7
7
|
fillToolBackground,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
renderToolMetrics,
|
|
12
12
|
renderTree,
|
|
13
13
|
setCollapsedToolTitle,
|
|
14
|
+
toolIndent,
|
|
14
15
|
} from "../render.js";
|
|
15
16
|
import { resolveTextCtor } from "../tui-text.js";
|
|
16
17
|
import type { LsDetails, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
|
|
@@ -60,8 +61,9 @@ export function registerLsTool(
|
|
|
60
61
|
let out = theme.fg("toolTitle", theme.bold("ls"));
|
|
61
62
|
if (path) out += ` ${theme.fg("accent", path)}`;
|
|
62
63
|
if (limit !== undefined && limit !== null) out += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
64
|
+
const ind = toolIndent(ctx);
|
|
63
65
|
const renderTitle = (suffix = ""): string =>
|
|
64
|
-
fillToolBackground(`\n${
|
|
66
|
+
fillToolBackground(`\n${ind}${out}${suffix}\n`, ctx.isError ? BG_ERROR : undefined, undefined, ind);
|
|
65
67
|
rememberToolTitle(ctx, text, renderTitle);
|
|
66
68
|
text.setText(renderTitle());
|
|
67
69
|
return text;
|
|
@@ -71,25 +73,29 @@ export function registerLsTool(
|
|
|
71
73
|
resolveBaseBackground(theme);
|
|
72
74
|
|
|
73
75
|
const text = ctx.lastComponent ?? new TC("", 0, 0);
|
|
76
|
+
const ind = toolIndent(ctx);
|
|
74
77
|
if (ctx.isError) {
|
|
75
|
-
text.setText(renderToolError(getText(result) || "Error", theme));
|
|
78
|
+
text.setText(renderToolError(getText(result) || "Error", theme, ind));
|
|
76
79
|
return text;
|
|
77
80
|
}
|
|
78
81
|
const d = result.details as LsDetails | undefined;
|
|
79
82
|
if (d?._type === "lsResult" && d.text) {
|
|
80
83
|
if (!ctx.expanded) {
|
|
81
|
-
const summary = `${FG_DIM}${d.entryCount} entries — ctrl+o to expand${RST}${renderToolMetrics(result)}`;
|
|
84
|
+
const summary = `${FG_DIM}${d.entryCount} entries — ctrl+o to expand${RST}${renderToolMetrics(result, ctx)}`;
|
|
82
85
|
if (setCollapsedToolTitle(ctx, text, ` ${summary}`)) return text;
|
|
83
|
-
text.setText(fillToolBody(`${
|
|
86
|
+
text.setText(fillToolBody(`${ind}${summary}`, undefined, undefined, ind));
|
|
84
87
|
return text;
|
|
85
88
|
}
|
|
86
89
|
const rendered = renderTree(d.text, d.path)
|
|
87
90
|
.split("\n")
|
|
88
|
-
.map((l) => `${
|
|
91
|
+
.map((l) => `${ind}${l}`)
|
|
89
92
|
.join("\n");
|
|
90
93
|
text.setText(
|
|
91
94
|
fillToolBody(
|
|
92
|
-
`${
|
|
95
|
+
`${ind}${FG_DIM}${d.entryCount} entries${RST}${renderToolMetrics(result, ctx)}\n${rendered}`,
|
|
96
|
+
undefined,
|
|
97
|
+
undefined,
|
|
98
|
+
ind,
|
|
93
99
|
),
|
|
94
100
|
);
|
|
95
101
|
return text;
|
|
@@ -97,7 +103,10 @@ export function registerLsTool(
|
|
|
97
103
|
const fc = result.content?.[0];
|
|
98
104
|
text.setText(
|
|
99
105
|
fillToolBody(
|
|
100
|
-
`${
|
|
106
|
+
`${ind}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`,
|
|
107
|
+
undefined,
|
|
108
|
+
undefined,
|
|
109
|
+
ind,
|
|
101
110
|
),
|
|
102
111
|
);
|
|
103
112
|
return text;
|
package/src/tools/read.ts
CHANGED
|
@@ -3,19 +3,9 @@
|
|
|
3
3
|
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
4
|
import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { getReadmePath } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
7
|
-
BG_BASE,
|
|
8
|
-
BG_ERROR,
|
|
9
|
-
FG_DIM,
|
|
10
|
-
FG_LNUM,
|
|
11
|
-
FG_RULE,
|
|
12
|
-
RST,
|
|
13
|
-
resolveBaseBackground,
|
|
14
|
-
TOOL_RESULT_INDENT,
|
|
15
|
-
termWidth,
|
|
16
|
-
} from "../config.js";
|
|
6
|
+
import { BG_BASE, BG_ERROR, FG_DIM, FG_LNUM, FG_RULE, RST, resolveBaseBackground, termWidth } from "../config.js";
|
|
17
7
|
import { normalizeLineEndings, shortPath } from "../helpers.js";
|
|
18
|
-
import { fillToolBackground, fillToolBody, renderFileContent, renderToolError } from "../render.js";
|
|
8
|
+
import { fillToolBackground, fillToolBody, renderFileContent, renderToolError, toolIndent } from "../render.js";
|
|
19
9
|
import { resolveTextCtor } from "../tui-text.js";
|
|
20
10
|
import type { ReadDetails, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
|
|
21
11
|
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
@@ -79,6 +69,7 @@ export function registerReadTool(
|
|
|
79
69
|
promptSnippet: sdkTool.promptSnippet,
|
|
80
70
|
promptGuidelines: sdkTool.promptGuidelines,
|
|
81
71
|
constrainedSampling: sdkTool.constrainedSampling,
|
|
72
|
+
outputSchema: sdkTool.outputSchema as ToolDefinition["outputSchema"],
|
|
82
73
|
renderShell: "self",
|
|
83
74
|
|
|
84
75
|
execute: wrapExecuteWithMetrics(async (tid, params, sig, _upd, ctx: ExtensionContext) => {
|
|
@@ -114,9 +105,10 @@ export function registerReadTool(
|
|
|
114
105
|
return text;
|
|
115
106
|
}
|
|
116
107
|
|
|
108
|
+
const ind = toolIndent(ctx);
|
|
117
109
|
const path = String(args.path ?? "");
|
|
118
110
|
const label = theme.fg("error", theme.bold("→ read"));
|
|
119
|
-
text.setText(fillToolBackground(`\n${
|
|
111
|
+
text.setText(fillToolBackground(`\n${ind}${label} ${theme.fg("toolTitle", path)}\n`, BG_ERROR, undefined, ind));
|
|
120
112
|
return text;
|
|
121
113
|
},
|
|
122
114
|
|
|
@@ -125,9 +117,10 @@ export function registerReadTool(
|
|
|
125
117
|
|
|
126
118
|
const renderToken = nextReadRenderToken(ctx);
|
|
127
119
|
const text = ctx.lastComponent ?? new TC("", 0, 0);
|
|
120
|
+
const ind = toolIndent(ctx);
|
|
128
121
|
|
|
129
122
|
if (ctx.isError) {
|
|
130
|
-
text.setText(renderToolError(getText(result) || "Error", theme));
|
|
123
|
+
text.setText(renderToolError(getText(result) || "Error", theme, ind));
|
|
131
124
|
return text;
|
|
132
125
|
}
|
|
133
126
|
|
|
@@ -155,7 +148,7 @@ export function registerReadTool(
|
|
|
155
148
|
if (!ctx.expanded) {
|
|
156
149
|
if (skillName) {
|
|
157
150
|
const header = renderSkillHeader(skillName, false, theme);
|
|
158
|
-
text.setText(fillToolBody(`\n${
|
|
151
|
+
text.setText(fillToolBody(`\n${ind}${header}\n`, BG_BASE, undefined, ind));
|
|
159
152
|
return text;
|
|
160
153
|
}
|
|
161
154
|
const docsLabel = getPiDocsLabel(filePath, cwd);
|
|
@@ -164,8 +157,10 @@ export function registerReadTool(
|
|
|
164
157
|
: `${theme.fg("toolTitle", theme.bold("→ read"))} ${theme.fg("toolTitle", p2)}`;
|
|
165
158
|
text.setText(
|
|
166
159
|
fillToolBody(
|
|
167
|
-
`\n${
|
|
160
|
+
`\n${ind}${title}${theme.fg("dim", off2)} ${FG_DIM}${total} lines — ctrl+o to expand${RST}`,
|
|
168
161
|
BG_BASE,
|
|
162
|
+
undefined,
|
|
163
|
+
ind,
|
|
169
164
|
),
|
|
170
165
|
);
|
|
171
166
|
return text;
|
|
@@ -175,7 +170,8 @@ export function registerReadTool(
|
|
|
175
170
|
const offset = d.offset || 0;
|
|
176
171
|
const nw = Math.max(3, String(offset + total).length);
|
|
177
172
|
const gw = nw + 3;
|
|
178
|
-
|
|
173
|
+
// Default pad 1 keeps its historical budget; extra padding is subtracted so rows do not grow.
|
|
174
|
+
const cw = Math.max(1, tw - gw - Math.max(0, ind.length - 1));
|
|
179
175
|
|
|
180
176
|
const header = skillName
|
|
181
177
|
? renderSkillHeader(skillName, true, theme)
|
|
@@ -189,27 +185,27 @@ export function registerReadTool(
|
|
|
189
185
|
};
|
|
190
186
|
const cachedHighlight = getCachedReadHighlight(ctx, highlightRequest);
|
|
191
187
|
if (cachedHighlight !== undefined) {
|
|
192
|
-
const highlighted = buildHighlightedRead(cachedHighlight, header, skillName, offset, nw, tw);
|
|
193
|
-
text.setText(fillToolBody(highlighted, BG_BASE));
|
|
188
|
+
const highlighted = buildHighlightedRead(cachedHighlight, header, skillName, offset, nw, tw, ind);
|
|
189
|
+
text.setText(fillToolBody(highlighted, BG_BASE, undefined, ind));
|
|
194
190
|
return text;
|
|
195
191
|
}
|
|
196
192
|
|
|
197
|
-
const out: string[] = ["", `${
|
|
198
|
-
out.push(`${
|
|
193
|
+
const out: string[] = ["", `${ind}${header}`, ""];
|
|
194
|
+
out.push(`${ind}${FG_RULE}${"─".repeat(Math.max(1, tw - ind.length))}${RST}`);
|
|
199
195
|
for (let i = 0; i < show.length; i++) {
|
|
200
196
|
const ln = offset + i + 1;
|
|
201
197
|
const code = show[i] ?? "";
|
|
202
198
|
const display = code.length > cw ? `${code.slice(0, Math.max(0, cw - 1))}${FG_DIM}›${RST}` : code;
|
|
203
199
|
const lineNo = String(ln);
|
|
204
200
|
out.push(
|
|
205
|
-
`${
|
|
201
|
+
`${ind}${FG_LNUM}${" ".repeat(Math.max(0, nw - lineNo.length))}${lineNo}${RST} ${FG_RULE}│${RST} ${display}${RST}`,
|
|
206
202
|
);
|
|
207
203
|
}
|
|
208
204
|
if (total > maxShow) {
|
|
209
|
-
out.push(`${
|
|
205
|
+
out.push(`${ind}${FG_DIM}… ${total - maxShow} more lines (${total} total)${RST}`);
|
|
210
206
|
}
|
|
211
207
|
const rendered = out.join("\n");
|
|
212
|
-
text.setText(fillToolBody(rendered, BG_BASE));
|
|
208
|
+
text.setText(fillToolBody(rendered, BG_BASE, undefined, ind));
|
|
213
209
|
|
|
214
210
|
// Async syntax highlighting via Shiki. The component is reused across
|
|
215
211
|
// expansion changes, so stale work must not restore an older view.
|
|
@@ -217,8 +213,8 @@ export function registerReadTool(
|
|
|
217
213
|
.then((hl) => {
|
|
218
214
|
if (!isCurrentReadRender(ctx, renderToken)) return;
|
|
219
215
|
setCachedReadHighlight(ctx, highlightRequest, hl);
|
|
220
|
-
const highlighted = buildHighlightedRead(hl, header, skillName, offset, nw, tw);
|
|
221
|
-
text.setText(fillToolBody(highlighted, BG_BASE));
|
|
216
|
+
const highlighted = buildHighlightedRead(hl, header, skillName, offset, nw, tw, ind);
|
|
217
|
+
text.setText(fillToolBody(highlighted, BG_BASE, undefined, ind));
|
|
222
218
|
ctx.invalidate?.();
|
|
223
219
|
})
|
|
224
220
|
.catch(() => {});
|
|
@@ -229,8 +225,10 @@ export function registerReadTool(
|
|
|
229
225
|
const fc = result.content?.[0];
|
|
230
226
|
text.setText(
|
|
231
227
|
fillToolBody(
|
|
232
|
-
`${
|
|
228
|
+
`${ind}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`,
|
|
233
229
|
BG_BASE,
|
|
230
|
+
undefined,
|
|
231
|
+
ind,
|
|
234
232
|
),
|
|
235
233
|
);
|
|
236
234
|
return text;
|
|
@@ -293,16 +291,17 @@ function buildHighlightedRead(
|
|
|
293
291
|
offset: number,
|
|
294
292
|
nw: number,
|
|
295
293
|
tw: number,
|
|
294
|
+
ind: string,
|
|
296
295
|
): string {
|
|
297
296
|
const padded = highlighted
|
|
298
297
|
.split("\n")
|
|
299
298
|
.map((line, index) => {
|
|
300
299
|
const lineNo = String(offset + index + 1);
|
|
301
|
-
return `${
|
|
300
|
+
return `${ind}${FG_LNUM}${" ".repeat(Math.max(0, nw - lineNo.length))}${lineNo}${RST} ${FG_RULE}│${RST} ${line}${RST}`;
|
|
302
301
|
})
|
|
303
302
|
.join("\n");
|
|
304
|
-
const divider = skillName ? `${
|
|
305
|
-
return `\n${
|
|
303
|
+
const divider = skillName ? `${ind}${FG_RULE}${"─".repeat(Math.max(1, tw - ind.length))}${RST}\n` : "";
|
|
304
|
+
return `\n${ind}${header}\n\n${divider}${padded}`;
|
|
306
305
|
}
|
|
307
306
|
|
|
308
307
|
function getText(result: Result): string {
|
package/src/types.ts
CHANGED
|
@@ -34,7 +34,16 @@ export interface RenderCtxLike {
|
|
|
34
34
|
isError?: boolean;
|
|
35
35
|
state: Record<string, unknown>;
|
|
36
36
|
expanded?: boolean;
|
|
37
|
+
/** Tool call arguments as streamed so far. */
|
|
38
|
+
args?: Record<string, unknown>;
|
|
39
|
+
isPartial?: boolean;
|
|
40
|
+
/** False while the tool call is still streaming its arguments. */
|
|
41
|
+
executionStarted?: boolean;
|
|
37
42
|
invalidate?: () => void;
|
|
43
|
+
/** Host horizontal padding (pi >= 1.1). Absent on older hosts; renderers fall back to 1. */
|
|
44
|
+
outputPad?: number;
|
|
45
|
+
/** Final host-measured execution time in ms; undefined while running or on older hosts. */
|
|
46
|
+
durationMs?: number;
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
export interface TextLike {
|
package/src/working-indicator.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* phase. The extension adds no interrupt hint or token-count suffix.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { keyText } from "@earendil-works/pi-coding-agent";
|
|
17
18
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
18
19
|
import { FG_BLUE, FG_DIM, FG_MUTED, type ThinkingIndicatorConfig, type WorkingIndicatorConfig } from "./config.js";
|
|
19
20
|
import { dimAccentHex, hexToAnsiFg, sessionAccentHex } from "./session-color.js";
|
|
@@ -539,7 +540,7 @@ export function installPerRowThinkingLabels(componentClass: unknown): PerRowThin
|
|
|
539
540
|
if (typeof ts !== "number") return incoming;
|
|
540
541
|
if (ts === state.activeTs) return incoming;
|
|
541
542
|
const ms = state.completed.get(ts);
|
|
542
|
-
return ms === undefined ?
|
|
543
|
+
return ms === undefined ? thinkingLabel() : thoughtLabel(ms);
|
|
543
544
|
} catch {
|
|
544
545
|
return incoming;
|
|
545
546
|
}
|
|
@@ -588,9 +589,20 @@ export interface ThinkingTimer {
|
|
|
588
589
|
restore(): void;
|
|
589
590
|
}
|
|
590
591
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
592
|
+
let claudeThinkingLabels = false;
|
|
593
|
+
/** claudecode tool style: flush-left `thinking` / `thought 3s` instead of ` Thinking...` / ` Thought for 3s`. */
|
|
594
|
+
export function setClaudeThinkingLabels(enabled: boolean): void {
|
|
595
|
+
claudeThinkingLabels = enabled;
|
|
596
|
+
}
|
|
597
|
+
const thinkingLabel = (): string => (claudeThinkingLabels ? "thinking" : " Thinking...");
|
|
598
|
+
const padThinkingLabel = (label: string): string =>
|
|
599
|
+
claudeThinkingLabels || label.startsWith(" ") ? label : ` ${label}`;
|
|
600
|
+
/** Settled label for a finished thinking run; claudecode style appends the toggle hint when bound. */
|
|
601
|
+
const thoughtLabel = (ms: number): string => {
|
|
602
|
+
if (!claudeThinkingLabels) return ` Thought for ${formatThinkingDuration(ms)}`;
|
|
603
|
+
const key = keyText("app.thinking.toggle");
|
|
604
|
+
return `thought ${formatThinkingDuration(ms)}${key ? ` (${key} to expand)` : ""}`;
|
|
605
|
+
};
|
|
594
606
|
|
|
595
607
|
/** Format sub-second elapsed time as milliseconds, then use compact whole seconds. */
|
|
596
608
|
export function formatThinkingDuration(elapsedMs: number): string {
|
|
@@ -626,13 +638,13 @@ export function createThinkingTimer(
|
|
|
626
638
|
return {
|
|
627
639
|
tick(): void {
|
|
628
640
|
if (terminal) return;
|
|
629
|
-
animator.tick(`${
|
|
641
|
+
animator.tick(`${thinkingLabel()} ${duration()}`);
|
|
630
642
|
},
|
|
631
643
|
complete(): number | undefined {
|
|
632
644
|
if (terminal) return undefined;
|
|
633
645
|
terminal = true;
|
|
634
646
|
const ms = elapsed();
|
|
635
|
-
animator.show(
|
|
647
|
+
animator.show(thoughtLabel(ms));
|
|
636
648
|
return ms;
|
|
637
649
|
},
|
|
638
650
|
restore(): void {
|
|
@@ -702,7 +714,7 @@ export function createThinkingLabelAnimator(
|
|
|
702
714
|
}).frames.map((frame) => `${leftPadding}${frame}`);
|
|
703
715
|
};
|
|
704
716
|
|
|
705
|
-
let currentLabel =
|
|
717
|
+
let currentLabel = thinkingLabel();
|
|
706
718
|
let frames = buildLabelFrames(currentLabel);
|
|
707
719
|
|
|
708
720
|
let index = 0;
|
|
@@ -711,7 +723,7 @@ export function createThinkingLabelAnimator(
|
|
|
711
723
|
get frames(): readonly string[] {
|
|
712
724
|
return frames;
|
|
713
725
|
},
|
|
714
|
-
tick(label =
|
|
726
|
+
tick(label = thinkingLabel()): void {
|
|
715
727
|
const paddedLabel = padThinkingLabel(label);
|
|
716
728
|
if (paddedLabel !== currentLabel) {
|
|
717
729
|
frames = buildLabelFrames(paddedLabel);
|