basemind 0.2.6 → 0.4.0-rc.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/install.js +120 -15
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -3,6 +3,7 @@ const os = require("node:os");
|
|
|
3
3
|
const path = require("node:path");
|
|
4
4
|
const https = require("node:https");
|
|
5
5
|
const http = require("node:http");
|
|
6
|
+
const crypto = require("node:crypto");
|
|
6
7
|
const tar = require("tar");
|
|
7
8
|
const AdmZip = require("adm-zip");
|
|
8
9
|
|
|
@@ -13,7 +14,7 @@ function getPlatformTriple() {
|
|
|
13
14
|
const arch = os.arch();
|
|
14
15
|
|
|
15
16
|
if (type === "Windows_NT") {
|
|
16
|
-
if (arch === "x64") return "x86_64-pc-windows-
|
|
17
|
+
if (arch === "x64") return "x86_64-pc-windows-msvc";
|
|
17
18
|
if (arch === "ia32") throw new Error("32-bit Windows is not supported");
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -32,11 +33,16 @@ function getPlatformTriple() {
|
|
|
32
33
|
throw new Error(`Unsupported platform: ${type} ${arch}`);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
function
|
|
36
|
+
function getReleaseAssets() {
|
|
36
37
|
const platform = getPlatformTriple();
|
|
37
38
|
const baseUrl = `https://github.com/Goldziher/basemind/releases/download/v${version}`;
|
|
38
39
|
const ext = platform.includes("windows") ? "zip" : "tar.gz";
|
|
39
|
-
|
|
40
|
+
const assetName = `basemind-${platform}.${ext}`;
|
|
41
|
+
return {
|
|
42
|
+
assetName,
|
|
43
|
+
archiveUrl: `${baseUrl}/${assetName}`,
|
|
44
|
+
checksumsUrl: `${baseUrl}/basemind_${version}_checksums.txt`,
|
|
45
|
+
};
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
function downloadWithRedirects(url, dest, maxRedirects = 5) {
|
|
@@ -89,12 +95,105 @@ function downloadWithRedirects(url, dest, maxRedirects = 5) {
|
|
|
89
95
|
});
|
|
90
96
|
}
|
|
91
97
|
|
|
98
|
+
// Download a (small) text resource into memory, following redirects.
|
|
99
|
+
function fetchTextWithRedirects(url, maxRedirects = 5) {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
if (maxRedirects <= 0) {
|
|
102
|
+
return reject(new Error("Too many redirects"));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const urlObj = new URL(url);
|
|
106
|
+
const client = urlObj.protocol === "https:" ? https : http;
|
|
107
|
+
|
|
108
|
+
const req = client.get(
|
|
109
|
+
url,
|
|
110
|
+
{
|
|
111
|
+
headers: {
|
|
112
|
+
"User-Agent": "basemind-npm-wrapper",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
(res) => {
|
|
116
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
117
|
+
return fetchTextWithRedirects(res.headers.location, maxRedirects - 1)
|
|
118
|
+
.then(resolve)
|
|
119
|
+
.catch(reject);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (res.statusCode !== 200) {
|
|
123
|
+
return reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const chunks = [];
|
|
127
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
128
|
+
res.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
129
|
+
res.on("error", reject);
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
req.on("error", reject);
|
|
134
|
+
req.setTimeout(30000, () => {
|
|
135
|
+
req.destroy();
|
|
136
|
+
reject(new Error("Download timeout"));
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function sha256File(filePath) {
|
|
142
|
+
const hash = crypto.createHash("sha256");
|
|
143
|
+
hash.update(fs.readFileSync(filePath));
|
|
144
|
+
return hash.digest("hex");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Parse a `sha256<space>filename` checksums file and return the digest for
|
|
148
|
+
// `assetName`, or null if absent. Lines may use one or two spaces (GNU coreutils
|
|
149
|
+
// uses two: binary-mode marker "* "), so split on whitespace.
|
|
150
|
+
function expectedDigest(checksumsText, assetName) {
|
|
151
|
+
for (const line of checksumsText.split(/\r?\n/)) {
|
|
152
|
+
const trimmed = line.trim();
|
|
153
|
+
if (!trimmed) continue;
|
|
154
|
+
const parts = trimmed.split(/\s+/);
|
|
155
|
+
if (parts.length < 2) continue;
|
|
156
|
+
const digest = parts[0];
|
|
157
|
+
const name = parts[parts.length - 1].replace(/^\*/, "");
|
|
158
|
+
if (name === assetName) return digest.toLowerCase();
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Verify the downloaded archive against the release checksums file. Fails CLOSED:
|
|
164
|
+
// any failure to fetch the checksums, locate the entry, or match the digest
|
|
165
|
+
// aborts the install.
|
|
166
|
+
async function verifyChecksum(archivePath, assetName, checksumsUrl) {
|
|
167
|
+
let checksumsText;
|
|
168
|
+
try {
|
|
169
|
+
checksumsText = await fetchTextWithRedirects(checksumsUrl);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`could not fetch checksums (${checksumsUrl}): ${error.message} — refusing to install unverified binary`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const expected = expectedDigest(checksumsText, assetName);
|
|
177
|
+
if (!expected) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`no checksum entry for ${assetName} in ${checksumsUrl} — refusing to install unverified binary`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const actual = sha256File(archivePath).toLowerCase();
|
|
184
|
+
if (actual !== expected) {
|
|
185
|
+
throw new Error(`checksum mismatch for ${assetName} (expected ${expected}, got ${actual})`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log("Checksum verified.");
|
|
189
|
+
}
|
|
190
|
+
|
|
92
191
|
async function installBinary() {
|
|
93
192
|
try {
|
|
94
|
-
const
|
|
95
|
-
const isZip =
|
|
193
|
+
const { assetName, archiveUrl, checksumsUrl } = getReleaseAssets();
|
|
194
|
+
const isZip = archiveUrl.endsWith(".zip");
|
|
96
195
|
const binDir = path.join(__dirname, "bin");
|
|
97
|
-
const archivePath = path.join(binDir,
|
|
196
|
+
const archivePath = path.join(binDir, assetName);
|
|
98
197
|
const binaryName = os.type() === "Windows_NT" ? "basemind.exe" : "basemind";
|
|
99
198
|
const binaryPath = path.join(binDir, binaryName);
|
|
100
199
|
|
|
@@ -106,29 +205,35 @@ async function installBinary() {
|
|
|
106
205
|
return;
|
|
107
206
|
}
|
|
108
207
|
|
|
109
|
-
console.log(`Downloading basemind binary from ${
|
|
208
|
+
console.log(`Downloading basemind binary from ${archiveUrl}...`);
|
|
110
209
|
|
|
111
|
-
await downloadWithRedirects(
|
|
210
|
+
await downloadWithRedirects(archiveUrl, archivePath);
|
|
112
211
|
|
|
113
|
-
|
|
212
|
+
// Fail CLOSED: verify the archive against the release checksums before
|
|
213
|
+
// extracting anything. Any fetch/parse/mismatch failure aborts the install.
|
|
214
|
+
await verifyChecksum(archivePath, assetName, checksumsUrl);
|
|
114
215
|
|
|
216
|
+
console.log("Extracting archive (binary + bundled libraries)...");
|
|
217
|
+
|
|
218
|
+
// Archives now contain the binary plus a lib/ tree of bundled native
|
|
219
|
+
// libraries (the binary finds them via rpath; Windows co-locates DLLs next
|
|
220
|
+
// to the exe). Extract the whole tree into bin/, not just the binary.
|
|
115
221
|
if (isZip) {
|
|
116
222
|
const zip = new AdmZip(archivePath);
|
|
117
|
-
|
|
118
|
-
if (!entry) {
|
|
119
|
-
throw new Error("Binary not found in downloaded archive");
|
|
120
|
-
}
|
|
121
|
-
zip.extractEntryTo(entry, binDir, false, true);
|
|
223
|
+
zip.extractAllTo(binDir, true);
|
|
122
224
|
} else {
|
|
123
225
|
await tar.extract({
|
|
124
226
|
file: archivePath,
|
|
125
227
|
cwd: binDir,
|
|
126
|
-
filter: (entryPath) => entryPath.endsWith(binaryName),
|
|
127
228
|
});
|
|
128
229
|
}
|
|
129
230
|
|
|
130
231
|
fs.unlinkSync(archivePath);
|
|
131
232
|
|
|
233
|
+
if (!fs.existsSync(binaryPath)) {
|
|
234
|
+
throw new Error(`binary ${binaryName} not found after extracting ${assetName}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
132
237
|
if (os.type() !== "Windows_NT") {
|
|
133
238
|
fs.chmodSync(binaryPath, 0o755);
|
|
134
239
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "basemind",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-rc.1",
|
|
4
4
|
"description": "Full AI context layer over MCP — tree-sitter code-map, document RAG (PDF/Office/HTML/email + OCR + reranker), shared agent memory, on-demand web crawl, git history + blame + per-symbol diff. 300+ languages, 8 coding-agent harnesses, content-addressed Fjall + LanceDB.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-context",
|