@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 quiet one-line previews without changing model-visible tool results; toggle temporarily with `/quiet-tools`.
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.19
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 output renders as one output line plus an inline hidden-line count and `Ctrl+O` expand hint. Expanding with `Ctrl+O` still shows pi's full rendered output.
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 `edit` and `write`, the collapsed call preview is also shortened so large diffs or file contents do not fill the TUI. Expanding restores pi's normal renderer.
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 { Box, Container, Text, truncateToWidth } from "@earendil-works/pi-tui";
17
+ import { Container, Text, truncateToWidth } from "@earendil-works/pi-tui";
19
18
 
20
- const COLLAPSED_PREVIEW_LINES = 1;
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
- type TruncationLike = {
38
- truncated?: boolean;
39
- truncatedBy?: "lines" | "bytes";
40
- firstLineExceedsLimit?: boolean;
41
- outputLines?: number;
42
- totalLines?: number;
43
- maxBytes?: number;
44
- maxLines?: number;
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 QuietResultRenderComponent extends Container {}
48
+ class QuietCallRenderComponent extends QuietLinesRenderComponent {}
49
+ class QuietResultRenderComponent extends QuietLinesRenderComponent {}
48
50
 
49
- function sanitizePreviewText(text: string): string {
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 getTextOutput(result: ToolRenderResultParams[0]): string {
57
- return sanitizePreviewText(
58
- result.content
59
- .filter((content) => content.type === "text")
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 visibleLength(text: string): number {
67
- return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").length;
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 plural(count: number, singular: string): string {
71
- return `${count} ${singular}${count === 1 ? "" : "s"}`;
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 formatDuration(ms: number): string {
75
- return `${(ms / 1000).toFixed(1)}s`;
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 renderCollapsedLine(line: string, hiddenLines: number, theme: RenderTheme, width: number): string {
79
- if (width <= 0) return "";
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
- const hint = theme.fg(
87
- "muted",
88
- ` ... ${plural(hiddenLines, "more line")} (${keyHint("app.tools.expand", "to expand")})`,
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
- return `${truncateToWidth(styledLine, width - hintWidth, "...")}${hint}`;
94
+ function formatToolTitle(toolName: string, theme: RenderTheme): string {
95
+ return theme.fg("toolTitle", theme.bold(toolName));
96
96
  }
97
97
 
98
- function formatLimitWarning(label: string, count: unknown): string | undefined {
99
- return typeof count === "number" ? `${count} ${label} limit` : undefined;
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 getTruncationWarnings(result: ToolRenderResultParams[0]): string[] {
103
- const details = result.details as
104
- | {
105
- truncation?: TruncationLike;
106
- fullOutputPath?: string;
107
- matchLimitReached?: number;
108
- resultLimitReached?: number;
109
- entryLimitReached?: number;
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
- if (truncation?.truncated) {
121
- if (truncation.firstLineExceedsLimit) {
122
- warnings.push(`First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
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
- const matchLimitWarning = formatLimitWarning("matches", details?.matchLimitReached);
135
- if (matchLimitWarning) warnings.push(matchLimitWarning);
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
- const resultLimitWarning = formatLimitWarning("results", details?.resultLimitReached);
138
- if (resultLimitWarning) warnings.push(resultLimitWarning);
129
+ function formatSearchPath(rawPath: string | null, theme: RenderTheme): string {
130
+ return rawPath === null ? invalidArgText(theme) : shortenPath(rawPath || ".");
131
+ }
139
132
 
140
- const entryLimitWarning = formatLimitWarning("entries", details?.entryLimitReached);
141
- if (entryLimitWarning) warnings.push(entryLimitWarning);
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
- if (details?.linesTruncated) warnings.push("Some lines truncated");
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
- return warnings;
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 syncElapsedTimer(options: ToolRenderResultParams[1], context: ToolRenderContext): TimerRenderState {
149
- const state = context.state as TimerRenderState;
168
+ function formatQuietPathOnlyCall(toolName: "edit" | "write", args: unknown, theme: RenderTheme): string {
169
+ return `${formatToolTitle(toolName, theme)} ${formatPathArg(args, theme)}`;
170
+ }
150
171
 
151
- if (state.startedAt !== undefined && options.isPartial && !state.interval) {
152
- state.interval = setInterval(() => context.invalidate(), 1000);
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
- if (!options.isPartial || context.isError) {
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
- return state;
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
- result: ToolRenderResultParams[0],
211
+ _result: ToolRenderResultParams[0],
168
212
  options: ToolRenderResultParams[1],
169
- theme: RenderTheme,
213
+ _theme: RenderTheme,
170
214
  context: ToolRenderContext,
171
- ): Container {
172
- const state = syncElapsedTimer(options, context);
215
+ ): QuietResultRenderComponent {
216
+ markToolTiming(options, context);
173
217
  const component = context.lastComponent instanceof QuietResultRenderComponent
174
218
  ? context.lastComponent
175
219
  : new QuietResultRenderComponent();
176
- component.clear();
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
- if (context.expanded || !QUIET_CALL_TOOL_NAMES.has(toolName)) {
247
- return base.renderCall?.(args, theme, context) ?? new Text(theme.fg("toolTitle", theme.bold(toolName)), 0, 0);
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 (toolName === "edit") {
251
- const component = context.lastComponent instanceof Box ? context.lastComponent : new Box(1, 1);
252
- const bgColor = context.isError
253
- ? "toolErrorBg"
254
- : context.executionStarted && !context.isPartial
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 text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
265
- text.setText(formatQuietWriteCall(args, theme));
266
- return text;
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 compact collapsed previews for built-in tool rows",
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 output.", "info");
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 output."
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 preview lines: ${enabled ? COLLAPSED_PREVIEW_LINES : "pi default"}. Model-visible tool results are unchanged.`,
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.0",
4
- "description": "A pi extension that visually compacts collapsed built-in tool output in the TUI without changing tool results sent to the model.",
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.19",
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 tool previews, a permission gate for dangerous bash commands, confirm-before-destructive session actions, and terminal notifications when pi is ready for input.",
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": {