@diegopetrucci/pi-extensions 0.1.74 → 0.1.76

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.
Files changed (93) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +9 -4
  3. package/extensions/agent-workflow-audit/index.ts +1398 -1009
  4. package/extensions/agent-workflow-audit/package.json +1 -1
  5. package/extensions/annotate-git-diff/README.md +3 -1
  6. package/extensions/annotate-git-diff/clipboard.ts +109 -100
  7. package/extensions/annotate-git-diff/git.ts +853 -767
  8. package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
  9. package/extensions/annotate-git-diff/index.ts +712 -671
  10. package/extensions/annotate-git-diff/package.json +1 -1
  11. package/extensions/annotate-git-diff/prompt.ts +45 -45
  12. package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
  13. package/extensions/annotate-git-diff/review-server.ts +337 -311
  14. package/extensions/annotate-git-diff/types.ts +132 -132
  15. package/extensions/annotate-git-diff/ui.ts +27 -27
  16. package/extensions/annotate-git-diff/watch.ts +74 -74
  17. package/extensions/annotate-git-diff/web/app.js +1947 -1884
  18. package/extensions/annotate-git-diff/web/index.html +5 -6
  19. package/extensions/annotate-last-message/README.md +1 -1
  20. package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
  21. package/extensions/annotate-last-message/index.ts +210 -206
  22. package/extensions/annotate-last-message/package.json +1 -1
  23. package/extensions/annotate-last-message/prompt.ts +87 -66
  24. package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
  25. package/extensions/annotate-last-message/session.ts +94 -89
  26. package/extensions/annotate-last-message/theme.ts +199 -193
  27. package/extensions/annotate-last-message/types.ts +23 -23
  28. package/extensions/annotate-last-message/ui.ts +27 -25
  29. package/extensions/annotate-last-message/web/app.js +4 -7
  30. package/extensions/annotate-last-message/web/md-renderer.js +1 -1
  31. package/extensions/brrr/README.md +1 -1
  32. package/extensions/brrr/index.ts +213 -191
  33. package/extensions/brrr/package.json +1 -1
  34. package/extensions/claude-fast/README.md +5 -79
  35. package/extensions/claude-fast/index.ts +31 -289
  36. package/extensions/claude-fast/package.json +2 -3
  37. package/extensions/code-reviewer/README.md +1 -1
  38. package/extensions/code-reviewer/index.ts +1598 -1350
  39. package/extensions/code-reviewer/package.json +1 -1
  40. package/extensions/confirm-destructive/README.md +1 -1
  41. package/extensions/confirm-destructive/index.ts +55 -51
  42. package/extensions/confirm-destructive/package.json +1 -1
  43. package/extensions/context-cap/index.ts +312 -304
  44. package/extensions/context-cap/package.json +1 -1
  45. package/extensions/context-inspector/index.ts +1211 -1064
  46. package/extensions/context-inspector/package.json +1 -1
  47. package/extensions/contrarian/index.ts +1854 -1735
  48. package/extensions/contrarian/package.json +1 -1
  49. package/extensions/dirty-repo-guard/index.ts +41 -41
  50. package/extensions/dynamic-context-pruning/README.md +6 -38
  51. package/extensions/dynamic-context-pruning/index.ts +2124 -1980
  52. package/extensions/dynamic-context-pruning/package.json +1 -1
  53. package/extensions/fast/README.md +50 -12
  54. package/extensions/fast/index.ts +513 -317
  55. package/extensions/fast/package.json +1 -1
  56. package/extensions/git-footer/README.md +1 -1
  57. package/extensions/git-footer/index.ts +458 -411
  58. package/extensions/git-footer/package.json +2 -3
  59. package/extensions/gnosis/index.ts +180 -130
  60. package/extensions/gnosis/package.json +1 -1
  61. package/extensions/inline-bash/index.ts +88 -88
  62. package/extensions/inline-bash/package.json +1 -1
  63. package/extensions/librarian/index.ts +1669 -1419
  64. package/extensions/librarian/package.json +1 -1
  65. package/extensions/minimal-footer/index.ts +994 -1012
  66. package/extensions/minimal-footer/openai-usage.ts +195 -190
  67. package/extensions/notify/index.ts +222 -230
  68. package/extensions/notify/package.json +1 -1
  69. package/extensions/openai-fast/README.md +5 -73
  70. package/extensions/openai-fast/index.ts +32 -272
  71. package/extensions/openai-fast/package.json +2 -3
  72. package/extensions/oracle/index.ts +1731 -1625
  73. package/extensions/oracle/package.json +1 -1
  74. package/extensions/permission-gate/index.ts +658 -608
  75. package/extensions/permission-gate/package.json +1 -1
  76. package/extensions/permission-gate/powershell-safety.ts +840 -793
  77. package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
  78. package/extensions/project-mcp-json/README.md +199 -0
  79. package/extensions/project-mcp-json/config.ts +564 -0
  80. package/extensions/project-mcp-json/index.ts +119 -0
  81. package/extensions/project-mcp-json/package.json +38 -0
  82. package/extensions/quiet-tools/index.ts +282 -264
  83. package/extensions/review/README.md +2 -2
  84. package/extensions/review/index.ts +1745 -1872
  85. package/extensions/review/package.json +1 -1
  86. package/extensions/todo/index.ts +284 -265
  87. package/extensions/triage-comments/README.md +2 -2
  88. package/extensions/triage-comments/index.ts +2294 -1882
  89. package/extensions/triage-comments/package.json +1 -1
  90. package/package.json +6 -3
  91. package/assets/social-preview.png +0 -0
  92. package/extensions/claude-fast/claude-fast.example.json +0 -4
  93. package/extensions/openai-fast/openai-fast.example.json +0 -4
