neurostack 0.1.0 → 0.3.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/bin/neurostack.js CHANGED
@@ -1,19 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- const { execFileSync } = require("child_process");
4
+ const { execFileSync, execSync } = require("child_process");
5
+ const fs = require("fs");
5
6
  const path = require("path");
6
7
  const os = require("os");
7
8
 
8
9
  const INSTALL_DIR = path.join(os.homedir(), ".local", "share", "neurostack", "repo");
10
+ const UV_BIN = path.join(os.homedir(), ".local", "bin", "uv");
11
+
12
+ function which(cmd) {
13
+ try {
14
+ return execSync(`command -v ${cmd}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
15
+ } catch { return null; }
16
+ }
17
+
18
+ // Handle `neurostack uninstall` — clean up data then remove the npm package
19
+ if (process.argv[2] === "uninstall") {
20
+ const preuninstall = path.join(__dirname, "..", "preuninstall.js");
21
+ if (fs.existsSync(preuninstall)) {
22
+ require(preuninstall);
23
+ }
24
+ console.log(" \x1b[36m▸\x1b[0m Removing npm package...\n");
25
+ try {
26
+ execSync("npm uninstall -g neurostack", { stdio: "inherit" });
27
+ } catch {
28
+ console.error(" \x1b[33m▸\x1b[0m npm uninstall failed — run manually: npm uninstall -g neurostack");
29
+ }
30
+ process.exit(0);
31
+ }
32
+
33
+ // Resolve uv — check PATH first, then common install location
34
+ const uv = which("uv") || (fs.existsSync(UV_BIN) ? UV_BIN : null);
35
+
36
+ if (!uv) {
37
+ console.error(
38
+ "\x1b[31mError:\x1b[0m uv is not installed or not on PATH.\n" +
39
+ " Fix: npm rebuild neurostack (will auto-install uv)\n" +
40
+ " Or: https://docs.astral.sh/uv/getting-started/installation/"
41
+ );
42
+ process.exit(1);
43
+ }
44
+
45
+ if (!fs.existsSync(path.join(INSTALL_DIR, "pyproject.toml"))) {
46
+ console.error(
47
+ "\x1b[31mError:\x1b[0m NeuroStack not found at " + INSTALL_DIR + "\n" +
48
+ " Fix: npm rebuild neurostack (will re-download and set up)"
49
+ );
50
+ process.exit(1);
51
+ }
9
52
 
10
53
  try {
11
- execFileSync("uv", ["run", "--project", INSTALL_DIR, "python", "-m", "neurostack.cli", ...process.argv.slice(2)], {
54
+ execFileSync(uv, ["run", "--python", "3.12", "--project", INSTALL_DIR, "python", "-m", "neurostack.cli", ...process.argv.slice(2)], {
12
55
  stdio: "inherit",
13
56
  env: { ...process.env }
14
57
  });
15
58
  } catch (e) {
16
59
  if (e.status != null) process.exit(e.status);
17
- console.error("Failed to run neurostack. Is it installed? Try: npm rebuild neurostack");
60
+ console.error(
61
+ "\x1b[31mError:\x1b[0m Failed to launch neurostack.\n" +
62
+ " Try: npm rebuild neurostack\n" +
63
+ " Debug: " + uv + " run --python 3.12 --project " + INSTALL_DIR + " python -m neurostack.cli --help"
64
+ );
18
65
  process.exit(1);
19
66
  }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "neurostack",
3
- "version": "0.1.0",
3
+ "version": "0.3.2",
4
4
  "description": "Build, maintain, and search your knowledge vault with AI",
5
5
  "bin": {
6
6
  "neurostack": "bin/neurostack.js"
7
7
  },
8
8
  "scripts": {
9
- "postinstall": "node postinstall.js"
9
+ "postinstall": "node postinstall.js",
10
+ "preuninstall": "node preuninstall.js"
10
11
  },
11
12
  "keywords": [
12
13
  "knowledge-management",
@@ -34,10 +35,14 @@
34
35
  "engines": {
35
36
  "node": ">=16"
36
37
  },
37
- "os": ["linux", "darwin"],
38
+ "os": [
39
+ "linux",
40
+ "darwin"
41
+ ],
38
42
  "files": [
39
43
  "bin/",
40
44
  "postinstall.js",
45
+ "preuninstall.js",
41
46
  "README.md",
42
47
  "LICENSE"
43
48
  ]
package/postinstall.js CHANGED
@@ -1,17 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- const { execSync, execFileSync } = require("child_process");
4
+ const { execSync } = require("child_process");
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
7
  const os = require("os");
8
+ const https = require("https");
9
+ const { createWriteStream } = require("fs");
10
+ const { createGunzip } = require("zlib");
8
11
 
9
12
  const INSTALL_DIR = path.join(os.homedir(), ".local", "share", "neurostack", "repo");
10
- const REPO = "https://github.com/raphasouthall/neurostack.git";
13
+ const TARBALL_URL = "https://github.com/raphasouthall/neurostack/archive/refs/heads/main.tar.gz";
14
+ const UV_INSTALL_URL = "https://astral.sh/uv/install.sh";
15
+ const UV_BIN_DIR = path.join(os.homedir(), ".local", "bin");
16
+ const PYTHON_VERSION = "3.12";
11
17
 
12
- function info(msg) { console.log(` [*] ${msg}`); }
13
- function warn(msg) { console.error(` [!] ${msg}`); }
14
- function die(msg) { console.error(` [X] ${msg}`); process.exit(1); }
18
+ function info(msg) { console.log(` \x1b[36m▸\x1b[0m ${msg}`); }
19
+ function warn(msg) { console.error(` \x1b[33m▸\x1b[0m ${msg}`); }
20
+ function die(msg) {
21
+ console.error(`\n \x1b[31m✗\x1b[0m ${msg}\n`);
22
+ process.exit(1);
23
+ }
15
24
 
16
25
  function which(cmd) {
17
26
  try {
@@ -23,64 +32,131 @@ function run(cmd, opts = {}) {
23
32
  return execSync(cmd, { encoding: "utf8", stdio: "inherit", ...opts });
24
33
  }
25
34
 
26
- // --- Python check ---
27
- let python = null;
28
- for (const cmd of ["python3.13", "python3.12", "python3.11", "python3"]) {
29
- if (!which(cmd)) continue;
30
- try {
31
- const ver = execSync(`${cmd} -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`,
32
- { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
33
- const [major, minor] = ver.split(".").map(Number);
34
- if (major >= 3 && minor >= 11) { python = cmd; break; }
35
- } catch { /* skip */ }
35
+ function uvCmd() {
36
+ return which("uv") || (fs.existsSync(path.join(UV_BIN_DIR, "uv")) ? path.join(UV_BIN_DIR, "uv") : null);
36
37
  }
37
- if (!python) die("Python 3.11+ is required. Install it and try again.");
38
- info(`Python: ${execSync(`${python} --version`, { encoding: "utf8" }).trim()}`);
39
-
40
- // --- FTS5 check ---
41
- try {
42
- execSync(`${python} -c "import sqlite3; c=sqlite3.connect(':memory:'); c.execute('CREATE VIRTUAL TABLE t USING fts5(c)'); c.close()"`,
43
- { stdio: ["pipe", "pipe", "pipe"] });
44
- info("FTS5: available");
45
- } catch {
46
- die("SQLite FTS5 extension required but not available in your Python build");
38
+
39
+ /** Download a URL to a file or string using Node built-in https (follows redirects) */
40
+ function download(url, destPath) {
41
+ return new Promise((resolve, reject) => {
42
+ const get = (u) => {
43
+ https.get(u, { headers: { "User-Agent": "neurostack-installer" } }, (res) => {
44
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
45
+ return get(res.headers.location);
46
+ }
47
+ if (res.statusCode !== 200) {
48
+ return reject(new Error(`HTTP ${res.statusCode} from ${u}`));
49
+ }
50
+ if (destPath) {
51
+ const ws = createWriteStream(destPath);
52
+ res.pipe(ws);
53
+ ws.on("finish", () => resolve(destPath));
54
+ ws.on("error", reject);
55
+ } else {
56
+ const chunks = [];
57
+ res.on("data", (c) => chunks.push(c));
58
+ res.on("end", () => resolve(Buffer.concat(chunks).toString()));
59
+ }
60
+ res.on("error", reject);
61
+ }).on("error", reject);
62
+ };
63
+ get(url);
64
+ });
47
65
  }
48
66
 
49
- // --- git check ---
50
- if (!which("git")) die("git is required. Install it and try again.");
67
+ async function main() {
68
+ console.log("\n \x1b[1mNeuroStack installer\x1b[0m\n");
51
69
 
52
- // --- uv check/install ---
53
- if (!which("uv")) {
54
- info("Installing uv...");
55
- run("curl -LsSf https://astral.sh/uv/install.sh | sh");
56
- process.env.PATH = `${path.join(os.homedir(), ".local", "bin")}:${process.env.PATH}`;
57
- }
58
- info(`uv: ${execSync("uv --version", { encoding: "utf8" }).trim()}`);
59
-
60
- // --- Clone/update ---
61
- if (fs.existsSync(path.join(INSTALL_DIR, ".git"))) {
62
- info("Updating existing installation...");
63
- run(`git -C ${INSTALL_DIR} pull --ff-only`);
64
- } else {
65
- info("Cloning NeuroStack...");
66
- fs.mkdirSync(path.dirname(INSTALL_DIR), { recursive: true });
67
- run(`git clone ${REPO} ${INSTALL_DIR}`);
68
- }
70
+ // ── Step 1: Install uv (the only external dependency) ──
71
+ if (!uvCmd()) {
72
+ info("Installing uv (Python package manager)...");
73
+ try {
74
+ const script = await download(UV_INSTALL_URL);
75
+ const tmpFile = path.join(os.tmpdir(), `uv-install-${Date.now()}.sh`);
76
+ fs.writeFileSync(tmpFile, script, { mode: 0o755 });
77
+ run(`sh "${tmpFile}"`, { env: { ...process.env, UV_UNMANAGED_INSTALL: UV_BIN_DIR } });
78
+ fs.unlinkSync(tmpFile);
79
+ process.env.PATH = `${UV_BIN_DIR}:${process.env.PATH}`;
80
+ } catch (e) {
81
+ die(
82
+ `Failed to install uv: ${e.message}\n` +
83
+ ` Install manually: https://docs.astral.sh/uv/getting-started/installation/\n` +
84
+ ` Then re-run: npm rebuild neurostack`
85
+ );
86
+ }
87
+ }
88
+ const uv = uvCmd();
89
+ if (!uv) die("uv installed but not found on PATH. Run: npm rebuild neurostack");
90
+ info(`uv: ${execSync(`"${uv}" --version`, { encoding: "utf8" }).trim()}`);
91
+
92
+ // ── Step 2: Install Python via uv (no system Python needed) ──
93
+ info(`Ensuring Python ${PYTHON_VERSION} is available...`);
94
+ try {
95
+ run(`"${uv}" python install ${PYTHON_VERSION}`);
96
+ } catch (e) {
97
+ die(`Failed to install Python ${PYTHON_VERSION} via uv: ${e.message}`);
98
+ }
69
99
 
70
- // --- Install with uv ---
71
- const mode = process.env.NEUROSTACK_MODE || "lite";
72
- const extraArgs = mode === "community" ? "--extra full --extra community"
73
- : mode === "full" ? "--extra full"
74
- : "";
75
- info(`Installing in ${mode} mode...`);
76
- run(`uv sync ${extraArgs}`.trim(), { cwd: INSTALL_DIR });
77
-
78
- // --- Config ---
79
- const configDir = path.join(os.homedir(), ".config", "neurostack");
80
- const configFile = path.join(configDir, "config.toml");
81
- if (!fs.existsSync(configFile)) {
82
- fs.mkdirSync(configDir, { recursive: true });
83
- fs.writeFileSync(configFile, `# NeuroStack Configuration
100
+ // Verify FTS5 in the uv-managed Python
101
+ try {
102
+ run(
103
+ `"${uv}" run --python ${PYTHON_VERSION} python -c "import sqlite3; c=sqlite3.connect(':memory:'); c.execute('CREATE VIRTUAL TABLE t USING fts5(c)'); c.close()"`,
104
+ { stdio: ["pipe", "pipe", "pipe"] }
105
+ );
106
+ info("SQLite FTS5: ok");
107
+ } catch {
108
+ warn("FTS5 check skipped — will verify at first run");
109
+ }
110
+
111
+ // ── Step 3: Download source tarball (no git needed) ──
112
+ if (fs.existsSync(path.join(INSTALL_DIR, "pyproject.toml"))) {
113
+ // Existing install — try git pull if available, otherwise re-download
114
+ if (fs.existsSync(path.join(INSTALL_DIR, ".git")) && which("git")) {
115
+ info("Updating existing installation...");
116
+ try {
117
+ run(`git -C "${INSTALL_DIR}" pull --ff-only`);
118
+ } catch {
119
+ warn("git pull failed — re-downloading...");
120
+ fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
121
+ }
122
+ } else {
123
+ info("Re-downloading source...");
124
+ fs.rmSync(INSTALL_DIR, { recursive: true, force: true });
125
+ }
126
+ }
127
+
128
+ if (!fs.existsSync(path.join(INSTALL_DIR, "pyproject.toml"))) {
129
+ info("Downloading NeuroStack...");
130
+ const tarFile = path.join(os.tmpdir(), `neurostack-${Date.now()}.tar.gz`);
131
+ try {
132
+ await download(TARBALL_URL, tarFile);
133
+ fs.mkdirSync(INSTALL_DIR, { recursive: true });
134
+ // GitHub tarballs extract to neurostack-main/ — strip that prefix
135
+ run(`tar xzf "${tarFile}" --strip-components=1 -C "${INSTALL_DIR}"`);
136
+ fs.unlinkSync(tarFile);
137
+ } catch (e) {
138
+ die(
139
+ `Failed to download source: ${e.message}\n` +
140
+ ` Check your internet connection and try: npm rebuild neurostack`
141
+ );
142
+ }
143
+ }
144
+ info("Source: ok");
145
+
146
+ // ── Step 4: Install Python dependencies ──
147
+ const mode = process.env.NEUROSTACK_MODE || "lite";
148
+ const extraArgs = mode === "community" ? "--extra full --extra community"
149
+ : mode === "full" ? "--extra full"
150
+ : "";
151
+ info(`Installing Python dependencies (${mode} mode)...`);
152
+ run(`"${uv}" sync --python ${PYTHON_VERSION} ${extraArgs}`.trim(), { cwd: INSTALL_DIR });
153
+
154
+ // ── Step 5: Default config ──
155
+ const configDir = path.join(os.homedir(), ".config", "neurostack");
156
+ const configFile = path.join(configDir, "config.toml");
157
+ if (!fs.existsSync(configFile)) {
158
+ fs.mkdirSync(configDir, { recursive: true });
159
+ fs.writeFileSync(configFile, `# NeuroStack Configuration
84
160
  # See: https://github.com/raphasouthall/neurostack#configuration
85
161
 
86
162
  vault_root = "${os.homedir()}/brain"
@@ -88,9 +164,23 @@ embed_url = "http://localhost:11435"
88
164
  llm_url = "http://localhost:11434"
89
165
  llm_model = "phi3.5"
90
166
  `);
91
- info(`Config written: ${configFile}`);
92
- } else {
93
- info(`Config exists: ${configFile}`);
167
+ info(`Config: ${configFile}`);
168
+ } else {
169
+ info(`Config exists: ${configFile}`);
170
+ }
171
+
172
+ // ── Done ──
173
+ console.log(`
174
+ \x1b[32m✓ NeuroStack installed!\x1b[0m (${mode} mode)
175
+
176
+ Get started:
177
+ neurostack init Set up vault structure
178
+ neurostack index Index your vault
179
+ neurostack search 'q' Search
180
+ neurostack doctor Health check
181
+ `);
94
182
  }
95
183
 
96
- info("NeuroStack installed successfully via npm!");
184
+ main().catch((e) => {
185
+ die(`Unexpected error: ${e.message}\n Please report: https://github.com/raphasouthall/neurostack/issues`);
186
+ });
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const os = require("os");
7
+
8
+ const INSTALL_DIR = path.join(os.homedir(), ".local", "share", "neurostack");
9
+ const CONFIG_DIR = path.join(os.homedir(), ".config", "neurostack");
10
+ const DB_FILE = path.join(INSTALL_DIR, "neurostack.db");
11
+
12
+ function info(msg) { console.log(` \x1b[36m▸\x1b[0m ${msg}`); }
13
+ function warn(msg) { console.error(` \x1b[33m▸\x1b[0m ${msg}`); }
14
+
15
+ console.log("\n \x1b[1mUninstalling NeuroStack\x1b[0m\n");
16
+
17
+ // Remove source repo + venv (~/.local/share/neurostack/repo)
18
+ const repoDir = path.join(INSTALL_DIR, "repo");
19
+ if (fs.existsSync(repoDir)) {
20
+ fs.rmSync(repoDir, { recursive: true, force: true });
21
+ info("Removed source and venv: " + repoDir);
22
+ } else {
23
+ info("Source directory not found (already clean)");
24
+ }
25
+
26
+ // Remove database if present
27
+ if (fs.existsSync(DB_FILE)) {
28
+ fs.rmSync(DB_FILE, { force: true });
29
+ // Also clean up WAL/SHM journal files
30
+ for (const suffix of ["-wal", "-shm"]) {
31
+ const f = DB_FILE + suffix;
32
+ if (fs.existsSync(f)) fs.rmSync(f, { force: true });
33
+ }
34
+ info("Removed database: " + DB_FILE);
35
+ }
36
+
37
+ // Remove parent dir if empty
38
+ if (fs.existsSync(INSTALL_DIR)) {
39
+ try {
40
+ fs.rmdirSync(INSTALL_DIR);
41
+ info("Removed empty directory: " + INSTALL_DIR);
42
+ } catch {
43
+ // Not empty — user may have other files there
44
+ warn("Directory not empty, kept: " + INSTALL_DIR);
45
+ }
46
+ }
47
+
48
+ // Preserve config — user may want to reinstall later
49
+ if (fs.existsSync(CONFIG_DIR)) {
50
+ warn("Config preserved: " + CONFIG_DIR);
51
+ warn("To remove manually: rm -rf " + CONFIG_DIR);
52
+ }
53
+
54
+ console.log("\n \x1b[32m✓ NeuroStack uninstalled.\x1b[0m\n");