@xynogen/pix-pretty 1.5.1 → 1.6.1

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/src/tools/grep.ts DELETED
@@ -1,179 +0,0 @@
1
- import type {
2
- ExtensionContext,
3
- GrepToolInput,
4
- ToolRenderResultOptions,
5
- } from "@earendil-works/pi-coding-agent";
6
-
7
- import { fffFormatGrepText } from "../fff.js";
8
- import type {
9
- GrepParams,
10
- GrepResultDetails,
11
- PiPrettyApi,
12
- RenderContextLike,
13
- ThemeLike,
14
- ToolFactory,
15
- ToolResultLike,
16
- } from "../types.js";
17
- import {
18
- appendNotices,
19
- countRipgrepMatches,
20
- fillToolBackground,
21
- getTextContent,
22
- isTextContent,
23
- makeTextResult,
24
- normalizeLineEndings,
25
- pluralize,
26
- renderDimPreview,
27
- renderToolError,
28
- setResultDetails,
29
- } from "../utils.js";
30
- import type { ToolContext } from "./context.js";
31
-
32
- export function registerGrepTool(
33
- pi: PiPrettyApi,
34
- createGrepTool: ToolFactory<GrepToolInput>,
35
- ctx: ToolContext,
36
- ): void {
37
- const { cwd, sp, TextComponent, fffState, cursorStore } = ctx;
38
- const origGrep = createGrepTool(cwd);
39
-
40
- pi.registerTool({
41
- ...origGrep,
42
- name: "grep",
43
- renderShell: "self",
44
-
45
- async execute(
46
- tid: string,
47
- params: GrepParams,
48
- sig: AbortSignal | undefined,
49
- upd: unknown,
50
- toolCtx: ExtensionContext,
51
- ) {
52
- // Try FFF first (SIMD-accelerated).
53
- // Constrained searches (path/glob) fall through to SDK — FFF 0.5.2
54
- // can abort the process on constrained searches with Unicode filenames.
55
- if (
56
- fffState.finder &&
57
- !fffState.finder.isDestroyed &&
58
- !params.path &&
59
- !params.glob
60
- ) {
61
- try {
62
- const effectiveLimit = Math.max(1, params.limit ?? 100);
63
- const grepResult = fffState.finder.grep(params.pattern, {
64
- mode: params.literal ? "plain" : "regex",
65
- smartCase: !params.ignoreCase,
66
- maxMatchesPerFile: Math.min(effectiveLimit, 50),
67
- cursor: null,
68
- beforeContext: params.context ?? 0,
69
- afterContext: params.context ?? 0,
70
- });
71
-
72
- if (grepResult.ok) {
73
- const grep = grepResult.value;
74
- const notices: string[] = [];
75
- if (fffState.partialIndex)
76
- notices.push("Warning: partial file index");
77
- if (grep.items.length >= effectiveLimit)
78
- notices.push(`${effectiveLimit} limit reached`);
79
- if (grep.regexFallbackError)
80
- notices.push(
81
- `Regex failed: ${grep.regexFallbackError}, used literal match`,
82
- );
83
- if (grep.nextCursor) {
84
- const cursorId = cursorStore.store(grep.nextCursor);
85
- notices.push(
86
- `More results available. Use cursor="${cursorId}" to continue`,
87
- );
88
- }
89
-
90
- const textContent = appendNotices(
91
- fffFormatGrepText(grep.items, effectiveLimit),
92
- notices,
93
- );
94
- return makeTextResult<GrepResultDetails>(textContent, {
95
- _type: "grepResult",
96
- text: textContent,
97
- pattern: params.pattern,
98
- matchCount: Math.min(grep.items.length, effectiveLimit),
99
- });
100
- }
101
- } catch {
102
- /* fall through to SDK */
103
- }
104
- }
105
-
106
- // SDK fallback
107
- const result = await origGrep.execute(
108
- tid,
109
- params,
110
- sig,
111
- upd as never,
112
- toolCtx,
113
- );
114
- const textContent = normalizeLineEndings(getTextContent(result));
115
- if (result.content) {
116
- for (const content of result.content) {
117
- if (isTextContent(content))
118
- content.text = normalizeLineEndings(content.text || "");
119
- }
120
- }
121
- const matchCount = textContent ? countRipgrepMatches(textContent) : 0;
122
-
123
- setResultDetails<GrepResultDetails>(result, {
124
- _type: "grepResult",
125
- text: textContent,
126
- pattern: params.pattern,
127
- matchCount,
128
- });
129
-
130
- return result;
131
- },
132
-
133
- renderCall(
134
- args: GrepParams,
135
- theme: ThemeLike,
136
- renderCtx: RenderContextLike,
137
- ) {
138
- const pattern = args.pattern ?? "";
139
- const path = args.path
140
- ? ` ${theme.fg("muted", `in ${sp(args.path)}`)}`
141
- : "";
142
- const glob = args.glob ? ` ${theme.fg("muted", `(${args.glob})`)}` : "";
143
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
144
- text.setText(
145
- fillToolBackground(
146
- `${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", pattern)}${path}${glob}`,
147
- ),
148
- );
149
- return text;
150
- },
151
-
152
- renderResult(
153
- result: ToolResultLike<GrepResultDetails>,
154
- _opt: ToolRenderResultOptions,
155
- theme: ThemeLike,
156
- renderCtx: RenderContextLike,
157
- ) {
158
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
159
-
160
- if (renderCtx.isError) {
161
- text.setText(renderToolError(getTextContent(result) || "Error", theme));
162
- return text;
163
- }
164
-
165
- const d = result.details;
166
- const output = getTextContent(result) || "searched";
167
- text.setText(
168
- renderDimPreview(output, theme, {
169
- header:
170
- d?._type === "grepResult"
171
- ? pluralize(d.matchCount, "match", "matches")
172
- : undefined,
173
- highlight: d?._type === "grepResult" ? d.pattern : undefined,
174
- }),
175
- );
176
- return text;
177
- },
178
- });
179
- }
package/src/tools/ls.ts DELETED
@@ -1,106 +0,0 @@
1
- import type {
2
- AgentToolUpdateCallback,
3
- ExtensionContext,
4
- LsToolInput,
5
- } from "@earendil-works/pi-coding-agent";
6
- import { FG_DIM, RST } from "../ansi.js";
7
- import { renderTree } from "../renderers.js";
8
- import type {
9
- LsParams,
10
- PiPrettyApi,
11
- RenderContextLike,
12
- ThemeLike,
13
- ToolFactory,
14
- ToolResultLike,
15
- } from "../types.js";
16
- import {
17
- fillToolBackground,
18
- getTextContent,
19
- renderToolError,
20
- setResultDetails,
21
- } from "../utils.js";
22
- import type { ToolContext } from "./context.js";
23
-
24
- export function registerLsTool(
25
- pi: PiPrettyApi,
26
- createLsTool: ToolFactory<LsToolInput>,
27
- ctx: ToolContext,
28
- ): void {
29
- const { cwd, sp, TextComponent } = ctx;
30
- const origLs = createLsTool(cwd);
31
-
32
- pi.registerTool({
33
- ...origLs,
34
- name: "ls",
35
- renderShell: "self",
36
-
37
- async execute(
38
- tid: string,
39
- params: LsParams,
40
- sig: AbortSignal | undefined,
41
- upd: AgentToolUpdateCallback<unknown> | undefined,
42
- toolCtx: ExtensionContext,
43
- ) {
44
- const result = (await origLs.execute(
45
- tid,
46
- params,
47
- sig,
48
- upd,
49
- toolCtx,
50
- )) as ToolResultLike;
51
- const textContent = getTextContent(result);
52
- const fp = params.path ?? cwd;
53
- const entryCount = textContent
54
- ? textContent.trim().split("\n").filter(Boolean).length
55
- : 0;
56
-
57
- setResultDetails(result, {
58
- _type: "lsResult",
59
- text: textContent ?? "",
60
- path: fp,
61
- entryCount,
62
- });
63
-
64
- return result;
65
- },
66
-
67
- renderCall(args: LsParams, theme: ThemeLike, renderCtx: RenderContextLike) {
68
- const fp = args.path ?? ".";
69
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
70
- text.setText(
71
- fillToolBackground(
72
- `${theme.fg("toolTitle", theme.bold("ls"))} ${theme.fg("accent", sp(fp))}`,
73
- ),
74
- );
75
- return text;
76
- },
77
-
78
- renderResult(
79
- result: ToolResultLike,
80
- _opt: unknown,
81
- theme: ThemeLike,
82
- renderCtx: RenderContextLike,
83
- ) {
84
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
85
-
86
- if (renderCtx.isError) {
87
- text.setText(renderToolError(getTextContent(result) || "Error", theme));
88
- return text;
89
- }
90
-
91
- const d = result.details as Record<string, unknown> | undefined;
92
- if (d?._type === "lsResult" && d.text) {
93
- const tree = renderTree(d.text as string, d.path as string);
94
- const info = `${FG_DIM}${d.entryCount} entries${RST}`;
95
- text.setText(fillToolBackground(` ${info}\n${tree}`));
96
- return text;
97
- }
98
-
99
- const output = getTextContent(result) || "listed";
100
- text.setText(
101
- fillToolBackground(` ${theme.fg("dim", output.slice(0, 120))}`),
102
- );
103
- return text;
104
- },
105
- });
106
- }
package/src/tools/read.ts DELETED
@@ -1,180 +0,0 @@
1
- import type {
2
- AgentToolUpdateCallback,
3
- ExtensionContext,
4
- ReadToolInput,
5
- } from "@earendil-works/pi-coding-agent";
6
-
7
- import { FG_DIM, RST } from "../ansi.js";
8
- import { MAX_PREVIEW_LINES } from "../config.js";
9
- import { fileIcon } from "../icons.js";
10
- import { renderFileContent } from "../renderers.js";
11
- import type {
12
- PiPrettyApi,
13
- ReadParams,
14
- RenderContextLike,
15
- ThemeLike,
16
- ToolFactory,
17
- ToolResultLike,
18
- } from "../types.js";
19
- import {
20
- fillToolBackground,
21
- getTextContent,
22
- humanSize,
23
- isImageContent,
24
- isTextContent,
25
- normalizeLineEndings,
26
- renderToolError,
27
- setResultDetails,
28
- } from "../utils.js";
29
- import type { ToolContext } from "./context.js";
30
-
31
- export function registerReadTool(
32
- pi: PiPrettyApi,
33
- createReadTool: ToolFactory<ReadToolInput>,
34
- ctx: ToolContext,
35
- ): void {
36
- const { cwd, sp, TextComponent } = ctx;
37
- const origRead = createReadTool(cwd);
38
-
39
- pi.registerTool({
40
- ...origRead,
41
- name: "read",
42
- // Full-width framing baked at termW(); default Box shell pads x by 1
43
- // and re-wraps at width-2, splitting every line into a padding row.
44
- renderShell: "self",
45
-
46
- async execute(
47
- tid: string,
48
- params: ReadParams,
49
- sig: AbortSignal | undefined,
50
- upd: AgentToolUpdateCallback<unknown> | undefined,
51
- toolCtx: ExtensionContext,
52
- ) {
53
- const result = (await origRead.execute(
54
- tid,
55
- params,
56
- sig,
57
- upd,
58
- toolCtx,
59
- )) as ToolResultLike;
60
-
61
- const fp = params.path ?? "";
62
- const offset = params.offset ?? 1;
63
-
64
- const imageBlock = result.content?.find(isImageContent);
65
- if (imageBlock) {
66
- setResultDetails(result, {
67
- _type: "readImage",
68
- filePath: fp,
69
- data: imageBlock.data,
70
- mimeType: imageBlock.mimeType ?? "image/png",
71
- });
72
- return result;
73
- }
74
-
75
- const textContent = getTextContent(result);
76
- if (textContent && fp) {
77
- const normalizedContent = normalizeLineEndings(textContent);
78
- const lineCount = normalizedContent.split("\n").length;
79
- setResultDetails(result, {
80
- _type: "readFile",
81
- filePath: fp,
82
- content: normalizedContent,
83
- offset,
84
- lineCount,
85
- });
86
- }
87
-
88
- return result;
89
- },
90
-
91
- renderCall(
92
- args: ReadParams,
93
- theme: ThemeLike,
94
- renderCtx: RenderContextLike,
95
- ) {
96
- const fp = args.path ?? "";
97
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
98
- const offset = args.offset
99
- ? ` ${theme.fg("muted", `from line ${args.offset}`)}`
100
- : "";
101
- const limit = args.limit
102
- ? ` ${theme.fg("muted", `(${args.limit} lines)`)}`
103
- : "";
104
- text.setText(
105
- fillToolBackground(
106
- `${theme.fg("toolTitle", theme.bold("read"))} ${theme.fg("accent", sp(fp))}${offset}${limit}`,
107
- ),
108
- );
109
- return text;
110
- },
111
-
112
- renderResult(
113
- result: ToolResultLike,
114
- _opt: unknown,
115
- theme: ThemeLike,
116
- renderCtx: RenderContextLike,
117
- ) {
118
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
119
-
120
- if (renderCtx.isError) {
121
- text.setText(renderToolError(getTextContent(result) || "Error", theme));
122
- return text;
123
- }
124
-
125
- const d = result.details as Record<string, unknown> | undefined;
126
-
127
- if (d?._type === "readImage") {
128
- const byteSize = Math.ceil(((d.data as string).length * 3) / 4);
129
- text.setText(
130
- fillToolBackground(
131
- ` ${fileIcon(d.filePath as string)}${FG_DIM}${d.mimeType ?? "image"} · ${humanSize(byteSize)}${RST}`,
132
- ),
133
- );
134
- return text;
135
- }
136
-
137
- if (d?._type === "readFile" && d.content) {
138
- const key = `read:${d.filePath}:${d.offset}:${d.lineCount}:${process.stdout.columns ?? 80}`;
139
- if (renderCtx.state._rk !== key) {
140
- renderCtx.state._rk = key;
141
- const info = `${FG_DIM}${d.lineCount} lines${RST}`;
142
- renderCtx.state._rt = fillToolBackground(` ${info}`);
143
-
144
- const maxShow = renderCtx.expanded
145
- ? (d.lineCount as number)
146
- : MAX_PREVIEW_LINES;
147
- renderFileContent(
148
- d.content as string,
149
- d.filePath as string,
150
- d.offset as number,
151
- maxShow,
152
- )
153
- .then((rendered: string) => {
154
- if (renderCtx.state._rk !== key) return;
155
- renderCtx.state._rt = fillToolBackground(
156
- ` ${info}\n${rendered}`,
157
- );
158
- renderCtx.invalidate();
159
- })
160
- .catch(() => {});
161
- }
162
- text.setText(
163
- renderCtx.state._rt ??
164
- fillToolBackground(` ${FG_DIM}${d.lineCount} lines${RST}`),
165
- );
166
- return text;
167
- }
168
-
169
- const fallback = result.content?.[0];
170
- const fallbackText =
171
- fallback && isTextContent(fallback) ? fallback.text : "read";
172
- text.setText(
173
- fillToolBackground(
174
- ` ${theme.fg("dim", String(fallbackText).slice(0, 120))}`,
175
- ),
176
- );
177
- return text;
178
- },
179
- });
180
- }
@@ -1,232 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import type {
3
- AgentToolUpdateCallback,
4
- ExtensionContext,
5
- ToolRenderResultOptions,
6
- WriteToolInput,
7
- } from "@earendil-works/pi-coding-agent";
8
-
9
- import { MAX_RENDER_LINES } from "../config.js";
10
- import { parseDiff } from "../diff.js";
11
- import {
12
- diffThemeCacheKey,
13
- renderSplit,
14
- resolveDiffColors,
15
- summarize,
16
- } from "../diff-render.js";
17
- import { hlBlock } from "../highlight.js";
18
- import { lang } from "../lang.js";
19
- import type {
20
- PiPrettyApi,
21
- RenderContextLike,
22
- ThemeLike,
23
- ToolFactory,
24
- ToolResultLike,
25
- WriteParams,
26
- WriteRenderState,
27
- } from "../types.js";
28
- import {
29
- fillToolBackground,
30
- getTextContent,
31
- isTextContent,
32
- renderToolError,
33
- setResultDetails,
34
- termW,
35
- } from "../utils.js";
36
- import type { ToolContext } from "./context.js";
37
-
38
- export function registerWriteTool(
39
- pi: PiPrettyApi,
40
- createWriteTool: ToolFactory<WriteToolInput>,
41
- ctx: ToolContext,
42
- trackInvalidator: (id: string, inv: () => void) => void,
43
- ): void {
44
- const { cwd, sp, TextComponent } = ctx;
45
- const origWrite = createWriteTool(cwd);
46
-
47
- pi.registerTool({
48
- ...origWrite,
49
- name: "write",
50
- renderShell: "self",
51
-
52
- async execute(
53
- tid: string,
54
- params: WriteParams,
55
- sig: AbortSignal | undefined,
56
- upd: AgentToolUpdateCallback<unknown> | undefined,
57
- toolCtx: ExtensionContext,
58
- ) {
59
- const fp = params.path ?? params.file_path ?? "";
60
- let old: string | null = null;
61
- try {
62
- if (fp && existsSync(fp)) old = readFileSync(fp, "utf-8");
63
- } catch {
64
- old = null;
65
- }
66
-
67
- const result = (await origWrite.execute(
68
- tid,
69
- params as unknown as Parameters<typeof origWrite.execute>[1],
70
- sig,
71
- upd,
72
- toolCtx,
73
- )) as ToolResultLike;
74
- const content = params.content ?? "";
75
-
76
- if (old !== null && old !== content) {
77
- const diff = parseDiff(old, content);
78
- setResultDetails(result, {
79
- _type: "diff",
80
- summary: summarize(diff.added, diff.removed),
81
- oldContent: old,
82
- newContent: content,
83
- language: lang(fp),
84
- });
85
- } else if (old === null) {
86
- setResultDetails(result, {
87
- _type: "new",
88
- lines: content ? content.split("\n").length : 0,
89
- content,
90
- filePath: fp,
91
- });
92
- } else {
93
- setResultDetails(result, { _type: "noChange" });
94
- }
95
- return result;
96
- },
97
-
98
- renderCall(
99
- args: WriteParams,
100
- theme: ThemeLike,
101
- renderCtx: RenderContextLike<WriteRenderState>,
102
- ) {
103
- const fp = args?.path ?? args?.file_path ?? "";
104
- const isNew = !fp || !existsSync(fp);
105
- const label = isNew ? "create" : "write";
106
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
107
- const hdr = `${theme.fg("toolTitle", theme.bold(label))} ${theme.fg("accent", sp(fp))}`;
108
-
109
- if (args?.content && isNew) {
110
- const previewKey = `create:${diffThemeCacheKey(theme)}:${fp}:${String(args.content).length}`;
111
- if (renderCtx.state._previewKey !== previewKey) {
112
- renderCtx.state._previewKey = previewKey;
113
- renderCtx.state._previewText = hdr;
114
- const lg = lang(fp);
115
- hlBlock(String(args.content), lg)
116
- .then((lines) => {
117
- if (renderCtx.state._previewKey !== previewKey) return;
118
- const maxShow = renderCtx.expanded ? lines.length : 16;
119
- const preview = lines.slice(0, maxShow).join("\n");
120
- const rem = lines.length - maxShow;
121
- let out = `${hdr}\n\n${preview}`;
122
- if (rem > 0)
123
- out += `\n${theme.fg("muted", `… (${rem} more lines, ${lines.length} total)`)}`;
124
- renderCtx.state._previewText = out;
125
- renderCtx.invalidate();
126
- })
127
- .catch(() => {});
128
- }
129
- text.setText(renderCtx.state._previewText ?? hdr);
130
- return text;
131
- }
132
-
133
- text.setText(fillToolBackground(hdr));
134
- return text;
135
- },
136
-
137
- renderResult(
138
- result: ToolResultLike,
139
- _opt: ToolRenderResultOptions,
140
- theme: ThemeLike,
141
- renderCtx: RenderContextLike<WriteRenderState>,
142
- ) {
143
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
144
- if (renderCtx.isError) {
145
- text.setText(renderToolError(getTextContent(result) || "Error", theme));
146
- return text;
147
- }
148
- const d = result.details as Record<string, unknown> | undefined;
149
-
150
- if (d?._type === "diff") {
151
- const key = `wd:${diffThemeCacheKey(theme)}:${termW()}:${d.summary}:${(d.newContent as string).length}:${d.language ?? ""}`;
152
- if (renderCtx.toolCallId)
153
- trackInvalidator(renderCtx.toolCallId, renderCtx.invalidate);
154
- if (renderCtx.state._wdk !== key) {
155
- renderCtx.state._wdk = key;
156
- renderCtx.state._wdt = ` ${d.summary}\n${theme.fg("muted", " rendering diff…")}`;
157
- const dc = resolveDiffColors(theme);
158
- const diff = parseDiff(
159
- d.oldContent as string,
160
- d.newContent as string,
161
- );
162
- renderSplit(
163
- diff,
164
- d.language as string | undefined,
165
- MAX_RENDER_LINES,
166
- dc,
167
- )
168
- .then((rendered) => {
169
- if (renderCtx.state._wdk !== key) return;
170
- renderCtx.state._wdt = ` ${d.summary}\n${rendered}`;
171
- renderCtx.invalidate();
172
- })
173
- .catch(() => {
174
- if (renderCtx.state._wdk !== key) return;
175
- renderCtx.state._wdt = ` ${d.summary}`;
176
- renderCtx.invalidate();
177
- });
178
- }
179
- text.setText(renderCtx.state._wdt ?? ` ${d.summary}`);
180
- return text;
181
- }
182
-
183
- if (d?._type === "noChange") {
184
- text.setText(
185
- fillToolBackground(` ${theme.fg("muted", "✓ no changes")}`),
186
- );
187
- return text;
188
- }
189
-
190
- if (d?._type === "new") {
191
- const {
192
- lines: lineCount,
193
- content: rawContent,
194
- filePath: fp,
195
- } = d as { lines: number; content: string; filePath: string };
196
- const base = ` ${theme.fg("success", `✓ new file (${lineCount} lines)`)}`;
197
- const pk = `nf:${diffThemeCacheKey(theme)}:${fp}:${lineCount}`;
198
- if (renderCtx.state._nfk !== pk) {
199
- renderCtx.state._nfk = pk;
200
- renderCtx.state._nft = base;
201
- if (rawContent) {
202
- hlBlock(rawContent, lang(fp))
203
- .then((hlLines) => {
204
- if (renderCtx.state._nfk !== pk) return;
205
- const maxShow = renderCtx.expanded ? hlLines.length : 12;
206
- const preview = hlLines.slice(0, maxShow).join("\n");
207
- const rem = hlLines.length - maxShow;
208
- let out = `${base}\n${preview}`;
209
- if (rem > 0)
210
- out += `\n${theme.fg("muted", ` … ${rem} more lines`)}`;
211
- renderCtx.state._nft = out;
212
- renderCtx.invalidate();
213
- })
214
- .catch(() => {});
215
- }
216
- }
217
- text.setText(renderCtx.state._nft ?? base);
218
- return text;
219
- }
220
-
221
- const fallback = result.content?.[0];
222
- const fallbackText =
223
- fallback && isTextContent(fallback) ? fallback.text : "written";
224
- text.setText(
225
- fillToolBackground(
226
- ` ${theme.fg("dim", String(fallbackText).slice(0, 120))}`,
227
- ),
228
- );
229
- return text;
230
- },
231
- });
232
- }