@@ -1,18 +1,18 @@
1
1
  import { homedir } from "node:os";
2
2
 
3
3
  import {
4
- createBashToolDefinition,
5
- createEditToolDefinition,
6
- createFindToolDefinition,
7
- createGrepToolDefinition,
8
- createLsToolDefinition,
9
- createReadToolDefinition,
10
- createWriteToolDefinition,
11
- keyHint,
12
- SettingsManager,
13
- type ExtensionAPI,
14
- type ToolDefinition as PiToolDefinition,
15
- type ToolsOptions,
4
+ createBashToolDefinition,
5
+ createEditToolDefinition,
6
+ createFindToolDefinition,
7
+ createGrepToolDefinition,
8
+ createLsToolDefinition,
9
+ createReadToolDefinition,
10
+ createWriteToolDefinition,
11
+ keyHint,
12
+ SettingsManager,
13
+ type ExtensionAPI,
14
+ type ToolDefinition as PiToolDefinition,
15
+ type ToolsOptions,
16
16
  } from "@earendil-works/pi-coding-agent";
17
17
  import { Container, Text, truncateToWidth } from "@earendil-works/pi-tui";
18
18
 
@@ -27,351 +27,369 @@ type RenderTheme = ToolRenderResultParams[2];
27
27
  type ToolRenderContext = ToolRenderResultParams[3];
28
28
 
29
29
  type TimerRenderState = {
30
- startedAt?: number;
31
- endedAt?: number;
32
- interval?: ReturnType<typeof setInterval>;
30
+ startedAt?: number;
31
+ endedAt?: number;
32
+ interval?: ReturnType<typeof setInterval>;
33
33
  };
34
34
 
35
35
  class QuietLinesRenderComponent extends Container {
36
- private linesRenderer: ((width: number) => string[]) | undefined;
36
+ private linesRenderer: ((width: number) => string[]) | undefined;
37
37
 
38
- setLinesRenderer(linesRenderer: (width: number) => string[]): void {
39
- this.linesRenderer = linesRenderer;
40
- this.invalidate();
41
- }
38
+ setLinesRenderer(linesRenderer: (width: number) => string[]): void {
39
+ this.linesRenderer = linesRenderer;
40
+ this.invalidate();
41
+ }
42
42
 
43
- render(width: number): string[] {
44
- return this.linesRenderer?.(width).filter((line) => line) ?? [];
45
- }
43
+ render(width: number): string[] {
44
+ return this.linesRenderer?.(width).filter((line) => line) ?? [];
45
+ }
46
46
  }
