onecrawl 4.0.0-alpha.60 → 4.0.0-alpha.62

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/cli.js CHANGED
@@ -53,6 +53,12 @@ if (command === "skills-status") {
53
53
  process.exit(0);
54
54
  }
55
55
 
56
+ if (command === "ensure-chrome") {
57
+ const { ensureChrome } = await import("../lib/chrome.js");
58
+ const result = await ensureChrome();
59
+ process.exit(result ? 0 : 1);
60
+ }
61
+
56
62
  // All other commands → delegate to Rust binary
57
63
  const binary = findBinary();
58
64
  if (!binary) {
@@ -60,9 +66,10 @@ if (!binary) {
60
66
  console.error(` Run: npm run postinstall (to download it)`);
61
67
  console.error(` Or: cargo build --release -p onecrawl-cli-rs (to build from source)`);
62
68
  console.error(`\n Available npm commands:`);
63
- console.error(` onecrawl init [dir] — Install AGENTS + skills`);
64
- console.error(` onecrawl update-skills [dir] — Update skills to latest`);
65
- console.error(` onecrawl skills-status [dir] — Show installed skills`);
69
+ console.error(` onecrawl init [dir] — Install AGENTS + skills`);
70
+ console.error(` onecrawl update-skills [dir] — Update skills to latest`);
71
+ console.error(` onecrawl skills-status [dir] — Show installed skills`);
72
+ console.error(` onecrawl ensure-chrome — Detect/download Chrome`);
66
73
  process.exit(1);
67
74
  }
68
75
 
package/lib/chrome.js ADDED
@@ -0,0 +1,209 @@
1
+ // chrome.js — Detect and auto-download Chrome/Chromium for OneCrawl
2
+ import { existsSync, mkdirSync, chmodSync, unlinkSync, createWriteStream, readdirSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir, platform, arch } from "node:os";
5
+ import { execFileSync, execSync } from "node:child_process";
6
+ import { get as httpsGet } from "node:https";
7
+
8
+ const CHROME_DIR = join(homedir(), ".onecrawl", "chrome");
9
+
10
+ const CHROME_CANDIDATES = {
11
+ darwin: [
12
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
13
+ "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
14
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
15
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
16
+ ],
17
+ linux: [
18
+ "/usr/bin/google-chrome",
19
+ "/usr/bin/google-chrome-stable",
20
+ "/usr/bin/chromium",
21
+ "/usr/bin/chromium-browser",
22
+ "/snap/bin/chromium",
23
+ "/opt/google/chrome/google-chrome",
24
+ ],
25
+ win32: [
26
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
27
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
28
+ "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
29
+ ],
30
+ };
31
+
32
+ /**
33
+ * Detect an existing Chrome/Chromium installation.
34
+ * Returns the path if found, null otherwise.
35
+ */
36
+ export function detectChrome() {
37
+ const envChrome = process.env.CHROME;
38
+ if (envChrome && existsSync(envChrome)) return envChrome;
39
+
40
+ const candidates = CHROME_CANDIDATES[platform()] || [];
41
+ for (const p of candidates) {
42
+ if (existsSync(p)) return p;
43
+ }
44
+
45
+ const names = platform() === "win32"
46
+ ? ["chrome.exe", "chromium.exe", "msedge.exe"]
47
+ : ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "chrome"];
48
+ for (const name of names) {
49
+ try {
50
+ const which = platform() === "win32" ? "where" : "which";
51
+ const result = execFileSync(which, [name], { encoding: "utf8" }).trim().split("\n")[0];
52
+ if (result && existsSync(result)) return result;
53
+ } catch { /* not found */ }
54
+ }
55
+
56
+ // Check our own download directory
57
+ const localChrome = findExtractedChrome(CHROME_DIR);
58
+ if (localChrome) return localChrome;
59
+
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Ensure Chrome is available — detect or download.
65
+ * Returns the Chrome executable path.
66
+ */
67
+ export async function ensureChrome({ silent = false } = {}) {
68
+ const existing = detectChrome();
69
+ if (existing) {
70
+ if (!silent) console.log(`✅ Chrome found: ${existing}`);
71
+ return existing;
72
+ }
73
+
74
+ if (!silent) console.log("📥 No Chrome detected — downloading Chromium for Testing...");
75
+
76
+ try {
77
+ const chromePath = await downloadChromium({ silent });
78
+ if (!silent) console.log(`✅ Chromium installed at: ${chromePath}`);
79
+ return chromePath;
80
+ } catch (e) {
81
+ if (!silent) {
82
+ console.log(`⚠️ Could not auto-download Chromium: ${e.message}`);
83
+ console.log(" Install Chrome: https://www.google.com/chrome/");
84
+ console.log(" Or set CHROME=/path/to/chrome");
85
+ }
86
+ return null;
87
+ }
88
+ }
89
+
90
+ // Chrome for Testing JSON API
91
+ const CFT_VERSIONS_URL = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json";
92
+
93
+ function cftPlatform() {
94
+ const p = platform();
95
+ const a = arch();
96
+ if (p === "darwin") return a === "arm64" ? "mac-arm64" : "mac-x64";
97
+ if (p === "linux") return "linux64";
98
+ if (p === "win32") return "win64";
99
+ return null;
100
+ }
101
+
102
+ function fetchJson(url) {
103
+ return new Promise((resolve, reject) => {
104
+ const follow = (target, depth = 0) => {
105
+ if (depth > 5) return reject(new Error("Too many redirects"));
106
+ httpsGet(target, (res) => {
107
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
108
+ return follow(res.headers.location, depth + 1);
109
+ }
110
+ if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
111
+ let data = "";
112
+ res.on("data", (c) => (data += c));
113
+ res.on("end", () => { try { resolve(JSON.parse(data)); } catch (e) { reject(e); } });
114
+ }).on("error", reject);
115
+ };
116
+ follow(url);
117
+ });
118
+ }
119
+
120
+ function downloadFile(url, destPath) {
121
+ return new Promise((resolve, reject) => {
122
+ const follow = (target, depth = 0) => {
123
+ if (depth > 5) return reject(new Error("Too many redirects"));
124
+ httpsGet(target, (res) => {
125
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
126
+ return follow(res.headers.location, depth + 1);
127
+ }
128
+ if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
129
+ const file = createWriteStream(destPath);
130
+ res.pipe(file);
131
+ file.on("finish", () => { file.close(); resolve(); });
132
+ file.on("error", reject);
133
+ }).on("error", reject);
134
+ };
135
+ follow(url);
136
+ });
137
+ }
138
+
139
+ async function downloadChromium({ silent = false } = {}) {
140
+ const cftPlat = cftPlatform();
141
+ if (!cftPlat) throw new Error(`Unsupported platform: ${platform()}-${arch()}`);
142
+
143
+ const versionInfo = await fetchJson(CFT_VERSIONS_URL);
144
+ const stable = versionInfo?.channels?.Stable;
145
+ if (!stable?.version) throw new Error("Could not determine latest Chrome version");
146
+
147
+ const version = stable.version;
148
+ const chromeDownload = stable.downloads?.chrome?.find((d) => d.platform === cftPlat);
149
+ if (!chromeDownload?.url) throw new Error(`No Chrome download for ${cftPlat}`);
150
+
151
+ if (!silent) console.log(` Version: ${version} (${cftPlat})`);
152
+
153
+ mkdirSync(CHROME_DIR, { recursive: true });
154
+ const zipPath = join(CHROME_DIR, "chrome-download.zip");
155
+
156
+ if (!silent) console.log(` Downloading...`);
157
+ await downloadFile(chromeDownload.url, zipPath);
158
+
159
+ if (!silent) console.log(` Extracting...`);
160
+ if (platform() === "win32") {
161
+ execSync(`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${CHROME_DIR}' -Force"`, { stdio: "ignore" });
162
+ } else {
163
+ execSync(`unzip -o -q "${zipPath}" -d "${CHROME_DIR}"`, { stdio: "ignore" });
164
+ }
165
+
166
+ const chromeBinary = findExtractedChrome(CHROME_DIR);
167
+ if (!chromeBinary) throw new Error("Chrome binary not found after extraction");
168
+
169
+ if (platform() !== "win32") chmodSync(chromeBinary, 0o755);
170
+
171
+ try { unlinkSync(zipPath); } catch {}
172
+
173
+ process.env.CHROME = chromeBinary;
174
+ return chromeBinary;
175
+ }
176
+
177
+ function findExtractedChrome(baseDir) {
178
+ if (!existsSync(baseDir)) return null;
179
+ const targets = platform() === "darwin"
180
+ ? ["Google Chrome for Testing", "Chromium"]
181
+ : platform() === "win32"
182
+ ? ["chrome.exe"]
183
+ : ["chrome"];
184
+
185
+ return searchFile(baseDir, targets, 0, 5);
186
+ }
187
+
188
+ function searchFile(dir, targets, depth, maxDepth) {
189
+ if (depth > maxDepth) return null;
190
+ try {
191
+ const entries = readdirSync(dir);
192
+ for (const entry of entries) {
193
+ const full = join(dir, entry);
194
+ if (targets.includes(entry)) {
195
+ try { statSync(full); return full; } catch {}
196
+ }
197
+ }
198
+ for (const entry of entries) {
199
+ const full = join(dir, entry);
200
+ try {
201
+ if (statSync(full).isDirectory()) {
202
+ const found = searchFile(full, targets, depth + 1, maxDepth);
203
+ if (found) return found;
204
+ }
205
+ } catch {}
206
+ }
207
+ } catch {}
208
+ return null;
209
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onecrawl",
3
- "version": "4.0.0-alpha.60",
3
+ "version": "4.0.0-alpha.62",
4
4
  "description": "Browser automation engine — CLI, MCP server, and agent skills installer",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Giulio Leone <giulio@onecrawl.dev>",
@@ -13,7 +13,8 @@
13
13
  "type": "module",
14
14
  "exports": {
15
15
  ".": "./lib/index.js",
16
- "./skills": "./lib/skills.js"
16
+ "./skills": "./lib/skills.js",
17
+ "./chrome": "./lib/chrome.js"
17
18
  },
18
19
  "main": "./lib/index.js",
19
20
  "bin": {
@@ -110,6 +110,14 @@ async function installBinary() {
110
110
  console.log(`⚠️ Failed to install binary: ${e.message}`);
111
111
  console.log(` Build from source: cargo build --release -p onecrawl-cli-rs`);
112
112
  }
113
+
114
+ // Ensure Chrome/Chromium is available (auto-download if missing)
115
+ try {
116
+ const { ensureChrome } = await import("../lib/chrome.js");
117
+ await ensureChrome({ silent: false });
118
+ } catch {
119
+ // Non-fatal — user can install Chrome manually
120
+ }
113
121
  }
114
122
 
115
123
  installBinary();