pi-neutrasearch 0.1.1 → 0.2.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/README.md +21 -6
- package/assets/neutrasearch.svg +7 -0
- package/index.js +42 -2
- package/lib.js +76 -5
- package/package.json +10 -1
- package/shortcuts.js +123 -0
package/README.md
CHANGED
|
@@ -6,20 +6,35 @@ 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 installs convenient shortcuts and opens the bundled Neutrasearch app:
|
|
24
|
+
|
|
25
|
+
- Linux: application menu, Desktop, and `~/.local/bin` CLI links
|
|
26
|
+
- Windows: Start Menu and Desktop shortcuts
|
|
27
|
+
- macOS: `~/Applications/Neutrasearch.app` and a Desktop alias
|
|
28
|
+
|
|
29
|
+
Approve local indexing in the app, then return to Pi. Indexing remains an explicit user action; package installation never scans disks or requests privileges.
|
|
30
|
+
|
|
31
|
+
Use `/neutrasearch` to inspect installation and index status.
|
|
16
32
|
|
|
17
33
|
The package resolves, in order:
|
|
18
34
|
|
|
19
|
-
1. `NEUTRASEARCH_QUERY`
|
|
20
|
-
2.
|
|
21
|
-
3. `
|
|
22
|
-
4. `neutrasearch` on `PATH`
|
|
35
|
+
1. `NEUTRASEARCH_QUERY` or `NEUTRASEARCH_BIN` when explicitly configured
|
|
36
|
+
2. the OS/architecture-specific binary package installed by npm
|
|
37
|
+
3. `neutrasearch-query` or `neutrasearch` on `PATH`
|
|
23
38
|
|
|
24
39
|
Set `NEUTRASEARCH_INDEX` to override Neutrasearch's platform-default index path.
|
|
25
40
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<rect width="64" height="64" rx="11" fill="#111512"/>
|
|
3
|
+
<path d="M16 17h23l9 9v21H16z" fill="#1d241f" stroke="#a8ff60" stroke-width="3"/>
|
|
4
|
+
<path d="M39 17v10h9" fill="none" stroke="#a8ff60" stroke-width="3"/>
|
|
5
|
+
<circle cx="29" cy="36" r="8" fill="none" stroke="#f2f5f1" stroke-width="3"/>
|
|
6
|
+
<path d="m35 42 8 8" stroke="#f2f5f1" stroke-width="4" stroke-linecap="square"/>
|
|
7
|
+
</svg>
|
package/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { installShortcuts } from "./shortcuts.js";
|
|
3
|
+
import {
|
|
4
|
+
compactSearchResult,
|
|
5
|
+
limits,
|
|
6
|
+
queryArguments,
|
|
7
|
+
resolveNeutrasearch,
|
|
8
|
+
resolveNeutrasearchApp,
|
|
9
|
+
resolveScope,
|
|
10
|
+
} from "./lib.js";
|
|
2
11
|
|
|
3
12
|
const TOOL_NAME = "neutrasearch";
|
|
4
13
|
|
|
@@ -17,7 +26,7 @@ function status(binary) {
|
|
|
17
26
|
ok: false,
|
|
18
27
|
tool: TOOL_NAME,
|
|
19
28
|
error: "Neutrasearch executable not found",
|
|
20
|
-
setup: "
|
|
29
|
+
setup: "Reinstall pi-neutrasearch to fetch its matching native binary package, or set NEUTRASEARCH_QUERY / NEUTRASEARCH_BIN.",
|
|
21
30
|
token_defaults: { limit: limits.defaultLimit, max_chars: limits.defaultMaxChars },
|
|
22
31
|
};
|
|
23
32
|
}
|
|
@@ -26,6 +35,8 @@ function status(binary) {
|
|
|
26
35
|
tool: TOOL_NAME,
|
|
27
36
|
executable: binary.command,
|
|
28
37
|
executable_kind: binary.kind,
|
|
38
|
+
bundled_package: binary.packageName || null,
|
|
39
|
+
app: binary.app || resolveNeutrasearchApp() || null,
|
|
29
40
|
index: process.env.NEUTRASEARCH_INDEX || "platform default",
|
|
30
41
|
default_scope: "current Pi workspace",
|
|
31
42
|
extra_scope_env: "NEUTRASEARCH_PI_ALLOWED_ROOTS",
|
|
@@ -159,6 +170,35 @@ export default function register(pi) {
|
|
|
159
170
|
},
|
|
160
171
|
});
|
|
161
172
|
|
|
173
|
+
pi.registerCommand("neutrasearch-setup", {
|
|
174
|
+
description: "Open the bundled Neutrasearch app to approve and build its index",
|
|
175
|
+
handler: async (_args, ctx) => {
|
|
176
|
+
const application = resolveNeutrasearchApp();
|
|
177
|
+
if (!application) {
|
|
178
|
+
ctx.ui.notify(
|
|
179
|
+
"Neutrasearch app is missing. Reinstall pi-neutrasearch or set NEUTRASEARCH_BIN.",
|
|
180
|
+
"error",
|
|
181
|
+
);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const shortcuts = installShortcuts(application);
|
|
186
|
+
const child = spawn(application, [], {
|
|
187
|
+
detached: true,
|
|
188
|
+
stdio: "ignore",
|
|
189
|
+
windowsHide: false,
|
|
190
|
+
});
|
|
191
|
+
child.unref();
|
|
192
|
+
ctx.ui.notify(
|
|
193
|
+
`Neutrasearch opened and ${shortcuts.length} shortcuts were installed. Approve local indexing in the app, then return to Pi.`,
|
|
194
|
+
"info",
|
|
195
|
+
);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
ctx.ui.notify(`Could not open Neutrasearch: ${shortError(error)}`, "error");
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
162
202
|
pi.registerCommand("neutrasearch", {
|
|
163
203
|
description: "Show Neutrasearch Pi integration status",
|
|
164
204
|
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.1",
|
|
4
4
|
"description": "Token-efficient native indexed filename and path search for Pi agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"index.js",
|
|
27
27
|
"lib.js",
|
|
28
|
+
"shortcuts.js",
|
|
29
|
+
"assets",
|
|
28
30
|
"README.md",
|
|
29
31
|
"LICENSE",
|
|
30
32
|
"skills"
|
|
@@ -32,6 +34,13 @@
|
|
|
32
34
|
"engines": {
|
|
33
35
|
"node": ">=20"
|
|
34
36
|
},
|
|
37
|
+
"optionalDependencies": {
|
|
38
|
+
"neutrasearch-darwin-arm64": "0.1.0",
|
|
39
|
+
"neutrasearch-darwin-x64": "0.1.0",
|
|
40
|
+
"neutrasearch-linux-arm64": "0.1.0",
|
|
41
|
+
"neutrasearch-linux-x64": "0.1.0",
|
|
42
|
+
"neutrasearch-windows-x64": "0.1.0"
|
|
43
|
+
},
|
|
35
44
|
"scripts": {
|
|
36
45
|
"test": "node --test",
|
|
37
46
|
"prepublishOnly": "npm test"
|
package/shortcuts.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
function quoteDesktop(value) {
|
|
8
|
+
return `"${String(value).replace(/(["`$\\])/g, "\\$1")}"`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function shellQuote(value) {
|
|
12
|
+
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeExecutable(file, content) {
|
|
16
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
17
|
+
fs.writeFileSync(file, content, { mode: 0o755 });
|
|
18
|
+
fs.chmodSync(file, 0o755);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function installLinux(application, home, packageRoot) {
|
|
22
|
+
const binRoot = path.dirname(application);
|
|
23
|
+
const icon = path.join(packageRoot, "assets", "neutrasearch.svg");
|
|
24
|
+
const applications = path.join(home, ".local", "share", "applications");
|
|
25
|
+
const desktopEntry = path.join(applications, "neutrasearch.desktop");
|
|
26
|
+
fs.mkdirSync(applications, { recursive: true });
|
|
27
|
+
const content = [
|
|
28
|
+
"[Desktop Entry]",
|
|
29
|
+
"Type=Application",
|
|
30
|
+
"Version=1.0",
|
|
31
|
+
"Name=Neutrasearch",
|
|
32
|
+
"GenericName=Indexed File Search",
|
|
33
|
+
"Comment=Search indexed filenames and paths without walking folders",
|
|
34
|
+
`Exec=${quoteDesktop(application)}`,
|
|
35
|
+
`Icon=${icon}`,
|
|
36
|
+
"Terminal=false",
|
|
37
|
+
"Categories=Utility;FileTools;System;",
|
|
38
|
+
"Keywords=search;files;index;filename;",
|
|
39
|
+
"StartupNotify=true",
|
|
40
|
+
"StartupWMClass=neutrasearch",
|
|
41
|
+
"",
|
|
42
|
+
].join("\n");
|
|
43
|
+
writeExecutable(desktopEntry, content);
|
|
44
|
+
|
|
45
|
+
const created = [desktopEntry];
|
|
46
|
+
const desktop = path.join(home, "Desktop");
|
|
47
|
+
if (fs.existsSync(desktop)) {
|
|
48
|
+
const desktopShortcut = path.join(desktop, "Neutrasearch.desktop");
|
|
49
|
+
writeExecutable(desktopShortcut, content);
|
|
50
|
+
created.push(desktopShortcut);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const localBin = path.join(home, ".local", "bin");
|
|
54
|
+
fs.mkdirSync(localBin, { recursive: true });
|
|
55
|
+
for (const name of ["neutrasearch", "neutrasearch-query", "neutrasearch-helper", "neutrasearch-mcp"]) {
|
|
56
|
+
const source = path.join(binRoot, name);
|
|
57
|
+
if (!fs.existsSync(source)) continue;
|
|
58
|
+
const destination = path.join(localBin, name);
|
|
59
|
+
try { fs.rmSync(destination, { force: true }); } catch {}
|
|
60
|
+
fs.symlinkSync(source, destination);
|
|
61
|
+
created.push(destination);
|
|
62
|
+
}
|
|
63
|
+
return created;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function installWindows(application, home, runPowerShell) {
|
|
67
|
+
const script = [
|
|
68
|
+
"$ErrorActionPreference = 'Stop'",
|
|
69
|
+
"$shell = New-Object -ComObject WScript.Shell",
|
|
70
|
+
"$targets = @(",
|
|
71
|
+
" [Environment]::GetFolderPath('Programs') + '\\Neutrasearch.lnk',",
|
|
72
|
+
" [Environment]::GetFolderPath('Desktop') + '\\Neutrasearch.lnk'",
|
|
73
|
+
")",
|
|
74
|
+
"foreach ($target in $targets) {",
|
|
75
|
+
" $shortcut = $shell.CreateShortcut($target)",
|
|
76
|
+
" $shortcut.TargetPath = $env:NEUTRASEARCH_APP",
|
|
77
|
+
" $shortcut.WorkingDirectory = Split-Path $env:NEUTRASEARCH_APP",
|
|
78
|
+
" $shortcut.IconLocation = $env:NEUTRASEARCH_APP",
|
|
79
|
+
" $shortcut.Description = 'Indexed filename and path search'",
|
|
80
|
+
" $shortcut.Save()",
|
|
81
|
+
"}",
|
|
82
|
+
].join("\n");
|
|
83
|
+
runPowerShell("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
84
|
+
env: { ...process.env, HOME: home, NEUTRASEARCH_APP: application },
|
|
85
|
+
stdio: "ignore",
|
|
86
|
+
});
|
|
87
|
+
return ["Windows Start Menu/Neutrasearch.lnk", "Windows Desktop/Neutrasearch.lnk"];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function installMac(application, home) {
|
|
91
|
+
const app = path.join(home, "Applications", "Neutrasearch.app");
|
|
92
|
+
const contents = path.join(app, "Contents");
|
|
93
|
+
const launcher = path.join(contents, "MacOS", "Neutrasearch");
|
|
94
|
+
writeExecutable(launcher, `#!/bin/sh\nexec ${shellQuote(application)} "$@"\n`);
|
|
95
|
+
fs.writeFileSync(
|
|
96
|
+
path.join(contents, "Info.plist"),
|
|
97
|
+
`<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>CFBundleName</key><string>Neutrasearch</string><key>CFBundleDisplayName</key><string>Neutrasearch</string><key>CFBundleIdentifier</key><string>dev.pi.neutrasearch</string><key>CFBundleExecutable</key><string>Neutrasearch</string><key>CFBundlePackageType</key><string>APPL</string></dict></plist>\n`,
|
|
98
|
+
);
|
|
99
|
+
const created = [app];
|
|
100
|
+
const desktop = path.join(home, "Desktop");
|
|
101
|
+
if (fs.existsSync(desktop)) {
|
|
102
|
+
const link = path.join(desktop, "Neutrasearch.app");
|
|
103
|
+
try { fs.rmSync(link, { recursive: true, force: true }); } catch {}
|
|
104
|
+
fs.symlinkSync(app, link);
|
|
105
|
+
created.push(link);
|
|
106
|
+
}
|
|
107
|
+
return created;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function installShortcuts(application, options = {}) {
|
|
111
|
+
if (!application || !path.isAbsolute(application) || !fs.existsSync(application)) {
|
|
112
|
+
throw new Error("Neutrasearch application is missing");
|
|
113
|
+
}
|
|
114
|
+
const platform = options.platform || process.platform;
|
|
115
|
+
const home = options.home || os.homedir();
|
|
116
|
+
const packageRoot = options.packageRoot || path.dirname(fileURLToPath(import.meta.url));
|
|
117
|
+
if (platform === "linux") return installLinux(application, home, packageRoot);
|
|
118
|
+
if (platform === "win32") {
|
|
119
|
+
return installWindows(application, home, options.runPowerShell || execFileSync);
|
|
120
|
+
}
|
|
121
|
+
if (platform === "darwin") return installMac(application, home);
|
|
122
|
+
throw new Error(`shortcut installation is unsupported on ${platform}`);
|
|
123
|
+
}
|