bb-browser-api 0.11.5
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/LICENSE +21 -0
- package/README.md +377 -0
- package/README.zh-CN.md +392 -0
- package/dist/chunk-3H3RKS2K.js +110 -0
- package/dist/chunk-3H3RKS2K.js.map +1 -0
- package/dist/chunk-CRK6CV23.js +494 -0
- package/dist/chunk-CRK6CV23.js.map +1 -0
- package/dist/chunk-D4HDZEJT.js +37 -0
- package/dist/chunk-D4HDZEJT.js.map +1 -0
- package/dist/chunk-ERAIAHQ5.js +4053 -0
- package/dist/chunk-ERAIAHQ5.js.map +1 -0
- package/dist/chunk-YEQ2M2XG.js +53 -0
- package/dist/chunk-YEQ2M2XG.js.map +1 -0
- package/dist/cli.js +3277 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon.js +2472 -0
- package/dist/daemon.js.map +1 -0
- package/dist/jq-J32FKI7Z.js +9 -0
- package/dist/jq-J32FKI7Z.js.map +1 -0
- package/dist/mcp.js +17512 -0
- package/dist/mcp.js.map +1 -0
- package/dist/openclaw-bridge-GWIKCRBD.js +69 -0
- package/dist/openclaw-bridge-GWIKCRBD.js.map +1 -0
- package/dist/provider.js +7940 -0
- package/dist/provider.js.map +1 -0
- package/package.json +81 -0
- package/packages/shared/buildDomTree.js +1501 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3277 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
COMMAND_TIMEOUT,
|
|
4
|
+
DAEMON_JSON,
|
|
5
|
+
generateId,
|
|
6
|
+
httpJson,
|
|
7
|
+
isProcessAlive,
|
|
8
|
+
readDaemonJson
|
|
9
|
+
} from "./chunk-CRK6CV23.js";
|
|
10
|
+
import "./chunk-ERAIAHQ5.js";
|
|
11
|
+
import {
|
|
12
|
+
applyJq
|
|
13
|
+
} from "./chunk-3H3RKS2K.js";
|
|
14
|
+
import {
|
|
15
|
+
parseOpenClawJson
|
|
16
|
+
} from "./chunk-YEQ2M2XG.js";
|
|
17
|
+
import "./chunk-D4HDZEJT.js";
|
|
18
|
+
|
|
19
|
+
// packages/cli/src/index.ts
|
|
20
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
21
|
+
|
|
22
|
+
// packages/cli/src/daemon-manager.ts
|
|
23
|
+
import { spawn as spawn2 } from "child_process";
|
|
24
|
+
import { unlink } from "fs/promises";
|
|
25
|
+
import { fileURLToPath } from "url";
|
|
26
|
+
import { dirname, resolve } from "path";
|
|
27
|
+
import { existsSync as existsSync2 } from "fs";
|
|
28
|
+
|
|
29
|
+
// packages/cli/src/cdp-discovery.ts
|
|
30
|
+
import { execFile, execSync, spawn } from "child_process";
|
|
31
|
+
import { existsSync } from "fs";
|
|
32
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
33
|
+
import os from "os";
|
|
34
|
+
import path from "path";
|
|
35
|
+
var DEFAULT_CDP_PORT = 19825;
|
|
36
|
+
var MANAGED_BROWSER_DIR = path.join(os.homedir(), ".bb-browser", "browser");
|
|
37
|
+
var MANAGED_USER_DATA_DIR = path.join(MANAGED_BROWSER_DIR, "user-data");
|
|
38
|
+
var MANAGED_PORT_FILE = path.join(MANAGED_BROWSER_DIR, "cdp-port");
|
|
39
|
+
var CDP_CACHE_FILE = path.join(os.tmpdir(), "bb-browser-cdp-cache.json");
|
|
40
|
+
var CACHE_TTL_MS = 3e4;
|
|
41
|
+
function execFileAsync(command, args, timeout) {
|
|
42
|
+
return new Promise((resolve2, reject) => {
|
|
43
|
+
execFile(command, args, { encoding: "utf8", timeout }, (error, stdout) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
reject(error);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
resolve2(stdout.trim());
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function getArgValue(flag) {
|
|
53
|
+
const index = process.argv.indexOf(flag);
|
|
54
|
+
if (index < 0) return void 0;
|
|
55
|
+
return process.argv[index + 1];
|
|
56
|
+
}
|
|
57
|
+
async function tryOpenClaw() {
|
|
58
|
+
try {
|
|
59
|
+
const raw = await execFileAsync("npx", ["openclaw", "browser", "status", "--json"], 3e4);
|
|
60
|
+
const parsed = parseOpenClawJson(raw);
|
|
61
|
+
let result = null;
|
|
62
|
+
if (parsed?.cdpUrl) {
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(parsed.cdpUrl);
|
|
65
|
+
const port = Number(url.port);
|
|
66
|
+
if (Number.isInteger(port) && port > 0) {
|
|
67
|
+
result = { host: url.hostname, port };
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!result) {
|
|
73
|
+
const port = Number(parsed?.cdpPort);
|
|
74
|
+
if (Number.isInteger(port) && port > 0) {
|
|
75
|
+
const host = parsed?.cdpHost || "127.0.0.1";
|
|
76
|
+
result = { host, port };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (result) {
|
|
80
|
+
try {
|
|
81
|
+
await writeFile(CDP_CACHE_FILE, JSON.stringify({ ...result, timestamp: Date.now() }), "utf8");
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
async function canConnect(host, port) {
|
|
91
|
+
try {
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
const timeout = setTimeout(() => controller.abort(), 1200);
|
|
94
|
+
const response = await fetch(`http://${host}:${port}/json/version`, { signal: controller.signal });
|
|
95
|
+
clearTimeout(timeout);
|
|
96
|
+
return response.ok;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function findBrowserExecutable() {
|
|
102
|
+
if (process.platform === "darwin") {
|
|
103
|
+
const candidates = [
|
|
104
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
105
|
+
"/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev",
|
|
106
|
+
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
|
107
|
+
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
|
|
108
|
+
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
109
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
110
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
111
|
+
];
|
|
112
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
113
|
+
}
|
|
114
|
+
if (process.platform === "linux") {
|
|
115
|
+
const candidates = ["google-chrome", "google-chrome-stable", "chromium-browser", "chromium"];
|
|
116
|
+
for (const candidate of candidates) {
|
|
117
|
+
try {
|
|
118
|
+
const resolved = execSync(`which ${candidate}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
119
|
+
if (resolved) {
|
|
120
|
+
return resolved;
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (process.platform === "win32") {
|
|
128
|
+
const localAppData = process.env.LOCALAPPDATA ?? "";
|
|
129
|
+
const candidates = [
|
|
130
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
131
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
132
|
+
...localAppData ? [
|
|
133
|
+
`${localAppData}\\Google\\Chrome Dev\\Application\\chrome.exe`,
|
|
134
|
+
`${localAppData}\\Google\\Chrome SxS\\Application\\chrome.exe`,
|
|
135
|
+
`${localAppData}\\Google\\Chrome Beta\\Application\\chrome.exe`
|
|
136
|
+
] : [],
|
|
137
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
138
|
+
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
139
|
+
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
|
|
140
|
+
];
|
|
141
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
async function launchManagedBrowser(port = DEFAULT_CDP_PORT) {
|
|
146
|
+
const executable = findBrowserExecutable();
|
|
147
|
+
if (!executable) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
await mkdir(MANAGED_USER_DATA_DIR, { recursive: true });
|
|
151
|
+
const defaultProfileDir = path.join(MANAGED_USER_DATA_DIR, "Default");
|
|
152
|
+
const prefsPath = path.join(defaultProfileDir, "Preferences");
|
|
153
|
+
await mkdir(defaultProfileDir, { recursive: true });
|
|
154
|
+
try {
|
|
155
|
+
let prefs = {};
|
|
156
|
+
try {
|
|
157
|
+
prefs = JSON.parse(await readFile(prefsPath, "utf8"));
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
if (!prefs.profile?.name || prefs.profile.name !== "bb-browser") {
|
|
161
|
+
prefs.profile = { ...prefs.profile || {}, name: "bb-browser" };
|
|
162
|
+
await writeFile(prefsPath, JSON.stringify(prefs), "utf8");
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
const args = [
|
|
167
|
+
`--remote-debugging-port=${port}`,
|
|
168
|
+
`--user-data-dir=${MANAGED_USER_DATA_DIR}`,
|
|
169
|
+
"--no-first-run",
|
|
170
|
+
"--no-default-browser-check",
|
|
171
|
+
"--disable-sync",
|
|
172
|
+
"--disable-background-networking",
|
|
173
|
+
"--disable-component-update",
|
|
174
|
+
"--disable-features=Translate,MediaRouter",
|
|
175
|
+
"--disable-session-crashed-bubble",
|
|
176
|
+
"--hide-crash-restore-bubble",
|
|
177
|
+
"--use-mock-keychain",
|
|
178
|
+
"about:blank"
|
|
179
|
+
];
|
|
180
|
+
try {
|
|
181
|
+
const child = spawn(executable, args, {
|
|
182
|
+
detached: true,
|
|
183
|
+
stdio: "ignore"
|
|
184
|
+
});
|
|
185
|
+
child.unref();
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
await mkdir(MANAGED_BROWSER_DIR, { recursive: true });
|
|
190
|
+
await writeFile(MANAGED_PORT_FILE, String(port), "utf8");
|
|
191
|
+
const deadline = Date.now() + 8e3;
|
|
192
|
+
while (Date.now() < deadline) {
|
|
193
|
+
if (await canConnect("127.0.0.1", port)) {
|
|
194
|
+
return { host: "127.0.0.1", port };
|
|
195
|
+
}
|
|
196
|
+
await new Promise((resolve2) => setTimeout(resolve2, 250));
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
async function discoverCdpPort() {
|
|
201
|
+
const envUrl = process.env.BB_BROWSER_CDP_URL;
|
|
202
|
+
if (envUrl) {
|
|
203
|
+
try {
|
|
204
|
+
const url = new URL(envUrl);
|
|
205
|
+
const port = Number(url.port);
|
|
206
|
+
if (Number.isInteger(port) && port > 0 && await canConnect(url.hostname, port)) {
|
|
207
|
+
return { host: url.hostname, port };
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const explicitPort = Number.parseInt(getArgValue("--port") ?? "", 10);
|
|
213
|
+
if (Number.isInteger(explicitPort) && explicitPort > 0 && await canConnect("127.0.0.1", explicitPort)) {
|
|
214
|
+
return { host: "127.0.0.1", port: explicitPort };
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const rawPort = await readFile(MANAGED_PORT_FILE, "utf8");
|
|
218
|
+
const managedPort = Number.parseInt(rawPort.trim(), 10);
|
|
219
|
+
if (Number.isInteger(managedPort) && managedPort > 0 && await canConnect("127.0.0.1", managedPort)) {
|
|
220
|
+
return { host: "127.0.0.1", port: managedPort };
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const cacheRaw = await readFile(CDP_CACHE_FILE, "utf8");
|
|
226
|
+
const cache = JSON.parse(cacheRaw);
|
|
227
|
+
if (Date.now() - cache.timestamp < CACHE_TTL_MS && await canConnect(cache.host, cache.port)) {
|
|
228
|
+
return { host: cache.host, port: cache.port };
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
if (process.argv.includes("--openclaw")) {
|
|
233
|
+
const viaOpenClaw = await tryOpenClaw();
|
|
234
|
+
if (viaOpenClaw && await canConnect(viaOpenClaw.host, viaOpenClaw.port)) {
|
|
235
|
+
return viaOpenClaw;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const launched = await launchManagedBrowser();
|
|
239
|
+
if (launched) {
|
|
240
|
+
return launched;
|
|
241
|
+
}
|
|
242
|
+
if (!process.argv.includes("--openclaw")) {
|
|
243
|
+
const detectedOpenClaw = await tryOpenClaw();
|
|
244
|
+
if (detectedOpenClaw && await canConnect(detectedOpenClaw.host, detectedOpenClaw.port)) {
|
|
245
|
+
return detectedOpenClaw;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// packages/cli/src/daemon-manager.ts
|
|
252
|
+
var cachedInfo = null;
|
|
253
|
+
var daemonReady = false;
|
|
254
|
+
async function deleteDaemonJson() {
|
|
255
|
+
try {
|
|
256
|
+
await unlink(DAEMON_JSON);
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function getDaemonPath() {
|
|
261
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
262
|
+
const currentDir = dirname(currentFile);
|
|
263
|
+
const sameDirPath = resolve(currentDir, "daemon.js");
|
|
264
|
+
if (existsSync2(sameDirPath)) {
|
|
265
|
+
return sameDirPath;
|
|
266
|
+
}
|
|
267
|
+
return resolve(currentDir, "../../daemon/dist/index.js");
|
|
268
|
+
}
|
|
269
|
+
async function ensureDaemon() {
|
|
270
|
+
if (daemonReady && cachedInfo) {
|
|
271
|
+
try {
|
|
272
|
+
const status = await httpJson("GET", "/status", cachedInfo, void 0, 2e3);
|
|
273
|
+
if (status.running && status.cdpConnected !== false) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
daemonReady = false;
|
|
279
|
+
cachedInfo = null;
|
|
280
|
+
}
|
|
281
|
+
let info = await readDaemonJson();
|
|
282
|
+
if (info) {
|
|
283
|
+
if (!isProcessAlive(info.pid)) {
|
|
284
|
+
await deleteDaemonJson();
|
|
285
|
+
info = null;
|
|
286
|
+
} else {
|
|
287
|
+
try {
|
|
288
|
+
const status = await httpJson("GET", "/status", info, void 0, 2e3);
|
|
289
|
+
if (status.running && status.cdpConnected !== false) {
|
|
290
|
+
cachedInfo = info;
|
|
291
|
+
daemonReady = true;
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (status.running && status.cdpConnected === false) {
|
|
295
|
+
await stopDaemon();
|
|
296
|
+
await deleteDaemonJson();
|
|
297
|
+
info = null;
|
|
298
|
+
}
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const cdpInfo = await discoverCdpPort();
|
|
304
|
+
if (!cdpInfo) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
"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"
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
const daemonPath = getDaemonPath();
|
|
310
|
+
const child = spawn2(process.execPath, [daemonPath, "--cdp-host", cdpInfo.host, "--cdp-port", String(cdpInfo.port)], {
|
|
311
|
+
detached: true,
|
|
312
|
+
stdio: "ignore"
|
|
313
|
+
});
|
|
314
|
+
child.unref();
|
|
315
|
+
const deadline = Date.now() + 1e4;
|
|
316
|
+
while (Date.now() < deadline) {
|
|
317
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
318
|
+
info = await readDaemonJson();
|
|
319
|
+
if (!info) continue;
|
|
320
|
+
try {
|
|
321
|
+
const status = await httpJson("GET", "/status", info, void 0, 2e3);
|
|
322
|
+
if (status.running) {
|
|
323
|
+
cachedInfo = info;
|
|
324
|
+
daemonReady = true;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
throw new Error(
|
|
331
|
+
"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"
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
async function daemonCommand(request) {
|
|
335
|
+
if (!cachedInfo) {
|
|
336
|
+
cachedInfo = await readDaemonJson();
|
|
337
|
+
}
|
|
338
|
+
if (!cachedInfo) {
|
|
339
|
+
throw new Error("No daemon.json found. Is the daemon running?");
|
|
340
|
+
}
|
|
341
|
+
return httpJson("POST", "/command", cachedInfo, request, COMMAND_TIMEOUT);
|
|
342
|
+
}
|
|
343
|
+
async function stopDaemon() {
|
|
344
|
+
const info = cachedInfo ?? await readDaemonJson();
|
|
345
|
+
if (!info) return false;
|
|
346
|
+
try {
|
|
347
|
+
await httpJson("POST", "/shutdown", info);
|
|
348
|
+
daemonReady = false;
|
|
349
|
+
cachedInfo = null;
|
|
350
|
+
return true;
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function getDaemonStatus() {
|
|
356
|
+
const info = cachedInfo ?? await readDaemonJson();
|
|
357
|
+
if (!info) return null;
|
|
358
|
+
try {
|
|
359
|
+
return await httpJson("GET", "/status", info, void 0, 2e3);
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
var ensureDaemonRunning = ensureDaemon;
|
|
365
|
+
|
|
366
|
+
// packages/cli/src/client.ts
|
|
367
|
+
var jqExpression;
|
|
368
|
+
function setJqExpression(expression) {
|
|
369
|
+
jqExpression = expression;
|
|
370
|
+
}
|
|
371
|
+
function printJqResults(response) {
|
|
372
|
+
const target = response.data ?? response;
|
|
373
|
+
const results = applyJq(target, jqExpression || ".");
|
|
374
|
+
for (const result of results) {
|
|
375
|
+
console.log(typeof result === "string" ? result : JSON.stringify(result));
|
|
376
|
+
}
|
|
377
|
+
process.exit(0);
|
|
378
|
+
}
|
|
379
|
+
function handleJqResponse(response) {
|
|
380
|
+
if (jqExpression) {
|
|
381
|
+
printJqResults(response);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function sendCommand(request) {
|
|
385
|
+
await ensureDaemon();
|
|
386
|
+
return daemonCommand(request);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// packages/cli/src/history-sqlite.ts
|
|
390
|
+
import { copyFileSync, existsSync as existsSync3, unlinkSync } from "fs";
|
|
391
|
+
import { execSync as execSync2 } from "child_process";
|
|
392
|
+
import { homedir, tmpdir } from "os";
|
|
393
|
+
import { join } from "path";
|
|
394
|
+
function getHistoryPathCandidates() {
|
|
395
|
+
const home = homedir();
|
|
396
|
+
const localAppData = process.env.LOCALAPPDATA || "";
|
|
397
|
+
const candidates = [
|
|
398
|
+
join(home, "Library/Application Support/Google/Chrome/Default/History"),
|
|
399
|
+
join(home, "Library/Application Support/Microsoft Edge/Default/History"),
|
|
400
|
+
join(home, "Library/Application Support/BraveSoftware/Brave-Browser/Default/History"),
|
|
401
|
+
join(home, "Library/Application Support/Arc/User Data/Default/History"),
|
|
402
|
+
join(home, ".config/google-chrome/Default/History")
|
|
403
|
+
];
|
|
404
|
+
if (localAppData) {
|
|
405
|
+
candidates.push(
|
|
406
|
+
join(localAppData, "Google/Chrome/User Data/Default/History"),
|
|
407
|
+
join(localAppData, "Microsoft/Edge/User Data/Default/History")
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return candidates;
|
|
411
|
+
}
|
|
412
|
+
function findHistoryPath() {
|
|
413
|
+
for (const historyPath of getHistoryPathCandidates()) {
|
|
414
|
+
if (existsSync3(historyPath)) {
|
|
415
|
+
return historyPath;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
function sqlEscape(value) {
|
|
421
|
+
return value.replace(/'/g, "''");
|
|
422
|
+
}
|
|
423
|
+
function buildTimeWhere(days) {
|
|
424
|
+
if (!days || days <= 0) {
|
|
425
|
+
return "";
|
|
426
|
+
}
|
|
427
|
+
return `last_visit_time > (strftime('%s', 'now') - ${Math.floor(days)}*86400) * 1000000 + 11644473600000000`;
|
|
428
|
+
}
|
|
429
|
+
function runHistoryQuery(sql, mapRow) {
|
|
430
|
+
const historyPath = findHistoryPath();
|
|
431
|
+
if (!historyPath) {
|
|
432
|
+
return [];
|
|
433
|
+
}
|
|
434
|
+
const tmpPath = join(tmpdir(), `bb-history-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
|
|
435
|
+
try {
|
|
436
|
+
copyFileSync(historyPath, tmpPath);
|
|
437
|
+
const escapedTmpPath = tmpPath.replace(/"/g, '\\"');
|
|
438
|
+
const escapedSql = sql.replace(/"/g, '\\"');
|
|
439
|
+
const output = execSync2(`sqlite3 -separator $'\\t' "${escapedTmpPath}" "${escapedSql}"`, {
|
|
440
|
+
encoding: "utf-8",
|
|
441
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
442
|
+
});
|
|
443
|
+
return output.split("\n").filter(Boolean).map((line) => mapRow(line.split(" "))).filter((item) => item !== null);
|
|
444
|
+
} catch {
|
|
445
|
+
return [];
|
|
446
|
+
} finally {
|
|
447
|
+
try {
|
|
448
|
+
unlinkSync(tmpPath);
|
|
449
|
+
} catch {
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function searchHistory(query, days) {
|
|
454
|
+
const conditions = [];
|
|
455
|
+
const trimmedQuery = query?.trim();
|
|
456
|
+
if (trimmedQuery) {
|
|
457
|
+
const escapedQuery = sqlEscape(trimmedQuery);
|
|
458
|
+
conditions.push(`(url LIKE '%${escapedQuery}%' OR title LIKE '%${escapedQuery}%')`);
|
|
459
|
+
}
|
|
460
|
+
const timeWhere = buildTimeWhere(days);
|
|
461
|
+
if (timeWhere) {
|
|
462
|
+
conditions.push(timeWhere);
|
|
463
|
+
}
|
|
464
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
465
|
+
const sql = `
|
|
466
|
+
SELECT
|
|
467
|
+
url,
|
|
468
|
+
REPLACE(IFNULL(title, ''), char(9), ' '),
|
|
469
|
+
IFNULL(visit_count, 0),
|
|
470
|
+
IFNULL(last_visit_time, 0)
|
|
471
|
+
FROM urls
|
|
472
|
+
${whereClause}
|
|
473
|
+
ORDER BY last_visit_time DESC
|
|
474
|
+
LIMIT 100;
|
|
475
|
+
`.trim();
|
|
476
|
+
return runHistoryQuery(sql, (row) => {
|
|
477
|
+
if (row.length < 4) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const chromeTimestamp = Number(row[3]) || 0;
|
|
481
|
+
return {
|
|
482
|
+
url: row[0] || "",
|
|
483
|
+
title: row[1] || "",
|
|
484
|
+
visitCount: Number(row[2]) || 0,
|
|
485
|
+
lastVisitTime: chromeTimestamp > 0 ? chromeTimestamp / 1e6 - 11644473600 : 0
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
function getHistoryDomains(days) {
|
|
490
|
+
const timeWhere = buildTimeWhere(days);
|
|
491
|
+
const whereClause = timeWhere ? `WHERE ${timeWhere}` : "";
|
|
492
|
+
const sql = `
|
|
493
|
+
SELECT
|
|
494
|
+
domain,
|
|
495
|
+
SUM(visit_count) AS visits,
|
|
496
|
+
GROUP_CONCAT(title, char(31)) AS titles
|
|
497
|
+
FROM (
|
|
498
|
+
SELECT
|
|
499
|
+
CASE
|
|
500
|
+
WHEN instr(url, '//') > 0 AND instr(substr(url, instr(url, '//') + 2), '/') > 0
|
|
501
|
+
THEN substr(
|
|
502
|
+
substr(url, instr(url, '//') + 2),
|
|
503
|
+
1,
|
|
504
|
+
instr(substr(url, instr(url, '//') + 2), '/') - 1
|
|
505
|
+
)
|
|
506
|
+
WHEN instr(url, '//') > 0 THEN substr(url, instr(url, '//') + 2)
|
|
507
|
+
WHEN instr(url, '/') > 0 THEN substr(url, 1, instr(url, '/') - 1)
|
|
508
|
+
ELSE url
|
|
509
|
+
END AS domain,
|
|
510
|
+
IFNULL(visit_count, 0) AS visit_count,
|
|
511
|
+
REPLACE(IFNULL(title, ''), char(31), ' ') AS title
|
|
512
|
+
FROM urls
|
|
513
|
+
${whereClause}
|
|
514
|
+
)
|
|
515
|
+
WHERE domain != ''
|
|
516
|
+
GROUP BY domain
|
|
517
|
+
ORDER BY visits DESC
|
|
518
|
+
LIMIT 50;
|
|
519
|
+
`.trim();
|
|
520
|
+
return runHistoryQuery(sql, (row) => {
|
|
521
|
+
if (row.length < 3) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
const titles = row[2] ? Array.from(new Set(row[2].split(String.fromCharCode(31)).map((title) => title.trim()).filter(Boolean))).slice(0, 10) : [];
|
|
525
|
+
return {
|
|
526
|
+
domain: row[0] || "",
|
|
527
|
+
visits: Number(row[1]) || 0,
|
|
528
|
+
titles
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// packages/cli/src/commands/site.ts
|
|
534
|
+
import { readFileSync, readdirSync, existsSync as existsSync4, mkdirSync } from "fs";
|
|
535
|
+
import { join as join2, relative } from "path";
|
|
536
|
+
import { homedir as homedir2 } from "os";
|
|
537
|
+
import { execSync as execSync3 } from "child_process";
|
|
538
|
+
var BB_DIR = join2(homedir2(), ".bb-browser");
|
|
539
|
+
var LOCAL_SITES_DIR = join2(BB_DIR, "sites");
|
|
540
|
+
var COMMUNITY_SITES_DIR = join2(BB_DIR, "bb-sites");
|
|
541
|
+
var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
|
|
542
|
+
function checkCliUpdate() {
|
|
543
|
+
try {
|
|
544
|
+
const current = execSync3("bb-browser --version", { timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
545
|
+
const latest = execSync3("npm view bb-browser version", { timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
546
|
+
if (latest && current && latest !== current && latest.localeCompare(current, void 0, { numeric: true }) > 0) {
|
|
547
|
+
console.log(`
|
|
548
|
+
\u{1F4E6} bb-browser ${latest} available (current: ${current}). Run: npm install -g bb-browser`);
|
|
549
|
+
}
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function exitJsonError(error, extra = {}) {
|
|
554
|
+
console.log(JSON.stringify({ success: false, error, ...extra }, null, 2));
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
function parseSiteMeta(filePath, source) {
|
|
558
|
+
let content;
|
|
559
|
+
try {
|
|
560
|
+
content = readFileSync(filePath, "utf-8");
|
|
561
|
+
} catch {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
const sitesDir = source === "local" ? LOCAL_SITES_DIR : COMMUNITY_SITES_DIR;
|
|
565
|
+
const relPath = relative(sitesDir, filePath);
|
|
566
|
+
const defaultName = relPath.replace(/\.js$/, "").replace(/\\/g, "/");
|
|
567
|
+
const metaMatch = content.match(/\/\*\s*@meta\s*\n([\s\S]*?)\*\//);
|
|
568
|
+
if (metaMatch) {
|
|
569
|
+
try {
|
|
570
|
+
const metaJson = JSON.parse(metaMatch[1]);
|
|
571
|
+
return {
|
|
572
|
+
name: metaJson.name || defaultName,
|
|
573
|
+
description: metaJson.description || "",
|
|
574
|
+
domain: metaJson.domain || "",
|
|
575
|
+
args: metaJson.args || {},
|
|
576
|
+
capabilities: metaJson.capabilities,
|
|
577
|
+
readOnly: metaJson.readOnly,
|
|
578
|
+
example: metaJson.example,
|
|
579
|
+
filePath,
|
|
580
|
+
source
|
|
581
|
+
};
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const meta = {
|
|
586
|
+
name: defaultName,
|
|
587
|
+
description: "",
|
|
588
|
+
domain: "",
|
|
589
|
+
args: {},
|
|
590
|
+
filePath,
|
|
591
|
+
source
|
|
592
|
+
};
|
|
593
|
+
const tagPattern = /\/\/\s*@(\w+)[ \t]+(.*)/g;
|
|
594
|
+
let match;
|
|
595
|
+
while ((match = tagPattern.exec(content)) !== null) {
|
|
596
|
+
const [, key, value] = match;
|
|
597
|
+
switch (key) {
|
|
598
|
+
case "name":
|
|
599
|
+
meta.name = value.trim();
|
|
600
|
+
break;
|
|
601
|
+
case "description":
|
|
602
|
+
meta.description = value.trim();
|
|
603
|
+
break;
|
|
604
|
+
case "domain":
|
|
605
|
+
meta.domain = value.trim();
|
|
606
|
+
break;
|
|
607
|
+
case "args":
|
|
608
|
+
for (const arg of value.trim().split(/[,\s]+/).filter(Boolean)) {
|
|
609
|
+
meta.args[arg] = { required: true };
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
case "example":
|
|
613
|
+
meta.example = value.trim();
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return meta;
|
|
618
|
+
}
|
|
619
|
+
function scanSites(dir, source) {
|
|
620
|
+
if (!existsSync4(dir)) return [];
|
|
621
|
+
const sites = [];
|
|
622
|
+
function walk(currentDir) {
|
|
623
|
+
let entries;
|
|
624
|
+
try {
|
|
625
|
+
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
626
|
+
} catch {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
for (const entry of entries) {
|
|
630
|
+
const fullPath = join2(currentDir, entry.name);
|
|
631
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
632
|
+
walk(fullPath);
|
|
633
|
+
} else if (entry.isFile() && entry.name.endsWith(".js")) {
|
|
634
|
+
const meta = parseSiteMeta(fullPath, source);
|
|
635
|
+
if (meta) sites.push(meta);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
walk(dir);
|
|
640
|
+
return sites;
|
|
641
|
+
}
|
|
642
|
+
function getSiteHintForDomain(url) {
|
|
643
|
+
try {
|
|
644
|
+
const hostname = new URL(url).hostname;
|
|
645
|
+
const sites = getAllSites();
|
|
646
|
+
const matched = sites.filter((s) => s.domain && (hostname === s.domain || hostname.endsWith("." + s.domain)));
|
|
647
|
+
if (matched.length === 0) return null;
|
|
648
|
+
const names = matched.map((s) => s.name);
|
|
649
|
+
const example = matched[0].example || `bb-browser site ${names[0]}`;
|
|
650
|
+
return `\u8BE5\u7F51\u7AD9\u6709 ${names.length} \u4E2A site adapter \u53EF\u76F4\u63A5\u83B7\u53D6\u6570\u636E\uFF0C\u65E0\u9700\u624B\u52A8\u64CD\u4F5C\u6D4F\u89C8\u5668\u3002\u8BD5\u8BD5: ${example}`;
|
|
651
|
+
} catch {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function getAllSites() {
|
|
656
|
+
const community = scanSites(COMMUNITY_SITES_DIR, "community");
|
|
657
|
+
const local = scanSites(LOCAL_SITES_DIR, "local");
|
|
658
|
+
const byName = /* @__PURE__ */ new Map();
|
|
659
|
+
for (const s of community) byName.set(s.name, s);
|
|
660
|
+
for (const s of local) byName.set(s.name, s);
|
|
661
|
+
return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
662
|
+
}
|
|
663
|
+
function matchTabOrigin(tabUrl, domain) {
|
|
664
|
+
try {
|
|
665
|
+
const tabOrigin = new URL(tabUrl).hostname;
|
|
666
|
+
return tabOrigin === domain || tabOrigin.endsWith("." + domain);
|
|
667
|
+
} catch {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function siteList(options) {
|
|
672
|
+
const sites = getAllSites();
|
|
673
|
+
if (sites.length === 0) {
|
|
674
|
+
if (options.json) {
|
|
675
|
+
console.log("[]");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
console.log("\u672A\u627E\u5230\u4EFB\u4F55 site adapter\u3002");
|
|
679
|
+
console.log(" \u5B89\u88C5\u793E\u533A adapter: bb-browser site update");
|
|
680
|
+
console.log(` \u79C1\u6709 adapter \u76EE\u5F55: ${LOCAL_SITES_DIR}`);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (options.json) {
|
|
684
|
+
console.log(JSON.stringify(sites.map((s) => ({
|
|
685
|
+
name: s.name,
|
|
686
|
+
description: s.description,
|
|
687
|
+
domain: s.domain,
|
|
688
|
+
args: s.args,
|
|
689
|
+
source: s.source
|
|
690
|
+
})), null, 2));
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const groups = /* @__PURE__ */ new Map();
|
|
694
|
+
for (const s of sites) {
|
|
695
|
+
const platform = s.name.split("/")[0];
|
|
696
|
+
if (!groups.has(platform)) groups.set(platform, []);
|
|
697
|
+
groups.get(platform).push(s);
|
|
698
|
+
}
|
|
699
|
+
for (const [platform, items] of groups) {
|
|
700
|
+
console.log(`
|
|
701
|
+
${platform}/`);
|
|
702
|
+
for (const s of items) {
|
|
703
|
+
const cmd = s.name.split("/").slice(1).join("/");
|
|
704
|
+
const src = s.source === "local" ? " (local)" : "";
|
|
705
|
+
const desc = s.description ? ` - ${s.description}` : "";
|
|
706
|
+
console.log(` ${cmd.padEnd(20)}${desc}${src}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
console.log();
|
|
710
|
+
}
|
|
711
|
+
function siteSearch(query, options) {
|
|
712
|
+
const sites = getAllSites();
|
|
713
|
+
const q = query.toLowerCase();
|
|
714
|
+
const matches = sites.filter(
|
|
715
|
+
(s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q)
|
|
716
|
+
);
|
|
717
|
+
if (matches.length === 0) {
|
|
718
|
+
if (options.json) {
|
|
719
|
+
console.log("[]");
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
console.log(`\u672A\u627E\u5230\u5339\u914D "${query}" \u7684 adapter\u3002`);
|
|
723
|
+
console.log(" \u67E5\u770B\u6240\u6709: bb-browser site list");
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (options.json) {
|
|
727
|
+
console.log(JSON.stringify(matches.map((s) => ({
|
|
728
|
+
name: s.name,
|
|
729
|
+
description: s.description,
|
|
730
|
+
domain: s.domain,
|
|
731
|
+
source: s.source
|
|
732
|
+
})), null, 2));
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
for (const s of matches) {
|
|
736
|
+
const src = s.source === "local" ? " (local)" : "";
|
|
737
|
+
console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function siteUpdate(options = {}) {
|
|
741
|
+
mkdirSync(BB_DIR, { recursive: true });
|
|
742
|
+
const updateMode = existsSync4(join2(COMMUNITY_SITES_DIR, ".git")) ? "pull" : "clone";
|
|
743
|
+
if (updateMode === "pull") {
|
|
744
|
+
if (!options.json) {
|
|
745
|
+
console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
execSync3("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
|
|
749
|
+
if (!options.json) {
|
|
750
|
+
console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
|
|
751
|
+
console.log("");
|
|
752
|
+
console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
|
|
753
|
+
}
|
|
754
|
+
} catch (e) {
|
|
755
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
756
|
+
const manualAction = "cd ~/.bb-browser/bb-sites && git pull";
|
|
757
|
+
if (options.json) {
|
|
758
|
+
exitJsonError(`\u66F4\u65B0\u5931\u8D25: ${message}`, { action: manualAction, updateMode });
|
|
759
|
+
}
|
|
760
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
|
|
761
|
+
console.error(" \u624B\u52A8\u4FEE\u590D: cd ~/.bb-browser/bb-sites && git pull");
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
if (!options.json) {
|
|
766
|
+
console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
execSync3(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
|
|
770
|
+
if (!options.json) {
|
|
771
|
+
console.log("\u514B\u9686\u5B8C\u6210\u3002");
|
|
772
|
+
console.log("");
|
|
773
|
+
console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
|
|
774
|
+
}
|
|
775
|
+
} catch (e) {
|
|
776
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
777
|
+
const manualAction = `git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`;
|
|
778
|
+
if (options.json) {
|
|
779
|
+
exitJsonError(`\u514B\u9686\u5931\u8D25: ${message}`, { action: manualAction, updateMode });
|
|
780
|
+
}
|
|
781
|
+
console.error(`\u514B\u9686\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
|
|
782
|
+
console.error(` \u624B\u52A8\u4FEE\u590D: git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`);
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const sites = scanSites(COMMUNITY_SITES_DIR, "community");
|
|
787
|
+
if (options.json) {
|
|
788
|
+
console.log(JSON.stringify({
|
|
789
|
+
success: true,
|
|
790
|
+
updateMode,
|
|
791
|
+
communityRepo: COMMUNITY_REPO,
|
|
792
|
+
communityDir: COMMUNITY_SITES_DIR,
|
|
793
|
+
siteCount: sites.length
|
|
794
|
+
}, null, 2));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
|
|
798
|
+
console.log(`\u2B50 Like bb-browser? \u2192 bb-browser star`);
|
|
799
|
+
checkCliUpdate();
|
|
800
|
+
}
|
|
801
|
+
function findSiteByName(name) {
|
|
802
|
+
return getAllSites().find((site) => site.name === name);
|
|
803
|
+
}
|
|
804
|
+
function siteInfo(name, options) {
|
|
805
|
+
const site = findSiteByName(name);
|
|
806
|
+
if (!site) {
|
|
807
|
+
if (options.json) {
|
|
808
|
+
exitJsonError(`adapter "${name}" not found`, { action: "bb-browser site list" });
|
|
809
|
+
}
|
|
810
|
+
console.error(`[error] site info: adapter "${name}" not found.`);
|
|
811
|
+
console.error(" Try: bb-browser site list");
|
|
812
|
+
process.exit(1);
|
|
813
|
+
}
|
|
814
|
+
const meta = {
|
|
815
|
+
name: site.name,
|
|
816
|
+
description: site.description,
|
|
817
|
+
domain: site.domain,
|
|
818
|
+
args: site.args,
|
|
819
|
+
example: site.example,
|
|
820
|
+
readOnly: site.readOnly
|
|
821
|
+
};
|
|
822
|
+
if (options.json) {
|
|
823
|
+
console.log(JSON.stringify(meta, null, 2));
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
console.log(`${site.name} \u2014 ${site.description}`);
|
|
827
|
+
console.log();
|
|
828
|
+
console.log("\u53C2\u6570\uFF1A");
|
|
829
|
+
const argEntries = Object.entries(site.args);
|
|
830
|
+
if (argEntries.length === 0) {
|
|
831
|
+
console.log(" \uFF08\u65E0\uFF09");
|
|
832
|
+
} else {
|
|
833
|
+
for (const [argName, argDef] of argEntries) {
|
|
834
|
+
const requiredText = argDef.required ? "\u5FC5\u586B" : "\u53EF\u9009";
|
|
835
|
+
const description = argDef.description || "";
|
|
836
|
+
console.log(` ${argName} (${requiredText}) ${description}`.trimEnd());
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
console.log();
|
|
840
|
+
console.log("\u793A\u4F8B\uFF1A");
|
|
841
|
+
console.log(` ${site.example || `bb-browser site ${site.name}`}`);
|
|
842
|
+
console.log();
|
|
843
|
+
console.log(`\u57DF\u540D\uFF1A${site.domain || "\uFF08\u672A\u58F0\u660E\uFF09"}`);
|
|
844
|
+
console.log(`\u53EA\u8BFB\uFF1A${site.readOnly ? "\u662F" : "\u5426"}`);
|
|
845
|
+
}
|
|
846
|
+
async function siteRecommend(options) {
|
|
847
|
+
const days = options.days ?? 30;
|
|
848
|
+
const historyDomains = getHistoryDomains(days);
|
|
849
|
+
const sites = getAllSites();
|
|
850
|
+
const sitesByDomain = /* @__PURE__ */ new Map();
|
|
851
|
+
for (const site of sites) {
|
|
852
|
+
if (!site.domain) continue;
|
|
853
|
+
const domain = site.domain.toLowerCase();
|
|
854
|
+
const existing = sitesByDomain.get(domain) || [];
|
|
855
|
+
existing.push(site);
|
|
856
|
+
sitesByDomain.set(domain, existing);
|
|
857
|
+
}
|
|
858
|
+
const available = [];
|
|
859
|
+
const notAvailable = [];
|
|
860
|
+
for (const item of historyDomains) {
|
|
861
|
+
const adapters = sitesByDomain.get(item.domain.toLowerCase());
|
|
862
|
+
if (adapters && adapters.length > 0) {
|
|
863
|
+
const sortedAdapters = [...adapters].sort((a, b) => a.name.localeCompare(b.name));
|
|
864
|
+
available.push({
|
|
865
|
+
domain: item.domain,
|
|
866
|
+
visits: item.visits,
|
|
867
|
+
adapterCount: sortedAdapters.length,
|
|
868
|
+
adapters: sortedAdapters.map((site) => ({
|
|
869
|
+
name: site.name,
|
|
870
|
+
description: site.description,
|
|
871
|
+
example: site.example || `bb-browser site ${site.name}`
|
|
872
|
+
}))
|
|
873
|
+
});
|
|
874
|
+
} else if (item.visits >= 5 && item.domain && !item.domain.includes("localhost") && item.domain.includes(".")) {
|
|
875
|
+
notAvailable.push(item);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const jsonData = {
|
|
879
|
+
days,
|
|
880
|
+
available,
|
|
881
|
+
not_available: notAvailable
|
|
882
|
+
};
|
|
883
|
+
if (options.jq) {
|
|
884
|
+
handleJqResponse({ id: generateId(), success: true, data: jsonData });
|
|
885
|
+
}
|
|
886
|
+
if (options.json) {
|
|
887
|
+
console.log(JSON.stringify(jsonData, null, 2));
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
console.log(`\u57FA\u4E8E\u4F60\u6700\u8FD1 ${days} \u5929\u7684\u6D4F\u89C8\u8BB0\u5F55\uFF1A`);
|
|
891
|
+
console.log();
|
|
892
|
+
console.log("\u{1F3AF} \u4F60\u5E38\u7528\u8FD9\u4E9B\u7F51\u7AD9\uFF0C\u53EF\u4EE5\u76F4\u63A5\u7528\uFF1A");
|
|
893
|
+
console.log();
|
|
894
|
+
if (available.length === 0) {
|
|
895
|
+
console.log(" \uFF08\u6682\u65E0\u5339\u914D\u7684 adapter\uFF09");
|
|
896
|
+
} else {
|
|
897
|
+
for (const item of available) {
|
|
898
|
+
console.log(` ${item.domain.padEnd(20)} ${item.visits} \u6B21\u8BBF\u95EE ${item.adapterCount} \u4E2A\u547D\u4EE4`);
|
|
899
|
+
console.log(` \u8BD5\u8BD5: ${item.adapters[0]?.example || `bb-browser site ${item.adapters[0]?.name || ""}`}`);
|
|
900
|
+
console.log();
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
console.log("\u{1F4CB} \u4F60\u5E38\u7528\u4F46\u8FD8\u6CA1\u6709 adapter\uFF1A");
|
|
904
|
+
console.log();
|
|
905
|
+
if (notAvailable.length === 0) {
|
|
906
|
+
console.log(" \uFF08\u6682\u65E0\uFF09");
|
|
907
|
+
} else {
|
|
908
|
+
for (const item of notAvailable) {
|
|
909
|
+
console.log(` ${item.domain.padEnd(20)} ${item.visits} \u6B21\u8BBF\u95EE`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
console.log();
|
|
913
|
+
console.log('\u{1F4A1} \u8DDF\u4F60\u7684 AI Agent \u8BF4 "\u628A notion.so CLI \u5316"\uFF0C\u5B83\u5C31\u80FD\u81EA\u52A8\u5B8C\u6210\u3002');
|
|
914
|
+
console.log();
|
|
915
|
+
console.log(`\u6240\u6709\u5206\u6790\u7EAF\u672C\u5730\u5B8C\u6210\u3002\u7528 --days 7 \u53EA\u770B\u6700\u8FD1\u4E00\u5468\u3002`);
|
|
916
|
+
}
|
|
917
|
+
async function siteRun(name, args, options) {
|
|
918
|
+
const sites = getAllSites();
|
|
919
|
+
const site = sites.find((s) => s.name === name);
|
|
920
|
+
if (!site) {
|
|
921
|
+
const fuzzy = sites.filter((s) => s.name.includes(name));
|
|
922
|
+
if (options.json) {
|
|
923
|
+
exitJsonError(`site "${name}" not found`, {
|
|
924
|
+
suggestions: fuzzy.slice(0, 5).map((s) => s.name),
|
|
925
|
+
action: fuzzy.length > 0 ? void 0 : "bb-browser site update"
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
console.error(`[error] site: "${name}" not found.`);
|
|
929
|
+
if (fuzzy.length > 0) {
|
|
930
|
+
console.error(" Did you mean:");
|
|
931
|
+
for (const s of fuzzy.slice(0, 5)) {
|
|
932
|
+
console.error(` bb-browser site ${s.name}`);
|
|
933
|
+
}
|
|
934
|
+
} else {
|
|
935
|
+
console.error(" Try: bb-browser site list");
|
|
936
|
+
console.error(" Or: bb-browser site update");
|
|
937
|
+
}
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
const argNames = Object.keys(site.args);
|
|
941
|
+
const argMap = {};
|
|
942
|
+
const positionalArgs = [];
|
|
943
|
+
for (let i = 0; i < args.length; i++) {
|
|
944
|
+
if (args[i].startsWith("--")) {
|
|
945
|
+
const flagName = args[i].slice(2);
|
|
946
|
+
if (flagName in site.args && args[i + 1]) {
|
|
947
|
+
argMap[flagName] = args[i + 1];
|
|
948
|
+
i++;
|
|
949
|
+
}
|
|
950
|
+
} else {
|
|
951
|
+
positionalArgs.push(args[i]);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
let posIdx = 0;
|
|
955
|
+
for (const argName of argNames) {
|
|
956
|
+
if (!argMap[argName] && posIdx < positionalArgs.length) {
|
|
957
|
+
argMap[argName] = positionalArgs[posIdx++];
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
for (const [argName, argDef] of Object.entries(site.args)) {
|
|
961
|
+
if (argDef.required && !argMap[argName]) {
|
|
962
|
+
const usage = argNames.map((a) => {
|
|
963
|
+
const def = site.args[a];
|
|
964
|
+
return def.required ? `<${a}>` : `[${a}]`;
|
|
965
|
+
}).join(" ");
|
|
966
|
+
if (options.json) {
|
|
967
|
+
exitJsonError(`missing required argument "${argName}"`, {
|
|
968
|
+
usage: `bb-browser site ${name} ${usage}`,
|
|
969
|
+
example: site.example
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
console.error(`[error] site ${name}: missing required argument "${argName}".`);
|
|
973
|
+
console.error(` Usage: bb-browser site ${name} ${usage}`);
|
|
974
|
+
if (site.example) console.error(` Example: ${site.example}`);
|
|
975
|
+
process.exit(1);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (site.source === "local" && !options.json) {
|
|
979
|
+
const communityVersion = scanSites(COMMUNITY_SITES_DIR, "community").find((s) => s.name === name);
|
|
980
|
+
if (communityVersion) {
|
|
981
|
+
console.error(`[local override] ${name} \u2014 ${site.filePath}`);
|
|
982
|
+
console.error(` Community version also exists. Run \`bb-browser site update\` to check for updates.`);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
const jsContent = readFileSync(site.filePath, "utf-8");
|
|
986
|
+
const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
|
|
987
|
+
const argsJson = JSON.stringify(argMap);
|
|
988
|
+
const script = `(${jsBody})(${argsJson})`;
|
|
989
|
+
if (options.openclaw) {
|
|
990
|
+
const { ocGetTabs, ocFindTabByDomain, ocOpenTab, ocEvaluate } = await import("./openclaw-bridge-GWIKCRBD.js");
|
|
991
|
+
let targetId;
|
|
992
|
+
if (site.domain) {
|
|
993
|
+
const tabs = ocGetTabs();
|
|
994
|
+
const existing = ocFindTabByDomain(tabs, site.domain);
|
|
995
|
+
if (existing) {
|
|
996
|
+
targetId = existing.targetId;
|
|
997
|
+
} else {
|
|
998
|
+
targetId = ocOpenTab(`https://${site.domain}`);
|
|
999
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1000
|
+
}
|
|
1001
|
+
} else {
|
|
1002
|
+
const tabs = ocGetTabs();
|
|
1003
|
+
if (tabs.length === 0) {
|
|
1004
|
+
throw new Error("No tabs open in OpenClaw browser");
|
|
1005
|
+
}
|
|
1006
|
+
targetId = tabs[0].targetId;
|
|
1007
|
+
}
|
|
1008
|
+
const wrappedFn = `async () => { const __fn = ${jsBody}; return await __fn(${argsJson}); }`;
|
|
1009
|
+
const parsed2 = ocEvaluate(targetId, wrappedFn);
|
|
1010
|
+
if (typeof parsed2 === "object" && parsed2 !== null && "error" in parsed2) {
|
|
1011
|
+
const errObj = parsed2;
|
|
1012
|
+
const checkText = `${errObj.error} ${errObj.hint || ""}`;
|
|
1013
|
+
const isAuthError = /401|403|unauthorized|forbidden|not.?logged|login.?required|sign.?in|auth/i.test(checkText);
|
|
1014
|
+
const loginHint = isAuthError && site.domain ? `Please log in to https://${site.domain} in your OpenClaw browser first, then retry.` : void 0;
|
|
1015
|
+
const hint = loginHint || errObj.hint;
|
|
1016
|
+
const reportHint = `If this is an adapter bug, report via: gh issue create --repo epiral/bb-sites --title "[${name}] <description>" OR: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] <description>"`;
|
|
1017
|
+
if (options.json) {
|
|
1018
|
+
console.log(JSON.stringify({ id: "openclaw", success: false, error: errObj.error, hint, reportHint }));
|
|
1019
|
+
} else {
|
|
1020
|
+
console.error(`[error] site ${name}: ${errObj.error}`);
|
|
1021
|
+
if (hint) console.error(` Hint: ${hint}`);
|
|
1022
|
+
console.error(` Report: gh issue create --repo epiral/bb-sites --title "[${name}] ..."`);
|
|
1023
|
+
console.error(` or: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] ..."`);
|
|
1024
|
+
}
|
|
1025
|
+
process.exit(1);
|
|
1026
|
+
}
|
|
1027
|
+
if (options.jq) {
|
|
1028
|
+
const { applyJq: applyJq2 } = await import("./jq-J32FKI7Z.js");
|
|
1029
|
+
const expr = options.jq.replace(/^\.data\./, ".");
|
|
1030
|
+
const results = applyJq2(parsed2, expr);
|
|
1031
|
+
for (const r of results) {
|
|
1032
|
+
console.log(typeof r === "string" ? r : JSON.stringify(r));
|
|
1033
|
+
}
|
|
1034
|
+
} else if (options.json) {
|
|
1035
|
+
console.log(JSON.stringify({ id: "openclaw", success: true, data: parsed2 }));
|
|
1036
|
+
} else {
|
|
1037
|
+
console.log(JSON.stringify(parsed2, null, 2));
|
|
1038
|
+
}
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
await ensureDaemonRunning();
|
|
1042
|
+
let targetTabId = options.tabId;
|
|
1043
|
+
if (!targetTabId && site.domain) {
|
|
1044
|
+
const listReq = { id: generateId(), action: "tab_list" };
|
|
1045
|
+
const listResp = await sendCommand(listReq);
|
|
1046
|
+
if (listResp.success && listResp.data?.tabs) {
|
|
1047
|
+
const matchingTab = listResp.data.tabs.find(
|
|
1048
|
+
(tab) => matchTabOrigin(tab.url, site.domain)
|
|
1049
|
+
);
|
|
1050
|
+
if (matchingTab) {
|
|
1051
|
+
targetTabId = matchingTab.tabId;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (!targetTabId) {
|
|
1055
|
+
const newResp = await sendCommand({
|
|
1056
|
+
id: generateId(),
|
|
1057
|
+
action: "tab_new",
|
|
1058
|
+
url: `https://${site.domain}`
|
|
1059
|
+
});
|
|
1060
|
+
targetTabId = newResp.data?.tabId;
|
|
1061
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
|
|
1065
|
+
const evalResp = await sendCommand(evalReq);
|
|
1066
|
+
if (!evalResp.success) {
|
|
1067
|
+
const hint = site.domain ? `Open https://${site.domain} in your browser, make sure you are logged in, then retry.` : void 0;
|
|
1068
|
+
if (options.json) {
|
|
1069
|
+
console.log(JSON.stringify({ id: evalReq.id, success: false, error: evalResp.error || "eval failed", hint }));
|
|
1070
|
+
} else {
|
|
1071
|
+
console.error(`[error] site ${name}: ${evalResp.error || "eval failed"}`);
|
|
1072
|
+
if (hint) console.error(` Hint: ${hint}`);
|
|
1073
|
+
}
|
|
1074
|
+
process.exit(1);
|
|
1075
|
+
}
|
|
1076
|
+
const result = evalResp.data?.result;
|
|
1077
|
+
if (result === void 0 || result === null) {
|
|
1078
|
+
if (options.json) {
|
|
1079
|
+
console.log(JSON.stringify({ id: evalReq.id, success: true, data: null }));
|
|
1080
|
+
} else {
|
|
1081
|
+
console.log("(no output)");
|
|
1082
|
+
}
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
let parsed;
|
|
1086
|
+
try {
|
|
1087
|
+
parsed = typeof result === "string" ? JSON.parse(result) : result;
|
|
1088
|
+
} catch {
|
|
1089
|
+
parsed = result;
|
|
1090
|
+
}
|
|
1091
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
|
|
1092
|
+
const errObj = parsed;
|
|
1093
|
+
const checkText = `${errObj.error} ${errObj.hint || ""}`;
|
|
1094
|
+
const isAuthError = /401|403|unauthorized|forbidden|not.?logged|login.?required|sign.?in|auth/i.test(checkText);
|
|
1095
|
+
const loginHint = isAuthError && site.domain ? `Please log in to https://${site.domain} in your browser first, then retry.` : void 0;
|
|
1096
|
+
const hint = loginHint || errObj.hint;
|
|
1097
|
+
const reportHint = `If this is an adapter bug, report via: gh issue create --repo epiral/bb-sites --title "[${name}] <description>" OR: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] <description>"`;
|
|
1098
|
+
if (options.json) {
|
|
1099
|
+
console.log(JSON.stringify({ id: evalReq.id, success: false, error: errObj.error, hint, reportHint }));
|
|
1100
|
+
} else {
|
|
1101
|
+
console.error(`[error] site ${name}: ${errObj.error}`);
|
|
1102
|
+
if (hint) console.error(` Hint: ${hint}`);
|
|
1103
|
+
console.error(` Report: gh issue create --repo epiral/bb-sites --title "[${name}] ..."`);
|
|
1104
|
+
console.error(` or: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] ..."`);
|
|
1105
|
+
}
|
|
1106
|
+
process.exit(1);
|
|
1107
|
+
}
|
|
1108
|
+
if (options.jq) {
|
|
1109
|
+
const { applyJq: applyJq2 } = await import("./jq-J32FKI7Z.js");
|
|
1110
|
+
const expr = options.jq.replace(/^\.data\./, ".");
|
|
1111
|
+
const results = applyJq2(parsed, expr);
|
|
1112
|
+
for (const r of results) {
|
|
1113
|
+
console.log(typeof r === "string" ? r : JSON.stringify(r));
|
|
1114
|
+
}
|
|
1115
|
+
} else if (options.json) {
|
|
1116
|
+
console.log(JSON.stringify({ id: evalReq.id, success: true, data: parsed }));
|
|
1117
|
+
} else {
|
|
1118
|
+
console.log(JSON.stringify(parsed, null, 2));
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
async function siteCommand(args, options = {}) {
|
|
1122
|
+
const subCommand = args[0];
|
|
1123
|
+
if (!subCommand || subCommand === "--help" || subCommand === "-h") {
|
|
1124
|
+
console.log(`bb-browser site - \u7F51\u7AD9 CLI \u5316\uFF08\u7BA1\u7406\u548C\u8FD0\u884C site adapter\uFF09
|
|
1125
|
+
|
|
1126
|
+
\u7528\u6CD5:
|
|
1127
|
+
bb-browser site list \u5217\u51FA\u6240\u6709\u53EF\u7528 adapter
|
|
1128
|
+
bb-browser site info <name> \u67E5\u770B adapter \u5143\u4FE1\u606F
|
|
1129
|
+
bb-browser site recommend \u57FA\u4E8E\u5386\u53F2\u8BB0\u5F55\u63A8\u8350 adapter
|
|
1130
|
+
bb-browser site search <query> \u641C\u7D22 adapter
|
|
1131
|
+
bb-browser site <name> [args...] \u8FD0\u884C adapter\uFF08\u7B80\u5199\uFF09
|
|
1132
|
+
bb-browser site run <name> [args...] \u8FD0\u884C adapter
|
|
1133
|
+
bb-browser site update \u66F4\u65B0\u793E\u533A adapter \u5E93 (git clone/pull)
|
|
1134
|
+
|
|
1135
|
+
\u76EE\u5F55:
|
|
1136
|
+
${LOCAL_SITES_DIR} \u79C1\u6709 adapter\uFF08\u4F18\u5148\uFF09
|
|
1137
|
+
${COMMUNITY_SITES_DIR} \u793E\u533A adapter
|
|
1138
|
+
|
|
1139
|
+
\u793A\u4F8B:
|
|
1140
|
+
bb-browser site update
|
|
1141
|
+
bb-browser site list
|
|
1142
|
+
bb-browser site reddit/thread https://www.reddit.com/r/LocalLLaMA/comments/...
|
|
1143
|
+
bb-browser site twitter/user yan5xu
|
|
1144
|
+
bb-browser site search reddit
|
|
1145
|
+
|
|
1146
|
+
\u521B\u5EFA\u65B0 adapter: bb-browser guide
|
|
1147
|
+
\u62A5\u544A\u95EE\u9898: gh issue create --repo epiral/bb-sites --title "[adapter-name] \u63CF\u8FF0"
|
|
1148
|
+
\u8D21\u732E\u793E\u533A: https://github.com/epiral/bb-sites`);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
switch (subCommand) {
|
|
1152
|
+
case "list":
|
|
1153
|
+
siteList(options);
|
|
1154
|
+
break;
|
|
1155
|
+
case "search":
|
|
1156
|
+
if (!args[1]) {
|
|
1157
|
+
console.error("[error] site search: <query> is required.");
|
|
1158
|
+
console.error(" Usage: bb-browser site search <query>");
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
siteSearch(args[1], options);
|
|
1162
|
+
break;
|
|
1163
|
+
case "info":
|
|
1164
|
+
if (!args[1]) {
|
|
1165
|
+
console.error("[error] site info: <name> is required.");
|
|
1166
|
+
console.error(" Usage: bb-browser site info <name>");
|
|
1167
|
+
process.exit(1);
|
|
1168
|
+
}
|
|
1169
|
+
siteInfo(args[1], options);
|
|
1170
|
+
break;
|
|
1171
|
+
case "recommend":
|
|
1172
|
+
await siteRecommend(options);
|
|
1173
|
+
break;
|
|
1174
|
+
case "update":
|
|
1175
|
+
siteUpdate(options);
|
|
1176
|
+
break;
|
|
1177
|
+
case "run":
|
|
1178
|
+
if (!args[1]) {
|
|
1179
|
+
console.error("[error] site run: <name> is required.");
|
|
1180
|
+
console.error(" Usage: bb-browser site run <name> [args...]");
|
|
1181
|
+
console.error(" Try: bb-browser site list");
|
|
1182
|
+
process.exit(1);
|
|
1183
|
+
}
|
|
1184
|
+
await siteRun(args[1], args.slice(2), options);
|
|
1185
|
+
break;
|
|
1186
|
+
default:
|
|
1187
|
+
if (subCommand.includes("/")) {
|
|
1188
|
+
await siteRun(subCommand, args.slice(1), options);
|
|
1189
|
+
} else {
|
|
1190
|
+
console.error(`[error] site: unknown subcommand "${subCommand}".`);
|
|
1191
|
+
console.error(" Available: list, info, recommend, search, run, update");
|
|
1192
|
+
console.error(" Try: bb-browser site --help");
|
|
1193
|
+
process.exit(1);
|
|
1194
|
+
}
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
silentUpdate();
|
|
1198
|
+
}
|
|
1199
|
+
function silentUpdate() {
|
|
1200
|
+
const gitDir = join2(COMMUNITY_SITES_DIR, ".git");
|
|
1201
|
+
if (!existsSync4(gitDir)) return;
|
|
1202
|
+
import("child_process").then(({ spawn: spawn3 }) => {
|
|
1203
|
+
const child = spawn3("git", ["pull", "--ff-only"], {
|
|
1204
|
+
cwd: COMMUNITY_SITES_DIR,
|
|
1205
|
+
stdio: "ignore",
|
|
1206
|
+
detached: true
|
|
1207
|
+
});
|
|
1208
|
+
child.unref();
|
|
1209
|
+
}).catch(() => {
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// packages/cli/src/commands/open.ts
|
|
1214
|
+
async function openCommand(url, options = {}) {
|
|
1215
|
+
if (!url) {
|
|
1216
|
+
throw new Error("\u7F3A\u5C11 URL \u53C2\u6570");
|
|
1217
|
+
}
|
|
1218
|
+
await ensureDaemonRunning();
|
|
1219
|
+
let normalizedUrl = url;
|
|
1220
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
1221
|
+
normalizedUrl = "https://" + url;
|
|
1222
|
+
}
|
|
1223
|
+
const request = {
|
|
1224
|
+
id: generateId(),
|
|
1225
|
+
action: "open",
|
|
1226
|
+
url: normalizedUrl
|
|
1227
|
+
};
|
|
1228
|
+
if (options.tab !== void 0) {
|
|
1229
|
+
if (options.tab === "current") {
|
|
1230
|
+
request.tabId = "current";
|
|
1231
|
+
} else {
|
|
1232
|
+
const tabId = parseInt(options.tab, 10);
|
|
1233
|
+
if (isNaN(tabId)) {
|
|
1234
|
+
throw new Error(`\u65E0\u6548\u7684 tabId: ${options.tab}`);
|
|
1235
|
+
}
|
|
1236
|
+
request.tabId = tabId;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const response = await sendCommand(request);
|
|
1240
|
+
if (options.json) {
|
|
1241
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1242
|
+
} else {
|
|
1243
|
+
if (response.success) {
|
|
1244
|
+
console.log(`\u5DF2\u6253\u5F00: ${response.data?.url ?? normalizedUrl}`);
|
|
1245
|
+
if (response.data?.title) {
|
|
1246
|
+
console.log(`\u6807\u9898: ${response.data.title}`);
|
|
1247
|
+
}
|
|
1248
|
+
if (response.data?.tabId) {
|
|
1249
|
+
console.log(`Tab ID: ${response.data.tabId}`);
|
|
1250
|
+
}
|
|
1251
|
+
const siteHint = getSiteHintForDomain(normalizedUrl);
|
|
1252
|
+
if (siteHint) {
|
|
1253
|
+
console.log(`
|
|
1254
|
+
\u{1F4A1} ${siteHint}`);
|
|
1255
|
+
}
|
|
1256
|
+
} else {
|
|
1257
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1258
|
+
process.exit(1);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// packages/cli/src/commands/snapshot.ts
|
|
1264
|
+
async function snapshotCommand(options = {}) {
|
|
1265
|
+
await ensureDaemonRunning();
|
|
1266
|
+
const request = {
|
|
1267
|
+
id: generateId(),
|
|
1268
|
+
action: "snapshot",
|
|
1269
|
+
interactive: options.interactive,
|
|
1270
|
+
compact: options.compact,
|
|
1271
|
+
maxDepth: options.maxDepth,
|
|
1272
|
+
selector: options.selector,
|
|
1273
|
+
tabId: options.tabId
|
|
1274
|
+
};
|
|
1275
|
+
const response = await sendCommand(request);
|
|
1276
|
+
if (options.json) {
|
|
1277
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1278
|
+
} else {
|
|
1279
|
+
if (response.success) {
|
|
1280
|
+
console.log(`\u6807\u9898: ${response.data?.title ?? "(\u65E0\u6807\u9898)"}`);
|
|
1281
|
+
console.log(`URL: ${response.data?.url ?? "(\u672A\u77E5)"}`);
|
|
1282
|
+
if (response.data?.snapshotData?.snapshot) {
|
|
1283
|
+
console.log("");
|
|
1284
|
+
console.log(response.data.snapshotData.snapshot);
|
|
1285
|
+
}
|
|
1286
|
+
} else {
|
|
1287
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1288
|
+
process.exit(1);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// packages/cli/src/commands/click.ts
|
|
1294
|
+
function parseRef(ref) {
|
|
1295
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1296
|
+
}
|
|
1297
|
+
async function clickCommand(ref, options = {}) {
|
|
1298
|
+
if (!ref) {
|
|
1299
|
+
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
1300
|
+
}
|
|
1301
|
+
await ensureDaemonRunning();
|
|
1302
|
+
const parsedRef = parseRef(ref);
|
|
1303
|
+
const request = {
|
|
1304
|
+
id: generateId(),
|
|
1305
|
+
action: "click",
|
|
1306
|
+
ref: parsedRef,
|
|
1307
|
+
tabId: options.tabId
|
|
1308
|
+
};
|
|
1309
|
+
const response = await sendCommand(request);
|
|
1310
|
+
if (options.json) {
|
|
1311
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1312
|
+
} else {
|
|
1313
|
+
if (response.success) {
|
|
1314
|
+
const role = response.data?.role ?? "element";
|
|
1315
|
+
const name = response.data?.name;
|
|
1316
|
+
if (name) {
|
|
1317
|
+
console.log(`\u5DF2\u70B9\u51FB: ${role} "${name}"`);
|
|
1318
|
+
} else {
|
|
1319
|
+
console.log(`\u5DF2\u70B9\u51FB: ${role}`);
|
|
1320
|
+
}
|
|
1321
|
+
} else {
|
|
1322
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1323
|
+
process.exit(1);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// packages/cli/src/commands/hover.ts
|
|
1329
|
+
function parseRef2(ref) {
|
|
1330
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1331
|
+
}
|
|
1332
|
+
async function hoverCommand(ref, options = {}) {
|
|
1333
|
+
if (!ref) {
|
|
1334
|
+
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
1335
|
+
}
|
|
1336
|
+
await ensureDaemonRunning();
|
|
1337
|
+
const parsedRef = parseRef2(ref);
|
|
1338
|
+
const request = {
|
|
1339
|
+
id: generateId(),
|
|
1340
|
+
action: "hover",
|
|
1341
|
+
ref: parsedRef,
|
|
1342
|
+
tabId: options.tabId
|
|
1343
|
+
};
|
|
1344
|
+
const response = await sendCommand(request);
|
|
1345
|
+
if (options.json) {
|
|
1346
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1347
|
+
} else {
|
|
1348
|
+
if (response.success) {
|
|
1349
|
+
const role = response.data?.role ?? "element";
|
|
1350
|
+
const name = response.data?.name;
|
|
1351
|
+
if (name) {
|
|
1352
|
+
console.log(`\u5DF2\u60AC\u505C: ${role} "${name}"`);
|
|
1353
|
+
} else {
|
|
1354
|
+
console.log(`\u5DF2\u60AC\u505C: ${role}`);
|
|
1355
|
+
}
|
|
1356
|
+
} else {
|
|
1357
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1358
|
+
process.exit(1);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// packages/cli/src/commands/fill.ts
|
|
1364
|
+
function parseRef3(ref) {
|
|
1365
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1366
|
+
}
|
|
1367
|
+
async function fillCommand(ref, text, options = {}) {
|
|
1368
|
+
if (!ref) {
|
|
1369
|
+
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
1370
|
+
}
|
|
1371
|
+
if (text === void 0 || text === null) {
|
|
1372
|
+
throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
|
|
1373
|
+
}
|
|
1374
|
+
await ensureDaemonRunning();
|
|
1375
|
+
const parsedRef = parseRef3(ref);
|
|
1376
|
+
const request = {
|
|
1377
|
+
id: generateId(),
|
|
1378
|
+
action: "fill",
|
|
1379
|
+
ref: parsedRef,
|
|
1380
|
+
text,
|
|
1381
|
+
tabId: options.tabId
|
|
1382
|
+
};
|
|
1383
|
+
const response = await sendCommand(request);
|
|
1384
|
+
if (options.json) {
|
|
1385
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1386
|
+
} else {
|
|
1387
|
+
if (response.success) {
|
|
1388
|
+
const role = response.data?.role ?? "element";
|
|
1389
|
+
const name = response.data?.name;
|
|
1390
|
+
if (name) {
|
|
1391
|
+
console.log(`\u5DF2\u586B\u5145: ${role} "${name}"`);
|
|
1392
|
+
} else {
|
|
1393
|
+
console.log(`\u5DF2\u586B\u5145: ${role}`);
|
|
1394
|
+
}
|
|
1395
|
+
console.log(`\u5185\u5BB9: "${text}"`);
|
|
1396
|
+
} else {
|
|
1397
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1398
|
+
process.exit(1);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// packages/cli/src/commands/type.ts
|
|
1404
|
+
function parseRef4(ref) {
|
|
1405
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1406
|
+
}
|
|
1407
|
+
async function typeCommand(ref, text, options = {}) {
|
|
1408
|
+
if (!ref) {
|
|
1409
|
+
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
1410
|
+
}
|
|
1411
|
+
if (text === void 0 || text === null) {
|
|
1412
|
+
throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
|
|
1413
|
+
}
|
|
1414
|
+
await ensureDaemonRunning();
|
|
1415
|
+
const parsedRef = parseRef4(ref);
|
|
1416
|
+
const request = {
|
|
1417
|
+
id: generateId(),
|
|
1418
|
+
action: "type",
|
|
1419
|
+
ref: parsedRef,
|
|
1420
|
+
text,
|
|
1421
|
+
tabId: options.tabId
|
|
1422
|
+
};
|
|
1423
|
+
const response = await sendCommand(request);
|
|
1424
|
+
if (options.json) {
|
|
1425
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1426
|
+
} else {
|
|
1427
|
+
if (response.success) {
|
|
1428
|
+
const role = response.data?.role ?? "element";
|
|
1429
|
+
const name = response.data?.name;
|
|
1430
|
+
if (name) {
|
|
1431
|
+
console.log(`\u5DF2\u8F93\u5165: ${role} "${name}"`);
|
|
1432
|
+
} else {
|
|
1433
|
+
console.log(`\u5DF2\u8F93\u5165: ${role}`);
|
|
1434
|
+
}
|
|
1435
|
+
console.log(`\u5185\u5BB9: "${text}"`);
|
|
1436
|
+
} else {
|
|
1437
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1438
|
+
process.exit(1);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// packages/cli/src/commands/close.ts
|
|
1444
|
+
async function closeCommand(options = {}) {
|
|
1445
|
+
await ensureDaemonRunning();
|
|
1446
|
+
const request = {
|
|
1447
|
+
id: generateId(),
|
|
1448
|
+
action: "close",
|
|
1449
|
+
tabId: options.tabId
|
|
1450
|
+
};
|
|
1451
|
+
const response = await sendCommand(request);
|
|
1452
|
+
if (options.json) {
|
|
1453
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1454
|
+
} else {
|
|
1455
|
+
if (response.success) {
|
|
1456
|
+
const title = response.data?.title ?? "";
|
|
1457
|
+
if (title) {
|
|
1458
|
+
console.log(`\u5DF2\u5173\u95ED: "${title}"`);
|
|
1459
|
+
} else {
|
|
1460
|
+
console.log("\u5DF2\u5173\u95ED\u5F53\u524D\u6807\u7B7E\u9875");
|
|
1461
|
+
}
|
|
1462
|
+
} else {
|
|
1463
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1464
|
+
process.exit(1);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// packages/cli/src/commands/get.ts
|
|
1470
|
+
function parseRef5(ref) {
|
|
1471
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1472
|
+
}
|
|
1473
|
+
async function getCommand(attribute, ref, options = {}) {
|
|
1474
|
+
if (attribute === "text" && !ref) {
|
|
1475
|
+
throw new Error("get text \u9700\u8981 ref \u53C2\u6570\uFF0C\u5982: get text @5");
|
|
1476
|
+
}
|
|
1477
|
+
await ensureDaemonRunning();
|
|
1478
|
+
const request = {
|
|
1479
|
+
id: generateId(),
|
|
1480
|
+
action: "get",
|
|
1481
|
+
attribute,
|
|
1482
|
+
ref: ref ? parseRef5(ref) : void 0,
|
|
1483
|
+
tabId: options.tabId
|
|
1484
|
+
};
|
|
1485
|
+
const response = await sendCommand(request);
|
|
1486
|
+
if (options.json) {
|
|
1487
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1488
|
+
} else {
|
|
1489
|
+
if (response.success) {
|
|
1490
|
+
const value = response.data?.value ?? "";
|
|
1491
|
+
console.log(value);
|
|
1492
|
+
} else {
|
|
1493
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1494
|
+
process.exit(1);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// packages/cli/src/commands/screenshot.ts
|
|
1500
|
+
import fs from "fs";
|
|
1501
|
+
import path2 from "path";
|
|
1502
|
+
import os2 from "os";
|
|
1503
|
+
function getDefaultPath() {
|
|
1504
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1505
|
+
const filename = `bb-screenshot-${timestamp}.png`;
|
|
1506
|
+
return path2.join(os2.tmpdir(), filename);
|
|
1507
|
+
}
|
|
1508
|
+
function saveBase64Image(dataUrl, filePath) {
|
|
1509
|
+
const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
1510
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
1511
|
+
const dir = path2.dirname(filePath);
|
|
1512
|
+
if (!fs.existsSync(dir)) {
|
|
1513
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1514
|
+
}
|
|
1515
|
+
fs.writeFileSync(filePath, buffer);
|
|
1516
|
+
}
|
|
1517
|
+
async function screenshotCommand(outputPath, options = {}) {
|
|
1518
|
+
await ensureDaemonRunning();
|
|
1519
|
+
const filePath = outputPath ? path2.resolve(outputPath) : getDefaultPath();
|
|
1520
|
+
const request = {
|
|
1521
|
+
id: generateId(),
|
|
1522
|
+
action: "screenshot",
|
|
1523
|
+
tabId: options.tabId
|
|
1524
|
+
};
|
|
1525
|
+
const response = await sendCommand(request);
|
|
1526
|
+
if (response.success && response.data?.dataUrl) {
|
|
1527
|
+
const dataUrl = response.data.dataUrl;
|
|
1528
|
+
saveBase64Image(dataUrl, filePath);
|
|
1529
|
+
if (options.json) {
|
|
1530
|
+
console.log(JSON.stringify({
|
|
1531
|
+
success: true,
|
|
1532
|
+
path: filePath,
|
|
1533
|
+
base64: dataUrl
|
|
1534
|
+
}, null, 2));
|
|
1535
|
+
} else {
|
|
1536
|
+
console.log(`\u622A\u56FE\u5DF2\u4FDD\u5B58: ${filePath}`);
|
|
1537
|
+
}
|
|
1538
|
+
} else {
|
|
1539
|
+
if (options.json) {
|
|
1540
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1541
|
+
} else {
|
|
1542
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1543
|
+
}
|
|
1544
|
+
process.exit(1);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// packages/cli/src/commands/wait.ts
|
|
1549
|
+
function isTimeWait(target) {
|
|
1550
|
+
return /^\d+$/.test(target);
|
|
1551
|
+
}
|
|
1552
|
+
function parseRef6(ref) {
|
|
1553
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1554
|
+
}
|
|
1555
|
+
async function waitCommand(target, options = {}) {
|
|
1556
|
+
if (!target) {
|
|
1557
|
+
throw new Error("\u7F3A\u5C11\u7B49\u5F85\u76EE\u6807\u53C2\u6570");
|
|
1558
|
+
}
|
|
1559
|
+
await ensureDaemonRunning();
|
|
1560
|
+
let request;
|
|
1561
|
+
if (isTimeWait(target)) {
|
|
1562
|
+
const ms = parseInt(target, 10);
|
|
1563
|
+
request = {
|
|
1564
|
+
id: generateId(),
|
|
1565
|
+
action: "wait",
|
|
1566
|
+
waitType: "time",
|
|
1567
|
+
ms,
|
|
1568
|
+
tabId: options.tabId
|
|
1569
|
+
};
|
|
1570
|
+
} else {
|
|
1571
|
+
const ref = parseRef6(target);
|
|
1572
|
+
request = {
|
|
1573
|
+
id: generateId(),
|
|
1574
|
+
action: "wait",
|
|
1575
|
+
waitType: "element",
|
|
1576
|
+
ref,
|
|
1577
|
+
tabId: options.tabId
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
const response = await sendCommand(request);
|
|
1581
|
+
if (options.json) {
|
|
1582
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1583
|
+
} else {
|
|
1584
|
+
if (response.success) {
|
|
1585
|
+
if (isTimeWait(target)) {
|
|
1586
|
+
console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
|
|
1587
|
+
} else {
|
|
1588
|
+
console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
|
|
1589
|
+
}
|
|
1590
|
+
} else {
|
|
1591
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1592
|
+
process.exit(1);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// packages/cli/src/commands/press.ts
|
|
1598
|
+
function parseKey(keyString) {
|
|
1599
|
+
const parts = keyString.split("+");
|
|
1600
|
+
const modifierNames = ["Control", "Alt", "Shift", "Meta"];
|
|
1601
|
+
const modifiers = [];
|
|
1602
|
+
let key = "";
|
|
1603
|
+
for (const part of parts) {
|
|
1604
|
+
if (modifierNames.includes(part)) {
|
|
1605
|
+
modifiers.push(part);
|
|
1606
|
+
} else {
|
|
1607
|
+
key = part;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return { key, modifiers };
|
|
1611
|
+
}
|
|
1612
|
+
async function pressCommand(keyString, options = {}) {
|
|
1613
|
+
if (!keyString) {
|
|
1614
|
+
throw new Error("\u7F3A\u5C11 key \u53C2\u6570");
|
|
1615
|
+
}
|
|
1616
|
+
await ensureDaemonRunning();
|
|
1617
|
+
const { key, modifiers } = parseKey(keyString);
|
|
1618
|
+
if (!key) {
|
|
1619
|
+
throw new Error("\u65E0\u6548\u7684\u6309\u952E\u683C\u5F0F");
|
|
1620
|
+
}
|
|
1621
|
+
const request = {
|
|
1622
|
+
id: generateId(),
|
|
1623
|
+
action: "press",
|
|
1624
|
+
key,
|
|
1625
|
+
modifiers,
|
|
1626
|
+
tabId: options.tabId
|
|
1627
|
+
};
|
|
1628
|
+
const response = await sendCommand(request);
|
|
1629
|
+
if (options.json) {
|
|
1630
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1631
|
+
} else {
|
|
1632
|
+
if (response.success) {
|
|
1633
|
+
const displayKey = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
|
|
1634
|
+
console.log(`\u5DF2\u6309\u4E0B: ${displayKey}`);
|
|
1635
|
+
} else {
|
|
1636
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1637
|
+
process.exit(1);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// packages/cli/src/commands/scroll.ts
|
|
1643
|
+
var VALID_DIRECTIONS = ["up", "down", "left", "right"];
|
|
1644
|
+
var DEFAULT_PIXELS = 300;
|
|
1645
|
+
async function scrollCommand(direction, pixels, options = {}) {
|
|
1646
|
+
if (!direction) {
|
|
1647
|
+
throw new Error("\u7F3A\u5C11 direction \u53C2\u6570");
|
|
1648
|
+
}
|
|
1649
|
+
if (!VALID_DIRECTIONS.includes(direction)) {
|
|
1650
|
+
throw new Error(
|
|
1651
|
+
`\u65E0\u6548\u7684\u6EDA\u52A8\u65B9\u5411: ${direction}\uFF0C\u652F\u6301: ${VALID_DIRECTIONS.join(", ")}`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
let pixelValue = DEFAULT_PIXELS;
|
|
1655
|
+
if (pixels !== void 0) {
|
|
1656
|
+
pixelValue = parseInt(pixels, 10);
|
|
1657
|
+
if (isNaN(pixelValue) || pixelValue <= 0) {
|
|
1658
|
+
throw new Error(`\u65E0\u6548\u7684\u50CF\u7D20\u503C: ${pixels}`);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
await ensureDaemonRunning();
|
|
1662
|
+
const request = {
|
|
1663
|
+
id: generateId(),
|
|
1664
|
+
action: "scroll",
|
|
1665
|
+
direction,
|
|
1666
|
+
pixels: pixelValue,
|
|
1667
|
+
tabId: options.tabId
|
|
1668
|
+
};
|
|
1669
|
+
const response = await sendCommand(request);
|
|
1670
|
+
if (options.json) {
|
|
1671
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1672
|
+
} else {
|
|
1673
|
+
if (response.success) {
|
|
1674
|
+
console.log(`\u5DF2\u6EDA\u52A8: ${direction} ${pixelValue}px`);
|
|
1675
|
+
} else {
|
|
1676
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1677
|
+
process.exit(1);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// packages/cli/src/commands/nav.ts
|
|
1683
|
+
async function backCommand(options = {}) {
|
|
1684
|
+
await ensureDaemonRunning();
|
|
1685
|
+
const request = {
|
|
1686
|
+
id: generateId(),
|
|
1687
|
+
action: "back",
|
|
1688
|
+
tabId: options.tabId
|
|
1689
|
+
};
|
|
1690
|
+
const response = await sendCommand(request);
|
|
1691
|
+
if (options.json) {
|
|
1692
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1693
|
+
} else {
|
|
1694
|
+
if (response.success) {
|
|
1695
|
+
const url = response.data?.url ?? "";
|
|
1696
|
+
if (url) {
|
|
1697
|
+
console.log(`\u540E\u9000\u81F3: ${url}`);
|
|
1698
|
+
} else {
|
|
1699
|
+
console.log("\u5DF2\u540E\u9000");
|
|
1700
|
+
}
|
|
1701
|
+
} else {
|
|
1702
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1703
|
+
process.exit(1);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
async function forwardCommand(options = {}) {
|
|
1708
|
+
await ensureDaemonRunning();
|
|
1709
|
+
const request = {
|
|
1710
|
+
id: generateId(),
|
|
1711
|
+
action: "forward",
|
|
1712
|
+
tabId: options.tabId
|
|
1713
|
+
};
|
|
1714
|
+
const response = await sendCommand(request);
|
|
1715
|
+
if (options.json) {
|
|
1716
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1717
|
+
} else {
|
|
1718
|
+
if (response.success) {
|
|
1719
|
+
const url = response.data?.url ?? "";
|
|
1720
|
+
if (url) {
|
|
1721
|
+
console.log(`\u524D\u8FDB\u81F3: ${url}`);
|
|
1722
|
+
} else {
|
|
1723
|
+
console.log("\u5DF2\u524D\u8FDB");
|
|
1724
|
+
}
|
|
1725
|
+
} else {
|
|
1726
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1727
|
+
process.exit(1);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
async function refreshCommand(options = {}) {
|
|
1732
|
+
await ensureDaemonRunning();
|
|
1733
|
+
const request = {
|
|
1734
|
+
id: generateId(),
|
|
1735
|
+
action: "refresh",
|
|
1736
|
+
tabId: options.tabId
|
|
1737
|
+
};
|
|
1738
|
+
const response = await sendCommand(request);
|
|
1739
|
+
if (options.json) {
|
|
1740
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1741
|
+
} else {
|
|
1742
|
+
if (response.success) {
|
|
1743
|
+
const title = response.data?.title ?? "";
|
|
1744
|
+
if (title) {
|
|
1745
|
+
console.log(`\u5DF2\u5237\u65B0: "${title}"`);
|
|
1746
|
+
} else {
|
|
1747
|
+
console.log("\u5DF2\u5237\u65B0\u9875\u9762");
|
|
1748
|
+
}
|
|
1749
|
+
} else {
|
|
1750
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1751
|
+
process.exit(1);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// packages/cli/src/commands/check.ts
|
|
1757
|
+
function parseRef7(ref) {
|
|
1758
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1759
|
+
}
|
|
1760
|
+
async function checkCommand(ref, options = {}) {
|
|
1761
|
+
if (!ref) {
|
|
1762
|
+
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
1763
|
+
}
|
|
1764
|
+
await ensureDaemonRunning();
|
|
1765
|
+
const parsedRef = parseRef7(ref);
|
|
1766
|
+
const request = {
|
|
1767
|
+
id: generateId(),
|
|
1768
|
+
action: "check",
|
|
1769
|
+
ref: parsedRef,
|
|
1770
|
+
tabId: options.tabId
|
|
1771
|
+
};
|
|
1772
|
+
const response = await sendCommand(request);
|
|
1773
|
+
if (options.json) {
|
|
1774
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1775
|
+
} else {
|
|
1776
|
+
if (response.success) {
|
|
1777
|
+
const role = response.data?.role ?? "checkbox";
|
|
1778
|
+
const name = response.data?.name;
|
|
1779
|
+
const wasAlreadyChecked = response.data?.wasAlreadyChecked;
|
|
1780
|
+
if (wasAlreadyChecked) {
|
|
1781
|
+
if (name) {
|
|
1782
|
+
console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role} "${name}"`);
|
|
1783
|
+
} else {
|
|
1784
|
+
console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role}`);
|
|
1785
|
+
}
|
|
1786
|
+
} else {
|
|
1787
|
+
if (name) {
|
|
1788
|
+
console.log(`\u5DF2\u52FE\u9009: ${role} "${name}"`);
|
|
1789
|
+
} else {
|
|
1790
|
+
console.log(`\u5DF2\u52FE\u9009: ${role}`);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
} else {
|
|
1794
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1795
|
+
process.exit(1);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
async function uncheckCommand(ref, options = {}) {
|
|
1800
|
+
if (!ref) {
|
|
1801
|
+
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
1802
|
+
}
|
|
1803
|
+
await ensureDaemonRunning();
|
|
1804
|
+
const parsedRef = parseRef7(ref);
|
|
1805
|
+
const request = {
|
|
1806
|
+
id: generateId(),
|
|
1807
|
+
action: "uncheck",
|
|
1808
|
+
ref: parsedRef,
|
|
1809
|
+
tabId: options.tabId
|
|
1810
|
+
};
|
|
1811
|
+
const response = await sendCommand(request);
|
|
1812
|
+
if (options.json) {
|
|
1813
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1814
|
+
} else {
|
|
1815
|
+
if (response.success) {
|
|
1816
|
+
const role = response.data?.role ?? "checkbox";
|
|
1817
|
+
const name = response.data?.name;
|
|
1818
|
+
const wasAlreadyUnchecked = response.data?.wasAlreadyUnchecked;
|
|
1819
|
+
if (wasAlreadyUnchecked) {
|
|
1820
|
+
if (name) {
|
|
1821
|
+
console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role} "${name}"`);
|
|
1822
|
+
} else {
|
|
1823
|
+
console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role}`);
|
|
1824
|
+
}
|
|
1825
|
+
} else {
|
|
1826
|
+
if (name) {
|
|
1827
|
+
console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role} "${name}"`);
|
|
1828
|
+
} else {
|
|
1829
|
+
console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role}`);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
} else {
|
|
1833
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1834
|
+
process.exit(1);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// packages/cli/src/commands/select.ts
|
|
1840
|
+
function parseRef8(ref) {
|
|
1841
|
+
return ref.startsWith("@") ? ref.slice(1) : ref;
|
|
1842
|
+
}
|
|
1843
|
+
async function selectCommand(ref, value, options = {}) {
|
|
1844
|
+
if (!ref) {
|
|
1845
|
+
throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
|
|
1846
|
+
}
|
|
1847
|
+
if (value === void 0 || value === null) {
|
|
1848
|
+
throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
|
|
1849
|
+
}
|
|
1850
|
+
await ensureDaemonRunning();
|
|
1851
|
+
const parsedRef = parseRef8(ref);
|
|
1852
|
+
const request = {
|
|
1853
|
+
id: generateId(),
|
|
1854
|
+
action: "select",
|
|
1855
|
+
ref: parsedRef,
|
|
1856
|
+
value,
|
|
1857
|
+
tabId: options.tabId
|
|
1858
|
+
};
|
|
1859
|
+
const response = await sendCommand(request);
|
|
1860
|
+
if (options.json) {
|
|
1861
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1862
|
+
} else {
|
|
1863
|
+
if (response.success) {
|
|
1864
|
+
const role = response.data?.role ?? "combobox";
|
|
1865
|
+
const name = response.data?.name;
|
|
1866
|
+
const selectedValue = response.data?.selectedValue;
|
|
1867
|
+
const selectedLabel = response.data?.selectedLabel;
|
|
1868
|
+
if (name) {
|
|
1869
|
+
console.log(`\u5DF2\u9009\u62E9: ${role} "${name}"`);
|
|
1870
|
+
} else {
|
|
1871
|
+
console.log(`\u5DF2\u9009\u62E9: ${role}`);
|
|
1872
|
+
}
|
|
1873
|
+
if (selectedLabel && selectedLabel !== selectedValue) {
|
|
1874
|
+
console.log(`\u9009\u9879: "${selectedLabel}" (value="${selectedValue}")`);
|
|
1875
|
+
} else {
|
|
1876
|
+
console.log(`\u9009\u9879: "${selectedValue}"`);
|
|
1877
|
+
}
|
|
1878
|
+
} else {
|
|
1879
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1880
|
+
process.exit(1);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// packages/cli/src/commands/eval.ts
|
|
1886
|
+
async function evalCommand(script, options = {}) {
|
|
1887
|
+
if (!script) {
|
|
1888
|
+
throw new Error("\u7F3A\u5C11 script \u53C2\u6570");
|
|
1889
|
+
}
|
|
1890
|
+
await ensureDaemonRunning();
|
|
1891
|
+
const request = {
|
|
1892
|
+
id: generateId(),
|
|
1893
|
+
action: "eval",
|
|
1894
|
+
script,
|
|
1895
|
+
tabId: options.tabId
|
|
1896
|
+
};
|
|
1897
|
+
const response = await sendCommand(request);
|
|
1898
|
+
if (options.json) {
|
|
1899
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1900
|
+
} else {
|
|
1901
|
+
if (response.success) {
|
|
1902
|
+
const result = response.data?.result;
|
|
1903
|
+
if (result !== void 0) {
|
|
1904
|
+
if (typeof result === "object" && result !== null) {
|
|
1905
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1906
|
+
} else {
|
|
1907
|
+
console.log(result);
|
|
1908
|
+
}
|
|
1909
|
+
} else {
|
|
1910
|
+
console.log("undefined");
|
|
1911
|
+
}
|
|
1912
|
+
} else {
|
|
1913
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
1914
|
+
process.exit(1);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// packages/cli/src/commands/tab.ts
|
|
1920
|
+
function parseTabSubcommand(args, rawArgv) {
|
|
1921
|
+
let tabId;
|
|
1922
|
+
if (rawArgv) {
|
|
1923
|
+
const idIdx = rawArgv.indexOf("--id");
|
|
1924
|
+
if (idIdx >= 0 && rawArgv[idIdx + 1]) {
|
|
1925
|
+
tabId = parseInt(rawArgv[idIdx + 1], 10);
|
|
1926
|
+
if (isNaN(tabId)) {
|
|
1927
|
+
throw new Error(`\u65E0\u6548\u7684 tabId: ${rawArgv[idIdx + 1]}`);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
if (args.length === 0) {
|
|
1932
|
+
return { action: "tab_list" };
|
|
1933
|
+
}
|
|
1934
|
+
const first = args[0];
|
|
1935
|
+
if (first === "list") {
|
|
1936
|
+
return { action: "tab_list" };
|
|
1937
|
+
}
|
|
1938
|
+
if (first === "new") {
|
|
1939
|
+
return { action: "tab_new", url: args[1] };
|
|
1940
|
+
}
|
|
1941
|
+
if (first === "select") {
|
|
1942
|
+
if (tabId !== void 0) {
|
|
1943
|
+
return { action: "tab_select", tabId };
|
|
1944
|
+
}
|
|
1945
|
+
throw new Error("tab select \u9700\u8981 --id \u53C2\u6570\uFF0C\u7528\u6CD5\uFF1Abb-browser tab select --id <tabId>");
|
|
1946
|
+
}
|
|
1947
|
+
if (first === "close") {
|
|
1948
|
+
if (tabId !== void 0) {
|
|
1949
|
+
return { action: "tab_close", tabId };
|
|
1950
|
+
}
|
|
1951
|
+
const indexArg = args[1];
|
|
1952
|
+
if (indexArg !== void 0) {
|
|
1953
|
+
const index2 = parseInt(indexArg, 10);
|
|
1954
|
+
if (isNaN(index2) || index2 < 0) {
|
|
1955
|
+
throw new Error(`\u65E0\u6548\u7684\u6807\u7B7E\u9875\u7D22\u5F15: ${indexArg}`);
|
|
1956
|
+
}
|
|
1957
|
+
return { action: "tab_close", index: index2 };
|
|
1958
|
+
}
|
|
1959
|
+
return { action: "tab_close" };
|
|
1960
|
+
}
|
|
1961
|
+
const index = parseInt(first, 10);
|
|
1962
|
+
if (!isNaN(index) && index >= 0) {
|
|
1963
|
+
return { action: "tab_select", index };
|
|
1964
|
+
}
|
|
1965
|
+
throw new Error(`\u672A\u77E5\u7684 tab \u5B50\u547D\u4EE4: ${first}`);
|
|
1966
|
+
}
|
|
1967
|
+
function formatTabList(tabs, activeIndex) {
|
|
1968
|
+
const lines = [];
|
|
1969
|
+
lines.push(`\u6807\u7B7E\u9875\u5217\u8868\uFF08\u5171 ${tabs.length} \u4E2A\uFF0C\u5F53\u524D #${activeIndex}\uFF09\uFF1A`);
|
|
1970
|
+
for (const tab of tabs) {
|
|
1971
|
+
const prefix = tab.active ? "*" : " ";
|
|
1972
|
+
const title = tab.title || "(\u65E0\u6807\u9898)";
|
|
1973
|
+
lines.push(`${prefix} [${tab.index}] ${tab.url} - ${title}`);
|
|
1974
|
+
}
|
|
1975
|
+
return lines.join("\n");
|
|
1976
|
+
}
|
|
1977
|
+
async function tabCommand(args, options = {}) {
|
|
1978
|
+
await ensureDaemonRunning();
|
|
1979
|
+
const parsed = parseTabSubcommand(args, process.argv);
|
|
1980
|
+
if (options.globalTabId && parsed.tabId === void 0 && parsed.index === void 0) {
|
|
1981
|
+
if (parsed.action === "tab_close" || parsed.action === "tab_select") {
|
|
1982
|
+
const numId = parseInt(options.globalTabId, 10);
|
|
1983
|
+
parsed.tabId = isNaN(numId) ? options.globalTabId : numId;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
const request = {
|
|
1987
|
+
id: generateId(),
|
|
1988
|
+
action: parsed.action,
|
|
1989
|
+
url: parsed.url,
|
|
1990
|
+
index: parsed.index,
|
|
1991
|
+
tabId: parsed.tabId
|
|
1992
|
+
};
|
|
1993
|
+
const response = await sendCommand(request);
|
|
1994
|
+
if (options.json) {
|
|
1995
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1996
|
+
} else {
|
|
1997
|
+
if (response.success) {
|
|
1998
|
+
switch (parsed.action) {
|
|
1999
|
+
case "tab_list": {
|
|
2000
|
+
const tabs = response.data?.tabs ?? [];
|
|
2001
|
+
const activeIndex = response.data?.activeIndex ?? 0;
|
|
2002
|
+
console.log(formatTabList(tabs, activeIndex));
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
2005
|
+
case "tab_new": {
|
|
2006
|
+
const url = response.data?.url ?? "about:blank";
|
|
2007
|
+
console.log(`\u5DF2\u521B\u5EFA\u65B0\u6807\u7B7E\u9875: ${url}`);
|
|
2008
|
+
break;
|
|
2009
|
+
}
|
|
2010
|
+
case "tab_select": {
|
|
2011
|
+
const title = response.data?.title ?? "(\u65E0\u6807\u9898)";
|
|
2012
|
+
const url = response.data?.url ?? "";
|
|
2013
|
+
console.log(`\u5DF2\u5207\u6362\u5230\u6807\u7B7E\u9875 #${parsed.index}: ${title}`);
|
|
2014
|
+
console.log(` URL: ${url}`);
|
|
2015
|
+
break;
|
|
2016
|
+
}
|
|
2017
|
+
case "tab_close": {
|
|
2018
|
+
const closedTitle = response.data?.title ?? "(\u65E0\u6807\u9898)";
|
|
2019
|
+
console.log(`\u5DF2\u5173\u95ED\u6807\u7B7E\u9875: ${closedTitle}`);
|
|
2020
|
+
break;
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
} else {
|
|
2024
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
2025
|
+
process.exit(1);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
// packages/cli/src/commands/frame.ts
|
|
2031
|
+
async function frameCommand(selector, options = {}) {
|
|
2032
|
+
if (!selector) {
|
|
2033
|
+
throw new Error("\u7F3A\u5C11 selector \u53C2\u6570");
|
|
2034
|
+
}
|
|
2035
|
+
await ensureDaemonRunning();
|
|
2036
|
+
const request = {
|
|
2037
|
+
id: generateId(),
|
|
2038
|
+
action: "frame",
|
|
2039
|
+
selector,
|
|
2040
|
+
tabId: options.tabId
|
|
2041
|
+
};
|
|
2042
|
+
const response = await sendCommand(request);
|
|
2043
|
+
if (options.json) {
|
|
2044
|
+
console.log(JSON.stringify(response, null, 2));
|
|
2045
|
+
} else {
|
|
2046
|
+
if (response.success) {
|
|
2047
|
+
const frameInfo = response.data?.frameInfo;
|
|
2048
|
+
if (frameInfo?.url) {
|
|
2049
|
+
console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector} (${frameInfo.url})`);
|
|
2050
|
+
} else {
|
|
2051
|
+
console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector}`);
|
|
2052
|
+
}
|
|
2053
|
+
} else {
|
|
2054
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
2055
|
+
process.exit(1);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
async function frameMainCommand(options = {}) {
|
|
2060
|
+
await ensureDaemonRunning();
|
|
2061
|
+
const request = {
|
|
2062
|
+
id: generateId(),
|
|
2063
|
+
action: "frame_main",
|
|
2064
|
+
tabId: options.tabId
|
|
2065
|
+
};
|
|
2066
|
+
const response = await sendCommand(request);
|
|
2067
|
+
if (options.json) {
|
|
2068
|
+
console.log(JSON.stringify(response, null, 2));
|
|
2069
|
+
} else {
|
|
2070
|
+
if (response.success) {
|
|
2071
|
+
console.log("\u5DF2\u8FD4\u56DE\u4E3B frame");
|
|
2072
|
+
} else {
|
|
2073
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
2074
|
+
process.exit(1);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
// packages/cli/src/commands/dialog.ts
|
|
2080
|
+
async function dialogCommand(subCommand, promptText, options = {}) {
|
|
2081
|
+
if (!subCommand || !["accept", "dismiss"].includes(subCommand)) {
|
|
2082
|
+
throw new Error("\u8BF7\u4F7F\u7528 'dialog accept [text]' \u6216 'dialog dismiss'");
|
|
2083
|
+
}
|
|
2084
|
+
await ensureDaemonRunning();
|
|
2085
|
+
const request = {
|
|
2086
|
+
id: generateId(),
|
|
2087
|
+
action: "dialog",
|
|
2088
|
+
dialogResponse: subCommand,
|
|
2089
|
+
promptText: subCommand === "accept" ? promptText : void 0,
|
|
2090
|
+
tabId: options.tabId
|
|
2091
|
+
};
|
|
2092
|
+
const response = await sendCommand(request);
|
|
2093
|
+
if (options.json) {
|
|
2094
|
+
console.log(JSON.stringify(response, null, 2));
|
|
2095
|
+
} else {
|
|
2096
|
+
if (response.success) {
|
|
2097
|
+
const dialogInfo = response.data?.dialogInfo;
|
|
2098
|
+
if (dialogInfo) {
|
|
2099
|
+
const action = subCommand === "accept" ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
|
|
2100
|
+
console.log(`${action}\u5BF9\u8BDD\u6846\uFF08${dialogInfo.type}\uFF09: "${dialogInfo.message}"`);
|
|
2101
|
+
} else {
|
|
2102
|
+
console.log("\u5BF9\u8BDD\u6846\u5DF2\u5904\u7406");
|
|
2103
|
+
}
|
|
2104
|
+
} else {
|
|
2105
|
+
console.error(`\u9519\u8BEF: ${response.error}`);
|
|
2106
|
+
process.exit(1);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// packages/cli/src/commands/network.ts
|
|
2112
|
+
async function networkCommand(subCommand, urlOrFilter, options = {}) {
|
|
2113
|
+
let since;
|
|
2114
|
+
if (subCommand === "requests" && options.since) {
|
|
2115
|
+
const num = parseInt(options.since, 10);
|
|
2116
|
+
since = !isNaN(num) && String(num) === options.since ? num : options.since;
|
|
2117
|
+
}
|
|
2118
|
+
const request = {
|
|
2119
|
+
id: generateId(),
|
|
2120
|
+
action: "network",
|
|
2121
|
+
networkCommand: subCommand,
|
|
2122
|
+
url: subCommand === "route" || subCommand === "unroute" ? urlOrFilter : void 0,
|
|
2123
|
+
filter: subCommand === "requests" ? urlOrFilter : void 0,
|
|
2124
|
+
routeOptions: subCommand === "route" ? {
|
|
2125
|
+
abort: options.abort,
|
|
2126
|
+
body: options.body
|
|
2127
|
+
} : void 0,
|
|
2128
|
+
withBody: subCommand === "requests" ? options.withBody : void 0,
|
|
2129
|
+
since,
|
|
2130
|
+
method: subCommand === "requests" ? options.method : void 0,
|
|
2131
|
+
status: subCommand === "requests" ? options.status : void 0,
|
|
2132
|
+
tabId: options.tabId
|
|
2133
|
+
};
|
|
2134
|
+
const response = await sendCommand(request);
|
|
2135
|
+
if (options.json) {
|
|
2136
|
+
console.log(JSON.stringify(response));
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
if (!response.success) {
|
|
2140
|
+
throw new Error(response.error || "Network command failed");
|
|
2141
|
+
}
|
|
2142
|
+
const data = response.data;
|
|
2143
|
+
switch (subCommand) {
|
|
2144
|
+
case "requests": {
|
|
2145
|
+
const requests = data?.networkRequests || [];
|
|
2146
|
+
if (requests.length === 0) {
|
|
2147
|
+
console.log("\u6CA1\u6709\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
|
|
2148
|
+
console.log("\u63D0\u793A: \u4F7F\u7528 network requests \u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
|
|
2149
|
+
} else {
|
|
2150
|
+
console.log(`\u7F51\u7EDC\u8BF7\u6C42 (${requests.length} \u6761):
|
|
2151
|
+
`);
|
|
2152
|
+
for (const req of requests) {
|
|
2153
|
+
const status = req.failed ? `FAILED (${req.failureReason})` : req.status ? `${req.status} ${req.statusText || ""}` : "pending";
|
|
2154
|
+
console.log(`${req.method} ${req.url}`);
|
|
2155
|
+
console.log(` \u7C7B\u578B: ${req.type}, \u72B6\u6001: ${status}`);
|
|
2156
|
+
if (options.withBody) {
|
|
2157
|
+
const requestHeaderCount = req.requestHeaders ? Object.keys(req.requestHeaders).length : 0;
|
|
2158
|
+
const responseHeaderCount = req.responseHeaders ? Object.keys(req.responseHeaders).length : 0;
|
|
2159
|
+
console.log(` \u8BF7\u6C42\u5934: ${requestHeaderCount}, \u54CD\u5E94\u5934: ${responseHeaderCount}`);
|
|
2160
|
+
if (req.requestBody !== void 0) {
|
|
2161
|
+
const preview = req.requestBody.length > 200 ? `${req.requestBody.slice(0, 200)}...` : req.requestBody;
|
|
2162
|
+
console.log(` \u8BF7\u6C42\u4F53: ${preview}`);
|
|
2163
|
+
}
|
|
2164
|
+
if (req.responseBody !== void 0) {
|
|
2165
|
+
const preview = req.responseBody.length > 200 ? `${req.responseBody.slice(0, 200)}...` : req.responseBody;
|
|
2166
|
+
console.log(` \u54CD\u5E94\u4F53: ${preview}`);
|
|
2167
|
+
}
|
|
2168
|
+
if (req.bodyError) {
|
|
2169
|
+
console.log(` Body\u9519\u8BEF: ${req.bodyError}`);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
console.log("");
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
case "route": {
|
|
2178
|
+
console.log(`\u5DF2\u6DFB\u52A0\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
|
|
2179
|
+
if (options.abort) {
|
|
2180
|
+
console.log(" \u884C\u4E3A: \u963B\u6B62\u8BF7\u6C42");
|
|
2181
|
+
} else if (options.body) {
|
|
2182
|
+
console.log(" \u884C\u4E3A: \u8FD4\u56DE mock \u6570\u636E");
|
|
2183
|
+
} else {
|
|
2184
|
+
console.log(" \u884C\u4E3A: \u7EE7\u7EED\u8BF7\u6C42");
|
|
2185
|
+
}
|
|
2186
|
+
console.log(`\u5F53\u524D\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
|
|
2187
|
+
break;
|
|
2188
|
+
}
|
|
2189
|
+
case "unroute": {
|
|
2190
|
+
if (urlOrFilter) {
|
|
2191
|
+
console.log(`\u5DF2\u79FB\u9664\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
|
|
2192
|
+
} else {
|
|
2193
|
+
console.log("\u5DF2\u79FB\u9664\u6240\u6709\u62E6\u622A\u89C4\u5219");
|
|
2194
|
+
}
|
|
2195
|
+
console.log(`\u5269\u4F59\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
|
|
2196
|
+
break;
|
|
2197
|
+
}
|
|
2198
|
+
case "clear": {
|
|
2199
|
+
console.log("\u5DF2\u6E05\u7A7A\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
|
|
2200
|
+
break;
|
|
2201
|
+
}
|
|
2202
|
+
default:
|
|
2203
|
+
throw new Error(`\u672A\u77E5\u7684 network \u5B50\u547D\u4EE4: ${subCommand}`);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// packages/cli/src/commands/console.ts
|
|
2208
|
+
async function consoleCommand(options = {}) {
|
|
2209
|
+
let since;
|
|
2210
|
+
if (options.since) {
|
|
2211
|
+
const num = parseInt(options.since, 10);
|
|
2212
|
+
since = !isNaN(num) && String(num) === options.since ? num : options.since;
|
|
2213
|
+
}
|
|
2214
|
+
const request = {
|
|
2215
|
+
id: generateId(),
|
|
2216
|
+
action: "console",
|
|
2217
|
+
consoleCommand: options.clear ? "clear" : "get",
|
|
2218
|
+
tabId: options.tabId,
|
|
2219
|
+
since
|
|
2220
|
+
};
|
|
2221
|
+
const response = await sendCommand(request);
|
|
2222
|
+
if (options.json) {
|
|
2223
|
+
console.log(JSON.stringify(response));
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
if (!response.success) {
|
|
2227
|
+
throw new Error(response.error || "Console command failed");
|
|
2228
|
+
}
|
|
2229
|
+
if (options.clear) {
|
|
2230
|
+
console.log("\u5DF2\u6E05\u7A7A\u63A7\u5236\u53F0\u6D88\u606F");
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
const messages = response.data?.consoleMessages || [];
|
|
2234
|
+
if (messages.length === 0) {
|
|
2235
|
+
console.log("\u6CA1\u6709\u63A7\u5236\u53F0\u6D88\u606F");
|
|
2236
|
+
console.log("\u63D0\u793A: console \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
console.log(`\u63A7\u5236\u53F0\u6D88\u606F (${messages.length} \u6761):
|
|
2240
|
+
`);
|
|
2241
|
+
const typeColors = {
|
|
2242
|
+
log: "",
|
|
2243
|
+
info: "[INFO]",
|
|
2244
|
+
warn: "[WARN]",
|
|
2245
|
+
error: "[ERROR]",
|
|
2246
|
+
debug: "[DEBUG]"
|
|
2247
|
+
};
|
|
2248
|
+
for (const msg of messages) {
|
|
2249
|
+
const prefix = typeColors[msg.type] || `[${msg.type.toUpperCase()}]`;
|
|
2250
|
+
const location = msg.url ? ` (${msg.url}${msg.lineNumber ? `:${msg.lineNumber}` : ""})` : "";
|
|
2251
|
+
if (prefix) {
|
|
2252
|
+
console.log(`${prefix} ${msg.text}${location}`);
|
|
2253
|
+
} else {
|
|
2254
|
+
console.log(`${msg.text}${location}`);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// packages/cli/src/commands/errors.ts
|
|
2260
|
+
async function errorsCommand(options = {}) {
|
|
2261
|
+
let since;
|
|
2262
|
+
if (options.since) {
|
|
2263
|
+
const num = parseInt(options.since, 10);
|
|
2264
|
+
since = !isNaN(num) && String(num) === options.since ? num : options.since;
|
|
2265
|
+
}
|
|
2266
|
+
const request = {
|
|
2267
|
+
id: generateId(),
|
|
2268
|
+
action: "errors",
|
|
2269
|
+
errorsCommand: options.clear ? "clear" : "get",
|
|
2270
|
+
tabId: options.tabId,
|
|
2271
|
+
since
|
|
2272
|
+
};
|
|
2273
|
+
const response = await sendCommand(request);
|
|
2274
|
+
if (options.json) {
|
|
2275
|
+
console.log(JSON.stringify(response));
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (!response.success) {
|
|
2279
|
+
throw new Error(response.error || "Errors command failed");
|
|
2280
|
+
}
|
|
2281
|
+
if (options.clear) {
|
|
2282
|
+
console.log("\u5DF2\u6E05\u7A7A JS \u9519\u8BEF\u8BB0\u5F55");
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
const errors = response.data?.jsErrors || [];
|
|
2286
|
+
if (errors.length === 0) {
|
|
2287
|
+
console.log("\u6CA1\u6709 JS \u9519\u8BEF");
|
|
2288
|
+
console.log("\u63D0\u793A: errors \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
console.log(`JS \u9519\u8BEF (${errors.length} \u6761):
|
|
2292
|
+
`);
|
|
2293
|
+
for (const err of errors) {
|
|
2294
|
+
console.log(`[ERROR] ${err.message}`);
|
|
2295
|
+
if (err.url) {
|
|
2296
|
+
console.log(` \u4F4D\u7F6E: ${err.url}:${err.lineNumber || 0}:${err.columnNumber || 0}`);
|
|
2297
|
+
}
|
|
2298
|
+
if (err.stackTrace) {
|
|
2299
|
+
console.log(` \u5806\u6808:`);
|
|
2300
|
+
console.log(err.stackTrace.split("\n").map((line) => ` ${line}`).join("\n"));
|
|
2301
|
+
}
|
|
2302
|
+
console.log("");
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
// packages/cli/src/commands/trace.ts
|
|
2307
|
+
async function traceCommand(subCommand, options = {}) {
|
|
2308
|
+
const response = await sendCommand({
|
|
2309
|
+
id: generateId(),
|
|
2310
|
+
action: "trace",
|
|
2311
|
+
traceCommand: subCommand,
|
|
2312
|
+
tabId: options.tabId
|
|
2313
|
+
});
|
|
2314
|
+
if (options.json) {
|
|
2315
|
+
console.log(JSON.stringify(response));
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
if (!response.success) {
|
|
2319
|
+
throw new Error(response.error || "Trace command failed");
|
|
2320
|
+
}
|
|
2321
|
+
const data = response.data;
|
|
2322
|
+
switch (subCommand) {
|
|
2323
|
+
case "start": {
|
|
2324
|
+
const status = data?.traceStatus;
|
|
2325
|
+
console.log("\u5F00\u59CB\u5F55\u5236\u7528\u6237\u64CD\u4F5C");
|
|
2326
|
+
console.log(`\u6807\u7B7E\u9875 ID: ${status?.tabId || "N/A"}`);
|
|
2327
|
+
console.log("\n\u5728\u6D4F\u89C8\u5668\u4E2D\u8FDB\u884C\u64CD\u4F5C\uFF0C\u5B8C\u6210\u540E\u8FD0\u884C 'bb-browser trace stop' \u505C\u6B62\u5F55\u5236");
|
|
2328
|
+
break;
|
|
2329
|
+
}
|
|
2330
|
+
case "stop": {
|
|
2331
|
+
const events = data?.traceEvents || [];
|
|
2332
|
+
const status = data?.traceStatus;
|
|
2333
|
+
console.log(`\u5F55\u5236\u5B8C\u6210\uFF0C\u5171 ${events.length} \u4E2A\u4E8B\u4EF6
|
|
2334
|
+
`);
|
|
2335
|
+
if (events.length === 0) {
|
|
2336
|
+
console.log("\u6CA1\u6709\u5F55\u5236\u5230\u4EFB\u4F55\u64CD\u4F5C");
|
|
2337
|
+
break;
|
|
2338
|
+
}
|
|
2339
|
+
for (let i = 0; i < events.length; i++) {
|
|
2340
|
+
const event = events[i];
|
|
2341
|
+
const refStr = event.ref !== void 0 ? `@${event.ref}` : "";
|
|
2342
|
+
switch (event.type) {
|
|
2343
|
+
case "navigation":
|
|
2344
|
+
console.log(`${i + 1}. \u5BFC\u822A\u5230: ${event.url}`);
|
|
2345
|
+
break;
|
|
2346
|
+
case "click":
|
|
2347
|
+
console.log(`${i + 1}. \u70B9\u51FB ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
|
|
2348
|
+
break;
|
|
2349
|
+
case "fill":
|
|
2350
|
+
console.log(`${i + 1}. \u586B\u5145 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
|
|
2351
|
+
break;
|
|
2352
|
+
case "select":
|
|
2353
|
+
console.log(`${i + 1}. \u9009\u62E9 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
|
|
2354
|
+
break;
|
|
2355
|
+
case "check":
|
|
2356
|
+
console.log(`${i + 1}. ${event.checked ? "\u52FE\u9009" : "\u53D6\u6D88\u52FE\u9009"} ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
|
|
2357
|
+
break;
|
|
2358
|
+
case "press":
|
|
2359
|
+
console.log(`${i + 1}. \u6309\u952E ${event.key}`);
|
|
2360
|
+
break;
|
|
2361
|
+
case "scroll":
|
|
2362
|
+
console.log(`${i + 1}. \u6EDA\u52A8 ${event.direction} ${event.pixels}px`);
|
|
2363
|
+
break;
|
|
2364
|
+
default:
|
|
2365
|
+
console.log(`${i + 1}. ${event.type}`);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
console.log(`
|
|
2369
|
+
\u72B6\u6001: ${status?.recording ? "\u5F55\u5236\u4E2D" : "\u5DF2\u505C\u6B62"}`);
|
|
2370
|
+
break;
|
|
2371
|
+
}
|
|
2372
|
+
case "status": {
|
|
2373
|
+
const status = data?.traceStatus;
|
|
2374
|
+
if (status?.recording) {
|
|
2375
|
+
console.log(`\u5F55\u5236\u4E2D (\u6807\u7B7E\u9875 ${status.tabId})`);
|
|
2376
|
+
console.log(`\u5DF2\u5F55\u5236 ${status.eventCount} \u4E2A\u4E8B\u4EF6`);
|
|
2377
|
+
} else {
|
|
2378
|
+
console.log("\u672A\u5728\u5F55\u5236");
|
|
2379
|
+
}
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
default:
|
|
2383
|
+
throw new Error(`\u672A\u77E5\u7684 trace \u5B50\u547D\u4EE4: ${subCommand}`);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// packages/cli/src/commands/fetch.ts
|
|
2388
|
+
function matchTabOrigin2(tabUrl, targetHostname) {
|
|
2389
|
+
try {
|
|
2390
|
+
const tabHostname = new URL(tabUrl).hostname;
|
|
2391
|
+
return tabHostname === targetHostname || tabHostname.endsWith("." + targetHostname);
|
|
2392
|
+
} catch {
|
|
2393
|
+
return false;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
async function ensureTabForOrigin(origin, hostname) {
|
|
2397
|
+
const listReq = { id: generateId(), action: "tab_list" };
|
|
2398
|
+
const listResp = await sendCommand(listReq);
|
|
2399
|
+
if (listResp.success && listResp.data?.tabs) {
|
|
2400
|
+
const matchingTab = listResp.data.tabs.find(
|
|
2401
|
+
(tab) => matchTabOrigin2(tab.url, hostname)
|
|
2402
|
+
);
|
|
2403
|
+
if (matchingTab) {
|
|
2404
|
+
return matchingTab.tabId;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
|
|
2408
|
+
if (!newResp.success) {
|
|
2409
|
+
throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
|
|
2410
|
+
}
|
|
2411
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
2412
|
+
return newResp.data?.tabId;
|
|
2413
|
+
}
|
|
2414
|
+
function buildFetchScript(url, options) {
|
|
2415
|
+
const method = (options.method || "GET").toUpperCase();
|
|
2416
|
+
const hasBody = options.body && method !== "GET" && method !== "HEAD";
|
|
2417
|
+
let headersExpr = "{}";
|
|
2418
|
+
if (options.headers) {
|
|
2419
|
+
try {
|
|
2420
|
+
JSON.parse(options.headers);
|
|
2421
|
+
headersExpr = options.headers;
|
|
2422
|
+
} catch {
|
|
2423
|
+
throw new Error(`--headers must be valid JSON. Got: ${options.headers}`);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
return `(async () => {
|
|
2427
|
+
try {
|
|
2428
|
+
const resp = await fetch(${JSON.stringify(url)}, {
|
|
2429
|
+
method: ${JSON.stringify(method)},
|
|
2430
|
+
credentials: 'include',
|
|
2431
|
+
headers: ${headersExpr}${hasBody ? `,
|
|
2432
|
+
body: ${JSON.stringify(options.body)}` : ""}
|
|
2433
|
+
});
|
|
2434
|
+
const contentType = resp.headers.get('content-type') || '';
|
|
2435
|
+
let body;
|
|
2436
|
+
if (contentType.includes('application/json') && resp.status !== 204) {
|
|
2437
|
+
try { body = await resp.json(); } catch { body = await resp.text(); }
|
|
2438
|
+
} else {
|
|
2439
|
+
body = await resp.text();
|
|
2440
|
+
}
|
|
2441
|
+
return JSON.stringify({
|
|
2442
|
+
status: resp.status,
|
|
2443
|
+
contentType,
|
|
2444
|
+
body
|
|
2445
|
+
});
|
|
2446
|
+
} catch (e) {
|
|
2447
|
+
return JSON.stringify({ error: e.message });
|
|
2448
|
+
}
|
|
2449
|
+
})()`;
|
|
2450
|
+
}
|
|
2451
|
+
async function fetchCommand(url, options = {}) {
|
|
2452
|
+
if (!url) {
|
|
2453
|
+
throw new Error(
|
|
2454
|
+
"\u7F3A\u5C11 URL \u53C2\u6570\n \u7528\u6CD5: bb-browser fetch <url> [--json] [--method POST] [--body '{...}']\n \u793A\u4F8B: bb-browser fetch https://www.reddit.com/api/me.json --json"
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
await ensureDaemonRunning();
|
|
2458
|
+
const isAbsolute = url.startsWith("http://") || url.startsWith("https://");
|
|
2459
|
+
let targetTabId = options.tabId;
|
|
2460
|
+
if (isAbsolute) {
|
|
2461
|
+
let origin;
|
|
2462
|
+
let hostname;
|
|
2463
|
+
try {
|
|
2464
|
+
const parsed = new URL(url);
|
|
2465
|
+
origin = parsed.origin;
|
|
2466
|
+
hostname = parsed.hostname;
|
|
2467
|
+
} catch {
|
|
2468
|
+
throw new Error(`\u65E0\u6548\u7684 URL: ${url}`);
|
|
2469
|
+
}
|
|
2470
|
+
if (!targetTabId) {
|
|
2471
|
+
targetTabId = await ensureTabForOrigin(origin, hostname);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
const script = buildFetchScript(url, options);
|
|
2475
|
+
const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
|
|
2476
|
+
const evalResp = await sendCommand(evalReq);
|
|
2477
|
+
if (!evalResp.success) {
|
|
2478
|
+
throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
|
|
2479
|
+
}
|
|
2480
|
+
const rawResult = evalResp.data?.result;
|
|
2481
|
+
if (rawResult === void 0 || rawResult === null) {
|
|
2482
|
+
throw new Error("Fetch \u672A\u8FD4\u56DE\u7ED3\u679C");
|
|
2483
|
+
}
|
|
2484
|
+
let result;
|
|
2485
|
+
try {
|
|
2486
|
+
result = typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult;
|
|
2487
|
+
} catch {
|
|
2488
|
+
console.log(rawResult);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
if (result.error) {
|
|
2492
|
+
throw new Error(`Fetch error: ${result.error}`);
|
|
2493
|
+
}
|
|
2494
|
+
if (options.output) {
|
|
2495
|
+
const { writeFileSync } = await import("fs");
|
|
2496
|
+
const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
|
|
2497
|
+
writeFileSync(options.output, content, "utf-8");
|
|
2498
|
+
console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
if (typeof result.body === "object") {
|
|
2502
|
+
console.log(JSON.stringify(result.body, null, 2));
|
|
2503
|
+
} else {
|
|
2504
|
+
console.log(result.body);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// packages/cli/src/commands/history.ts
|
|
2509
|
+
async function historyCommand(subCommand, options = {}) {
|
|
2510
|
+
const days = options.days || 30;
|
|
2511
|
+
const data = subCommand === "search" ? { historyItems: searchHistory(options.query, days) } : { historyDomains: getHistoryDomains(days) };
|
|
2512
|
+
if (options.json) {
|
|
2513
|
+
console.log(JSON.stringify({
|
|
2514
|
+
id: generateId(),
|
|
2515
|
+
success: true,
|
|
2516
|
+
data
|
|
2517
|
+
}));
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
switch (subCommand) {
|
|
2521
|
+
case "search": {
|
|
2522
|
+
const items = data?.historyItems || [];
|
|
2523
|
+
console.log(`\u627E\u5230 ${items.length} \u6761\u5386\u53F2\u8BB0\u5F55
|
|
2524
|
+
`);
|
|
2525
|
+
if (items.length === 0) {
|
|
2526
|
+
console.log("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u5386\u53F2\u8BB0\u5F55");
|
|
2527
|
+
break;
|
|
2528
|
+
}
|
|
2529
|
+
for (let i = 0; i < items.length; i++) {
|
|
2530
|
+
const item = items[i];
|
|
2531
|
+
console.log(`${i + 1}. ${item.title || "(\u65E0\u6807\u9898)"}`);
|
|
2532
|
+
console.log(` ${item.url}`);
|
|
2533
|
+
console.log(` \u8BBF\u95EE\u6B21\u6570: ${item.visitCount}`);
|
|
2534
|
+
}
|
|
2535
|
+
break;
|
|
2536
|
+
}
|
|
2537
|
+
case "domains": {
|
|
2538
|
+
const domains = data?.historyDomains || [];
|
|
2539
|
+
console.log(`\u627E\u5230 ${domains.length} \u4E2A\u57DF\u540D
|
|
2540
|
+
`);
|
|
2541
|
+
if (domains.length === 0) {
|
|
2542
|
+
console.log("\u6CA1\u6709\u627E\u5230\u5386\u53F2\u8BB0\u5F55");
|
|
2543
|
+
break;
|
|
2544
|
+
}
|
|
2545
|
+
for (let i = 0; i < domains.length; i++) {
|
|
2546
|
+
const domain = domains[i];
|
|
2547
|
+
console.log(`${i + 1}. ${domain.domain}`);
|
|
2548
|
+
console.log(` \u8BBF\u95EE\u6B21\u6570: ${domain.visits}`);
|
|
2549
|
+
}
|
|
2550
|
+
break;
|
|
2551
|
+
}
|
|
2552
|
+
default:
|
|
2553
|
+
throw new Error(`\u672A\u77E5\u7684 history \u5B50\u547D\u4EE4: ${subCommand}`);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// packages/cli/src/commands/daemon.ts
|
|
2558
|
+
async function statusCommand(options = {}) {
|
|
2559
|
+
const status = await getDaemonStatus();
|
|
2560
|
+
if (!status) {
|
|
2561
|
+
if (options.json) {
|
|
2562
|
+
console.log(JSON.stringify({ running: false }));
|
|
2563
|
+
} else {
|
|
2564
|
+
console.log("Daemon not running");
|
|
2565
|
+
console.log("\n\u{1F4A1} \u542F\u52A8: bb-browser daemon start");
|
|
2566
|
+
}
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
if (options.json) {
|
|
2570
|
+
console.log(JSON.stringify(status, null, 2));
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
console.log(`Daemon running: ${status.running ? "yes" : "no"}`);
|
|
2574
|
+
console.log(`CDP connected: ${status.cdpConnected ? "yes" : "no"}`);
|
|
2575
|
+
console.log(`Uptime: ${formatUptime(status.uptime)}`);
|
|
2576
|
+
console.log(`Global seq: ${status.currentSeq ?? "N/A"}`);
|
|
2577
|
+
const tabs = status.tabs;
|
|
2578
|
+
if (tabs && tabs.length > 0) {
|
|
2579
|
+
console.log(`
|
|
2580
|
+
Tabs (${tabs.length}):`);
|
|
2581
|
+
for (const tab of tabs) {
|
|
2582
|
+
const active = tab.targetId === status.currentTargetId ? " *" : "";
|
|
2583
|
+
console.log(
|
|
2584
|
+
` ${tab.shortId}${active} net:${tab.networkRequests} console:${tab.consoleMessages} err:${tab.jsErrors} seq:${tab.lastActionSeq}`
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
} else {
|
|
2588
|
+
console.log("\nNo tabs");
|
|
2589
|
+
}
|
|
2590
|
+
if (status.cdpConnected === false) {
|
|
2591
|
+
console.log("\n\u26A0\uFE0F Chrome \u672A\u8FDE\u63A5\u3002\u8FD0\u884C bb-browser daemon stop && bb-browser tab list \u91CD\u65B0\u542F\u52A8");
|
|
2592
|
+
} else {
|
|
2593
|
+
console.log("\n\u{1F4A1} \u505C\u6B62: bb-browser daemon stop");
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
async function startCommand(options = {}) {
|
|
2597
|
+
await ensureDaemon();
|
|
2598
|
+
const status = await getDaemonStatus();
|
|
2599
|
+
if (options.json) {
|
|
2600
|
+
console.log(JSON.stringify(status, null, 2));
|
|
2601
|
+
} else {
|
|
2602
|
+
console.log("Daemon started");
|
|
2603
|
+
if (status) {
|
|
2604
|
+
console.log(`CDP connected: ${status.cdpConnected ? "yes" : "no"}`);
|
|
2605
|
+
const tabs = status.tabs;
|
|
2606
|
+
console.log(`Tabs: ${tabs?.length ?? 0}`);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
async function shutdownCommand(options = {}) {
|
|
2611
|
+
const ok = await stopDaemon();
|
|
2612
|
+
if (options.json) {
|
|
2613
|
+
console.log(JSON.stringify({ stopped: ok }));
|
|
2614
|
+
} else {
|
|
2615
|
+
console.log(ok ? "Daemon stopped" : "Daemon was not running");
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
function formatUptime(ms) {
|
|
2619
|
+
if (!ms || ms <= 0) return "0s";
|
|
2620
|
+
const s = Math.floor(ms / 1e3);
|
|
2621
|
+
if (s < 60) return `${s}s`;
|
|
2622
|
+
const m = Math.floor(s / 60);
|
|
2623
|
+
if (m < 60) return `${m}m ${s % 60}s`;
|
|
2624
|
+
const h = Math.floor(m / 60);
|
|
2625
|
+
return `${h}h ${m % 60}m`;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// packages/cli/src/index.ts
|
|
2629
|
+
var VERSION = "0.11.5";
|
|
2630
|
+
var HELP_TEXT = `
|
|
2631
|
+
bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
|
|
2632
|
+
|
|
2633
|
+
\u5B89\u88C5\uFF1A
|
|
2634
|
+
npm install -g bb-browser
|
|
2635
|
+
|
|
2636
|
+
\u63D0\u793A\uFF1A\u5927\u591A\u6570\u6570\u636E\u83B7\u53D6\u4EFB\u52A1\u8BF7\u76F4\u63A5\u4F7F\u7528 site \u547D\u4EE4\uFF0C\u65E0\u9700\u624B\u52A8\u64CD\u4F5C\u6D4F\u89C8\u5668\uFF1A
|
|
2637
|
+
bb-browser site list \u67E5\u770B\u6240\u6709\u53EF\u7528\u547D\u4EE4
|
|
2638
|
+
bb-browser site twitter/search "AI" \u793A\u4F8B\uFF1A\u641C\u7D22\u63A8\u6587
|
|
2639
|
+
bb-browser site xueqiu/hot-stock 5 \u793A\u4F8B\uFF1A\u83B7\u53D6\u4EBA\u6C14\u80A1\u7968
|
|
2640
|
+
|
|
2641
|
+
\u7528\u6CD5\uFF1A
|
|
2642
|
+
bb-browser <command> [options]
|
|
2643
|
+
|
|
2644
|
+
\u5F00\u59CB\u4F7F\u7528\uFF1A
|
|
2645
|
+
site recommend \u63A8\u8350\u4F60\u53EF\u80FD\u9700\u8981\u7684 adapter\uFF08\u57FA\u4E8E\u6D4F\u89C8\u5386\u53F2\uFF09
|
|
2646
|
+
site list \u5217\u51FA\u6240\u6709 adapter
|
|
2647
|
+
site info <name> \u67E5\u770B adapter \u7528\u6CD5\uFF08\u53C2\u6570\u3001\u8FD4\u56DE\u503C\u3001\u793A\u4F8B\uFF09
|
|
2648
|
+
site <name> [args] \u8FD0\u884C adapter
|
|
2649
|
+
site update \u66F4\u65B0\u793E\u533A adapter \u5E93
|
|
2650
|
+
guide \u5982\u4F55\u628A\u4EFB\u4F55\u7F51\u7AD9\u53D8\u6210 adapter
|
|
2651
|
+
star \u2B50 Star bb-browser on GitHub
|
|
2652
|
+
|
|
2653
|
+
\u6D4F\u89C8\u5668\u64CD\u4F5C\uFF1A
|
|
2654
|
+
open <url> [--tab] \u6253\u5F00 URL
|
|
2655
|
+
snapshot [-i] [-c] [-d <n>] \u83B7\u53D6\u9875\u9762\u5FEB\u7167
|
|
2656
|
+
click <ref> \u70B9\u51FB\u5143\u7D20
|
|
2657
|
+
hover <ref> \u60AC\u505C\u5143\u7D20
|
|
2658
|
+
fill <ref> <text> \u586B\u5145\u8F93\u5165\u6846\uFF08\u6E05\u7A7A\u540E\u586B\u5165\uFF09
|
|
2659
|
+
type <ref> <text> \u9010\u5B57\u7B26\u8F93\u5165\uFF08\u4E0D\u6E05\u7A7A\uFF09
|
|
2660
|
+
check/uncheck <ref> \u52FE\u9009/\u53D6\u6D88\u590D\u9009\u6846
|
|
2661
|
+
select <ref> <val> \u4E0B\u62C9\u6846\u9009\u62E9
|
|
2662
|
+
press <key> \u53D1\u9001\u6309\u952E
|
|
2663
|
+
scroll <dir> [px] \u6EDA\u52A8\u9875\u9762
|
|
2664
|
+
|
|
2665
|
+
\u9875\u9762\u4FE1\u606F\uFF1A
|
|
2666
|
+
get text|url|title <ref> \u83B7\u53D6\u9875\u9762\u5185\u5BB9
|
|
2667
|
+
screenshot [path] \u622A\u56FE
|
|
2668
|
+
eval "<js>" \u6267\u884C JavaScript
|
|
2669
|
+
fetch <url> \u5E26\u767B\u5F55\u6001\u7684 HTTP \u8BF7\u6C42
|
|
2670
|
+
|
|
2671
|
+
\u6807\u7B7E\u9875\uFF1A
|
|
2672
|
+
tab [list|new|close|<n>] \u7BA1\u7406\u6807\u7B7E\u9875
|
|
2673
|
+
status \u67E5\u770B\u53D7\u7BA1\u6D4F\u89C8\u5668\u72B6\u6001
|
|
2674
|
+
|
|
2675
|
+
\u5BFC\u822A\uFF1A
|
|
2676
|
+
back / forward / refresh \u540E\u9000 / \u524D\u8FDB / \u5237\u65B0
|
|
2677
|
+
|
|
2678
|
+
\u8C03\u8BD5\uFF1A
|
|
2679
|
+
network requests [filter] \u67E5\u770B\u7F51\u7EDC\u8BF7\u6C42
|
|
2680
|
+
console [--clear] \u67E5\u770B/\u6E05\u7A7A\u63A7\u5236\u53F0
|
|
2681
|
+
errors [--clear] \u67E5\u770B/\u6E05\u7A7A JS \u9519\u8BEF
|
|
2682
|
+
trace start|stop|status \u5F55\u5236\u7528\u6237\u64CD\u4F5C
|
|
2683
|
+
history search|domains \u67E5\u770B\u6D4F\u89C8\u5386\u53F2
|
|
2684
|
+
daemon [start|status|stop] \u7BA1\u7406 daemon\uFF08start: \u540E\u53F0\u542F\u52A8\uFF09
|
|
2685
|
+
|
|
2686
|
+
\u9009\u9879\uFF1A
|
|
2687
|
+
--json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
|
|
2688
|
+
--port <n> \u6307\u5B9A Chrome CDP \u7AEF\u53E3
|
|
2689
|
+
--openclaw \u4F18\u5148\u590D\u7528 OpenClaw \u6D4F\u89C8\u5668\u5B9E\u4F8B
|
|
2690
|
+
--jq <expr> \u5BF9 JSON \u8F93\u51FA\u5E94\u7528 jq \u8FC7\u6EE4\uFF08\u76F4\u63A5\u4F5C\u7528\u4E8E\u6570\u636E\uFF0C\u8DF3\u8FC7 id/success \u4FE1\u5C01\uFF09
|
|
2691
|
+
-i, --interactive \u53EA\u8F93\u51FA\u53EF\u4EA4\u4E92\u5143\u7D20\uFF08snapshot \u547D\u4EE4\uFF09
|
|
2692
|
+
-c, --compact \u79FB\u9664\u7A7A\u7ED3\u6784\u8282\u70B9\uFF08snapshot \u547D\u4EE4\uFF09
|
|
2693
|
+
-d, --depth <n> \u9650\u5236\u6811\u6DF1\u5EA6\uFF08snapshot \u547D\u4EE4\uFF09
|
|
2694
|
+
-s, --selector <sel> \u9650\u5B9A CSS \u9009\u62E9\u5668\u8303\u56F4\uFF08snapshot \u547D\u4EE4\uFF09
|
|
2695
|
+
--tab <tabId> \u6307\u5B9A\u64CD\u4F5C\u7684\u6807\u7B7E\u9875 ID
|
|
2696
|
+
--mcp \u542F\u52A8 MCP server\uFF08\u7528\u4E8E Claude Code / Cursor \u7B49 AI \u5DE5\u5177\uFF09
|
|
2697
|
+
--help, -h \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
|
|
2698
|
+
--version, -v \u663E\u793A\u7248\u672C\u53F7
|
|
2699
|
+
`.trim();
|
|
2700
|
+
function parseArgs(argv) {
|
|
2701
|
+
const args = argv.slice(2);
|
|
2702
|
+
const result = {
|
|
2703
|
+
command: null,
|
|
2704
|
+
args: [],
|
|
2705
|
+
flags: {
|
|
2706
|
+
json: false,
|
|
2707
|
+
help: false,
|
|
2708
|
+
version: false,
|
|
2709
|
+
interactive: false,
|
|
2710
|
+
compact: false
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
let skipNext = false;
|
|
2714
|
+
for (const arg of args) {
|
|
2715
|
+
if (skipNext) {
|
|
2716
|
+
skipNext = false;
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
2719
|
+
if (arg === "--json") {
|
|
2720
|
+
result.flags.json = true;
|
|
2721
|
+
} else if (arg === "--jq") {
|
|
2722
|
+
skipNext = true;
|
|
2723
|
+
const nextIdx = args.indexOf(arg) + 1;
|
|
2724
|
+
if (nextIdx < args.length) {
|
|
2725
|
+
result.flags.jq = args[nextIdx];
|
|
2726
|
+
result.flags.json = true;
|
|
2727
|
+
}
|
|
2728
|
+
} else if (arg === "--openclaw") {
|
|
2729
|
+
result.flags.openclaw = true;
|
|
2730
|
+
} else if (arg === "--port") {
|
|
2731
|
+
skipNext = true;
|
|
2732
|
+
const nextIdx = args.indexOf(arg) + 1;
|
|
2733
|
+
if (nextIdx < args.length) {
|
|
2734
|
+
result.flags.port = parseInt(args[nextIdx], 10);
|
|
2735
|
+
}
|
|
2736
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
2737
|
+
result.flags.help = true;
|
|
2738
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
2739
|
+
result.flags.version = true;
|
|
2740
|
+
} else if (arg === "--interactive" || arg === "-i") {
|
|
2741
|
+
result.flags.interactive = true;
|
|
2742
|
+
} else if (arg === "--compact" || arg === "-c") {
|
|
2743
|
+
result.flags.compact = true;
|
|
2744
|
+
} else if (arg === "--depth" || arg === "-d") {
|
|
2745
|
+
skipNext = true;
|
|
2746
|
+
const nextIdx = args.indexOf(arg) + 1;
|
|
2747
|
+
if (nextIdx < args.length) {
|
|
2748
|
+
result.flags.depth = parseInt(args[nextIdx], 10);
|
|
2749
|
+
}
|
|
2750
|
+
} else if (arg === "--selector" || arg === "-s") {
|
|
2751
|
+
skipNext = true;
|
|
2752
|
+
const nextIdx = args.indexOf(arg) + 1;
|
|
2753
|
+
if (nextIdx < args.length) {
|
|
2754
|
+
result.flags.selector = args[nextIdx];
|
|
2755
|
+
}
|
|
2756
|
+
} else if (arg === "--days") {
|
|
2757
|
+
skipNext = true;
|
|
2758
|
+
const nextIdx = args.indexOf(arg) + 1;
|
|
2759
|
+
if (nextIdx < args.length) {
|
|
2760
|
+
result.flags.days = parseInt(args[nextIdx], 10);
|
|
2761
|
+
}
|
|
2762
|
+
} else if (arg === "--id") {
|
|
2763
|
+
skipNext = true;
|
|
2764
|
+
} else if (arg === "--tab") {
|
|
2765
|
+
skipNext = true;
|
|
2766
|
+
} else if (arg === "--since") {
|
|
2767
|
+
skipNext = true;
|
|
2768
|
+
} else if (arg === "--method") {
|
|
2769
|
+
skipNext = true;
|
|
2770
|
+
} else if (arg === "--status") {
|
|
2771
|
+
skipNext = true;
|
|
2772
|
+
} else if (arg.startsWith("-")) {
|
|
2773
|
+
} else if (result.command === null) {
|
|
2774
|
+
result.command = arg;
|
|
2775
|
+
} else {
|
|
2776
|
+
result.args.push(arg);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
return result;
|
|
2780
|
+
}
|
|
2781
|
+
async function main() {
|
|
2782
|
+
const parsed = parseArgs(process.argv);
|
|
2783
|
+
setJqExpression(parsed.flags.jq);
|
|
2784
|
+
const tabArgIdx = process.argv.indexOf("--tab");
|
|
2785
|
+
const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? process.argv[tabArgIdx + 1] : void 0;
|
|
2786
|
+
const sinceArgIdx = process.argv.indexOf("--since");
|
|
2787
|
+
const globalSince = sinceArgIdx >= 0 && process.argv[sinceArgIdx + 1] ? process.argv[sinceArgIdx + 1] : void 0;
|
|
2788
|
+
if (parsed.flags.version) {
|
|
2789
|
+
console.log(VERSION);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
if (process.argv.includes("--mcp")) {
|
|
2793
|
+
const mcpPath = fileURLToPath2(new URL("./mcp.js", import.meta.url));
|
|
2794
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
2795
|
+
const child = spawn3(process.execPath, [mcpPath], { stdio: "inherit" });
|
|
2796
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
if (!parsed.command) {
|
|
2800
|
+
console.log(HELP_TEXT);
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2803
|
+
if (parsed.flags.help && parsed.command !== "daemon") {
|
|
2804
|
+
console.log(HELP_TEXT);
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
try {
|
|
2808
|
+
switch (parsed.command) {
|
|
2809
|
+
case "open": {
|
|
2810
|
+
const url = parsed.args[0];
|
|
2811
|
+
if (!url) {
|
|
2812
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 URL \u53C2\u6570");
|
|
2813
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser open <url> [--tab current|<tabId>]");
|
|
2814
|
+
process.exit(1);
|
|
2815
|
+
}
|
|
2816
|
+
const tabIndex = process.argv.findIndex((a) => a === "--tab");
|
|
2817
|
+
const tab = tabIndex >= 0 ? process.argv[tabIndex + 1] : void 0;
|
|
2818
|
+
await openCommand(url, { json: parsed.flags.json, tab });
|
|
2819
|
+
break;
|
|
2820
|
+
}
|
|
2821
|
+
case "snapshot": {
|
|
2822
|
+
await snapshotCommand({
|
|
2823
|
+
json: parsed.flags.json,
|
|
2824
|
+
interactive: parsed.flags.interactive,
|
|
2825
|
+
compact: parsed.flags.compact,
|
|
2826
|
+
maxDepth: parsed.flags.depth,
|
|
2827
|
+
selector: parsed.flags.selector,
|
|
2828
|
+
tabId: globalTabId
|
|
2829
|
+
});
|
|
2830
|
+
break;
|
|
2831
|
+
}
|
|
2832
|
+
case "click": {
|
|
2833
|
+
const ref = parsed.args[0];
|
|
2834
|
+
if (!ref) {
|
|
2835
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2836
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser click <ref>");
|
|
2837
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser click @5");
|
|
2838
|
+
process.exit(1);
|
|
2839
|
+
}
|
|
2840
|
+
await clickCommand(ref, { json: parsed.flags.json, tabId: globalTabId });
|
|
2841
|
+
break;
|
|
2842
|
+
}
|
|
2843
|
+
case "hover": {
|
|
2844
|
+
const ref = parsed.args[0];
|
|
2845
|
+
if (!ref) {
|
|
2846
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2847
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser hover <ref>");
|
|
2848
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser hover @5");
|
|
2849
|
+
process.exit(1);
|
|
2850
|
+
}
|
|
2851
|
+
await hoverCommand(ref, { json: parsed.flags.json, tabId: globalTabId });
|
|
2852
|
+
break;
|
|
2853
|
+
}
|
|
2854
|
+
case "check": {
|
|
2855
|
+
const ref = parsed.args[0];
|
|
2856
|
+
if (!ref) {
|
|
2857
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2858
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser check <ref>");
|
|
2859
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser check @5");
|
|
2860
|
+
process.exit(1);
|
|
2861
|
+
}
|
|
2862
|
+
await checkCommand(ref, { json: parsed.flags.json, tabId: globalTabId });
|
|
2863
|
+
break;
|
|
2864
|
+
}
|
|
2865
|
+
case "uncheck": {
|
|
2866
|
+
const ref = parsed.args[0];
|
|
2867
|
+
if (!ref) {
|
|
2868
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2869
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser uncheck <ref>");
|
|
2870
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser uncheck @5");
|
|
2871
|
+
process.exit(1);
|
|
2872
|
+
}
|
|
2873
|
+
await uncheckCommand(ref, { json: parsed.flags.json, tabId: globalTabId });
|
|
2874
|
+
break;
|
|
2875
|
+
}
|
|
2876
|
+
case "fill": {
|
|
2877
|
+
const ref = parsed.args[0];
|
|
2878
|
+
const text = parsed.args[1];
|
|
2879
|
+
if (!ref) {
|
|
2880
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2881
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser fill <ref> <text>");
|
|
2882
|
+
console.error('\u793A\u4F8B\uFF1Abb-browser fill @3 "hello world"');
|
|
2883
|
+
process.exit(1);
|
|
2884
|
+
}
|
|
2885
|
+
if (text === void 0) {
|
|
2886
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 text \u53C2\u6570");
|
|
2887
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser fill <ref> <text>");
|
|
2888
|
+
console.error('\u793A\u4F8B\uFF1Abb-browser fill @3 "hello world"');
|
|
2889
|
+
process.exit(1);
|
|
2890
|
+
}
|
|
2891
|
+
await fillCommand(ref, text, { json: parsed.flags.json, tabId: globalTabId });
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
case "type": {
|
|
2895
|
+
const ref = parsed.args[0];
|
|
2896
|
+
const text = parsed.args[1];
|
|
2897
|
+
if (!ref) {
|
|
2898
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2899
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser type <ref> <text>");
|
|
2900
|
+
console.error('\u793A\u4F8B\uFF1Abb-browser type @3 "append text"');
|
|
2901
|
+
process.exit(1);
|
|
2902
|
+
}
|
|
2903
|
+
if (text === void 0) {
|
|
2904
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 text \u53C2\u6570");
|
|
2905
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser type <ref> <text>");
|
|
2906
|
+
console.error('\u793A\u4F8B\uFF1Abb-browser type @3 "append text"');
|
|
2907
|
+
process.exit(1);
|
|
2908
|
+
}
|
|
2909
|
+
await typeCommand(ref, text, { json: parsed.flags.json, tabId: globalTabId });
|
|
2910
|
+
break;
|
|
2911
|
+
}
|
|
2912
|
+
case "select": {
|
|
2913
|
+
const ref = parsed.args[0];
|
|
2914
|
+
const value = parsed.args[1];
|
|
2915
|
+
if (!ref) {
|
|
2916
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 ref \u53C2\u6570");
|
|
2917
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser select <ref> <value>");
|
|
2918
|
+
console.error('\u793A\u4F8B\uFF1Abb-browser select @4 "option1"');
|
|
2919
|
+
process.exit(1);
|
|
2920
|
+
}
|
|
2921
|
+
if (value === void 0) {
|
|
2922
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 value \u53C2\u6570");
|
|
2923
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser select <ref> <value>");
|
|
2924
|
+
console.error('\u793A\u4F8B\uFF1Abb-browser select @4 "option1"');
|
|
2925
|
+
process.exit(1);
|
|
2926
|
+
}
|
|
2927
|
+
await selectCommand(ref, value, { json: parsed.flags.json, tabId: globalTabId });
|
|
2928
|
+
break;
|
|
2929
|
+
}
|
|
2930
|
+
case "eval": {
|
|
2931
|
+
const script = parsed.args[0];
|
|
2932
|
+
if (!script) {
|
|
2933
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 script \u53C2\u6570");
|
|
2934
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser eval <script>");
|
|
2935
|
+
console.error('\u793A\u4F8B\uFF1Abb-browser eval "document.title"');
|
|
2936
|
+
process.exit(1);
|
|
2937
|
+
}
|
|
2938
|
+
await evalCommand(script, { json: parsed.flags.json, tabId: globalTabId });
|
|
2939
|
+
break;
|
|
2940
|
+
}
|
|
2941
|
+
case "get": {
|
|
2942
|
+
const attribute = parsed.args[0];
|
|
2943
|
+
if (!attribute) {
|
|
2944
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u5C5E\u6027\u53C2\u6570");
|
|
2945
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser get <text|url|title> [ref]");
|
|
2946
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser get text @5");
|
|
2947
|
+
console.error(" bb-browser get url");
|
|
2948
|
+
process.exit(1);
|
|
2949
|
+
}
|
|
2950
|
+
if (!["text", "url", "title"].includes(attribute)) {
|
|
2951
|
+
console.error(`\u9519\u8BEF\uFF1A\u672A\u77E5\u5C5E\u6027 "${attribute}"`);
|
|
2952
|
+
console.error("\u652F\u6301\u7684\u5C5E\u6027\uFF1Atext, url, title");
|
|
2953
|
+
process.exit(1);
|
|
2954
|
+
}
|
|
2955
|
+
const ref = parsed.args[1];
|
|
2956
|
+
await getCommand(attribute, ref, { json: parsed.flags.json, tabId: globalTabId });
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2959
|
+
case "daemon": {
|
|
2960
|
+
const daemonSubcommand = parsed.args[0];
|
|
2961
|
+
if (daemonSubcommand === "status") {
|
|
2962
|
+
await statusCommand({ json: parsed.flags.json });
|
|
2963
|
+
break;
|
|
2964
|
+
}
|
|
2965
|
+
if (daemonSubcommand === "stop" || daemonSubcommand === "shutdown") {
|
|
2966
|
+
await shutdownCommand({ json: parsed.flags.json });
|
|
2967
|
+
break;
|
|
2968
|
+
}
|
|
2969
|
+
if (daemonSubcommand === "start") {
|
|
2970
|
+
await startCommand({ json: parsed.flags.json });
|
|
2971
|
+
break;
|
|
2972
|
+
}
|
|
2973
|
+
const daemonPath = getDaemonPath();
|
|
2974
|
+
const daemonArgs = process.argv.slice(3);
|
|
2975
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
2976
|
+
const child = spawn3(process.execPath, [daemonPath, ...daemonArgs], {
|
|
2977
|
+
stdio: "inherit"
|
|
2978
|
+
});
|
|
2979
|
+
child.on("exit", (code, signal) => {
|
|
2980
|
+
if (signal) {
|
|
2981
|
+
process.kill(process.pid, signal);
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
process.exit(code ?? 0);
|
|
2985
|
+
});
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
case "close": {
|
|
2989
|
+
await closeCommand({ json: parsed.flags.json, tabId: globalTabId });
|
|
2990
|
+
break;
|
|
2991
|
+
}
|
|
2992
|
+
case "back": {
|
|
2993
|
+
await backCommand({ json: parsed.flags.json, tabId: globalTabId });
|
|
2994
|
+
break;
|
|
2995
|
+
}
|
|
2996
|
+
case "forward": {
|
|
2997
|
+
await forwardCommand({ json: parsed.flags.json, tabId: globalTabId });
|
|
2998
|
+
break;
|
|
2999
|
+
}
|
|
3000
|
+
case "refresh": {
|
|
3001
|
+
await refreshCommand({ json: parsed.flags.json, tabId: globalTabId });
|
|
3002
|
+
break;
|
|
3003
|
+
}
|
|
3004
|
+
case "screenshot": {
|
|
3005
|
+
const outputPath = parsed.args[0];
|
|
3006
|
+
await screenshotCommand(outputPath, { json: parsed.flags.json, tabId: globalTabId });
|
|
3007
|
+
break;
|
|
3008
|
+
}
|
|
3009
|
+
case "wait": {
|
|
3010
|
+
const target = parsed.args[0];
|
|
3011
|
+
if (!target) {
|
|
3012
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u7B49\u5F85\u76EE\u6807\u53C2\u6570");
|
|
3013
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser wait <ms|@ref>");
|
|
3014
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser wait 2000");
|
|
3015
|
+
console.error(" bb-browser wait @5");
|
|
3016
|
+
process.exit(1);
|
|
3017
|
+
}
|
|
3018
|
+
await waitCommand(target, { json: parsed.flags.json, tabId: globalTabId });
|
|
3019
|
+
break;
|
|
3020
|
+
}
|
|
3021
|
+
case "press": {
|
|
3022
|
+
const key = parsed.args[0];
|
|
3023
|
+
if (!key) {
|
|
3024
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 key \u53C2\u6570");
|
|
3025
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser press <key>");
|
|
3026
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser press Enter");
|
|
3027
|
+
console.error(" bb-browser press Control+a");
|
|
3028
|
+
process.exit(1);
|
|
3029
|
+
}
|
|
3030
|
+
await pressCommand(key, { json: parsed.flags.json, tabId: globalTabId });
|
|
3031
|
+
break;
|
|
3032
|
+
}
|
|
3033
|
+
case "scroll": {
|
|
3034
|
+
const direction = parsed.args[0];
|
|
3035
|
+
const pixels = parsed.args[1];
|
|
3036
|
+
if (!direction) {
|
|
3037
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u65B9\u5411\u53C2\u6570");
|
|
3038
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser scroll <up|down|left|right> [pixels]");
|
|
3039
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser scroll down");
|
|
3040
|
+
console.error(" bb-browser scroll up 500");
|
|
3041
|
+
process.exit(1);
|
|
3042
|
+
}
|
|
3043
|
+
await scrollCommand(direction, pixels, { json: parsed.flags.json, tabId: globalTabId });
|
|
3044
|
+
break;
|
|
3045
|
+
}
|
|
3046
|
+
case "tab": {
|
|
3047
|
+
await tabCommand(parsed.args, { json: parsed.flags.json, globalTabId });
|
|
3048
|
+
break;
|
|
3049
|
+
}
|
|
3050
|
+
case "status": {
|
|
3051
|
+
await statusCommand({ json: parsed.flags.json });
|
|
3052
|
+
break;
|
|
3053
|
+
}
|
|
3054
|
+
case "frame": {
|
|
3055
|
+
const selectorOrMain = parsed.args[0];
|
|
3056
|
+
if (!selectorOrMain) {
|
|
3057
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11 selector \u53C2\u6570");
|
|
3058
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser frame <selector>");
|
|
3059
|
+
console.error('\u793A\u4F8B\uFF1Abb-browser frame "iframe#editor"');
|
|
3060
|
+
console.error(" bb-browser frame main");
|
|
3061
|
+
process.exit(1);
|
|
3062
|
+
}
|
|
3063
|
+
if (selectorOrMain === "main") {
|
|
3064
|
+
await frameMainCommand({ json: parsed.flags.json, tabId: globalTabId });
|
|
3065
|
+
} else {
|
|
3066
|
+
await frameCommand(selectorOrMain, { json: parsed.flags.json, tabId: globalTabId });
|
|
3067
|
+
}
|
|
3068
|
+
break;
|
|
3069
|
+
}
|
|
3070
|
+
case "dialog": {
|
|
3071
|
+
const subCommand = parsed.args[0];
|
|
3072
|
+
if (!subCommand) {
|
|
3073
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u5B50\u547D\u4EE4");
|
|
3074
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser dialog <accept|dismiss> [text]");
|
|
3075
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser dialog accept");
|
|
3076
|
+
console.error(' bb-browser dialog accept "my input"');
|
|
3077
|
+
console.error(" bb-browser dialog dismiss");
|
|
3078
|
+
process.exit(1);
|
|
3079
|
+
}
|
|
3080
|
+
const promptText = parsed.args[1];
|
|
3081
|
+
await dialogCommand(subCommand, promptText, { json: parsed.flags.json, tabId: globalTabId });
|
|
3082
|
+
break;
|
|
3083
|
+
}
|
|
3084
|
+
case "network": {
|
|
3085
|
+
const subCommand = parsed.args[0] || "requests";
|
|
3086
|
+
const urlOrFilter = parsed.args[1];
|
|
3087
|
+
const abort = process.argv.includes("--abort");
|
|
3088
|
+
const withBody = process.argv.includes("--with-body");
|
|
3089
|
+
const bodyIndex = process.argv.findIndex((a) => a === "--body");
|
|
3090
|
+
const body = bodyIndex >= 0 ? process.argv[bodyIndex + 1] : void 0;
|
|
3091
|
+
const methodIndex = process.argv.findIndex((a) => a === "--method");
|
|
3092
|
+
const method = methodIndex >= 0 ? process.argv[methodIndex + 1] : void 0;
|
|
3093
|
+
const statusIndex = process.argv.findIndex((a) => a === "--status");
|
|
3094
|
+
const statusFilter = statusIndex >= 0 ? process.argv[statusIndex + 1] : void 0;
|
|
3095
|
+
await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId, since: globalSince, method, status: statusFilter });
|
|
3096
|
+
break;
|
|
3097
|
+
}
|
|
3098
|
+
case "console": {
|
|
3099
|
+
const clear = process.argv.includes("--clear");
|
|
3100
|
+
await consoleCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
|
|
3101
|
+
break;
|
|
3102
|
+
}
|
|
3103
|
+
case "errors": {
|
|
3104
|
+
const clear = process.argv.includes("--clear");
|
|
3105
|
+
await errorsCommand({ json: parsed.flags.json, clear, tabId: globalTabId, since: globalSince });
|
|
3106
|
+
break;
|
|
3107
|
+
}
|
|
3108
|
+
case "trace": {
|
|
3109
|
+
const subCmd = parsed.args[0];
|
|
3110
|
+
if (!subCmd || !["start", "stop", "status"].includes(subCmd)) {
|
|
3111
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u6216\u65E0\u6548\u7684\u5B50\u547D\u4EE4");
|
|
3112
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser trace <start|stop|status>");
|
|
3113
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser trace start");
|
|
3114
|
+
console.error(" bb-browser trace stop");
|
|
3115
|
+
console.error(" bb-browser trace status");
|
|
3116
|
+
process.exit(1);
|
|
3117
|
+
}
|
|
3118
|
+
await traceCommand(subCmd, { json: parsed.flags.json, tabId: globalTabId });
|
|
3119
|
+
break;
|
|
3120
|
+
}
|
|
3121
|
+
case "history": {
|
|
3122
|
+
const subCmd = parsed.args[0];
|
|
3123
|
+
if (!subCmd || !["search", "domains"].includes(subCmd)) {
|
|
3124
|
+
console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u6216\u65E0\u6548\u7684\u5B50\u547D\u4EE4");
|
|
3125
|
+
console.error("\u7528\u6CD5\uFF1Abb-browser history <search|domains> [query] [--days <n>]");
|
|
3126
|
+
console.error("\u793A\u4F8B\uFF1Abb-browser history search github");
|
|
3127
|
+
console.error(" bb-browser history domains --days 7");
|
|
3128
|
+
process.exit(1);
|
|
3129
|
+
}
|
|
3130
|
+
const query = parsed.args.slice(1).join(" ");
|
|
3131
|
+
await historyCommand(subCmd, {
|
|
3132
|
+
json: parsed.flags.json,
|
|
3133
|
+
days: parsed.flags.days || 30,
|
|
3134
|
+
query
|
|
3135
|
+
});
|
|
3136
|
+
break;
|
|
3137
|
+
}
|
|
3138
|
+
case "fetch": {
|
|
3139
|
+
const fetchUrl = parsed.args[0];
|
|
3140
|
+
if (!fetchUrl) {
|
|
3141
|
+
console.error("[error] fetch: <url> is required.");
|
|
3142
|
+
console.error(" Usage: bb-browser fetch <url> [--json] [--method POST] [--body '{...}']");
|
|
3143
|
+
console.error(" Example: bb-browser fetch https://www.reddit.com/api/me.json --json");
|
|
3144
|
+
process.exit(1);
|
|
3145
|
+
}
|
|
3146
|
+
const methodIdx = process.argv.findIndex((a) => a === "--method");
|
|
3147
|
+
const fetchMethod = methodIdx >= 0 ? process.argv[methodIdx + 1] : void 0;
|
|
3148
|
+
const fetchBodyIdx = process.argv.findIndex((a) => a === "--body");
|
|
3149
|
+
const fetchBody = fetchBodyIdx >= 0 ? process.argv[fetchBodyIdx + 1] : void 0;
|
|
3150
|
+
const headersIdx = process.argv.findIndex((a) => a === "--headers");
|
|
3151
|
+
const fetchHeaders = headersIdx >= 0 ? process.argv[headersIdx + 1] : void 0;
|
|
3152
|
+
const outputIdx = process.argv.findIndex((a) => a === "--output");
|
|
3153
|
+
const fetchOutput = outputIdx >= 0 ? process.argv[outputIdx + 1] : void 0;
|
|
3154
|
+
await fetchCommand(fetchUrl, {
|
|
3155
|
+
json: parsed.flags.json,
|
|
3156
|
+
method: fetchMethod,
|
|
3157
|
+
body: fetchBody,
|
|
3158
|
+
headers: fetchHeaders,
|
|
3159
|
+
output: fetchOutput,
|
|
3160
|
+
tabId: globalTabId
|
|
3161
|
+
});
|
|
3162
|
+
break;
|
|
3163
|
+
}
|
|
3164
|
+
case "site": {
|
|
3165
|
+
await siteCommand(parsed.args, {
|
|
3166
|
+
json: parsed.flags.json,
|
|
3167
|
+
jq: parsed.flags.jq,
|
|
3168
|
+
days: parsed.flags.days,
|
|
3169
|
+
tabId: globalTabId,
|
|
3170
|
+
openclaw: parsed.flags.openclaw
|
|
3171
|
+
});
|
|
3172
|
+
break;
|
|
3173
|
+
}
|
|
3174
|
+
case "star": {
|
|
3175
|
+
const { execSync: execSync4 } = await import("child_process");
|
|
3176
|
+
try {
|
|
3177
|
+
execSync4("gh auth status", { stdio: "pipe" });
|
|
3178
|
+
} catch {
|
|
3179
|
+
console.error("\u9700\u8981\u5148\u5B89\u88C5\u5E76\u767B\u5F55 GitHub CLI: https://cli.github.com");
|
|
3180
|
+
console.error(" brew install gh && gh auth login");
|
|
3181
|
+
process.exit(1);
|
|
3182
|
+
}
|
|
3183
|
+
const repos = ["epiral/bb-browser", "epiral/bb-sites"];
|
|
3184
|
+
for (const repo of repos) {
|
|
3185
|
+
try {
|
|
3186
|
+
execSync4(`gh api user/starred/${repo} -X PUT`, { stdio: "pipe" });
|
|
3187
|
+
console.log(`\u2B50 Starred ${repo}`);
|
|
3188
|
+
} catch {
|
|
3189
|
+
console.log(`Already starred or failed: ${repo}`);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
console.log("\nThanks for your support! \u{1F64F}");
|
|
3193
|
+
break;
|
|
3194
|
+
}
|
|
3195
|
+
case "guide": {
|
|
3196
|
+
console.log(`How to turn any website into a bb-browser site adapter
|
|
3197
|
+
=======================================================
|
|
3198
|
+
|
|
3199
|
+
1. REVERSE ENGINEER the API
|
|
3200
|
+
bb-browser network clear --tab <tabId>
|
|
3201
|
+
bb-browser refresh --tab <tabId>
|
|
3202
|
+
bb-browser network requests --filter "api" --with-body --json --tab <tabId>
|
|
3203
|
+
|
|
3204
|
+
2. TEST if direct fetch works (Tier 1)
|
|
3205
|
+
bb-browser eval "fetch('/api/endpoint',{credentials:'include'}).then(r=>r.json())" --tab <tabId>
|
|
3206
|
+
|
|
3207
|
+
If it works \u2192 Tier 1 (Cookie auth, like Reddit/GitHub/Zhihu/Bilibili)
|
|
3208
|
+
If needs extra headers \u2192 Tier 2 (like Twitter: Bearer + CSRF token)
|
|
3209
|
+
If needs request signing \u2192 Tier 3 (like Xiaohongshu: Pinia store actions)
|
|
3210
|
+
|
|
3211
|
+
3. WRITE the adapter (one JS file per operation)
|
|
3212
|
+
|
|
3213
|
+
/* @meta
|
|
3214
|
+
{
|
|
3215
|
+
"name": "platform/command",
|
|
3216
|
+
"description": "What it does",
|
|
3217
|
+
"domain": "www.example.com",
|
|
3218
|
+
"args": { "query": {"required": true, "description": "Search query"} },
|
|
3219
|
+
"readOnly": true,
|
|
3220
|
+
"example": "bb-browser site platform/command value"
|
|
3221
|
+
}
|
|
3222
|
+
*/
|
|
3223
|
+
async function(args) {
|
|
3224
|
+
if (!args.query) return {error: 'Missing argument: query'};
|
|
3225
|
+
const resp = await fetch('/api/search?q=' + encodeURIComponent(args.query), {credentials: 'include'});
|
|
3226
|
+
if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};
|
|
3227
|
+
return await resp.json();
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
4. TEST it
|
|
3231
|
+
Save to ~/.bb-browser/sites/platform/command.js (private, takes priority)
|
|
3232
|
+
bb-browser site platform/command "test query" --json
|
|
3233
|
+
|
|
3234
|
+
5. CONTRIBUTE
|
|
3235
|
+
Option A (with gh CLI):
|
|
3236
|
+
git clone https://github.com/epiral/bb-sites && cd bb-sites
|
|
3237
|
+
git checkout -b feat-platform
|
|
3238
|
+
# add adapter files
|
|
3239
|
+
git push -u origin feat-platform
|
|
3240
|
+
gh pr create --repo epiral/bb-sites
|
|
3241
|
+
|
|
3242
|
+
Option B (without gh CLI, using bb-browser itself):
|
|
3243
|
+
bb-browser site github/fork epiral/bb-sites
|
|
3244
|
+
git clone https://github.com/YOUR_USER/bb-sites && cd bb-sites
|
|
3245
|
+
git checkout -b feat-platform
|
|
3246
|
+
# add adapter files
|
|
3247
|
+
git push -u origin feat-platform
|
|
3248
|
+
bb-browser site github/pr-create epiral/bb-sites --title "feat(platform): add adapters" --head "YOUR_USER:feat-platform"
|
|
3249
|
+
|
|
3250
|
+
Private adapters: ~/.bb-browser/sites/<platform>/<command>.js
|
|
3251
|
+
Community: ~/.bb-browser/bb-sites/ (via bb-browser site update)
|
|
3252
|
+
Full guide: https://github.com/epiral/bb-sites/blob/main/SKILL.md`);
|
|
3253
|
+
break;
|
|
3254
|
+
}
|
|
3255
|
+
default: {
|
|
3256
|
+
console.error(`\u9519\u8BEF\uFF1A\u672A\u77E5\u547D\u4EE4 "${parsed.command}"`);
|
|
3257
|
+
console.error("\u8FD0\u884C bb-browser --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4");
|
|
3258
|
+
process.exit(1);
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
} catch (error) {
|
|
3262
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3263
|
+
if (parsed.flags.json) {
|
|
3264
|
+
console.log(
|
|
3265
|
+
JSON.stringify({
|
|
3266
|
+
success: false,
|
|
3267
|
+
error: message
|
|
3268
|
+
})
|
|
3269
|
+
);
|
|
3270
|
+
} else {
|
|
3271
|
+
console.error(`\u9519\u8BEF\uFF1A${message}`);
|
|
3272
|
+
}
|
|
3273
|
+
process.exit(1);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
main().then(() => process.exit(0));
|
|
3277
|
+
//# sourceMappingURL=cli.js.map
|