@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/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pi-pretty — Pretty terminal output for pi built-in tools.
|
|
3
3
|
*
|
|
4
|
-
* Enhances read, bash, ls, find, grep
|
|
4
|
+
* Enhances read, bash, ls, find, and grep with:
|
|
5
5
|
* • Syntax-highlighted file content (Shiki)
|
|
6
6
|
* • Colored bash exit status + output
|
|
7
7
|
* • Tree-view directory listings with file-type icons
|
|
@@ -12,30 +12,28 @@
|
|
|
12
12
|
// Re-export for tests
|
|
13
13
|
export { __imageInternals } from "./image.js";
|
|
14
14
|
|
|
15
|
-
import type { ExtensionAPI,
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import { registerReadTool } from "./tools/read.js";
|
|
15
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { createFffAutocompleteProvider } from "./autocomplete.js";
|
|
17
|
+
import { getDefaultAgentDir } from "./config.js";
|
|
18
|
+
import { type FffService, getSharedFffService } from "./fff.js";
|
|
20
19
|
import { registerBashTool } from "./tools/bash.js";
|
|
21
|
-
import { registerLsTool } from "./tools/ls.js";
|
|
22
20
|
import { registerFindTool } from "./tools/find.js";
|
|
23
21
|
import { registerGrepTool } from "./tools/grep.js";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { createFffAutocompleteProvider } from "./autocomplete.js";
|
|
28
|
-
import { registerDefaultCollapsedToolOutput } from "./expand.js";
|
|
22
|
+
import { registerLsTool } from "./tools/ls.js";
|
|
23
|
+
import { registerReadTool } from "./tools/read.js";
|
|
24
|
+
import type { PiPrettyDeps } from "./types.js";
|
|
29
25
|
|
|
30
26
|
// ---------------------------------------------------------------------------
|
|
31
27
|
// Config
|
|
32
28
|
// ---------------------------------------------------------------------------
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
const DEFAULT_DISABLED_TOOLS = new Set(["ls"]);
|
|
31
|
+
|
|
32
|
+
function envTools(name: "PRETTY_DISABLE_TOOLS" | "PRETTY_ENABLE_TOOLS"): Set<string> {
|
|
35
33
|
return new Set(
|
|
36
|
-
(process.env
|
|
34
|
+
(process.env[name] ?? "")
|
|
37
35
|
.split(",")
|
|
38
|
-
.map((
|
|
36
|
+
.map((tool) => tool.trim().toLowerCase())
|
|
39
37
|
.filter(Boolean),
|
|
40
38
|
);
|
|
41
39
|
}
|
|
@@ -47,9 +45,15 @@ function envDisabledTools(): Set<string> {
|
|
|
47
45
|
export type { PiPrettyDeps };
|
|
48
46
|
|
|
49
47
|
export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrettyDeps): Promise<void> {
|
|
50
|
-
const disabledTools =
|
|
51
|
-
const
|
|
52
|
-
|
|
48
|
+
const disabledTools = envTools("PRETTY_DISABLE_TOOLS");
|
|
49
|
+
const enabledTools = envTools("PRETTY_ENABLE_TOOLS");
|
|
50
|
+
const isToolEnabled = (name: string) => {
|
|
51
|
+
const normalizedName = name.toLowerCase();
|
|
52
|
+
return (
|
|
53
|
+
!disabledTools.has(normalizedName) &&
|
|
54
|
+
(!DEFAULT_DISABLED_TOOLS.has(normalizedName) || enabledTools.has(normalizedName))
|
|
55
|
+
);
|
|
56
|
+
};
|
|
53
57
|
const cwd = process.cwd();
|
|
54
58
|
|
|
55
59
|
// ------------------------------------------------------------------
|
|
@@ -62,10 +66,7 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
62
66
|
|
|
63
67
|
const maybeGetAgentDir = deps?.sdk?.getAgentDir;
|
|
64
68
|
const agentDir = typeof maybeGetAgentDir === "function" ? maybeGetAgentDir() : getDefaultAgentDir();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Ripgrep fallback for multi_grep
|
|
68
|
-
const multiGrepFallback = deps?.multiGrepRipgrepFallback ?? runMultiGrepRipgrepFallback;
|
|
69
|
+
const fffService: FffService | null = getSharedFffService(deps?.fffModule, agentDir);
|
|
69
70
|
|
|
70
71
|
// Text component for custom rendering (DI-friendly)
|
|
71
72
|
const TextComp = deps?.TextComponent;
|
|
@@ -78,8 +79,8 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
78
79
|
pi.registerCommand("fff-health", {
|
|
79
80
|
description: "Show FFF file finder health and indexer status",
|
|
80
81
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
81
|
-
const fff = fffService
|
|
82
|
-
if (!fff.isAvailable) {
|
|
82
|
+
const fff = fffService;
|
|
83
|
+
if (!fff || !fff.isAvailable) {
|
|
83
84
|
ctx.ui.notify("FFF not initialized", "warning");
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
@@ -229,16 +230,6 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
229
230
|
if (isToolEnabled("grep") && createGrepTool) {
|
|
230
231
|
registerGrepTool(pi, cwd, fffService, createGrepTool(cwd), TextComp);
|
|
231
232
|
}
|
|
232
|
-
if (isToolEnabled("multi_grep") && (fffService || createGrepTool)) {
|
|
233
|
-
registerMultiGrepTool(
|
|
234
|
-
pi,
|
|
235
|
-
cwd,
|
|
236
|
-
fffService,
|
|
237
|
-
createGrepTool ? createGrepTool(cwd) : undefined,
|
|
238
|
-
multiGrepFallback,
|
|
239
|
-
TextComp,
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
233
|
|
|
243
234
|
// Fallback padding for SDK-rendered tool bodies. The SDK reads
|
|
244
235
|
// result.content[0].text and slices collapsed output to roughly the first
|
package/src/notices.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Stable user-facing strings for FFF/grep tool footers (not source-line titles). */
|
|
2
2
|
|
|
3
|
-
export const NOTICE_PARTIAL_FILE_INDEX = "FFF index incomplete; results may be missing files";
|
|
3
|
+
export const NOTICE_PARTIAL_FILE_INDEX = "FFF index incomplete; results may be missing files";
|
package/src/render.ts
CHANGED
|
@@ -5,49 +5,47 @@
|
|
|
5
5
|
* They are async only when Shiki syntax highlighting is involved.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { BundledLanguage } from "shiki";
|
|
9
|
-
import { codeToANSI } from "@shikijs/cli";
|
|
10
8
|
import { basename, dirname } from "node:path";
|
|
11
|
-
|
|
9
|
+
import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
// Top-level value import so jiti's pi-tui alias applies (it only rewrites
|
|
11
|
+
// static top-level import/require, not function-body require — see tui-text.ts).
|
|
12
|
+
import { Text as TuiText, truncateToWidth } from "@earendil-works/pi-tui";
|
|
13
|
+
import { codeToANSI } from "@shikijs/cli";
|
|
14
|
+
import type { BundledLanguage } from "shiki";
|
|
12
15
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
BG_BASE,
|
|
17
|
+
BG_ERROR,
|
|
18
|
+
CACHE_LIMIT,
|
|
19
|
+
detectLang,
|
|
20
|
+
dirIcon,
|
|
21
|
+
FG_BLUE,
|
|
16
22
|
FG_DIM,
|
|
17
|
-
FG_RULE,
|
|
18
23
|
FG_GREEN,
|
|
24
|
+
FG_LNUM,
|
|
19
25
|
FG_RED,
|
|
26
|
+
FG_RULE,
|
|
20
27
|
FG_YELLOW,
|
|
21
|
-
FG_BLUE,
|
|
22
|
-
BG_BASE,
|
|
23
|
-
BG_ERROR,
|
|
24
|
-
dirIcon,
|
|
25
|
-
detectLang,
|
|
26
|
-
termWidth,
|
|
27
|
-
MAX_PREVIEW_LINES,
|
|
28
28
|
MAX_HL_CHARS,
|
|
29
|
-
|
|
29
|
+
MAX_PREVIEW_LINES,
|
|
30
|
+
RST,
|
|
30
31
|
resolveBaseBackground,
|
|
32
|
+
TOOL_RESULT_INDENT,
|
|
33
|
+
termWidth,
|
|
31
34
|
} from "./config.js";
|
|
32
35
|
import {
|
|
33
|
-
normalizeLineEndings,
|
|
34
|
-
humanSize,
|
|
35
|
-
formatElapsedMs,
|
|
36
|
-
formatCharCount,
|
|
37
|
-
ELAPSED_KEY,
|
|
38
36
|
CHARS_KEY,
|
|
39
37
|
compactErrorLines,
|
|
38
|
+
ELAPSED_KEY,
|
|
39
|
+
formatCharCount,
|
|
40
|
+
formatElapsedMs,
|
|
41
|
+
humanSize,
|
|
42
|
+
normalizeLineEndings,
|
|
40
43
|
} from "./helpers.js";
|
|
41
|
-
import type {
|
|
42
|
-
import type { ThemeLike, RenderCtxLike as RenderContext } from "./types.js";
|
|
43
|
-
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// Lazy imports — avoid top-level require() that blocks module loading
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
44
|
+
import type { RenderCtxLike as RenderContext, ThemeLike } from "./types.js";
|
|
47
45
|
|
|
48
|
-
/**
|
|
46
|
+
/** Thin wrapper over pi-tui's truncateToWidth (imported at top level above). */
|
|
49
47
|
function _truncateToWidth(text: string, maxWidth: number, ellipsis?: string, pad?: boolean): string {
|
|
50
|
-
return
|
|
48
|
+
return truncateToWidth(text, maxWidth, ellipsis, pad);
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
// ---------------------------------------------------------------------------
|
|
@@ -73,7 +71,7 @@ function resolveTheme(): BundledTheme {
|
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
|
|
74
|
+
const THEME: BundledTheme = resolveTheme();
|
|
77
75
|
const _cache = new Map<string, string[]>();
|
|
78
76
|
|
|
79
77
|
function _touch(k: string, v: string[]): string[] {
|
|
@@ -174,8 +172,7 @@ export function fillToolBackground(text: string, bg = BG_BASE, width?: number):
|
|
|
174
172
|
return bg ? bg + stripped : stripped;
|
|
175
173
|
}
|
|
176
174
|
const plainLead = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
177
|
-
const skipPad =
|
|
178
|
-
line.startsWith(TOOL_RESULT_INDENT) || plainLead.startsWith(TOOL_RESULT_INDENT);
|
|
175
|
+
const skipPad = line.startsWith(TOOL_RESULT_INDENT) || plainLead.startsWith(TOOL_RESULT_INDENT);
|
|
179
176
|
const fitted = _truncateToWidth(line, width, "", !skipPad);
|
|
180
177
|
const stripped = preserveBoxBackground(fitted);
|
|
181
178
|
return bg ? bg + stripped : stripped;
|
|
@@ -205,6 +202,11 @@ export function renderToolMetrics(result: AgentToolResult<Record<string, unknown
|
|
|
205
202
|
return `${FG_DIM}· ${[elapsed, chars].filter(Boolean).join(" · ")}${RST}`;
|
|
206
203
|
}
|
|
207
204
|
|
|
205
|
+
export function renderToolDuration(result: AgentToolResult<Record<string, unknown>>): string {
|
|
206
|
+
const details = result.details as Record<string, unknown> | undefined;
|
|
207
|
+
return formatElapsedMs(details?.[ELAPSED_KEY] as number | undefined);
|
|
208
|
+
}
|
|
209
|
+
|
|
208
210
|
// ---------------------------------------------------------------------------
|
|
209
211
|
// Error renderer
|
|
210
212
|
// ---------------------------------------------------------------------------
|
|
@@ -246,14 +248,7 @@ export async function renderFileContent(
|
|
|
246
248
|
// Bash — colored exit status
|
|
247
249
|
// ---------------------------------------------------------------------------
|
|
248
250
|
|
|
249
|
-
export function renderBashOutput(text: string
|
|
250
|
-
const isOk = exitCode === 0;
|
|
251
|
-
const statusIcon = isOk ? "✓" : "✗";
|
|
252
|
-
const codeStr =
|
|
253
|
-
exitCode !== null
|
|
254
|
-
? `${isOk ? FG_GREEN : FG_RED}${statusIcon} exit ${exitCode}${RST}`
|
|
255
|
-
: `${FG_YELLOW}⚡ killed${RST}`;
|
|
256
|
-
|
|
251
|
+
export function renderBashOutput(text: string): string {
|
|
257
252
|
const lines = text.split("\n");
|
|
258
253
|
const maxShow = MAX_PREVIEW_LINES;
|
|
259
254
|
const show = lines.slice(0, maxShow);
|
|
@@ -262,7 +257,7 @@ export function renderBashOutput(text: string, exitCode: number | null): { summa
|
|
|
262
257
|
let body = show.join("\n");
|
|
263
258
|
if (remaining > 0) body += `\n${TOOL_RESULT_INDENT}${FG_DIM}… ${remaining} more lines${RST}`;
|
|
264
259
|
|
|
265
|
-
return
|
|
260
|
+
return body;
|
|
266
261
|
}
|
|
267
262
|
|
|
268
263
|
// ---------------------------------------------------------------------------
|
|
@@ -391,7 +386,7 @@ export async function renderGrepResults(text: string, pattern: string): Promise<
|
|
|
391
386
|
export function makeRenderCall(toolName: string) {
|
|
392
387
|
return (args: Record<string, unknown>, theme: ThemeLike, ctx: RenderContext) => {
|
|
393
388
|
resolveBaseBackground(theme);
|
|
394
|
-
const text = ctx.lastComponent ?? new (
|
|
389
|
+
const text = ctx.lastComponent ?? new TuiText("", 0, 0);
|
|
395
390
|
const bg = ctx.isError ? BG_ERROR : undefined;
|
|
396
391
|
text.setText(fillToolBackground(`${theme.fg("toolTitle", theme.bold(toolName))}`, bg));
|
|
397
392
|
return text;
|
|
@@ -401,7 +396,7 @@ export function makeRenderCall(toolName: string) {
|
|
|
401
396
|
export function makeRenderResult() {
|
|
402
397
|
return (result: AgentToolResult<Record<string, unknown>>, _opt: unknown, theme: ThemeLike, ctx: RenderContext) => {
|
|
403
398
|
resolveBaseBackground(theme);
|
|
404
|
-
const text = ctx.lastComponent ?? new (
|
|
399
|
+
const text = ctx.lastComponent ?? new TuiText("", 0, 0);
|
|
405
400
|
if (ctx.isError) {
|
|
406
401
|
text.setText(renderToolError(getTextContent(result) || "Error", theme));
|
|
407
402
|
return text;
|
|
@@ -414,7 +409,13 @@ export function makeRenderResult() {
|
|
|
414
409
|
const preview = lines.slice(0, maxShow).join("\n");
|
|
415
410
|
const more = lines.length > maxShow ? `\n${FG_DIM}... ${lines.length - maxShow} more lines${RST}` : "";
|
|
416
411
|
const metrics = renderToolMetrics(result);
|
|
417
|
-
text.setText(
|
|
412
|
+
text.setText(
|
|
413
|
+
fillToolBackground(
|
|
414
|
+
`${TOOL_RESULT_INDENT}${preview}${more}${metrics ? `\n${TOOL_RESULT_INDENT}${metrics}` : ""}`,
|
|
415
|
+
undefined,
|
|
416
|
+
renderWidth,
|
|
417
|
+
),
|
|
418
|
+
);
|
|
418
419
|
} else {
|
|
419
420
|
text.setText(fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", "(no text output)")}`));
|
|
420
421
|
}
|
package/src/tools/bash.ts
CHANGED
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
/* pi-pretty: bash tool -- command execution with styled output. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type AgentToolResult,
|
|
8
|
-
} from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import type { SdkToolDef, BashDetails, TextContent, ComponentLike, ThemeLike, RenderCtxLike } from "../types.js";
|
|
10
|
-
import {
|
|
11
|
-
TOOL_RESULT_INDENT,
|
|
12
|
-
resolveBaseBackground,
|
|
13
|
-
termWidth,
|
|
14
|
-
MAX_PREVIEW_LINES,
|
|
15
|
-
BG_BASE,
|
|
16
|
-
BG_ERROR,
|
|
17
|
-
FG_DIM,
|
|
18
|
-
FG_RULE,
|
|
19
|
-
RST,
|
|
20
|
-
} from "../config.js";
|
|
21
|
-
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
22
|
-
import { collapsedExpandFooter } from "../collapsed-hint.js";
|
|
23
|
-
import { renderBashOutput, renderToolError, renderToolMetrics, fillToolBackground } from "../render.js";
|
|
3
|
+
import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { BG_BASE, FG_DIM, MAX_PREVIEW_LINES, resolveBaseBackground, TOOL_RESULT_INDENT, termWidth } from "../config.js";
|
|
5
|
+
import { compactErrorLines, inferBashExitCode, stripBashExitStatusLine } from "../helpers.js";
|
|
6
|
+
import { fillToolBackground, renderToolDuration, renderToolError } from "../render.js";
|
|
24
7
|
import { resolveTextCtor } from "../tui-text.js";
|
|
25
|
-
import {
|
|
8
|
+
import type { BashDetails, ComponentLike, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
|
|
9
|
+
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
26
10
|
|
|
27
11
|
type Result = AgentToolResult<Record<string, unknown>>;
|
|
28
12
|
|
|
@@ -40,8 +24,8 @@ export function registerBashTool(
|
|
|
40
24
|
label: "Bash",
|
|
41
25
|
description: sdkTool.description
|
|
42
26
|
? `${sdkTool.description} For text search: \`rg -n\`.`
|
|
43
|
-
: "Execute shell commands. For text search:
|
|
44
|
-
promptSnippet: "Execute commands via bash. For text search:
|
|
27
|
+
: "Execute shell commands. For text search: `rg -n`.",
|
|
28
|
+
promptSnippet: "Execute commands via bash. For text search: `rg -n`.",
|
|
45
29
|
promptGuidelines: [
|
|
46
30
|
"For text search: `rg -n`. If no results, try `rg -u` (respects .gitignore by default).",
|
|
47
31
|
"In rg: | means alternation, \\| means literal pipe. Opposite of GNU grep. Never use \\| for alternation.",
|
|
@@ -78,14 +62,11 @@ export function registerBashTool(
|
|
|
78
62
|
rawCmd.length === 0
|
|
79
63
|
? theme.fg("toolOutput", "...")
|
|
80
64
|
: !ctx.expanded && rawCmd.length > headerBudget
|
|
81
|
-
? rawCmd.slice(0, Math.max(1, headerBudget))
|
|
65
|
+
? `${rawCmd.slice(0, Math.max(1, headerBudget))}…`
|
|
82
66
|
: rawCmd;
|
|
67
|
+
const commandLabel = theme.fg(ctx.isError ? "error" : "toolTitle", theme.bold(`$ ${cmd}`));
|
|
83
68
|
text.setText(
|
|
84
|
-
fillToolBackground(
|
|
85
|
-
`\n${TOOL_RESULT_INDENT}${theme.fg("toolTitle", theme.bold(`$ ${cmd}`))}${t}`,
|
|
86
|
-
ctx.isError ? BG_ERROR : undefined,
|
|
87
|
-
ctx.expanded ? undefined : tw,
|
|
88
|
-
),
|
|
69
|
+
fillToolBackground(`\n${TOOL_RESULT_INDENT}${commandLabel}${t}`, undefined, ctx.expanded ? undefined : tw),
|
|
89
70
|
);
|
|
90
71
|
return text;
|
|
91
72
|
},
|
|
@@ -111,26 +92,22 @@ export function registerBashTool(
|
|
|
111
92
|
|
|
112
93
|
if (d?._type === "bashResult") {
|
|
113
94
|
const isErr = ctx.isError || (d.exitCode !== null && d.exitCode !== 0);
|
|
114
|
-
const bg = isErr ? BG_ERROR : undefined;
|
|
115
95
|
const cleaned = stripBashExitStatusLine(d.text);
|
|
116
96
|
const output = isErr ? compactErrorLines(cleaned).join("\n") : cleaned;
|
|
117
|
-
const { summary } = renderBashOutput(output, d.exitCode);
|
|
118
97
|
const lineCount = output.split("\n").length;
|
|
119
|
-
const info =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const header = `${TOOL_RESULT_INDENT}${
|
|
98
|
+
const info = [`${lineCount} lines`, renderToolDuration(result), !ctx.expanded ? "ctrl+o to expand" : ""]
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.map((part) => theme.fg("dim", part))
|
|
101
|
+
.join(theme.fg("dim", " · "));
|
|
102
|
+
const header = `${TOOL_RESULT_INDENT}${info}`;
|
|
124
103
|
const rw = termWidth();
|
|
125
104
|
|
|
126
105
|
const renderFn = (w: number) => {
|
|
127
|
-
if (!ctx.expanded) {
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
if (!output.trim()) return fillToolBackground(header, bg, w);
|
|
106
|
+
if (!ctx.expanded) return fillToolBackground(`${header}\n`, undefined, w);
|
|
107
|
+
if (!output.trim()) return fillToolBackground(`${header}\n`, undefined, w);
|
|
131
108
|
const show = output.split("\n");
|
|
132
|
-
const out = [header,
|
|
133
|
-
return fillToolBackground(out.join("\n")
|
|
109
|
+
const out = [header, "", ...show.map((line: string) => `${TOOL_RESULT_INDENT}${line}`)];
|
|
110
|
+
return fillToolBackground(`${out.join("\n")}\n`, undefined, w);
|
|
134
111
|
};
|
|
135
112
|
|
|
136
113
|
text.setText(renderFn(rw));
|
|
@@ -140,7 +117,7 @@ export function registerBashTool(
|
|
|
140
117
|
let key: string | undefined;
|
|
141
118
|
(text as unknown as Record<string, unknown>).render = (w: number) => {
|
|
142
119
|
const width = Math.max(1, Math.floor(w || termWidth()));
|
|
143
|
-
const k = `bash:${ctx.expanded ? "1" : "0"}:${width}:${d.exitCode ?? "killed"}:${output.length}:${
|
|
120
|
+
const k = `bash:${ctx.expanded ? "1" : "0"}:${width}:${d.exitCode ?? "killed"}:${output.length}:${renderToolDuration(result)}`;
|
|
144
121
|
if (key !== k) {
|
|
145
122
|
text.setText(renderFn(width));
|
|
146
123
|
key = k;
|
|
@@ -157,16 +134,15 @@ export function registerBashTool(
|
|
|
157
134
|
}
|
|
158
135
|
const fc = result.content?.[0];
|
|
159
136
|
text.setText(
|
|
160
|
-
fillToolBackground(
|
|
137
|
+
fillToolBackground(
|
|
138
|
+
`${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`,
|
|
139
|
+
),
|
|
161
140
|
);
|
|
162
141
|
return text;
|
|
163
142
|
},
|
|
164
143
|
} as unknown as ToolDefinition<any, any, any>);
|
|
165
144
|
}
|
|
166
145
|
|
|
167
|
-
function rule(w: number): string {
|
|
168
|
-
return `${FG_RULE}${"\u2500".repeat(w)}${RST}`;
|
|
169
|
-
}
|
|
170
146
|
function getText(result: Result): string {
|
|
171
147
|
return (
|
|
172
148
|
((result.content ?? []) as TextContent[])
|
package/src/tools/find.ts
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
/* pi-pretty: find tool -- FFF-backed file search with SDK (fd) fallback. */
|
|
2
2
|
|
|
3
3
|
import { isAbsolute, relative } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type ExtensionContext,
|
|
8
|
-
type AgentToolResult,
|
|
9
|
-
} from "@earendil-works/pi-coding-agent";
|
|
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";
|
|
4
|
+
import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { BG_ERROR, FG_DIM, RST, resolveBaseBackground, TOOL_RESULT_INDENT } from "../config.js";
|
|
6
|
+
import { isLikelyGlobPattern, normalizeFindGlobPattern } from "../find-glob.js";
|
|
12
7
|
import { shortPath } from "../helpers.js";
|
|
13
|
-
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
14
|
-
import { renderFindResults, renderToolError, renderToolMetrics, fillToolBackground } from "../render.js";
|
|
15
|
-
import { resolveTextCtor } from "../tui-text.js";
|
|
16
8
|
import { NOTICE_PARTIAL_FILE_INDEX } from "../notices.js";
|
|
17
|
-
import {
|
|
9
|
+
import { fillToolBackground, renderFindResults, renderToolDuration, renderToolError } from "../render.js";
|
|
10
|
+
import { resolveTextCtor } from "../tui-text.js";
|
|
11
|
+
import type { FffServiceWithCursor, FindDetails, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
|
|
12
|
+
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
18
13
|
|
|
19
14
|
type Result = AgentToolResult<Record<string, unknown>>;
|
|
20
15
|
|
|
@@ -105,7 +100,9 @@ export function registerFindTool(
|
|
|
105
100
|
const basePathResult = fff.getBasePath();
|
|
106
101
|
const basePath = basePathResult.ok ? basePathResult.value : null;
|
|
107
102
|
const globPattern = buildGlobPattern(pattern, path, basePath);
|
|
108
|
-
const searchResult = fff.glob(globPattern, {
|
|
103
|
+
const searchResult = fff.glob(globPattern, {
|
|
104
|
+
pageSize: effectiveLimit,
|
|
105
|
+
});
|
|
109
106
|
|
|
110
107
|
if (searchResult.ok) {
|
|
111
108
|
const items = searchResult.value.items.slice(0, effectiveLimit);
|
|
@@ -153,22 +150,15 @@ export function registerFindTool(
|
|
|
153
150
|
const a = args as { pattern?: unknown; path?: unknown; limit?: unknown };
|
|
154
151
|
const text = (ctx as RenderCtxLike).lastComponent ?? new TC("", 0, 0);
|
|
155
152
|
const pattern = a.pattern == null ? "" : String(a.pattern);
|
|
156
|
-
const pathArg =
|
|
157
|
-
a.path == null ? "<missing>" : shortPath(cwd, home, String(a.path));
|
|
153
|
+
const pathArg = a.path == null ? "<missing>" : shortPath(cwd, home, String(a.path));
|
|
158
154
|
const limit = a.limit;
|
|
159
|
-
const findLabel = theme.fg("toolTitle", theme.bold("find"));
|
|
160
|
-
const patternPart = pattern ? theme.fg("
|
|
155
|
+
const findLabel = theme.fg(ctx.isError ? "error" : "toolTitle", theme.bold("✱ find"));
|
|
156
|
+
const patternPart = pattern ? theme.fg("toolTitle", pattern) : "";
|
|
161
157
|
const inPart = theme.fg("dim", " in ");
|
|
162
158
|
const pathPart = theme.fg("toolOutput", pathArg);
|
|
163
|
-
const limitPart =
|
|
164
|
-
limit !== undefined && limit !== null ? theme.fg("dim", ` limit ${limit}`) : "";
|
|
159
|
+
const limitPart = limit !== undefined && limit !== null ? theme.fg("dim", ` limit ${limit}`) : "";
|
|
165
160
|
const out = `${findLabel} ${patternPart}${inPart}${pathPart}${limitPart}`;
|
|
166
|
-
text.setText(
|
|
167
|
-
fillToolBackground(
|
|
168
|
-
`\n${TOOL_RESULT_INDENT}${out}`,
|
|
169
|
-
ctx.isError ? BG_ERROR : undefined,
|
|
170
|
-
),
|
|
171
|
-
);
|
|
161
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}`, ctx.isError ? BG_ERROR : undefined));
|
|
172
162
|
return text;
|
|
173
163
|
},
|
|
174
164
|
|
|
@@ -186,17 +176,14 @@ export function registerFindTool(
|
|
|
186
176
|
const noticeStr = d.notices?.length
|
|
187
177
|
? `\n${TOOL_RESULT_INDENT}${theme.fg("warning", `[${d.notices.join(". ")}]`)}`
|
|
188
178
|
: "";
|
|
189
|
-
text.setText(
|
|
190
|
-
fillToolBackground(
|
|
191
|
-
`\n${TOOL_RESULT_INDENT}${theme.fg("dim", "0 files")}${noticeStr}\n`,
|
|
192
|
-
),
|
|
193
|
-
);
|
|
179
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${theme.fg("dim", "0 files")}${noticeStr}\n`));
|
|
194
180
|
return text;
|
|
195
181
|
}
|
|
196
182
|
if (!ctx.expanded) {
|
|
183
|
+
const duration = renderToolDuration(r);
|
|
197
184
|
text.setText(
|
|
198
185
|
fillToolBackground(
|
|
199
|
-
`${TOOL_RESULT_INDENT}${FG_DIM}${d.matchCount} files — ctrl+o to expand${RST}${
|
|
186
|
+
`${TOOL_RESULT_INDENT}${FG_DIM}${d.matchCount} files — ctrl+o to expand${RST}${duration ? `${FG_DIM}· ${duration}${RST}` : ""}\n`,
|
|
200
187
|
),
|
|
201
188
|
);
|
|
202
189
|
return text;
|
|
@@ -208,20 +195,19 @@ export function registerFindTool(
|
|
|
208
195
|
const noticeStr = d.notices?.length
|
|
209
196
|
? `\n${TOOL_RESULT_INDENT}${theme.fg("warning", `[${d.notices.join(". ")}]`)}`
|
|
210
197
|
: "";
|
|
198
|
+
const duration = renderToolDuration(r);
|
|
211
199
|
text.setText(
|
|
212
200
|
fillToolBackground(
|
|
213
|
-
`\n${TOOL_RESULT_INDENT}${theme.fg("dim", `${d.matchCount} files`)}${
|
|
201
|
+
`\n${TOOL_RESULT_INDENT}${theme.fg("dim", `${d.matchCount} files`)}${duration ? `${FG_DIM}· ${duration}${RST}` : ""}\n${rendered}${noticeStr}\n`,
|
|
214
202
|
),
|
|
215
203
|
);
|
|
216
204
|
return text;
|
|
217
205
|
}
|
|
218
206
|
const fc = r.content?.[0] as TextContent | undefined;
|
|
219
207
|
text.setText(
|
|
220
|
-
fillToolBackground(
|
|
221
|
-
`\n${TOOL_RESULT_INDENT}${theme.fg("dim", fc?.text?.slice(0, 120) ?? "0 files")}\n`,
|
|
222
|
-
),
|
|
208
|
+
fillToolBackground(`\n${TOOL_RESULT_INDENT}${theme.fg("dim", fc?.text?.slice(0, 120) ?? "0 files")}\n`),
|
|
223
209
|
);
|
|
224
210
|
return text;
|
|
225
211
|
},
|
|
226
212
|
} as unknown as ToolDefinition);
|
|
227
|
-
}
|
|
213
|
+
}
|
package/src/tools/grep.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/* pi-pretty: grep tool -- FFF-backed text search with SDK fallback. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
type ExtensionAPI,
|
|
6
|
-
type ExtensionContext,
|
|
7
|
-
type AgentToolResult,
|
|
8
|
-
} from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import type { SdkToolDef, GrepDetails, FffServiceWithCursor, TextContent, ThemeLike, RenderCtxLike } from "../types.js";
|
|
10
|
-
import { TOOL_RESULT_INDENT, MAX_PREVIEW_LINES, BG_ERROR, FG_DIM, RST, resolveBaseBackground } from "../config.js";
|
|
11
|
-
import { shortPath, normalizeLineEndings } from "../helpers.js";
|
|
12
|
-
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
13
|
-
import { renderToolError, fillToolBackground } from "../render.js";
|
|
14
|
-
import { resolveTextCtor } from "../tui-text.js";
|
|
3
|
+
import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { BG_ERROR, FG_DIM, MAX_PREVIEW_LINES, RST, resolveBaseBackground, TOOL_RESULT_INDENT } from "../config.js";
|
|
15
5
|
import { fffFormatGrepText } from "../fff-helpers.js";
|
|
6
|
+
import { normalizeLineEndings, shortPath } from "../helpers.js";
|
|
16
7
|
import { NOTICE_PARTIAL_FILE_INDEX } from "../notices.js";
|
|
8
|
+
import { fillToolBackground, renderToolError } from "../render.js";
|
|
9
|
+
import { resolveTextCtor } from "../tui-text.js";
|
|
10
|
+
import type { FffServiceWithCursor, GrepDetails, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
|
|
11
|
+
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
17
12
|
|
|
18
13
|
const invalidArg = "<missing>";
|
|
19
14
|
|
|
@@ -111,7 +106,7 @@ export function registerGrepTool(
|
|
|
111
106
|
const limit = args.limit;
|
|
112
107
|
const literal = args.literal === true;
|
|
113
108
|
const caseInsensitive = args.caseInsensitive === true || args.ignoreCase === true;
|
|
114
|
-
let out = `${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("
|
|
109
|
+
let out = `${theme.fg(ctx.isError ? "error" : "toolTitle", theme.bold("✱ grep"))} ${theme.fg("toolTitle", `/${pattern || ""}/`)}${theme.fg("toolOutput", ` in ${path}`)}`;
|
|
115
110
|
if (glob) out += theme.fg("dim", ` (${String(glob)})`);
|
|
116
111
|
if (limit !== undefined && limit !== null) out += theme.fg("dim", ` limit ${limit}`);
|
|
117
112
|
if (literal) out += theme.fg("dim", ` (literal)`);
|
|
@@ -158,13 +153,15 @@ export function registerGrepTool(
|
|
|
158
153
|
if (remaining > 0) {
|
|
159
154
|
out.push(theme.fg("muted", `… (${remaining} more ${remaining === 1 ? "line" : "lines"}, to expand)`));
|
|
160
155
|
}
|
|
161
|
-
const body = out.map((l) => `${TOOL_RESULT_INDENT}${l}`).join("\n")
|
|
156
|
+
const body = `${out.map((l) => `${TOOL_RESULT_INDENT}${l}`).join("\n")}\n\n`;
|
|
162
157
|
text.setText(fillToolBackground(body, ctx.isError ? BG_ERROR : undefined));
|
|
163
158
|
return text;
|
|
164
159
|
}
|
|
165
160
|
const fc = result.content?.[0];
|
|
166
161
|
const fallback = fc && "text" in fc ? String(fc.text).slice(0, 120) : "no matches";
|
|
167
|
-
text.setText(
|
|
162
|
+
text.setText(
|
|
163
|
+
fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", fallback)}`, ctx.isError ? BG_ERROR : undefined),
|
|
164
|
+
);
|
|
168
165
|
return text;
|
|
169
166
|
},
|
|
170
167
|
} as unknown as ToolDefinition<any, any, any>);
|
package/src/tools/ls.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
/* pi-pretty: ls tool -- directory listing with styled output. */
|
|
2
2
|
|
|
3
|
+
import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
BG_BASE,
|
|
6
|
+
BG_ERROR,
|
|
7
|
+
FG_DIM,
|
|
8
|
+
MAX_PREVIEW_LINES,
|
|
9
|
+
RST,
|
|
10
|
+
resolveBaseBackground,
|
|
11
|
+
TOOL_RESULT_INDENT,
|
|
12
|
+
termWidth,
|
|
13
|
+
} from "../config.js";
|
|
11
14
|
import { shortPath } from "../helpers.js";
|
|
12
|
-
import {
|
|
13
|
-
import { renderTree, renderToolError, renderToolMetrics, fillToolBackground } from "../render.js";
|
|
15
|
+
import { fillToolBackground, renderToolError, renderToolMetrics, renderTree } from "../render.js";
|
|
14
16
|
import { resolveTextCtor } from "../tui-text.js";
|
|
17
|
+
import type { ComponentLike, LsDetails, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
|
|
18
|
+
import { wrapExecuteWithMetrics } from "./metrics.js";
|
|
15
19
|
|
|
16
20
|
type Result = AgentToolResult<Record<string, unknown>>;
|
|
17
21
|
|
|
@@ -91,7 +95,9 @@ export function registerLsTool(
|
|
|
91
95
|
}
|
|
92
96
|
const fc = result.content?.[0];
|
|
93
97
|
text.setText(
|
|
94
|
-
fillToolBackground(
|
|
98
|
+
fillToolBackground(
|
|
99
|
+
`${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`,
|
|
100
|
+
),
|
|
95
101
|
);
|
|
96
102
|
return text;
|
|
97
103
|
},
|
package/src/tools/metrics.ts
CHANGED