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.
Files changed (4) hide show
  1. package/README.md +15 -6
  2. package/index.js +55 -3
  3. package/lib.js +76 -5
  4. 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 Neutrasearch and build its index first, then install the Pi package:
9
+ Install the Pi package:
10
10
 
11
11
  ```sh
12
12
  pi install npm:pi-neutrasearch
13
13
  ```
14
14
 
15
- Use `/neutrasearch` inside Pi to confirm executable discovery.
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. `neutrasearch-query` on `PATH`
21
- 3. `NEUTRASEARCH_BIN`
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 { compactSearchResult, limits, queryArguments, resolveNeutrasearch, resolveScope } from "./lib.js";
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: "Install Neutrasearch, or set NEUTRASEARCH_QUERY / NEUTRASEARCH_BIN.",
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
- const execution = await pi.exec(binary.command, [...binary.prefix, ...args], {
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 resolveNeutrasearch(env = process.env, platform = process.platform) {
50
- const candidates = [
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
- for (const candidate of candidates) {
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.1.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"