@heyhuynhgiabuu/pi-pretty 0.6.15 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -122
- package/README.md +2 -0
- package/dist/collapsed-hint.d.ts +7 -0
- package/dist/collapsed-hint.d.ts.map +1 -0
- package/dist/collapsed-hint.js +17 -0
- package/dist/collapsed-hint.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/expand.d.ts +13 -0
- package/dist/expand.d.ts.map +1 -0
- package/dist/expand.js +24 -0
- package/dist/expand.js.map +1 -0
- package/dist/find-glob.d.ts +9 -0
- package/dist/find-glob.d.ts.map +1 -0
- package/dist/find-glob.js +29 -0
- package/dist/find-glob.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +15 -10
- package/dist/render.js.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +13 -12
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/find.d.ts +2 -2
- package/dist/tools/find.d.ts.map +1 -1
- package/dist/tools/find.js +90 -45
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +8 -4
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +8 -4
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/multi-grep.d.ts.map +1 -1
- package/dist/tools/multi-grep.js +12 -8
- package/dist/tools/multi-grep.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +15 -11
- package/dist/tools/read.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/collapsed-hint.ts +14 -0
- package/src/config.ts +3 -0
- package/src/expand.ts +24 -0
- package/src/find-glob.ts +21 -0
- package/src/index.ts +2 -0
- package/src/render.ts +17 -10
- package/src/tools/bash.ts +15 -12
- package/src/tools/find.ts +132 -73
- package/src/tools/grep.ts +14 -5
- package/src/tools/ls.ts +13 -5
- package/src/tools/multi-grep.ts +17 -9
- package/src/tools/read.ts +20 -11
- package/src/types.ts +1 -0
package/src/tools/find.ts
CHANGED
|
@@ -1,35 +1,85 @@
|
|
|
1
|
-
/* pi-pretty: find tool -- FFF-backed file search with SDK fallback. */
|
|
1
|
+
/* pi-pretty: find tool -- FFF-backed file search with SDK (fd) fallback. */
|
|
2
2
|
|
|
3
|
+
import { isAbsolute, relative } from "node:path";
|
|
3
4
|
import {
|
|
4
5
|
type ToolDefinition,
|
|
5
6
|
type ExtensionAPI,
|
|
6
7
|
type ExtensionContext,
|
|
7
8
|
type AgentToolResult,
|
|
8
9
|
} from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
SdkToolDef,
|
|
12
|
-
FindDetails,
|
|
13
|
-
FffServiceLike,
|
|
14
|
-
FileItem,
|
|
15
|
-
TextContent,
|
|
16
|
-
ThemeLike,
|
|
17
|
-
RenderCtxLike,
|
|
18
|
-
ComponentLike,
|
|
19
|
-
} from "../types.js";
|
|
20
|
-
import { resolveBaseBackground, BG_ERROR, FG_DIM, RST } from "../config.js";
|
|
10
|
+
import type { SdkToolDef, FindDetails, FffServiceWithCursor, TextContent, ThemeLike, RenderCtxLike } from "../types.js";
|
|
11
|
+
import { TOOL_RESULT_INDENT, BG_ERROR, FG_DIM, RST, resolveBaseBackground } from "../config.js";
|
|
21
12
|
import { shortPath } from "../helpers.js";
|
|
22
13
|
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
23
14
|
import { renderFindResults, renderToolError, renderToolMetrics, fillToolBackground } from "../render.js";
|
|
24
15
|
import { resolveTextCtor } from "../tui-text.js";
|
|
25
16
|
import { NOTICE_PARTIAL_FILE_INDEX } from "../notices.js";
|
|
17
|
+
import { isLikelyGlobPattern, normalizeFindGlobPattern } from "../find-glob.js";
|
|
26
18
|
|
|
27
19
|
type Result = AgentToolResult<Record<string, unknown>>;
|
|
28
20
|
|
|
21
|
+
function getText(result: Result): string {
|
|
22
|
+
return (result.content ?? [])
|
|
23
|
+
.filter((c) => c.type === "text")
|
|
24
|
+
.map((c) => (c as TextContent).text ?? "")
|
|
25
|
+
.join("\n");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildGlobPattern(pattern: string, path: string | undefined, basePath: string | null): string {
|
|
29
|
+
const raw = pattern.startsWith("/") ? pattern.slice(1) : pattern;
|
|
30
|
+
const normalized = normalizeFindGlobPattern(raw);
|
|
31
|
+
let cleanPath = path ?? "";
|
|
32
|
+
if (cleanPath && isAbsolute(cleanPath) && basePath) {
|
|
33
|
+
cleanPath = relative(basePath, cleanPath) || "";
|
|
34
|
+
}
|
|
35
|
+
cleanPath = cleanPath.replace(/\/$/, "");
|
|
36
|
+
if (cleanPath) {
|
|
37
|
+
if (normalized.startsWith("**/")) {
|
|
38
|
+
return `${cleanPath}/${normalized}`;
|
|
39
|
+
}
|
|
40
|
+
if (normalized.includes("/")) {
|
|
41
|
+
return `${cleanPath}/${normalized}`;
|
|
42
|
+
}
|
|
43
|
+
return `${cleanPath}/**/${normalized}`;
|
|
44
|
+
}
|
|
45
|
+
return normalized.startsWith("**/") || normalized.includes("/") ? normalized : `**/${normalized}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function appendFindNotices(result: Result, extra: string[]): Result {
|
|
49
|
+
if (extra.length === 0) return result;
|
|
50
|
+
const d = result.details;
|
|
51
|
+
if (!d || d._type !== "findResult") return result;
|
|
52
|
+
const prev = Array.isArray(d.notices) ? (d.notices as string[]) : [];
|
|
53
|
+
return { ...result, details: { ...d, notices: [...prev, ...extra] } };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function sdkFindAsFindResult(
|
|
57
|
+
sdkTool: SdkToolDef,
|
|
58
|
+
tid: string,
|
|
59
|
+
params: Record<string, unknown>,
|
|
60
|
+
sig: AbortSignal | undefined,
|
|
61
|
+
ctx: ExtensionContext,
|
|
62
|
+
pattern: string,
|
|
63
|
+
extraNotices: string[],
|
|
64
|
+
): Promise<Result> {
|
|
65
|
+
const result = (await sdkTool.execute(tid, params, sig, undefined, ctx)) as Result;
|
|
66
|
+
const tc = getText(result);
|
|
67
|
+
const prev = (result.details as FindDetails | undefined)?.notices ?? [];
|
|
68
|
+
const notices = [...(Array.isArray(prev) ? prev : []), ...extraNotices];
|
|
69
|
+
result.details = {
|
|
70
|
+
_type: "findResult",
|
|
71
|
+
text: tc,
|
|
72
|
+
pattern,
|
|
73
|
+
matchCount: tc ? tc.trim().split("\n").filter(Boolean).length : 0,
|
|
74
|
+
notices,
|
|
75
|
+
};
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
29
79
|
export function registerFindTool(
|
|
30
80
|
pi: ExtensionAPI,
|
|
31
81
|
cwd: string,
|
|
32
|
-
fffService:
|
|
82
|
+
fffService: FffServiceWithCursor | null | undefined,
|
|
33
83
|
sdkTool: SdkToolDef,
|
|
34
84
|
TextComp?: new (t?: string, x?: number, y?: number) => { setText(v: string): void },
|
|
35
85
|
): void {
|
|
@@ -44,32 +94,38 @@ export function registerFindTool(
|
|
|
44
94
|
renderShell: "self",
|
|
45
95
|
|
|
46
96
|
execute: wrapExecuteWithMetrics(async (tid, params, sig, _upd, ctx: ExtensionContext) => {
|
|
47
|
-
const pattern = String(
|
|
48
|
-
const path =
|
|
49
|
-
const limit =
|
|
50
|
-
const glob = (params as any).glob;
|
|
51
|
-
|
|
97
|
+
const pattern = String(params.pattern ?? "");
|
|
98
|
+
const path = params.path ? String(params.path) : undefined;
|
|
99
|
+
const limit = params.limit;
|
|
52
100
|
const fff = fffService?.isAvailable ? fffService.getFinder() : null;
|
|
101
|
+
|
|
53
102
|
if (fff) {
|
|
54
103
|
try {
|
|
55
104
|
const effectiveLimit = Math.max(1, typeof limit === "number" ? limit : 100);
|
|
56
105
|
const basePathResult = fff.getBasePath();
|
|
57
106
|
const basePath = basePathResult.ok ? basePathResult.value : null;
|
|
58
|
-
|
|
59
|
-
if (cleanPath && isAbsolute(cleanPath) && basePath) {
|
|
60
|
-
cleanPath = toRelative(basePath, cleanPath) || "";
|
|
61
|
-
}
|
|
62
|
-
cleanPath = cleanPath.replace(/\/$/, "");
|
|
63
|
-
const cleanPattern = pattern.startsWith("/") ? pattern.slice(1) : pattern;
|
|
64
|
-
const globPattern = cleanPath ? `${cleanPath}/**/${cleanPattern}` : `**/${cleanPattern}`;
|
|
107
|
+
const globPattern = buildGlobPattern(pattern, path, basePath);
|
|
65
108
|
const searchResult = fff.glob(globPattern, { pageSize: effectiveLimit });
|
|
109
|
+
|
|
66
110
|
if (searchResult.ok) {
|
|
67
|
-
const items
|
|
111
|
+
const items = searchResult.value.items.slice(0, effectiveLimit);
|
|
68
112
|
const notices: string[] = [];
|
|
69
113
|
if (fffService?.partialIndex) notices.push(NOTICE_PARTIAL_FILE_INDEX);
|
|
70
114
|
if (items.length >= effectiveLimit) notices.push(`${effectiveLimit} limit reached`);
|
|
71
|
-
if (searchResult.value.totalMatched > items.length)
|
|
115
|
+
if (searchResult.value.totalMatched > items.length) {
|
|
72
116
|
notices.push(`${searchResult.value.totalMatched} total matches`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (items.length === 0 && isLikelyGlobPattern(pattern)) {
|
|
120
|
+
return sdkFindAsFindResult(sdkTool, tid, params, sig, ctx, pattern, [
|
|
121
|
+
"FFF glob returned no matches; results from SDK find (fd).",
|
|
122
|
+
...notices,
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (items.length > 0) notices.push("Search engine: FFF glob.");
|
|
127
|
+
else if (notices.length === 0) notices.push("Search engine: FFF glob (no matches).");
|
|
128
|
+
|
|
73
129
|
const paths = items.map((i) => i.relativePath).join("\n");
|
|
74
130
|
return {
|
|
75
131
|
content: [{ type: "text" as const, text: paths }],
|
|
@@ -79,7 +135,7 @@ export function registerFindTool(
|
|
|
79
135
|
pattern,
|
|
80
136
|
matchCount: items.length,
|
|
81
137
|
notices,
|
|
82
|
-
}
|
|
138
|
+
},
|
|
83
139
|
};
|
|
84
140
|
}
|
|
85
141
|
} catch {
|
|
@@ -87,82 +143,85 @@ export function registerFindTool(
|
|
|
87
143
|
}
|
|
88
144
|
}
|
|
89
145
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
_type: "findResult",
|
|
94
|
-
text: tc,
|
|
95
|
-
pattern,
|
|
96
|
-
matchCount: tc ? tc.trim().split("\n").filter(Boolean).length : 0,
|
|
97
|
-
} as FindDetails;
|
|
98
|
-
return result;
|
|
146
|
+
return sdkFindAsFindResult(sdkTool, tid, params, sig, ctx, pattern, [
|
|
147
|
+
fff ? "FFF find unavailable; results from SDK find (fd)." : "Search engine: SDK find (fd).",
|
|
148
|
+
]);
|
|
99
149
|
}),
|
|
100
150
|
|
|
101
151
|
renderCall(args: any, theme: ThemeLike, ctx: RenderCtxLike) {
|
|
102
152
|
resolveBaseBackground(theme);
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
153
|
+
const a = args as { pattern?: unknown; path?: unknown; limit?: unknown };
|
|
154
|
+
const text = (ctx as RenderCtxLike).lastComponent ?? new TC("", 0, 0);
|
|
155
|
+
const pattern = a.pattern == null ? "" : String(a.pattern);
|
|
156
|
+
const pathArg =
|
|
157
|
+
a.path == null ? "<missing>" : shortPath(cwd, home, String(a.path));
|
|
158
|
+
const limit = a.limit;
|
|
109
159
|
const findLabel = theme.fg("toolTitle", theme.bold("find"));
|
|
110
160
|
const patternPart = pattern ? theme.fg("accent", pattern) : "";
|
|
111
161
|
const inPart = theme.fg("dim", " in ");
|
|
112
|
-
const pathPart = theme.fg("toolOutput",
|
|
113
|
-
const limitPart =
|
|
162
|
+
const pathPart = theme.fg("toolOutput", pathArg);
|
|
163
|
+
const limitPart =
|
|
164
|
+
limit !== undefined && limit !== null ? theme.fg("dim", ` limit ${limit}`) : "";
|
|
114
165
|
const out = `${findLabel} ${patternPart}${inPart}${pathPart}${limitPart}`;
|
|
115
|
-
text.setText(
|
|
166
|
+
text.setText(
|
|
167
|
+
fillToolBackground(
|
|
168
|
+
`\n${TOOL_RESULT_INDENT}${out}`,
|
|
169
|
+
ctx.isError ? BG_ERROR : undefined,
|
|
170
|
+
),
|
|
171
|
+
);
|
|
116
172
|
return text;
|
|
117
173
|
},
|
|
118
174
|
|
|
119
175
|
renderResult(result: Result, _opt: unknown, theme: ThemeLike, ctx: RenderCtxLike) {
|
|
120
176
|
resolveBaseBackground(theme);
|
|
121
|
-
const
|
|
177
|
+
const r = result;
|
|
178
|
+
const text = (ctx as RenderCtxLike).lastComponent ?? new TC("", 0, 0);
|
|
122
179
|
if (ctx.isError) {
|
|
123
|
-
text.setText(renderToolError(getText(
|
|
180
|
+
text.setText(renderToolError(getText(r) || "Error", theme));
|
|
124
181
|
return text;
|
|
125
182
|
}
|
|
126
|
-
const d =
|
|
183
|
+
const d = r.details as FindDetails | undefined;
|
|
127
184
|
if (d?._type === "findResult") {
|
|
128
185
|
if (!d.text.trim()) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
186
|
+
const noticeStr = d.notices?.length
|
|
187
|
+
? `\n${TOOL_RESULT_INDENT}${theme.fg("warning", `[${d.notices.join(". ")}]`)}`
|
|
188
|
+
: "";
|
|
189
|
+
text.setText(
|
|
190
|
+
fillToolBackground(
|
|
191
|
+
`\n${TOOL_RESULT_INDENT}${theme.fg("dim", "0 files")}${noticeStr}\n`,
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
return text;
|
|
195
|
+
}
|
|
196
|
+
if (!ctx.expanded) {
|
|
197
|
+
text.setText(
|
|
198
|
+
fillToolBackground(
|
|
199
|
+
`${TOOL_RESULT_INDENT}${FG_DIM}${d.matchCount} files — ctrl+o to expand${RST}${renderToolMetrics(r)}\n`,
|
|
200
|
+
),
|
|
201
|
+
);
|
|
132
202
|
return text;
|
|
133
203
|
}
|
|
134
204
|
const rendered = renderFindResults(d.text, theme)
|
|
135
205
|
.split("\n")
|
|
136
|
-
.map((l) =>
|
|
206
|
+
.map((l) => `${TOOL_RESULT_INDENT}${l}`)
|
|
137
207
|
.join("\n");
|
|
138
|
-
const
|
|
139
|
-
|
|
208
|
+
const noticeStr = d.notices?.length
|
|
209
|
+
? `\n${TOOL_RESULT_INDENT}${theme.fg("warning", `[${d.notices.join(". ")}]`)}`
|
|
210
|
+
: "";
|
|
140
211
|
text.setText(
|
|
141
212
|
fillToolBackground(
|
|
142
|
-
|
|
213
|
+
`\n${TOOL_RESULT_INDENT}${theme.fg("dim", `${d.matchCount} files`)}${renderToolMetrics(r)}\n${rendered}${noticeStr}\n`,
|
|
143
214
|
),
|
|
144
215
|
);
|
|
145
216
|
return text;
|
|
146
217
|
}
|
|
147
|
-
const fc =
|
|
218
|
+
const fc = r.content?.[0] as TextContent | undefined;
|
|
148
219
|
text.setText(
|
|
149
220
|
fillToolBackground(
|
|
150
|
-
|
|
221
|
+
`\n${TOOL_RESULT_INDENT}${theme.fg("dim", fc?.text?.slice(0, 120) ?? "0 files")}\n`,
|
|
151
222
|
),
|
|
152
223
|
);
|
|
153
224
|
return text;
|
|
154
225
|
},
|
|
155
|
-
} as unknown as ToolDefinition
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function appendNotices(text: string, notices: string[]): string {
|
|
159
|
-
return notices.length ? `${text}\n\n[${notices.join(". ")}]` : text;
|
|
160
|
-
}
|
|
161
|
-
function getText(result: Result): string {
|
|
162
|
-
return (
|
|
163
|
-
((result.content ?? []) as TextContent[])
|
|
164
|
-
.filter((c) => c.type === "text")
|
|
165
|
-
.map((c) => c.text ?? "")
|
|
166
|
-
.join("\n") ?? ""
|
|
167
|
-
);
|
|
168
|
-
}
|
|
226
|
+
} as unknown as ToolDefinition);
|
|
227
|
+
}
|
package/src/tools/grep.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type AgentToolResult,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import type { SdkToolDef, GrepDetails, FffServiceWithCursor, TextContent, ThemeLike, RenderCtxLike } from "../types.js";
|
|
10
|
-
import { MAX_PREVIEW_LINES, BG_ERROR, resolveBaseBackground } from "../config.js";
|
|
10
|
+
import { TOOL_RESULT_INDENT, MAX_PREVIEW_LINES, BG_ERROR, FG_DIM, RST, resolveBaseBackground } from "../config.js";
|
|
11
11
|
import { shortPath, normalizeLineEndings } from "../helpers.js";
|
|
12
12
|
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
13
13
|
import { renderToolError, fillToolBackground } from "../render.js";
|
|
@@ -116,7 +116,7 @@ export function registerGrepTool(
|
|
|
116
116
|
if (limit !== undefined && limit !== null) out += theme.fg("dim", ` limit ${limit}`);
|
|
117
117
|
if (literal) out += theme.fg("dim", ` (literal)`);
|
|
118
118
|
if (caseInsensitive) out += theme.fg("dim", ` (case-insensitive)`);
|
|
119
|
-
text.setText(fillToolBackground(`\n
|
|
119
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}`, ctx.isError ? BG_ERROR : undefined));
|
|
120
120
|
return text;
|
|
121
121
|
},
|
|
122
122
|
|
|
@@ -138,7 +138,16 @@ export function registerGrepTool(
|
|
|
138
138
|
const d = result.details as GrepDetails | undefined;
|
|
139
139
|
if (d?._type === "grepResult" && d.text) {
|
|
140
140
|
const lines = d.text.split("\n");
|
|
141
|
-
|
|
141
|
+
if (!ctx.expanded) {
|
|
142
|
+
text.setText(
|
|
143
|
+
fillToolBackground(
|
|
144
|
+
`${TOOL_RESULT_INDENT}${FG_DIM}${lines.length} lines — ctrl+o to expand${RST}\n`,
|
|
145
|
+
ctx.isError ? BG_ERROR : undefined,
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
return text;
|
|
149
|
+
}
|
|
150
|
+
const maxShow = lines.length;
|
|
142
151
|
const show = lines.slice(0, maxShow);
|
|
143
152
|
const remaining = lines.length - maxShow;
|
|
144
153
|
const out: string[] = [];
|
|
@@ -149,13 +158,13 @@ export function registerGrepTool(
|
|
|
149
158
|
if (remaining > 0) {
|
|
150
159
|
out.push(theme.fg("muted", `… (${remaining} more ${remaining === 1 ? "line" : "lines"}, to expand)`));
|
|
151
160
|
}
|
|
152
|
-
const body = out.map((l) =>
|
|
161
|
+
const body = out.map((l) => `${TOOL_RESULT_INDENT}${l}`).join("\n") + "\n\n";
|
|
153
162
|
text.setText(fillToolBackground(body, ctx.isError ? BG_ERROR : undefined));
|
|
154
163
|
return text;
|
|
155
164
|
}
|
|
156
165
|
const fc = result.content?.[0];
|
|
157
166
|
const fallback = fc && "text" in fc ? String(fc.text).slice(0, 120) : "no matches";
|
|
158
|
-
text.setText(fillToolBackground(
|
|
167
|
+
text.setText(fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", fallback)}`, ctx.isError ? BG_ERROR : undefined));
|
|
159
168
|
return text;
|
|
160
169
|
},
|
|
161
170
|
} as unknown as ToolDefinition<any, any, any>);
|
package/src/tools/ls.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type AgentToolResult,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import type { SdkToolDef, LsDetails, TextContent, ComponentLike, ThemeLike, RenderCtxLike } from "../types.js";
|
|
10
|
-
import { resolveBaseBackground, termWidth, MAX_PREVIEW_LINES, BG_BASE, BG_ERROR, FG_DIM, RST } from "../config.js";
|
|
10
|
+
import { TOOL_RESULT_INDENT, resolveBaseBackground, termWidth, MAX_PREVIEW_LINES, BG_BASE, BG_ERROR, FG_DIM, RST } from "../config.js";
|
|
11
11
|
import { shortPath } from "../helpers.js";
|
|
12
12
|
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
13
13
|
import { renderTree, renderToolError, renderToolMetrics, fillToolBackground } from "../render.js";
|
|
@@ -56,7 +56,7 @@ export function registerLsTool(
|
|
|
56
56
|
let out = theme.fg("toolTitle", theme.bold("ls"));
|
|
57
57
|
if (path) out += ` ${theme.fg("accent", path)}`;
|
|
58
58
|
if (limit !== undefined && limit !== null) out += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
59
|
-
text.setText(fillToolBackground(
|
|
59
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}`, ctx.isError ? BG_ERROR : undefined));
|
|
60
60
|
return text;
|
|
61
61
|
},
|
|
62
62
|
|
|
@@ -70,20 +70,28 @@ export function registerLsTool(
|
|
|
70
70
|
}
|
|
71
71
|
const d = result.details as LsDetails | undefined;
|
|
72
72
|
if (d?._type === "lsResult" && d.text) {
|
|
73
|
+
if (!ctx.expanded) {
|
|
74
|
+
text.setText(
|
|
75
|
+
fillToolBackground(
|
|
76
|
+
`${TOOL_RESULT_INDENT}${FG_DIM}${d.entryCount} entries — ctrl+o to expand${RST}${renderToolMetrics(result)}\n`,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
73
81
|
const rendered = renderTree(d.text, d.path)
|
|
74
82
|
.split("\n")
|
|
75
|
-
.map((l) =>
|
|
83
|
+
.map((l) => `${TOOL_RESULT_INDENT}${l}`)
|
|
76
84
|
.join("\n");
|
|
77
85
|
text.setText(
|
|
78
86
|
fillToolBackground(
|
|
79
|
-
|
|
87
|
+
`\n${TOOL_RESULT_INDENT}${FG_DIM}${d.entryCount} entries${RST}${renderToolMetrics(result)}\n${rendered}\n`,
|
|
80
88
|
),
|
|
81
89
|
);
|
|
82
90
|
return text;
|
|
83
91
|
}
|
|
84
92
|
const fc = result.content?.[0];
|
|
85
93
|
text.setText(
|
|
86
|
-
fillToolBackground(
|
|
94
|
+
fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`),
|
|
87
95
|
);
|
|
88
96
|
return text;
|
|
89
97
|
},
|
package/src/tools/multi-grep.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
ThemeLike,
|
|
17
17
|
RenderCtxLike,
|
|
18
18
|
} from "../types.js";
|
|
19
|
-
import { resolveBaseBackground, MAX_PREVIEW_LINES, BG_ERROR, FG_DIM, FG_LNUM, FG_RULE, RST } from "../config.js";
|
|
19
|
+
import { TOOL_RESULT_INDENT, resolveBaseBackground, MAX_PREVIEW_LINES, BG_ERROR, FG_DIM, FG_LNUM, FG_RULE, RST } from "../config.js";
|
|
20
20
|
import { shortPath, normalizeLineEndings } from "../helpers.js";
|
|
21
21
|
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
22
22
|
import { renderToolError, renderToolMetrics, fillToolBackground } from "../render.js";
|
|
@@ -256,7 +256,7 @@ export function registerMultiGrepTool(
|
|
|
256
256
|
let out = `${theme.fg("toolTitle", theme.bold("mgrep"))} ${theme.fg("accent", `/${patternStr || ""}/`)}${theme.fg("toolOutput", ` in ${path}`)}`;
|
|
257
257
|
if (literal) out += theme.fg("dim", ` (literal)`);
|
|
258
258
|
if (limit !== undefined) out += theme.fg("dim", ` limit ${limit}`);
|
|
259
|
-
text.setText(fillToolBackground(`\n
|
|
259
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}`, ctx.isError ? BG_ERROR : undefined));
|
|
260
260
|
return text;
|
|
261
261
|
},
|
|
262
262
|
|
|
@@ -271,7 +271,15 @@ export function registerMultiGrepTool(
|
|
|
271
271
|
const d = result.details as GrepDetails | undefined;
|
|
272
272
|
if (d?._type === "grepResult" && d.text) {
|
|
273
273
|
const lines = d.text.split("\n");
|
|
274
|
-
|
|
274
|
+
if (!ctx.expanded) {
|
|
275
|
+
text.setText(
|
|
276
|
+
fillToolBackground(
|
|
277
|
+
`${TOOL_RESULT_INDENT}${FG_DIM}${lines.length} lines — ctrl+o to expand${RST}${renderToolMetrics(result)}\n`,
|
|
278
|
+
),
|
|
279
|
+
);
|
|
280
|
+
return text;
|
|
281
|
+
}
|
|
282
|
+
const maxShow = lines.length;
|
|
275
283
|
const show = lines.slice(0, maxShow);
|
|
276
284
|
const nw = Math.max(3, 5);
|
|
277
285
|
|
|
@@ -288,27 +296,27 @@ export function registerMultiGrepTool(
|
|
|
288
296
|
const [, file, lineNo, content] = fileMatch;
|
|
289
297
|
if (file !== currentFile) {
|
|
290
298
|
if (currentFile) out.push("");
|
|
291
|
-
out.push(
|
|
299
|
+
out.push(`${TOOL_RESULT_INDENT}${theme.fg("accent", theme.bold(file))}`);
|
|
292
300
|
currentFile = file;
|
|
293
301
|
}
|
|
294
302
|
let display = content;
|
|
295
303
|
if (hlRe) display = content.replace(hlRe, (m) => `${RST}${theme.fg("warning", theme.bold(m))}${RST}`);
|
|
296
304
|
const padded = `${FG_LNUM}${String(lineNo).padStart(nw)}${RST} ${FG_RULE}│${RST} ${display}${RST}`;
|
|
297
|
-
out.push(
|
|
305
|
+
out.push(`${TOOL_RESULT_INDENT}${padded}`);
|
|
298
306
|
} else if (line.trim()) {
|
|
299
|
-
out.push(
|
|
307
|
+
out.push(`${TOOL_RESULT_INDENT}${FG_DIM}${line.trim()}${RST}`);
|
|
300
308
|
}
|
|
301
309
|
}
|
|
302
310
|
const preview = out.join("\n");
|
|
303
|
-
const more = lines.length > maxShow ? `\n${FG_DIM}
|
|
311
|
+
const more = lines.length > maxShow ? `\n${TOOL_RESULT_INDENT}${FG_DIM}... ${lines.length - maxShow} more lines${RST}` : "";
|
|
304
312
|
text.setText(
|
|
305
|
-
fillToolBackground(
|
|
313
|
+
fillToolBackground(`${TOOL_RESULT_INDENT}${FG_DIM}${d.matchCount} matches${RST}${renderToolMetrics(result)}\n${preview}${more}`),
|
|
306
314
|
);
|
|
307
315
|
return text;
|
|
308
316
|
}
|
|
309
317
|
const fc = result.content?.[0];
|
|
310
318
|
text.setText(
|
|
311
|
-
fillToolBackground(
|
|
319
|
+
fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "no matches")}`),
|
|
312
320
|
);
|
|
313
321
|
return text;
|
|
314
322
|
},
|
package/src/tools/read.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import type { SdkToolDef, ReadDetails, TextContent, ComponentLike, ThemeLike, RenderCtxLike } from "../types.js";
|
|
10
10
|
import {
|
|
11
|
+
TOOL_RESULT_INDENT,
|
|
11
12
|
resolveBaseBackground,
|
|
12
13
|
termWidth,
|
|
13
14
|
MAX_PREVIEW_LINES,
|
|
@@ -124,7 +125,7 @@ export function registerReadTool(
|
|
|
124
125
|
const fc = result.content?.[0];
|
|
125
126
|
text.setText(
|
|
126
127
|
fillToolBackground(
|
|
127
|
-
|
|
128
|
+
`${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 80) : `[image: ${d.filePath}]`)}`,
|
|
128
129
|
),
|
|
129
130
|
);
|
|
130
131
|
return text;
|
|
@@ -135,28 +136,36 @@ export function registerReadTool(
|
|
|
135
136
|
const tw = termWidth();
|
|
136
137
|
const lines = d.content.split("\n");
|
|
137
138
|
const total = lines.length;
|
|
138
|
-
const
|
|
139
|
+
const p2 = shortPath(cwd, home, String(d.filePath ?? ""));
|
|
140
|
+
const off2 = typeof d.offset === "number" ? `:${d.offset}` : "";
|
|
141
|
+
if (!ctx.expanded) {
|
|
142
|
+
text.setText(
|
|
143
|
+
fillToolBackground(
|
|
144
|
+
`\n${TOOL_RESULT_INDENT}${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", p2)}${theme.fg("dim", off2)}\n${TOOL_RESULT_INDENT}${FG_DIM}${total} lines — ctrl+o to expand${RST}\n`,
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
return text;
|
|
148
|
+
}
|
|
149
|
+
const maxShow = lines.length;
|
|
139
150
|
const show = lines.slice(0, maxShow);
|
|
140
151
|
const nw = Math.max(3, String(total).length);
|
|
141
152
|
const gw = nw + 3;
|
|
142
153
|
const cw = Math.max(1, tw - gw);
|
|
143
154
|
|
|
144
|
-
const p2 = shortPath(cwd, home, String(d.filePath ?? ""));
|
|
145
|
-
const off2 = typeof d.offset === "number" ? `:${d.offset}` : "";
|
|
146
155
|
const header = `${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", p2)}${theme.fg("dim", off2)}`;
|
|
147
|
-
const out: string[] = ["",
|
|
148
|
-
out.push(
|
|
156
|
+
const out: string[] = ["", `${TOOL_RESULT_INDENT}${header}`];
|
|
157
|
+
out.push(`${TOOL_RESULT_INDENT}${FG_RULE}${"─".repeat(tw - 1)}${RST}`);
|
|
149
158
|
for (let i = 0; i < show.length; i++) {
|
|
150
159
|
const ln = (d.offset || 0) + i + 1;
|
|
151
160
|
const code = show[i] ?? "";
|
|
152
161
|
const display = code.length > cw ? code.slice(0, cw) + `${FG_DIM}›${RST}` : code;
|
|
153
162
|
const lineNo = String(ln);
|
|
154
163
|
out.push(
|
|
155
|
-
|
|
164
|
+
`${TOOL_RESULT_INDENT}${FG_LNUM}${" ".repeat(Math.max(0, nw - lineNo.length))}${lineNo}${RST} ${FG_RULE}│${RST} ${display}${RST}`,
|
|
156
165
|
);
|
|
157
166
|
}
|
|
158
167
|
if (total > maxShow) {
|
|
159
|
-
out.push(
|
|
168
|
+
out.push(`${TOOL_RESULT_INDENT}${FG_DIM}… ${total - maxShow} more lines (${total} total)${RST}`);
|
|
160
169
|
}
|
|
161
170
|
out.push("");
|
|
162
171
|
const rendered = out.join("\n");
|
|
@@ -168,9 +177,9 @@ export function registerReadTool(
|
|
|
168
177
|
.then((hl) => {
|
|
169
178
|
const padded = hl
|
|
170
179
|
.split("\n")
|
|
171
|
-
.map((l) =>
|
|
180
|
+
.map((l) => `${TOOL_RESULT_INDENT}${l}`)
|
|
172
181
|
.join("\n");
|
|
173
|
-
const rendered = `\n
|
|
182
|
+
const rendered = `\n${TOOL_RESULT_INDENT}${header}\n${padded}\n`;
|
|
174
183
|
text.setText(fillToolBackground(rendered));
|
|
175
184
|
(ctx as any).state._rt = rendered;
|
|
176
185
|
})
|
|
@@ -181,7 +190,7 @@ export function registerReadTool(
|
|
|
181
190
|
|
|
182
191
|
const fc = result.content?.[0];
|
|
183
192
|
text.setText(
|
|
184
|
-
fillToolBackground(
|
|
193
|
+
fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`),
|
|
185
194
|
);
|
|
186
195
|
return text;
|
|
187
196
|
},
|