bb-browser 0.4.5 → 0.5.0

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/dist/cli.js CHANGED
@@ -3,9 +3,30 @@ import {
3
3
  COMMAND_TIMEOUT,
4
4
  DAEMON_BASE_URL,
5
5
  generateId
6
- } from "./chunk-H7M4J4CW.js";
6
+ } from "./chunk-DBJBHYC7.js";
7
+ import {
8
+ applyJq
9
+ } from "./chunk-AHGAQEFO.js";
10
+ import "./chunk-D4HDZEJT.js";
7
11
 
8
12
  // packages/cli/src/client.ts
13
+ var jqExpression;
14
+ function setJqExpression(expression) {
15
+ jqExpression = expression;
16
+ }
17
+ function printJqResults(response) {
18
+ const target = response.data ?? response;
19
+ const results = applyJq(target, jqExpression || ".");
20
+ for (const result of results) {
21
+ console.log(typeof result === "string" ? result : JSON.stringify(result));
22
+ }
23
+ process.exit(0);
24
+ }
25
+ function handleJqResponse(response) {
26
+ if (jqExpression) {
27
+ printJqResults(response);
28
+ }
29
+ }
9
30
  async function sendCommand(request) {
10
31
  const controller = new AbortController();
11
32
  const timeoutId = setTimeout(() => controller.abort(), COMMAND_TIMEOUT);
@@ -47,7 +68,8 @@ async function sendCommand(request) {
47
68
  error: `HTTP \u9519\u8BEF: ${res.status} ${res.statusText}`
48
69
  };
49
70
  }
