pi-neutrasearch 0.1.0 → 0.2.0
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 +15 -6
- package/index.js +55 -3
- package/lib.js +76 -5
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -6,20 +6,29 @@ Token-efficient native indexed filename and path search for [Pi](https://pi.dev)
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
Install
|
|
9
|
+
Install the Pi package:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
12
|
pi install npm:pi-neutrasearch
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
npm automatically installs the matching Neutrasearch application for Linux x64/ARM64, Windows x64, or macOS x64/ARM64. No separate application download or compiler is required.
|
|
16
|
+
|
|
17
|
+
Inside Pi, run:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
/neutrasearch-setup
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This opens the bundled Neutrasearch app. Approve local indexing there, then return to Pi. Indexing remains an explicit user action; package installation never scans disks or requests privileges.
|
|
24
|
+
|
|
25
|
+
Use `/neutrasearch` to inspect installation and index status.
|
|
16
26
|
|
|
17
27
|
The package resolves, in order:
|
|
18
28
|
|
|
19
|
-
1. `NEUTRASEARCH_QUERY`
|
|
20
|
-
2.
|
|
21
|
-
3. `
|
|
22
|
-
4. `neutrasearch` on `PATH`
|
|
29
|
+
1. `NEUTRASEARCH_QUERY` or `NEUTRASEARCH_BIN` when explicitly configured
|
|
30
|
+
2. the OS/architecture-specific binary package installed by npm
|
|
31
|
+
3. `neutrasearch-query` or `neutrasearch` on `PATH`
|
|
23
32
|
|
|
24
33
|
Set `NEUTRASEARCH_INDEX` to override Neutrasearch's platform-default index path.
|
|
25
34
|
|
package/index.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
compactSearchResult,
|
|
4
|
+
limits,
|
|
5
|
+
queryArguments,
|
|
6
|
+
resolveNeutrasearch,
|
|
7
|
+
resolveNeutrasearchApp,
|
|
8
|
+
resolveScope,
|
|
9
|
+
} from "./lib.js";
|
|
2
10
|
|
|
3
11
|
const TOOL_NAME = "neutrasearch";
|
|
4
12
|
|
|
@@ -17,7 +25,7 @@ function status(binary) {
|
|
|
17
25
|
ok: false,
|
|
18
26
|
tool: TOOL_NAME,
|
|
19
27
|
error: "Neutrasearch executable not found",
|
|
20
|
-
setup: "
|
|
28
|
+
setup: "Reinstall pi-neutrasearch to fetch its matching native binary package, or set NEUTRASEARCH_QUERY / NEUTRASEARCH_BIN.",
|
|
21
29
|
token_defaults: { limit: limits.defaultLimit, max_chars: limits.defaultMaxChars },
|
|
22
30
|
};
|
|
23
31
|
}
|
|
@@ -26,6 +34,8 @@ function status(binary) {
|
|
|
26
34
|
tool: TOOL_NAME,
|
|
27
35
|
executable: binary.command,
|
|
28
36
|
executable_kind: binary.kind,
|
|
37
|
+
bundled_package: binary.packageName || null,
|
|
38
|
+
app: binary.app || resolveNeutrasearchApp() || null,
|
|
29
39
|
index: process.env.NEUTRASEARCH_INDEX || "platform default",
|
|
30
40
|
default_scope: "current Pi workspace",
|
|
31
41
|
extra_scope_env: "NEUTRASEARCH_PI_ALLOWED_ROOTS",
|
|
@@ -50,11 +60,27 @@ async function search(pi, params, signal, ctx) {
|
|
|
50
60
|
const { scope } = resolveScope(params.scope, ctx.cwd);
|
|
51
61
|
const { args, limit } = queryArguments(params, scope);
|
|
52
62
|
const timeout = Math.max(1000, Math.min(30000, Math.trunc(Number(params.timeout_ms) || 10000)));
|
|
53
|
-
|
|
63
|
+
let transport = params.metadata ? "metadata-json" : "paths-only-json";
|
|
64
|
+
let execution = await pi.exec(binary.command, [...binary.prefix, ...args], {
|
|
54
65
|
cwd: ctx.cwd,
|
|
55
66
|
signal,
|
|
56
67
|
timeout,
|
|
57
68
|
});
|
|
69
|
+
if (
|
|
70
|
+
execution.code !== 0
|
|
71
|
+
&& !params.metadata
|
|
72
|
+
&& args.includes("--json-paths")
|
|
73
|
+
&& /(?:unknown|unrecognized|unexpected).*(?:--json-paths)|--json-paths.*(?:unknown|unrecognized|unexpected)/i
|
|
74
|
+
.test(`${execution.stderr}\n${execution.stdout}`)
|
|
75
|
+
) {
|
|
76
|
+
const compatibleArgs = args.map((argument) => argument === "--json-paths" ? "--json" : argument);
|
|
77
|
+
execution = await pi.exec(binary.command, [...binary.prefix, ...compatibleArgs], {
|
|
78
|
+
cwd: ctx.cwd,
|
|
79
|
+
signal,
|
|
80
|
+
timeout,
|
|
81
|
+
});
|
|
82
|
+
transport = "metadata-json-compat";
|
|
83
|
+
}
|
|
58
84
|
if (execution.code !== 0) {
|
|
59
85
|
throw new Error(`Neutrasearch exited ${execution.code}: ${shortError(execution.stderr || execution.stdout)}`);
|
|
60
86
|
}
|
|
@@ -75,6 +101,7 @@ async function search(pi, params, signal, ctx) {
|
|
|
75
101
|
query: params.query,
|
|
76
102
|
limit,
|
|
77
103
|
executable_kind: binary.kind,
|
|
104
|
+
transport,
|
|
78
105
|
token_efficient: true,
|
|
79
106
|
});
|
|
80
107
|
}
|
|
@@ -142,6 +169,31 @@ export default function register(pi) {
|
|
|
142
169
|
},
|
|
143
170
|
});
|
|
144
171
|
|
|
172
|
+
pi.registerCommand("neutrasearch-setup", {
|
|
173
|
+
description: "Open the bundled Neutrasearch app to approve and build its index",
|
|
174
|
+
handler: async (_args, ctx) => {
|
|
175
|
+
const application = resolveNeutrasearchApp();
|
|
176
|
+
if (!application) {
|
|
177
|
+
ctx.ui.notify(
|
|
178
|
+
"Neutrasearch app is missing. Reinstall pi-neutrasearch or set NEUTRASEARCH_BIN.",
|
|
179
|
+
"error",
|
|
180
|
+
);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const child = spawn(application, [], {
|
|
185
|
+
detached: true,
|
|
186
|
+
stdio: "ignore",
|
|
187
|
+
windowsHide: false,
|
|
188
|
+
});
|
|
189
|
+
child.unref();
|
|
190
|
+
ctx.ui.notify("Neutrasearch opened. Approve local indexing in the app, then return to Pi.", "info");
|
|
191
|
+
} catch (error) {
|
|
192
|
+
ctx.ui.notify(`Could not open Neutrasearch: ${shortError(error)}`, "error");
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
145
197
|
pi.registerCommand("neutrasearch", {
|
|
146
198
|
description: "Show Neutrasearch Pi integration status",
|
|
147
199
|
handler: async (_args, ctx) => {
|
package/lib.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
4
7
|
|
|
5
8
|
const DEFAULT_LIMIT = 20;
|
|
6
9
|
const MAX_LIMIT = 200;
|
|
@@ -46,25 +49,93 @@ function findOnPath(command, env = process.env, platform = process.platform) {
|
|
|
46
49
|
return null;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
export function
|
|
50
|
-
|
|
52
|
+
export function bundledPackageName(platform = process.platform, architecture = process.arch) {
|
|
53
|
+
return {
|
|
54
|
+
"linux:x64": "neutrasearch-linux-x64",
|
|
55
|
+
"linux:arm64": "neutrasearch-linux-arm64",
|
|
56
|
+
"win32:x64": "neutrasearch-windows-x64",
|
|
57
|
+
"darwin:x64": "neutrasearch-darwin-x64",
|
|
58
|
+
"darwin:arm64": "neutrasearch-darwin-arm64",
|
|
59
|
+
}[`${platform}:${architecture}`] || null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function resolveBundledInstallation(
|
|
63
|
+
platform = process.platform,
|
|
64
|
+
architecture = process.arch,
|
|
65
|
+
packageResolver = (specifier) => require.resolve(specifier),
|
|
66
|
+
) {
|
|
67
|
+
const packageName = bundledPackageName(platform, architecture);
|
|
68
|
+
if (!packageName) return null;
|
|
69
|
+
try {
|
|
70
|
+
const packageJson = packageResolver(`${packageName}/package.json`);
|
|
71
|
+
const root = path.dirname(packageJson);
|
|
72
|
+
const suffix = platform === "win32" ? ".exe" : "";
|
|
73
|
+
const installation = {
|
|
74
|
+
packageName,
|
|
75
|
+
root,
|
|
76
|
+
query: path.join(root, "bin", `neutrasearch-query${suffix}`),
|
|
77
|
+
app: path.join(root, "bin", `neutrasearch${suffix}`),
|
|
78
|
+
helper: path.join(root, "bin", `neutrasearch-helper${suffix}`),
|
|
79
|
+
mcp: path.join(root, "bin", `neutrasearch-mcp${suffix}`),
|
|
80
|
+
};
|
|
81
|
+
return canExecute(installation.query, platform) && canExecute(installation.app, platform)
|
|
82
|
+
? installation
|
|
83
|
+
: null;
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveNeutrasearch(
|
|
90
|
+
env = process.env,
|
|
91
|
+
platform = process.platform,
|
|
92
|
+
architecture = process.arch,
|
|
93
|
+
) {
|
|
94
|
+
const explicit = [
|
|
51
95
|
env.NEUTRASEARCH_QUERY
|
|
52
96
|
? { command: env.NEUTRASEARCH_QUERY, prefix: [], kind: "query" }
|
|
53
97
|
: null,
|
|
54
|
-
{ command: "neutrasearch-query", prefix: [], kind: "query" },
|
|
55
98
|
env.NEUTRASEARCH_BIN
|
|
56
99
|
? { command: env.NEUTRASEARCH_BIN, prefix: ["search"], kind: "launcher" }
|
|
57
100
|
: null,
|
|
58
|
-
{ command: "neutrasearch", prefix: ["search"], kind: "launcher" },
|
|
59
101
|
].filter(Boolean);
|
|
102
|
+
for (const candidate of explicit) {
|
|
103
|
+
const resolved = findOnPath(candidate.command, env, platform);
|
|
104
|
+
if (resolved) return { ...candidate, command: resolved };
|
|
105
|
+
}
|
|
60
106
|
|
|
61
|
-
|
|
107
|
+
const bundled = resolveBundledInstallation(platform, architecture);
|
|
108
|
+
if (bundled) {
|
|
109
|
+
return {
|
|
110
|
+
command: bundled.query,
|
|
111
|
+
prefix: [],
|
|
112
|
+
kind: "bundled-query",
|
|
113
|
+
app: bundled.app,
|
|
114
|
+
packageName: bundled.packageName,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const pathCandidates = [
|
|
119
|
+
{ command: "neutrasearch-query", prefix: [], kind: "query" },
|
|
120
|
+
{ command: "neutrasearch", prefix: ["search"], kind: "launcher" },
|
|
121
|
+
];
|
|
122
|
+
for (const candidate of pathCandidates) {
|
|
62
123
|
const resolved = findOnPath(candidate.command, env, platform);
|
|
63
124
|
if (resolved) return { ...candidate, command: resolved };
|
|
64
125
|
}
|
|
65
126
|
return null;
|
|
66
127
|
}
|
|
67
128
|
|
|
129
|
+
export function resolveNeutrasearchApp(env = process.env, platform = process.platform) {
|
|
130
|
+
if (env.NEUTRASEARCH_BIN) {
|
|
131
|
+
const explicit = findOnPath(env.NEUTRASEARCH_BIN, env, platform);
|
|
132
|
+
if (explicit) return explicit;
|
|
133
|
+
}
|
|
134
|
+
const bundled = resolveBundledInstallation(platform, process.arch);
|
|
135
|
+
if (bundled) return bundled.app;
|
|
136
|
+
return findOnPath("neutrasearch", env, platform);
|
|
137
|
+
}
|
|
138
|
+
|
|
68
139
|
function canonicalDirectory(input) {
|
|
69
140
|
const resolved = fs.realpathSync(path.resolve(input));
|
|
70
141
|
if (!fs.statSync(resolved).isDirectory()) throw new Error(`scope is not a directory: ${input}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-neutrasearch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Token-efficient native indexed filename and path search for Pi agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,6 +32,13 @@
|
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=20"
|
|
34
34
|
},
|
|
35
|
+
"optionalDependencies": {
|
|
36
|
+
"neutrasearch-darwin-arm64": "0.1.0",
|
|
37
|
+
"neutrasearch-darwin-x64": "0.1.0",
|
|
38
|
+
"neutrasearch-linux-arm64": "0.1.0",
|
|
39
|
+
"neutrasearch-linux-x64": "0.1.0",
|
|
40
|
+
"neutrasearch-windows-x64": "0.1.0"
|
|
41
|
+
},
|
|
35
42
|
"scripts": {
|
|
36
43
|
"test": "node --test",
|
|
37
44
|
"prepublishOnly": "npm test"
|