@xynogen/pix-find 0.1.13 → 0.1.15
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/README.md +2 -2
- package/package.json +2 -2
- package/src/find.test.ts +105 -1
- package/src/find.ts +31 -4
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@ Replaces Pi's default `find` tool with an enhanced version backed by `pix-pretty
|
|
|
8
8
|
|
|
9
9
|
## Auto-collapse
|
|
10
10
|
|
|
11
|
-
After a configurable delay (default 10 seconds)
|
|
11
|
+
After a configurable delay (default 10 seconds), completed output collapses to a row such as `✓ find **/*.ts · 24 files`. Structured failures use a compact `✗` row after the delay. Expanding restores the existing bounded result list or exact diagnostic without restarting the elapsed timer. This is controlled via the `collapse` section of `~/.pi/agent/pix.json`:
|
|
12
12
|
|
|
13
13
|
```jsonc
|
|
14
14
|
{
|
|
15
15
|
"collapse": {
|
|
16
16
|
"enabled": true,
|
|
17
|
-
"
|
|
17
|
+
"delaySec": 10,
|
|
18
18
|
"tools": { "find": true }
|
|
19
19
|
}
|
|
20
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-find",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Pi tool — glob file search",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@xynogen/pix-pretty": "^1.7.
|
|
37
|
+
"@xynogen/pix-pretty": "^1.7.20",
|
|
38
38
|
"@xynogen/pix-data": "^0.3.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
package/src/find.test.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import type { CursorStore, FffState } from "@xynogen/pix-pretty/fff";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
PiPrettyApi,
|
|
5
|
+
RenderContextLike,
|
|
6
|
+
TextComponentCtor,
|
|
7
|
+
ThemeLike,
|
|
8
|
+
} from "@xynogen/pix-pretty/types";
|
|
4
9
|
import { applyFindDefaults, DEFAULT_FIND_LIMIT, registerFindTool } from "./find";
|
|
5
10
|
|
|
6
11
|
class MockTextComponent {
|
|
@@ -58,4 +63,103 @@ describe("registerFindTool", () => {
|
|
|
58
63
|
);
|
|
59
64
|
expect(tools).toEqual(["find"]);
|
|
60
65
|
});
|
|
66
|
+
|
|
67
|
+
it("restores result paths when an elapsed card is expanded", () => {
|
|
68
|
+
const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
|
|
69
|
+
const mockPi: PiPrettyApi = {
|
|
70
|
+
registerTool(tool: unknown) {
|
|
71
|
+
Object.assign(registered, tool);
|
|
72
|
+
},
|
|
73
|
+
registerCommand() {},
|
|
74
|
+
on() {},
|
|
75
|
+
};
|
|
76
|
+
registerFindTool(
|
|
77
|
+
mockPi,
|
|
78
|
+
() => ({ execute: async () => ({ content: [], details: undefined }) }),
|
|
79
|
+
{
|
|
80
|
+
cwd: process.cwd(),
|
|
81
|
+
sp: (p: string) => p,
|
|
82
|
+
TextComponent: MockTextComponent as unknown as TextComponentCtor,
|
|
83
|
+
fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
|
|
84
|
+
cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
const theme: ThemeLike = {
|
|
88
|
+
fg: (_key: string, value: string) => value,
|
|
89
|
+
bold: (value: string) => value,
|
|
90
|
+
};
|
|
91
|
+
const result = registered.renderResult?.(
|
|
92
|
+
{
|
|
93
|
+
content: [{ type: "text", text: "src/one.ts\nsrc/two.ts" }],
|
|
94
|
+
details: {
|
|
95
|
+
_type: "findResult",
|
|
96
|
+
text: "src/one.ts\nsrc/two.ts",
|
|
97
|
+
pattern: "**/*.ts",
|
|
98
|
+
matchCount: 2,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
undefined,
|
|
102
|
+
theme,
|
|
103
|
+
{
|
|
104
|
+
expanded: true,
|
|
105
|
+
isError: false,
|
|
106
|
+
invalidate: () => {},
|
|
107
|
+
state: { collapsed: true },
|
|
108
|
+
} as unknown as RenderContextLike,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(result?.getText()).toContain("src/one.ts");
|
|
112
|
+
expect(result?.getText()).toContain("src/two.ts");
|
|
113
|
+
expect(result?.getText()).not.toContain("2 files");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("collapses structured errors and restores the exact diagnostic on expansion", () => {
|
|
117
|
+
const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
|
|
118
|
+
const mockPi: PiPrettyApi = {
|
|
119
|
+
registerTool(tool: unknown) {
|
|
120
|
+
Object.assign(registered, tool);
|
|
121
|
+
},
|
|
122
|
+
registerCommand() {},
|
|
123
|
+
on() {},
|
|
124
|
+
};
|
|
125
|
+
registerFindTool(
|
|
126
|
+
mockPi,
|
|
127
|
+
() => ({ execute: async () => ({ content: [], details: undefined }) }),
|
|
128
|
+
{
|
|
129
|
+
cwd: process.cwd(),
|
|
130
|
+
sp: (p: string) => p,
|
|
131
|
+
TextComponent: MockTextComponent as unknown as TextComponentCtor,
|
|
132
|
+
fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
|
|
133
|
+
cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
const theme: ThemeLike = {
|
|
137
|
+
fg: (_key: string, value: string) => value,
|
|
138
|
+
bold: (value: string) => value,
|
|
139
|
+
};
|
|
140
|
+
const diagnostic = "Invalid glob pattern: [";
|
|
141
|
+
const result = {
|
|
142
|
+
content: [{ type: "text", text: diagnostic }],
|
|
143
|
+
details: {
|
|
144
|
+
_type: "findResult",
|
|
145
|
+
text: diagnostic,
|
|
146
|
+
pattern: "[",
|
|
147
|
+
path: "src",
|
|
148
|
+
matchCount: 0,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
const render = (state: Record<string, unknown>, expanded = false) =>
|
|
152
|
+
registered
|
|
153
|
+
.renderResult?.(result, { isPartial: false }, theme, {
|
|
154
|
+
expanded,
|
|
155
|
+
isError: true,
|
|
156
|
+
invalidate: () => {},
|
|
157
|
+
state,
|
|
158
|
+
} as unknown as RenderContextLike)
|
|
159
|
+
?.getText() ?? "";
|
|
160
|
+
|
|
161
|
+
expect(render({ timer: 1 })).toContain(diagnostic);
|
|
162
|
+
expect(render({ collapsed: true })).toContain("✗ find [ in src · failed");
|
|
163
|
+
expect(render({ collapsed: true }, true)).toContain(diagnostic);
|
|
164
|
+
});
|
|
61
165
|
});
|
package/src/find.ts
CHANGED
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
appendNotices,
|
|
19
19
|
fillToolBackground,
|
|
20
20
|
getTextContent,
|
|
21
|
+
hideCollapsedToolCall,
|
|
21
22
|
makeTextResult,
|
|
23
|
+
renderCollapsedToolRow,
|
|
22
24
|
renderDimPreview,
|
|
23
25
|
renderToolError,
|
|
24
26
|
setResultDetails,
|
|
@@ -80,6 +82,7 @@ export function registerFindTool(
|
|
|
80
82
|
_type: "findResult",
|
|
81
83
|
text: textContent,
|
|
82
84
|
pattern: effectiveParams.pattern,
|
|
85
|
+
path: effectiveParams.path,
|
|
83
86
|
matchCount: trimmed.length,
|
|
84
87
|
});
|
|
85
88
|
}
|
|
@@ -97,6 +100,7 @@ export function registerFindTool(
|
|
|
97
100
|
_type: "findResult",
|
|
98
101
|
text: textContent,
|
|
99
102
|
pattern: params.pattern,
|
|
103
|
+
path: params.path,
|
|
100
104
|
matchCount,
|
|
101
105
|
});
|
|
102
106
|
|
|
@@ -107,6 +111,12 @@ export function registerFindTool(
|
|
|
107
111
|
const pattern = args.pattern ?? "";
|
|
108
112
|
const path = args.path ? ` ${theme.fg("muted", `in ${sp(args.path)}`)}` : "";
|
|
109
113
|
const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
|
|
114
|
+
if (
|
|
115
|
+
hideCollapsedToolCall(renderCtx.state as CollapseState, renderCtx.expanded, (value) =>
|
|
116
|
+
text.setText(value),
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
return text;
|
|
110
120
|
text.setText(
|
|
111
121
|
fillToolBackground(
|
|
112
122
|
`${theme.fg("toolTitle", theme.bold("find"))} ${theme.fg("accent", pattern)}${path}`,
|
|
@@ -122,19 +132,36 @@ export function registerFindTool(
|
|
|
122
132
|
renderCtx: RenderContextLike,
|
|
123
133
|
) {
|
|
124
134
|
const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
|
|
135
|
+
const d = result.details;
|
|
136
|
+
const isPartial = _opt?.isPartial === true;
|
|
137
|
+
const structuredError = renderCtx.isError && d?._type === "findResult";
|
|
125
138
|
|
|
126
|
-
if (renderCtx.isError) {
|
|
139
|
+
if (renderCtx.isError && (!structuredError || isPartial)) {
|
|
127
140
|
text.setText(renderToolError(getTextContent(result) || "Error", theme));
|
|
128
141
|
return text;
|
|
129
142
|
}
|
|
130
143
|
|
|
131
144
|
// Auto-collapse: show summary line after delay
|
|
132
145
|
const cs = renderCtx.state as CollapseState;
|
|
133
|
-
if (tickCollapse("find", cs, renderCtx.invalidate)) {
|
|
134
|
-
const d = result.details;
|
|
146
|
+
if (!isPartial && tickCollapse("find", cs, renderCtx.invalidate, renderCtx.expanded)) {
|
|
135
147
|
const summary =
|
|
136
148
|
d?._type === "findResult" && d.matchCount != null ? `${d.matchCount} files` : "found";
|
|
137
|
-
|
|
149
|
+
const target = d?._type === "findResult" ? d.pattern : "";
|
|
150
|
+
const scope = d?._type === "findResult" && d.path ? ` in ${sp(d.path)}` : "";
|
|
151
|
+
text.setText(
|
|
152
|
+
renderCollapsedToolRow(
|
|
153
|
+
theme,
|
|
154
|
+
"find",
|
|
155
|
+
`${target}${scope}`,
|
|
156
|
+
renderCtx.isError ? "failed" : summary,
|
|
157
|
+
renderCtx.isError ? "error" : "success",
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
return text;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (renderCtx.isError) {
|
|
164
|
+
text.setText(renderToolError(getTextContent(result) || "Error", theme));
|
|
138
165
|
return text;
|
|
139
166
|
}
|
|
140
167
|
|