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.
Files changed (2) hide show
  1. package/package.json +6 -6
  2. 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.2",
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.2",
49
- "@epsiekygrr_zedxx/grok-search-rs-linux-x64": "0.1.2",
50
- "@epsiekygrr_zedxx/grok-search-rs-linux-arm64": "0.1.2",
51
- "@epsiekygrr_zedxx/grok-search-rs-win32-x64": "0.1.2",
52
- "@epsiekygrr_zedxx/grok-search-rs-win32-arm64": "0.1.2"
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 REPO_OWNER = "Episkey-G";
11
- const REPO_NAME = "GrokSearch-rs";
12
- const REQUEST_TIMEOUT = 60000;
13
- const MAX_REDIRECTS = 10;
14
- const MAX_RETRIES = 3;
15
-
16
- function version() {
17
- return require("./package.json").version;
18
- }
19
-
20
- function platformInfo() {
21
- const platform = process.platform;
22
- const arch = process.arch;
23
- if (platform === "darwin" && (arch === "x64" || arch === "arm64")) {
24
- return {
25
- packageName: "@epsiekygrr_zedxx/grok-search-rs-darwin-universal",
26
- assetName: "grok-search-rs_Darwin_universal.tar.gz",
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
- if (fs.existsSync(bin)) return bin;
214
- console.error(`Downloading ${PACKAGE_NAME} v${version()}...`);
215
- const release = await releaseForVersion();
216
- const asset = release.assets.find((item) => item.name === info.assetName);
217
- if (!asset) {
218
- throw new Error(`No matching release asset: ${info.assetName}`);
219
- }
220
- const headers = {
221
- "User-Agent": PACKAGE_NAME,
222
- Accept: "application/octet-stream",
223
- ...(process.env.GITHUB_TOKEN ? { Authorization: `token ${process.env.GITHUB_TOKEN}` } : {}),
224
- };
225
- await withRetry(() => downloadFile(asset.browser_download_url, archive, { headers }));
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
- async function main() {
242
- const info = platformInfo();
243
- const optional = findOptionalBinary(info);
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 || 0);
64
+ process.exit(code ?? 0);
261
65
  });
262
66
  }
263
67
 
264
- main().catch((err) => {
265
- console.error(err.message || err);
266
- process.exit(1);
267
- });
68
+ run();