@krosskinetic/pi-zg 0.1.0 → 0.1.2
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 +84 -28
- package/extensions/pi-zg.ts +140 -73
- package/package.json +17 -4
package/README.md
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
**WORK IN PROGRESS / ALPHA**
|
|
2
2
|
|
|
3
|
-
# pi-zg
|
|
3
|
+
# @krosskinetic/pi-zg
|
|
4
4
|
|
|
5
5
|
A [Pi](https://github.com/earendil-works/pi-mono) package that natively integrates
|
|
6
|
-
[zvec-grep](https://github.com/) (`zg`) semantic code search into Pi: it
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
(`/zg-
|
|
10
|
-
it does not install or bundle zvec-grep itself.
|
|
6
|
+
[zvec-grep](https://github.com/) (`zg`) semantic code search into Pi: it offers
|
|
7
|
+
to build a missing index interactively, and gives the agent four tools
|
|
8
|
+
(`zg_search`, `zg_rg`, `zg_index`, `zg_status`) plus three commands
|
|
9
|
+
(`/zg-settings`, `/zg-status`, `/zg-index`). It calls an existing local `zg` CLI;
|
|
11
10
|
|
|
12
11
|
## Prerequisites
|
|
13
12
|
|
|
14
13
|
Install `@zvec/zvec-grep` separately and ensure `zg` is on `PATH`.
|
|
14
|
+
This extension invokes that local CLI; it does not install, bundle, or
|
|
15
|
+
configure zvec-grep for you.
|
|
15
16
|
|
|
16
17
|
## Install into Pi
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
|
-
pi install npm
|
|
20
|
+
pi install npm:@krosskinetic/pi-zg
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
For local development, from a checkout of this repo:
|
|
@@ -25,22 +26,33 @@ For local development, from a checkout of this repo:
|
|
|
25
26
|
pi -e .
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
##
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
1. Install and configure `zg`, then verify that `zg version` works from your
|
|
32
|
+
project directory.
|
|
33
|
+
2. Install this package and start Pi in the project you want to search.
|
|
34
|
+
3. At session start, pi-zg checks whether `zg` is available and whether the
|
|
35
|
+
project has an index. Each `zg_search` refreshes the index first, so
|
|
36
|
+
results are never stale — no server daemon is used.
|
|
37
|
+
4. Ask Pi a natural-language code-search question. Its `zg_search` tool
|
|
38
|
+
searches the index. If the project has not been indexed, pi-zg offers to
|
|
39
|
+
build one interactively.
|
|
29
40
|
|
|
30
|
-
|
|
41
|
+
For example, ask: “Where is authentication token refresh implemented?” For an
|
|
42
|
+
exact identifier, literal, or regex search, Pi can use `zg_rg` instead.
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
configured via `zg install`) may also use. This extension:
|
|
44
|
+
## What it does
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
(`--no-zg-autostart` disables this). `zg server on` is idempotent, so this
|
|
37
|
-
is safe to run every session.
|
|
38
|
-
- **Never auto-stops it** — since it's shared, stopping it could break other
|
|
39
|
-
tools relying on it. Use `/zg-server off` to stop it explicitly.
|
|
46
|
+
### Always-fresh direct searches
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
This extension runs every search in **direct mode and refreshes the index before
|
|
49
|
+
answering** (`zg query --mode direct --refresh wait`). It does **not** use or manage
|
|
50
|
+
zg's shared server daemon — there is no background process to keep alive, start,
|
|
51
|
+
or shut down, so nothing can linger as an orphan between sessions.
|
|
52
|
+
|
|
53
|
+
Because each semantic search rebuilds any stale parts of the index first, results
|
|
54
|
+
are never stale: after editing a file, the next `zg_search` reflects the change.
|
|
55
|
+
This also means no index auto-refresh daemon needs to run at all.
|
|
44
56
|
|
|
45
57
|
### Tools (LLM-callable)
|
|
46
58
|
|
|
@@ -54,33 +66,77 @@ project has an initial index.
|
|
|
54
66
|
does not require an index.
|
|
55
67
|
- **`zg_index`** — build, rebuild, or drop the persistent index. Gated by
|
|
56
68
|
prompt guidelines so the agent only uses it when the user explicitly asks.
|
|
69
|
+
- **`zg_status`** — report zg version and the current project's index status.
|
|
70
|
+
Reads pi's cached zg state, so no extra `zg` subprocess is spawned for it.
|
|
71
|
+
|
|
72
|
+
`zg_search` accepts a natural-language `query` and an optional `limit` of
|
|
73
|
+
1–100 results (zg defaults to 7). `zg_rg` accepts a regex `pattern`, optional
|
|
74
|
+
paths, `fixedString` for literal matching, and one `glob` filter. Its output
|
|
75
|
+
and the output of the other tools are capped at 2,000 lines or 50 KB.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#### Choosing a search tool
|
|
79
|
+
|
|
80
|
+
Use the right tool for the kind of query, and don't reach for grep when semantic
|
|
81
|
+
search is the better fit:
|
|
82
|
+
|
|
83
|
+
- **`zg_search`** — find code by **meaning**. Use when you don't know the exact
|
|
84
|
+
identifiers or wording (“where is token refresh handled”, “how does the cache
|
|
85
|
+
layer work”). Keyword grep would miss these.
|
|
86
|
+
- **`zg_rg`** — find code by **exact text**: a known identifier, string literal, or
|
|
87
|
+
regex, honoring the project's ignore/glob rules.
|
|
88
|
+
- **Pi's built-in `grep`** — a quick literal scan when you don't need zg's
|
|
89
|
+
ignore rules or rg features.
|
|
57
90
|
|
|
91
|
+
In short: concept → `zg_search`; exact token/regex → `zg_rg`; everything is
|
|
92
|
+
refreshed before answering, so you always search current code.
|
|
58
93
|
### Commands (human-invoked)
|
|
59
94
|
|
|
60
|
-
- **`/zg-
|
|
95
|
+
- **`/zg-settings`** — interactive configuration for zg's default embedding
|
|
96
|
+
model, embedding device, and provider API key.
|
|
97
|
+
- **`/zg-status`** — zg version and index status/coverage.
|
|
61
98
|
- **`/zg-index [--rebuild|--drop]`** — build, rebuild, or drop the index
|
|
62
99
|
directly, without going through the LLM. Confirms before `--drop`.
|
|
63
|
-
|
|
64
|
-
|
|
100
|
+
|
|
101
|
+
### Settings (`/zg-settings`)
|
|
102
|
+
|
|
103
|
+
`/zg-settings` exposes the configuration that most directly affects Pi search:
|
|
104
|
+
|
|
105
|
+
- **Default embedding model** — sets zg's persistent default for newly built
|
|
106
|
+
indexes, for example `local/potion-code-16m-v2` or
|
|
107
|
+
`qwen/text-embedding-v4`. Existing indexes keep their recorded embedding
|
|
108
|
+
schema.
|
|
109
|
+
- **Embedding device** — configures `auto`, `cpu`, `metal`, `vulkan`, or
|
|
110
|
+
`cuda` for a specified local model.
|
|
111
|
+
- **Provider API key** — saves credentials for a named embedding provider via
|
|
112
|
+
`zg config provider set`. The value is passed directly to zg and is not
|
|
113
|
+
shown in Pi notifications.
|
|
114
|
+
|
|
115
|
+
For advanced index-selection rules, remote endpoints, authentication, and
|
|
116
|
+
other less-common settings, use the underlying `zg` CLI directly.
|
|
65
117
|
|
|
66
118
|
### Status
|
|
67
119
|
|
|
68
|
-
The footer shows `
|
|
69
|
-
|
|
120
|
+
The footer shows `index ✓/✗` (colored via the active theme), refreshed at
|
|
121
|
+
session start and at the start of every turn.
|
|
70
122
|
|
|
71
123
|
### Flags
|
|
72
124
|
|
|
73
|
-
- `--no-zg-autostart` — disable automatically starting the shared server.
|
|
74
125
|
- `--no-zg-onboard` — disable the interactive "build an index?" offer;
|
|
75
126
|
`zg_search` fails with a manual-fix message instead (useful for
|
|
76
127
|
non-interactive/scripted `pi -p` runs).
|
|
77
128
|
|
|
129
|
+
Pass these when launching Pi, for example:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pi --no-zg-onboard
|
|
133
|
+
```
|
|
134
|
+
|
|
78
135
|
## Non-goals
|
|
79
136
|
|
|
80
137
|
- Does not register zg's MCP server as an actual MCP tool source inside Pi
|
|
81
138
|
— Pi extensions have no MCP-client API, so integration stays CLI-based
|
|
82
|
-
(`pi.exec`),
|
|
83
|
-
|
|
139
|
+
(`pi.exec`), stateful rather than re-deriving status via subprocess spawns
|
|
140
|
+
before every call.
|
|
84
141
|
- Does not override Pi's built-in `grep` tool. `zg_search`/`zg_rg` are
|
|
85
142
|
purely additive.
|
|
86
|
-
- Does not auto-stop the shared `zg` server.
|
package/extensions/pi-zg.ts
CHANGED
|
@@ -9,10 +9,9 @@ import { Type } from "typebox";
|
|
|
9
9
|
* pi-zg: a native Pi integration for the zvec-grep (`zg`) CLI.
|
|
10
10
|
*
|
|
11
11
|
* Beyond wrapping `zg query`/`zg index`, this extension:
|
|
12
|
-
* -
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - Caches zg's availability/server/index state per session (refreshed at
|
|
12
|
+
* - Runs every search in direct mode, refreshing the index before answering so
|
|
13
|
+
* results are never stale (no background server daemon involved).
|
|
14
|
+
* - Caches zg's availability and index state per session (refreshed at
|
|
16
15
|
* session_start and each turn_start) instead of re-deriving it with
|
|
17
16
|
* extra `zg` subprocess spawns before every tool call.
|
|
18
17
|
* - Offers to build a missing index interactively instead of just failing.
|
|
@@ -25,15 +24,12 @@ import { Type } from "typebox";
|
|
|
25
24
|
|
|
26
25
|
const STATUS_KEY = "pi-zg";
|
|
27
26
|
const DEFAULT_LOCAL_MODEL = "local/potion-code-16m-v2";
|
|
28
|
-
|
|
29
27
|
interface ZgState {
|
|
30
28
|
/** Whether refreshZgState has run at least once this session. */
|
|
31
29
|
checked: boolean;
|
|
32
30
|
/** Whether `zg` is on PATH. */
|
|
33
31
|
available: boolean;
|
|
34
32
|
version?: string;
|
|
35
|
-
/** Whether the shared zg server daemon is up and ready. */
|
|
36
|
-
serverRunning: boolean;
|
|
37
33
|
/** Whether the current project has a ready index. */
|
|
38
34
|
indexed: boolean;
|
|
39
35
|
/** Whether the user already declined the "build an index?" offer this session. */
|
|
@@ -44,7 +40,6 @@ function createZgState(): ZgState {
|
|
|
44
40
|
return {
|
|
45
41
|
checked: false,
|
|
46
42
|
available: false,
|
|
47
|
-
serverRunning: false,
|
|
48
43
|
indexed: false,
|
|
49
44
|
declinedIndexOffer: false,
|
|
50
45
|
};
|
|
@@ -65,12 +60,12 @@ function renderStatus(ctx: ExtensionContext, state: ZgState) {
|
|
|
65
60
|
ctx.ui.setStatus(STATUS_KEY, theme.fg("dim", "zg: not found"));
|
|
66
61
|
return;
|
|
67
62
|
}
|
|
68
|
-
const server = state.serverRunning ? theme.fg("success", "server\u25cf") : theme.fg("dim", "server\u25cb");
|
|
69
63
|
const index = state.indexed ? theme.fg("success", "index\u2713") : theme.fg("warning", "index\u2717");
|
|
70
|
-
ctx.ui.setStatus(STATUS_KEY,
|
|
64
|
+
ctx.ui.setStatus(STATUS_KEY, index);
|
|
71
65
|
}
|
|
72
66
|
|
|
73
|
-
|
|
67
|
+
|
|
68
|
+
/** Refresh cached zg availability and index state, then update the footer. */
|
|
74
69
|
async function refreshZgState(
|
|
75
70
|
pi: ExtensionAPI,
|
|
76
71
|
ctx: ExtensionContext,
|
|
@@ -83,17 +78,12 @@ async function refreshZgState(
|
|
|
83
78
|
state.version = state.available ? version.stdout.trim() : undefined;
|
|
84
79
|
|
|
85
80
|
if (!state.available) {
|
|
86
|
-
state.serverRunning = false;
|
|
87
81
|
state.indexed = false;
|
|
88
82
|
renderStatus(ctx, state);
|
|
89
83
|
return state;
|
|
90
84
|
}
|
|
91
85
|
|
|
92
|
-
const
|
|
93
|
-
execZg(pi, ["server", "status", "--check-ready"], ctx, { signal, timeout: 5_000 }),
|
|
94
|
-
execZg(pi, ["status", "--check-ready"], ctx, { signal, timeout: 5_000 }),
|
|
95
|
-
]);
|
|
96
|
-
state.serverRunning = server.code === 0;
|
|
86
|
+
const status = await execZg(pi, ["status", "--check-ready"], ctx, { signal, timeout: 5_000 });
|
|
97
87
|
state.indexed = status.code === 0;
|
|
98
88
|
|
|
99
89
|
renderStatus(ctx, state);
|
|
@@ -114,6 +104,83 @@ function parseHitCount(output: string): number | undefined {
|
|
|
114
104
|
return match ? Number(match[1]) : undefined;
|
|
115
105
|
}
|
|
116
106
|
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async function configureDefaultModel(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
110
|
+
const model = await ctx.ui.input("Default embedding model", "e.g. local/potion-code-16m-v2 or qwen/text-embedding-v4");
|
|
111
|
+
if (!model?.trim()) return;
|
|
112
|
+
const result = await execZg(pi, ["config", "model", "set", model.trim(), "--default"], ctx);
|
|
113
|
+
if (result.code !== 0) {
|
|
114
|
+
ctx.ui.notify("Could not set the embedding model. Inspect zg configuration in a terminal for details.", "error");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
ctx.ui.notify(`Default embedding model set to ${model.trim()}. Existing indexes keep their current schema.`, "info");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function configureEmbeddingDevice(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
121
|
+
const model = await ctx.ui.input("Model to configure", "e.g. local/potion-code-16m-v2");
|
|
122
|
+
if (!model?.trim()) return;
|
|
123
|
+
const device = await ctx.ui.select("Embedding device", ["auto", "cpu", "metal", "vulkan", "cuda", "Cancel"]);
|
|
124
|
+
if (!device || device === "Cancel") return;
|
|
125
|
+
const result = await execZg(pi, ["config", "model", "set", model.trim(), "--device", device], ctx);
|
|
126
|
+
if (result.code !== 0) {
|
|
127
|
+
ctx.ui.notify("Could not set the embedding device. Inspect zg configuration in a terminal for details.", "error");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
ctx.ui.notify(`Embedding device for ${model.trim()} set to ${device}.`, "info");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function configureProviderKey(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
134
|
+
const provider = await ctx.ui.input("Embedding provider", "e.g. qwen");
|
|
135
|
+
if (!provider?.trim()) return;
|
|
136
|
+
const apiKey = await ctx.ui.input("API key", "Paste the provider API key");
|
|
137
|
+
if (!apiKey?.trim()) return;
|
|
138
|
+
const result = await execZg(pi, ["config", "provider", "set", provider.trim(), "--api-key", apiKey.trim()], ctx);
|
|
139
|
+
if (result.code !== 0) {
|
|
140
|
+
ctx.ui.notify("Could not save the provider API key. Inspect zg configuration in a terminal for details.", "error");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
ctx.ui.notify(`Credentials saved for ${provider.trim()}.`, "info");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function openZgSettings(pi: ExtensionAPI, ctx: ExtensionContext, state: ZgState): Promise<void> {
|
|
147
|
+
if (!state.checked) await refreshZgState(pi, ctx, state);
|
|
148
|
+
|
|
149
|
+
while (true) {
|
|
150
|
+
const choice = await ctx.ui.select("zg settings", [
|
|
151
|
+
"Default embedding model",
|
|
152
|
+
"Embedding device",
|
|
153
|
+
"Provider API key",
|
|
154
|
+
"View active settings",
|
|
155
|
+
"Done",
|
|
156
|
+
]);
|
|
157
|
+
if (!choice || choice === "Done") return;
|
|
158
|
+
|
|
159
|
+
switch (choice) {
|
|
160
|
+
case "Default embedding model":
|
|
161
|
+
await configureDefaultModel(pi, ctx);
|
|
162
|
+
break;
|
|
163
|
+
case "Embedding device":
|
|
164
|
+
await configureEmbeddingDevice(pi, ctx);
|
|
165
|
+
break;
|
|
166
|
+
case "Provider API key":
|
|
167
|
+
await configureProviderKey(pi, ctx);
|
|
168
|
+
break;
|
|
169
|
+
case "View active settings": {
|
|
170
|
+
await refreshZgState(pi, ctx, state);
|
|
171
|
+
const detail = await execZg(pi, ["status"], ctx);
|
|
172
|
+
ctx.ui.notify(
|
|
173
|
+
[
|
|
174
|
+
(detail.stdout || detail.stderr).trim(),
|
|
175
|
+
].join("\n"),
|
|
176
|
+
state.indexed ? "info" : "warning",
|
|
177
|
+
);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
117
184
|
/**
|
|
118
185
|
* Lazily offer to build a missing index right where the LLM discovered it's
|
|
119
186
|
* missing, instead of dead-ending with a "go run this command yourself" error.
|
|
@@ -140,7 +207,7 @@ async function offerToBuildIndex(
|
|
|
140
207
|
}
|
|
141
208
|
|
|
142
209
|
ctx.ui.setWorkingMessage("Building zg index...");
|
|
143
|
-
let result = await execZg(pi, ["index"], ctx, { signal, timeout: 300_000 });
|
|
210
|
+
let result = await execZg(pi, ["index", "--mode", "direct"], ctx, { signal, timeout: 300_000 });
|
|
144
211
|
|
|
145
212
|
if (result.code !== 0 && /embedding/i.test(result.stderr)) {
|
|
146
213
|
// No default embedding model configured yet -- offer to set one and retry.
|
|
@@ -157,7 +224,10 @@ async function offerToBuildIndex(
|
|
|
157
224
|
}
|
|
158
225
|
if (model) {
|
|
159
226
|
await execZg(pi, ["config", "model", "set", model, "--default"], ctx, { signal });
|
|
160
|
-
result = await execZg(pi, ["index", "--embedding", model], ctx, {
|
|
227
|
+
result = await execZg(pi, ["index", "--mode", "direct", "--embedding", model], ctx, {
|
|
228
|
+
signal,
|
|
229
|
+
timeout: 300_000,
|
|
230
|
+
});
|
|
161
231
|
}
|
|
162
232
|
}
|
|
163
233
|
|
|
@@ -176,29 +246,16 @@ async function offerToBuildIndex(
|
|
|
176
246
|
export default function (pi: ExtensionAPI) {
|
|
177
247
|
const state = createZgState();
|
|
178
248
|
|
|
179
|
-
pi.registerFlag("no-zg-autostart", {
|
|
180
|
-
description: "Disable automatically starting the shared zg server at session start",
|
|
181
|
-
type: "boolean",
|
|
182
|
-
default: false,
|
|
183
|
-
});
|
|
184
249
|
pi.registerFlag("no-zg-onboard", {
|
|
185
250
|
description: "Disable the interactive offer to build a missing zg index; fail with a manual-fix message instead",
|
|
186
251
|
type: "boolean",
|
|
187
252
|
default: false,
|
|
188
253
|
});
|
|
189
254
|
|
|
190
|
-
|
|
191
|
-
|
|
255
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
256
|
+
await refreshZgState(pi, ctx, state);
|
|
257
|
+
});
|
|
192
258
|
|
|
193
|
-
if (!pi.getFlag("no-zg-autostart") && state.available && !state.serverRunning) {
|
|
194
|
-
// Fire-and-forget: don't block startup on daemon warmup. `zg server on`
|
|
195
|
-
// is idempotent, so this is safe even if something else started it
|
|
196
|
-
// in the meantime.
|
|
197
|
-
execZg(pi, ["server", "on"], ctx, { timeout: 20_000 })
|
|
198
|
-
.then(() => refreshZgState(pi, ctx, state))
|
|
199
|
-
.catch(() => {});
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
259
|
|
|
203
260
|
pi.on("turn_start", async (_event, ctx) => {
|
|
204
261
|
if (state.available) {
|
|
@@ -210,7 +267,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
210
267
|
name: "zg_search",
|
|
211
268
|
label: "zg search",
|
|
212
269
|
description:
|
|
213
|
-
|
|
270
|
+
"Semantic (meaning-based) code search over the current project's zvec-grep index. The go-to when you want to find code by CONCEPT rather than exact text — e.g. where authentication is handled, or how a feature works — without knowing the precise identifiers. Each call refreshes the index first, so results are never stale; it offers to build the index interactively if none exists. Prefer over keyword grep when an exact-token search would miss relevant code. For precise identifiers, string literals, or regex, use zg_rg or the built-in grep tool instead. Returns at most 2,000 lines or 50 KB of CLI output.",
|
|
214
271
|
promptSnippet: "Semantic search in the current project's zg index",
|
|
215
272
|
parameters: Type.Object({
|
|
216
273
|
query: Type.String({ minLength: 1, description: "Semantic code-search query" }),
|
|
@@ -237,7 +294,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
237
294
|
);
|
|
238
295
|
}
|
|
239
296
|
|
|
240
|
-
const args = ["query"];
|
|
297
|
+
const args = ["query", "--mode", "direct", "--refresh", "wait"];
|
|
241
298
|
if (params.limit !== undefined) args.push("--limit", String(params.limit));
|
|
242
299
|
args.push(params.query);
|
|
243
300
|
|
|
@@ -260,7 +317,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
260
317
|
name: "zg_rg",
|
|
261
318
|
label: "zg managed ripgrep",
|
|
262
319
|
description:
|
|
263
|
-
"Exhaustive exact-match search via zvec-grep's managed ripgrep (`zg query --rg`).
|
|
320
|
+
"Exhaustive exact-match search via zvec-grep's managed ripgrep (`zg query --rg`). Use when you know the precise identifier, string literal, or regex to locate and want results that honor the project's configured ignore/glob rules (e.g. excluding node_modules). Prefer it over zg_search when you need literal/regex matches rather than meaning; prefer it over the built-in grep tool when you want rg's powers (globs, -F literal) with the project's ignore rules applied. Does not require an index.",
|
|
264
321
|
promptSnippet: "Exhaustive managed ripgrep search via zg (respects project ignore rules)",
|
|
265
322
|
parameters: Type.Object({
|
|
266
323
|
pattern: Type.String({ minLength: 1, description: "Pattern to search for" }),
|
|
@@ -302,7 +359,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
302
359
|
name: "zg_index",
|
|
303
360
|
label: "zg index",
|
|
304
361
|
description:
|
|
305
|
-
"Build, rebuild, or drop the current project's persistent zvec-grep index. Use only when the user explicitly asks:
|
|
362
|
+
"Build, rebuild, or drop the current project's persistent zvec-grep index. Use only when the user explicitly asks: searches already refresh a stale index before answering, so this is mainly for the first-time build, an explicit rebuild, or dropping the index.",
|
|
306
363
|
promptSnippet: "Explicitly build, rebuild, or drop the current project's zg index",
|
|
307
364
|
promptGuidelines: [
|
|
308
365
|
"Use zg_index only when the user explicitly requests indexing, rebuilding, or dropping the zg index; do not call it merely because zg_search reports a missing index -- that flow already offers to build it interactively.",
|
|
@@ -323,7 +380,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
323
380
|
throw new Error("`zg` is not available on PATH. Install @zvec/zvec-grep separately before indexing.");
|
|
324
381
|
}
|
|
325
382
|
|
|
326
|
-
const args = ["index"];
|
|
383
|
+
const args = ["index", "--mode", "direct"];
|
|
327
384
|
if (params.drop) args.push("--drop", "--yes");
|
|
328
385
|
else if (params.rebuild) args.push("--rebuild");
|
|
329
386
|
|
|
@@ -341,8 +398,49 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
398
|
},
|
|
342
399
|
});
|
|
343
400
|
|
|
401
|
+
pi.registerTool({
|
|
402
|
+
name: "zg_status",
|
|
403
|
+
label: "zg status",
|
|
404
|
+
description:
|
|
405
|
+
"Report zg availability and the current project's index status (ready or not). Use to confirm zg is set up and the index is current before relying on semantic search, or to diagnose why zg_search/zg_rg return nothing. Reads pi's cached state (refreshed for this call) and does not spawn a separate `zg` subprocess. For full index detail/coverage, humans can use `/zg-status`.",
|
|
406
|
+
promptSnippet: "Check zg availability and index status",
|
|
407
|
+
parameters: Type.Object({}),
|
|
408
|
+
async execute(_toolCallId, _params, signal, _onUpdate, ctx) {
|
|
409
|
+
if (!state.checked) await refreshZgState(pi, ctx, state, signal);
|
|
410
|
+
if (!state.available) {
|
|
411
|
+
throw new Error("`zg` is not available on PATH. Install @zvec/zvec-grep separately before checking status.");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
await refreshZgState(pi, ctx, state, signal);
|
|
415
|
+
const index = state.indexed ? "index: ready" : "index: not ready";
|
|
416
|
+
return {
|
|
417
|
+
content: [
|
|
418
|
+
{
|
|
419
|
+
type: "text",
|
|
420
|
+
text: [
|
|
421
|
+
`zg: ${state.version || "available"}`,
|
|
422
|
+
index,
|
|
423
|
+
].join("\n"),
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
details: {
|
|
427
|
+
available: true,
|
|
428
|
+
version: state.version,
|
|
429
|
+
indexed: state.indexed,
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
pi.registerCommand("zg-settings", {
|
|
436
|
+
description: "Configure zg embedding model, device, and provider credentials",
|
|
437
|
+
handler: async (_args, ctx) => {
|
|
438
|
+
await openZgSettings(pi, ctx, state);
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
|
|
344
442
|
pi.registerCommand("zg-status", {
|
|
345
|
-
description: "Report zg version
|
|
443
|
+
description: "Report zg version and index status for this project",
|
|
346
444
|
handler: async (_args, ctx) => {
|
|
347
445
|
await refreshZgState(pi, ctx, state);
|
|
348
446
|
if (!state.available) {
|
|
@@ -353,7 +451,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
353
451
|
const detail = await execZg(pi, ["status"], ctx);
|
|
354
452
|
const lines = [
|
|
355
453
|
`zg: ${state.version || "available"}`,
|
|
356
|
-
`server: ${state.serverRunning ? "running" : "stopped"}`,
|
|
357
454
|
"",
|
|
358
455
|
(detail.stdout || detail.stderr).trim(),
|
|
359
456
|
];
|
|
@@ -378,7 +475,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
378
475
|
if (!confirmed) return;
|
|
379
476
|
}
|
|
380
477
|
|
|
381
|
-
const cmdArgs = ["index"];
|
|
478
|
+
const cmdArgs = ["index", "--mode", "direct"];
|
|
382
479
|
if (drop) cmdArgs.push("--drop", "--yes");
|
|
383
480
|
else if (rebuild) cmdArgs.push("--rebuild");
|
|
384
481
|
|
|
@@ -395,34 +492,4 @@ export default function (pi: ExtensionAPI) {
|
|
|
395
492
|
},
|
|
396
493
|
});
|
|
397
494
|
|
|
398
|
-
pi.registerCommand("zg-server", {
|
|
399
|
-
description: "Control the shared zg server: /zg-server <on|off|status>",
|
|
400
|
-
handler: async (args, ctx) => {
|
|
401
|
-
if (!state.available) {
|
|
402
|
-
ctx.ui.notify("zg: not found on PATH", "error");
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const action = args.trim().toLowerCase() || "status";
|
|
407
|
-
if (action !== "on" && action !== "off" && action !== "status") {
|
|
408
|
-
ctx.ui.notify("Usage: /zg-server <on|off|status>", "warning");
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (action === "off") {
|
|
413
|
-
const confirmed = await ctx.ui.confirm(
|
|
414
|
-
"Stop the shared zg server?",
|
|
415
|
-
"This daemon may be used by other agents/tools (Claude, Cursor, etc.) configured via `zg install`. Stopping it affects all of them, not just this session.",
|
|
416
|
-
);
|
|
417
|
-
if (!confirmed) return;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const result = await execZg(pi, ["server", action], ctx, { timeout: 20_000 });
|
|
421
|
-
await refreshZgState(pi, ctx, state);
|
|
422
|
-
ctx.ui.notify(
|
|
423
|
-
(result.stdout || result.stderr).trim() || `zg server ${action} done.`,
|
|
424
|
-
result.code === 0 ? "info" : "error",
|
|
425
|
-
);
|
|
426
|
-
},
|
|
427
|
-
});
|
|
428
495
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krosskinetic/pi-zg",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Pi extension for zvec-grep (zg): semantic code search, managed ripgrep, and shared-server-aware index management",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"keywords": [
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi",
|
|
9
|
+
"pi-coding-agent",
|
|
10
|
+
"zvec-grep",
|
|
11
|
+
"semantic-search",
|
|
12
|
+
"code-search"
|
|
13
|
+
],
|
|
7
14
|
"license": "MIT",
|
|
8
15
|
"author": "KrossKinetic",
|
|
9
16
|
"repository": {
|
|
@@ -13,9 +20,15 @@
|
|
|
13
20
|
"homepage": "https://github.com/KrossKinetic/pi-zg#readme",
|
|
14
21
|
"bugs": "https://github.com/KrossKinetic/pi-zg/issues",
|
|
15
22
|
"pi": {
|
|
16
|
-
"extensions": [
|
|
23
|
+
"extensions": [
|
|
24
|
+
"./extensions"
|
|
25
|
+
]
|
|
17
26
|
},
|
|
18
|
-
"files": [
|
|
27
|
+
"files": [
|
|
28
|
+
"extensions",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
19
32
|
"peerDependencies": {
|
|
20
33
|
"@earendil-works/pi-coding-agent": "*",
|
|
21
34
|
"typebox": "*"
|