@heyhuynhgiabuu/pi-pretty 0.6.16 → 0.6.18
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 +26 -0
- package/README.md +6 -16
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -18
- package/dist/config.js.map +1 -1
- package/dist/expand.d.ts +0 -6
- package/dist/expand.d.ts.map +1 -1
- package/dist/expand.js +0 -13
- package/dist/expand.js.map +1 -1
- package/dist/image.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -21
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +5 -7
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +16 -16
- package/dist/render.js.map +1 -1
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +18 -23
- 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 +12 -8
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/grep.d.ts +2 -2
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +5 -5
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/labels.d.ts +2 -0
- package/dist/tools/labels.d.ts.map +1 -0
- package/dist/tools/labels.js +7 -0
- package/dist/tools/labels.js.map +1 -0
- package/dist/tools/ls.d.ts +1 -1
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +1 -1
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +22 -40
- package/dist/tools/read.js.map +1 -1
- package/dist/tui-text.d.ts +2 -20
- package/dist/tui-text.d.ts.map +1 -1
- package/dist/tui-text.js +3 -46
- package/dist/tui-text.js.map +1 -1
- package/dist/types.d.ts +3 -27
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/collapsed-hint.ts +1 -1
- package/src/config.ts +67 -22
- package/src/expand.ts +0 -14
- package/src/find-glob.ts +1 -1
- package/src/image.ts +1 -1
- package/src/index.ts +25 -34
- package/src/notices.ts +1 -1
- package/src/render.ts +44 -43
- package/src/tools/bash.ts +24 -48
- package/src/tools/find.ts +22 -36
- package/src/tools/grep.ts +12 -15
- package/src/tools/labels.ts +3 -0
- package/src/tools/ls.ts +16 -10
- package/src/tools/metrics.ts +1 -1
- package/src/tools/read.ts +39 -59
- package/src/tui-text.ts +10 -49
- package/src/types.ts +3 -35
- package/dist/multi-grep-fallback.d.ts +0 -30
- package/dist/multi-grep-fallback.d.ts.map +0 -1
- package/dist/multi-grep-fallback.js +0 -205
- package/dist/multi-grep-fallback.js.map +0 -1
- package/dist/tools/multi-grep.d.ts +0 -7
- package/dist/tools/multi-grep.d.ts.map +0 -1
- package/dist/tools/multi-grep.js +0 -298
- package/dist/tools/multi-grep.js.map +0 -1
- package/src/multi-grep-fallback.ts +0 -248
- package/src/tools/multi-grep.ts +0 -339
package/src/tools/multi-grep.ts
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
/* pi-pretty: multi_grep tool -- FFF-backed multi-pattern search with ripgrep/SDK fallback. */
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type ToolDefinition,
|
|
5
|
-
type ExtensionAPI,
|
|
6
|
-
type ExtensionContext,
|
|
7
|
-
type AgentToolResult,
|
|
8
|
-
} from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import { Type } from "typebox";
|
|
10
|
-
import type {
|
|
11
|
-
SdkToolDef,
|
|
12
|
-
GrepDetails,
|
|
13
|
-
FffServiceWithCursor,
|
|
14
|
-
TextContent,
|
|
15
|
-
ComponentLike,
|
|
16
|
-
ThemeLike,
|
|
17
|
-
RenderCtxLike,
|
|
18
|
-
} from "../types.js";
|
|
19
|
-
import { TOOL_RESULT_INDENT, resolveBaseBackground, MAX_PREVIEW_LINES, BG_ERROR, FG_DIM, FG_LNUM, FG_RULE, RST } from "../config.js";
|
|
20
|
-
import { shortPath, normalizeLineEndings } from "../helpers.js";
|
|
21
|
-
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
22
|
-
import { renderToolError, renderToolMetrics, fillToolBackground } from "../render.js";
|
|
23
|
-
import { resolveTextCtor } from "../tui-text.js";
|
|
24
|
-
import { fffFormatGrepText } from "../fff-helpers.js";
|
|
25
|
-
import { parseMultiGrepConstraints } from "../multi-grep-fallback.js";
|
|
26
|
-
import { NOTICE_PARTIAL_FILE_INDEX } from "../notices.js";
|
|
27
|
-
import type { MultiGrepFallback } from "../types.js";
|
|
28
|
-
|
|
29
|
-
type Result = AgentToolResult<Record<string, unknown>>;
|
|
30
|
-
|
|
31
|
-
const noopFallback: MultiGrepFallback = async () => ({
|
|
32
|
-
text: "",
|
|
33
|
-
matchCount: 0,
|
|
34
|
-
limitReached: false,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
export function registerMultiGrepTool(
|
|
38
|
-
pi: ExtensionAPI,
|
|
39
|
-
cwd: string,
|
|
40
|
-
fffService: FffServiceWithCursor | null | undefined,
|
|
41
|
-
sdkGrepTool?: SdkToolDef,
|
|
42
|
-
ripgrepFallback: MultiGrepFallback = noopFallback,
|
|
43
|
-
TextComp?: new (t?: string, x?: number, y?: number) => { setText(v: string): void },
|
|
44
|
-
): void {
|
|
45
|
-
const TC = resolveTextCtor(TextComp);
|
|
46
|
-
const home = process.env.HOME ?? "";
|
|
47
|
-
|
|
48
|
-
pi.registerTool({
|
|
49
|
-
name: "multi_grep",
|
|
50
|
-
label: "Multi Grep",
|
|
51
|
-
description: "Search file contents using multiple patterns (OR logic)",
|
|
52
|
-
parameters: Type.Object({
|
|
53
|
-
patterns: Type.Array(Type.String()),
|
|
54
|
-
path: Type.Optional(Type.String()),
|
|
55
|
-
constraints: Type.Optional(Type.String()),
|
|
56
|
-
context: Type.Optional(Type.Number()),
|
|
57
|
-
limit: Type.Optional(Type.Number()),
|
|
58
|
-
}),
|
|
59
|
-
renderShell: "self",
|
|
60
|
-
|
|
61
|
-
execute: wrapExecuteWithMetrics(async (tid, params, sig, _upd, ctx: ExtensionContext) => {
|
|
62
|
-
const p = params as any;
|
|
63
|
-
const patterns = Array.isArray(p.patterns) ? p.patterns.map(String) : [String(p.patterns ?? "")];
|
|
64
|
-
|
|
65
|
-
// Guard: empty patterns
|
|
66
|
-
if (!patterns.length || (patterns.length === 1 && !patterns[0])) {
|
|
67
|
-
return {
|
|
68
|
-
content: [{ text: "patterns array must have at least 1 element", type: "text" as const }],
|
|
69
|
-
details: { _type: "grepResult" } as GrepDetails,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Guard: aborted signal
|
|
74
|
-
if (sig?.aborted) {
|
|
75
|
-
return {
|
|
76
|
-
content: [{ text: "Aborted", type: "text" as const }],
|
|
77
|
-
details: { _type: "grepResult" } as GrepDetails,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
const constraintsStr = p.constraints ? String(p.constraints) : undefined;
|
|
81
|
-
const context = typeof p.context === "number" ? p.context : undefined;
|
|
82
|
-
const effectiveLimit = typeof p.limit === "number" ? p.limit : 200;
|
|
83
|
-
const alternationPattern = patterns.length === 1 ? patterns[0] : patterns.join("|");
|
|
84
|
-
|
|
85
|
-
const hasNativeConstraints = Boolean(constraintsStr);
|
|
86
|
-
const parsedConstraints = constraintsStr ? parseMultiGrepConstraints(constraintsStr) : null;
|
|
87
|
-
const requestedConstraints = parsedConstraints?.ok ? constraintsStr : undefined;
|
|
88
|
-
let effectivePath = p.path ? String(p.path) : undefined;
|
|
89
|
-
const requestedPath = parsedConstraints?.ok ? parsedConstraints.tokens[0] : undefined;
|
|
90
|
-
if (requestedPath && !effectivePath) effectivePath = requestedPath;
|
|
91
|
-
|
|
92
|
-
// 1. FFF multiGrep (no constraints AND no path)
|
|
93
|
-
if (fffService?.isAvailable && !hasNativeConstraints && !effectivePath) {
|
|
94
|
-
try {
|
|
95
|
-
const fff = fffService.getFinder();
|
|
96
|
-
if (!fff) throw new Error("FFF finder not available");
|
|
97
|
-
const grepResult = fff.multiGrep({
|
|
98
|
-
patterns,
|
|
99
|
-
pageSize: effectiveLimit,
|
|
100
|
-
smartCase: !shouldIgnoreCase(patterns),
|
|
101
|
-
beforeContext: context ?? 0,
|
|
102
|
-
afterContext: context ?? 0,
|
|
103
|
-
});
|
|
104
|
-
if (grepResult.ok) {
|
|
105
|
-
const grep = grepResult.value;
|
|
106
|
-
const items = grep.items.slice(0, effectiveLimit);
|
|
107
|
-
const cursorStore = fffService.getCursorStore();
|
|
108
|
-
const notices: string[] = [];
|
|
109
|
-
if (fffService.partialIndex) notices.push(NOTICE_PARTIAL_FILE_INDEX);
|
|
110
|
-
if (items.length >= effectiveLimit) notices.push(`${effectiveLimit} limit reached`);
|
|
111
|
-
if (grep.nextCursor) {
|
|
112
|
-
const cursorId = cursorStore.store(grep.nextCursor);
|
|
113
|
-
notices.push(`More results available: cursor="${cursorId}"`);
|
|
114
|
-
}
|
|
115
|
-
const text = appendNotices(fffFormatGrepText(items, effectiveLimit), notices);
|
|
116
|
-
return {
|
|
117
|
-
content: [{ type: "text" as const, text }],
|
|
118
|
-
details: {
|
|
119
|
-
_type: "grepResult",
|
|
120
|
-
text,
|
|
121
|
-
pattern: alternationPattern,
|
|
122
|
-
matchCount: items.length,
|
|
123
|
-
} as GrepDetails,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
// FFF failure -> return error directly
|
|
127
|
-
return {
|
|
128
|
-
content: [{ type: "text" as const, text: grepResult.error || "multi_grep failed" }],
|
|
129
|
-
details: {
|
|
130
|
-
_type: "grepResult",
|
|
131
|
-
text: "",
|
|
132
|
-
pattern: alternationPattern,
|
|
133
|
-
matchCount: 0,
|
|
134
|
-
} as GrepDetails,
|
|
135
|
-
};
|
|
136
|
-
} catch {
|
|
137
|
-
/* fall through */
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 2. Ripgrep fallback
|
|
142
|
-
if (requestedConstraints || !sdkGrepTool) {
|
|
143
|
-
try {
|
|
144
|
-
const pathBacked = Boolean(
|
|
145
|
-
requestedConstraints &&
|
|
146
|
-
requestedPath &&
|
|
147
|
-
!Boolean(p.path) &&
|
|
148
|
-
!requestedConstraints.includes("*") &&
|
|
149
|
-
!requestedConstraints.includes("?"),
|
|
150
|
-
);
|
|
151
|
-
const constraintsForRg = pathBacked ? undefined : requestedConstraints;
|
|
152
|
-
const notices: string[] = [];
|
|
153
|
-
if (!fffService?.isAvailable) notices.push("FFF unavailable, used ripgrep fallback");
|
|
154
|
-
else if (hasNativeConstraints) notices.push("Used ripgrep fallback for constrained search");
|
|
155
|
-
else notices.push("Used ripgrep fallback");
|
|
156
|
-
|
|
157
|
-
const rgResult = await ripgrepFallback({
|
|
158
|
-
cwd,
|
|
159
|
-
patterns,
|
|
160
|
-
path: effectivePath,
|
|
161
|
-
constraints: constraintsForRg,
|
|
162
|
-
ignoreCase: shouldIgnoreCase(patterns),
|
|
163
|
-
context,
|
|
164
|
-
limit: effectiveLimit,
|
|
165
|
-
signal: sig,
|
|
166
|
-
});
|
|
167
|
-
const text = normalizeLineEndings(rgResult.text) || "No matches found";
|
|
168
|
-
if (rgResult.limitReached) notices.push(`${effectiveLimit} limit reached`);
|
|
169
|
-
return {
|
|
170
|
-
content: [{ type: "text" as const, text: appendNotices(text, notices) }],
|
|
171
|
-
details: {
|
|
172
|
-
_type: "grepResult",
|
|
173
|
-
text,
|
|
174
|
-
pattern: alternationPattern,
|
|
175
|
-
matchCount: rgResult.matchCount,
|
|
176
|
-
} as GrepDetails,
|
|
177
|
-
};
|
|
178
|
-
} catch (error: unknown) {
|
|
179
|
-
return {
|
|
180
|
-
content: [
|
|
181
|
-
{
|
|
182
|
-
type: "text" as const,
|
|
183
|
-
text: `multi_grep error: ${error instanceof Error ? error.message : String(error)}`,
|
|
184
|
-
},
|
|
185
|
-
],
|
|
186
|
-
details: {
|
|
187
|
-
_type: "grepResult",
|
|
188
|
-
text: "",
|
|
189
|
-
pattern: alternationPattern,
|
|
190
|
-
matchCount: 0,
|
|
191
|
-
} as GrepDetails,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// 3. SDK grep fallback
|
|
197
|
-
try {
|
|
198
|
-
const notices: string[] = [];
|
|
199
|
-
if (!fffService?.isAvailable) notices.push("FFF unavailable, used SDK grep fallback");
|
|
200
|
-
const result = (await sdkGrepTool.execute(
|
|
201
|
-
tid,
|
|
202
|
-
{
|
|
203
|
-
pattern: alternationPattern,
|
|
204
|
-
path: effectivePath,
|
|
205
|
-
ignoreCase: shouldIgnoreCase(patterns),
|
|
206
|
-
context,
|
|
207
|
-
limit: effectiveLimit,
|
|
208
|
-
},
|
|
209
|
-
sig,
|
|
210
|
-
null,
|
|
211
|
-
ctx,
|
|
212
|
-
)) as Result;
|
|
213
|
-
const tc = getText(result);
|
|
214
|
-
result.content = [{ type: "text" as const, text: appendNotices(tc, notices) }];
|
|
215
|
-
result.details = {
|
|
216
|
-
_type: "grepResult",
|
|
217
|
-
text: tc,
|
|
218
|
-
pattern: alternationPattern,
|
|
219
|
-
matchCount: tc ? tc.trim().split("\n").filter(Boolean).length : 0,
|
|
220
|
-
} as GrepDetails;
|
|
221
|
-
return result;
|
|
222
|
-
} catch (error: unknown) {
|
|
223
|
-
return {
|
|
224
|
-
content: [
|
|
225
|
-
{
|
|
226
|
-
type: "text" as const,
|
|
227
|
-
text: `multi_grep error: ${error instanceof Error ? error.message : String(error)}`,
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
details: {
|
|
231
|
-
_type: "grepResult",
|
|
232
|
-
text: "",
|
|
233
|
-
pattern: alternationPattern,
|
|
234
|
-
matchCount: 0,
|
|
235
|
-
} as GrepDetails,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
}),
|
|
239
|
-
|
|
240
|
-
renderCall(args: any, theme: ThemeLike, ctx: RenderCtxLike) {
|
|
241
|
-
resolveBaseBackground(theme);
|
|
242
|
-
const text = ctx.lastComponent ?? new TC("", 0, 0);
|
|
243
|
-
const patterns: string[] = Array.isArray(args.patterns) ? args.patterns.map((p: unknown) => String(p)) : [];
|
|
244
|
-
const limit = typeof args.limit === "number" ? args.limit : undefined;
|
|
245
|
-
const path =
|
|
246
|
-
args.path === null || args.path === undefined ? "<missing>" : shortPath(cwd, home, String(args.path));
|
|
247
|
-
const literal = args.literal === true;
|
|
248
|
-
const patternStr =
|
|
249
|
-
patterns.length === 0
|
|
250
|
-
? ""
|
|
251
|
-
: patterns.length === 1
|
|
252
|
-
? patterns[0]!
|
|
253
|
-
: patterns.length === 2
|
|
254
|
-
? `${patterns[0]}|${patterns[1]}`
|
|
255
|
-
: `${patterns[0]}|${patterns[1]}|+${patterns.length - 2}`;
|
|
256
|
-
let out = `${theme.fg("toolTitle", theme.bold("mgrep"))} ${theme.fg("accent", `/${patternStr || ""}/`)}${theme.fg("toolOutput", ` in ${path}`)}`;
|
|
257
|
-
if (literal) out += theme.fg("dim", ` (literal)`);
|
|
258
|
-
if (limit !== undefined) out += theme.fg("dim", ` limit ${limit}`);
|
|
259
|
-
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}`, ctx.isError ? BG_ERROR : undefined));
|
|
260
|
-
return text;
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
renderResult(result: Result, _opt: unknown, theme: ThemeLike, ctx: RenderCtxLike) {
|
|
264
|
-
resolveBaseBackground(theme);
|
|
265
|
-
|
|
266
|
-
const text = ctx.lastComponent ?? new TC("", 0, 0);
|
|
267
|
-
if (ctx.isError) {
|
|
268
|
-
text.setText(renderToolError(getText(result) || "Error", theme));
|
|
269
|
-
return text;
|
|
270
|
-
}
|
|
271
|
-
const d = result.details as GrepDetails | undefined;
|
|
272
|
-
if (d?._type === "grepResult" && d.text) {
|
|
273
|
-
const lines = d.text.split("\n");
|
|
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;
|
|
283
|
-
const show = lines.slice(0, maxShow);
|
|
284
|
-
const nw = Math.max(3, 5);
|
|
285
|
-
|
|
286
|
-
let hlRe: RegExp | null = null;
|
|
287
|
-
try {
|
|
288
|
-
hlRe = new RegExp(`(${d.pattern})`, "gi");
|
|
289
|
-
} catch {}
|
|
290
|
-
|
|
291
|
-
const out: string[] = [];
|
|
292
|
-
let currentFile = "";
|
|
293
|
-
for (const line of show) {
|
|
294
|
-
const fileMatch = line.match(/^(.+?)[:-](\d+)[:-](.*)$/);
|
|
295
|
-
if (fileMatch) {
|
|
296
|
-
const [, file, lineNo, content] = fileMatch;
|
|
297
|
-
if (file !== currentFile) {
|
|
298
|
-
if (currentFile) out.push("");
|
|
299
|
-
out.push(`${TOOL_RESULT_INDENT}${theme.fg("accent", theme.bold(file))}`);
|
|
300
|
-
currentFile = file;
|
|
301
|
-
}
|
|
302
|
-
let display = content;
|
|
303
|
-
if (hlRe) display = content.replace(hlRe, (m) => `${RST}${theme.fg("warning", theme.bold(m))}${RST}`);
|
|
304
|
-
const padded = `${FG_LNUM}${String(lineNo).padStart(nw)}${RST} ${FG_RULE}│${RST} ${display}${RST}`;
|
|
305
|
-
out.push(`${TOOL_RESULT_INDENT}${padded}`);
|
|
306
|
-
} else if (line.trim()) {
|
|
307
|
-
out.push(`${TOOL_RESULT_INDENT}${FG_DIM}${line.trim()}${RST}`);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
const preview = out.join("\n");
|
|
311
|
-
const more = lines.length > maxShow ? `\n${TOOL_RESULT_INDENT}${FG_DIM}... ${lines.length - maxShow} more lines${RST}` : "";
|
|
312
|
-
text.setText(
|
|
313
|
-
fillToolBackground(`${TOOL_RESULT_INDENT}${FG_DIM}${d.matchCount} matches${RST}${renderToolMetrics(result)}\n${preview}${more}`),
|
|
314
|
-
);
|
|
315
|
-
return text;
|
|
316
|
-
}
|
|
317
|
-
const fc = result.content?.[0];
|
|
318
|
-
text.setText(
|
|
319
|
-
fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "no matches")}`),
|
|
320
|
-
);
|
|
321
|
-
return text;
|
|
322
|
-
},
|
|
323
|
-
} as unknown as ToolDefinition<any, any, any>);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function shouldIgnoreCase(patterns: string[]): boolean {
|
|
327
|
-
return !patterns.some((p) => /[A-Z]/.test(p));
|
|
328
|
-
}
|
|
329
|
-
function appendNotices(text: string, notices: string[]): string {
|
|
330
|
-
return notices.length ? `${text}\n\n[${notices.join(". ")}]` : text;
|
|
331
|
-
}
|
|
332
|
-
function getText(result: Result): string {
|
|
333
|
-
return (
|
|
334
|
-
((result.content ?? []) as TextContent[])
|
|
335
|
-
.filter((c) => c.type === "text")
|
|
336
|
-
.map((c) => c.text)
|
|
337
|
-
.join("\n") ?? ""
|
|
338
|
-
);
|
|
339
|
-
}
|