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 +10 -3
- package/lib/chrome.js +209 -0
- package/package.json +3 -2
- package/scripts/postinstall.js +8 -0
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]
|
|
64
|
-
console.error(` onecrawl update-skills [dir]
|
|
65
|
-
console.error(` onecrawl skills-status [dir]
|
|
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.
|
|
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": {
|
package/scripts/postinstall.js
CHANGED
|
@@ -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();
|