@diegopetrucci/pi-extensions 0.1.19 → 0.1.20
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
|
@@ -10,7 +10,7 @@ A collection of [pi](https://github.com/earendil-works/pi-mono) agent extensions
|
|
|
10
10
|
- [`notify`](./extensions/notify): Sends configurable terminal, desktop, bell, and sound notifications when pi finishes and is ready for input.
|
|
11
11
|
- [`oracle`](./extensions/oracle): Adds an Amp-style read-only oracle tool that auto-selects the strongest reasoning model on the current provider/subscription, covers pi’s built-in providers with hardcoded rankings, sets reasoning to xhigh by default, and shows live status while running.
|
|
12
12
|
- [`permission-gate`](./extensions/permission-gate): Prompts for confirmation before dangerous bash commands like `rm -rf`, `sudo`, and `chmod 777`.
|
|
13
|
-
- [`quiet-tools`](./extensions/quiet-tools): Renders collapsed built-in tool rows as
|
|
13
|
+
- [`quiet-tools`](./extensions/quiet-tools): Renders collapsed built-in tool rows as a one-line invocation plus an expand hint without changing model-visible tool results; toggle temporarily with `/quiet-tools`.
|
|
14
14
|
|
|
15
15
|
(For the full list of pi extensions I use, [check out my dotfiles](https://github.com/diegopetrucci/dot/blob/main/.pi/agent/settings.json).)
|
|
16
16
|
|
|
@@ -25,7 +25,7 @@ pi install npm:@diegopetrucci/pi-extensions
|
|
|
25
25
|
Or pin the GitHub package to this release:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
pi install git:github.com/diegopetrucci/pi-extensions@v0.1.
|
|
28
|
+
pi install git:github.com/diegopetrucci/pi-extensions@v0.1.20
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Or a specific extension:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A pi extension that makes collapsed built-in tool rows much quieter in the TUI.
|
|
4
4
|
|
|
5
|
-
When enabled, collapsed tool
|
|
5
|
+
When enabled, each collapsed tool row renders as one invocation line plus a separate `(Ctrl+O to expand)` hint line. Tool output is hidden until expanded. Expanding with `Ctrl+O` still shows pi's full rendered output.
|
|
6
6
|
|
|
7
7
|
`quiet-tools` only changes the visual renderer. It does not truncate, summarize, or rewrite the actual tool results sent to the model.
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ When enabled, collapsed tool output renders as one output line plus an inline hi
|
|
|
16
16
|
- `edit`
|
|
17
17
|
- `write`
|
|
18
18
|
|
|
19
|
-
For
|
|
19
|
+
For every covered tool, the collapsed invocation is truncated to a single visual line so long paths, commands, diffs, or file contents do not fill the TUI. Expanding restores pi's normal renderer.
|
|
20
20
|
|
|
21
21
|
## Commands
|
|
22
22
|
|
|
@@ -61,3 +61,4 @@ Then reload pi:
|
|
|
61
61
|
- It reuses pi's built-in implementations and preserves `shellPath`, `shellCommandPrefix`, and image autoresize settings when they are available from settings files.
|
|
62
62
|
- If another extension also overrides built-in tool execution, pi's extension load order determines which override wins.
|
|
63
63
|
- It affects assistant-invoked tool rows. User `!`/`!!` bash commands are rendered by a separate pi component and keep pi's default preview behavior.
|
|
64
|
+
- Pi renders image attachments outside tool result renderers, so inline image display for image reads is still controlled by pi's image settings.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
createBashToolDefinition,
|
|
3
5
|
createEditToolDefinition,
|
|
@@ -6,19 +8,15 @@ import {
|
|
|
6
8
|
createLsToolDefinition,
|
|
7
9
|
createReadToolDefinition,
|
|
8
10
|
createWriteToolDefinition,
|
|
9
|
-
DEFAULT_MAX_BYTES,
|
|
10
|
-
DEFAULT_MAX_LINES,
|
|
11
|
-
formatSize,
|
|
12
11
|
keyHint,
|
|
13
12
|
SettingsManager,
|
|
14
13
|
type ExtensionAPI,
|
|
15
14
|
type ToolDefinition as PiToolDefinition,
|
|
16
15
|
type ToolsOptions,
|
|
17
16
|
} from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import {
|
|
17
|
+
import { Container, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
19
18
|
|
|
20
|
-
const
|
|
21
|
-
const QUIET_CALL_TOOL_NAMES = new Set(["edit", "write"]);
|
|
19
|
+
const QUIET_CALL_TOOL_NAMES = new Set(["bash", "edit", "find", "grep", "ls", "read", "write"]);
|
|
22
20
|
|
|
23
21
|
type ToolDefinition = PiToolDefinition<any, any, any>;
|
|
24
22
|
type ToolRenderCall = NonNullable<ToolDefinition["renderCall"]>;
|
|
@@ -34,208 +32,195 @@ type TimerRenderState = {
|
|
|
34
32
|
interval?: ReturnType<typeof setInterval>;
|
|
35
33
|
};
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
class QuietLinesRenderComponent extends Container {
|
|
36
|
+
private linesRenderer: ((width: number) => string[]) | undefined;
|
|
37
|
+
|
|
38
|
+
setLinesRenderer(linesRenderer: (width: number) => string[]): void {
|
|
39
|
+
this.linesRenderer = linesRenderer;
|
|
40
|
+
this.invalidate();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render(width: number): string[] {
|
|
44
|
+
return this.linesRenderer?.(width).filter((line) => line) ?? [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
46
47
|
|
|
47
|
-
class
|
|
48
|
+
class QuietCallRenderComponent extends QuietLinesRenderComponent {}
|
|
49
|
+
class QuietResultRenderComponent extends QuietLinesRenderComponent {}
|
|
48
50
|
|
|
49
|
-
function
|
|
51
|
+
function sanitizeInlineText(text: string): string {
|
|
50
52
|
return text
|
|
51
53
|
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
52
54
|
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
55
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
53
56
|
.replace(/[\x00-\x08\x0B-\x1F\x7F]/g, "");
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
function
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.map((content) => content.text ?? "")
|
|
61
|
-
.join("\n")
|
|
62
|
-
.trim(),
|
|
63
|
-
);
|
|
59
|
+
function str(value: unknown): string | null {
|
|
60
|
+
if (typeof value === "string") return sanitizeInlineText(value);
|
|
61
|
+
if (value == null) return "";
|
|
62
|
+
return null;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
function
|
|
67
|
-
return
|
|
65
|
+
function asRecord(args: unknown): Record<string, unknown> | undefined {
|
|
66
|
+
return args && typeof args === "object" ? (args as Record<string, unknown>) : undefined;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
function
|
|
71
|
-
|
|
69
|
+
function firstStringArg(args: unknown, names: string[]): string | null {
|
|
70
|
+
const record = asRecord(args);
|
|
71
|
+
if (!record) return "";
|
|
72
|
+
for (const name of names) {
|
|
73
|
+
if (Object.prototype.hasOwnProperty.call(record, name)) {
|
|
74
|
+
return str(record[name]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return "";
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
function
|
|
75
|
-
|
|
80
|
+
function numberArg(args: unknown, name: string): number | undefined {
|
|
81
|
+
const value = asRecord(args)?.[name];
|
|
82
|
+
return typeof value === "number" ? value : undefined;
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const styledLine = theme.fg("toolOutput", line);
|
|
82
|
-
if (hiddenLines <= 0) {
|
|
83
|
-
return truncateToWidth(styledLine, width, "...");
|
|
84
|
-
}
|
|
85
|
+
function invalidArgText(theme: RenderTheme): string {
|
|
86
|
+
return theme.fg("error", "[invalid arg]");
|
|
87
|
+
}
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const hintWidth = visibleLength(hint);
|
|
91
|
-
if (hintWidth + 8 > width) {
|
|
92
|
-
return truncateToWidth(`${styledLine}${hint}`, width, "...");
|
|
93
|
-
}
|
|
89
|
+
function shortenPath(path: string): string {
|
|
90
|
+
const home = homedir();
|
|
91
|
+
return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
92
|
+
}
|
|
94
93
|
|
|
95
|
-
|
|
94
|
+
function formatToolTitle(toolName: string, theme: RenderTheme): string {
|
|
95
|
+
return theme.fg("toolTitle", theme.bold(toolName));
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function
|
|
99
|
-
|
|
98
|
+
function formatPathArg(args: unknown, theme: RenderTheme, placeholder = "..."): string {
|
|
99
|
+
const path = firstStringArg(args, ["file_path", "path"]);
|
|
100
|
+
if (path === null) return invalidArgText(theme);
|
|
101
|
+
return path ? theme.fg("accent", shortenPath(path)) : theme.fg("toolOutput", placeholder);
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
function
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
linesTruncated?: boolean;
|
|
111
|
-
}
|
|
112
|
-
| undefined;
|
|
113
|
-
const truncation = details?.truncation;
|
|
114
|
-
const warnings: string[] = [];
|
|
115
|
-
|
|
116
|
-
if (details?.fullOutputPath) {
|
|
117
|
-
warnings.push(`Full output: ${details.fullOutputPath}`);
|
|
118
|
-
}
|
|
104
|
+
function formatReadLineRange(args: unknown, theme: RenderTheme): string {
|
|
105
|
+
const offset = numberArg(args, "offset");
|
|
106
|
+
const limit = numberArg(args, "limit");
|
|
107
|
+
if (offset === undefined && limit === undefined) return "";
|
|
108
|
+
const startLine = offset ?? 1;
|
|
109
|
+
const endLine = limit !== undefined ? startLine + limit - 1 : "";
|
|
110
|
+
return theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
|
111
|
+
}
|
|
119
112
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
} else if (truncation.truncatedBy === "lines") {
|
|
124
|
-
warnings.push(
|
|
125
|
-
`Truncated: showing ${truncation.outputLines ?? "some"} of ${truncation.totalLines ?? "?"} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)`,
|
|
126
|
-
);
|
|
127
|
-
} else {
|
|
128
|
-
warnings.push(
|
|
129
|
-
`Truncated: ${truncation.outputLines ?? "some"} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
113
|
+
function formatQuietReadCall(args: unknown, theme: RenderTheme): string {
|
|
114
|
+
return `${formatToolTitle("read", theme)} ${formatPathArg(args, theme)}${formatReadLineRange(args, theme)}`;
|
|
115
|
+
}
|
|
133
116
|
|
|
134
|
-
|
|
135
|
-
|
|
117
|
+
function formatQuietBashCall(args: unknown, theme: RenderTheme): string {
|
|
118
|
+
const command = str(asRecord(args)?.command);
|
|
119
|
+
const timeout = numberArg(args, "timeout");
|
|
120
|
+
const commandDisplay = command === null
|
|
121
|
+
? invalidArgText(theme)
|
|
122
|
+
: command
|
|
123
|
+
? theme.fg("toolTitle", theme.bold(command))
|
|
124
|
+
: theme.fg("toolOutput", "...");
|
|
125
|
+
const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
|
|
126
|
+
return `${theme.fg("toolTitle", theme.bold("$"))} ${commandDisplay}${timeoutSuffix}`;
|
|
127
|
+
}
|
|
136
128
|
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
function formatSearchPath(rawPath: string | null, theme: RenderTheme): string {
|
|
130
|
+
return rawPath === null ? invalidArgText(theme) : shortenPath(rawPath || ".");
|
|
131
|
+
}
|
|
139
132
|
|
|
140
|
-
|
|
141
|
-
|
|
133
|
+
function formatQuietGrepCall(args: unknown, theme: RenderTheme): string {
|
|
134
|
+
const pattern = str(asRecord(args)?.pattern);
|
|
135
|
+
const rawPath = str(asRecord(args)?.path);
|
|
136
|
+
const glob = str(asRecord(args)?.glob);
|
|
137
|
+
const limit = numberArg(args, "limit");
|
|
138
|
+
let text = `${formatToolTitle("grep", theme)} ${
|
|
139
|
+
pattern === null ? invalidArgText(theme) : theme.fg("accent", `/${pattern || ""}/`)
|
|
140
|
+
}${theme.fg("toolOutput", ` in ${formatSearchPath(rawPath, theme)}`)}`;
|
|
141
|
+
if (glob) text += theme.fg("toolOutput", ` (${glob})`);
|
|
142
|
+
if (glob === null) text += ` ${invalidArgText(theme)}`;
|
|
143
|
+
if (limit !== undefined) text += theme.fg("toolOutput", ` limit ${limit}`);
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
function formatQuietFindCall(args: unknown, theme: RenderTheme): string {
|
|
148
|
+
const pattern = str(asRecord(args)?.pattern);
|
|
149
|
+
const rawPath = str(asRecord(args)?.path);
|
|
150
|
+
const limit = numberArg(args, "limit");
|
|
151
|
+
let text = `${formatToolTitle("find", theme)} ${
|
|
152
|
+
pattern === null ? invalidArgText(theme) : theme.fg("accent", pattern || "")
|
|
153
|
+
}${theme.fg("toolOutput", ` in ${formatSearchPath(rawPath, theme)}`)}`;
|
|
154
|
+
if (limit !== undefined) text += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
155
|
+
return text;
|
|
156
|
+
}
|
|
144
157
|
|
|
145
|
-
|
|
158
|
+
function formatQuietLsCall(args: unknown, theme: RenderTheme): string {
|
|
159
|
+
const rawPath = str(asRecord(args)?.path);
|
|
160
|
+
const limit = numberArg(args, "limit");
|
|
161
|
+
let text = `${formatToolTitle("ls", theme)} ${
|
|
162
|
+
rawPath === null ? invalidArgText(theme) : theme.fg("accent", shortenPath(rawPath || "."))
|
|
163
|
+
}`;
|
|
164
|
+
if (limit !== undefined) text += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
165
|
+
return text;
|
|
146
166
|
}
|
|
147
167
|
|
|
148
|
-
function
|
|
149
|
-
|
|
168
|
+
function formatQuietPathOnlyCall(toolName: "edit" | "write", args: unknown, theme: RenderTheme): string {
|
|
169
|
+
return `${formatToolTitle(toolName, theme)} ${formatPathArg(args, theme)}`;
|
|
170
|
+
}
|
|
150
171
|
|
|
151
|
-
|
|
152
|
-
|
|
172
|
+
function formatQuietCallLine(toolName: string, args: unknown, theme: RenderTheme): string {
|
|
173
|
+
switch (toolName) {
|
|
174
|
+
case "bash":
|
|
175
|
+
return formatQuietBashCall(args, theme);
|
|
176
|
+
case "edit":
|
|
177
|
+
return formatQuietPathOnlyCall("edit", args, theme);
|
|
178
|
+
case "find":
|
|
179
|
+
return formatQuietFindCall(args, theme);
|
|
180
|
+
case "grep":
|
|
181
|
+
return formatQuietGrepCall(args, theme);
|
|
182
|
+
case "ls":
|
|
183
|
+
return formatQuietLsCall(args, theme);
|
|
184
|
+
case "read":
|
|
185
|
+
return formatQuietReadCall(args, theme);
|
|
186
|
+
case "write":
|
|
187
|
+
return formatQuietPathOnlyCall("write", args, theme);
|
|
188
|
+
default:
|
|
189
|
+
return formatToolTitle(toolName, theme);
|
|
153
190
|
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function formatExpandHint(theme: RenderTheme): string {
|
|
194
|
+
return `${theme.fg("muted", "(")}${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
|
|
195
|
+
}
|
|
154
196
|
|
|
155
|
-
|
|
197
|
+
function markToolTiming(options: ToolRenderResultParams[1], context: ToolRenderContext): void {
|
|
198
|
+
const state = context.state as TimerRenderState;
|
|
199
|
+
|
|
200
|
+
if ((!options.isPartial || context.isError) && state.startedAt !== undefined) {
|
|
156
201
|
state.endedAt ??= Date.now();
|
|
157
|
-
if (state.interval) {
|
|
158
|
-
clearInterval(state.interval);
|
|
159
|
-
state.interval = undefined;
|
|
160
|
-
}
|
|
161
202
|
}
|
|
162
203
|
|
|
163
|
-
|
|
204
|
+
if ((!options.isPartial || context.isError) && state.interval) {
|
|
205
|
+
clearInterval(state.interval);
|
|
206
|
+
state.interval = undefined;
|
|
207
|
+
}
|
|
164
208
|
}
|
|
165
209
|
|
|
166
210
|
function renderQuietCollapsedResult(
|
|
167
|
-
|
|
211
|
+
_result: ToolRenderResultParams[0],
|
|
168
212
|
options: ToolRenderResultParams[1],
|
|
169
|
-
|
|
213
|
+
_theme: RenderTheme,
|
|
170
214
|
context: ToolRenderContext,
|
|
171
|
-
):
|
|
172
|
-
|
|
215
|
+
): QuietResultRenderComponent {
|
|
216
|
+
markToolTiming(options, context);
|
|
173
217
|
const component = context.lastComponent instanceof QuietResultRenderComponent
|
|
174
218
|
? context.lastComponent
|
|
175
219
|
: new QuietResultRenderComponent();
|
|
176
|
-
component.
|
|
177
|
-
|
|
178
|
-
const output = getTextOutput(result);
|
|
179
|
-
const outputLines = output ? output.split("\n") : [];
|
|
180
|
-
|
|
181
|
-
if (outputLines.length > 0) {
|
|
182
|
-
const firstLine = outputLines[0] ?? "";
|
|
183
|
-
const hiddenLines = Math.max(0, outputLines.length - COLLAPSED_PREVIEW_LINES);
|
|
184
|
-
component.addChild({
|
|
185
|
-
render: (width) => [renderCollapsedLine(firstLine, hiddenLines, theme, width)],
|
|
186
|
-
invalidate: () => undefined,
|
|
187
|
-
});
|
|
188
|
-
} else if (options.isPartial) {
|
|
189
|
-
component.addChild(new Text(theme.fg("muted", "Running..."), 0, 0));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const warnings = getTruncationWarnings(result);
|
|
193
|
-
if (warnings.length > 0) {
|
|
194
|
-
component.addChild(new Text(theme.fg("warning", `[${warnings.join(". ")}]`), 0, 0));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (state.startedAt !== undefined && options.isPartial) {
|
|
198
|
-
component.addChild(new Text(theme.fg("muted", `Elapsed ${formatDuration(Date.now() - state.startedAt)}`), 0, 0));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
component.invalidate();
|
|
220
|
+
component.setLinesRenderer(() => []);
|
|
202
221
|
return component;
|
|
203
222
|
}
|
|
204
223
|
|
|
205
|
-
function getPathArg(args: unknown): string | undefined {
|
|
206
|
-
if (!args || typeof args !== "object") return undefined;
|
|
207
|
-
const values = args as { path?: unknown; file_path?: unknown };
|
|
208
|
-
return typeof values.file_path === "string"
|
|
209
|
-
? values.file_path
|
|
210
|
-
: typeof values.path === "string"
|
|
211
|
-
? values.path
|
|
212
|
-
: undefined;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function formatQuietEditCall(args: unknown, theme: ToolRenderCallParams[1]): string {
|
|
216
|
-
const path = getPathArg(args);
|
|
217
|
-
const edits = args && typeof args === "object" && Array.isArray((args as { edits?: unknown }).edits)
|
|
218
|
-
? (args as { edits: unknown[] }).edits.length
|
|
219
|
-
: undefined;
|
|
220
|
-
const editSummary = typeof edits === "number" && edits > 0 ? `${plural(edits, "edit block")} hidden` : "preview hidden";
|
|
221
|
-
return `${theme.fg("toolTitle", theme.bold("edit"))} ${theme.fg("accent", path ?? "...")}${theme.fg(
|
|
222
|
-
"muted",
|
|
223
|
-
` ... ${editSummary} (${keyHint("app.tools.expand", "to expand")})`,
|
|
224
|
-
)}`;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function formatQuietWriteCall(args: unknown, theme: ToolRenderCallParams[1]): string {
|
|
228
|
-
const path = getPathArg(args);
|
|
229
|
-
const content = args && typeof args === "object" ? (args as { content?: unknown }).content : undefined;
|
|
230
|
-
const contentSummary = typeof content === "string"
|
|
231
|
-
? `${plural(content.split("\n").length, "line")}, ${formatSize(Buffer.byteLength(content, "utf8"))} hidden`
|
|
232
|
-
: "content hidden";
|
|
233
|
-
return `${theme.fg("toolTitle", theme.bold("write"))} ${theme.fg("accent", path ?? "...")}${theme.fg(
|
|
234
|
-
"muted",
|
|
235
|
-
` ... ${contentSummary} (${keyHint("app.tools.expand", "to expand")})`,
|
|
236
|
-
)}`;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
224
|
function renderQuietCall(
|
|
240
225
|
toolName: string,
|
|
241
226
|
base: ToolDefinition,
|
|
@@ -243,27 +228,29 @@ function renderQuietCall(
|
|
|
243
228
|
theme: ToolRenderCallParams[1],
|
|
244
229
|
context: ToolRenderCallParams[2],
|
|
245
230
|
) {
|
|
246
|
-
|
|
247
|
-
|
|
231
|
+
const state = context.state as TimerRenderState;
|
|
232
|
+
if (context.executionStarted && state.startedAt === undefined) {
|
|
233
|
+
state.startedAt = Date.now();
|
|
234
|
+
state.endedAt = undefined;
|
|
248
235
|
}
|
|
249
236
|
|
|
250
|
-
if (
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
? "toolSuccessBg"
|
|
256
|
-
: "toolPendingBg";
|
|
257
|
-
component.setBgFn((text) => theme.bg(bgColor, text));
|
|
258
|
-
component.clear();
|
|
259
|
-
component.addChild(new Text(formatQuietEditCall(args, theme), 0, 0));
|
|
260
|
-
component.invalidate();
|
|
261
|
-
return component;
|
|
237
|
+
if (context.expanded || !QUIET_CALL_TOOL_NAMES.has(toolName)) {
|
|
238
|
+
const delegateContext = context.lastComponent instanceof QuietCallRenderComponent
|
|
239
|
+
? { ...context, lastComponent: undefined }
|
|
240
|
+
: context;
|
|
241
|
+
return base.renderCall?.(args, theme, delegateContext) ?? new Text(theme.fg("toolTitle", theme.bold(toolName)), 0, 0);
|
|
262
242
|
}
|
|
263
243
|
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
244
|
+
const component = context.lastComponent instanceof QuietCallRenderComponent
|
|
245
|
+
? context.lastComponent
|
|
246
|
+
: new QuietCallRenderComponent();
|
|
247
|
+
const line = formatQuietCallLine(toolName, args, theme);
|
|
248
|
+
const hint = formatExpandHint(theme);
|
|
249
|
+
component.setLinesRenderer((width) => [
|
|
250
|
+
truncateToWidth(line, width, "..."),
|
|
251
|
+
truncateToWidth(hint, width, "..."),
|
|
252
|
+
]);
|
|
253
|
+
return component;
|
|
267
254
|
}
|
|
268
255
|
|
|
269
256
|
function createQuietToolDefinition(base: ToolDefinition): ToolDefinition {
|
|
@@ -334,7 +321,7 @@ export default function quietToolsExtension(pi: ExtensionAPI) {
|
|
|
334
321
|
});
|
|
335
322
|
|
|
336
323
|
pi.registerCommand("quiet-tools", {
|
|
337
|
-
description: "Toggle
|
|
324
|
+
description: "Toggle one-line collapsed invocations for built-in tool rows",
|
|
338
325
|
getArgumentCompletions: (prefix) => {
|
|
339
326
|
const commands = ["on", "off", "toggle", "status"];
|
|
340
327
|
const query = prefix.trim().toLowerCase();
|
|
@@ -347,7 +334,7 @@ export default function quietToolsExtension(pi: ExtensionAPI) {
|
|
|
347
334
|
if (action === "on" || action === "enable") {
|
|
348
335
|
enabled = true;
|
|
349
336
|
registerTools(ctx.cwd);
|
|
350
|
-
ctx.ui.notify("Quiet tool previews enabled: collapsed built-in tool rows show one-line
|
|
337
|
+
ctx.ui.notify("Quiet tool previews enabled: collapsed built-in tool rows show a one-line invocation plus an expand hint.", "info");
|
|
351
338
|
return;
|
|
352
339
|
}
|
|
353
340
|
|
|
@@ -363,7 +350,7 @@ export default function quietToolsExtension(pi: ExtensionAPI) {
|
|
|
363
350
|
registerTools(ctx.cwd);
|
|
364
351
|
ctx.ui.notify(
|
|
365
352
|
enabled
|
|
366
|
-
? "Quiet tool previews enabled: collapsed built-in tool rows show one-line
|
|
353
|
+
? "Quiet tool previews enabled: collapsed built-in tool rows show a one-line invocation plus an expand hint."
|
|
367
354
|
: "Quiet tool previews disabled: restored pi's standard built-in tool renderers.",
|
|
368
355
|
"info",
|
|
369
356
|
);
|
|
@@ -372,7 +359,7 @@ export default function quietToolsExtension(pi: ExtensionAPI) {
|
|
|
372
359
|
|
|
373
360
|
if (action === "status") {
|
|
374
361
|
ctx.ui.notify(
|
|
375
|
-
`Quiet tool previews are ${enabled ? "enabled" : "disabled"}. Collapsed
|
|
362
|
+
`Quiet tool previews are ${enabled ? "enabled" : "disabled"}. Collapsed tool rows ${enabled ? "show a one-line invocation and hide output until expanded" : "use pi's default rendering"}. Model-visible tool results are unchanged.`,
|
|
376
363
|
"info",
|
|
377
364
|
);
|
|
378
365
|
return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-quiet-tools",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A pi extension that visually compacts collapsed built-in tool
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A pi extension that visually compacts collapsed built-in tool rows in the TUI without changing tool results sent to the model.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "tools", "terminal", "tui"],
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A collection of pi extensions, including a GitHub librarian with opt-in local repo checkout caching, a minimal custom footer, an Amp-style oracle, a 200k context cap for auto-compaction, a local HTML context inspector, quiet one-line collapsed
|
|
3
|
+
"version": "0.1.20",
|
|
4
|
+
"description": "A collection of pi extensions, including a GitHub librarian with opt-in local repo checkout caching, a minimal custom footer, an Amp-style oracle, a 200k context cap for auto-compaction, a local HTML context inspector, quiet one-line collapsed invocation previews, a permission gate for dangerous bash commands, confirm-before-destructive session actions, and terminal notifications when pi is ready for input.",
|
|
5
5
|
"keywords": ["pi-package", "pi", "terminal", "agent"],
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|