@diegopetrucci/pi-extensions 0.1.74 → 0.1.78
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/LICENSE +2 -0
- package/README.md +17 -12
- package/extensions/agent-workflow-audit/README.md +1 -1
- package/extensions/agent-workflow-audit/index.ts +1631 -1009
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +3 -1
- package/extensions/annotate-git-diff/clipboard.ts +109 -100
- package/extensions/annotate-git-diff/git.ts +846 -768
- package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
- package/extensions/annotate-git-diff/index.ts +712 -671
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +45 -45
- package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
- package/extensions/annotate-git-diff/review-server.ts +337 -311
- package/extensions/annotate-git-diff/types.ts +132 -132
- package/extensions/annotate-git-diff/ui.ts +27 -27
- package/extensions/annotate-git-diff/watch.ts +74 -74
- package/extensions/annotate-git-diff/web/app.js +1947 -1884
- package/extensions/annotate-git-diff/web/index.html +5 -6
- package/extensions/annotate-last-message/README.md +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
- package/extensions/annotate-last-message/index.ts +210 -206
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/prompt.ts +87 -66
- package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
- package/extensions/annotate-last-message/session.ts +94 -89
- package/extensions/annotate-last-message/theme.ts +199 -193
- package/extensions/annotate-last-message/types.ts +23 -23
- package/extensions/annotate-last-message/ui.ts +27 -25
- package/extensions/annotate-last-message/web/app.js +4 -7
- package/extensions/annotate-last-message/web/index.html +3 -3
- package/extensions/annotate-last-message/web/md-renderer.js +1 -1
- package/extensions/brrr/README.md +1 -1
- package/extensions/brrr/index.ts +213 -191
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/README.md +5 -79
- package/extensions/claude-fast/index.ts +31 -289
- package/extensions/claude-fast/package.json +2 -3
- package/extensions/code-reviewer/README.md +6 -6
- package/extensions/code-reviewer/index.ts +1825 -1373
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +50 -52
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/README.md +9 -9
- package/extensions/context-cap/index.ts +313 -305
- package/extensions/context-cap/package.json +2 -2
- package/extensions/context-inspector/index.ts +1211 -1064
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/README.md +4 -3
- package/extensions/contrarian/index.ts +1863 -1737
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +37 -41
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/dynamic-context-pruning/README.md +11 -43
- package/extensions/dynamic-context-pruning/index.ts +2131 -2105
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +52 -14
- package/extensions/fast/index.ts +513 -317
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -1
- package/extensions/git-footer/index.ts +458 -411
- package/extensions/git-footer/package.json +2 -3
- package/extensions/gnosis/index.ts +180 -130
- package/extensions/gnosis/package.json +1 -1
- package/extensions/inline-bash/index.ts +87 -103
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/README.md +1 -1
- package/extensions/librarian/index.ts +1877 -1419
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/README.md +5 -5
- package/extensions/minimal-footer/index.ts +987 -1012
- package/extensions/minimal-footer/minimal-footer.example.json +1 -1
- package/extensions/minimal-footer/openai-usage.ts +195 -190
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/notify/index.ts +222 -230
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/README.md +5 -73
- package/extensions/openai-fast/index.ts +32 -272
- package/extensions/openai-fast/package.json +2 -3
- package/extensions/oracle/README.md +4 -3
- package/extensions/oracle/index.ts +1740 -1627
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/README.md +3 -0
- package/extensions/permission-gate/index.ts +785 -612
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/permission-gate/powershell-safety.ts +840 -793
- package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
- package/extensions/project-mcp-json/README.md +199 -0
- package/extensions/project-mcp-json/config.ts +564 -0
- package/extensions/project-mcp-json/index.ts +119 -0
- package/extensions/project-mcp-json/package.json +38 -0
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/README.md +30 -19
- package/extensions/quiet-tools/index.ts +376 -291
- package/extensions/quiet-tools/package.json +2 -2
- package/extensions/review/README.md +2 -2
- package/extensions/review/index.ts +1745 -1872
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +280 -280
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/README.md +6 -4
- package/extensions/triage-comments/index.ts +2620 -1888
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +9 -6
- package/assets/social-preview.png +0 -0
- package/extensions/claude-fast/claude-fast.example.json +0 -4
- package/extensions/openai-fast/openai-fast.example.json +0 -4
|
@@ -1,377 +1,462 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
createLsToolDefinition,
|
|
9
|
-
createReadToolDefinition,
|
|
10
|
-
createWriteToolDefinition,
|
|
11
|
-
keyHint,
|
|
12
|
-
SettingsManager,
|
|
13
|
-
type ExtensionAPI,
|
|
14
|
-
type ToolDefinition as PiToolDefinition,
|
|
15
|
-
type ToolsOptions,
|
|
4
|
+
keyHint,
|
|
5
|
+
type ExtensionAPI,
|
|
6
|
+
type ToolRendererResolver,
|
|
7
|
+
type ToolRenderers,
|
|
16
8
|
} from "@earendil-works/pi-coding-agent";
|
|
17
|
-
import {
|
|
9
|
+
import { Box, Container, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
18
10
|
|
|
19
|
-
const
|
|
11
|
+
const QUIET_TOOL_NAMES = new Set(["bash", "edit", "find", "grep", "ls", "read", "write"]);
|
|
12
|
+
const MAX_MCP_ARG_LENGTH = 2000;
|
|
20
13
|
|
|
21
|
-
type
|
|
22
|
-
type
|
|
23
|
-
type ToolRenderResult = NonNullable<ToolDefinition["renderResult"]>;
|
|
14
|
+
type ToolRenderCall = NonNullable<ToolRenderers["renderCall"]>;
|
|
15
|
+
type ToolRenderResult = NonNullable<ToolRenderers["renderResult"]>;
|
|
24
16
|
type ToolRenderCallParams = Parameters<ToolRenderCall>;
|
|
25
17
|
type ToolRenderResultParams = Parameters<ToolRenderResult>;
|
|
26
|
-
type RenderTheme =
|
|
27
|
-
type ToolRenderContext =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
type RenderTheme = ToolRenderCallParams[1];
|
|
19
|
+
type ToolRenderContext = ToolRenderCallParams[2];
|
|
20
|
+
type QuietToolRenderContext = ToolRenderContext & {
|
|
21
|
+
durationMs?: number;
|
|
22
|
+
outputPad?: number;
|
|
23
|
+
};
|
|
24
|
+
type BashRenderState = {
|
|
25
|
+
startedAt?: number;
|
|
26
|
+
endedAt?: number;
|
|
27
|
+
interval?: ReturnType<typeof setInterval>;
|
|
33
28
|
};
|
|
34
29
|
|
|
30
|
+
type QuietLinesRenderer = (width: number) => string[];
|
|
31
|
+
|
|
35
32
|
class QuietLinesRenderComponent extends Container {
|
|
36
|
-
|
|
33
|
+
private linesRenderer: QuietLinesRenderer | undefined;
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
setLinesRenderer(linesRenderer: QuietLinesRenderer): void {
|
|
36
|
+
this.linesRenderer = linesRenderer;
|
|
37
|
+
this.invalidate();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected renderLines(width: number): string[] {
|
|
41
|
+
return this.linesRenderer?.(width).filter((line) => line) ?? [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
render(width: number): string[] {
|
|
45
|
+
return this.renderLines(width);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
class QuietCallRenderComponent extends QuietLinesRenderComponent {
|
|
50
|
+
private frame: Box | undefined;
|
|
51
|
+
private framePaddingX: number | undefined;
|
|
52
|
+
|
|
53
|
+
private readonly frameBody = {
|
|
54
|
+
render: (width: number) => this.renderLines(width),
|
|
55
|
+
invalidate: () => {},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
setNativeFrame(theme: RenderTheme, context: QuietToolRenderContext): void {
|
|
59
|
+
const outputPad = getOutputPad(context);
|
|
60
|
+
if (!this.frame || this.framePaddingX !== outputPad) {
|
|
61
|
+
this.frame = new Box(outputPad, 1);
|
|
62
|
+
this.framePaddingX = outputPad;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const themeWithBackground = theme as RenderTheme & {
|
|
66
|
+
bg?: (kind: string, text: string) => string;
|
|
67
|
+
};
|
|
68
|
+
const backgroundKind = context.isPartial
|
|
69
|
+
? "toolPendingBg"
|
|
70
|
+
: context.isError
|
|
71
|
+
? "toolErrorBg"
|
|
72
|
+
: "toolSuccessBg";
|
|
73
|
+
const background = themeWithBackground.bg;
|
|
74
|
+
this.frame.setBgFn(
|
|
75
|
+
background ? (text) => background.call(themeWithBackground, backgroundKind, text) : undefined,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
clearNativeFrame(): void {
|
|
80
|
+
this.frame = undefined;
|
|
81
|
+
this.framePaddingX = undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
render(width: number): string[] {
|
|
85
|
+
if (!this.frame) return super.render(width);
|
|
86
|
+
|
|
87
|
+
this.frame.clear();
|
|
88
|
+
this.frame.addChild(this.frameBody);
|
|
89
|
+
return this.frame.render(width);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
invalidate(): void {
|
|
93
|
+
super.invalidate();
|
|
94
|
+
this.frame?.invalidate();
|
|
95
|
+
}
|
|
46
96
|
}
|
|
47
97
|
|
|
48
|
-
class QuietCallRenderComponent extends QuietLinesRenderComponent {}
|
|
49
98
|
class QuietResultRenderComponent extends QuietLinesRenderComponent {}
|
|
50
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Thrown from a delegating renderCall/renderResult slot when next() did not supply that slot.
|
|
102
|
+
* Pi's ToolExecutionComponent catches renderer errors and falls back to its native
|
|
103
|
+
* createCallFallback/createResultFallback, which preserve the host's expanded behavior.
|
|
104
|
+
*/
|
|
105
|
+
class QuietToolsDelegateToDefault extends Error {
|
|
106
|
+
constructor() {
|
|
107
|
+
super("quiet-tools: delegate to Pi default renderer");
|
|
108
|
+
this.name = "QuietToolsDelegateToDefault";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
51
112
|
function sanitizeInlineText(text: string): string {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
113
|
+
return text
|
|
114
|
+
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
115
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
116
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
117
|
+
.replace(/[\x00-\x08\x0B-\x1F\x7F]/g, "");
|
|
57
118
|
}
|
|
58
119
|
|
|
59
120
|
function str(value: unknown): string | null {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
121
|
+
if (typeof value === "string") return sanitizeInlineText(value);
|
|
122
|
+
if (value == null) return "";
|
|
123
|
+
return null;
|
|
63
124
|
}
|
|
64
125
|
|
|
65
126
|
function asRecord(args: unknown): Record<string, unknown> | undefined {
|
|
66
|
-
|
|
127
|
+
return args && typeof args === "object" ? (args as Record<string, unknown>) : undefined;
|
|
67
128
|
}
|
|
68
129
|
|
|
69
130
|
function firstStringArg(args: unknown, names: string[]): string | null {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
131
|
+
const record = asRecord(args);
|
|
132
|
+
if (!record) return "";
|
|
133
|
+
for (const name of names) {
|
|
134
|
+
if (Object.prototype.hasOwnProperty.call(record, name)) {
|
|
135
|
+
return str(record[name]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return "";
|
|
78
139
|
}
|
|
79
140
|
|
|
80
141
|
function numberArg(args: unknown, name: string): number | undefined {
|
|
81
|
-
|
|
82
|
-
|
|
142
|
+
const value = asRecord(args)?.[name];
|
|
143
|
+
return typeof value === "number" ? value : undefined;
|
|
83
144
|
}
|
|
84
145
|
|
|
85
146
|
function invalidArgText(theme: RenderTheme): string {
|
|
86
|
-
|
|
147
|
+
return theme.fg("error", "[invalid arg]");
|
|
87
148
|
}
|
|
88
149
|
|
|
89
150
|
function shortenPath(path: string): string {
|
|
90
|
-
|
|
91
|
-
|
|
151
|
+
const home = homedir();
|
|
152
|
+
return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
92
153
|
}
|
|
93
154
|
|
|
94
155
|
function formatToolTitle(toolName: string, theme: RenderTheme): string {
|
|
95
|
-
|
|
156
|
+
return theme.fg("toolTitle", theme.bold(toolName));
|
|
96
157
|
}
|
|
97
158
|
|
|
98
159
|
function formatPathArg(args: unknown, theme: RenderTheme, placeholder = "..."): string {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
160
|
+
const path = firstStringArg(args, ["file_path", "path"]);
|
|
161
|
+
if (path === null) return invalidArgText(theme);
|
|
162
|
+
return path ? theme.fg("accent", shortenPath(path)) : theme.fg("toolOutput", placeholder);
|
|
102
163
|
}
|
|
103
164
|
|
|
104
165
|
function formatReadLineRange(args: unknown, theme: RenderTheme): string {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
166
|
+
const offset = numberArg(args, "offset");
|
|
167
|
+
const limit = numberArg(args, "limit");
|
|
168
|
+
if (offset === undefined && limit === undefined) return "";
|
|
169
|
+
const startLine = offset ?? 1;
|
|
170
|
+
const endLine = limit !== undefined ? startLine + limit - 1 : "";
|
|
171
|
+
return theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
|
111
172
|
}
|
|
112
173
|
|
|
113
174
|
function formatQuietReadCall(args: unknown, theme: RenderTheme): string {
|
|
114
|
-
|
|
175
|
+
return `${formatToolTitle("read", theme)} ${formatPathArg(args, theme)}${formatReadLineRange(args, theme)}`;
|
|
115
176
|
}
|
|
116
177
|
|
|
117
178
|
function formatQuietBashCall(args: unknown, theme: RenderTheme): string {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
179
|
+
const command = str(asRecord(args)?.command);
|
|
180
|
+
const timeout = numberArg(args, "timeout");
|
|
181
|
+
const commandDisplay =
|
|
182
|
+
command === null
|
|
183
|
+
? invalidArgText(theme)
|
|
184
|
+
: command
|
|
185
|
+
? theme.fg("toolTitle", theme.bold(command))
|
|
186
|
+
: theme.fg("toolOutput", "...");
|
|
187
|
+
const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
|
|
188
|
+
return `${theme.fg("toolTitle", theme.bold("$"))} ${commandDisplay}${timeoutSuffix}`;
|
|
127
189
|
}
|
|
128
190
|
|
|
129
191
|
function formatSearchPath(rawPath: string | null, theme: RenderTheme): string {
|
|
130
|
-
|
|
192
|
+
return rawPath === null ? invalidArgText(theme) : shortenPath(rawPath || ".");
|
|
131
193
|
}
|
|
132
194
|
|
|
133
195
|
function formatQuietGrepCall(args: unknown, theme: RenderTheme): string {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
196
|
+
const pattern = str(asRecord(args)?.pattern);
|
|
197
|
+
const rawPath = str(asRecord(args)?.path);
|
|
198
|
+
const glob = str(asRecord(args)?.glob);
|
|
199
|
+
const limit = numberArg(args, "limit");
|
|
200
|
+
let text = `${formatToolTitle("grep", theme)} ${
|
|
201
|
+
pattern === null ? invalidArgText(theme) : theme.fg("accent", `/${pattern || ""}/`)
|
|
202
|
+
}${theme.fg("toolOutput", ` in ${formatSearchPath(rawPath, theme)}`)}`;
|
|
203
|
+
if (glob) text += theme.fg("toolOutput", ` (${glob})`);
|
|
204
|
+
if (glob === null) text += ` ${invalidArgText(theme)}`;
|
|
205
|
+
if (limit !== undefined) text += theme.fg("toolOutput", ` limit ${limit}`);
|
|
206
|
+
return text;
|
|
145
207
|
}
|
|
146
208
|
|
|
147
209
|
function formatQuietFindCall(args: unknown, theme: RenderTheme): string {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
210
|
+
const pattern = str(asRecord(args)?.pattern);
|
|
211
|
+
const rawPath = str(asRecord(args)?.path);
|
|
212
|
+
const limit = numberArg(args, "limit");
|
|
213
|
+
let text = `${formatToolTitle("find", theme)} ${
|
|
214
|
+
pattern === null ? invalidArgText(theme) : theme.fg("accent", pattern || "")
|
|
215
|
+
}${theme.fg("toolOutput", ` in ${formatSearchPath(rawPath, theme)}`)}`;
|
|
216
|
+
if (limit !== undefined) text += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
217
|
+
return text;
|
|
156
218
|
}
|
|
157
219
|
|
|
158
220
|
function formatQuietLsCall(args: unknown, theme: RenderTheme): string {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
221
|
+
const rawPath = str(asRecord(args)?.path);
|
|
222
|
+
const limit = numberArg(args, "limit");
|
|
223
|
+
let text = `${formatToolTitle("ls", theme)} ${
|
|
224
|
+
rawPath === null ? invalidArgText(theme) : theme.fg("accent", shortenPath(rawPath || "."))
|
|
225
|
+
}`;
|
|
226
|
+
if (limit !== undefined) text += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
227
|
+
return text;
|
|
166
228
|
}
|
|
167
229
|
|
|
168
|
-
function formatQuietPathOnlyCall(
|
|
169
|
-
|
|
230
|
+
function formatQuietPathOnlyCall(
|
|
231
|
+
toolName: "edit" | "write",
|
|
232
|
+
args: unknown,
|
|
233
|
+
theme: RenderTheme,
|
|
234
|
+
): string {
|
|
235
|
+
return `${formatToolTitle(toolName, theme)} ${formatPathArg(args, theme)}`;
|
|
170
236
|
}
|
|
171
237
|
|
|
172
238
|
function formatQuietCallLine(toolName: string, args: unknown, theme: RenderTheme): string {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
239
|
+
switch (toolName) {
|
|
240
|
+
case "bash":
|
|
241
|
+
return formatQuietBashCall(args, theme);
|
|
242
|
+
case "edit":
|
|
243
|
+
return formatQuietPathOnlyCall("edit", args, theme);
|
|
244
|
+
case "find":
|
|
245
|
+
return formatQuietFindCall(args, theme);
|
|
246
|
+
case "grep":
|
|
247
|
+
return formatQuietGrepCall(args, theme);
|
|
248
|
+
case "ls":
|
|
249
|
+
return formatQuietLsCall(args, theme);
|
|
250
|
+
case "read":
|
|
251
|
+
return formatQuietReadCall(args, theme);
|
|
252
|
+
case "write":
|
|
253
|
+
return formatQuietPathOnlyCall("write", args, theme);
|
|
254
|
+
default:
|
|
255
|
+
return formatToolTitle(toolName, theme);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function formatMcpCallLine(toolName: string, args: unknown, theme: RenderTheme): string {
|
|
260
|
+
let argsStr = "";
|
|
261
|
+
if (args != null) {
|
|
262
|
+
let raw: string | undefined;
|
|
263
|
+
try {
|
|
264
|
+
raw = JSON.stringify(args);
|
|
265
|
+
} catch {
|
|
266
|
+
// circular references, BigInt, etc.
|
|
267
|
+
}
|
|
268
|
+
if (raw === undefined) {
|
|
269
|
+
argsStr = "[\u2026]";
|
|
270
|
+
} else {
|
|
271
|
+
const capped = raw.length > MAX_MCP_ARG_LENGTH ? raw.slice(0, MAX_MCP_ARG_LENGTH) : raw;
|
|
272
|
+
argsStr = sanitizeInlineText(capped);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return `${formatToolTitle(toolName, theme)}${argsStr ? ` ${theme.fg("toolOutput", argsStr)}` : ""}`;
|
|
191
276
|
}
|
|
192
277
|
|
|
193
278
|
function formatExpandHint(theme: RenderTheme): string {
|
|
194
|
-
|
|
279
|
+
return `${theme.fg("muted", "(")}${keyHint("app.tools.expand", "to expand")}${theme.fg("muted", ")")}`;
|
|
195
280
|
}
|
|
196
281
|
|
|
197
|
-
function
|
|
198
|
-
|
|
282
|
+
function formatFailedMarker(theme: RenderTheme): string {
|
|
283
|
+
return theme.fg("error", " [failed]");
|
|
284
|
+
}
|
|
199
285
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
286
|
+
function truncateQuietCallLine(line: string, status: string, width: number): string {
|
|
287
|
+
if (!status) return truncateToWidth(line, width, "...");
|
|
288
|
+
const statusWidth = visibleWidth(status);
|
|
289
|
+
if (statusWidth >= width) return truncateToWidth(status, width, "");
|
|
290
|
+
return `${truncateToWidth(line, width - statusWidth, "...")}${status}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getOutputPad(context: QuietToolRenderContext): number {
|
|
294
|
+
const outputPad = context.outputPad;
|
|
295
|
+
if (typeof outputPad !== "number" || !Number.isFinite(outputPad)) return 1;
|
|
296
|
+
return Math.max(0, Math.floor(outputPad));
|
|
297
|
+
}
|
|
203
298
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
299
|
+
function finishBashTiming(
|
|
300
|
+
options: ToolRenderResultParams[1],
|
|
301
|
+
context: QuietToolRenderContext,
|
|
302
|
+
): void {
|
|
303
|
+
const state = context.state as BashRenderState;
|
|
304
|
+
|
|
305
|
+
if (!options.isPartial || context.isError) {
|
|
306
|
+
state.endedAt ??= Date.now();
|
|
307
|
+
if (state.interval) {
|
|
308
|
+
clearInterval(state.interval);
|
|
309
|
+
state.interval = undefined;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
208
312
|
}
|
|
209
313
|
|
|
210
314
|
function renderQuietCollapsedResult(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
315
|
+
toolName: string,
|
|
316
|
+
_result: ToolRenderResultParams[0],
|
|
317
|
+
options: ToolRenderResultParams[1],
|
|
318
|
+
_theme: RenderTheme,
|
|
319
|
+
context: QuietToolRenderContext,
|
|
215
320
|
): QuietResultRenderComponent {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
321
|
+
if (toolName === "bash") finishBashTiming(options, context);
|
|
322
|
+
const component =
|
|
323
|
+
context.lastComponent instanceof QuietResultRenderComponent
|
|
324
|
+
? context.lastComponent
|
|
325
|
+
: new QuietResultRenderComponent();
|
|
326
|
+
component.setLinesRenderer(() => []);
|
|
327
|
+
return component;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function delegateCall(
|
|
331
|
+
nextRenderers: ToolRenderers | undefined,
|
|
332
|
+
args: ToolRenderCallParams[0],
|
|
333
|
+
theme: RenderTheme,
|
|
334
|
+
context: ToolRenderContext,
|
|
335
|
+
): ReturnType<ToolRenderCall> {
|
|
336
|
+
if (!nextRenderers?.renderCall) throw new QuietToolsDelegateToDefault();
|
|
337
|
+
const delegateContext =
|
|
338
|
+
context.lastComponent instanceof QuietCallRenderComponent
|
|
339
|
+
? { ...context, lastComponent: undefined }
|
|
340
|
+
: context;
|
|
341
|
+
return nextRenderers.renderCall(args, theme, delegateContext);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function delegateResult(
|
|
345
|
+
nextRenderers: ToolRenderers | undefined,
|
|
346
|
+
result: ToolRenderResultParams[0],
|
|
347
|
+
options: ToolRenderResultParams[1],
|
|
348
|
+
theme: RenderTheme,
|
|
349
|
+
context: ToolRenderContext,
|
|
350
|
+
): ReturnType<ToolRenderResult> {
|
|
351
|
+
if (!nextRenderers?.renderResult) throw new QuietToolsDelegateToDefault();
|
|
352
|
+
const delegateContext =
|
|
353
|
+
context.lastComponent instanceof QuietResultRenderComponent
|
|
354
|
+
? { ...context, lastComponent: undefined }
|
|
355
|
+
: context;
|
|
356
|
+
return nextRenderers.renderResult(result, options, theme, delegateContext);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function createQuietResolver(): ToolRendererResolver {
|
|
360
|
+
return (toolName, next) => {
|
|
361
|
+
const isBuiltin = QUIET_TOOL_NAMES.has(toolName);
|
|
362
|
+
const isMcp = toolName.startsWith("mcp__");
|
|
363
|
+
|
|
364
|
+
if (!isBuiltin && !isMcp) return next();
|
|
365
|
+
|
|
366
|
+
const nextRenderers = next();
|
|
367
|
+
const usesNativeShell = nextRenderers?.renderShell === "self";
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
renderShell: nextRenderers?.renderShell,
|
|
371
|
+
renderCall(args, theme, context) {
|
|
372
|
+
if (context.expanded) return delegateCall(nextRenderers, args, theme, context);
|
|
373
|
+
|
|
374
|
+
const quietContext = context as QuietToolRenderContext;
|
|
375
|
+
if (toolName === "bash") {
|
|
376
|
+
const state = quietContext.state as BashRenderState;
|
|
377
|
+
if (quietContext.executionStarted && state.startedAt === undefined) {
|
|
378
|
+
state.startedAt = Date.now();
|
|
379
|
+
state.endedAt = undefined;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const component =
|
|
384
|
+
context.lastComponent instanceof QuietCallRenderComponent
|
|
385
|
+
? context.lastComponent
|
|
386
|
+
: new QuietCallRenderComponent();
|
|
387
|
+
if (usesNativeShell) component.setNativeFrame(theme, quietContext);
|
|
388
|
+
else component.clearNativeFrame();
|
|
389
|
+
|
|
390
|
+
const line = isBuiltin
|
|
391
|
+
? formatQuietCallLine(toolName, args, theme)
|
|
392
|
+
: formatMcpCallLine(toolName, args, theme);
|
|
393
|
+
const status = quietContext.isError ? formatFailedMarker(theme) : "";
|
|
394
|
+
const hint = formatExpandHint(theme);
|
|
395
|
+
component.setLinesRenderer((width) => [
|
|
396
|
+
truncateQuietCallLine(line, status, width),
|
|
397
|
+
truncateToWidth(hint, width, "..."),
|
|
398
|
+
]);
|
|
399
|
+
return component;
|
|
400
|
+
},
|
|
401
|
+
renderResult(result, options, theme, context) {
|
|
402
|
+
if (options.expanded) return delegateResult(nextRenderers, result, options, theme, context);
|
|
403
|
+
return renderQuietCollapsedResult(
|
|
404
|
+
toolName,
|
|
405
|
+
result,
|
|
406
|
+
options,
|
|
407
|
+
theme,
|
|
408
|
+
context as QuietToolRenderContext,
|
|
409
|
+
);
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
};
|
|
308
413
|
}
|
|
309
414
|
|
|
310
415
|
export const __testing = {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
416
|
+
sanitizeInlineText,
|
|
417
|
+
formatQuietCallLine,
|
|
418
|
+
formatMcpCallLine,
|
|
419
|
+
createQuietResolver,
|
|
420
|
+
QuietCallRenderComponent,
|
|
421
|
+
QuietResultRenderComponent,
|
|
422
|
+
QuietToolsDelegateToDefault,
|
|
314
423
|
};
|
|
315
424
|
|
|
425
|
+
const STATUS_MESSAGE =
|
|
426
|
+
"Quiet tool previews are always on. Collapsed built-in and MCP tool rows show a one-line invocation and hide output until expanded. Model-visible tool results are unchanged.";
|
|
427
|
+
const FALLBACK_STATUS_MESSAGE =
|
|
428
|
+
"Quiet tool previews are inactive: this Pi host lacks registerToolRenderer (requires Pi >=1.0.1).";
|
|
429
|
+
const STATUS_USAGE = "Usage: /quiet-tools status";
|
|
430
|
+
|
|
316
431
|
export default function quietToolsExtension(pi: ExtensionAPI) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
});
|
|
432
|
+
let notifiedFallback = false;
|
|
433
|
+
|
|
434
|
+
const hasRenderer = typeof (pi as any).registerToolRenderer === "function";
|
|
435
|
+
|
|
436
|
+
if (hasRenderer) pi.registerToolRenderer(createQuietResolver());
|
|
437
|
+
|
|
438
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
439
|
+
if (!hasRenderer && !notifiedFallback) {
|
|
440
|
+
notifiedFallback = true;
|
|
441
|
+
ctx.ui.notify(
|
|
442
|
+
"quiet-tools needs Pi >=1.0.1 to customise tool renderers. Quiet rendering is inactive.",
|
|
443
|
+
"warning",
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
pi.registerCommand("quiet-tools", {
|
|
449
|
+
description: "Show always-on one-line collapsed invocations for built-in and MCP tool rows",
|
|
450
|
+
getArgumentCompletions: (prefix) => {
|
|
451
|
+
const query = prefix.trim().toLowerCase();
|
|
452
|
+
return "status".startsWith(query) ? [{ value: "status", label: "status" }] : null;
|
|
453
|
+
},
|
|
454
|
+
handler: async (args, ctx) => {
|
|
455
|
+
if (!args.trim() || args.trim().toLowerCase() === "status") {
|
|
456
|
+
ctx.ui.notify(hasRenderer ? STATUS_MESSAGE : FALLBACK_STATUS_MESSAGE, "info");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
ctx.ui.notify(STATUS_USAGE, "warning");
|
|
460
|
+
},
|
|
461
|
+
});
|
|
377
462
|
}
|