grok-search-rs 0.1.2 → 0.1.3
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/package.json +6 -6
- package/run.js +39 -238
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grok-search-rs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Rust MCP server for Grok Responses web search, Tavily fetch/map, and Firecrawl fallback",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"arm64"
|
|
46
46
|
],
|
|
47
47
|
"optionalDependencies": {
|
|
48
|
-
"@epsiekygrr_zedxx/grok-search-rs-darwin-universal": "0.1.
|
|
49
|
-
"@epsiekygrr_zedxx/grok-search-rs-linux-x64": "0.1.
|
|
50
|
-
"@epsiekygrr_zedxx/grok-search-rs-linux-arm64": "0.1.
|
|
51
|
-
"@epsiekygrr_zedxx/grok-search-rs-win32-x64": "0.1.
|
|
52
|
-
"@epsiekygrr_zedxx/grok-search-rs-win32-arm64": "0.1.
|
|
48
|
+
"@epsiekygrr_zedxx/grok-search-rs-darwin-universal": "0.1.3",
|
|
49
|
+
"@epsiekygrr_zedxx/grok-search-rs-linux-x64": "0.1.3",
|
|
50
|
+
"@epsiekygrr_zedxx/grok-search-rs-linux-arm64": "0.1.3",
|
|
51
|
+
"@epsiekygrr_zedxx/grok-search-rs-win32-x64": "0.1.3",
|
|
52
|
+
"@epsiekygrr_zedxx/grok-search-rs-win32-arm64": "0.1.3"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/run.js
CHANGED
|
@@ -1,267 +1,68 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
2
3
|
const { spawn } = require("child_process");
|
|
3
4
|
const path = require("path");
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const https = require("https");
|
|
6
5
|
const os = require("os");
|
|
7
|
-
const crypto = require("crypto");
|
|
8
6
|
|
|
9
7
|
const PACKAGE_NAME = "grok-search-rs";
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
binaryName: "grok-search-rs",
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
if (platform === "linux" && arch === "x64") {
|
|
31
|
-
return {
|
|
32
|
-
packageName: "@epsiekygrr_zedxx/grok-search-rs-linux-x64",
|
|
33
|
-
assetName: "grok-search-rs_Linux_x86_64.tar.gz",
|
|
34
|
-
binaryName: "grok-search-rs",
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
if (platform === "linux" && arch === "arm64") {
|
|
38
|
-
return {
|
|
39
|
-
packageName: "@epsiekygrr_zedxx/grok-search-rs-linux-arm64",
|
|
40
|
-
assetName: "grok-search-rs_Linux_aarch64.tar.gz",
|
|
41
|
-
binaryName: "grok-search-rs",
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
if (platform === "win32" && arch === "x64") {
|
|
45
|
-
return {
|
|
46
|
-
packageName: "@epsiekygrr_zedxx/grok-search-rs-win32-x64",
|
|
47
|
-
assetName: "grok-search-rs_Windows_x86_64.zip",
|
|
48
|
-
binaryName: "grok-search-rs.exe",
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
if (platform === "win32" && arch === "arm64") {
|
|
52
|
-
return {
|
|
53
|
-
packageName: "@epsiekygrr_zedxx/grok-search-rs-win32-arm64",
|
|
54
|
-
assetName: "grok-search-rs_Windows_aarch64.zip",
|
|
55
|
-
binaryName: "grok-search-rs.exe",
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
throw new Error(`Unsupported platform: ${platform}/${arch}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function cacheDir() {
|
|
62
|
-
const home = os.homedir();
|
|
63
|
-
let base;
|
|
64
|
-
if (process.platform === "win32") {
|
|
65
|
-
base = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
66
|
-
} else if (process.platform === "darwin") {
|
|
67
|
-
base = path.join(home, "Library", "Caches");
|
|
68
|
-
} else {
|
|
69
|
-
base = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
|
|
70
|
-
}
|
|
71
|
-
return path.join(base, PACKAGE_NAME, version());
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function findOptionalBinary(info) {
|
|
75
|
-
try {
|
|
76
|
-
const pkg = require.resolve(`${info.packageName}/package.json`);
|
|
77
|
-
const bin = path.join(path.dirname(pkg), "bin", info.binaryName);
|
|
78
|
-
if (fs.existsSync(bin)) return bin;
|
|
79
|
-
} catch (_) {}
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function requestBuffer(url, options = {}, redirects = 0) {
|
|
84
|
-
return new Promise((resolve, reject) => {
|
|
85
|
-
if (redirects > MAX_REDIRECTS) return reject(new Error("Too many redirects"));
|
|
86
|
-
const req = https.get(url, options, (res) => {
|
|
87
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
88
|
-
res.resume();
|
|
89
|
-
if (!res.headers.location.startsWith("https://")) {
|
|
90
|
-
return reject(new Error(`Insecure redirect: ${res.headers.location}`));
|
|
91
|
-
}
|
|
92
|
-
return requestBuffer(res.headers.location, options, redirects + 1).then(resolve, reject);
|
|
93
|
-
}
|
|
94
|
-
if (res.statusCode !== 200) {
|
|
95
|
-
res.resume();
|
|
96
|
-
return reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
97
|
-
}
|
|
98
|
-
const chunks = [];
|
|
99
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
100
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
101
|
-
});
|
|
102
|
-
req.on("error", reject);
|
|
103
|
-
req.setTimeout(REQUEST_TIMEOUT, () => {
|
|
104
|
-
req.destroy();
|
|
105
|
-
reject(new Error("Request timeout"));
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function withRetry(fn) {
|
|
111
|
-
let last;
|
|
112
|
-
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
113
|
-
try {
|
|
114
|
-
return await fn();
|
|
115
|
-
} catch (err) {
|
|
116
|
-
last = err;
|
|
117
|
-
if (String(err.message).includes("404")) throw err;
|
|
118
|
-
if (i < MAX_RETRIES - 1) {
|
|
119
|
-
await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, i)));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
throw last;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function releaseForVersion() {
|
|
127
|
-
const tag = `v${version()}`;
|
|
128
|
-
const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${tag}`;
|
|
129
|
-
const headers = {
|
|
130
|
-
"User-Agent": PACKAGE_NAME,
|
|
131
|
-
Accept: "application/vnd.github.v3+json",
|
|
132
|
-
...(process.env.GITHUB_TOKEN ? { Authorization: `token ${process.env.GITHUB_TOKEN}` } : {}),
|
|
133
|
-
};
|
|
134
|
-
const data = await withRetry(() => requestBuffer(url, { headers }));
|
|
135
|
-
return JSON.parse(data.toString());
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function downloadFile(url, dest, options = {}, redirects = 0) {
|
|
139
|
-
return new Promise((resolve, reject) => {
|
|
140
|
-
if (redirects > MAX_REDIRECTS) return reject(new Error("Too many redirects"));
|
|
141
|
-
const file = fs.createWriteStream(dest);
|
|
142
|
-
const req = https.get(url, options, (res) => {
|
|
143
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
144
|
-
res.resume();
|
|
145
|
-
file.close(() => {
|
|
146
|
-
try { fs.unlinkSync(dest); } catch (_) {}
|
|
147
|
-
if (!res.headers.location.startsWith("https://")) {
|
|
148
|
-
return reject(new Error(`Insecure redirect: ${res.headers.location}`));
|
|
149
|
-
}
|
|
150
|
-
downloadFile(res.headers.location, dest, options, redirects + 1).then(resolve, reject);
|
|
151
|
-
});
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (res.statusCode !== 200) {
|
|
155
|
-
res.resume();
|
|
156
|
-
file.close(() => {
|
|
157
|
-
try { fs.unlinkSync(dest); } catch (_) {}
|
|
158
|
-
reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
159
|
-
});
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
res.pipe(file);
|
|
163
|
-
file.on("finish", () => file.close(resolve));
|
|
164
|
-
});
|
|
165
|
-
req.on("error", (err) => file.close(() => reject(err)));
|
|
166
|
-
req.setTimeout(REQUEST_TIMEOUT, () => {
|
|
167
|
-
req.destroy();
|
|
168
|
-
file.close(() => reject(new Error("Download timeout")));
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function extractArchive(archive, dir, info) {
|
|
174
|
-
return new Promise((resolve, reject) => {
|
|
175
|
-
const child = info.assetName.endsWith(".zip")
|
|
176
|
-
? spawn("powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", `Expand-Archive -LiteralPath '${archive.replace(/'/g, "''")}' -DestinationPath '${dir.replace(/'/g, "''")}' -Force`], { stdio: ["ignore", process.stderr, process.stderr] })
|
|
177
|
-
: spawn("tar", ["-xzf", archive, "-C", dir], { stdio: ["ignore", process.stderr, process.stderr] });
|
|
178
|
-
child.on("error", reject);
|
|
179
|
-
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`extract exited with code ${code}`)));
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function acquireLock(lock) {
|
|
184
|
-
try {
|
|
185
|
-
fs.writeFileSync(lock, String(process.pid), { flag: "wx" });
|
|
186
|
-
return true;
|
|
187
|
-
} catch (err) {
|
|
188
|
-
if (err.code !== "EEXIST") throw err;
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function downloadBinary(info) {
|
|
194
|
-
const dir = cacheDir();
|
|
195
|
-
const bin = path.join(dir, info.binaryName);
|
|
196
|
-
const lock = path.join(dir, ".lock");
|
|
197
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
198
|
-
if (fs.existsSync(bin)) return bin;
|
|
199
|
-
|
|
200
|
-
if (!acquireLock(lock)) {
|
|
201
|
-
console.error("Another grok-search-rs process is installing, waiting...");
|
|
202
|
-
for (let i = 0; i < 60; i++) {
|
|
203
|
-
if (fs.existsSync(bin)) return bin;
|
|
204
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
205
|
-
}
|
|
206
|
-
throw new Error("Timeout waiting for install lock");
|
|
8
|
+
const PLATFORMS = {
|
|
9
|
+
"darwin-x64": "@epsiekygrr_zedxx/grok-search-rs-darwin-universal",
|
|
10
|
+
"darwin-arm64": "@epsiekygrr_zedxx/grok-search-rs-darwin-universal",
|
|
11
|
+
"linux-x64": "@epsiekygrr_zedxx/grok-search-rs-linux-x64",
|
|
12
|
+
"linux-arm64": "@epsiekygrr_zedxx/grok-search-rs-linux-arm64",
|
|
13
|
+
"win32-x64": "@epsiekygrr_zedxx/grok-search-rs-win32-x64",
|
|
14
|
+
"win32-arm64": "@epsiekygrr_zedxx/grok-search-rs-win32-arm64",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function getBinaryPath() {
|
|
18
|
+
const platformKey = `${process.platform}-${process.arch}`;
|
|
19
|
+
const pkgName = PLATFORMS[platformKey];
|
|
20
|
+
|
|
21
|
+
if (!pkgName) {
|
|
22
|
+
console.error(`Unsupported platform: ${process.platform}-${process.arch}`);
|
|
23
|
+
console.error(`Supported platforms: ${Object.keys(PLATFORMS).join(", ")}`);
|
|
24
|
+
process.exit(1);
|
|
207
25
|
}
|
|
208
26
|
|
|
209
|
-
const tempId = crypto.randomBytes(8).toString("hex");
|
|
210
|
-
const archive = path.join(dir, `${tempId}-${info.assetName}`);
|
|
211
|
-
const extractDir = path.join(dir, `${tempId}-extract`);
|
|
212
27
|
try {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
fs.mkdirSync(extractDir, { recursive: true });
|
|
227
|
-
await extractArchive(archive, extractDir, info);
|
|
228
|
-
const extracted = path.join(extractDir, info.binaryName);
|
|
229
|
-
if (!fs.existsSync(extracted)) throw new Error(`Binary not found in archive: ${info.binaryName}`);
|
|
230
|
-
fs.renameSync(extracted, bin);
|
|
231
|
-
if (process.platform !== "win32") fs.chmodSync(bin, 0o755);
|
|
232
|
-
console.error(`Installed ${PACKAGE_NAME} to ${bin}`);
|
|
233
|
-
return bin;
|
|
234
|
-
} finally {
|
|
235
|
-
try { fs.unlinkSync(lock); } catch (_) {}
|
|
236
|
-
try { fs.unlinkSync(archive); } catch (_) {}
|
|
237
|
-
try { fs.rmSync(extractDir, { recursive: true, force: true }); } catch (_) {}
|
|
28
|
+
const pkgPath = require.resolve(`${pkgName}/package.json`);
|
|
29
|
+
const binName = process.platform === "win32" ? `${PACKAGE_NAME}.exe` : PACKAGE_NAME;
|
|
30
|
+
return path.join(path.dirname(pkgPath), "bin", binName);
|
|
31
|
+
} catch (_) {
|
|
32
|
+
console.error(`Failed to find platform package: ${pkgName}`);
|
|
33
|
+
console.error("This may happen if npm failed to install the optional dependency.");
|
|
34
|
+
console.error("");
|
|
35
|
+
console.error("Try reinstalling:");
|
|
36
|
+
console.error(` npm install ${PACKAGE_NAME}`);
|
|
37
|
+
console.error("");
|
|
38
|
+
console.error("Or install the platform package directly:");
|
|
39
|
+
console.error(` npm install ${pkgName}`);
|
|
40
|
+
process.exit(1);
|
|
238
41
|
}
|
|
239
42
|
}
|
|
240
43
|
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
const binary = optional || await downloadBinary(info);
|
|
245
|
-
const child = spawn(binary, process.argv.slice(2), {
|
|
44
|
+
function run() {
|
|
45
|
+
const binaryPath = getBinaryPath();
|
|
46
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
246
47
|
stdio: "inherit",
|
|
247
48
|
env: process.env,
|
|
248
49
|
});
|
|
50
|
+
|
|
249
51
|
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
250
52
|
process.on(signal, () => {
|
|
251
53
|
if (!child.killed) child.kill(signal);
|
|
252
54
|
});
|
|
253
55
|
}
|
|
56
|
+
|
|
254
57
|
child.on("error", (err) => {
|
|
255
58
|
console.error(`Failed to start ${PACKAGE_NAME}: ${err.message}`);
|
|
256
59
|
process.exit(1);
|
|
257
60
|
});
|
|
61
|
+
|
|
258
62
|
child.on("exit", (code, signal) => {
|
|
259
63
|
if (signal) process.exit(128 + (os.constants.signals[signal] || 0));
|
|
260
|
-
process.exit(code
|
|
64
|
+
process.exit(code ?? 0);
|
|
261
65
|
});
|
|
262
66
|
}
|
|
263
67
|
|
|
264
|
-
|
|
265
|
-
console.error(err.message || err);
|
|
266
|
-
process.exit(1);
|
|
267
|
-
});
|
|
68
|
+
run();
|