pi-neutrasearch 0.2.0 → 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 CHANGED
@@ -20,7 +20,13 @@ Inside Pi, run:
20
20
  /neutrasearch-setup
21
21
  ```
22
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.
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.
24
30
 
25
31
  Use `/neutrasearch` to inspect installation and index status.
26
32
 
@@ -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,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { installShortcuts } from "./shortcuts.js";
2
3
  import {
3
4
  compactSearchResult,
4
5
  limits,
@@ -181,13 +182,17 @@ export default function register(pi) {
181
182
  return;
182
183
  }
183
184
  try {
185
+ const shortcuts = installShortcuts(application);
184
186
  const child = spawn(application, [], {
185
187
  detached: true,
186
188
  stdio: "ignore",
187
189
  windowsHide: false,
188
190
  });
189
191
  child.unref();
190
- ctx.ui.notify("Neutrasearch opened. Approve local indexing in the app, then return to Pi.", "info");
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
+ );
191
196
  } catch (error) {
192
197
  ctx.ui.notify(`Could not open Neutrasearch: ${shortError(error)}`, "error");
193
198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-neutrasearch",
3
- "version": "0.2.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"
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
+ }