47
47
 
48
48
  class QuietCallRenderComponent extends QuietLinesRenderComponent {}
49
49
  class QuietResultRenderComponent extends QuietLinesRenderComponent {}
50
50
 
51
51
  function sanitizeInlineText(text: string): string {
52
- return text
53
- .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
54
- .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
55
- .replace(/[\r\n\t]+/g, " ")
56
- .replace(/[\x00-\x08\x0B-\x1F\x7F]/g, "");
52
+ return text
53
+ .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
54
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
55
+ .replace(/[\r\n\t]+/g, " ")
56
+ .replace(/[\x00-\x08\x0B-\x1F\x7F]/g, "");
57
57
  }
58
58
 
59
59
  function str(value: unknown): string | null {
60
- if (typeof value === "string") return sanitizeInlineText(value);
61
- if (value == null) return "";
62
- return null;
60
+ if (typeof value === "string") return sanitizeInlineText(value);
61
+ if (value == null) return "";
62
+ return null;
63
63
  }
64
64
 
65
65
  function asRecord(args: unknown): Record<string, unknown> | undefined {
66
- return args && typeof args === "object" ? (args as Record<string, unknown>) : undefined;
66
+ return args && typeof args === "object" ? (args as Record<string, unknown>) : undefined;
67
67
  }
68
68
 
69
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 "";
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 "";
78
78
  }
79
79
 
80
80
  function numberArg(args: unknown, name: string): number | undefined {
81
- const value = asRecord(args)?.[name];
82
- return typeof value === "number" ? value : undefined;
81
+ const value = asRecord(args)?.[name];
82
+ return typeof value === "number" ? value : undefined;
83
83
  }
84
84
 
85
85
  function invalidArgText(theme: RenderTheme): string {
86
- return theme.fg("error", "[invalid arg]");
86
+ return theme.fg("error", "[invalid arg]");
87
87
  }
88
88
 
89
89
  function shortenPath(path: string): string {
90
- const home = homedir();
91
- return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
90
+ const home = homedir();
91
+ return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
92
92
  }
93
93
 
94
94
  function formatToolTitle(toolName: string, theme: RenderTheme): string {
95
- return theme.fg("toolTitle", theme.bold(toolName));
95
+ return theme.fg("toolTitle", theme.bold(toolName));
96
96
  }
97
97
 
98
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);
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);
102
102
  }
103
103
 
104
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}` : ""}`);
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
111
  }
112
112
 
113
113
  function formatQuietReadCall(args: unknown, theme: RenderTheme): string {
114
- return `${formatToolTitle("read", theme)} ${formatPathArg(args, theme)}${formatReadLineRange(args, theme)}`;
114
+ return `${formatToolTitle("read", theme)} ${formatPathArg(args, theme)}${formatReadLineRange(args, theme)}`;
115
115
  }
116
116
 