50
- return await res.json();
71
+ const response = await res.json();
72
+ return response;
51
73
  } catch (error) {
52
74
  clearTimeout(timeoutId);
53
75
  if (error instanceof Error) {
@@ -150,377 +172,595 @@ async function stopDaemon() {
150
172
  }
151
173
  }
152
174
 
153
- // packages/cli/src/commands/open.ts
154
- async function openCommand(url, options = {}) {
155
- if (!url) {
156
- throw new Error("\u7F3A\u5C11 URL \u53C2\u6570");
175
+ // packages/cli/src/commands/site.ts
176
+ import { readFileSync, readdirSync, existsSync as existsSync2, mkdirSync } from "fs";
177
+ import { join, relative } from "path";
178
+ import { homedir } from "os";
179
+ import { execSync } from "child_process";
180
+ var BB_DIR = join(homedir(), ".bb-browser");
181
+ var LOCAL_SITES_DIR = join(BB_DIR, "sites");
182
+ var COMMUNITY_SITES_DIR = join(BB_DIR, "bb-sites");
183
+ var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
184
+ function parseSiteMeta(filePath, source) {
185
+ let content;
186
+ try {
187
+ content = readFileSync(filePath, "utf-8");
188
+ } catch {
189
+ return null;
157
190
  }
158
- await ensureDaemonRunning();
159
- let normalizedUrl = url;
160
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
161
- normalizedUrl = "https://" + url;
191
+ const sitesDir = source === "local" ? LOCAL_SITES_DIR : COMMUNITY_SITES_DIR;
192
+ const relPath = relative(sitesDir, filePath);
193
+ const defaultName = relPath.replace(/\.js$/, "").replace(/\\/g, "/");
194
+ const metaMatch = content.match(/\/\*\s*@meta\s*\n([\s\S]*?)\*\//);
195
+ if (metaMatch) {
196
+ try {
197
+ const metaJson = JSON.parse(metaMatch[1]);
198
+ return {
199
+ name: metaJson.name || defaultName,
200
+ description: metaJson.description || "",
201
+ domain: metaJson.domain || "",
202
+ args: metaJson.args || {},
203
+ capabilities: metaJson.capabilities,
204
+ readOnly: metaJson.readOnly,
205
+ example: metaJson.example,
206
+ filePath,
207
+ source
208
+ };
209
+ } catch {
210
+ }
162
211
  }
163
- const request = {
164
- id: generateId(),
165
- action: "open",
166
- url: normalizedUrl
212
+ const meta = {
213
+ name: defaultName,
214
+ description: "",
215
+ domain: "",
216
+ args: {},
217
+ filePath,
218
+ source
167
219
  };
168
- if (options.tab !== void 0) {
169
- if (options.tab === "current") {
170
- request.tabId = "current";
171
- } else {
172
- const tabId = parseInt(options.tab, 10);
173
- if (isNaN(tabId)) {
174
- throw new Error(`\u65E0\u6548\u7684 tabId: ${options.tab}`);
175
- }
176
- request.tabId = tabId;
220
+ const tagPattern = /\/\/\s*@(\w+)[ \t]+(.*)/g;
221
+ let match;
222
+ while ((match = tagPattern.exec(content)) !== null) {
223
+ const [, key, value] = match;
224
+ switch (key) {
225
+ case "name":
226
+ meta.name = value.trim();
227
+ break;
228
+ case "description":
229
+ meta.description = value.trim();
230
+ break;
231
+ case "domain":
232
+ meta.domain = value.trim();
233
+ break;
234
+ case "args":
235
+ for (const arg of value.trim().split(/[,\s]+/).filter(Boolean)) {
236
+ meta.args[arg] = { required: true };
237
+ }
238
+ break;
239
+ case "example":
240
+ meta.example = value.trim();
241
+ break;
177
242
  }
178
243
  }
179
- const response = await sendCommand(request);
180
- if (options.json) {
181
- console.log(JSON.stringify(response, null, 2));
182
- } else {
183
- if (response.success) {
184
- console.log(`\u5DF2\u6253\u5F00: ${response.data?.url ?? normalizedUrl}`);
185
- if (response.data?.title) {
186
- console.log(`\u6807\u9898: ${response.data.title}`);
187
- }
188
- if (response.data?.tabId) {
189
- console.log(`Tab ID: ${response.data.tabId}`);
244
+ return meta;
245
+ }
246
+ function scanSites(dir, source) {
247
+ if (!existsSync2(dir)) return [];
248
+ const sites = [];
249
+ function walk(currentDir) {
250
+ let entries;
251
+ try {
252
+ entries = readdirSync(currentDir, { withFileTypes: true });
253
+ } catch {
254
+ return;
255
+ }
256
+ for (const entry of entries) {
257
+ const fullPath = join(currentDir, entry.name);
258
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
259
+ walk(fullPath);
260
+ } else if (entry.isFile() && entry.name.endsWith(".js")) {
261
+ const meta = parseSiteMeta(fullPath, source);
262
+ if (meta) sites.push(meta);
190
263
  }
191
- } else {
192
- console.error(`\u9519\u8BEF: ${response.error}`);
193
- process.exit(1);
194
264
  }
195
265
  }
266
+ walk(dir);
267
+ return sites;
196
268
  }
197
-
198
- // packages/cli/src/commands/snapshot.ts
199
- async function snapshotCommand(options = {}) {
200
- await ensureDaemonRunning();
201
- const request = {
202
- id: generateId(),
203
- action: "snapshot",
204
- interactive: options.interactive,
205
- compact: options.compact,
206
- maxDepth: options.maxDepth,
207
- selector: options.selector,
208
- tabId: options.tabId
209
- };
210
- const response = await sendCommand(request);
211
- if (options.json) {
212
- console.log(JSON.stringify(response, null, 2));
213
- } else {
214
- if (response.success) {
215
- console.log(`\u6807\u9898: ${response.data?.title ?? "(\u65E0\u6807\u9898)"}`);
216
- console.log(`URL: ${response.data?.url ?? "(\u672A\u77E5)"}`);
217
- if (response.data?.snapshotData?.snapshot) {
218
- console.log("");
219
- console.log(response.data.snapshotData.snapshot);
220
- }
221
- } else {
222
- console.error(`\u9519\u8BEF: ${response.error}`);
223
- process.exit(1);
224
- }
269
+ function getSiteHintForDomain(url) {
270
+ try {
271
+ const hostname = new URL(url).hostname;
272
+ const sites = getAllSites();
273
+ const matched = sites.filter((s) => s.domain && (hostname === s.domain || hostname.endsWith("." + s.domain)));
274
+ if (matched.length === 0) return null;
275
+ const names = matched.map((s) => s.name);
276
+ const example = matched[0].example || `bb-browser site ${names[0]}`;
277
+ 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}`;
278
+ } catch {
279
+ return null;
225
280
  }
226
281
  }
227
-
228
- // packages/cli/src/commands/click.ts
229
- function parseRef(ref) {
230
- return ref.startsWith("@") ? ref.slice(1) : ref;
282
+ function getAllSites() {
283
+ const community = scanSites(COMMUNITY_SITES_DIR, "community");
284
+ const local = scanSites(LOCAL_SITES_DIR, "local");
285
+ const byName = /* @__PURE__ */ new Map();
286
+ for (const s of community) byName.set(s.name, s);
287
+ for (const s of local) byName.set(s.name, s);
288
+ return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
231
289
  }
232
- async function clickCommand(ref, options = {}) {
233
- if (!ref) {
234
- throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
290
+ function matchTabOrigin(tabUrl, domain) {
291
+ try {
292
+ const tabOrigin = new URL(tabUrl).hostname;
293
+ return tabOrigin === domain || tabOrigin.endsWith("." + domain);
294
+ } catch {
295
+ return false;
296
+ }
297
+ }
298
+ function siteList(options) {
299
+ const sites = getAllSites();
300
+ if (sites.length === 0) {
301
+ console.log("\u672A\u627E\u5230\u4EFB\u4F55 site adapter\u3002");
302
+ console.log(" \u5B89\u88C5\u793E\u533A adapter: bb-browser site update");
303
+ console.log(` \u79C1\u6709 adapter \u76EE\u5F55: ${LOCAL_SITES_DIR}`);
304
+ return;
235
305
  }
236
- await ensureDaemonRunning();
237
- const parsedRef = parseRef(ref);
238
- const request = {
239
- id: generateId(),
240
- action: "click",
241
- ref: parsedRef,
242
- tabId: options.tabId
243
- };
244
- const response = await sendCommand(request);
245
306
  if (options.json) {
246
- console.log(JSON.stringify(response, null, 2));
247
- } else {
248
- if (response.success) {
249
- const role = response.data?.role ?? "element";
250
- const name = response.data?.name;
251
- if (name) {
252
- console.log(`\u5DF2\u70B9\u51FB: ${role} "${name}"`);
253
- } else {
254
- console.log(`\u5DF2\u70B9\u51FB: ${role}`);
255
- }
256
- } else {
257
- console.error(`\u9519\u8BEF: ${response.error}`);
258
- process.exit(1);
307
+ console.log(JSON.stringify(sites.map((s) => ({
308
+ name: s.name,
309
+ description: s.description,
310
+ domain: s.domain,
311
+ args: s.args,
312
+ source: s.source
313
+ })), null, 2));
314
+ return;
315
+ }
316
+ const groups = /* @__PURE__ */ new Map();
317
+ for (const s of sites) {
318
+ const platform = s.name.split("/")[0];
319
+ if (!groups.has(platform)) groups.set(platform, []);
320
+ groups.get(platform).push(s);
321
+ }
322
+ for (const [platform, items] of groups) {
323
+ console.log(`
324
+ ${platform}/`);
325
+ for (const s of items) {
326
+ const cmd = s.name.split("/").slice(1).join("/");
327
+ const src = s.source === "local" ? " (local)" : "";
328
+ const desc = s.description ? ` - ${s.description}` : "";
329
+ console.log(` ${cmd.padEnd(20)}${desc}${src}`);
259
330
  }
260
331
  }
332
+ console.log();
261
333
  }
262
-
263
- // packages/cli/src/commands/hover.ts
264
- function parseRef2(ref) {
265
- return ref.startsWith("@") ? ref.slice(1) : ref;
266
- }
267
- async function hoverCommand(ref, options = {}) {
268
- if (!ref) {
269
- throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
334
+ function siteSearch(query, options) {
335
+ const sites = getAllSites();
336
+ const q = query.toLowerCase();
337
+ const matches = sites.filter(
338
+ (s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q)
339
+ );
340
+ if (matches.length === 0) {
341
+ console.log(`\u672A\u627E\u5230\u5339\u914D "${query}" \u7684 adapter\u3002`);
342
+ console.log(" \u67E5\u770B\u6240\u6709: bb-browser site list");
343
+ return;
270
344
  }
271
- await ensureDaemonRunning();
272
- const parsedRef = parseRef2(ref);
273
- const request = {
274
- id: generateId(),
275
- action: "hover",
276
- ref: parsedRef,
277
- tabId: options.tabId
278
- };
279
- const response = await sendCommand(request);
280
345
  if (options.json) {
281
- console.log(JSON.stringify(response, null, 2));
346
+ console.log(JSON.stringify(matches.map((s) => ({
347
+ name: s.name,
348
+ description: s.description,
349
+ domain: s.domain,
350
+ source: s.source
351
+ })), null, 2));
352
+ return;
353
+ }
354
+ for (const s of matches) {
355
+ const src = s.source === "local" ? " (local)" : "";
356
+ console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
357
+ }
358
+ }
359
+ function siteUpdate() {
360
+ mkdirSync(BB_DIR, { recursive: true });
361
+ if (existsSync2(join(COMMUNITY_SITES_DIR, ".git"))) {
362
+ console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
363
+ try {
364
+ execSync("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
365
+ console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
366
+ console.log("");
367
+ console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
368
+ } catch (e) {
369
+ console.error(`\u66F4\u65B0\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
370
+ console.error(" \u624B\u52A8\u4FEE\u590D: cd ~/.bb-browser/bb-sites && git pull");
371
+ process.exit(1);
372
+ }
282
373
  } else {
283
- if (response.success) {
284
- const role = response.data?.role ?? "element";
285
- const name = response.data?.name;
286
- if (name) {
287
- console.log(`\u5DF2\u60AC\u505C: ${role} "${name}"`);
288
- } else {
289
- console.log(`\u5DF2\u60AC\u505C: ${role}`);
290
- }
291
- } else {
292
- console.error(`\u9519\u8BEF: ${response.error}`);
374
+ console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
375
+ try {
376
+ execSync(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
377
+ console.log("\u514B\u9686\u5B8C\u6210\u3002");
378
+ console.log("");
379
+ console.log("\u{1F4A1} \u8FD0\u884C bb-browser site recommend \u770B\u770B\u54EA\u4E9B\u548C\u4F60\u7684\u6D4F\u89C8\u4E60\u60EF\u5339\u914D");
380
+ } catch (e) {
381
+ console.error(`\u514B\u9686\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
382
+ console.error(` \u624B\u52A8\u4FEE\u590D: git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`);
293
383
  process.exit(1);
294
384
  }
295
385
  }
386
+ const sites = scanSites(COMMUNITY_SITES_DIR, "community");
387
+ console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
296
388
  }
297
-
298
- // packages/cli/src/commands/fill.ts
299
- function parseRef3(ref) {
300
- return ref.startsWith("@") ? ref.slice(1) : ref;
389
+ function findSiteByName(name) {
390
+ return getAllSites().find((site) => site.name === name);
301
391
  }
302
- async function fillCommand(ref, text, options = {}) {
303
- if (!ref) {
304
- throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
305
- }
306
- if (text === void 0 || text === null) {
307
- throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
392
+ function siteInfo(name, options) {
393
+ const site = findSiteByName(name);
394
+ if (!site) {
395
+ console.error(`[error] site info: adapter "${name}" not found.`);
396
+ console.error(" Try: bb-browser site list");
397
+ process.exit(1);
308
398
  }
309
- await ensureDaemonRunning();
310
- const parsedRef = parseRef3(ref);
311
- const request = {
312
- id: generateId(),
313
- action: "fill",
314
- ref: parsedRef,
315
- text,
316
- tabId: options.tabId
399
+ const meta = {
400
+ name: site.name,
401
+ description: site.description,
402
+ domain: site.domain,
403
+ args: site.args,
404
+ example: site.example,
405
+ readOnly: site.readOnly
317
406
  };
318
- const response = await sendCommand(request);
319
407
  if (options.json) {
320
- console.log(JSON.stringify(response, null, 2));
408
+ console.log(JSON.stringify(meta, null, 2));
409
+ return;
410
+ }
411
+ console.log(`${site.name} \u2014 ${site.description}`);
412
+ console.log();
413
+ console.log("\u53C2\u6570\uFF1A");
414
+ const argEntries = Object.entries(site.args);
415
+ if (argEntries.length === 0) {
416
+ console.log(" \uFF08\u65E0\uFF09");
321
417
  } else {
322
- if (response.success) {
323
- const role = response.data?.role ?? "element";
324
- const name = response.data?.name;
325
- if (name) {
326
- console.log(`\u5DF2\u586B\u5145: ${role} "${name}"`);
327
- } else {
328
- console.log(`\u5DF2\u586B\u5145: ${role}`);
329
- }
330
- console.log(`\u5185\u5BB9: "${text}"`);
331
- } else {
332
- console.error(`\u9519\u8BEF: ${response.error}`);
333
- process.exit(1);
418
+ for (const [argName, argDef] of argEntries) {
419
+ const requiredText = argDef.required ? "\u5FC5\u586B" : "\u53EF\u9009";
420
+ const description = argDef.description || "";
421
+ console.log(` ${argName} (${requiredText}) ${description}`.trimEnd());
334
422
  }
335
423
  }
424
+ console.log();
425
+ console.log("\u793A\u4F8B\uFF1A");
426
+ console.log(` ${site.example || `bb-browser site ${site.name}`}`);
427
+ console.log();
428
+ console.log(`\u57DF\u540D\uFF1A${site.domain || "\uFF08\u672A\u58F0\u660E\uFF09"}`);
429
+ console.log(`\u53EA\u8BFB\uFF1A${site.readOnly ? "\u662F" : "\u5426"}`);
336
430
  }
337
-
338
- // packages/cli/src/commands/type.ts
339
- function parseRef4(ref) {
340
- return ref.startsWith("@") ? ref.slice(1) : ref;
341
- }
342
- async function typeCommand(ref, text, options = {}) {
343
- if (!ref) {
344
- throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
431
+ async function siteRecommend(options) {
432
+ const days = options.days ?? 30;
433
+ const response = await sendCommand({
434
+ id: generateId(),
435
+ action: "history",
436
+ historyCommand: "domains",
437
+ ms: days
438
+ });
439
+ if (!response.success) {
440
+ throw new Error(response.error || "History command failed");
345
441
  }
346
- if (text === void 0 || text === null) {
347
- throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
442
+ const historyDomains = response.data?.historyDomains || [];
443
+ const sites = getAllSites();
444
+ const sitesByDomain = /* @__PURE__ */ new Map();
445
+ for (const site of sites) {
446
+ if (!site.domain) continue;
447
+ const domain = site.domain.toLowerCase();
448
+ const existing = sitesByDomain.get(domain) || [];
449
+ existing.push(site);
450
+ sitesByDomain.set(domain, existing);
451
+ }
452
+ const available = [];
453
+ const notAvailable = [];
454
+ for (const item of historyDomains) {
455
+ const adapters = sitesByDomain.get(item.domain.toLowerCase());
456
+ if (adapters && adapters.length > 0) {
457
+ const sortedAdapters = [...adapters].sort((a, b) => a.name.localeCompare(b.name));
458
+ available.push({
459
+ domain: item.domain,
460
+ visits: item.visits,
461
+ adapterCount: sortedAdapters.length,
462
+ adapters: sortedAdapters.map((site) => ({
463
+ name: site.name,
464
+ description: site.description,
465
+ example: site.example || `bb-browser site ${site.name}`
466
+ }))
467
+ });
468
+ } else if (item.visits >= 5 && item.domain && !item.domain.includes("localhost") && item.domain.includes(".")) {
469
+ notAvailable.push(item);
470
+ }
348
471
  }
349
- await ensureDaemonRunning();
350
- const parsedRef = parseRef4(ref);
351
- const request = {
352
- id: generateId(),
353
- action: "type",
354
- ref: parsedRef,
355
- text,
356
- tabId: options.tabId
472
+ const jsonData = {
473
+ days,
474
+ available,
475
+ not_available: notAvailable
357
476
  };
358
- const response = await sendCommand(request);
477
+ if (options.jq) {
478
+ handleJqResponse({ id: generateId(), success: true, data: jsonData });
479
+ }
359
480
  if (options.json) {
360
- console.log(JSON.stringify(response, null, 2));
481
+ console.log(JSON.stringify(jsonData, null, 2));
482
+ return;
483
+ }
484
+ console.log(`\u57FA\u4E8E\u4F60\u6700\u8FD1 ${days} \u5929\u7684\u6D4F\u89C8\u8BB0\u5F55\uFF1A`);
485
+ console.log();
486
+ console.log("\u{1F3AF} \u4F60\u5E38\u7528\u8FD9\u4E9B\u7F51\u7AD9\uFF0C\u53EF\u4EE5\u76F4\u63A5\u7528\uFF1A");
487
+ console.log();
488
+ if (available.length === 0) {
489
+ console.log(" \uFF08\u6682\u65E0\u5339\u914D\u7684 adapter\uFF09");
361
490
  } else {
362
- if (response.success) {
363
- const role = response.data?.role ?? "element";
364
- const name = response.data?.name;
365
- if (name) {
366
- console.log(`\u5DF2\u8F93\u5165: ${role} "${name}"`);
367
- } else {
368
- console.log(`\u5DF2\u8F93\u5165: ${role}`);
491
+ for (const item of available) {
492
+ console.log(` ${item.domain.padEnd(20)} ${item.visits} \u6B21\u8BBF\u95EE ${item.adapterCount} \u4E2A\u547D\u4EE4`);
493
+ console.log(` \u8BD5\u8BD5: ${item.adapters[0]?.example || `bb-browser site ${item.adapters[0]?.name || ""}`}`);
494
+ console.log();
495
+ }
496
+ }
497
+ console.log("\u{1F4CB} \u4F60\u5E38\u7528\u4F46\u8FD8\u6CA1\u6709 adapter\uFF1A");
498
+ console.log();
499
+ if (notAvailable.length === 0) {
500
+ console.log(" \uFF08\u6682\u65E0\uFF09");
501
+ } else {
502
+ for (const item of notAvailable) {
503
+ console.log(` ${item.domain.padEnd(20)} ${item.visits} \u6B21\u8BBF\u95EE`);
504
+ }
505
+ }
506
+ console.log();
507
+ console.log('\u{1F4A1} \u8DDF\u4F60\u7684 AI Agent \u8BF4 "\u628A notion.so CLI \u5316"\uFF0C\u5B83\u5C31\u80FD\u81EA\u52A8\u5B8C\u6210\u3002');
508
+ console.log();
509
+ console.log(`\u6240\u6709\u5206\u6790\u7EAF\u672C\u5730\u5B8C\u6210\u3002\u7528 --days 7 \u53EA\u770B\u6700\u8FD1\u4E00\u5468\u3002`);
510
+ }
511
+ async function siteRun(name, args, options) {
512
+ const sites = getAllSites();
513
+ const site = sites.find((s) => s.name === name);
514
+ if (!site) {
515
+ const fuzzy = sites.filter((s) => s.name.includes(name));
516
+ console.error(`[error] site: "${name}" not found.`);
517
+ if (fuzzy.length > 0) {
518
+ console.error(" Did you mean:");
519
+ for (const s of fuzzy.slice(0, 5)) {
520
+ console.error(` bb-browser site ${s.name}`);
369
521
  }
370
- console.log(`\u5185\u5BB9: "${text}"`);
371
522
  } else {
372
- console.error(`\u9519\u8BEF: ${response.error}`);
373
- process.exit(1);
523
+ console.error(" Try: bb-browser site list");
524
+ console.error(" Or: bb-browser site update");
374
525
  }
526
+ process.exit(1);
375
527
  }
376
- }
377
-
378
- // packages/cli/src/commands/close.ts
379
- async function closeCommand(options = {}) {
380
- await ensureDaemonRunning();
381
- const request = {
382
- id: generateId(),
383
- action: "close",
384
- tabId: options.tabId
385
- };
386
- const response = await sendCommand(request);
387
- if (options.json) {
388
- console.log(JSON.stringify(response, null, 2));
389
- } else {
390
- if (response.success) {
391
- const title = response.data?.title ?? "";
392
- if (title) {
393
- console.log(`\u5DF2\u5173\u95ED: "${title}"`);
394
- } else {
395
- console.log("\u5DF2\u5173\u95ED\u5F53\u524D\u6807\u7B7E\u9875");
528
+ const argNames = Object.keys(site.args);
529
+ const argMap = {};
530
+ const positionalArgs = [];
531
+ for (let i = 0; i < args.length; i++) {
532
+ if (args[i].startsWith("--")) {
533
+ const flagName = args[i].slice(2);
534
+ if (flagName in site.args && args[i + 1]) {
535
+ argMap[flagName] = args[i + 1];
536
+ i++;
396
537
  }
397
538
  } else {
398
- console.error(`\u9519\u8BEF: ${response.error}`);
399
- process.exit(1);
539
+ positionalArgs.push(args[i]);
400
540
  }
401
541
  }
402
- }
403
-
404
- // packages/cli/src/commands/get.ts
405
- function parseRef5(ref) {
406
- return ref.startsWith("@") ? ref.slice(1) : ref;
407
- }
408
- async function getCommand(attribute, ref, options = {}) {
409
- if (attribute === "text" && !ref) {
410
- throw new Error("get text \u9700\u8981 ref \u53C2\u6570\uFF0C\u5982: get text @5");
542
+ let posIdx = 0;
543
+ for (const argName of argNames) {
544
+ if (!argMap[argName] && posIdx < positionalArgs.length) {
545
+ argMap[argName] = positionalArgs[posIdx++];
546
+ }
411
547
  }
412
- await ensureDaemonRunning();
413
- const request = {
414
- id: generateId(),
415
- action: "get",
416
- attribute,
417
- ref: ref ? parseRef5(ref) : void 0,
418
- tabId: options.tabId
419
- };
420
- const response = await sendCommand(request);
421
- if (options.json) {
422
- console.log(JSON.stringify(response, null, 2));
423
- } else {
424
- if (response.success) {
425
- const value = response.data?.value ?? "";
426
- console.log(value);
427
- } else {
428
- console.error(`\u9519\u8BEF: ${response.error}`);
548
+ for (const [argName, argDef] of Object.entries(site.args)) {
549
+ if (argDef.required && !argMap[argName]) {
550
+ console.error(`[error] site ${name}: missing required argument "${argName}".`);
551
+ const usage = argNames.map((a) => {
552
+ const def = site.args[a];
553
+ return def.required ? `<${a}>` : `[${a}]`;
554
+ }).join(" ");
555
+ console.error(` Usage: bb-browser site ${name} ${usage}`);
556
+ if (site.example) console.error(` Example: ${site.example}`);
429
557
  process.exit(1);
430
558
  }
431
559
  }
432
- }
433
-
434
- // packages/cli/src/commands/screenshot.ts
435
- import fs from "fs";
436
- import path from "path";
437
- import os from "os";
438
- function getDefaultPath() {
439
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
440
- const filename = `bb-screenshot-${timestamp}.png`;
441
- return path.join(os.tmpdir(), filename);
442
- }
443
- function saveBase64Image(dataUrl, filePath) {
444
- const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
445
- const buffer = Buffer.from(base64Data, "base64");
446
- const dir = path.dirname(filePath);
447
- if (!fs.existsSync(dir)) {
448
- fs.mkdirSync(dir, { recursive: true });
449
- }
450
- fs.writeFileSync(filePath, buffer);
451
- }
452
- async function screenshotCommand(outputPath, options = {}) {
560
+ const jsContent = readFileSync(site.filePath, "utf-8");
561
+ const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
562
+ const argsJson = JSON.stringify(argMap);
563
+ const script = `(${jsBody})(${argsJson})`;
453
564
  await ensureDaemonRunning();
454
- const filePath = outputPath ? path.resolve(outputPath) : getDefaultPath();
455
- const request = {
456
- id: generateId(),
457
- action: "screenshot",
458
- tabId: options.tabId
459
- };
460
- const response = await sendCommand(request);
461
- if (response.success && response.data?.dataUrl) {
462
- const dataUrl = response.data.dataUrl;
463
- saveBase64Image(dataUrl, filePath);
565
+ let targetTabId = options.tabId;
566
+ if (!targetTabId && site.domain) {
567
+ const listReq = { id: generateId(), action: "tab_list" };
568
+ const listResp = await sendCommand(listReq);
569
+ if (listResp.success && listResp.data?.tabs) {
570
+ const matchingTab = listResp.data.tabs.find(
571
+ (tab) => matchTabOrigin(tab.url, site.domain)
572
+ );
573
+ if (matchingTab) {
574
+ targetTabId = matchingTab.tabId;
575
+ }
576
+ }
577
+ if (!targetTabId) {
578
+ const newResp = await sendCommand({
579
+ id: generateId(),
580
+ action: "tab_new",
581
+ url: `https://${site.domain}`
582
+ });
583
+ targetTabId = newResp.data?.tabId;
584
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
585
+ }
586
+ }
587
+ const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
588
+ const evalResp = await sendCommand(evalReq);
589
+ if (!evalResp.success) {
590
+ const hint = site.domain ? `Open https://${site.domain} in your browser, make sure you are logged in, then retry.` : void 0;
464
591
  if (options.json) {
465
- console.log(JSON.stringify({
466
- success: true,
467
- path: filePath,
468
- base64: dataUrl
469
- }, null, 2));
592
+ console.log(JSON.stringify({ id: evalReq.id, success: false, error: evalResp.error || "eval failed", hint }));
470
593
  } else {
471
- console.log(`\u622A\u56FE\u5DF2\u4FDD\u5B58: ${filePath}`);
594
+ console.error(`[error] site ${name}: ${evalResp.error || "eval failed"}`);
595
+ if (hint) console.error(` Hint: ${hint}`);
472
596
  }
473
- } else {
597
+ process.exit(1);
598
+ }
599
+ const result = evalResp.data?.result;
600
+ if (result === void 0 || result === null) {
474
601
  if (options.json) {
475
- console.log(JSON.stringify(response, null, 2));
602
+ console.log(JSON.stringify({ id: evalReq.id, success: true, data: null }));
476
603
  } else {
477
- console.error(`\u9519\u8BEF: ${response.error}`);
604
+ console.log("(no output)");
605
+ }
606
+ return;
607
+ }
608
+ let parsed;
609
+ try {
610
+ parsed = typeof result === "string" ? JSON.parse(result) : result;
611
+ } catch {
612
+ parsed = result;
613
+ }
614
+ if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
615
+ const errObj = parsed;
616
+ const checkText = `${errObj.error} ${errObj.hint || ""}`;
617
+ const isAuthError = /401|403|unauthorized|forbidden|not.?logged|login.?required|sign.?in|auth/i.test(checkText);
618
+ const loginHint = isAuthError && site.domain ? `Please log in to https://${site.domain} in your browser first, then retry.` : void 0;
619
+ const hint = loginHint || errObj.hint;
620
+ 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>"`;
621
+ if (options.json) {
622
+ console.log(JSON.stringify({ id: evalReq.id, success: false, error: errObj.error, hint, reportHint }));
623
+ } else {
624
+ console.error(`[error] site ${name}: ${errObj.error}`);
625
+ if (hint) console.error(` Hint: ${hint}`);
626
+ console.error(` Report: gh issue create --repo epiral/bb-sites --title "[${name}] ..."`);
627
+ console.error(` or: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] ..."`);
478
628
  }
479
629
  process.exit(1);
480
630
  }
631
+ if (options.jq) {
632
+ const { applyJq: applyJq2 } = await import("./jq-HHMLHEPA.js");
633
+ const expr = options.jq.replace(/^\.data\./, ".");
634
+ const results = applyJq2(parsed, expr);
635
+ for (const r of results) {
636
+ console.log(typeof r === "string" ? r : JSON.stringify(r));
637
+ }
638
+ } else if (options.json) {
639
+ console.log(JSON.stringify({ id: evalReq.id, success: true, data: parsed }));
640
+ } else {
641
+ console.log(JSON.stringify(parsed, null, 2));
642
+ }
481
643
  }
644
+ async function siteCommand(args, options = {}) {
645
+ const subCommand = args[0];
646
+ if (!subCommand || subCommand === "--help" || subCommand === "-h") {
647
+ console.log(`bb-browser site - \u7F51\u7AD9 CLI \u5316\uFF08\u7BA1\u7406\u548C\u8FD0\u884C site adapter\uFF09
482
648
 
483
- // packages/cli/src/commands/wait.ts
484
- function isTimeWait(target) {
485
- return /^\d+$/.test(target);
486
- }
487
- function parseRef6(ref) {
488
- return ref.startsWith("@") ? ref.slice(1) : ref;
649
+ \u7528\u6CD5:
650
+ bb-browser site list \u5217\u51FA\u6240\u6709\u53EF\u7528 adapter
651
+ bb-browser site info <name> \u67E5\u770B adapter \u5143\u4FE1\u606F
652
+ bb-browser site recommend \u57FA\u4E8E\u5386\u53F2\u8BB0\u5F55\u63A8\u8350 adapter
653
+ bb-browser site search <query> \u641C\u7D22 adapter
654
+ bb-browser site <name> [args...] \u8FD0\u884C adapter\uFF08\u7B80\u5199\uFF09
655
+ bb-browser site run <name> [args...] \u8FD0\u884C adapter
656
+ bb-browser site update \u66F4\u65B0\u793E\u533A adapter \u5E93 (git clone/pull)
657
+
658
+ \u76EE\u5F55:
659
+ ${LOCAL_SITES_DIR} \u79C1\u6709 adapter\uFF08\u4F18\u5148\uFF09
660
+ ${COMMUNITY_SITES_DIR} \u793E\u533A adapter
661
+
662
+ \u793A\u4F8B:
663
+ bb-browser site update
664
+ bb-browser site list
665
+ bb-browser site reddit/thread https://www.reddit.com/r/LocalLLaMA/comments/...
666
+ bb-browser site twitter/user yan5xu
667
+ bb-browser site search reddit
668
+
669
+ \u521B\u5EFA\u65B0 adapter: bb-browser guide
670
+ \u62A5\u544A\u95EE\u9898: gh issue create --repo epiral/bb-sites --title "[adapter-name] \u63CF\u8FF0"
671
+ \u8D21\u732E\u793E\u533A: https://github.com/epiral/bb-sites`);
672
+ return;
673
+ }
674
+ switch (subCommand) {
675
+ case "list":
676
+ siteList(options);
677
+ break;
678
+ case "search":
679
+ if (!args[1]) {
680
+ console.error("[error] site search: <query> is required.");
681
+ console.error(" Usage: bb-browser site search <query>");
682
+ process.exit(1);
683
+ }
684
+ siteSearch(args[1], options);
685
+ break;
686
+ case "info":
687
+ if (!args[1]) {
688
+ console.error("[error] site info: <name> is required.");
689
+ console.error(" Usage: bb-browser site info <name>");
690
+ process.exit(1);
691
+ }
692
+ siteInfo(args[1], options);
693
+ break;
694
+ case "recommend":
695
+ await siteRecommend(options);
696
+ break;
697
+ case "update":
698
+ siteUpdate();
699
+ break;
700
+ case "run":
701
+ if (!args[1]) {
702
+ console.error("[error] site run: <name> is required.");
703
+ console.error(" Usage: bb-browser site run <name> [args...]");
704
+ console.error(" Try: bb-browser site list");
705
+ process.exit(1);
706
+ }
707
+ await siteRun(args[1], args.slice(2), options);
708
+ break;
709
+ default:
710
+ if (subCommand.includes("/")) {
711
+ await siteRun(subCommand, args.slice(1), options);
712
+ } else {
713
+ console.error(`[error] site: unknown subcommand "${subCommand}".`);
714
+ console.error(" Available: list, info, recommend, search, run, update");
715
+ console.error(" Try: bb-browser site --help");
716
+ process.exit(1);
717
+ }
718
+ break;
719
+ }
489
720
  }
490
- async function waitCommand(target, options = {}) {
491
- if (!target) {
492
- throw new Error("\u7F3A\u5C11\u7B49\u5F85\u76EE\u6807\u53C2\u6570");
721
+
722
+ // packages/cli/src/commands/open.ts
723
+ async function openCommand(url, options = {}) {
724
+ if (!url) {
725
+ throw new Error("\u7F3A\u5C11 URL \u53C2\u6570");
493
726
  }
494
727
  await ensureDaemonRunning();
495
- let request;
496
- if (isTimeWait(target)) {
497
- const ms = parseInt(target, 10);
498
- request = {
499
- id: generateId(),
500
- action: "wait",
501
- waitType: "time",
502
- ms,
503
- tabId: options.tabId
504
- };
505
- } else {
506
- const ref = parseRef6(target);
507
- request = {
508
- id: generateId(),
509
- action: "wait",
510
- waitType: "element",
511
- ref,
512
- tabId: options.tabId
513
- };
728
+ let normalizedUrl = url;
729
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
730
+ normalizedUrl = "https://" + url;
731
+ }
732
+ const request = {
733
+ id: generateId(),
734
+ action: "open",
735
+ url: normalizedUrl
736
+ };
737
+ if (options.tab !== void 0) {
738
+ if (options.tab === "current") {
739
+ request.tabId = "current";
740
+ } else {
741
+ const tabId = parseInt(options.tab, 10);
742
+ if (isNaN(tabId)) {
743
+ throw new Error(`\u65E0\u6548\u7684 tabId: ${options.tab}`);
744
+ }
745
+ request.tabId = tabId;
746
+ }
514
747
  }
515
748
  const response = await sendCommand(request);
516
749
  if (options.json) {
517
750
  console.log(JSON.stringify(response, null, 2));
518
751
  } else {
519
752
  if (response.success) {
520
- if (isTimeWait(target)) {
521
- console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
522
- } else {
523
- console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
753
+ console.log(`\u5DF2\u6253\u5F00: ${response.data?.url ?? normalizedUrl}`);
754
+ if (response.data?.title) {
755
+ console.log(`\u6807\u9898: ${response.data.title}`);
756
+ }
757
+ if (response.data?.tabId) {
758
+ console.log(`Tab ID: ${response.data.tabId}`);
759
+ }
760
+ const siteHint = getSiteHintForDomain(normalizedUrl);
761
+ if (siteHint) {
762
+ console.log(`
763
+ \u{1F4A1} ${siteHint}`);
524
764
  }
525
765
  } else {
526
766
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -529,35 +769,16 @@ async function waitCommand(target, options = {}) {
529
769
  }
530
770
  }
531
771
 
532
- // packages/cli/src/commands/press.ts
533
- function parseKey(keyString) {
534
- const parts = keyString.split("+");
535
- const modifierNames = ["Control", "Alt", "Shift", "Meta"];
536
- const modifiers = [];
537
- let key = "";
538
- for (const part of parts) {
539
- if (modifierNames.includes(part)) {
540
- modifiers.push(part);
541
- } else {
542
- key = part;
543
- }
544
- }
545
- return { key, modifiers };
546
- }
547
- async function pressCommand(keyString, options = {}) {
548
- if (!keyString) {
549
- throw new Error("\u7F3A\u5C11 key \u53C2\u6570");
550
- }
772
+ // packages/cli/src/commands/snapshot.ts
773
+ async function snapshotCommand(options = {}) {
551
774
  await ensureDaemonRunning();
552
- const { key, modifiers } = parseKey(keyString);
553
- if (!key) {
554
- throw new Error("\u65E0\u6548\u7684\u6309\u952E\u683C\u5F0F");
555
- }
556
775
  const request = {
557
776
  id: generateId(),
558
- action: "press",
559
- key,
560
- modifiers,
777
+ action: "snapshot",
778
+ interactive: options.interactive,
779
+ compact: options.compact,
780
+ maxDepth: options.maxDepth,
781
+ selector: options.selector,
561
782
  tabId: options.tabId
562
783
  };
563
784
  const response = await sendCommand(request);
@@ -565,8 +786,12 @@ async function pressCommand(keyString, options = {}) {
565
786
  console.log(JSON.stringify(response, null, 2));
566
787
  } else {
567
788
  if (response.success) {
568
- const displayKey = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
569
- console.log(`\u5DF2\u6309\u4E0B: ${displayKey}`);
789
+ console.log(`\u6807\u9898: ${response.data?.title ?? "(\u65E0\u6807\u9898)"}`);
790
+ console.log(`URL: ${response.data?.url ?? "(\u672A\u77E5)"}`);
791
+ if (response.data?.snapshotData?.snapshot) {
792
+ console.log("");
793
+ console.log(response.data.snapshotData.snapshot);
794
+ }
570
795
  } else {
571
796
  console.error(`\u9519\u8BEF: ${response.error}`);
572
797
  process.exit(1);
@@ -574,31 +799,20 @@ async function pressCommand(keyString, options = {}) {
574
799
  }
575
800
  }
576
801
 
577
- // packages/cli/src/commands/scroll.ts
578
- var VALID_DIRECTIONS = ["up", "down", "left", "right"];
579
- var DEFAULT_PIXELS = 300;
580
- async function scrollCommand(direction, pixels, options = {}) {
581
- if (!direction) {
582
- throw new Error("\u7F3A\u5C11 direction \u53C2\u6570");
583
- }
584
- if (!VALID_DIRECTIONS.includes(direction)) {
585
- throw new Error(
586
- `\u65E0\u6548\u7684\u6EDA\u52A8\u65B9\u5411: ${direction}\uFF0C\u652F\u6301: ${VALID_DIRECTIONS.join(", ")}`
587
- );
588
- }
589
- let pixelValue = DEFAULT_PIXELS;
590
- if (pixels !== void 0) {
591
- pixelValue = parseInt(pixels, 10);
592
- if (isNaN(pixelValue) || pixelValue <= 0) {
593
- throw new Error(`\u65E0\u6548\u7684\u50CF\u7D20\u503C: ${pixels}`);
594
- }
802
+ // packages/cli/src/commands/click.ts
803
+ function parseRef(ref) {
804
+ return ref.startsWith("@") ? ref.slice(1) : ref;
805
+ }
806
+ async function clickCommand(ref, options = {}) {
807
+ if (!ref) {
808
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
595
809
  }
596
810
  await ensureDaemonRunning();
811
+ const parsedRef = parseRef(ref);
597
812
  const request = {
598
813
  id: generateId(),
599
- action: "scroll",
600
- direction,
601
- pixels: pixelValue,
814
+ action: "click",
815
+ ref: parsedRef,
602
816
  tabId: options.tabId
603
817
  };
604
818
  const response = await sendCommand(request);
@@ -606,7 +820,13 @@ async function scrollCommand(direction, pixels, options = {}) {
606
820
  console.log(JSON.stringify(response, null, 2));
607
821
  } else {
608
822
  if (response.success) {
609
- console.log(`\u5DF2\u6EDA\u52A8: ${direction} ${pixelValue}px`);
823
+ const role = response.data?.role ?? "element";
824
+ const name = response.data?.name;
825
+ if (name) {
826
+ console.log(`\u5DF2\u70B9\u51FB: ${role} "${name}"`);
827
+ } else {
828
+ console.log(`\u5DF2\u70B9\u51FB: ${role}`);
829
+ }
610
830
  } else {
611
831
  console.error(`\u9519\u8BEF: ${response.error}`);
612
832
  process.exit(1);
@@ -614,188 +834,20 @@ async function scrollCommand(direction, pixels, options = {}) {
614
834
  }
615
835
  }
616
836
 
617
- // packages/cli/src/commands/daemon.ts
618
- import { spawn as spawn2 } from "child_process";
619
- async function daemonCommand(options = {}) {
620
- if (await isDaemonRunning()) {
621
- if (options.json) {
622
- console.log(JSON.stringify({ success: false, error: "Daemon \u5DF2\u5728\u8FD0\u884C" }));
623
- } else {
624
- console.log("Daemon \u5DF2\u5728\u8FD0\u884C");
625
- }
626
- return;
627
- }
628
- const daemonPath = getDaemonPath();
629
- const args = [daemonPath];
630
- if (options.host) {
631
- args.push("--host", options.host);
632
- }
633
- if (options.json) {
634
- console.log(JSON.stringify({ success: true, message: "Daemon \u542F\u52A8\u4E2D..." }));
635
- } else {
636
- console.log("Daemon \u542F\u52A8\u4E2D...");
637
- }
638
- await new Promise((resolve2, reject) => {
639
- const child = spawn2(process.execPath, args, {
640
- stdio: "inherit"
641
- });
642
- child.on("exit", (code) => {
643
- if (code && code !== 0) {
644
- reject(new Error(`Daemon exited with code ${code}`));
645
- } else {
646
- resolve2();
647
- }
648
- });
649
- child.on("error", reject);
650
- });
651
- }
652
- async function stopCommand(options = {}) {
653
- if (!await isDaemonRunning()) {
654
- if (options.json) {
655
- console.log(JSON.stringify({ success: false, error: "Daemon \u672A\u8FD0\u884C" }));
656
- } else {
657
- console.log("Daemon \u672A\u8FD0\u884C");
658
- }
659
- return;
660
- }
661
- const stopped = await stopDaemon();
662
- if (stopped) {
663
- if (options.json) {
664
- console.log(JSON.stringify({ success: true, message: "Daemon \u5DF2\u505C\u6B62" }));
665
- } else {
666
- console.log("Daemon \u5DF2\u505C\u6B62");
667
- }
668
- } else {
669
- if (options.json) {
670
- console.log(JSON.stringify({ success: false, error: "\u65E0\u6CD5\u505C\u6B62 Daemon" }));
671
- } else {
672
- console.error("\u65E0\u6CD5\u505C\u6B62 Daemon");
673
- }
674
- process.exit(1);
675
- }
676
- }
677
- async function statusCommand(options = {}) {
678
- const running = await isDaemonRunning();
679
- if (options.json) {
680
- console.log(JSON.stringify({ running }));
681
- } else {
682
- console.log(running ? "Daemon \u8FD0\u884C\u4E2D" : "Daemon \u672A\u8FD0\u884C");
683
- }
837
+ // packages/cli/src/commands/hover.ts
838
+ function parseRef2(ref) {
839
+ return ref.startsWith("@") ? ref.slice(1) : ref;
684
840
  }
685
-
686
- // packages/cli/src/commands/reload.ts
687
- import WebSocket from "ws";
688
- var EXTENSION_NAME = "bb-browser";
689
- async function reloadCommand(options = {}) {
690
- const port = options.port || 9222;
691
- try {
692
- const listRes = await fetch(`http://127.0.0.1:${port}/json/list`);
693
- if (!listRes.ok) {
694
- throw new Error(`CDP \u672A\u542F\u7528\u3002\u8BF7\u7528 --remote-debugging-port=${port} \u542F\u52A8 Chrome`);
695
- }
696
- const list = await listRes.json();
697
- const extPage = list.find(
698
- (t) => t.type === "page" && t.url.includes("chrome://extensions")
699
- );
700
- if (!extPage) {
701
- throw new Error("\u8BF7\u5148\u6253\u5F00 chrome://extensions \u9875\u9762");
702
- }
703
- const result = await new Promise((resolve2, reject) => {
704
- const ws = new WebSocket(extPage.webSocketDebuggerUrl);
705
- let resolved = false;
706
- const timeout = setTimeout(() => {
707
- if (!resolved) {
708
- resolved = true;
709
- ws.close();
710
- reject(new Error("CDP \u8FDE\u63A5\u8D85\u65F6"));
711
- }
712
- }, 1e4);
713
- ws.on("open", () => {
714
- const script = `
715
- (async function() {
716
- if (!chrome || !chrome.developerPrivate) {
717
- return { error: 'developerPrivate API not available' };
718
- }
719
-
720
- try {
721
- const exts = await chrome.developerPrivate.getExtensionsInfo();
722
- const bbExt = exts.find(e => e.name === '${EXTENSION_NAME}');
723
-
724
- if (!bbExt) {
725
- return { error: '${EXTENSION_NAME} \u6269\u5C55\u672A\u5B89\u88C5' };
726
- }
727
-
728
- if (bbExt.state !== 'ENABLED') {
729
- return { error: '${EXTENSION_NAME} \u6269\u5C55\u5DF2\u7981\u7528' };
730
- }
731
-
732
- await chrome.developerPrivate.reload(bbExt.id, {failQuietly: true});
733
- return { success: true, extensionId: bbExt.id };
734
- } catch (e) {
735
- return { error: e.message };
736
- }
737
- })()
738
- `;
739
- ws.send(JSON.stringify({
740
- id: 1,
741
- method: "Runtime.evaluate",
742
- params: {
743
- expression: script,
744
- awaitPromise: true,
745
- returnByValue: true
746
- }
747
- }));
748
- });
749
- ws.on("message", (data) => {
750
- const msg = JSON.parse(data.toString());
751
- if (msg.id === 1) {
752
- clearTimeout(timeout);
753
- resolved = true;
754
- ws.close();
755
- const value = msg.result?.result?.value;
756
- if (value?.success) {
757
- resolve2({
758
- success: true,
759
- message: "\u6269\u5C55\u5DF2\u91CD\u8F7D",
760
- extensionId: value.extensionId
761
- });
762
- } else if (value?.error) {
763
- reject(new Error(value.error));
764
- } else {
765
- reject(new Error(`\u91CD\u8F7D\u5931\u8D25: ${JSON.stringify(value)}`));
766
- }
767
- }
768
- });
769
- ws.on("error", (err) => {
770
- clearTimeout(timeout);
771
- if (!resolved) {
772
- resolved = true;
773
- reject(new Error(`CDP \u8FDE\u63A5\u5931\u8D25: ${err.message}`));
774
- }
775
- });
776
- });
777
- if (options.json) {
778
- console.log(JSON.stringify(result));
779
- } else {
780
- console.log(`${result.message} (${result.extensionId})`);
781
- }
782
- } catch (error) {
783
- const message = error instanceof Error ? error.message : String(error);
784
- if (options.json) {
785
- console.log(JSON.stringify({ success: false, error: message }));
786
- } else {
787
- console.error(`\u9519\u8BEF: ${message}`);
788
- }
789
- process.exit(1);
841
+ async function hoverCommand(ref, options = {}) {
842
+ if (!ref) {
843
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
790
844
  }
791
- }
792
-
793
- // packages/cli/src/commands/nav.ts
794
- async function backCommand(options = {}) {
795
845
  await ensureDaemonRunning();
846
+ const parsedRef = parseRef2(ref);
796
847
  const request = {
797
848
  id: generateId(),
798
- action: "back",
849
+ action: "hover",
850
+ ref: parsedRef,
799
851
  tabId: options.tabId
800
852
  };
801
853
  const response = await sendCommand(request);
@@ -803,11 +855,12 @@ async function backCommand(options = {}) {
803
855
  console.log(JSON.stringify(response, null, 2));
804
856
  } else {
805
857
  if (response.success) {
806
- const url = response.data?.url ?? "";
807
- if (url) {
808
- console.log(`\u540E\u9000\u81F3: ${url}`);
858
+ const role = response.data?.role ?? "element";
859
+ const name = response.data?.name;
860
+ if (name) {
861
+ console.log(`\u5DF2\u60AC\u505C: ${role} "${name}"`);
809
862
  } else {
810
- console.log("\u5DF2\u540E\u9000");
863
+ console.log(`\u5DF2\u60AC\u505C: ${role}`);
811
864
  }
812
865
  } else {
813
866
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -815,11 +868,25 @@ async function backCommand(options = {}) {
815
868
  }
816
869
  }
817
870
  }
818
- async function forwardCommand(options = {}) {
871
+
872
+ // packages/cli/src/commands/fill.ts
873
+ function parseRef3(ref) {
874
+ return ref.startsWith("@") ? ref.slice(1) : ref;
875
+ }
876
+ async function fillCommand(ref, text, options = {}) {
877
+ if (!ref) {
878
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
879
+ }
880
+ if (text === void 0 || text === null) {
881
+ throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
882
+ }
819
883
  await ensureDaemonRunning();
884
+ const parsedRef = parseRef3(ref);
820
885
  const request = {
821
886
  id: generateId(),
822
- action: "forward",
887
+ action: "fill",
888
+ ref: parsedRef,
889
+ text,
823
890
  tabId: options.tabId
824
891
  };
825
892
  const response = await sendCommand(request);
@@ -827,36 +894,14 @@ async function forwardCommand(options = {}) {
827
894
  console.log(JSON.stringify(response, null, 2));
828
895
  } else {
829
896
  if (response.success) {
830
- const url = response.data?.url ?? "";
831
- if (url) {
832
- console.log(`\u524D\u8FDB\u81F3: ${url}`);
897
+ const role = response.data?.role ?? "element";
898
+ const name = response.data?.name;
899
+ if (name) {
900
+ console.log(`\u5DF2\u586B\u5145: ${role} "${name}"`);
833
901
  } else {
834
- console.log("\u5DF2\u524D\u8FDB");
835
- }
836
- } else {
837
- console.error(`\u9519\u8BEF: ${response.error}`);
838
- process.exit(1);
839
- }
840
- }
841
- }
842
- async function refreshCommand(options = {}) {
843
- await ensureDaemonRunning();
844
- const request = {
845
- id: generateId(),
846
- action: "refresh",
847
- tabId: options.tabId
848
- };
849
- const response = await sendCommand(request);
850
- if (options.json) {
851
- console.log(JSON.stringify(response, null, 2));
852
- } else {
853
- if (response.success) {
854
- const title = response.data?.title ?? "";
855
- if (title) {
856
- console.log(`\u5DF2\u5237\u65B0: "${title}"`);
857
- } else {
858
- console.log("\u5DF2\u5237\u65B0\u9875\u9762");
902
+ console.log(`\u5DF2\u586B\u5145: ${role}`);
859
903
  }
904
+ console.log(`\u5185\u5BB9: "${text}"`);
860
905
  } else {
861
906
  console.error(`\u9519\u8BEF: ${response.error}`);
862
907
  process.exit(1);
@@ -864,20 +909,24 @@ async function refreshCommand(options = {}) {
864
909
  }
865
910
  }
866
911
 
867
- // packages/cli/src/commands/check.ts
868
- function parseRef7(ref) {
912
+ // packages/cli/src/commands/type.ts
913
+ function parseRef4(ref) {
869
914
  return ref.startsWith("@") ? ref.slice(1) : ref;
870
915
  }
871
- async function checkCommand(ref, options = {}) {
916
+ async function typeCommand(ref, text, options = {}) {
872
917
  if (!ref) {
873
918
  throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
874
919
  }
920
+ if (text === void 0 || text === null) {
921
+ throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
922
+ }
875
923
  await ensureDaemonRunning();
876
- const parsedRef = parseRef7(ref);
924
+ const parsedRef = parseRef4(ref);
877
925
  const request = {
878
926
  id: generateId(),
879
- action: "check",
927
+ action: "type",
880
928
  ref: parsedRef,
929
+ text,
881
930
  tabId: options.tabId
882
931
  };
883
932
  const response = await sendCommand(request);
@@ -885,38 +934,27 @@ async function checkCommand(ref, options = {}) {
885
934
  console.log(JSON.stringify(response, null, 2));
886
935
  } else {
887
936
  if (response.success) {
888
- const role = response.data?.role ?? "checkbox";
937
+ const role = response.data?.role ?? "element";
889
938
  const name = response.data?.name;
890
- const wasAlreadyChecked = response.data?.wasAlreadyChecked;
891
- if (wasAlreadyChecked) {
892
- if (name) {
893
- console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role} "${name}"`);
894
- } else {
895
- console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role}`);
896
- }
939
+ if (name) {
940
+ console.log(`\u5DF2\u8F93\u5165: ${role} "${name}"`);
897
941
  } else {
898
- if (name) {
899
- console.log(`\u5DF2\u52FE\u9009: ${role} "${name}"`);
900
- } else {
901
- console.log(`\u5DF2\u52FE\u9009: ${role}`);
902
- }
942
+ console.log(`\u5DF2\u8F93\u5165: ${role}`);
903
943
  }
944
+ console.log(`\u5185\u5BB9: "${text}"`);
904
945
  } else {
905
946
  console.error(`\u9519\u8BEF: ${response.error}`);
906
947
  process.exit(1);
907
948
  }
908
949
  }
909
950
  }
910
- async function uncheckCommand(ref, options = {}) {
911
- if (!ref) {
912
- throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
913
- }
951
+
952
+ // packages/cli/src/commands/close.ts
953
+ async function closeCommand(options = {}) {
914
954
  await ensureDaemonRunning();
915
- const parsedRef = parseRef7(ref);
916
955
  const request = {
917
956
  id: generateId(),
918
- action: "uncheck",
919
- ref: parsedRef,
957
+ action: "close",
920
958
  tabId: options.tabId
921
959
  };
922
960
  const response = await sendCommand(request);
@@ -924,21 +962,11 @@ async function uncheckCommand(ref, options = {}) {
924
962
  console.log(JSON.stringify(response, null, 2));
925
963
  } else {
926
964
  if (response.success) {
927
- const role = response.data?.role ?? "checkbox";
928
- const name = response.data?.name;
929
- const wasAlreadyUnchecked = response.data?.wasAlreadyUnchecked;
930
- if (wasAlreadyUnchecked) {
931
- if (name) {
932
- console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role} "${name}"`);
933
- } else {
934
- console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role}`);
935
- }
965
+ const title = response.data?.title ?? "";
966
+ if (title) {
967
+ console.log(`\u5DF2\u5173\u95ED: "${title}"`);
936
968
  } else {
937
- if (name) {
938
- console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role} "${name}"`);
939
- } else {
940
- console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role}`);
941
- }
969
+ console.log("\u5DF2\u5173\u95ED\u5F53\u524D\u6807\u7B7E\u9875");
942
970
  }
943
971
  } else {
944
972
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -947,24 +975,20 @@ async function uncheckCommand(ref, options = {}) {
947
975
  }
948
976
  }
949
977
 
950
- // packages/cli/src/commands/select.ts
951
- function parseRef8(ref) {
978
+ // packages/cli/src/commands/get.ts
979
+ function parseRef5(ref) {
952
980
  return ref.startsWith("@") ? ref.slice(1) : ref;
953
981
  }
954
- async function selectCommand(ref, value, options = {}) {
955
- if (!ref) {
956
- throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
957
- }
958
- if (value === void 0 || value === null) {
959
- throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
982
+ async function getCommand(attribute, ref, options = {}) {
983
+ if (attribute === "text" && !ref) {
984
+ throw new Error("get text \u9700\u8981 ref \u53C2\u6570\uFF0C\u5982: get text @5");
960
985
  }
961
986
  await ensureDaemonRunning();
962
- const parsedRef = parseRef8(ref);
963
987
  const request = {
964
988
  id: generateId(),
965
- action: "select",
966
- ref: parsedRef,
967
- value,
989
+ action: "get",
990
+ attribute,
991
+ ref: ref ? parseRef5(ref) : void 0,
968
992
  tabId: options.tabId
969
993
  };
970
994
  const response = await sendCommand(request);
@@ -972,20 +996,8 @@ async function selectCommand(ref, value, options = {}) {
972
996
  console.log(JSON.stringify(response, null, 2));
973
997
  } else {
974
998
  if (response.success) {
975
- const role = response.data?.role ?? "combobox";
976
- const name = response.data?.name;
977
- const selectedValue = response.data?.selectedValue;
978
- const selectedLabel = response.data?.selectedLabel;
979
- if (name) {
980
- console.log(`\u5DF2\u9009\u62E9: ${role} "${name}"`);
981
- } else {
982
- console.log(`\u5DF2\u9009\u62E9: ${role}`);
983
- }
984
- if (selectedLabel && selectedLabel !== selectedValue) {
985
- console.log(`\u9009\u9879: "${selectedLabel}" (value="${selectedValue}")`);
986
- } else {
987
- console.log(`\u9009\u9879: "${selectedValue}"`);
988
- }
999
+ const value = response.data?.value ?? "";
1000
+ console.log(value);
989
1001
  } else {
990
1002
  console.error(`\u9519\u8BEF: ${response.error}`);
991
1003
  process.exit(1);
@@ -993,32 +1005,96 @@ async function selectCommand(ref, value, options = {}) {
993
1005
  }
994
1006
  }
995
1007
 
996
- // packages/cli/src/commands/eval.ts
997
- async function evalCommand(script, options = {}) {
998
- if (!script) {
999
- throw new Error("\u7F3A\u5C11 script \u53C2\u6570");
1008
+ // packages/cli/src/commands/screenshot.ts
1009
+ import fs from "fs";
1010
+ import path from "path";
1011
+ import os from "os";
1012
+ function getDefaultPath() {
1013
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1014
+ const filename = `bb-screenshot-${timestamp}.png`;
1015
+ return path.join(os.tmpdir(), filename);
1016
+ }
1017
+ function saveBase64Image(dataUrl, filePath) {
1018
+ const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
1019
+ const buffer = Buffer.from(base64Data, "base64");
1020
+ const dir = path.dirname(filePath);
1021
+ if (!fs.existsSync(dir)) {
1022
+ fs.mkdirSync(dir, { recursive: true });
1000
1023
  }
1024
+ fs.writeFileSync(filePath, buffer);
1025
+ }
1026
+ async function screenshotCommand(outputPath, options = {}) {
1001
1027
  await ensureDaemonRunning();
1028
+ const filePath = outputPath ? path.resolve(outputPath) : getDefaultPath();
1002
1029
  const request = {
1003
1030
  id: generateId(),
1004
- action: "eval",
1005
- script,
1031
+ action: "screenshot",
1006
1032
  tabId: options.tabId
1007
1033
  };
1008
1034
  const response = await sendCommand(request);
1035
+ if (response.success && response.data?.dataUrl) {
1036
+ const dataUrl = response.data.dataUrl;
1037
+ saveBase64Image(dataUrl, filePath);
1038
+ if (options.json) {
1039
+ console.log(JSON.stringify({
1040
+ success: true,
1041
+ path: filePath,
1042
+ base64: dataUrl
1043
+ }, null, 2));
1044
+ } else {
1045
+ console.log(`\u622A\u56FE\u5DF2\u4FDD\u5B58: ${filePath}`);
1046
+ }
1047
+ } else {
1048
+ if (options.json) {
1049
+ console.log(JSON.stringify(response, null, 2));
1050
+ } else {
1051
+ console.error(`\u9519\u8BEF: ${response.error}`);
1052
+ }
1053
+ process.exit(1);
1054
+ }
1055
+ }
1056
+
1057
+ // packages/cli/src/commands/wait.ts
1058
+ function isTimeWait(target) {
1059
+ return /^\d+$/.test(target);
1060
+ }
1061
+ function parseRef6(ref) {
1062
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1063
+ }
1064
+ async function waitCommand(target, options = {}) {
1065
+ if (!target) {
1066
+ throw new Error("\u7F3A\u5C11\u7B49\u5F85\u76EE\u6807\u53C2\u6570");
1067
+ }
1068
+ await ensureDaemonRunning();
1069
+ let request;
1070
+ if (isTimeWait(target)) {
1071
+ const ms = parseInt(target, 10);
1072
+ request = {
1073
+ id: generateId(),
1074
+ action: "wait",
1075
+ waitType: "time",
1076
+ ms,
1077
+ tabId: options.tabId
1078
+ };
1079
+ } else {
1080
+ const ref = parseRef6(target);
1081
+ request = {
1082
+ id: generateId(),
1083
+ action: "wait",
1084
+ waitType: "element",
1085
+ ref,
1086
+ tabId: options.tabId
1087
+ };
1088
+ }
1089
+ const response = await sendCommand(request);
1009
1090
  if (options.json) {
1010
1091
  console.log(JSON.stringify(response, null, 2));
1011
1092
  } else {
1012
1093
  if (response.success) {
1013
- const result = response.data?.result;
1014
- if (result !== void 0) {
1015
- if (typeof result === "object" && result !== null) {
1016
- console.log(JSON.stringify(result, null, 2));
1017
- } else {
1018
- console.log(result);
1019
- }
1094
+ if (isTimeWait(target)) {
1095
+ console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
1020
1096
  } else {
1021
- console.log("undefined");
1097
+ console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
1022
1098
  }
1023
1099
  } else {
1024
1100
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -1027,101 +1103,44 @@ async function evalCommand(script, options = {}) {
1027
1103
  }
1028
1104
  }
1029
1105
 
1030
- // packages/cli/src/commands/tab.ts
1031
- function parseTabSubcommand(args, rawArgv) {
1032
- let tabId;
1033
- if (rawArgv) {
1034
- const idIdx = rawArgv.indexOf("--id");
1035
- if (idIdx >= 0 && rawArgv[idIdx + 1]) {
1036
- tabId = parseInt(rawArgv[idIdx + 1], 10);
1037
- if (isNaN(tabId)) {
1038
- throw new Error(`\u65E0\u6548\u7684 tabId: ${rawArgv[idIdx + 1]}`);
1039
- }
1106
+ // packages/cli/src/commands/press.ts
1107
+ function parseKey(keyString) {
1108
+ const parts = keyString.split("+");
1109
+ const modifierNames = ["Control", "Alt", "Shift", "Meta"];
1110
+ const modifiers = [];
1111
+ let key = "";
1112
+ for (const part of parts) {
1113
+ if (modifierNames.includes(part)) {
1114
+ modifiers.push(part);
1115
+ } else {
1116
+ key = part;
1040
1117
  }
1041
1118
  }
1042
- if (args.length === 0) {
1043
- return { action: "tab_list" };
1044
- }
1045
- const first = args[0];
1046
- if (first === "new") {
1047
- return { action: "tab_new", url: args[1] };
1119
+ return { key, modifiers };
1120
+ }
1121
+ async function pressCommand(keyString, options = {}) {
1122
+ if (!keyString) {
1123
+ throw new Error("\u7F3A\u5C11 key \u53C2\u6570");
1048
1124
  }
1049
- if (first === "select") {
1050
- if (tabId !== void 0) {
1051
- return { action: "tab_select", tabId };
1052
- }
1053
- throw new Error("tab select \u9700\u8981 --id \u53C2\u6570\uFF0C\u7528\u6CD5\uFF1Abb-browser tab select --id <tabId>");
1125
+ await ensureDaemonRunning();
1126
+ const { key, modifiers } = parseKey(keyString);
1127
+ if (!key) {
1128
+ throw new Error("\u65E0\u6548\u7684\u6309\u952E\u683C\u5F0F");
1054
1129
  }
1055
- if (first === "close") {
1056
- if (tabId !== void 0) {
1057
- return { action: "tab_close", tabId };
1058
- }
1059
- const indexArg = args[1];
1060
- if (indexArg !== void 0) {
1061
- const index2 = parseInt(indexArg, 10);
1062
- if (isNaN(index2) || index2 < 0) {
1063
- throw new Error(`\u65E0\u6548\u7684\u6807\u7B7E\u9875\u7D22\u5F15: ${indexArg}`);
1064
- }
1065
- return { action: "tab_close", index: index2 };
1066
- }
1067
- return { action: "tab_close" };
1068
- }
1069
- const index = parseInt(first, 10);
1070
- if (!isNaN(index) && index >= 0) {
1071
- return { action: "tab_select", index };
1072
- }
1073
- throw new Error(`\u672A\u77E5\u7684 tab \u5B50\u547D\u4EE4: ${first}`);
1074
- }
1075
- function formatTabList(tabs, activeIndex) {
1076
- const lines = [];
1077
- lines.push(`\u6807\u7B7E\u9875\u5217\u8868\uFF08\u5171 ${tabs.length} \u4E2A\uFF0C\u5F53\u524D #${activeIndex}\uFF09\uFF1A`);
1078
- for (const tab of tabs) {
1079
- const prefix = tab.active ? "*" : " ";
1080
- const title = tab.title || "(\u65E0\u6807\u9898)";
1081
- lines.push(`${prefix} [${tab.index}] ${tab.url} - ${title}`);
1082
- }
1083
- return lines.join("\n");
1084
- }
1085
- async function tabCommand(args, options = {}) {
1086
- await ensureDaemonRunning();
1087
- const parsed = parseTabSubcommand(args, process.argv);
1088
1130
  const request = {
1089
1131
  id: generateId(),
1090
- action: parsed.action,
1091
- url: parsed.url,
1092
- index: parsed.index,
1093
- tabId: parsed.tabId
1132
+ action: "press",
1133
+ key,
1134
+ modifiers,
1135
+ tabId: options.tabId
1094
1136
  };
1095
1137
  const response = await sendCommand(request);
1096
1138
  if (options.json) {
1097
1139
  console.log(JSON.stringify(response, null, 2));
1098
1140
  } else {
1099
1141
  if (response.success) {
1100
- switch (parsed.action) {
1101
- case "tab_list": {
1102
- const tabs = response.data?.tabs ?? [];
1103
- const activeIndex = response.data?.activeIndex ?? 0;
1104
- console.log(formatTabList(tabs, activeIndex));
1105
- break;
1106
- }
1107
- case "tab_new": {
1108
- const url = response.data?.url ?? "about:blank";
1109
- console.log(`\u5DF2\u521B\u5EFA\u65B0\u6807\u7B7E\u9875: ${url}`);
1110
- break;
1111
- }
1112
- case "tab_select": {
1113
- const title = response.data?.title ?? "(\u65E0\u6807\u9898)";
1114
- const url = response.data?.url ?? "";
1115
- console.log(`\u5DF2\u5207\u6362\u5230\u6807\u7B7E\u9875 #${parsed.index}: ${title}`);
1116
- console.log(` URL: ${url}`);
1117
- break;
1118
- }
1119
- case "tab_close": {
1120
- const closedTitle = response.data?.title ?? "(\u65E0\u6807\u9898)";
1121
- console.log(`\u5DF2\u5173\u95ED\u6807\u7B7E\u9875: ${closedTitle}`);
1122
- break;
1123
- }
1124
- }
1142
+ const displayKey = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
1143
+ console.log(`\u5DF2\u6309\u4E0B: ${displayKey}`);
1125
1144
  } else {
1126
1145
  console.error(`\u9519\u8BEF: ${response.error}`);
1127
1146
  process.exit(1);
@@ -1129,40 +1148,31 @@ async function tabCommand(args, options = {}) {
1129
1148
  }
1130
1149
  }
1131
1150
 
1132
- // packages/cli/src/commands/frame.ts
1133
- async function frameCommand(selector, options = {}) {
1134
- if (!selector) {
1135
- throw new Error("\u7F3A\u5C11 selector \u53C2\u6570");
1151
+ // packages/cli/src/commands/scroll.ts
1152
+ var VALID_DIRECTIONS = ["up", "down", "left", "right"];
1153
+ var DEFAULT_PIXELS = 300;
1154
+ async function scrollCommand(direction, pixels, options = {}) {
1155
+ if (!direction) {
1156
+ throw new Error("\u7F3A\u5C11 direction \u53C2\u6570");
1136
1157
  }
1137
- await ensureDaemonRunning();
1138
- const request = {
1139
- id: generateId(),
1140
- action: "frame",
1141
- selector,
1142
- tabId: options.tabId
1143
- };
1144
- const response = await sendCommand(request);
1145
- if (options.json) {
1146
- console.log(JSON.stringify(response, null, 2));
1147
- } else {
1148
- if (response.success) {
1149
- const frameInfo = response.data?.frameInfo;
1150
- if (frameInfo?.url) {
1151
- console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector} (${frameInfo.url})`);
1152
- } else {
1153
- console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector}`);
1154
- }
1155
- } else {
1156
- console.error(`\u9519\u8BEF: ${response.error}`);
1157
- process.exit(1);
1158
+ if (!VALID_DIRECTIONS.includes(direction)) {
1159
+ throw new Error(
1160
+ `\u65E0\u6548\u7684\u6EDA\u52A8\u65B9\u5411: ${direction}\uFF0C\u652F\u6301: ${VALID_DIRECTIONS.join(", ")}`
1161
+ );
1162
+ }
1163
+ let pixelValue = DEFAULT_PIXELS;
1164
+ if (pixels !== void 0) {
1165
+ pixelValue = parseInt(pixels, 10);
1166
+ if (isNaN(pixelValue) || pixelValue <= 0) {
1167
+ throw new Error(`\u65E0\u6548\u7684\u50CF\u7D20\u503C: ${pixels}`);
1158
1168
  }
1159
1169
  }
1160
- }
1161
- async function frameMainCommand(options = {}) {
1162
1170
  await ensureDaemonRunning();
1163
1171
  const request = {
1164
1172
  id: generateId(),
1165
- action: "frame_main",
1173
+ action: "scroll",
1174
+ direction,
1175
+ pixels: pixelValue,
1166
1176
  tabId: options.tabId
1167
1177
  };
1168
1178
  const response = await sendCommand(request);
@@ -1170,7 +1180,7 @@ async function frameMainCommand(options = {}) {
1170
1180
  console.log(JSON.stringify(response, null, 2));
1171
1181
  } else {
1172
1182
  if (response.success) {
1173
- console.log("\u5DF2\u8FD4\u56DE\u4E3B frame");
1183
+ console.log(`\u5DF2\u6EDA\u52A8: ${direction} ${pixelValue}px`);
1174
1184
  } else {
1175
1185
  console.error(`\u9519\u8BEF: ${response.error}`);
1176
1186
  process.exit(1);
@@ -1178,797 +1188,1027 @@ async function frameMainCommand(options = {}) {
1178
1188
  }
1179
1189
  }
1180
1190
 
1181
- // packages/cli/src/commands/dialog.ts
1182
- async function dialogCommand(subCommand, promptText, options = {}) {
1183
- if (!subCommand || !["accept", "dismiss"].includes(subCommand)) {
1184
- throw new Error("\u8BF7\u4F7F\u7528 'dialog accept [text]' \u6216 'dialog dismiss'");
1191
+ // packages/cli/src/commands/daemon.ts
1192
+ import { spawn as spawn2 } from "child_process";
1193
+ async function daemonCommand(options = {}) {
1194
+ if (await isDaemonRunning()) {
1195
+ if (options.json) {
1196
+ console.log(JSON.stringify({ success: false, error: "Daemon \u5DF2\u5728\u8FD0\u884C" }));
1197
+ } else {
1198
+ console.log("Daemon \u5DF2\u5728\u8FD0\u884C");
1199
+ }
1200
+ return;
1201
+ }
1202
+ const daemonPath = getDaemonPath();
1203
+ const args = [daemonPath];
1204
+ if (options.host) {
1205
+ args.push("--host", options.host);
1185
1206
  }
1186
- await ensureDaemonRunning();
1187
- const request = {
1188
- id: generateId(),
1189
- action: "dialog",
1190
- dialogResponse: subCommand,
1191
- promptText: subCommand === "accept" ? promptText : void 0,
1192
- tabId: options.tabId
1193
- };
1194
- const response = await sendCommand(request);
1195
1207
  if (options.json) {
1196
- console.log(JSON.stringify(response, null, 2));
1208
+ console.log(JSON.stringify({ success: true, message: "Daemon \u542F\u52A8\u4E2D..." }));
1197
1209
  } else {
1198
- if (response.success) {
1199
- const dialogInfo = response.data?.dialogInfo;
1200
- if (dialogInfo) {
1201
- const action = subCommand === "accept" ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
1202
- console.log(`${action}\u5BF9\u8BDD\u6846\uFF08${dialogInfo.type}\uFF09: "${dialogInfo.message}"`);
1210
+ console.log("Daemon \u542F\u52A8\u4E2D...");
1211
+ }
1212
+ await new Promise((resolve2, reject) => {
1213
+ const child = spawn2(process.execPath, args, {
1214
+ stdio: "inherit"
1215
+ });
1216
+ child.on("exit", (code) => {
1217
+ if (code && code !== 0) {
1218
+ reject(new Error(`Daemon exited with code ${code}`));
1203
1219
  } else {
1204
- console.log("\u5BF9\u8BDD\u6846\u5DF2\u5904\u7406");
1220
+ resolve2();
1205
1221
  }
1222
+ });
1223
+ child.on("error", reject);
1224
+ });
1225
+ }
1226
+ async function stopCommand(options = {}) {
1227
+ if (!await isDaemonRunning()) {
1228
+ if (options.json) {
1229
+ console.log(JSON.stringify({ success: false, error: "Daemon \u672A\u8FD0\u884C" }));
1206
1230
  } else {
1207
- console.error(`\u9519\u8BEF: ${response.error}`);
1208
- process.exit(1);
1231
+ console.log("Daemon \u672A\u8FD0\u884C");
1232
+ }
1233
+ return;
1234
+ }
1235
+ const stopped = await stopDaemon();
1236
+ if (stopped) {
1237
+ if (options.json) {
1238
+ console.log(JSON.stringify({ success: true, message: "Daemon \u5DF2\u505C\u6B62" }));
1239
+ } else {
1240
+ console.log("Daemon \u5DF2\u505C\u6B62");
1241
+ }
1242
+ } else {
1243
+ if (options.json) {
1244
+ console.log(JSON.stringify({ success: false, error: "\u65E0\u6CD5\u505C\u6B62 Daemon" }));
1245
+ } else {
1246
+ console.error("\u65E0\u6CD5\u505C\u6B62 Daemon");
1209
1247
  }
1248
+ process.exit(1);
1210
1249
  }
1211
1250
  }
1212
-
1213
- // packages/cli/src/commands/network.ts
1214
- async function networkCommand(subCommand, urlOrFilter, options = {}) {
1215
- const response = await sendCommand({
1216
- id: crypto.randomUUID(),
1217
- action: "network",
1218
- networkCommand: subCommand,
1219
- url: subCommand === "route" || subCommand === "unroute" ? urlOrFilter : void 0,
1220
- filter: subCommand === "requests" ? urlOrFilter : void 0,
1221
- routeOptions: subCommand === "route" ? {
1222
- abort: options.abort,
1223
- body: options.body
1224
- } : void 0,
1225
- withBody: subCommand === "requests" ? options.withBody : void 0,
1226
- tabId: options.tabId
1227
- });
1251
+ async function statusCommand(options = {}) {
1252
+ const running = await isDaemonRunning();
1228
1253
  if (options.json) {
1229
- console.log(JSON.stringify(response));
1230
- return;
1231
- }
1232
- if (!response.success) {
1233
- throw new Error(response.error || "Network command failed");
1254
+ console.log(JSON.stringify({ running }));
1255
+ } else {
1256
+ console.log(running ? "Daemon \u8FD0\u884C\u4E2D" : "Daemon \u672A\u8FD0\u884C");
1234
1257
  }
1235
- const data = response.data;
1236
- switch (subCommand) {
1237
- case "requests": {
1238
- const requests = data?.networkRequests || [];
1239
- if (requests.length === 0) {
1240
- console.log("\u6CA1\u6709\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
1241
- console.log("\u63D0\u793A: \u4F7F\u7528 network requests \u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1242
- } else {
1243
- console.log(`\u7F51\u7EDC\u8BF7\u6C42 (${requests.length} \u6761):
1244
- `);
1245
- for (const req of requests) {
1246
- const status = req.failed ? `FAILED (${req.failureReason})` : req.status ? `${req.status} ${req.statusText || ""}` : "pending";
1247
- console.log(`${req.method} ${req.url}`);
1248
- console.log(` \u7C7B\u578B: ${req.type}, \u72B6\u6001: ${status}`);
1249
- if (options.withBody) {
1250
- const requestHeaderCount = req.requestHeaders ? Object.keys(req.requestHeaders).length : 0;
1251
- const responseHeaderCount = req.responseHeaders ? Object.keys(req.responseHeaders).length : 0;
1252
- console.log(` \u8BF7\u6C42\u5934: ${requestHeaderCount}, \u54CD\u5E94\u5934: ${responseHeaderCount}`);
1253
- if (req.requestBody !== void 0) {
1254
- const preview = req.requestBody.length > 200 ? `${req.requestBody.slice(0, 200)}...` : req.requestBody;
1255
- console.log(` \u8BF7\u6C42\u4F53: ${preview}`);
1256
- }
1257
- if (req.responseBody !== void 0) {
1258
- const preview = req.responseBody.length > 200 ? `${req.responseBody.slice(0, 200)}...` : req.responseBody;
1259
- console.log(` \u54CD\u5E94\u4F53: ${preview}`);
1260
- }
1261
- if (req.bodyError) {
1262
- console.log(` Body\u9519\u8BEF: ${req.bodyError}`);
1263
- }
1264
- }
1265
- console.log("");
1266
- }
1267
- }
1268
- break;
1258
+ }
1259
+
1260
+ // packages/cli/src/commands/reload.ts
1261
+ import WebSocket from "ws";
1262
+ var EXTENSION_NAME = "bb-browser";
1263
+ async function reloadCommand(options = {}) {
1264
+ const port = options.port || 9222;
1265
+ try {
1266
+ const listRes = await fetch(`http://127.0.0.1:${port}/json/list`);
1267
+ if (!listRes.ok) {
1268
+ throw new Error(`CDP \u672A\u542F\u7528\u3002\u8BF7\u7528 --remote-debugging-port=${port} \u542F\u52A8 Chrome`);
1269
1269
  }
1270
- case "route": {
1271
- console.log(`\u5DF2\u6DFB\u52A0\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
1272
- if (options.abort) {
1273
- console.log(" \u884C\u4E3A: \u963B\u6B62\u8BF7\u6C42");
1274
- } else if (options.body) {
1275
- console.log(" \u884C\u4E3A: \u8FD4\u56DE mock \u6570\u636E");
1276
- } else {
1277
- console.log(" \u884C\u4E3A: \u7EE7\u7EED\u8BF7\u6C42");
1278
- }
1279
- console.log(`\u5F53\u524D\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
1280
- break;
1270
+ const list = await listRes.json();
1271
+ const extPage = list.find(
1272
+ (t) => t.type === "page" && t.url.includes("chrome://extensions")
1273
+ );
1274
+ if (!extPage) {
1275
+ throw new Error("\u8BF7\u5148\u6253\u5F00 chrome://extensions \u9875\u9762");
1281
1276
  }
1282
- case "unroute": {
1283
- if (urlOrFilter) {
1284
- console.log(`\u5DF2\u79FB\u9664\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
1285
- } else {
1286
- console.log("\u5DF2\u79FB\u9664\u6240\u6709\u62E6\u622A\u89C4\u5219");
1287
- }
1288
- console.log(`\u5269\u4F59\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
1289
- break;
1277
+ const result = await new Promise((resolve2, reject) => {
1278
+ const ws = new WebSocket(extPage.webSocketDebuggerUrl);
1279
+ let resolved = false;
1280
+ const timeout = setTimeout(() => {
1281
+ if (!resolved) {
1282
+ resolved = true;
1283
+ ws.close();
1284
+ reject(new Error("CDP \u8FDE\u63A5\u8D85\u65F6"));
1285
+ }
1286
+ }, 1e4);
1287
+ ws.on("open", () => {
1288
+ const script = `
1289
+ (async function() {
1290
+ if (!chrome || !chrome.developerPrivate) {
1291
+ return { error: 'developerPrivate API not available' };
1292
+ }
1293
+
1294
+ try {
1295
+ const exts = await chrome.developerPrivate.getExtensionsInfo();
1296
+ const bbExt = exts.find(e => e.name === '${EXTENSION_NAME}');
1297
+
1298
+ if (!bbExt) {
1299
+ return { error: '${EXTENSION_NAME} \u6269\u5C55\u672A\u5B89\u88C5' };
1300
+ }
1301
+
1302
+ if (bbExt.state !== 'ENABLED') {
1303
+ return { error: '${EXTENSION_NAME} \u6269\u5C55\u5DF2\u7981\u7528' };
1304
+ }
1305
+
1306
+ await chrome.developerPrivate.reload(bbExt.id, {failQuietly: true});
1307
+ return { success: true, extensionId: bbExt.id };
1308
+ } catch (e) {
1309
+ return { error: e.message };
1310
+ }
1311
+ })()
1312
+ `;
1313
+ ws.send(JSON.stringify({
1314
+ id: 1,
1315
+ method: "Runtime.evaluate",
1316
+ params: {
1317
+ expression: script,
1318
+ awaitPromise: true,
1319
+ returnByValue: true
1320
+ }
1321
+ }));
1322
+ });
1323
+ ws.on("message", (data) => {
1324
+ const msg = JSON.parse(data.toString());
1325
+ if (msg.id === 1) {
1326
+ clearTimeout(timeout);
1327
+ resolved = true;
1328
+ ws.close();
1329
+ const value = msg.result?.result?.value;
1330
+ if (value?.success) {
1331
+ resolve2({
1332
+ success: true,
1333
+ message: "\u6269\u5C55\u5DF2\u91CD\u8F7D",
1334
+ extensionId: value.extensionId
1335
+ });
1336
+ } else if (value?.error) {
1337
+ reject(new Error(value.error));
1338
+ } else {
1339
+ reject(new Error(`\u91CD\u8F7D\u5931\u8D25: ${JSON.stringify(value)}`));
1340
+ }
1341
+ }
1342
+ });
1343
+ ws.on("error", (err) => {
1344
+ clearTimeout(timeout);
1345
+ if (!resolved) {
1346
+ resolved = true;
1347
+ reject(new Error(`CDP \u8FDE\u63A5\u5931\u8D25: ${err.message}`));
1348
+ }
1349
+ });
1350
+ });
1351
+ if (options.json) {
1352
+ console.log(JSON.stringify(result));
1353
+ } else {
1354
+ console.log(`${result.message} (${result.extensionId})`);
1290
1355
  }
1291
- case "clear": {
1292
- console.log("\u5DF2\u6E05\u7A7A\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
1293
- break;
1356
+ } catch (error) {
1357
+ const message = error instanceof Error ? error.message : String(error);
1358
+ if (options.json) {
1359
+ console.log(JSON.stringify({ success: false, error: message }));
1360
+ } else {
1361
+ console.error(`\u9519\u8BEF: ${message}`);
1294
1362
  }
1295
- default:
1296
- throw new Error(`\u672A\u77E5\u7684 network \u5B50\u547D\u4EE4: ${subCommand}`);
1363
+ process.exit(1);
1297
1364
  }
1298
1365
  }
1299
1366
 
1300
- // packages/cli/src/commands/console.ts
1301
- async function consoleCommand(options = {}) {
1302
- const response = await sendCommand({
1303
- id: crypto.randomUUID(),
1304
- action: "console",
1305
- consoleCommand: options.clear ? "clear" : "get",
1367
+ // packages/cli/src/commands/nav.ts
1368
+ async function backCommand(options = {}) {
1369
+ await ensureDaemonRunning();
1370
+ const request = {
1371
+ id: generateId(),
1372
+ action: "back",
1306
1373
  tabId: options.tabId
1307
- });
1308
- if (options.json) {
1309
- console.log(JSON.stringify(response));
1310
- return;
1311
- }
1312
- if (!response.success) {
1313
- throw new Error(response.error || "Console command failed");
1314
- }
1315
- if (options.clear) {
1316
- console.log("\u5DF2\u6E05\u7A7A\u63A7\u5236\u53F0\u6D88\u606F");
1317
- return;
1318
- }
1319
- const messages = response.data?.consoleMessages || [];
1320
- if (messages.length === 0) {
1321
- console.log("\u6CA1\u6709\u63A7\u5236\u53F0\u6D88\u606F");
1322
- console.log("\u63D0\u793A: console \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1323
- return;
1324
- }
1325
- console.log(`\u63A7\u5236\u53F0\u6D88\u606F (${messages.length} \u6761):
1326
- `);
1327
- const typeColors = {
1328
- log: "",
1329
- info: "[INFO]",
1330
- warn: "[WARN]",
1331
- error: "[ERROR]",
1332
- debug: "[DEBUG]"
1333
1374
  };
1334
- for (const msg of messages) {
1335
- const prefix = typeColors[msg.type] || `[${msg.type.toUpperCase()}]`;
1336
- const location = msg.url ? ` (${msg.url}${msg.lineNumber ? `:${msg.lineNumber}` : ""})` : "";
1337
- if (prefix) {
1338
- console.log(`${prefix} ${msg.text}${location}`);
1375
+ const response = await sendCommand(request);
1376
+ if (options.json) {
1377
+ console.log(JSON.stringify(response, null, 2));
1378
+ } else {
1379
+ if (response.success) {
1380
+ const url = response.data?.url ?? "";
1381
+ if (url) {
1382
+ console.log(`\u540E\u9000\u81F3: ${url}`);
1383
+ } else {
1384
+ console.log("\u5DF2\u540E\u9000");
1385
+ }
1339
1386
  } else {
1340
- console.log(`${msg.text}${location}`);
1387
+ console.error(`\u9519\u8BEF: ${response.error}`);
1388
+ process.exit(1);
1341
1389
  }
1342
1390
  }
1343
1391
  }
1344
-
1345
- // packages/cli/src/commands/errors.ts
1346
- async function errorsCommand(options = {}) {
1347
- const response = await sendCommand({
1348
- id: crypto.randomUUID(),
1349
- action: "errors",
1350
- errorsCommand: options.clear ? "clear" : "get",
1392
+ async function forwardCommand(options = {}) {
1393
+ await ensureDaemonRunning();
1394
+ const request = {
1395
+ id: generateId(),
1396
+ action: "forward",
1351
1397
  tabId: options.tabId
1352
- });
1398
+ };
1399
+ const response = await sendCommand(request);
1353
1400
  if (options.json) {
1354
- console.log(JSON.stringify(response));
1355
- return;
1356
- }
1357
- if (!response.success) {
1358
- throw new Error(response.error || "Errors command failed");
1359
- }
1360
- if (options.clear) {
1361
- console.log("\u5DF2\u6E05\u7A7A JS \u9519\u8BEF\u8BB0\u5F55");
1362
- return;
1363
- }
1364
- const errors = response.data?.jsErrors || [];
1365
- if (errors.length === 0) {
1366
- console.log("\u6CA1\u6709 JS \u9519\u8BEF");
1367
- console.log("\u63D0\u793A: errors \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1368
- return;
1369
- }
1370
- console.log(`JS \u9519\u8BEF (${errors.length} \u6761):
1371
- `);
1372
- for (const err of errors) {
1373
- console.log(`[ERROR] ${err.message}`);
1374
- if (err.url) {
1375
- console.log(` \u4F4D\u7F6E: ${err.url}:${err.lineNumber || 0}:${err.columnNumber || 0}`);
1376
- }
1377
- if (err.stackTrace) {
1378
- console.log(` \u5806\u6808:`);
1379
- console.log(err.stackTrace.split("\n").map((line) => ` ${line}`).join("\n"));
1401
+ console.log(JSON.stringify(response, null, 2));
1402
+ } else {
1403
+ if (response.success) {
1404
+ const url = response.data?.url ?? "";
1405
+ if (url) {
1406
+ console.log(`\u524D\u8FDB\u81F3: ${url}`);
1407
+ } else {
1408
+ console.log("\u5DF2\u524D\u8FDB");
1409
+ }
1410
+ } else {
1411
+ console.error(`\u9519\u8BEF: ${response.error}`);
1412
+ process.exit(1);
1380
1413
  }
1381
- console.log("");
1382
1414
  }
1383
1415
  }
1384
-
1385
- // packages/cli/src/commands/trace.ts
1386
- async function traceCommand(subCommand, options = {}) {
1387
- const response = await sendCommand({
1388
- id: crypto.randomUUID(),
1389
- action: "trace",
1390
- traceCommand: subCommand,
1416
+ async function refreshCommand(options = {}) {
1417
+ await ensureDaemonRunning();
1418
+ const request = {
1419
+ id: generateId(),
1420
+ action: "refresh",
1391
1421
  tabId: options.tabId
1392
- });
1422
+ };
1423
+ const response = await sendCommand(request);
1393
1424
  if (options.json) {
1394
- console.log(JSON.stringify(response));
1395
- return;
1396
- }
1397
- if (!response.success) {
1398
- throw new Error(response.error || "Trace command failed");
1425
+ console.log(JSON.stringify(response, null, 2));
1426
+ } else {
1427
+ if (response.success) {
1428
+ const title = response.data?.title ?? "";
1429
+ if (title) {
1430
+ console.log(`\u5DF2\u5237\u65B0: "${title}"`);
1431
+ } else {
1432
+ console.log("\u5DF2\u5237\u65B0\u9875\u9762");
1433
+ }
1434
+ } else {
1435
+ console.error(`\u9519\u8BEF: ${response.error}`);
1436
+ process.exit(1);
1437
+ }
1399
1438
  }
1400
- const data = response.data;
1401
- switch (subCommand) {
1402
- case "start": {
1403
- const status = data?.traceStatus;
1404
- console.log("\u5F00\u59CB\u5F55\u5236\u7528\u6237\u64CD\u4F5C");
1405
- console.log(`\u6807\u7B7E\u9875 ID: ${status?.tabId || "N/A"}`);
1406
- 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");
1407
- break;
1408
- }
1409
- case "stop": {
1410
- const events = data?.traceEvents || [];
1411
- const status = data?.traceStatus;
1412
- console.log(`\u5F55\u5236\u5B8C\u6210\uFF0C\u5171 ${events.length} \u4E2A\u4E8B\u4EF6
1413
- `);
1414
- if (events.length === 0) {
1415
- console.log("\u6CA1\u6709\u5F55\u5236\u5230\u4EFB\u4F55\u64CD\u4F5C");
1416
- break;
1417
- }
1418
- for (let i = 0; i < events.length; i++) {
1419
- const event = events[i];
1420
- const refStr = event.ref !== void 0 ? `@${event.ref}` : "";
1421
- switch (event.type) {
1422
- case "navigation":
1423
- console.log(`${i + 1}. \u5BFC\u822A\u5230: ${event.url}`);
1424
- break;
1425
- case "click":
1426
- console.log(`${i + 1}. \u70B9\u51FB ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
1427
- break;
1428
- case "fill":
1429
- console.log(`${i + 1}. \u586B\u5145 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
1430
- break;
1431
- case "select":
1432
- console.log(`${i + 1}. \u9009\u62E9 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
1433
- break;
1434
- case "check":
1435
- console.log(`${i + 1}. ${event.checked ? "\u52FE\u9009" : "\u53D6\u6D88\u52FE\u9009"} ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
1436
- break;
1437
- case "press":
1438
- console.log(`${i + 1}. \u6309\u952E ${event.key}`);
1439
- break;
1440
- case "scroll":
1441
- console.log(`${i + 1}. \u6EDA\u52A8 ${event.direction} ${event.pixels}px`);
1442
- break;
1443
- default:
1444
- console.log(`${i + 1}. ${event.type}`);
1439
+ }
1440
+
1441
+ // packages/cli/src/commands/check.ts
1442
+ function parseRef7(ref) {
1443
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1444
+ }
1445
+ async function checkCommand(ref, options = {}) {
1446
+ if (!ref) {
1447
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1448
+ }
1449
+ await ensureDaemonRunning();
1450
+ const parsedRef = parseRef7(ref);
1451
+ const request = {
1452
+ id: generateId(),
1453
+ action: "check",
1454
+ ref: parsedRef,
1455
+ tabId: options.tabId
1456
+ };
1457
+ const response = await sendCommand(request);
1458
+ if (options.json) {
1459
+ console.log(JSON.stringify(response, null, 2));
1460
+ } else {
1461
+ if (response.success) {
1462
+ const role = response.data?.role ?? "checkbox";
1463
+ const name = response.data?.name;
1464
+ const wasAlreadyChecked = response.data?.wasAlreadyChecked;
1465
+ if (wasAlreadyChecked) {
1466
+ if (name) {
1467
+ console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role} "${name}"`);
1468
+ } else {
1469
+ console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role}`);
1470
+ }
1471
+ } else {
1472
+ if (name) {
1473
+ console.log(`\u5DF2\u52FE\u9009: ${role} "${name}"`);
1474
+ } else {
1475
+ console.log(`\u5DF2\u52FE\u9009: ${role}`);
1445
1476
  }
1446
1477
  }
1447
- console.log(`
1448
- \u72B6\u6001: ${status?.recording ? "\u5F55\u5236\u4E2D" : "\u5DF2\u505C\u6B62"}`);
1449
- break;
1478
+ } else {
1479
+ console.error(`\u9519\u8BEF: ${response.error}`);
1480
+ process.exit(1);
1450
1481
  }
1451
- case "status": {
1452
- const status = data?.traceStatus;
1453
- if (status?.recording) {
1454
- console.log(`\u5F55\u5236\u4E2D (\u6807\u7B7E\u9875 ${status.tabId})`);
1455
- console.log(`\u5DF2\u5F55\u5236 ${status.eventCount} \u4E2A\u4E8B\u4EF6`);
1482
+ }
1483
+ }
1484
+ async function uncheckCommand(ref, options = {}) {
1485
+ if (!ref) {
1486
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1487
+ }
1488
+ await ensureDaemonRunning();
1489
+ const parsedRef = parseRef7(ref);
1490
+ const request = {
1491
+ id: generateId(),
1492
+ action: "uncheck",
1493
+ ref: parsedRef,
1494
+ tabId: options.tabId
1495
+ };
1496
+ const response = await sendCommand(request);
1497
+ if (options.json) {
1498
+ console.log(JSON.stringify(response, null, 2));
1499
+ } else {
1500
+ if (response.success) {
1501
+ const role = response.data?.role ?? "checkbox";
1502
+ const name = response.data?.name;
1503
+ const wasAlreadyUnchecked = response.data?.wasAlreadyUnchecked;
1504
+ if (wasAlreadyUnchecked) {
1505
+ if (name) {
1506
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role} "${name}"`);
1507
+ } else {
1508
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role}`);
1509
+ }
1456
1510
  } else {
1457
- console.log("\u672A\u5728\u5F55\u5236");
1511
+ if (name) {
1512
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role} "${name}"`);
1513
+ } else {
1514
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role}`);
1515
+ }
1458
1516
  }
1459
- break;
1517
+ } else {
1518
+ console.error(`\u9519\u8BEF: ${response.error}`);
1519
+ process.exit(1);
1460
1520
  }
1461
- default:
1462
- throw new Error(`\u672A\u77E5\u7684 trace \u5B50\u547D\u4EE4: ${subCommand}`);
1463
1521
  }
1464
1522
  }
1465
1523
 
1466
- // packages/cli/src/commands/fetch.ts
1467
- function matchTabOrigin(tabUrl, targetHostname) {
1468
- try {
1469
- const tabHostname = new URL(tabUrl).hostname;
1470
- return tabHostname === targetHostname || tabHostname.endsWith("." + targetHostname);
1471
- } catch {
1472
- return false;
1473
- }
1524
+ // packages/cli/src/commands/select.ts
1525
+ function parseRef8(ref) {
1526
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1474
1527
  }
1475
- async function ensureTabForOrigin(origin, hostname) {
1476
- const listReq = { id: generateId(), action: "tab_list" };
1477
- const listResp = await sendCommand(listReq);
1478
- if (listResp.success && listResp.data?.tabs) {
1479
- const matchingTab = listResp.data.tabs.find(
1480
- (tab) => matchTabOrigin(tab.url, hostname)
1481
- );
1482
- if (matchingTab) {
1483
- return matchingTab.tabId;
1484
- }
1485
- }
1486
- const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
1487
- if (!newResp.success) {
1488
- throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
1528
+ async function selectCommand(ref, value, options = {}) {
1529
+ if (!ref) {
1530
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1489
1531
  }
1490
- await new Promise((resolve2) => setTimeout(resolve2, 3e3));
1491
- return newResp.data?.tabId;
1492
- }
1493
- function buildFetchScript(url, options) {
1494
- const method = (options.method || "GET").toUpperCase();
1495
- const hasBody = options.body && method !== "GET" && method !== "HEAD";
1496
- let headersExpr = "{}";
1497
- if (options.headers) {
1498
- try {
1499
- JSON.parse(options.headers);
1500
- headersExpr = options.headers;
1501
- } catch {
1502
- throw new Error(`--headers must be valid JSON. Got: ${options.headers}`);
1503
- }
1532
+ if (value === void 0 || value === null) {
1533
+ throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
1504
1534
  }
1505
- return `(async () => {
1506
- try {
1507
- const resp = await fetch(${JSON.stringify(url)}, {
1508
- method: ${JSON.stringify(method)},
1509
- credentials: 'include',
1510
- headers: ${headersExpr}${hasBody ? `,
1511
- body: ${JSON.stringify(options.body)}` : ""}
1512
- });
1513
- const contentType = resp.headers.get('content-type') || '';
1514
- let body;
1515
- if (contentType.includes('application/json') && resp.status !== 204) {
1516
- try { body = await resp.json(); } catch { body = await resp.text(); }
1535
+ await ensureDaemonRunning();
1536
+ const parsedRef = parseRef8(ref);
1537
+ const request = {
1538
+ id: generateId(),
1539
+ action: "select",
1540
+ ref: parsedRef,
1541
+ value,
1542
+ tabId: options.tabId
1543
+ };
1544
+ const response = await sendCommand(request);
1545
+ if (options.json) {
1546
+ console.log(JSON.stringify(response, null, 2));
1547
+ } else {
1548
+ if (response.success) {
1549
+ const role = response.data?.role ?? "combobox";
1550
+ const name = response.data?.name;
1551
+ const selectedValue = response.data?.selectedValue;
1552
+ const selectedLabel = response.data?.selectedLabel;
1553
+ if (name) {
1554
+ console.log(`\u5DF2\u9009\u62E9: ${role} "${name}"`);
1517
1555
  } else {
1518
- body = await resp.text();
1556
+ console.log(`\u5DF2\u9009\u62E9: ${role}`);
1519
1557
  }
1520
- return JSON.stringify({
1521
- status: resp.status,
1522
- contentType,
1523
- body
1524
- });
1525
- } catch (e) {
1526
- return JSON.stringify({ error: e.message });
1558
+ if (selectedLabel && selectedLabel !== selectedValue) {
1559
+ console.log(`\u9009\u9879: "${selectedLabel}" (value="${selectedValue}")`);
1560
+ } else {
1561
+ console.log(`\u9009\u9879: "${selectedValue}"`);
1562
+ }
1563
+ } else {
1564
+ console.error(`\u9519\u8BEF: ${response.error}`);
1565
+ process.exit(1);
1527
1566
  }
1528
- })()`;
1567
+ }
1529
1568
  }
1530
- async function fetchCommand(url, options = {}) {
1531
- if (!url) {
1532
- throw new Error(
1533
- "\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"
1534
- );
1569
+
1570
+ // packages/cli/src/commands/eval.ts
1571
+ async function evalCommand(script, options = {}) {
1572
+ if (!script) {
1573
+ throw new Error("\u7F3A\u5C11 script \u53C2\u6570");
1535
1574
  }
1536
1575
  await ensureDaemonRunning();
1537
- const isAbsolute = url.startsWith("http://") || url.startsWith("https://");
1538
- let targetTabId = options.tabId;
1539
- if (isAbsolute) {
1540
- let origin;
1541
- let hostname;
1542
- try {
1543
- const parsed = new URL(url);
1544
- origin = parsed.origin;
1545
- hostname = parsed.hostname;
1546
- } catch {
1547
- throw new Error(`\u65E0\u6548\u7684 URL: ${url}`);
1548
- }
1549
- if (!targetTabId) {
1550
- targetTabId = await ensureTabForOrigin(origin, hostname);
1551
- }
1552
- }
1553
- const script = buildFetchScript(url, options);
1554
- const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
1555
- const evalResp = await sendCommand(evalReq);
1556
- if (!evalResp.success) {
1557
- throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
1576
+ const request = {
1577
+ id: generateId(),
1578
+ action: "eval",
1579
+ script,
1580
+ tabId: options.tabId
1581
+ };
1582
+ const response = await sendCommand(request);
1583
+ if (options.json) {
1584
+ console.log(JSON.stringify(response, null, 2));
1585
+ } else {
1586
+ if (response.success) {
1587
+ const result = response.data?.result;
1588
+ if (result !== void 0) {
1589
+ if (typeof result === "object" && result !== null) {
1590
+ console.log(JSON.stringify(result, null, 2));
1591
+ } else {
1592
+ console.log(result);
1593
+ }
1594
+ } else {
1595
+ console.log("undefined");
1596
+ }
1597
+ } else {
1598
+ console.error(`\u9519\u8BEF: ${response.error}`);
1599
+ process.exit(1);
1600
+ }
1558
1601
  }
1559
- const rawResult = evalResp.data?.result;
1560
- if (rawResult === void 0 || rawResult === null) {
1561
- throw new Error("Fetch \u672A\u8FD4\u56DE\u7ED3\u679C");
1602
+ }
1603
+
1604
+ // packages/cli/src/commands/tab.ts
1605
+ function parseTabSubcommand(args, rawArgv) {
1606
+ let tabId;
1607
+ if (rawArgv) {
1608
+ const idIdx = rawArgv.indexOf("--id");
1609
+ if (idIdx >= 0 && rawArgv[idIdx + 1]) {
1610
+ tabId = parseInt(rawArgv[idIdx + 1], 10);
1611
+ if (isNaN(tabId)) {
1612
+ throw new Error(`\u65E0\u6548\u7684 tabId: ${rawArgv[idIdx + 1]}`);
1613
+ }
1614
+ }
1562
1615
  }
1563
- let result;
1564
- try {
1565
- result = typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult;
1566
- } catch {
1567
- console.log(rawResult);
1568
- return;
1616
+ if (args.length === 0) {
1617
+ return { action: "tab_list" };
1569
1618
  }
1570
- if (result.error) {
1571
- throw new Error(`Fetch error: ${result.error}`);
1619
+ const first = args[0];
1620
+ if (first === "new") {
1621
+ return { action: "tab_new", url: args[1] };
1572
1622
  }
1573
- if (options.output) {
1574
- const { writeFileSync } = await import("fs");
1575
- const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
1576
- writeFileSync(options.output, content, "utf-8");
1577
- console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
1578
- return;
1623
+ if (first === "select") {
1624
+ if (tabId !== void 0) {
1625
+ return { action: "tab_select", tabId };
1626
+ }
1627
+ throw new Error("tab select \u9700\u8981 --id \u53C2\u6570\uFF0C\u7528\u6CD5\uFF1Abb-browser tab select --id <tabId>");
1579
1628
  }
1580
- if (typeof result.body === "object") {
1581
- console.log(JSON.stringify(result.body, null, 2));
1629
+ if (first === "close") {
1630
+ if (tabId !== void 0) {
1631
+ return { action: "tab_close", tabId };
1632
+ }
1633
+ const indexArg = args[1];
1634
+ if (indexArg !== void 0) {
1635
+ const index2 = parseInt(indexArg, 10);
1636
+ if (isNaN(index2) || index2 < 0) {
1637
+ throw new Error(`\u65E0\u6548\u7684\u6807\u7B7E\u9875\u7D22\u5F15: ${indexArg}`);
1638
+ }
1639
+ return { action: "tab_close", index: index2 };
1640
+ }
1641
+ return { action: "tab_close" };
1642
+ }
1643
+ const index = parseInt(first, 10);
1644
+ if (!isNaN(index) && index >= 0) {
1645
+ return { action: "tab_select", index };
1646
+ }
1647
+ throw new Error(`\u672A\u77E5\u7684 tab \u5B50\u547D\u4EE4: ${first}`);
1648
+ }
1649
+ function formatTabList(tabs, activeIndex) {
1650
+ const lines = [];
1651
+ lines.push(`\u6807\u7B7E\u9875\u5217\u8868\uFF08\u5171 ${tabs.length} \u4E2A\uFF0C\u5F53\u524D #${activeIndex}\uFF09\uFF1A`);
1652
+ for (const tab of tabs) {
1653
+ const prefix = tab.active ? "*" : " ";
1654
+ const title = tab.title || "(\u65E0\u6807\u9898)";
1655
+ lines.push(`${prefix} [${tab.index}] ${tab.url} - ${title}`);
1656
+ }
1657
+ return lines.join("\n");
1658
+ }
1659
+ async function tabCommand(args, options = {}) {
1660
+ await ensureDaemonRunning();
1661
+ const parsed = parseTabSubcommand(args, process.argv);
1662
+ const request = {
1663
+ id: generateId(),
1664
+ action: parsed.action,
1665
+ url: parsed.url,
1666
+ index: parsed.index,
1667
+ tabId: parsed.tabId
1668
+ };
1669
+ const response = await sendCommand(request);
1670
+ if (options.json) {
1671
+ console.log(JSON.stringify(response, null, 2));
1582
1672
  } else {
1583
- console.log(result.body);
1673
+ if (response.success) {
1674
+ switch (parsed.action) {
1675
+ case "tab_list": {
1676
+ const tabs = response.data?.tabs ?? [];
1677
+ const activeIndex = response.data?.activeIndex ?? 0;
1678
+ console.log(formatTabList(tabs, activeIndex));
1679
+ break;
1680
+ }
1681
+ case "tab_new": {
1682
+ const url = response.data?.url ?? "about:blank";
1683
+ console.log(`\u5DF2\u521B\u5EFA\u65B0\u6807\u7B7E\u9875: ${url}`);
1684
+ break;
1685
+ }
1686
+ case "tab_select": {
1687
+ const title = response.data?.title ?? "(\u65E0\u6807\u9898)";
1688
+ const url = response.data?.url ?? "";
1689
+ console.log(`\u5DF2\u5207\u6362\u5230\u6807\u7B7E\u9875 #${parsed.index}: ${title}`);
1690
+ console.log(` URL: ${url}`);
1691
+ break;
1692
+ }
1693
+ case "tab_close": {
1694
+ const closedTitle = response.data?.title ?? "(\u65E0\u6807\u9898)";
1695
+ console.log(`\u5DF2\u5173\u95ED\u6807\u7B7E\u9875: ${closedTitle}`);
1696
+ break;
1697
+ }
1698
+ }
1699
+ } else {
1700
+ console.error(`\u9519\u8BEF: ${response.error}`);
1701
+ process.exit(1);
1702
+ }
1584
1703
  }
1585
1704
  }
1586
1705
 
1587
- // packages/cli/src/commands/site.ts
1588
- import { readFileSync, readdirSync, existsSync as existsSync2, mkdirSync } from "fs";
1589
- import { join, relative } from "path";
1590
- import { homedir } from "os";
1591
- import { execSync } from "child_process";
1592
- var BB_DIR = join(homedir(), ".bb-browser");
1593
- var LOCAL_SITES_DIR = join(BB_DIR, "sites");
1594
- var COMMUNITY_SITES_DIR = join(BB_DIR, "bb-sites");
1595
- var COMMUNITY_REPO = "https://github.com/epiral/bb-sites.git";
1596
- function parseSiteMeta(filePath, source) {
1597
- let content;
1598
- try {
1599
- content = readFileSync(filePath, "utf-8");
1600
- } catch {
1601
- return null;
1706
+ // packages/cli/src/commands/frame.ts
1707
+ async function frameCommand(selector, options = {}) {
1708
+ if (!selector) {
1709
+ throw new Error("\u7F3A\u5C11 selector \u53C2\u6570");
1602
1710
  }
1603
- const sitesDir = source === "local" ? LOCAL_SITES_DIR : COMMUNITY_SITES_DIR;
1604
- const relPath = relative(sitesDir, filePath);
1605
- const defaultName = relPath.replace(/\.js$/, "").replace(/\\/g, "/");
1606
- const metaMatch = content.match(/\/\*\s*@meta\s*\n([\s\S]*?)\*\//);
1607
- if (metaMatch) {
1608
- try {
1609
- const metaJson = JSON.parse(metaMatch[1]);
1610
- return {
1611
- name: metaJson.name || defaultName,
1612
- description: metaJson.description || "",
1613
- domain: metaJson.domain || "",
1614
- args: metaJson.args || {},
1615
- capabilities: metaJson.capabilities,
1616
- readOnly: metaJson.readOnly,
1617
- example: metaJson.example,
1618
- filePath,
1619
- source
1620
- };
1621
- } catch {
1711
+ await ensureDaemonRunning();
1712
+ const request = {
1713
+ id: generateId(),
1714
+ action: "frame",
1715
+ selector,
1716
+ tabId: options.tabId
1717
+ };
1718
+ const response = await sendCommand(request);
1719
+ if (options.json) {
1720
+ console.log(JSON.stringify(response, null, 2));
1721
+ } else {
1722
+ if (response.success) {
1723
+ const frameInfo = response.data?.frameInfo;
1724
+ if (frameInfo?.url) {
1725
+ console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector} (${frameInfo.url})`);
1726
+ } else {
1727
+ console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector}`);
1728
+ }
1729
+ } else {
1730
+ console.error(`\u9519\u8BEF: ${response.error}`);
1731
+ process.exit(1);
1622
1732
  }
1623
1733
  }
1624
- const meta = {
1625
- name: defaultName,
1626
- description: "",
1627
- domain: "",
1628
- args: {},
1629
- filePath,
1630
- source
1734
+ }
1735
+ async function frameMainCommand(options = {}) {
1736
+ await ensureDaemonRunning();
1737
+ const request = {
1738
+ id: generateId(),
1739
+ action: "frame_main",
1740
+ tabId: options.tabId
1631
1741
  };
1632
- const tagPattern = /\/\/\s*@(\w+)[ \t]+(.*)/g;
1633
- let match;
1634
- while ((match = tagPattern.exec(content)) !== null) {
1635
- const [, key, value] = match;
1636
- switch (key) {
1637
- case "name":
1638
- meta.name = value.trim();
1639
- break;
1640
- case "description":
1641
- meta.description = value.trim();
1642
- break;
1643
- case "domain":
1644
- meta.domain = value.trim();
1645
- break;
1646
- case "args":
1647
- for (const arg of value.trim().split(/[,\s]+/).filter(Boolean)) {
1648
- meta.args[arg] = { required: true };
1649
- }
1650
- break;
1651
- case "example":
1652
- meta.example = value.trim();
1653
- break;
1742
+ const response = await sendCommand(request);
1743
+ if (options.json) {
1744
+ console.log(JSON.stringify(response, null, 2));
1745
+ } else {
1746
+ if (response.success) {
1747
+ console.log("\u5DF2\u8FD4\u56DE\u4E3B frame");
1748
+ } else {
1749
+ console.error(`\u9519\u8BEF: ${response.error}`);
1750
+ process.exit(1);
1654
1751
  }
1655
1752
  }
1656
- return meta;
1657
1753
  }
1658
- function scanSites(dir, source) {
1659
- if (!existsSync2(dir)) return [];
1660
- const sites = [];
1661
- function walk(currentDir) {
1662
- let entries;
1663
- try {
1664
- entries = readdirSync(currentDir, { withFileTypes: true });
1665
- } catch {
1666
- return;
1667
- }
1668
- for (const entry of entries) {
1669
- const fullPath = join(currentDir, entry.name);
1670
- if (entry.isDirectory() && !entry.name.startsWith(".")) {
1671
- walk(fullPath);
1672
- } else if (entry.isFile() && entry.name.endsWith(".js")) {
1673
- const meta = parseSiteMeta(fullPath, source);
1674
- if (meta) sites.push(meta);
1754
+
1755
+ // packages/cli/src/commands/dialog.ts
1756
+ async function dialogCommand(subCommand, promptText, options = {}) {
1757
+ if (!subCommand || !["accept", "dismiss"].includes(subCommand)) {
1758
+ throw new Error("\u8BF7\u4F7F\u7528 'dialog accept [text]' \u6216 'dialog dismiss'");
1759
+ }
1760
+ await ensureDaemonRunning();
1761
+ const request = {
1762
+ id: generateId(),
1763
+ action: "dialog",
1764
+ dialogResponse: subCommand,
1765
+ promptText: subCommand === "accept" ? promptText : void 0,
1766
+ tabId: options.tabId
1767
+ };
1768
+ const response = await sendCommand(request);
1769
+ if (options.json) {
1770
+ console.log(JSON.stringify(response, null, 2));
1771
+ } else {
1772
+ if (response.success) {
1773
+ const dialogInfo = response.data?.dialogInfo;
1774
+ if (dialogInfo) {
1775
+ const action = subCommand === "accept" ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
1776
+ console.log(`${action}\u5BF9\u8BDD\u6846\uFF08${dialogInfo.type}\uFF09: "${dialogInfo.message}"`);
1777
+ } else {
1778
+ console.log("\u5BF9\u8BDD\u6846\u5DF2\u5904\u7406");
1675
1779
  }
1780
+ } else {
1781
+ console.error(`\u9519\u8BEF: ${response.error}`);
1782
+ process.exit(1);
1676
1783
  }
1677
1784
  }
1678
- walk(dir);
1679
- return sites;
1680
- }
1681
- function getAllSites() {
1682
- const community = scanSites(COMMUNITY_SITES_DIR, "community");
1683
- const local = scanSites(LOCAL_SITES_DIR, "local");
1684
- const byName = /* @__PURE__ */ new Map();
1685
- for (const s of community) byName.set(s.name, s);
1686
- for (const s of local) byName.set(s.name, s);
1687
- return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
1688
1785
  }
1689
- function matchTabOrigin2(tabUrl, domain) {
1690
- try {
1691
- const tabOrigin = new URL(tabUrl).hostname;
1692
- return tabOrigin === domain || tabOrigin.endsWith("." + domain);
1693
- } catch {
1694
- return false;
1786
+
1787
+ // packages/cli/src/commands/network.ts
1788
+ async function networkCommand(subCommand, urlOrFilter, options = {}) {
1789
+ const response = await sendCommand({
1790
+ id: crypto.randomUUID(),
1791
+ action: "network",
1792
+ networkCommand: subCommand,
1793
+ url: subCommand === "route" || subCommand === "unroute" ? urlOrFilter : void 0,
1794
+ filter: subCommand === "requests" ? urlOrFilter : void 0,
1795
+ routeOptions: subCommand === "route" ? {
1796
+ abort: options.abort,
1797
+ body: options.body
1798
+ } : void 0,
1799
+ withBody: subCommand === "requests" ? options.withBody : void 0,
1800
+ tabId: options.tabId
1801
+ });
1802
+ if (options.json) {
1803
+ console.log(JSON.stringify(response));
1804
+ return;
1805
+ }
1806
+ if (!response.success) {
1807
+ throw new Error(response.error || "Network command failed");
1808
+ }
1809
+ const data = response.data;
1810
+ switch (subCommand) {
1811
+ case "requests": {
1812
+ const requests = data?.networkRequests || [];
1813
+ if (requests.length === 0) {
1814
+ console.log("\u6CA1\u6709\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
1815
+ console.log("\u63D0\u793A: \u4F7F\u7528 network requests \u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1816
+ } else {
1817
+ console.log(`\u7F51\u7EDC\u8BF7\u6C42 (${requests.length} \u6761):
1818
+ `);
1819
+ for (const req of requests) {
1820
+ const status = req.failed ? `FAILED (${req.failureReason})` : req.status ? `${req.status} ${req.statusText || ""}` : "pending";
1821
+ console.log(`${req.method} ${req.url}`);
1822
+ console.log(` \u7C7B\u578B: ${req.type}, \u72B6\u6001: ${status}`);
1823
+ if (options.withBody) {
1824
+ const requestHeaderCount = req.requestHeaders ? Object.keys(req.requestHeaders).length : 0;
1825
+ const responseHeaderCount = req.responseHeaders ? Object.keys(req.responseHeaders).length : 0;
1826
+ console.log(` \u8BF7\u6C42\u5934: ${requestHeaderCount}, \u54CD\u5E94\u5934: ${responseHeaderCount}`);
1827
+ if (req.requestBody !== void 0) {
1828
+ const preview = req.requestBody.length > 200 ? `${req.requestBody.slice(0, 200)}...` : req.requestBody;
1829
+ console.log(` \u8BF7\u6C42\u4F53: ${preview}`);
1830
+ }
1831
+ if (req.responseBody !== void 0) {
1832
+ const preview = req.responseBody.length > 200 ? `${req.responseBody.slice(0, 200)}...` : req.responseBody;
1833
+ console.log(` \u54CD\u5E94\u4F53: ${preview}`);
1834
+ }
1835
+ if (req.bodyError) {
1836
+ console.log(` Body\u9519\u8BEF: ${req.bodyError}`);
1837
+ }
1838
+ }
1839
+ console.log("");
1840
+ }
1841
+ }
1842
+ break;
1843
+ }
1844
+ case "route": {
1845
+ console.log(`\u5DF2\u6DFB\u52A0\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
1846
+ if (options.abort) {
1847
+ console.log(" \u884C\u4E3A: \u963B\u6B62\u8BF7\u6C42");
1848
+ } else if (options.body) {
1849
+ console.log(" \u884C\u4E3A: \u8FD4\u56DE mock \u6570\u636E");
1850
+ } else {
1851
+ console.log(" \u884C\u4E3A: \u7EE7\u7EED\u8BF7\u6C42");
1852
+ }
1853
+ console.log(`\u5F53\u524D\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
1854
+ break;
1855
+ }
1856
+ case "unroute": {
1857
+ if (urlOrFilter) {
1858
+ console.log(`\u5DF2\u79FB\u9664\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
1859
+ } else {
1860
+ console.log("\u5DF2\u79FB\u9664\u6240\u6709\u62E6\u622A\u89C4\u5219");
1861
+ }
1862
+ console.log(`\u5269\u4F59\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
1863
+ break;
1864
+ }
1865
+ case "clear": {
1866
+ console.log("\u5DF2\u6E05\u7A7A\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
1867
+ break;
1868
+ }
1869
+ default:
1870
+ throw new Error(`\u672A\u77E5\u7684 network \u5B50\u547D\u4EE4: ${subCommand}`);
1695
1871
  }
1696
1872
  }
1697
- function siteList(options) {
1698
- const sites = getAllSites();
1699
- if (sites.length === 0) {
1700
- console.log("\u672A\u627E\u5230\u4EFB\u4F55 site adapter\u3002");
1701
- console.log(" \u5B89\u88C5\u793E\u533A adapter: bb-browser site update");
1702
- console.log(` \u79C1\u6709 adapter \u76EE\u5F55: ${LOCAL_SITES_DIR}`);
1873
+
1874
+ // packages/cli/src/commands/console.ts
1875
+ async function consoleCommand(options = {}) {
1876
+ const response = await sendCommand({
1877
+ id: crypto.randomUUID(),
1878
+ action: "console",
1879
+ consoleCommand: options.clear ? "clear" : "get",
1880
+ tabId: options.tabId
1881
+ });
1882
+ if (options.json) {
1883
+ console.log(JSON.stringify(response));
1703
1884
  return;
1704
1885
  }
1705
- if (options.json) {
1706
- console.log(JSON.stringify(sites.map((s) => ({
1707
- name: s.name,
1708
- description: s.description,
1709
- domain: s.domain,
1710
- args: s.args,
1711
- source: s.source
1712
- })), null, 2));
1886
+ if (!response.success) {
1887
+ throw new Error(response.error || "Console command failed");
1888
+ }
1889
+ if (options.clear) {
1890
+ console.log("\u5DF2\u6E05\u7A7A\u63A7\u5236\u53F0\u6D88\u606F");
1713
1891
  return;
1714
1892
  }
1715
- const groups = /* @__PURE__ */ new Map();
1716
- for (const s of sites) {
1717
- const platform = s.name.split("/")[0];
1718
- if (!groups.has(platform)) groups.set(platform, []);
1719
- groups.get(platform).push(s);
1893
+ const messages = response.data?.consoleMessages || [];
1894
+ if (messages.length === 0) {
1895
+ console.log("\u6CA1\u6709\u63A7\u5236\u53F0\u6D88\u606F");
1896
+ console.log("\u63D0\u793A: console \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1897
+ return;
1720
1898
  }
1721
- for (const [platform, items] of groups) {
1722
- console.log(`
1723
- ${platform}/`);
1724
- for (const s of items) {
1725
- const cmd = s.name.split("/").slice(1).join("/");
1726
- const src = s.source === "local" ? " (local)" : "";
1727
- const desc = s.description ? ` - ${s.description}` : "";
1728
- console.log(` ${cmd.padEnd(20)}${desc}${src}`);
1899
+ console.log(`\u63A7\u5236\u53F0\u6D88\u606F (${messages.length} \u6761):
1900
+ `);
1901
+ const typeColors = {
1902
+ log: "",
1903
+ info: "[INFO]",
1904
+ warn: "[WARN]",
1905
+ error: "[ERROR]",
1906
+ debug: "[DEBUG]"
1907
+ };
1908
+ for (const msg of messages) {
1909
+ const prefix = typeColors[msg.type] || `[${msg.type.toUpperCase()}]`;
1910
+ const location = msg.url ? ` (${msg.url}${msg.lineNumber ? `:${msg.lineNumber}` : ""})` : "";
1911
+ if (prefix) {
1912
+ console.log(`${prefix} ${msg.text}${location}`);
1913
+ } else {
1914
+ console.log(`${msg.text}${location}`);
1729
1915
  }
1730
1916
  }
1731
- console.log();
1732
1917
  }
1733
- function siteSearch(query, options) {
1734
- const sites = getAllSites();
1735
- const q = query.toLowerCase();
1736
- const matches = sites.filter(
1737
- (s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q)
1738
- );
1739
- if (matches.length === 0) {
1740
- console.log(`\u672A\u627E\u5230\u5339\u914D "${query}" \u7684 adapter\u3002`);
1741
- console.log(" \u67E5\u770B\u6240\u6709: bb-browser site list");
1918
+
1919
+ // packages/cli/src/commands/errors.ts
1920
+ async function errorsCommand(options = {}) {
1921
+ const response = await sendCommand({
1922
+ id: crypto.randomUUID(),
1923
+ action: "errors",
1924
+ errorsCommand: options.clear ? "clear" : "get",
1925
+ tabId: options.tabId
1926
+ });
1927
+ if (options.json) {
1928
+ console.log(JSON.stringify(response));
1742
1929
  return;
1743
1930
  }
1744
- if (options.json) {
1745
- console.log(JSON.stringify(matches.map((s) => ({
1746
- name: s.name,
1747
- description: s.description,
1748
- domain: s.domain,
1749
- source: s.source
1750
- })), null, 2));
1931
+ if (!response.success) {
1932
+ throw new Error(response.error || "Errors command failed");
1933
+ }
1934
+ if (options.clear) {
1935
+ console.log("\u5DF2\u6E05\u7A7A JS \u9519\u8BEF\u8BB0\u5F55");
1751
1936
  return;
1752
1937
  }
1753
- for (const s of matches) {
1754
- const src = s.source === "local" ? " (local)" : "";
1755
- console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
1938
+ const errors = response.data?.jsErrors || [];
1939
+ if (errors.length === 0) {
1940
+ console.log("\u6CA1\u6709 JS \u9519\u8BEF");
1941
+ console.log("\u63D0\u793A: errors \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1942
+ return;
1756
1943
  }
1757
- }
1758
- function siteUpdate() {
1759
- mkdirSync(BB_DIR, { recursive: true });
1760
- if (existsSync2(join(COMMUNITY_SITES_DIR, ".git"))) {
1761
- console.log("\u66F4\u65B0\u793E\u533A site adapter \u5E93...");
1762
- try {
1763
- execSync("git pull --ff-only", { cwd: COMMUNITY_SITES_DIR, stdio: "pipe" });
1764
- console.log("\u66F4\u65B0\u5B8C\u6210\u3002");
1765
- } catch (e) {
1766
- console.error(`\u66F4\u65B0\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
1767
- console.error(" \u624B\u52A8\u4FEE\u590D: cd ~/.bb-browser/bb-sites && git pull");
1768
- process.exit(1);
1944
+ console.log(`JS \u9519\u8BEF (${errors.length} \u6761):
1945
+ `);
1946
+ for (const err of errors) {
1947
+ console.log(`[ERROR] ${err.message}`);
1948
+ if (err.url) {
1949
+ console.log(` \u4F4D\u7F6E: ${err.url}:${err.lineNumber || 0}:${err.columnNumber || 0}`);
1769
1950
  }
1770
- } else {
1771
- console.log(`\u514B\u9686\u793E\u533A adapter \u5E93: ${COMMUNITY_REPO}`);
1772
- try {
1773
- execSync(`git clone ${COMMUNITY_REPO} ${COMMUNITY_SITES_DIR}`, { stdio: "pipe" });
1774
- console.log("\u514B\u9686\u5B8C\u6210\u3002");
1775
- } catch (e) {
1776
- console.error(`\u514B\u9686\u5931\u8D25: ${e instanceof Error ? e.message : e}`);
1777
- console.error(` \u624B\u52A8\u4FEE\u590D: git clone ${COMMUNITY_REPO} ~/.bb-browser/bb-sites`);
1778
- process.exit(1);
1951
+ if (err.stackTrace) {
1952
+ console.log(` \u5806\u6808:`);
1953
+ console.log(err.stackTrace.split("\n").map((line) => ` ${line}`).join("\n"));
1779
1954
  }
1955
+ console.log("");
1780
1956
  }
1781
- const sites = scanSites(COMMUNITY_SITES_DIR, "community");
1782
- console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
1783
1957
  }
1784
- async function siteRun(name, args, options) {
1785
- const sites = getAllSites();
1786
- const site = sites.find((s) => s.name === name);
1787
- if (!site) {
1788
- const fuzzy = sites.filter((s) => s.name.includes(name));
1789
- console.error(`[error] site: "${name}" not found.`);
1790
- if (fuzzy.length > 0) {
1791
- console.error(" Did you mean:");
1792
- for (const s of fuzzy.slice(0, 5)) {
1793
- console.error(` bb-browser site ${s.name}`);
1958
+
1959
+ // packages/cli/src/commands/trace.ts
1960
+ async function traceCommand(subCommand, options = {}) {
1961
+ const response = await sendCommand({
1962
+ id: crypto.randomUUID(),
1963
+ action: "trace",
1964
+ traceCommand: subCommand,
1965
+ tabId: options.tabId
1966
+ });
1967
+ if (options.json) {
1968
+ console.log(JSON.stringify(response));
1969
+ return;
1970
+ }
1971
+ if (!response.success) {
1972
+ throw new Error(response.error || "Trace command failed");
1973
+ }
1974
+ const data = response.data;
1975
+ switch (subCommand) {
1976
+ case "start": {
1977
+ const status = data?.traceStatus;
1978
+ console.log("\u5F00\u59CB\u5F55\u5236\u7528\u6237\u64CD\u4F5C");
1979
+ console.log(`\u6807\u7B7E\u9875 ID: ${status?.tabId || "N/A"}`);
1980
+ 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");
1981
+ break;
1982
+ }
1983
+ case "stop": {
1984
+ const events = data?.traceEvents || [];
1985
+ const status = data?.traceStatus;
1986
+ console.log(`\u5F55\u5236\u5B8C\u6210\uFF0C\u5171 ${events.length} \u4E2A\u4E8B\u4EF6
1987
+ `);
1988
+ if (events.length === 0) {
1989
+ console.log("\u6CA1\u6709\u5F55\u5236\u5230\u4EFB\u4F55\u64CD\u4F5C");
1990
+ break;
1794
1991
  }
1795
- } else {
1796
- console.error(" Try: bb-browser site list");
1797
- console.error(" Or: bb-browser site update");
1992
+ for (let i = 0; i < events.length; i++) {
1993
+ const event = events[i];
1994
+ const refStr = event.ref !== void 0 ? `@${event.ref}` : "";
1995
+ switch (event.type) {
1996
+ case "navigation":
1997
+ console.log(`${i + 1}. \u5BFC\u822A\u5230: ${event.url}`);
1998
+ break;
1999
+ case "click":
2000
+ console.log(`${i + 1}. \u70B9\u51FB ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
2001
+ break;
2002
+ case "fill":
2003
+ console.log(`${i + 1}. \u586B\u5145 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
2004
+ break;
2005
+ case "select":
2006
+ console.log(`${i + 1}. \u9009\u62E9 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
2007
+ break;
2008
+ case "check":
2009
+ console.log(`${i + 1}. ${event.checked ? "\u52FE\u9009" : "\u53D6\u6D88\u52FE\u9009"} ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
2010
+ break;
2011
+ case "press":
2012
+ console.log(`${i + 1}. \u6309\u952E ${event.key}`);
2013
+ break;
2014
+ case "scroll":
2015
+ console.log(`${i + 1}. \u6EDA\u52A8 ${event.direction} ${event.pixels}px`);
2016
+ break;
2017
+ default:
2018
+ console.log(`${i + 1}. ${event.type}`);
2019
+ }
2020
+ }
2021
+ console.log(`
2022
+ \u72B6\u6001: ${status?.recording ? "\u5F55\u5236\u4E2D" : "\u5DF2\u505C\u6B62"}`);
2023
+ break;
2024
+ }
2025
+ case "status": {
2026
+ const status = data?.traceStatus;
2027
+ if (status?.recording) {
2028
+ console.log(`\u5F55\u5236\u4E2D (\u6807\u7B7E\u9875 ${status.tabId})`);
2029
+ console.log(`\u5DF2\u5F55\u5236 ${status.eventCount} \u4E2A\u4E8B\u4EF6`);
2030
+ } else {
2031
+ console.log("\u672A\u5728\u5F55\u5236");
2032
+ }
2033
+ break;
2034
+ }
2035
+ default:
2036
+ throw new Error(`\u672A\u77E5\u7684 trace \u5B50\u547D\u4EE4: ${subCommand}`);
2037
+ }
2038
+ }
2039
+
2040
+ // packages/cli/src/commands/fetch.ts
2041
+ function matchTabOrigin2(tabUrl, targetHostname) {
2042
+ try {
2043
+ const tabHostname = new URL(tabUrl).hostname;
2044
+ return tabHostname === targetHostname || tabHostname.endsWith("." + targetHostname);
2045
+ } catch {
2046
+ return false;
2047
+ }
2048
+ }
2049
+ async function ensureTabForOrigin(origin, hostname) {
2050
+ const listReq = { id: generateId(), action: "tab_list" };
2051
+ const listResp = await sendCommand(listReq);
2052
+ if (listResp.success && listResp.data?.tabs) {
2053
+ const matchingTab = listResp.data.tabs.find(
2054
+ (tab) => matchTabOrigin2(tab.url, hostname)
2055
+ );
2056
+ if (matchingTab) {
2057
+ return matchingTab.tabId;
1798
2058
  }
1799
- process.exit(1);
1800
2059
  }
1801
- const argNames = Object.keys(site.args);
1802
- const argMap = {};
1803
- const positionalArgs = [];
1804
- for (let i = 0; i < args.length; i++) {
1805
- if (args[i].startsWith("--")) {
1806
- const flagName = args[i].slice(2);
1807
- if (flagName in site.args && args[i + 1]) {
1808
- argMap[flagName] = args[i + 1];
1809
- i++;
1810
- }
1811
- } else {
1812
- positionalArgs.push(args[i]);
1813
- }
2060
+ const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
2061
+ if (!newResp.success) {
2062
+ throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
1814
2063
  }
1815
- let posIdx = 0;
1816
- for (const argName of argNames) {
1817
- if (!argMap[argName] && posIdx < positionalArgs.length) {
1818
- argMap[argName] = positionalArgs[posIdx++];
2064
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2065
+ return newResp.data?.tabId;
2066
+ }
2067
+ function buildFetchScript(url, options) {
2068
+ const method = (options.method || "GET").toUpperCase();
2069
+ const hasBody = options.body && method !== "GET" && method !== "HEAD";
2070
+ let headersExpr = "{}";
2071
+ if (options.headers) {
2072
+ try {
2073
+ JSON.parse(options.headers);
2074
+ headersExpr = options.headers;
2075
+ } catch {
2076
+ throw new Error(`--headers must be valid JSON. Got: ${options.headers}`);
1819
2077
  }
1820
2078
  }
1821
- for (const [argName, argDef] of Object.entries(site.args)) {
1822
- if (argDef.required && !argMap[argName]) {
1823
- console.error(`[error] site ${name}: missing required argument "${argName}".`);
1824
- const usage = argNames.map((a) => {
1825
- const def = site.args[a];
1826
- return def.required ? `<${a}>` : `[${a}]`;
1827
- }).join(" ");
1828
- console.error(` Usage: bb-browser site ${name} ${usage}`);
1829
- if (site.example) console.error(` Example: ${site.example}`);
1830
- process.exit(1);
2079
+ return `(async () => {
2080
+ try {
2081
+ const resp = await fetch(${JSON.stringify(url)}, {
2082
+ method: ${JSON.stringify(method)},
2083
+ credentials: 'include',
2084
+ headers: ${headersExpr}${hasBody ? `,
2085
+ body: ${JSON.stringify(options.body)}` : ""}
2086
+ });
2087
+ const contentType = resp.headers.get('content-type') || '';
2088
+ let body;
2089
+ if (contentType.includes('application/json') && resp.status !== 204) {
2090
+ try { body = await resp.json(); } catch { body = await resp.text(); }
2091
+ } else {
2092
+ body = await resp.text();
2093
+ }
2094
+ return JSON.stringify({
2095
+ status: resp.status,
2096
+ contentType,
2097
+ body
2098
+ });
2099
+ } catch (e) {
2100
+ return JSON.stringify({ error: e.message });
1831
2101
  }
2102
+ })()`;
2103
+ }
2104
+ async function fetchCommand(url, options = {}) {
2105
+ if (!url) {
2106
+ throw new Error(
2107
+ "\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"
2108
+ );
1832
2109
  }
1833
- const jsContent = readFileSync(site.filePath, "utf-8");
1834
- const jsBody = jsContent.replace(/\/\*\s*@meta[\s\S]*?\*\//, "").trim();
1835
- const argsJson = JSON.stringify(argMap);
1836
- const script = `(${jsBody})(${argsJson})`;
1837
2110
  await ensureDaemonRunning();
2111
+ const isAbsolute = url.startsWith("http://") || url.startsWith("https://");
1838
2112
  let targetTabId = options.tabId;
1839
- if (!targetTabId && site.domain) {
1840
- const listReq = { id: generateId(), action: "tab_list" };
1841
- const listResp = await sendCommand(listReq);
1842
- if (listResp.success && listResp.data?.tabs) {
1843
- const matchingTab = listResp.data.tabs.find(
1844
- (tab) => matchTabOrigin2(tab.url, site.domain)
1845
- );
1846
- if (matchingTab) {
1847
- targetTabId = matchingTab.tabId;
1848
- }
2113
+ if (isAbsolute) {
2114
+ let origin;
2115
+ let hostname;
2116
+ try {
2117
+ const parsed = new URL(url);
2118
+ origin = parsed.origin;
2119
+ hostname = parsed.hostname;
2120
+ } catch {
2121
+ throw new Error(`\u65E0\u6548\u7684 URL: ${url}`);
1849
2122
  }
1850
2123
  if (!targetTabId) {
1851
- const newResp = await sendCommand({
1852
- id: generateId(),
1853
- action: "tab_new",
1854
- url: `https://${site.domain}`
1855
- });
1856
- targetTabId = newResp.data?.tabId;
1857
- await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2124
+ targetTabId = await ensureTabForOrigin(origin, hostname);
1858
2125
  }
1859
2126
  }
2127
+ const script = buildFetchScript(url, options);
1860
2128
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
1861
2129
  const evalResp = await sendCommand(evalReq);
1862
2130
  if (!evalResp.success) {
1863
- const hint = site.domain ? `Open https://${site.domain} in your browser, make sure you are logged in, then retry.` : void 0;
1864
- if (options.json) {
1865
- console.log(JSON.stringify({ id: evalReq.id, success: false, error: evalResp.error || "eval failed", hint }));
1866
- } else {
1867
- console.error(`[error] site ${name}: ${evalResp.error || "eval failed"}`);
1868
- if (hint) console.error(` Hint: ${hint}`);
1869
- }
1870
- process.exit(1);
2131
+ throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
1871
2132
  }
1872
- const result = evalResp.data?.result;
1873
- if (result === void 0 || result === null) {
1874
- if (options.json) {
1875
- console.log(JSON.stringify({ id: evalReq.id, success: true, data: null }));
1876
- } else {
1877
- console.log("(no output)");
1878
- }
1879
- return;
2133
+ const rawResult = evalResp.data?.result;
2134
+ if (rawResult === void 0 || rawResult === null) {
2135
+ throw new Error("Fetch \u672A\u8FD4\u56DE\u7ED3\u679C");
1880
2136
  }
1881
- let parsed;
2137
+ let result;
1882
2138
  try {
1883
- parsed = typeof result === "string" ? JSON.parse(result) : result;
2139
+ result = typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult;
1884
2140
  } catch {
1885
- parsed = result;
2141
+ console.log(rawResult);
2142
+ return;
1886
2143
  }
1887
- if (typeof parsed === "object" && parsed !== null && "error" in parsed) {
1888
- const errObj = parsed;
1889
- const checkText = `${errObj.error} ${errObj.hint || ""}`;
1890
- const isAuthError = /401|403|unauthorized|forbidden|not.?logged|login.?required|sign.?in|auth/i.test(checkText);
1891
- const loginHint = isAuthError && site.domain ? `Please log in to https://${site.domain} in your browser first, then retry.` : void 0;
1892
- const hint = loginHint || errObj.hint;
1893
- 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>"`;
1894
- if (options.json) {
1895
- console.log(JSON.stringify({ id: evalReq.id, success: false, error: errObj.error, hint, reportHint }));
1896
- } else {
1897
- console.error(`[error] site ${name}: ${errObj.error}`);
1898
- if (hint) console.error(` Hint: ${hint}`);
1899
- console.error(` Report: gh issue create --repo epiral/bb-sites --title "[${name}] ..."`);
1900
- console.error(` or: bb-browser site github/issue-create epiral/bb-sites --title "[${name}] ..."`);
1901
- }
1902
- process.exit(1);
2144
+ if (result.error) {
2145
+ throw new Error(`Fetch error: ${result.error}`);
1903
2146
  }
1904
- if (options.json) {
1905
- console.log(JSON.stringify({ id: evalReq.id, success: true, data: parsed }));
2147
+ if (options.output) {
2148
+ const { writeFileSync } = await import("fs");
2149
+ const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
2150
+ writeFileSync(options.output, content, "utf-8");
2151
+ console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
2152
+ return;
2153
+ }
2154
+ if (typeof result.body === "object") {
2155
+ console.log(JSON.stringify(result.body, null, 2));
1906
2156
  } else {
1907
- console.log(JSON.stringify(parsed, null, 2));
2157
+ console.log(result.body);
1908
2158
  }
1909
2159
  }
1910
- async function siteCommand(args, options = {}) {
1911
- const subCommand = args[0];
1912
- if (!subCommand || subCommand === "--help" || subCommand === "-h") {
1913
- console.log(`bb-browser site - \u7F51\u7AD9 CLI \u5316\uFF08\u7BA1\u7406\u548C\u8FD0\u884C site adapter\uFF09
1914
-
1915
- \u7528\u6CD5:
1916
- bb-browser site list \u5217\u51FA\u6240\u6709\u53EF\u7528 adapter
1917
- bb-browser site search <query> \u641C\u7D22 adapter
1918
- bb-browser site <name> [args...] \u8FD0\u884C adapter\uFF08\u7B80\u5199\uFF09
1919
- bb-browser site run <name> [args...] \u8FD0\u884C adapter
1920
- bb-browser site update \u66F4\u65B0\u793E\u533A adapter \u5E93 (git clone/pull)
1921
2160
 
1922
- \u76EE\u5F55:
1923
- ${LOCAL_SITES_DIR} \u79C1\u6709 adapter\uFF08\u4F18\u5148\uFF09
1924
- ${COMMUNITY_SITES_DIR} \u793E\u533A adapter
1925
-
1926
- \u793A\u4F8B:
1927
- bb-browser site update
1928
- bb-browser site list
1929
- bb-browser site reddit/thread https://www.reddit.com/r/LocalLLaMA/comments/...
1930
- bb-browser site twitter/user yan5xu
1931
- bb-browser site search reddit
1932
-
1933
- \u521B\u5EFA\u65B0 adapter: bb-browser guide
1934
- \u62A5\u544A\u95EE\u9898: gh issue create --repo epiral/bb-sites --title "[adapter-name] \u63CF\u8FF0"
1935
- \u8D21\u732E\u793E\u533A: https://github.com/epiral/bb-sites`);
2161
+ // packages/cli/src/commands/history.ts
2162
+ async function historyCommand(subCommand, options = {}) {
2163
+ const response = await sendCommand({
2164
+ id: crypto.randomUUID(),
2165
+ action: "history",
2166
+ historyCommand: subCommand,
2167
+ text: options.query,
2168
+ ms: options.days
2169
+ });
2170
+ if (options.json) {
2171
+ console.log(JSON.stringify(response));
1936
2172
  return;
1937
2173
  }
2174
+ if (!response.success) {
2175
+ throw new Error(response.error || "History command failed");
2176
+ }
2177
+ const data = response.data;
1938
2178
  switch (subCommand) {
1939
- case "list":
1940
- siteList(options);
1941
- break;
1942
- case "search":
1943
- if (!args[1]) {
1944
- console.error("[error] site search: <query> is required.");
1945
- console.error(" Usage: bb-browser site search <query>");
1946
- process.exit(1);
2179
+ case "search": {
2180
+ const items = data?.historyItems || [];
2181
+ console.log(`\u627E\u5230 ${items.length} \u6761\u5386\u53F2\u8BB0\u5F55
2182
+ `);
2183
+ if (items.length === 0) {
2184
+ console.log("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u5386\u53F2\u8BB0\u5F55");
2185
+ break;
1947
2186
  }
1948
- siteSearch(args[1], options);
1949
- break;
1950
- case "update":
1951
- siteUpdate();
1952
- break;
1953
- case "run":
1954
- if (!args[1]) {
1955
- console.error("[error] site run: <name> is required.");
1956
- console.error(" Usage: bb-browser site run <name> [args...]");
1957
- console.error(" Try: bb-browser site list");
1958
- process.exit(1);
2187
+ for (let i = 0; i < items.length; i++) {
2188
+ const item = items[i];
2189
+ console.log(`${i + 1}. ${item.title || "(\u65E0\u6807\u9898)"}`);
2190
+ console.log(` ${item.url}`);
2191
+ console.log(` \u8BBF\u95EE\u6B21\u6570: ${item.visitCount}`);
1959
2192
  }
1960
- await siteRun(args[1], args.slice(2), options);
1961
2193
  break;
1962
- default:
1963
- if (subCommand.includes("/")) {
1964
- await siteRun(subCommand, args.slice(1), options);
1965
- } else {
1966
- console.error(`[error] site: unknown subcommand "${subCommand}".`);
1967
- console.error(" Available: list, search, run, update");
1968
- console.error(" Try: bb-browser site --help");
1969
- process.exit(1);
2194
+ }
2195
+ case "domains": {
2196
+ const domains = data?.historyDomains || [];
2197
+ console.log(`\u627E\u5230 ${domains.length} \u4E2A\u57DF\u540D
2198
+ `);
2199
+ if (domains.length === 0) {
2200
+ console.log("\u6CA1\u6709\u627E\u5230\u5386\u53F2\u8BB0\u5F55");
2201
+ break;
2202
+ }
2203
+ for (let i = 0; i < domains.length; i++) {
2204
+ const domain = domains[i];
2205
+ console.log(`${i + 1}. ${domain.domain}`);
2206
+ console.log(` \u8BBF\u95EE\u6B21\u6570: ${domain.visits}`);
1970
2207
  }
1971
2208
  break;
2209
+ }
2210
+ default:
2211
+ throw new Error(`\u672A\u77E5\u7684 history \u5B50\u547D\u4EE4: ${subCommand}`);
1972
2212
  }
1973
2213
  }
1974
2214
 
@@ -1977,74 +2217,56 @@ var VERSION = "0.3.0";
1977
2217
  var HELP_TEXT = `
1978
2218
  bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
1979
2219
 
2220
+ \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
2221
+ bb-browser site list \u67E5\u770B\u6240\u6709\u53EF\u7528\u547D\u4EE4
2222
+ bb-browser site twitter/search "AI" \u793A\u4F8B\uFF1A\u641C\u7D22\u63A8\u6587
2223
+ bb-browser site xueqiu/hot-stock 5 \u793A\u4F8B\uFF1A\u83B7\u53D6\u4EBA\u6C14\u80A1\u7968
2224
+
1980
2225
  \u7528\u6CD5\uFF1A
1981
2226
  bb-browser <command> [options]
1982
2227
 
1983
- \u7F51\u7AD9 CLI \u5316\uFF08\u628A\u4EFB\u4F55\u7F51\u7AD9\u53D8\u6210\u547D\u4EE4\u884C API\uFF09\uFF1A
1984
- site list \u5217\u51FA\u6240\u6709\u53EF\u7528 adapter\uFF0850+\uFF09
1985
- site search <q> \u641C\u7D22 adapter
1986
- site <name> [args] \u8FD0\u884C adapter\uFF08\u5982 site reddit/thread <url>\uFF09
1987
- site update \u66F4\u65B0\u793E\u533A adapter \u5E93
1988
- guide \u5982\u4F55\u521B\u5EFA\u65B0 adapter\uFF08\u5F00\u53D1\u6307\u5357\uFF09
1989
-
1990
- \u793A\u4F8B\uFF1A
1991
- bb-browser site twitter/search "claude code"
1992
- bb-browser site reddit/thread <url>
1993
- bb-browser site github/pr-create owner/repo --title "feat: ..."
1994
-
1995
- \u9875\u9762\u5BFC\u822A\uFF1A
1996
- open <url> [--tab] \u6253\u5F00\u6307\u5B9A URL\uFF08\u9ED8\u8BA4\u65B0 tab\uFF0C--tab current \u5F53\u524D tab\uFF09
1997
- back / forward \u540E\u9000 / \u524D\u8FDB
1998
- refresh \u5237\u65B0\u9875\u9762
1999
- close \u5173\u95ED\u5F53\u524D\u6807\u7B7E\u9875
2000
- tab \u5217\u51FA\u6240\u6709\u6807\u7B7E\u9875
2001
- tab new [url] \u65B0\u5EFA\u6807\u7B7E\u9875
2002
- tab <n> \u5207\u6362\u5230\u7B2C n \u4E2A\u6807\u7B7E\u9875\uFF08\u6309 index\uFF09
2003
- tab select --id <id> \u5207\u6362\u5230\u6307\u5B9A tabId \u7684\u6807\u7B7E\u9875
2004
- tab close [n|--id <id>] \u5173\u95ED\u6807\u7B7E\u9875
2005
- frame <selector> \u5207\u6362\u5230\u6307\u5B9A iframe
2006
- frame main \u8FD4\u56DE\u4E3B frame
2007
- wait <ms|@ref> \u7B49\u5F85\u65F6\u95F4\u6216\u5143\u7D20
2228
+ \u5F00\u59CB\u4F7F\u7528\uFF1A
2229
+ site recommend \u63A8\u8350\u4F60\u53EF\u80FD\u9700\u8981\u7684 adapter\uFF08\u57FA\u4E8E\u6D4F\u89C8\u5386\u53F2\uFF09
2230
+ site list \u5217\u51FA\u6240\u6709 adapter
2231
+ site info <name> \u67E5\u770B adapter \u7528\u6CD5\uFF08\u53C2\u6570\u3001\u8FD4\u56DE\u503C\u3001\u793A\u4F8B\uFF09
2232
+ site <name> [args] \u8FD0\u884C adapter
2233
+ site update \u66F4\u65B0\u793E\u533A adapter \u5E93
2234
+ guide \u5982\u4F55\u628A\u4EFB\u4F55\u7F51\u7AD9\u53D8\u6210 adapter
2008
2235
 
2009
- \u9875\u9762\u4EA4\u4E92\uFF1A
2010
- click <ref> \u70B9\u51FB\u5143\u7D20\uFF08ref \u5982 @5 \u6216 5\uFF09
2011
- hover <ref> \u60AC\u505C\u5728\u5143\u7D20\u4E0A
2012
- fill <ref> <text> \u586B\u5145\u8F93\u5165\u6846\uFF08\u6E05\u7A7A\u540E\u586B\u5165\uFF09
2013
- type <ref> <text> \u9010\u5B57\u7B26\u8F93\u5165\uFF08\u4E0D\u6E05\u7A7A\uFF09
2014
- check <ref> \u52FE\u9009\u590D\u9009\u6846
2015
- uncheck <ref> \u53D6\u6D88\u52FE\u9009\u590D\u9009\u6846
2016
- select <ref> <val> \u4E0B\u62C9\u6846\u9009\u62E9
2017
- press <key> \u53D1\u9001\u952E\u76D8\u6309\u952E\uFF08\u5982 Enter, Tab, Control+a\uFF09
2018
- scroll <dir> [px] \u6EDA\u52A8\u9875\u9762\uFF08up/down/left/right\uFF0C\u9ED8\u8BA4 300px\uFF09
2019
- dialog accept [text] \u63A5\u53D7\u5BF9\u8BDD\u6846
2020
- dialog dismiss \u62D2\u7EDD/\u5173\u95ED\u5BF9\u8BDD\u6846
2236
+ \u6D4F\u89C8\u5668\u64CD\u4F5C\uFF1A
2237
+ open <url> [--tab] \u6253\u5F00 URL
2238
+ snapshot [-i] [-c] [-d <n>] \u83B7\u53D6\u9875\u9762\u5FEB\u7167
2239
+ click <ref> \u70B9\u51FB\u5143\u7D20
2240
+ hover <ref> \u60AC\u505C\u5143\u7D20
2241
+ fill <ref> <text> \u586B\u5145\u8F93\u5165\u6846\uFF08\u6E05\u7A7A\u540E\u586B\u5165\uFF09
2242
+ type <ref> <text> \u9010\u5B57\u7B26\u8F93\u5165\uFF08\u4E0D\u6E05\u7A7A\uFF09
2243
+ check/uncheck <ref> \u52FE\u9009/\u53D6\u6D88\u590D\u9009\u6846
2244
+ select <ref> <val> \u4E0B\u62C9\u6846\u9009\u62E9
2245
+ press <key> \u53D1\u9001\u6309\u952E
2246
+ scroll <dir> [px] \u6EDA\u52A8\u9875\u9762
2021
2247
 
2022
2248
  \u9875\u9762\u4FE1\u606F\uFF1A
2023
- snapshot \u83B7\u53D6\u5F53\u524D\u9875\u9762\u5FEB\u7167\uFF08\u9ED8\u8BA4\u5B8C\u6574\u6811\uFF09
2024
- get text <ref> \u83B7\u53D6\u5143\u7D20\u6587\u672C
2025
- get url \u83B7\u53D6\u5F53\u524D\u9875\u9762 URL
2026
- get title \u83B7\u53D6\u9875\u9762\u6807\u9898
2027
- screenshot [path] \u622A\u53D6\u5F53\u524D\u9875\u9762
2028
- eval "<js>" \u6267\u884C JavaScript
2029
- fetch <url> \u5728\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u4E2D fetch\uFF08\u81EA\u52A8\u540C\u6E90\u8DEF\u7531\uFF0C\u5E26\u767B\u5F55\u6001\uFF09
2249
+ get text|url|title <ref> \u83B7\u53D6\u9875\u9762\u5185\u5BB9
2250
+ screenshot [path] \u622A\u56FE
2251
+ eval "<js>" \u6267\u884C JavaScript
2252
+ fetch <url> \u5E26\u767B\u5F55\u6001\u7684 HTTP \u8BF7\u6C42
2030
2253
 
2031
- \u7F51\u7EDC\u4E0E\u8C03\u8BD5\uFF1A
2032
- network requests [filter] \u67E5\u770B\u7F51\u7EDC\u8BF7\u6C42
2033
- network route <url> [--abort|--body <json>] \u62E6\u622A\u8BF7\u6C42
2034
- network unroute [url] \u79FB\u9664\u62E6\u622A\u89C4\u5219
2035
- network clear \u6E05\u7A7A\u8BF7\u6C42\u8BB0\u5F55
2036
- console [--clear] \u67E5\u770B/\u6E05\u7A7A\u63A7\u5236\u53F0\u6D88\u606F
2037
- errors [--clear] \u67E5\u770B/\u6E05\u7A7A JS \u9519\u8BEF
2038
- trace start|stop|status \u5F55\u5236\u7528\u6237\u64CD\u4F5C
2254
+ \u6807\u7B7E\u9875\uFF1A
2255
+ tab [list|new|close|<n>] \u7BA1\u7406\u6807\u7B7E\u9875
2039
2256
 
2040
- Daemon \u7BA1\u7406\uFF1A
2041
- daemon / start \u524D\u53F0\u542F\u52A8 Daemon
2042
- stop \u505C\u6B62 Daemon
2043
- status \u67E5\u770B Daemon \u72B6\u6001
2044
- reload \u91CD\u8F7D\u6269\u5C55\uFF08\u9700\u8981 CDP \u6A21\u5F0F\uFF09
2257
+ \u5BFC\u822A\uFF1A
2258
+ back / forward / refresh \u540E\u9000 / \u524D\u8FDB / \u5237\u65B0
2259
+
2260
+ \u8C03\u8BD5\uFF1A
2261
+ network requests [filter] \u67E5\u770B\u7F51\u7EDC\u8BF7\u6C42
2262
+ console [--clear] \u67E5\u770B/\u6E05\u7A7A\u63A7\u5236\u53F0
2263
+ errors [--clear] \u67E5\u770B/\u6E05\u7A7A JS \u9519\u8BEF
2264
+ trace start|stop|status \u5F55\u5236\u7528\u6237\u64CD\u4F5C
2265
+ history search|domains \u67E5\u770B\u6D4F\u89C8\u5386\u53F2
2045
2266
 
2046
2267
  \u9009\u9879\uFF1A
2047
2268
  --json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
2269
+ --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
2048
2270
  -i, --interactive \u53EA\u8F93\u51FA\u53EF\u4EA4\u4E92\u5143\u7D20\uFF08snapshot \u547D\u4EE4\uFF09
2049
2271
  -c, --compact \u79FB\u9664\u7A7A\u7ED3\u6784\u8282\u70B9\uFF08snapshot \u547D\u4EE4\uFF09
2050
2272
  -d, --depth <n> \u9650\u5236\u6811\u6DF1\u5EA6\uFF08snapshot \u547D\u4EE4\uFF09
@@ -2075,6 +2297,13 @@ function parseArgs(argv) {
2075
2297
  }
2076
2298
  if (arg === "--json") {
2077
2299
  result.flags.json = true;
2300
+ } else if (arg === "--jq") {
2301
+ skipNext = true;
2302
+ const nextIdx = args.indexOf(arg) + 1;
2303
+ if (nextIdx < args.length) {
2304
+ result.flags.jq = args[nextIdx];
2305
+ result.flags.json = true;
2306
+ }
2078
2307
  } else if (arg === "--help" || arg === "-h") {
2079
2308
  result.flags.help = true;
2080
2309
  } else if (arg === "--version" || arg === "-v") {
@@ -2095,6 +2324,12 @@ function parseArgs(argv) {
2095
2324
  if (nextIdx < args.length) {
2096
2325
  result.flags.selector = args[nextIdx];
2097
2326
  }
2327
+ } else if (arg === "--days") {
2328
+ skipNext = true;
2329
+ const nextIdx = args.indexOf(arg) + 1;
2330
+ if (nextIdx < args.length) {
2331
+ result.flags.days = parseInt(args[nextIdx], 10);
2332
+ }
2098
2333
  } else if (arg === "--id") {
2099
2334
  skipNext = true;
2100
2335
  } else if (arg === "--tab") {
@@ -2110,6 +2345,7 @@ function parseArgs(argv) {
2110
2345
  }
2111
2346
  async function main() {
2112
2347
  const parsed = parseArgs(process.argv);
2348
+ setJqExpression(parsed.flags.jq);
2113
2349
  const tabArgIdx = process.argv.indexOf("--tab");
2114
2350
  const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? parseInt(process.argv[tabArgIdx + 1], 10) : void 0;
2115
2351
  if (parsed.flags.version) {
@@ -2423,6 +2659,23 @@ async function main() {
2423
2659
  await traceCommand(subCmd, { json: parsed.flags.json, tabId: globalTabId });
2424
2660
  break;
2425
2661
  }
2662
+ case "history": {
2663
+ const subCmd = parsed.args[0];
2664
+ if (!subCmd || !["search", "domains"].includes(subCmd)) {
2665
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u6216\u65E0\u6548\u7684\u5B50\u547D\u4EE4");
2666
+ console.error("\u7528\u6CD5\uFF1Abb-browser history <search|domains> [query] [--days <n>]");
2667
+ console.error("\u793A\u4F8B\uFF1Abb-browser history search github");
2668
+ console.error(" bb-browser history domains --days 7");
2669
+ process.exit(1);
2670
+ }
2671
+ const query = parsed.args.slice(1).join(" ");
2672
+ await historyCommand(subCmd, {
2673
+ json: parsed.flags.json,
2674
+ days: parsed.flags.days || 30,
2675
+ query
2676
+ });
2677
+ break;
2678
+ }
2426
2679
  case "fetch": {
2427
2680
  const fetchUrl = parsed.args[0];
2428
2681
  if (!fetchUrl) {
@@ -2450,7 +2703,12 @@ async function main() {
2450
2703
  break;
2451
2704
  }
2452
2705
  case "site": {
2453
- await siteCommand(parsed.args, { json: parsed.flags.json, tabId: globalTabId });
2706
+ await siteCommand(parsed.args, {
2707
+ json: parsed.flags.json,
2708
+ jq: parsed.flags.jq,
2709
+ days: parsed.flags.days,
2710
+ tabId: globalTabId
2711
+ });
2454
2712
  break;
2455
2713
  }
2456
2714
  case "guide": {