bb-browser 0.11.0 → 0.11.2
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/chunk-FSL4RNI6.js +53 -0
- package/dist/chunk-FSL4RNI6.js.map +1 -0
- package/dist/{chunk-5WGFUZLM.js → chunk-H2VEQBAU.js} +2 -2
- package/dist/{chunk-5WGFUZLM.js.map → chunk-H2VEQBAU.js.map} +1 -1
- package/dist/cli.js +374 -70
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +48 -39
- package/dist/daemon.js.map +1 -1
- package/dist/mcp.js +25 -4
- package/dist/mcp.js.map +1 -1
- package/dist/{openclaw-bridge-Q6EFUQCH.js → openclaw-bridge-4XJKWEQ3.js} +14 -51
- package/dist/openclaw-bridge-4XJKWEQ3.js.map +1 -0
- package/package.json +4 -5
- package/dist/openclaw-bridge-Q6EFUQCH.js.map +0 -1
- package/extension/background.js +0 -3258
- package/extension/background.js.map +0 -1
- package/extension/content/trace.js +0 -339
- package/extension/content/trace.js.map +0 -1
- package/extension/manifest.json +0 -45
- package/extension/options.html +0 -26
- package/extension/options.js +0 -19
- package/extension/options.js.map +0 -1
- /package/{extension → packages/shared}/buildDomTree.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,41 +1,274 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
COMMAND_TIMEOUT,
|
|
4
|
-
DAEMON_PORT,
|
|
5
4
|
generateId
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-H2VEQBAU.js";
|
|
7
6
|
import {
|
|
8
7
|
applyJq
|
|
9
8
|
} from "./chunk-AHGAQEFO.js";
|
|
9
|
+
import {
|
|
10
|
+
parseOpenClawJson
|
|
11
|
+
} from "./chunk-FSL4RNI6.js";
|
|
10
12
|
import "./chunk-D4HDZEJT.js";
|
|
11
13
|
|
|
12
14
|
// packages/cli/src/index.ts
|
|
13
15
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14
16
|
|
|
15
17
|
// packages/cli/src/daemon-manager.ts
|
|
16
|
-
import { spawn } from "child_process";
|
|
17
|
-
import { readFile } from "fs/promises";
|
|
18
|
+
import { spawn as spawn2 } from "child_process";
|
|
19
|
+
import { readFile as readFile2, unlink } from "fs/promises";
|
|
18
20
|
import { request as httpRequest } from "http";
|
|
19
21
|
import { fileURLToPath } from "url";
|
|
20
22
|
import { dirname, resolve } from "path";
|
|
23
|
+
import { existsSync as existsSync2 } from "fs";
|
|
24
|
+
import os2 from "os";
|
|
25
|
+
import path2 from "path";
|
|
26
|
+
|
|
27
|
+
// packages/cli/src/cdp-discovery.ts
|
|
28
|
+
import { execFile, execSync, spawn } from "child_process";
|
|
21
29
|
import { existsSync } from "fs";
|
|
30
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
22
31
|
import os from "os";
|
|
23
32
|
import path from "path";
|
|
24
|
-
var
|
|
25
|
-
var
|
|
26
|
-
var
|
|
33
|
+
var DEFAULT_CDP_PORT = 19825;
|
|
34
|
+
var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
|
|
35
|
+
var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
|
|
36
|
+
var MANAGED_PORT_FILE = path.join(MANAGED_BROWSER_DIR, "cdp-port");
|
|
37
|
+
var CDP_CACHE_FILE = path.join(os.tmpdir(), "bb-browser-cdp-cache.json");
|
|
38
|
+
var CACHE_TTL_MS = 3e4;
|
|
39
|
+
function execFileAsync(command, args, timeout) {
|
|
40
|
+
return new Promise((resolve2, reject) => {
|
|
41
|
+
execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
|
|
42
|
+
if (error) {
|
|
43
|
+
reject(error);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
resolve2(stdout.trim());
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function getArgValue(flag) {
|
|
51
|
+
const index = process.argv.indexOf(flag);
|
|
52
|
+
if (index < 0) return void 0;
|
|
53
|
+
return process.argv[index + 1];
|
|
54
|
+
}
|
|
55
|
+
async function tryOpenClaw() {
|
|
56
|
+
try {
|
|
57
|
+
const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 3e4);
|
|
58
|
+
const parsed = parseOpenClawJson(raw);
|
|
59
|
+
let result = null;
|
|
60
|
+
if (parsed?.cdpUrl) {
|
|
61
|
+
try {
|
|
62
|
+
const url = new URL(parsed.cdpUrl);
|
|
63
|
+
const port = Number(url.port);
|
|
64
|
+
if (Number.isInteger(port) && port > 0) {
|
|
65
|
+
result = { host: url.hostname, port };
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!result) {
|
|
71
|
+
const port = Number(parsed?.cdpPort);
|
|
72
|
+
if (Number.isInteger(port) && port > 0) {
|
|
73
|
+
const host = parsed?.cdpHost || "127.0.0.1";
|
|
74
|
+
result = { host, port };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (result) {
|
|
78
|
+
try {
|
|
79
|
+
await writeFile(CDP_CACHE_FILE, JSON.stringify({ ...result, timestamp: Date.now() }), "utf8");
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
async function canConnect(host, port) {
|
|
89
|
+
try {
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const timeout = setTimeout(() => controller.abort(), 1200);
|
|
92
|
+
const response = await fetch(`http://${host}:${port}/json/version`, { signal: controller.signal });
|
|
93
|
+
clearTimeout(timeout);
|
|
94
|
+
return response.ok;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function findBrowserExecutable() {
|
|
100
|
+
if (process.platform === "darwin") {
|
|
101
|
+
const candidates = [
|
|
102
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
103
|
+
"/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
|
|
104
|
+
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
|
105
|
+
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
|
|
106
|
+
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
107
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
108
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
109
|
+
];
|
|
110
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
111
|
+
}
|
|
112
|
+
if (process.platform === "linux") {
|
|
113
|
+
const candidates = ["google-chrome", "google-chrome-stable", "chromium-browser", "chromium"];
|
|
114
|
+
for (const candidate of candidates) {
|
|
115
|
+
try {
|
|
116
|
+
const resolved = execSync(`which ${candidate}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
117
|
+
if (resolved) {
|
|
118
|
+
return resolved;
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
if (process.platform === "win32") {
|
|
126
|
+
const localAppData = process.env.LOCALAPPDATA ?? "";
|
|
127
|
+
const candidates = [
|
|
128
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
129
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
130
|
+
...localAppData ? [
|
|
131
|
+
`${localAppData}\\Google\\Chrome Dev\\Application\\chrome.exe`,
|
|
132
|
+
`${localAppData}\\Google\\Chrome SxS\\Application\\chrome.exe`,
|
|
133
|
+
`${localAppData}\\Google\\Chrome Beta\\Application\\chrome.exe`
|
|
134
|
+
] : [],
|
|
135
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
136
|
+
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
137
|
+
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
|
|
138
|
+
];
|
|
139
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
|
|
144
|
+
const executable = findBrowserExecutable();
|
|
145
|
+
if (!executable) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
await mkdir(MANAGED_USER_DATA_DIR, { recursive: true });
|
|
149
|
+
const defaultProfileDir = path.join(MANAGED_USER_DATA_DIR, "Default");
|
|
150
|
+
const prefsPath = path.join(defaultProfileDir, "Preferences");
|
|
151
|
+
await mkdir(defaultProfileDir, { recursive: true });
|
|
152
|
+
try {
|
|
153
|
+
let prefs = {};
|
|
154
|
+
try {
|
|
155
|
+
prefs = JSON.parse(await readFile(prefsPath, "utf8"));
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
if (!prefs.profile?.name || prefs.profile.name !== "bb-browser") {
|
|
159
|
+
prefs.profile = { ...prefs.profile || {}, name: "bb-browser" };
|
|
160
|
+
await writeFile(prefsPath, JSON.stringify(prefs), "utf8");
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
const args = [
|
|
165
|
+
`--remote-debugging-port=${port}`,
|
|
166
|
+
`--user-data-dir=${MANAGED_USER_DATA_DIR}`,
|
|
167
|
+
"--no-first-run",
|
|
168
|
+
"--no-default-browser-check",
|
|
169
|
+
"--disable-sync",
|
|
170
|
+
"--disable-background-networking",
|
|
171
|
+
"--disable-component-update",
|
|
172
|
+
"--disable-features=Translate,MediaRouter",
|
|
173
|
+
"--disable-session-crashed-bubble",
|
|
174
|
+
"--hide-crash-restore-bubble",
|
|
175
|
+
"about:blank"
|
|
176
|
+
];
|
|
177
|
+
try {
|
|
178
|
+
const child = spawn(executable, args, {
|
|
179
|
+
detached: true,
|
|
180
|
+
stdio: "ignore"
|
|
181
|
+
});
|
|
182
|
+
child.unref();
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
await mkdir(MANAGED_BROWSER_DIR, { recursive: true });
|
|
187
|
+
await writeFile(MANAGED_PORT_FILE, String(port), "utf8");
|
|
188
|
+
const deadline = Date.now() + 8e3;
|
|
189
|
+
while (Date.now() < deadline) {
|
|
190
|
+
if (await canConnect("127.0.0.1", port)) {
|
|
191
|
+
return { host: "127.0.0.1", port };
|
|
192
|
+
}
|
|
193
|
+
await new Promise((resolve2) => setTimeout(resolve2, 250));
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
async function discoverCdpPort() {
|
|
198
|
+
const envUrl = process.env.BB_BROWSER_CDP_URL;
|
|
199
|
+
if (envUrl) {
|
|
200
|
+
try {
|
|
201
|
+
const url = new URL(envUrl);
|
|
202
|
+
const port = Number(url.port);
|
|
203
|
+
if (Number.isInteger(port) && port > 0 && await canConnect(url.hostname, port)) {
|
|
204
|
+
return { host: url.hostname, port };
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const explicitPort = Number.parseInt(getArgValue("--port") ?? "", 10);
|
|
210
|
+
if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
|
|
211
|
+
return { host: "127.0.0.1", port: explicitPort };
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
|
|
215
|
+
const managedPort = Number.parseInt(rawPort.trim(), 10);
|
|
216
|
+
if (Number.isInteger(managedPort) && managedPort > 0 && await canConnect("127.0.0.1", managedPort)) {
|
|
217
|
+
return { host: "127.0.0.1", port: managedPort };
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const cacheRaw = await readFile(CDP_CACHE_FILE, "utf8");
|
|
223
|
+
const cache = JSON.parse(cacheRaw);
|
|
224
|
+
if (Date.now() - cache.timestamp < CACHE_TTL_MS && await canConnect(cache.host, cache.port)) {
|
|
225
|
+
return { host: cache.host, port: cache.port };
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
if (process.argv.includes("--openclaw")) {
|
|
230
|
+
const viaOpenClaw = await tryOpenClaw();
|
|
231
|
+
if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
|
|
232
|
+
return viaOpenClaw;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const launched = await launchManagedBrowser();
|
|
236
|
+
if (launched) {
|
|
237
|
+
return launched;
|
|
238
|
+
}
|
|
239
|
+
if (!process.argv.includes("--openclaw")) {
|
|
240
|
+
const detectedOpenClaw = await tryOpenClaw();
|
|
241
|
+
if (detectedOpenClaw && await canConnect(detectedOpenClaw.host, detectedOpenClaw.port)) {
|
|
242
|
+
return detectedOpenClaw;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// packages/cli/src/daemon-manager.ts
|
|
249
|
+
var DAEMON_DIR = process.env.BB_BROWSER_HOME || path2.join(os2.homedir(), ".bb-browser");
|
|
250
|
+
var DAEMON_JSON = path2.join(DAEMON_DIR, "daemon.json");
|
|
251
|
+
var cachedInfo = null;
|
|
27
252
|
var daemonReady = false;
|
|
28
|
-
function
|
|
253
|
+
function isProcessAlive(pid) {
|
|
254
|
+
try {
|
|
255
|
+
process.kill(pid, 0);
|
|
256
|
+
return true;
|
|
257
|
+
} catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function httpJson(method, urlPath, info, body, timeout = 5e3) {
|
|
29
262
|
return new Promise((resolve2, reject) => {
|
|
30
263
|
const payload = body !== void 0 ? JSON.stringify(body) : void 0;
|
|
31
264
|
const req = httpRequest(
|
|
32
265
|
{
|
|
33
|
-
hostname:
|
|
34
|
-
port:
|
|
266
|
+
hostname: info.host,
|
|
267
|
+
port: info.port,
|
|
35
268
|
path: urlPath,
|
|
36
269
|
method,
|
|
37
270
|
headers: {
|
|
38
|
-
Authorization: `Bearer ${token}`,
|
|
271
|
+
Authorization: `Bearer ${info.token}`,
|
|
39
272
|
...payload ? {
|
|
40
273
|
"Content-Type": "application/json",
|
|
41
274
|
"Content-Length": Buffer.byteLength(payload)
|
|
@@ -69,59 +302,81 @@ function httpJson(method, urlPath, token, body, timeout = 5e3) {
|
|
|
69
302
|
req.end();
|
|
70
303
|
});
|
|
71
304
|
}
|
|
72
|
-
async function
|
|
305
|
+
async function readDaemonJson() {
|
|
73
306
|
try {
|
|
74
|
-
|
|
307
|
+
const raw = await readFile2(DAEMON_JSON, "utf8");
|
|
308
|
+
const info = JSON.parse(raw);
|
|
309
|
+
if (typeof info.pid === "number" && typeof info.host === "string" && typeof info.port === "number" && typeof info.token === "string") {
|
|
310
|
+
return info;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
75
313
|
} catch {
|
|
76
314
|
return null;
|
|
77
315
|
}
|
|
78
316
|
}
|
|
317
|
+
async function deleteDaemonJson() {
|
|
318
|
+
try {
|
|
319
|
+
await unlink(DAEMON_JSON);
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
322
|
+
}
|
|
79
323
|
function getDaemonPath() {
|
|
80
324
|
const currentFile = fileURLToPath(import.meta.url);
|
|
81
325
|
const currentDir = dirname(currentFile);
|
|
82
326
|
const sameDirPath = resolve(currentDir, "daemon.js");
|
|
83
|
-
if (
|
|
327
|
+
if (existsSync2(sameDirPath)) {
|
|
84
328
|
return sameDirPath;
|
|
85
329
|
}
|
|
86
330
|
return resolve(currentDir, "../../daemon/dist/index.js");
|
|
87
331
|
}
|
|
88
332
|
async function ensureDaemon() {
|
|
89
|
-
if (daemonReady &&
|
|
333
|
+
if (daemonReady && cachedInfo) {
|
|
90
334
|
try {
|
|
91
|
-
await httpJson("GET", "/status",
|
|
335
|
+
await httpJson("GET", "/status", cachedInfo, void 0, 2e3);
|
|
92
336
|
return;
|
|
93
337
|
} catch {
|
|
94
338
|
daemonReady = false;
|
|
95
|
-
|
|
339
|
+
cachedInfo = null;
|
|
96
340
|
}
|
|
97
341
|
}
|
|
98
|
-
let
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
342
|
+
let info = await readDaemonJson();
|
|
343
|
+
if (info) {
|
|
344
|
+
if (!isProcessAlive(info.pid)) {
|
|
345
|
+
await deleteDaemonJson();
|
|
346
|
+
info = null;
|
|
347
|
+
} else {
|
|
348
|
+
try {
|
|
349
|
+
const status = await httpJson("GET", "/status", info, void 0, 2e3);
|
|
350
|
+
if (status.running) {
|
|
351
|
+
cachedInfo = info;
|
|
352
|
+
daemonReady = true;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
106
356
|
}
|
|
107
|
-
} catch {
|
|
108
357
|
}
|
|
109
358
|
}
|
|
359
|
+
const cdpInfo = await discoverCdpPort();
|
|
360
|
+
if (!cdpInfo) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
"bb-browser: Cannot find a Chromium-based browser.\n\nPlease do one of the following:\n 1. Install Google Chrome, Edge, or Brave\n 2. Start Chrome with: google-chrome --remote-debugging-port=19825\n 3. Set BB_BROWSER_CDP_URL=http://host:port"
|
|
363
|
+
);
|
|
364
|
+
}
|
|
110
365
|
const daemonPath = getDaemonPath();
|
|
111
|
-
const child =
|
|
366
|
+
const child = spawn2(process.execPath, [daemonPath, "--cdp-host", cdpInfo.host, "--cdp-port", String(cdpInfo.port)], {
|
|
112
367
|
detached: true,
|
|
113
368
|
stdio: "ignore"
|
|
114
369
|
});
|
|
115
370
|
child.unref();
|
|
116
|
-
const deadline = Date.now() +
|
|
371
|
+
const deadline = Date.now() + 1e4;
|
|
117
372
|
while (Date.now() < deadline) {
|
|
118
373
|
await new Promise((r) => setTimeout(r, 200));
|
|
119
|
-
|
|
120
|
-
if (!
|
|
374
|
+
info = await readDaemonJson();
|
|
375
|
+
if (!info) continue;
|
|
121
376
|
try {
|
|
122
|
-
const status = await httpJson("GET", "/status",
|
|
377
|
+
const status = await httpJson("GET", "/status", info, void 0, 2e3);
|
|
123
378
|
if (status.running) {
|
|
124
|
-
|
|
379
|
+
cachedInfo = info;
|
|
125
380
|
daemonReady = true;
|
|
126
381
|
return;
|
|
127
382
|
}
|
|
@@ -129,23 +384,35 @@ async function ensureDaemon() {
|
|
|
129
384
|
}
|
|
130
385
|
}
|
|
131
386
|
throw new Error(
|
|
132
|
-
"bb-browser: Daemon did not start in time.\n\
|
|
387
|
+
"bb-browser: Daemon did not start in time.\n\nChrome CDP is reachable, but the daemon process failed to initialize.\nTry: bb-browser daemon status"
|
|
133
388
|
);
|
|
134
389
|
}
|
|
135
390
|
async function daemonCommand(request) {
|
|
136
|
-
if (!
|
|
137
|
-
|
|
391
|
+
if (!cachedInfo) {
|
|
392
|
+
cachedInfo = await readDaemonJson();
|
|
393
|
+
}
|
|
394
|
+
if (!cachedInfo) {
|
|
395
|
+
throw new Error("No daemon.json found. Is the daemon running?");
|
|
138
396
|
}
|
|
139
|
-
|
|
140
|
-
|
|
397
|
+
return httpJson("POST", "/command", cachedInfo, request, COMMAND_TIMEOUT);
|
|
398
|
+
}
|
|
399
|
+
async function stopDaemon() {
|
|
400
|
+
const info = cachedInfo ?? await readDaemonJson();
|
|
401
|
+
if (!info) return false;
|
|
402
|
+
try {
|
|
403
|
+
await httpJson("POST", "/shutdown", info);
|
|
404
|
+
daemonReady = false;
|
|
405
|
+
cachedInfo = null;
|
|
406
|
+
return true;
|
|
407
|
+
} catch {
|
|
408
|
+
return false;
|
|
141
409
|
}
|
|
142
|
-
return httpJson("POST", "/command", cachedToken, request, COMMAND_TIMEOUT);
|
|
143
410
|
}
|
|
144
411
|
async function getDaemonStatus() {
|
|
145
|
-
const
|
|
146
|
-
if (!
|
|
412
|
+
const info = cachedInfo ?? await readDaemonJson();
|
|
413
|
+
if (!info) return null;
|
|
147
414
|
try {
|
|
148
|
-
return await httpJson("GET", "/status",
|
|
415
|
+
return await httpJson("GET", "/status", info, void 0, 2e3);
|
|
149
416
|
} catch {
|
|
150
417
|
return null;
|
|
151
418
|
}
|
|
@@ -176,8 +443,8 @@ async function sendCommand(request) {
|
|
|
176
443
|
}
|
|
177
444
|
|
|
178
445
|
// packages/cli/src/history-sqlite.ts
|
|
179
|
-
import { copyFileSync, existsSync as
|
|
180
|
-
import { execSync } from "child_process";
|
|
446
|
+
import { copyFileSync, existsSync as existsSync3, unlinkSync } from "fs";
|
|
447
|
+
import { execSync as execSync2 } from "child_process";
|
|
181
448
|
import { homedir, tmpdir } from "os";
|
|
182
449
|
import { join } from "path";
|
|
183
450
|
function getHistoryPathCandidates() {
|
|
@@ -200,7 +467,7 @@ function getHistoryPathCandidates() {
|
|
|
200
467
|
}
|
|
201
468
|
function findHistoryPath() {
|
|
202
469
|
for (const historyPath of getHistoryPathCandidates()) {
|
|
203
|
-
if (
|
|
470
|
+
if (existsSync3(historyPath)) {
|
|
204
471
|
return historyPath;
|
|
205
472
|
}
|
|
206
473
|
}
|
|
@@ -225,7 +492,7 @@ function runHistoryQuery(sql, mapRow) {
|
|
|
225
492
|
copyFileSync(historyPath, tmpPath);
|
|
226
493
|
const escapedTmpPath = tmpPath.replace(/"/g, '\\"');
|
|
227
494
|
const escapedSql = sql.replace(/"/g, '\\"');
|
|
228
|
-
const output =
|
|
495
|
+
const output = execSync2(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
|
|
229
496
|
encoding: "utf-8",
|
|
230
497
|
stdio: ["pipe", "pipe", "pipe"]
|
|
231
498
|
});
|
|
@@ -320,18 +587,18 @@ function getHistoryDomains(days) {
|
|
|
320
587
|
}
|
|
321
588
|
|
|
322
589
|
// packages/cli/src/commands/site.ts
|
|
323
|
-
import { readFileSync, readdirSync, existsSync as
|
|
590
|
+
import { readFileSync, readdirSync, existsSync as existsSync4, mkdirSync } from "fs";
|
|
324
591
|
import { join as join2, relative } from "path";
|
|
325
592
|
import { homedir as homedir2 } from "os";
|
|
326
|
-
import { execSync as
|
|
593
|
+
import { execSync as execSync3 } from "child_process";
|
|
327
594
|
var BB_DIR = join2(homedir2(), ".bb-browser");
|
|
328
595
|
var LOCAL_SITES_DIR = join2(BB_DIR, "sites");
|
|
329
596
|
var COMMUNITY_SITES_DIR = join2(BB_DIR, "bb-sites");
|
|
330
597
|
var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
|
|
331
598
|
function checkCliUpdate() {
|
|
332
599
|
try {
|
|
333
|
-
const current =
|
|
334
|
-
const latest =
|
|
600
|
+
const current = execSync3("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
601
|
+
const latest = execSync3("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
335
602
|
if (latest && current && latest !== current && latest.localeCompare(current, void 0, { numeric: true }) > 0) {
|
|
336
603
|
console.log(`
|
|
337
604
|
\u{1F4E6} bb-browser ${latest} available (current: ${current}). Run: npm install -g bb-browser`);
|
|
@@ -406,7 +673,7 @@ function parseSiteMeta(filePath, source) {
|
|
|
406
673
|
return meta;
|
|
407
674
|
}
|
|
408
675
|
function scanSites(dir, source) {
|
|
409
|
-
if (!
|
|
676
|
+
if (!existsSync4(dir)) return [];
|
|
410
677
|
const sites = [];
|
|
411
678
|
function walk(currentDir) {
|
|
412
679
|
let entries;
|
|
@@ -528,13 +795,13 @@ function siteSearch(query, options) {
|
|
|
528
795
|
}
|
|
529
796
|
function siteUpdate(options = {}) {
|
|
530
797
|
mkdirSync(BB_DIR, { recursive: true });
|
|
531
|
-
const updateMode =
|
|
798
|
+
const updateMode = existsSync4(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
|
|
532
799
|
if (updateMode === "pull") {
|
|
533
800
|
if (!options.json) {
|
|
534
801
|
console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
|
|
535
802
|
}
|
|
536
803
|
try {
|
|
537
|
-
|
|
804
|
+
execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
|
|
538
805
|
if (!options.json) {
|
|
539
806
|
console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
|
|
540
807
|
console.log("");
|
|
@@ -555,7 +822,7 @@ function siteUpdate(options = {}) {
|
|
|
555
822
|
console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
|
|
556
823
|
}
|
|
557
824
|
try {
|
|
558
|
-
|
|
825
|
+
execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
|
|
559
826
|
if (!options.json) {
|
|
560
827
|
console.log("\u514B\u9686\u5B8C\u6210\u3002");
|
|
561
828
|
console.log("");
|
|
@@ -769,7 +1036,7 @@ async function siteRun(name, args, options) {
|
|
|
769
1036
|
const argsJson = JSON.stringify(argMap);
|
|
770
1037
|
const script = `(${jsBody})(${argsJson})`;
|
|
771
1038
|
if (options.openclaw) {
|
|
772
|
-
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-
|
|
1039
|
+
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-4XJKWEQ3.js");
|
|
773
1040
|
let targetId;
|
|
774
1041
|
if (site.domain) {
|
|
775
1042
|
const tabs = ocGetTabs();
|
|
@@ -980,9 +1247,9 @@ async function siteCommand(args, options = {}) {
|
|
|
980
1247
|
}
|
|
981
1248
|
function silentUpdate() {
|
|
982
1249
|
const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
|
|
983
|
-
if (!
|
|
984
|
-
import("child_process").then(({ spawn:
|
|
985
|
-
const child =
|
|
1250
|
+
if (!existsSync4(gitDir)) return;
|
|
1251
|
+
import("child_process").then(({ spawn: spawn3 }) => {
|
|
1252
|
+
const child = spawn3("git", ["pull", "--ff-only"], {
|
|
986
1253
|
cwd: COMMUNITY_SITES_DIR,
|
|
987
1254
|
stdio: "ignore",
|
|
988
1255
|
detached: true
|
|
@@ -1280,17 +1547,17 @@ async function getCommand(attribute, ref, options = {}) {
|
|
|
1280
1547
|
|
|
1281
1548
|
// packages/cli/src/commands/screenshot.ts
|
|
1282
1549
|
import fs from "fs";
|
|
1283
|
-
import
|
|
1284
|
-
import
|
|
1550
|
+
import path3 from "path";
|
|
1551
|
+
import os3 from "os";
|
|
1285
1552
|
function getDefaultPath() {
|
|
1286
1553
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1287
1554
|
const filename = `bb-screenshot-${timestamp}.png`;
|
|
1288
|
-
return
|
|
1555
|
+
return path3.join(os3.tmpdir(), filename);
|
|
1289
1556
|
}
|
|
1290
1557
|
function saveBase64Image(dataUrl, filePath) {
|
|
1291
1558
|
const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
1292
1559
|
const buffer = Buffer.from(base64Data, "base64");
|
|
1293
|
-
const dir =
|
|
1560
|
+
const dir = path3.dirname(filePath);
|
|
1294
1561
|
if (!fs.existsSync(dir)) {
|
|
1295
1562
|
fs.mkdirSync(dir, { recursive: true });
|
|
1296
1563
|
}
|
|
@@ -1298,7 +1565,7 @@ function saveBase64Image(dataUrl, filePath) {
|
|
|
1298
1565
|
}
|
|
1299
1566
|
async function screenshotCommand(outputPath, options = {}) {
|
|
1300
1567
|
await ensureDaemonRunning();
|
|
1301
|
-
const filePath = outputPath ?
|
|
1568
|
+
const filePath = outputPath ? path3.resolve(outputPath) : getDefaultPath();
|
|
1302
1569
|
const request = {
|
|
1303
1570
|
id: generateId(),
|
|
1304
1571
|
action: "screenshot",
|
|
@@ -2369,6 +2636,14 @@ Tabs (${tabs.length}):`);
|
|
|
2369
2636
|
console.log("\nNo tabs");
|
|
2370
2637
|
}
|
|
2371
2638
|
}
|
|
2639
|
+
async function shutdownCommand(options = {}) {
|
|
2640
|
+
const ok = await stopDaemon();
|
|
2641
|
+
if (options.json) {
|
|
2642
|
+
console.log(JSON.stringify({ stopped: ok }));
|
|
2643
|
+
} else {
|
|
2644
|
+
console.log(ok ? "Daemon stopped" : "Daemon was not running");
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2372
2647
|
function formatUptime(ms) {
|
|
2373
2648
|
if (!ms || ms <= 0) return "0s";
|
|
2374
2649
|
const s = Math.floor(ms / 1e3);
|
|
@@ -2380,7 +2655,7 @@ function formatUptime(ms) {
|
|
|
2380
2655
|
}
|
|
2381
2656
|
|
|
2382
2657
|
// packages/cli/src/index.ts
|
|
2383
|
-
var VERSION = "0.11.
|
|
2658
|
+
var VERSION = "0.11.2";
|
|
2384
2659
|
var HELP_TEXT = `
|
|
2385
2660
|
bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
2386
2661
|
|
|
@@ -2435,6 +2710,7 @@ bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
|
2435
2710
|
errors [--clear] \u67E5\u770B/\u6E05\u7A7A JS \u9519\u8BEF
|
|
2436
2711
|
trace start|stop|status \u5F55\u5236\u7528\u6237\u64CD\u4F5C
|
|
2437
2712
|
history search|domains \u67E5\u770B\u6D4F\u89C8\u5386\u53F2
|
|
2713
|
+
daemon [status|stop] [opts] \u524D\u53F0\u8FD0\u884C\u6216\u7BA1\u7406 daemon
|
|
2438
2714
|
|
|
2439
2715
|
\u9009\u9879\uFF1A
|
|
2440
2716
|
--json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
|
|
@@ -2544,12 +2820,16 @@ async function main() {
|
|
|
2544
2820
|
}
|
|
2545
2821
|
if (process.argv.includes("--mcp")) {
|
|
2546
2822
|
const mcpPath = fileURLToPath2(new URL("./mcp.js", import.meta.url));
|
|
2547
|
-
const { spawn:
|
|
2548
|
-
const child =
|
|
2823
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
2824
|
+
const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
|
|
2549
2825
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
2550
2826
|
return;
|
|
2551
2827
|
}
|
|
2552
|
-
if (
|
|
2828
|
+
if (!parsed.command) {
|
|
2829
|
+
console.log(HELP_TEXT);
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
if (parsed.flags.help && parsed.command !== "daemon") {
|
|
2553
2833
|
console.log(HELP_TEXT);
|
|
2554
2834
|
return;
|
|
2555
2835
|
}
|
|
@@ -2705,7 +2985,31 @@ async function main() {
|
|
|
2705
2985
|
await getCommand(attribute, ref, { json: parsed.flags.json, tabId: globalTabId });
|
|
2706
2986
|
break;
|
|
2707
2987
|
}
|
|
2708
|
-
case "daemon":
|
|
2988
|
+
case "daemon": {
|
|
2989
|
+
const daemonSubcommand = parsed.args[0];
|
|
2990
|
+
if (daemonSubcommand === "status") {
|
|
2991
|
+
await statusCommand({ json: parsed.flags.json });
|
|
2992
|
+
break;
|
|
2993
|
+
}
|
|
2994
|
+
if (daemonSubcommand === "stop" || daemonSubcommand === "shutdown") {
|
|
2995
|
+
await shutdownCommand({ json: parsed.flags.json });
|
|
2996
|
+
break;
|
|
2997
|
+
}
|
|
2998
|
+
const daemonPath = getDaemonPath();
|
|
2999
|
+
const daemonArgs = process.argv.slice(3);
|
|
3000
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
3001
|
+
const child = spawn3(process.execPath, [daemonPath, ...daemonArgs], {
|
|
3002
|
+
stdio: "inherit"
|
|
3003
|
+
});
|
|
3004
|
+
child.on("exit", (code, signal) => {
|
|
3005
|
+
if (signal) {
|
|
3006
|
+
process.kill(process.pid, signal);
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
process.exit(code ?? 0);
|
|
3010
|
+
});
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
2709
3013
|
case "close": {
|
|
2710
3014
|
await closeCommand({ json: parsed.flags.json, tabId: globalTabId });
|
|
2711
3015
|
break;
|
|
@@ -2893,9 +3197,9 @@ async function main() {
|
|
|
2893
3197
|
break;
|
|
2894
3198
|
}
|
|
2895
3199
|
case "star": {
|
|
2896
|
-
const { execSync:
|
|
3200
|
+
const { execSync: execSync4 } = await import("child_process");
|
|
2897
3201
|
try {
|
|
2898
|
-
|
|
3202
|
+
execSync4("gh auth status", { stdio: "pipe" });
|
|
2899
3203
|
} catch {
|
|
2900
3204
|
console.error("\u9700\u8981\u5148\u5B89\u88C5\u5E76\u767B\u5F55 GitHub CLI: https://cli.github.com");
|
|
2901
3205
|
console.error(" brew install gh && gh auth login");
|
|
@@ -2904,7 +3208,7 @@ async function main() {
|
|
|
2904
3208
|
const repos = ["epiral/bb-browser", "epiral/bb-sites"];
|
|
2905
3209
|
for (const repo of repos) {
|
|
2906
3210
|
try {
|
|
2907
|
-
|
|
3211
|
+
execSync4(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
|
|
2908
3212
|
console.log(`\u2B50 Starred ${repo}`);
|
|
2909
3213
|
} catch {
|
|
2910
3214
|
console.log(`Already starred or failed: ${repo}`);
|