117
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}`;
118
+ const command = str(asRecord(args)?.command);
119
+ const timeout = numberArg(args, "timeout");
120
+ const commandDisplay =
121
+ command === null
122
+ ? invalidArgText(theme)
123
+ : command
124
+ ? theme.fg("toolTitle", theme.bold(command))
125
+ : theme.fg("toolOutput", "...");
126
+ const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
127
+ return `${theme.fg("toolTitle", theme.bold("$"))} ${commandDisplay}${timeoutSuffix}`;
127
128
  }
128
129
 
129
130
  function formatSearchPath(rawPath: string | null, theme: RenderTheme): string {
130
- return rawPath === null ? invalidArgText(theme) : shortenPath(rawPath || ".");
131
+ return rawPath === null ? invalidArgText(theme) : shortenPath(rawPath || ".");
131
132
  }
132
133
 
133
134
  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;
135
+ const pattern = str(asRecord(args)?.pattern);
136
+ const rawPath = str(asRecord(args)?.path);
137
+ const glob = str(asRecord(args)?.glob);
138
+ const limit = numberArg(args, "limit");
139
+ let text = `${formatToolTitle("grep", theme)} ${
140
+ pattern === null ? invalidArgText(theme) : theme.fg("accent", `/${pattern || ""}/`)
141
+ }${theme.fg("toolOutput", ` in ${formatSearchPath(rawPath, theme)}`)}`;
142
+ if (glob) text += theme.fg("toolOutput", ` (${glob})`);
143
+ if (glob === null) text += ` ${invalidArgText(theme)}`;
144
+ if (limit !== undefined) text += theme.fg("toolOutput", ` limit ${limit}`);
145
+ return text;
145
146
  }
146
147
 
147
148
  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;
149
+ const pattern = str(asRecord(args)?.pattern);
150
+ const rawPath = str(asRecord(args)?.path);
151
+ const limit = numberArg(args, "limit");
152
+ let text = `${formatToolTitle("find", theme)} ${
153
+ pattern === null ? invalidArgText(theme) : theme.fg("accent", pattern || "")
154
+ }${theme.fg("toolOutput", ` in ${formatSearchPath(rawPath, theme)}`)}`;
155
+ if (limit !== undefined) text += theme.fg("toolOutput", ` (limit ${limit})`);
156
+ return text;
156
157
  }
157
158
 
158
159
  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;
160
+ const rawPath = str(asRecord(args)?.path);
161
+ const limit = numberArg(args, "limit");
162
+ let text = `${formatToolTitle("ls", theme)} ${
163
+ rawPath === null ? invalidArgText(theme) : theme.fg("accent", shortenPath(rawPath || "."))
164
+ }`;
165
+ if (limit !== undefined) text += theme.fg("toolOutput", ` (limit ${limit})`);
166
+ return text;
166
167
  }
167
168
 
168
- function formatQuietPathOnlyCall(toolName: "edit" | "write", args: unknown, theme: RenderTheme): string {
169
- return `${formatToolTitle(toolName, theme)} ${formatPathArg(args, theme)}`;
169
+ function formatQuietPathOnlyCall(
170
+ toolName: "edit" | "write",
171
+ args: unknown,
172
+ theme: RenderTheme,
173
+ ): string {
174
+ return `${formatToolTitle(toolName, theme)} ${formatPathArg(args, theme)}`;
170
175
  }
171
176
 
172
177
  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);
190
- }
178
+ switch (toolName) {
179
+ case "bash":
180
+ return formatQuietBashCall(args, theme);
181
+ case "edit":
182
+ return formatQuietPathOnlyCall("edit", args, theme);
183
+ case "find":
184
+ return formatQuietFindCall(args, theme);
185
+ case "grep":
186
+ return formatQuietGrepCall(args, theme);
187
+ case "ls":
188
+ return formatQuietLsCall(args, theme);
189
+ case "read":
190
+ return formatQuietReadCall(args, theme);
191
+ case "write":
192
+ return formatQuietPathOnlyCall("write", args, theme);
193
+ default:
194
+ return formatToolTitle(toolName, theme);
195
+ }
191
196
  }
192
197
 
193
198
  function formatExpandHint(theme: RenderTheme): string {
194
- return `${theme.fg("muted", "(")}${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
199
+ return `${theme.fg("muted", "(")}${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
195
200
  }
196
201
 
197
202
  function markToolTiming(options: ToolRenderResultParams[1], context: ToolRenderContext): void {
198
- const state = context.state as TimerRenderState;
203
+ const state = context.state as TimerRenderState;
199
204
 
200
- if ((!options.isPartial || context.isError) && state.startedAt !== undefined) {
201
- state.endedAt ??= Date.now();
202
- }
205
+ if ((!options.isPartial || context.isError) && state.startedAt !== undefined) {
206
+ state.endedAt ??= Date.now();
207
+ }
203
208
 
204
- if ((!options.isPartial || context.isError) && state.interval) {
205
- clearInterval(state.interval);
206
- state.interval = undefined;
207
- }
209
+ if ((!options.isPartial || context.isError) && state.interval) {
210
+ clearInterval(state.interval);
211
+ state.interval = undefined;
212
+ }
208
213
  }
209
214
 
210
215
  function renderQuietCollapsedResult(
211
- _result: ToolRenderResultParams[0],
212
- options: ToolRenderResultParams[1],
213
- _theme: RenderTheme,
214
- context: ToolRenderContext,
216
+ _result: ToolRenderResultParams[0],
217
+ options: ToolRenderResultParams[1],
218
+ _theme: RenderTheme,
219
+ context: ToolRenderContext,
215
220
  ): QuietResultRenderComponent {
216
- markToolTiming(options, context);
217
- const component = context.lastComponent instanceof QuietResultRenderComponent
218
- ? context.lastComponent
219
- : new QuietResultRenderComponent();
220
- component.setLinesRenderer(() => []);
221
- return component;
221
+ markToolTiming(options, context);
222
+ const component =
223
+ context.lastComponent instanceof QuietResultRenderComponent
224
+ ? context.lastComponent
225
+ : new QuietResultRenderComponent();
226
+ component.setLinesRenderer(() => []);
227
+ return component;
222
228
  }
223
229
 
224
230
  function renderQuietCall(
225
- toolName: string,
226
- base: ToolDefinition,
227
- args: ToolRenderCallParams[0],
228
- theme: ToolRenderCallParams[1],
229
- context: ToolRenderCallParams[2],
231
+ toolName: string,
232
+ base: ToolDefinition,
233
+ args: ToolRenderCallParams[0],
234
+ theme: ToolRenderCallParams[1],
235
+ context: ToolRenderCallParams[2],
230
236
  ) {
231
- const state = context.state as TimerRenderState;
232
- if (context.executionStarted && state.startedAt === undefined) {
233
- state.startedAt = Date.now();
234
- state.endedAt = undefined;
235
- }
236
-
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);
242
- }
243
-
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;
237
+ const state = context.state as TimerRenderState;
238
+ if (context.executionStarted && state.startedAt === undefined) {
239
+ state.startedAt = Date.now();
240
+ state.endedAt = undefined;
241
+ }
242
+
243
+ if (context.expanded || !QUIET_CALL_TOOL_NAMES.has(toolName)) {
244
+ const delegateContext =
245
+ context.lastComponent instanceof QuietCallRenderComponent
246
+ ? { ...context, lastComponent: undefined }
247
+ : context;
248
+ return (
249
+ base.renderCall?.(args, theme, delegateContext) ??
250
+ new Text(theme.fg("toolTitle", theme.bold(toolName)), 0, 0)
251
+ );
252
+ }
253
+
254
+ const component =
255
+ context.lastComponent instanceof QuietCallRenderComponent
256
+ ? context.lastComponent
257
+ : new QuietCallRenderComponent();
258
+ const line = formatQuietCallLine(toolName, args, theme);
259
+ const hint = formatExpandHint(theme);
260
+ component.setLinesRenderer((width) => [
261
+ truncateToWidth(line, width, "..."),
262
+ truncateToWidth(hint, width, "..."),
263
+ ]);
264
+ return component;
254
265
  }
255
266
 
256
267
  function createQuietToolDefinition(base: ToolDefinition): ToolDefinition {
257
- const baseRenderResult = base.renderResult;
258
-
259
- return {
260
- ...base,
261
- renderCall(args, theme, context) {
262
- return renderQuietCall(base.name, base, args, theme, context);
263
- },
264
- renderResult(result, options, theme, context) {
265
- if (options.expanded && baseRenderResult) {
266
- const delegateContext = context.lastComponent instanceof QuietResultRenderComponent
267
- ? { ...context, lastComponent: undefined }
268
- : context;
269
- return baseRenderResult(result, options, theme, delegateContext);
270
- }
271
-
272
- return renderQuietCollapsedResult(result, options, theme, context);
273
- },
274
- };
268
+ const baseRenderResult = base.renderResult;
269
+
270
+ return {
271
+ ...base,
272
+ renderCall(args, theme, context) {
273
+ return renderQuietCall(base.name, base, args, theme, context);
274
+ },
275
+ renderResult(result, options, theme, context) {
276
+ if (options.expanded && baseRenderResult) {
277
+ const delegateContext =
278
+ context.lastComponent instanceof QuietResultRenderComponent
279
+ ? { ...context, lastComponent: undefined }
280
+ : context;
281
+ return baseRenderResult(result, options, theme, delegateContext);
282
+ }
283
+
284
+ return renderQuietCollapsedResult(result, options, theme, context);
285
+ },
286
+ };
275
287
  }
276
288
 
277
289
  function createBaseToolOptions(cwd: string): ToolsOptions | undefined {
278
- try {
279
- const settings = SettingsManager.create(cwd);
280
- return {
281
- read: { autoResizeImages: settings.getImageAutoResize() },
282
- bash: {
283
- commandPrefix: settings.getShellCommandPrefix(),
284
- shellPath: settings.getShellPath(),
285
- },
286
- };
287
- } catch {
288
- return undefined;
289
- }
290
+ try {
291
+ const settings = SettingsManager.create(cwd);
292
+ return {
293
+ read: { autoResizeImages: settings.getImageAutoResize() },
294
+ bash: {
295
+ commandPrefix: settings.getShellCommandPrefix(),
296
+ shellPath: settings.getShellPath(),
297
+ },
298
+ };
299
+ } catch {
300
+ return undefined;
301
+ }
290
302
  }
291
303
 
292
304
  function createBaseToolDefinitions(cwd: string): ToolDefinition[] {
293
- const options = createBaseToolOptions(cwd);
294
- return [
295
- createReadToolDefinition(cwd, options?.read),
296
- createBashToolDefinition(cwd, options?.bash),
297
- createEditToolDefinition(cwd, options?.edit),
298
- createWriteToolDefinition(cwd, options?.write),
299
- createGrepToolDefinition(cwd, options?.grep),
300
- createFindToolDefinition(cwd, options?.find),
301
- createLsToolDefinition(cwd, options?.ls),
302
- ] as ToolDefinition[];
305
+ const options = createBaseToolOptions(cwd);
306
+ return [
307
+ createReadToolDefinition(cwd, options?.read),
308
+ createBashToolDefinition(cwd, options?.bash),
309
+ createEditToolDefinition(cwd, options?.edit),
310
+ createWriteToolDefinition(cwd, options?.write),
311
+ createGrepToolDefinition(cwd, options?.grep),
312
+ createFindToolDefinition(cwd, options?.find),
313
+ createLsToolDefinition(cwd, options?.ls),
314
+ ] as ToolDefinition[];
303
315
  }
304
316
 
305
317
  function createQuietToolDefinitions(cwd: string, enabled: boolean): ToolDefinition[] {
306
- const baseDefinitions = createBaseToolDefinitions(cwd);
307
- return enabled ? baseDefinitions.map(createQuietToolDefinition) : baseDefinitions;
318
+ const baseDefinitions = createBaseToolDefinitions(cwd);
319
+ return enabled ? baseDefinitions.map(createQuietToolDefinition) : baseDefinitions;
308
320
  }
309
321
 
310
322
  export const __testing = {
311
- sanitizeInlineText,
312
- formatQuietCallLine,
313
- createQuietToolDefinition,
323
+ sanitizeInlineText,
324
+ formatQuietCallLine,
325
+ createQuietToolDefinition,
314
326
  };
315
327
 
316
328
  export default function quietToolsExtension(pi: ExtensionAPI) {
317
- let enabled = true;
318
-
319
- function registerTools(cwd: string): void {
320
- for (const tool of createQuietToolDefinitions(cwd, enabled)) {
321
- pi.registerTool(tool);
322
- }
323
- }
324
-
325
- pi.on("session_start", async (_event, ctx) => {
326
- registerTools(ctx.cwd);
327
- });
328
-
329
- pi.registerCommand("quiet-tools", {
330
- description: "Toggle one-line collapsed invocations for built-in tool rows",
331
- getArgumentCompletions: (prefix) => {
332
- const commands = ["on", "off", "toggle", "status"];
333
- const query = prefix.trim().toLowerCase();
334
- const matches = commands.filter((command) => command.startsWith(query));
335
- return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
336
- },
337
- handler: async (args, ctx) => {
338
- const action = args.trim().toLowerCase() || "toggle";
339
-
340
- if (action === "on" || action === "enable") {
341
- enabled = true;
342
- registerTools(ctx.cwd);
343
- ctx.ui.notify("Quiet tool previews enabled: collapsed built-in tool rows show a one-line invocation plus an expand hint.", "info");
344
- return;
345
- }
346
-
347
- if (action === "off" || action === "disable") {
348
- enabled = false;
349
- registerTools(ctx.cwd);
350
- ctx.ui.notify("Quiet tool previews disabled: restored pi's standard built-in tool renderers.", "info");
351
- return;
352
- }
353
-
354
- if (action === "toggle") {
355
- enabled = !enabled;
356
- registerTools(ctx.cwd);
357
- ctx.ui.notify(
358
- enabled
359
- ? "Quiet tool previews enabled: collapsed built-in tool rows show a one-line invocation plus an expand hint."
360
- : "Quiet tool previews disabled: restored pi's standard built-in tool renderers.",
361
- "info",
362
- );
363
- return;
364
- }
365
-
366
- if (action === "status") {
367
- ctx.ui.notify(
368
- `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.`,
369
- "info",
370
- );
371
- return;
372
- }
373
-
374
- ctx.ui.notify("Usage: /quiet-tools on | off | toggle | status", "warning");
375
- },
376
- });
329
+ let enabled = true;
330
+
331
+ function registerTools(cwd: string): void {
332
+ for (const tool of createQuietToolDefinitions(cwd, enabled)) {
333
+ pi.registerTool(tool);
334
+ }
335
+ }
336
+
337
+ pi.on("session_start", async (_event, ctx) => {
338
+ registerTools(ctx.cwd);
339
+ });
340
+
341
+ pi.registerCommand("quiet-tools", {
342
+ description: "Toggle one-line collapsed invocations for built-in tool rows",
343
+ getArgumentCompletions: (prefix) => {
344
+ const commands = ["on", "off", "toggle", "status"];
345
+ const query = prefix.trim().toLowerCase();
346
+ const matches = commands.filter((command) => command.startsWith(query));
347
+ return matches.length > 0 ? matches.map((value) => ({ value, label: value })) : null;
348
+ },
349
+ handler: async (args, ctx) => {
350
+ const action = args.trim().toLowerCase() || "toggle";
351
+
352
+ if (action === "on" || action === "enable") {
353
+ enabled = true;
354
+ registerTools(ctx.cwd);
355
+ ctx.ui.notify(
356
+ "Quiet tool previews enabled: collapsed built-in tool rows show a one-line invocation plus an expand hint.",
357
+ "info",
358
+ );
359
+ return;
360
+ }
361
+
362
+ if (action === "off" || action === "disable") {
363
+ enabled = false;
364
+ registerTools(ctx.cwd);
365
+ ctx.ui.notify(
366
+ "Quiet tool previews disabled: restored pi's standard built-in tool renderers.",
367
+ "info",
368
+ );
369
+ return;
370
+ }
371
+
372
+ if (action === "toggle") {
373
+ enabled = !enabled;
374
+ registerTools(ctx.cwd);
375
+ ctx.ui.notify(
376
+ enabled
377
+ ? "Quiet tool previews enabled: collapsed built-in tool rows show a one-line invocation plus an expand hint."
378
+ : "Quiet tool previews disabled: restored pi's standard built-in tool renderers.",
379
+ "info",
380
+ );
381
+ return;
382
+ }
383
+
384
+ if (action === "status") {
385
+ ctx.ui.notify(
386
+ `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.`,
387
+ "info",
388
+ );
389
+ return;
390
+ }
391
+
392
+ ctx.ui.notify("Usage: /quiet-tools on | off | toggle | status", "warning");
393
+ },
394
+ });
377
395
  }