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 +50 -3
- package/package.json +8 -3
- package/postinstall.js +152 -62
- package/preuninstall.js +54 -0
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(
|
|
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(
|
|
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.
|
|
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": [
|
|
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
|
|
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
|
|
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(` [
|
|
13
|
-
function warn(msg) { console.error(` [
|
|
14
|
-
function die(msg) {
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
67
|
+
async function main() {
|
|
68
|
+
console.log("\n \x1b[1mNeuroStack installer\x1b[0m\n");
|
|
51
69
|
|
|
52
|
-
//
|
|
53
|
-
if (!
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
fs.
|
|
83
|
-
|
|
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
|
-
|
|
92
|
-
} else {
|
|
93
|
-
|
|
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
|
-
|
|
184
|
+
main().catch((e) => {
|
|
185
|
+
die(`Unexpected error: ${e.message}\n Please report: https://github.com/raphasouthall/neurostack/issues`);
|
|
186
|
+
});
|
package/preuninstall.js
ADDED
|
@@ -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");
|