bb-browser 0.4.5 → 0.5.1

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,609 @@ 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);
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
+ }
720
+ silentUpdate();
486
721
  }
487
- function parseRef6(ref) {
488
- return ref.startsWith("@") ? ref.slice(1) : ref;
722
+ function silentUpdate() {
723
+ const gitDir = join(COMMUNITY_SITES_DIR, ".git");
724
+ if (!existsSync2(gitDir)) return;
725
+ import("child_process").then(({ spawn: spawn3 }) => {
726
+ const child = spawn3("git", ["pull", "--ff-only"], {
727
+ cwd: COMMUNITY_SITES_DIR,
728
+ stdio: "ignore",
729
+ detached: true
730
+ });
731
+ child.unref();
732
+ }).catch(() => {
733
+ });
489
734
  }
490
- async function waitCommand(target, options = {}) {
491
- if (!target) {
492
- throw new Error("\u7F3A\u5C11\u7B49\u5F85\u76EE\u6807\u53C2\u6570");
735
+
736
+ // packages/cli/src/commands/open.ts
737
+ async function openCommand(url, options = {}) {
738
+ if (!url) {
739
+ throw new Error("\u7F3A\u5C11 URL \u53C2\u6570");
493
740
  }
494
741
  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
- };
742
+ let normalizedUrl = url;
743
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
744
+ normalizedUrl = "https://" + url;
745
+ }
746
+ const request = {
747
+ id: generateId(),
748
+ action: "open",
749
+ url: normalizedUrl
750
+ };
751
+ if (options.tab !== void 0) {
752
+ if (options.tab === "current") {
753
+ request.tabId = "current";
754
+ } else {
755
+ const tabId = parseInt(options.tab, 10);
756
+ if (isNaN(tabId)) {
757
+ throw new Error(`\u65E0\u6548\u7684 tabId: ${options.tab}`);
758
+ }
759
+ request.tabId = tabId;
760
+ }
514
761
  }
515
762
  const response = await sendCommand(request);
516
763
  if (options.json) {
517
764
  console.log(JSON.stringify(response, null, 2));
518
765
  } else {
519
766
  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`);
767
+ console.log(`\u5DF2\u6253\u5F00: ${response.data?.url ?? normalizedUrl}`);
768
+ if (response.data?.title) {
769
+ console.log(`\u6807\u9898: ${response.data.title}`);
770
+ }
771
+ if (response.data?.tabId) {
772
+ console.log(`Tab ID: ${response.data.tabId}`);
773
+ }
774
+ const siteHint = getSiteHintForDomain(normalizedUrl);
775
+ if (siteHint) {
776
+ console.log(`
777
+ \u{1F4A1} ${siteHint}`);
524
778
  }
525
779
  } else {
526
780
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -529,35 +783,16 @@ async function waitCommand(target, options = {}) {
529
783
  }
530
784
  }
531
785
 
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
- }
786
+ // packages/cli/src/commands/snapshot.ts
787
+ async function snapshotCommand(options = {}) {
551
788
  await ensureDaemonRunning();
552
- const { key, modifiers } = parseKey(keyString);
553
- if (!key) {
554
- throw new Error("\u65E0\u6548\u7684\u6309\u952E\u683C\u5F0F");
555
- }
556
789
  const request = {
557
790
  id: generateId(),
558
- action: "press",
559
- key,
560
- modifiers,
791
+ action: "snapshot",
792
+ interactive: options.interactive,
793
+ compact: options.compact,
794
+ maxDepth: options.maxDepth,
795
+ selector: options.selector,
561
796
  tabId: options.tabId
562
797
  };
563
798
  const response = await sendCommand(request);
@@ -565,8 +800,12 @@ async function pressCommand(keyString, options = {}) {
565
800
  console.log(JSON.stringify(response, null, 2));
566
801
  } else {
567
802
  if (response.success) {
568
- const displayKey = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
569
- console.log(`\u5DF2\u6309\u4E0B: ${displayKey}`);
803
+ console.log(`\u6807\u9898: ${response.data?.title ?? "(\u65E0\u6807\u9898)"}`);
804
+ console.log(`URL: ${response.data?.url ?? "(\u672A\u77E5)"}`);
805
+ if (response.data?.snapshotData?.snapshot) {
806
+ console.log("");
807
+ console.log(response.data.snapshotData.snapshot);
808
+ }
570
809
  } else {
571
810
  console.error(`\u9519\u8BEF: ${response.error}`);
572
811
  process.exit(1);
@@ -574,31 +813,20 @@ async function pressCommand(keyString, options = {}) {
574
813
  }
575
814
  }
576
815
 
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
- }
816
+ // packages/cli/src/commands/click.ts
817
+ function parseRef(ref) {
818
+ return ref.startsWith("@") ? ref.slice(1) : ref;
819
+ }
820
+ async function clickCommand(ref, options = {}) {
821
+ if (!ref) {
822
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
595
823
  }
596
824
  await ensureDaemonRunning();
825
+ const parsedRef = parseRef(ref);
597
826
  const request = {
598
827
  id: generateId(),
599
- action: "scroll",
600
- direction,
601
- pixels: pixelValue,
828
+ action: "click",
829
+ ref: parsedRef,
602
830
  tabId: options.tabId
603
831
  };
604
832
  const response = await sendCommand(request);
@@ -606,7 +834,13 @@ async function scrollCommand(direction, pixels, options = {}) {
606
834
  console.log(JSON.stringify(response, null, 2));
607
835
  } else {
608
836
  if (response.success) {
609
- console.log(`\u5DF2\u6EDA\u52A8: ${direction} ${pixelValue}px`);
837
+ const role = response.data?.role ?? "element";
838
+ const name = response.data?.name;
839
+ if (name) {
840
+ console.log(`\u5DF2\u70B9\u51FB: ${role} "${name}"`);
841
+ } else {
842
+ console.log(`\u5DF2\u70B9\u51FB: ${role}`);
843
+ }
610
844
  } else {
611
845
  console.error(`\u9519\u8BEF: ${response.error}`);
612
846
  process.exit(1);
@@ -614,188 +848,20 @@ async function scrollCommand(direction, pixels, options = {}) {
614
848
  }
615
849
  }
616
850
 
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
- }
851
+ // packages/cli/src/commands/hover.ts
852
+ function parseRef2(ref) {
853
+ return ref.startsWith("@") ? ref.slice(1) : ref;
684
854
  }
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);
855
+ async function hoverCommand(ref, options = {}) {
856
+ if (!ref) {
857
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
790
858
  }
791
- }
792
-
793
- // packages/cli/src/commands/nav.ts
794
- async function backCommand(options = {}) {
795
859
  await ensureDaemonRunning();
860
+ const parsedRef = parseRef2(ref);
796
861
  const request = {
797
862
  id: generateId(),
798
- action: "back",
863
+ action: "hover",
864
+ ref: parsedRef,
799
865
  tabId: options.tabId
800
866
  };
801
867
  const response = await sendCommand(request);
@@ -803,11 +869,12 @@ async function backCommand(options = {}) {
803
869
  console.log(JSON.stringify(response, null, 2));
804
870
  } else {
805
871
  if (response.success) {
806
- const url = response.data?.url ?? "";
807
- if (url) {
808
- console.log(`\u540E\u9000\u81F3: ${url}`);
872
+ const role = response.data?.role ?? "element";
873
+ const name = response.data?.name;
874
+ if (name) {
875
+ console.log(`\u5DF2\u60AC\u505C: ${role} "${name}"`);
809
876
  } else {
810
- console.log("\u5DF2\u540E\u9000");
877
+ console.log(`\u5DF2\u60AC\u505C: ${role}`);
811
878
  }
812
879
  } else {
813
880
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -815,11 +882,25 @@ async function backCommand(options = {}) {
815
882
  }
816
883
  }
817
884
  }
818
- async function forwardCommand(options = {}) {
885
+
886
+ // packages/cli/src/commands/fill.ts
887
+ function parseRef3(ref) {
888
+ return ref.startsWith("@") ? ref.slice(1) : ref;
889
+ }
890
+ async function fillCommand(ref, text, options = {}) {
891
+ if (!ref) {
892
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
893
+ }
894
+ if (text === void 0 || text === null) {
895
+ throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
896
+ }
819
897
  await ensureDaemonRunning();
898
+ const parsedRef = parseRef3(ref);
820
899
  const request = {
821
900
  id: generateId(),
822
- action: "forward",
901
+ action: "fill",
902
+ ref: parsedRef,
903
+ text,
823
904
  tabId: options.tabId
824
905
  };
825
906
  const response = await sendCommand(request);
@@ -827,36 +908,54 @@ async function forwardCommand(options = {}) {
827
908
  console.log(JSON.stringify(response, null, 2));
828
909
  } else {
829
910
  if (response.success) {
830
- const url = response.data?.url ?? "";
831
- if (url) {
832
- console.log(`\u524D\u8FDB\u81F3: ${url}`);
911
+ const role = response.data?.role ?? "element";
912
+ const name = response.data?.name;
913
+ if (name) {
914
+ console.log(`\u5DF2\u586B\u5145: ${role} "${name}"`);
833
915
  } else {
834
- console.log("\u5DF2\u524D\u8FDB");
916
+ console.log(`\u5DF2\u586B\u5145: ${role}`);
835
917
  }
918
+ console.log(`\u5185\u5BB9: "${text}"`);
836
919
  } else {
837
920
  console.error(`\u9519\u8BEF: ${response.error}`);
838
921
  process.exit(1);
839
922
  }
840
923
  }
841
924
  }
842
- async function refreshCommand(options = {}) {
843
- await ensureDaemonRunning();
844
- const request = {
845
- id: generateId(),
846
- action: "refresh",
847
- tabId: options.tabId
925
+
926
+ // packages/cli/src/commands/type.ts
927
+ function parseRef4(ref) {
928
+ return ref.startsWith("@") ? ref.slice(1) : ref;
929
+ }
930
+ async function typeCommand(ref, text, options = {}) {
931
+ if (!ref) {
932
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
933
+ }
934
+ if (text === void 0 || text === null) {
935
+ throw new Error("\u7F3A\u5C11 text \u53C2\u6570");
936
+ }
937
+ await ensureDaemonRunning();
938
+ const parsedRef = parseRef4(ref);
939
+ const request = {
940
+ id: generateId(),
941
+ action: "type",
942
+ ref: parsedRef,
943
+ text,
944
+ tabId: options.tabId
848
945
  };
849
946
  const response = await sendCommand(request);
850
947
  if (options.json) {
851
948
  console.log(JSON.stringify(response, null, 2));
852
949
  } else {
853
950
  if (response.success) {
854
- const title = response.data?.title ?? "";
855
- if (title) {
856
- console.log(`\u5DF2\u5237\u65B0: "${title}"`);
951
+ const role = response.data?.role ?? "element";
952
+ const name = response.data?.name;
953
+ if (name) {
954
+ console.log(`\u5DF2\u8F93\u5165: ${role} "${name}"`);
857
955
  } else {
858
- console.log("\u5DF2\u5237\u65B0\u9875\u9762");
956
+ console.log(`\u5DF2\u8F93\u5165: ${role}`);
859
957
  }
958
+ console.log(`\u5185\u5BB9: "${text}"`);
860
959
  } else {
861
960
  console.error(`\u9519\u8BEF: ${response.error}`);
862
961
  process.exit(1);
@@ -864,20 +963,12 @@ async function refreshCommand(options = {}) {
864
963
  }
865
964
  }
866
965
 
867
- // packages/cli/src/commands/check.ts
868
- function parseRef7(ref) {
869
- return ref.startsWith("@") ? ref.slice(1) : ref;
870
- }
871
- async function checkCommand(ref, options = {}) {
872
- if (!ref) {
873
- throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
874
- }
966
+ // packages/cli/src/commands/close.ts
967
+ async function closeCommand(options = {}) {
875
968
  await ensureDaemonRunning();
876
- const parsedRef = parseRef7(ref);
877
969
  const request = {
878
970
  id: generateId(),
879
- action: "check",
880
- ref: parsedRef,
971
+ action: "close",
881
972
  tabId: options.tabId
882
973
  };
883
974
  const response = await sendCommand(request);
@@ -885,21 +976,11 @@ async function checkCommand(ref, options = {}) {
885
976
  console.log(JSON.stringify(response, null, 2));
886
977
  } else {
887
978
  if (response.success) {
888
- const role = response.data?.role ?? "checkbox";
889
- 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
- }
979
+ const title = response.data?.title ?? "";
980
+ if (title) {
981
+ console.log(`\u5DF2\u5173\u95ED: "${title}"`);
897
982
  } else {
898
- if (name) {
899
- console.log(`\u5DF2\u52FE\u9009: ${role} "${name}"`);
900
- } else {
901
- console.log(`\u5DF2\u52FE\u9009: ${role}`);
902
- }
983
+ console.log("\u5DF2\u5173\u95ED\u5F53\u524D\u6807\u7B7E\u9875");
903
984
  }
904
985
  } else {
905
986
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -907,16 +988,21 @@ async function checkCommand(ref, options = {}) {
907
988
  }
908
989
  }
909
990
  }
910
- async function uncheckCommand(ref, options = {}) {
911
- if (!ref) {
912
- throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
991
+
992
+ // packages/cli/src/commands/get.ts
993
+ function parseRef5(ref) {
994
+ return ref.startsWith("@") ? ref.slice(1) : ref;
995
+ }
996
+ async function getCommand(attribute, ref, options = {}) {
997
+ if (attribute === "text" && !ref) {
998
+ throw new Error("get text \u9700\u8981 ref \u53C2\u6570\uFF0C\u5982: get text @5");
913
999
  }
914
1000
  await ensureDaemonRunning();
915
- const parsedRef = parseRef7(ref);
916
1001
  const request = {
917
1002
  id: generateId(),
918
- action: "uncheck",
919
- ref: parsedRef,
1003
+ action: "get",
1004
+ attribute,
1005
+ ref: ref ? parseRef5(ref) : void 0,
920
1006
  tabId: options.tabId
921
1007
  };
922
1008
  const response = await sendCommand(request);
@@ -924,22 +1010,8 @@ async function uncheckCommand(ref, options = {}) {
924
1010
  console.log(JSON.stringify(response, null, 2));
925
1011
  } else {
926
1012
  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
- }
936
- } 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
- }
942
- }
1013
+ const value = response.data?.value ?? "";
1014
+ console.log(value);
943
1015
  } else {
944
1016
  console.error(`\u9519\u8BEF: ${response.error}`);
945
1017
  process.exit(1);
@@ -947,44 +1019,96 @@ async function uncheckCommand(ref, options = {}) {
947
1019
  }
948
1020
  }
949
1021
 
950
- // packages/cli/src/commands/select.ts
951
- function parseRef8(ref) {
952
- return ref.startsWith("@") ? ref.slice(1) : ref;
1022
+ // packages/cli/src/commands/screenshot.ts
1023
+ import fs from "fs";
1024
+ import path from "path";
1025
+ import os from "os";
1026
+ function getDefaultPath() {
1027
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1028
+ const filename = `bb-screenshot-${timestamp}.png`;
1029
+ return path.join(os.tmpdir(), filename);
953
1030
  }
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");
1031
+ function saveBase64Image(dataUrl, filePath) {
1032
+ const base64Data = dataUrl.replace(/^data:image\/png;base64,/, "");
1033
+ const buffer = Buffer.from(base64Data, "base64");
1034
+ const dir = path.dirname(filePath);
1035
+ if (!fs.existsSync(dir)) {
1036
+ fs.mkdirSync(dir, { recursive: true });
960
1037
  }
1038
+ fs.writeFileSync(filePath, buffer);
1039
+ }
1040
+ async function screenshotCommand(outputPath, options = {}) {
961
1041
  await ensureDaemonRunning();
962
- const parsedRef = parseRef8(ref);
1042
+ const filePath = outputPath ? path.resolve(outputPath) : getDefaultPath();
963
1043
  const request = {
964
1044
  id: generateId(),
965
- action: "select",
966
- ref: parsedRef,
967
- value,
1045
+ action: "screenshot",
968
1046
  tabId: options.tabId
969
1047
  };
970
1048
  const response = await sendCommand(request);
1049
+ if (response.success && response.data?.dataUrl) {
1050
+ const dataUrl = response.data.dataUrl;
1051
+ saveBase64Image(dataUrl, filePath);
1052
+ if (options.json) {
1053
+ console.log(JSON.stringify({
1054
+ success: true,
1055
+ path: filePath,
1056
+ base64: dataUrl
1057
+ }, null, 2));
1058
+ } else {
1059
+ console.log(`\u622A\u56FE\u5DF2\u4FDD\u5B58: ${filePath}`);
1060
+ }
1061
+ } else {
1062
+ if (options.json) {
1063
+ console.log(JSON.stringify(response, null, 2));
1064
+ } else {
1065
+ console.error(`\u9519\u8BEF: ${response.error}`);
1066
+ }
1067
+ process.exit(1);
1068
+ }
1069
+ }
1070
+
1071
+ // packages/cli/src/commands/wait.ts
1072
+ function isTimeWait(target) {
1073
+ return /^\d+$/.test(target);
1074
+ }
1075
+ function parseRef6(ref) {
1076
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1077
+ }
1078
+ async function waitCommand(target, options = {}) {
1079
+ if (!target) {
1080
+ throw new Error("\u7F3A\u5C11\u7B49\u5F85\u76EE\u6807\u53C2\u6570");
1081
+ }
1082
+ await ensureDaemonRunning();
1083
+ let request;
1084
+ if (isTimeWait(target)) {
1085
+ const ms = parseInt(target, 10);
1086
+ request = {
1087
+ id: generateId(),
1088
+ action: "wait",
1089
+ waitType: "time",
1090
+ ms,
1091
+ tabId: options.tabId
1092
+ };
1093
+ } else {
1094
+ const ref = parseRef6(target);
1095
+ request = {
1096
+ id: generateId(),
1097
+ action: "wait",
1098
+ waitType: "element",
1099
+ ref,
1100
+ tabId: options.tabId
1101
+ };
1102
+ }
1103
+ const response = await sendCommand(request);
971
1104
  if (options.json) {
972
1105
  console.log(JSON.stringify(response, null, 2));
973
1106
  } else {
974
1107
  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}")`);
1108
+ if (isTimeWait(target)) {
1109
+ console.log(`\u5DF2\u7B49\u5F85 ${target}ms`);
986
1110
  } else {
987
- console.log(`\u9009\u9879: "${selectedValue}"`);
1111
+ console.log(`\u5143\u7D20 @${parseRef6(target)} \u5DF2\u51FA\u73B0`);
988
1112
  }
989
1113
  } else {
990
1114
  console.error(`\u9519\u8BEF: ${response.error}`);
@@ -993,16 +1117,35 @@ async function selectCommand(ref, value, options = {}) {
993
1117
  }
994
1118
  }
995
1119
 
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");
1120
+ // packages/cli/src/commands/press.ts
1121
+ function parseKey(keyString) {
1122
+ const parts = keyString.split("+");
1123
+ const modifierNames = ["Control", "Alt", "Shift", "Meta"];
1124
+ const modifiers = [];
1125
+ let key = "";
1126
+ for (const part of parts) {
1127
+ if (modifierNames.includes(part)) {
1128
+ modifiers.push(part);
1129
+ } else {
1130
+ key = part;
1131
+ }
1132
+ }
1133
+ return { key, modifiers };
1134
+ }
1135
+ async function pressCommand(keyString, options = {}) {
1136
+ if (!keyString) {
1137
+ throw new Error("\u7F3A\u5C11 key \u53C2\u6570");
1000
1138
  }
1001
1139
  await ensureDaemonRunning();
1140
+ const { key, modifiers } = parseKey(keyString);
1141
+ if (!key) {
1142
+ throw new Error("\u65E0\u6548\u7684\u6309\u952E\u683C\u5F0F");
1143
+ }
1002
1144
  const request = {
1003
1145
  id: generateId(),
1004
- action: "eval",
1005
- script,
1146
+ action: "press",
1147
+ key,
1148
+ modifiers,
1006
1149
  tabId: options.tabId
1007
1150
  };
1008
1151
  const response = await sendCommand(request);
@@ -1010,16 +1153,8 @@ async function evalCommand(script, options = {}) {
1010
1153
  console.log(JSON.stringify(response, null, 2));
1011
1154
  } else {
1012
1155
  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
- }
1020
- } else {
1021
- console.log("undefined");
1022
- }
1156
+ const displayKey = modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
1157
+ console.log(`\u5DF2\u6309\u4E0B: ${displayKey}`);
1023
1158
  } else {
1024
1159
  console.error(`\u9519\u8BEF: ${response.error}`);
1025
1160
  process.exit(1);
@@ -1027,101 +1162,39 @@ async function evalCommand(script, options = {}) {
1027
1162
  }
1028
1163
  }
1029
1164
 
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
- }
1040
- }
1041
- }
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] };
1165
+ // packages/cli/src/commands/scroll.ts
1166
+ var VALID_DIRECTIONS = ["up", "down", "left", "right"];
1167
+ var DEFAULT_PIXELS = 300;
1168
+ async function scrollCommand(direction, pixels, options = {}) {
1169
+ if (!direction) {
1170
+ throw new Error("\u7F3A\u5C11 direction \u53C2\u6570");
1048
1171
  }
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>");
1172
+ if (!VALID_DIRECTIONS.includes(direction)) {
1173
+ throw new Error(
1174
+ `\u65E0\u6548\u7684\u6EDA\u52A8\u65B9\u5411: ${direction}\uFF0C\u652F\u6301: ${VALID_DIRECTIONS.join(", ")}`
1175
+ );
1054
1176
  }
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 };
1177
+ let pixelValue = DEFAULT_PIXELS;
1178
+ if (pixels !== void 0) {
1179
+ pixelValue = parseInt(pixels, 10);
1180
+ if (isNaN(pixelValue) || pixelValue <= 0) {
1181
+ throw new Error(`\u65E0\u6548\u7684\u50CF\u7D20\u503C: ${pixels}`);
1066
1182
  }
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
1183
  }
1083
- return lines.join("\n");
1084
- }
1085
- async function tabCommand(args, options = {}) {
1086
1184
  await ensureDaemonRunning();
1087
- const parsed = parseTabSubcommand(args, process.argv);
1088
1185
  const request = {
1089
1186
  id: generateId(),
1090
- action: parsed.action,
1091
- url: parsed.url,
1092
- index: parsed.index,
1093
- tabId: parsed.tabId
1187
+ action: "scroll",
1188
+ direction,
1189
+ pixels: pixelValue,
1190
+ tabId: options.tabId
1094
1191
  };
1095
1192
  const response = await sendCommand(request);
1096
1193
  if (options.json) {
1097
1194
  console.log(JSON.stringify(response, null, 2));
1098
1195
  } else {
1099
1196
  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
- }
1197
+ console.log(`\u5DF2\u6EDA\u52A8: ${direction} ${pixelValue}px`);
1125
1198
  } else {
1126
1199
  console.error(`\u9519\u8BEF: ${response.error}`);
1127
1200
  process.exit(1);
@@ -1129,846 +1202,1027 @@ async function tabCommand(args, options = {}) {
1129
1202
  }
1130
1203
  }
1131
1204
 
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");
1205
+ // packages/cli/src/commands/daemon.ts
1206
+ import { spawn as spawn2 } from "child_process";
1207
+ async function daemonCommand(options = {}) {
1208
+ if (await isDaemonRunning()) {
1209
+ if (options.json) {
1210
+ console.log(JSON.stringify({ success: false, error: "Daemon \u5DF2\u5728\u8FD0\u884C" }));
1211
+ } else {
1212
+ console.log("Daemon \u5DF2\u5728\u8FD0\u884C");
1213
+ }
1214
+ return;
1215
+ }
1216
+ const daemonPath = getDaemonPath();
1217
+ const args = [daemonPath];
1218
+ if (options.host) {
1219
+ args.push("--host", options.host);
1136
1220
  }
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
1221
  if (options.json) {
1146
- console.log(JSON.stringify(response, null, 2));
1222
+ console.log(JSON.stringify({ success: true, message: "Daemon \u542F\u52A8\u4E2D..." }));
1147
1223
  } 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})`);
1224
+ console.log("Daemon \u542F\u52A8\u4E2D...");
1225
+ }
1226
+ await new Promise((resolve2, reject) => {
1227
+ const child = spawn2(process.execPath, args, {
1228
+ stdio: "inherit"
1229
+ });
1230
+ child.on("exit", (code) => {
1231
+ if (code && code !== 0) {
1232
+ reject(new Error(`Daemon exited with code ${code}`));
1152
1233
  } else {
1153
- console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector}`);
1234
+ resolve2();
1154
1235
  }
1236
+ });
1237
+ child.on("error", reject);
1238
+ });
1239
+ }
1240
+ async function stopCommand(options = {}) {
1241
+ if (!await isDaemonRunning()) {
1242
+ if (options.json) {
1243
+ console.log(JSON.stringify({ success: false, error: "Daemon \u672A\u8FD0\u884C" }));
1155
1244
  } else {
1156
- console.error(`\u9519\u8BEF: ${response.error}`);
1157
- process.exit(1);
1245
+ console.log("Daemon \u672A\u8FD0\u884C");
1158
1246
  }
1247
+ return;
1159
1248
  }
1160
- }
1161
- async function frameMainCommand(options = {}) {
1162
- await ensureDaemonRunning();
1163
- const request = {
1164
- id: generateId(),
1165
- action: "frame_main",
1166
- tabId: options.tabId
1167
- };
1168
- const response = await sendCommand(request);
1169
- if (options.json) {
1170
- console.log(JSON.stringify(response, null, 2));
1249
+ const stopped = await stopDaemon();
1250
+ if (stopped) {
1251
+ if (options.json) {
1252
+ console.log(JSON.stringify({ success: true, message: "Daemon \u5DF2\u505C\u6B62" }));
1253
+ } else {
1254
+ console.log("Daemon \u5DF2\u505C\u6B62");
1255
+ }
1171
1256
  } else {
1172
- if (response.success) {
1173
- console.log("\u5DF2\u8FD4\u56DE\u4E3B frame");
1257
+ if (options.json) {
1258
+ console.log(JSON.stringify({ success: false, error: "\u65E0\u6CD5\u505C\u6B62 Daemon" }));
1174
1259
  } else {
1175
- console.error(`\u9519\u8BEF: ${response.error}`);
1176
- process.exit(1);
1260
+ console.error("\u65E0\u6CD5\u505C\u6B62 Daemon");
1177
1261
  }
1262
+ process.exit(1);
1178
1263
  }
1179
1264
  }
1180
-
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'");
1185
- }
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);
1265
+ async function statusCommand(options = {}) {
1266
+ const running = await isDaemonRunning();
1195
1267
  if (options.json) {
1196
- console.log(JSON.stringify(response, null, 2));
1268
+ console.log(JSON.stringify({ running }));
1197
1269
  } 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}"`);
1203
- } else {
1204
- console.log("\u5BF9\u8BDD\u6846\u5DF2\u5904\u7406");
1205
- }
1206
- } else {
1207
- console.error(`\u9519\u8BEF: ${response.error}`);
1208
- process.exit(1);
1209
- }
1270
+ console.log(running ? "Daemon \u8FD0\u884C\u4E2D" : "Daemon \u672A\u8FD0\u884C");
1210
1271
  }
1211
1272
  }
1212
1273
 
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
- });
1228
- 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");
1234
- }
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;
1274
+ // packages/cli/src/commands/reload.ts
1275
+ import WebSocket from "ws";
1276
+ var EXTENSION_NAME = "bb-browser";
1277
+ async function reloadCommand(options = {}) {
1278
+ const port = options.port || 9222;
1279
+ try {
1280
+ const listRes = await fetch(`http://127.0.0.1:${port}/json/list`);
1281
+ if (!listRes.ok) {
1282
+ throw new Error(`CDP \u672A\u542F\u7528\u3002\u8BF7\u7528 --remote-debugging-port=${port} \u542F\u52A8 Chrome`);
1269
1283
  }
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;
1284
+ const list = await listRes.json();
1285
+ const extPage = list.find(
1286
+ (t) => t.type === "page" && t.url.includes("chrome://extensions")
1287
+ );
1288
+ if (!extPage) {
1289
+ throw new Error("\u8BF7\u5148\u6253\u5F00 chrome://extensions \u9875\u9762");
1281
1290
  }
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;
1291
+ const result = await new Promise((resolve2, reject) => {
1292
+ const ws = new WebSocket(extPage.webSocketDebuggerUrl);
1293
+ let resolved = false;
1294
+ const timeout = setTimeout(() => {
1295
+ if (!resolved) {
1296
+ resolved = true;
1297
+ ws.close();
1298
+ reject(new Error("CDP \u8FDE\u63A5\u8D85\u65F6"));
1299
+ }
1300
+ }, 1e4);
1301
+ ws.on("open", () => {
1302
+ const script = `
1303
+ (async function() {
1304
+ if (!chrome || !chrome.developerPrivate) {
1305
+ return { error: 'developerPrivate API not available' };
1306
+ }
1307
+
1308
+ try {
1309
+ const exts = await chrome.developerPrivate.getExtensionsInfo();
1310
+ const bbExt = exts.find(e => e.name === '${EXTENSION_NAME}');
1311
+
1312
+ if (!bbExt) {
1313
+ return { error: '${EXTENSION_NAME} \u6269\u5C55\u672A\u5B89\u88C5' };
1314
+ }
1315
+
1316
+ if (bbExt.state !== 'ENABLED') {
1317
+ return { error: '${EXTENSION_NAME} \u6269\u5C55\u5DF2\u7981\u7528' };
1318
+ }
1319
+
1320
+ await chrome.developerPrivate.reload(bbExt.id, {failQuietly: true});
1321
+ return { success: true, extensionId: bbExt.id };
1322
+ } catch (e) {
1323
+ return { error: e.message };
1324
+ }
1325
+ })()
1326
+ `;
1327
+ ws.send(JSON.stringify({
1328
+ id: 1,
1329
+ method: "Runtime.evaluate",
1330
+ params: {
1331
+ expression: script,
1332
+ awaitPromise: true,
1333
+ returnByValue: true
1334
+ }
1335
+ }));
1336
+ });
1337
+ ws.on("message", (data) => {
1338
+ const msg = JSON.parse(data.toString());
1339
+ if (msg.id === 1) {
1340
+ clearTimeout(timeout);
1341
+ resolved = true;
1342
+ ws.close();
1343
+ const value = msg.result?.result?.value;
1344
+ if (value?.success) {
1345
+ resolve2({
1346
+ success: true,
1347
+ message: "\u6269\u5C55\u5DF2\u91CD\u8F7D",
1348
+ extensionId: value.extensionId
1349
+ });
1350
+ } else if (value?.error) {
1351
+ reject(new Error(value.error));
1352
+ } else {
1353
+ reject(new Error(`\u91CD\u8F7D\u5931\u8D25: ${JSON.stringify(value)}`));
1354
+ }
1355
+ }
1356
+ });
1357
+ ws.on("error", (err) => {
1358
+ clearTimeout(timeout);
1359
+ if (!resolved) {
1360
+ resolved = true;
1361
+ reject(new Error(`CDP \u8FDE\u63A5\u5931\u8D25: ${err.message}`));
1362
+ }
1363
+ });
1364
+ });
1365
+ if (options.json) {
1366
+ console.log(JSON.stringify(result));
1367
+ } else {
1368
+ console.log(`${result.message} (${result.extensionId})`);
1290
1369
  }
1291
- case "clear": {
1292
- console.log("\u5DF2\u6E05\u7A7A\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
1293
- break;
1370
+ } catch (error) {
1371
+ const message = error instanceof Error ? error.message : String(error);
1372
+ if (options.json) {
1373
+ console.log(JSON.stringify({ success: false, error: message }));
1374
+ } else {
1375
+ console.error(`\u9519\u8BEF: ${message}`);
1294
1376
  }
1295
- default:
1296
- throw new Error(`\u672A\u77E5\u7684 network \u5B50\u547D\u4EE4: ${subCommand}`);
1377
+ process.exit(1);
1297
1378
  }
1298
1379
  }
1299
1380
 
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",
1381
+ // packages/cli/src/commands/nav.ts
1382
+ async function backCommand(options = {}) {
1383
+ await ensureDaemonRunning();
1384
+ const request = {
1385
+ id: generateId(),
1386
+ action: "back",
1306
1387
  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
1388
  };
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}`);
1389
+ const response = await sendCommand(request);
1390
+ if (options.json) {
1391
+ console.log(JSON.stringify(response, null, 2));
1392
+ } else {
1393
+ if (response.success) {
1394
+ const url = response.data?.url ?? "";
1395
+ if (url) {
1396
+ console.log(`\u540E\u9000\u81F3: ${url}`);
1397
+ } else {
1398
+ console.log("\u5DF2\u540E\u9000");
1399
+ }
1339
1400
  } else {
1340
- console.log(`${msg.text}${location}`);
1401
+ console.error(`\u9519\u8BEF: ${response.error}`);
1402
+ process.exit(1);
1341
1403
  }
1342
1404
  }
1343
1405
  }
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",
1406
+ async function forwardCommand(options = {}) {
1407
+ await ensureDaemonRunning();
1408
+ const request = {
1409
+ id: generateId(),
1410
+ action: "forward",
1351
1411
  tabId: options.tabId
1352
- });
1412
+ };
1413
+ const response = await sendCommand(request);
1353
1414
  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"));
1415
+ console.log(JSON.stringify(response, null, 2));
1416
+ } else {
1417
+ if (response.success) {
1418
+ const url = response.data?.url ?? "";
1419
+ if (url) {
1420
+ console.log(`\u524D\u8FDB\u81F3: ${url}`);
1421
+ } else {
1422
+ console.log("\u5DF2\u524D\u8FDB");
1423
+ }
1424
+ } else {
1425
+ console.error(`\u9519\u8BEF: ${response.error}`);
1426
+ process.exit(1);
1380
1427
  }
1381
- console.log("");
1382
1428
  }
1383
1429
  }
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,
1430
+ async function refreshCommand(options = {}) {
1431
+ await ensureDaemonRunning();
1432
+ const request = {
1433
+ id: generateId(),
1434
+ action: "refresh",
1391
1435
  tabId: options.tabId
1392
- });
1436
+ };
1437
+ const response = await sendCommand(request);
1393
1438
  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");
1399
- }
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;
1439
+ console.log(JSON.stringify(response, null, 2));
1440
+ } else {
1441
+ if (response.success) {
1442
+ const title = response.data?.title ?? "";
1443
+ if (title) {
1444
+ console.log(`\u5DF2\u5237\u65B0: "${title}"`);
1445
+ } else {
1446
+ console.log("\u5DF2\u5237\u65B0\u9875\u9762");
1417
1447
  }
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}`);
1448
+ } else {
1449
+ console.error(`\u9519\u8BEF: ${response.error}`);
1450
+ process.exit(1);
1451
+ }
1452
+ }
1453
+ }
1454
+
1455
+ // packages/cli/src/commands/check.ts
1456
+ function parseRef7(ref) {
1457
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1458
+ }
1459
+ async function checkCommand(ref, options = {}) {
1460
+ if (!ref) {
1461
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1462
+ }
1463
+ await ensureDaemonRunning();
1464
+ const parsedRef = parseRef7(ref);
1465
+ const request = {
1466
+ id: generateId(),
1467
+ action: "check",
1468
+ ref: parsedRef,
1469
+ tabId: options.tabId
1470
+ };
1471
+ const response = await sendCommand(request);
1472
+ if (options.json) {
1473
+ console.log(JSON.stringify(response, null, 2));
1474
+ } else {
1475
+ if (response.success) {
1476
+ const role = response.data?.role ?? "checkbox";
1477
+ const name = response.data?.name;
1478
+ const wasAlreadyChecked = response.data?.wasAlreadyChecked;
1479
+ if (wasAlreadyChecked) {
1480
+ if (name) {
1481
+ console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role} "${name}"`);
1482
+ } else {
1483
+ console.log(`\u5DF2\u52FE\u9009\uFF08\u4E4B\u524D\u5DF2\u52FE\u9009\uFF09: ${role}`);
1484
+ }
1485
+ } else {
1486
+ if (name) {
1487
+ console.log(`\u5DF2\u52FE\u9009: ${role} "${name}"`);
1488
+ } else {
1489
+ console.log(`\u5DF2\u52FE\u9009: ${role}`);
1445
1490
  }
1446
1491
  }
1447
- console.log(`
1448
- \u72B6\u6001: ${status?.recording ? "\u5F55\u5236\u4E2D" : "\u5DF2\u505C\u6B62"}`);
1449
- break;
1492
+ } else {
1493
+ console.error(`\u9519\u8BEF: ${response.error}`);
1494
+ process.exit(1);
1450
1495
  }
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`);
1496
+ }
1497
+ }
1498
+ async function uncheckCommand(ref, options = {}) {
1499
+ if (!ref) {
1500
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1501
+ }
1502
+ await ensureDaemonRunning();
1503
+ const parsedRef = parseRef7(ref);
1504
+ const request = {
1505
+ id: generateId(),
1506
+ action: "uncheck",
1507
+ ref: parsedRef,
1508
+ tabId: options.tabId
1509
+ };
1510
+ const response = await sendCommand(request);
1511
+ if (options.json) {
1512
+ console.log(JSON.stringify(response, null, 2));
1513
+ } else {
1514
+ if (response.success) {
1515
+ const role = response.data?.role ?? "checkbox";
1516
+ const name = response.data?.name;
1517
+ const wasAlreadyUnchecked = response.data?.wasAlreadyUnchecked;
1518
+ if (wasAlreadyUnchecked) {
1519
+ if (name) {
1520
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role} "${name}"`);
1521
+ } else {
1522
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009\uFF08\u4E4B\u524D\u672A\u52FE\u9009\uFF09: ${role}`);
1523
+ }
1456
1524
  } else {
1457
- console.log("\u672A\u5728\u5F55\u5236");
1525
+ if (name) {
1526
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role} "${name}"`);
1527
+ } else {
1528
+ console.log(`\u5DF2\u53D6\u6D88\u52FE\u9009: ${role}`);
1529
+ }
1458
1530
  }
1459
- break;
1531
+ } else {
1532
+ console.error(`\u9519\u8BEF: ${response.error}`);
1533
+ process.exit(1);
1460
1534
  }
1461
- default:
1462
- throw new Error(`\u672A\u77E5\u7684 trace \u5B50\u547D\u4EE4: ${subCommand}`);
1463
1535
  }
1464
1536
  }
1465
1537
 
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
- }
1538
+ // packages/cli/src/commands/select.ts
1539
+ function parseRef8(ref) {
1540
+ return ref.startsWith("@") ? ref.slice(1) : ref;
1474
1541
  }
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}`);
1542
+ async function selectCommand(ref, value, options = {}) {
1543
+ if (!ref) {
1544
+ throw new Error("\u7F3A\u5C11 ref \u53C2\u6570");
1489
1545
  }
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
- }
1546
+ if (value === void 0 || value === null) {
1547
+ throw new Error("\u7F3A\u5C11 value \u53C2\u6570");
1504
1548
  }
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(); }
1549
+ await ensureDaemonRunning();
1550
+ const parsedRef = parseRef8(ref);
1551
+ const request = {
1552
+ id: generateId(),
1553
+ action: "select",
1554
+ ref: parsedRef,
1555
+ value,
1556
+ tabId: options.tabId
1557
+ };
1558
+ const response = await sendCommand(request);
1559
+ if (options.json) {
1560
+ console.log(JSON.stringify(response, null, 2));
1561
+ } else {
1562
+ if (response.success) {
1563
+ const role = response.data?.role ?? "combobox";
1564
+ const name = response.data?.name;
1565
+ const selectedValue = response.data?.selectedValue;
1566
+ const selectedLabel = response.data?.selectedLabel;
1567
+ if (name) {
1568
+ console.log(`\u5DF2\u9009\u62E9: ${role} "${name}"`);
1517
1569
  } else {
1518
- body = await resp.text();
1570
+ console.log(`\u5DF2\u9009\u62E9: ${role}`);
1519
1571
  }
1520
- return JSON.stringify({
1521
- status: resp.status,
1522
- contentType,
1523
- body
1524
- });
1525
- } catch (e) {
1526
- return JSON.stringify({ error: e.message });
1572
+ if (selectedLabel && selectedLabel !== selectedValue) {
1573
+ console.log(`\u9009\u9879: "${selectedLabel}" (value="${selectedValue}")`);
1574
+ } else {
1575
+ console.log(`\u9009\u9879: "${selectedValue}"`);
1576
+ }
1577
+ } else {
1578
+ console.error(`\u9519\u8BEF: ${response.error}`);
1579
+ process.exit(1);
1527
1580
  }
1528
- })()`;
1581
+ }
1529
1582
  }
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
- );
1583
+
1584
+ // packages/cli/src/commands/eval.ts
1585
+ async function evalCommand(script, options = {}) {
1586
+ if (!script) {
1587
+ throw new Error("\u7F3A\u5C11 script \u53C2\u6570");
1535
1588
  }
1536
1589
  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}`);
1590
+ const request = {
1591
+ id: generateId(),
1592
+ action: "eval",
1593
+ script,
1594
+ tabId: options.tabId
1595
+ };
1596
+ const response = await sendCommand(request);
1597
+ if (options.json) {
1598
+ console.log(JSON.stringify(response, null, 2));
1599
+ } else {
1600
+ if (response.success) {
1601
+ const result = response.data?.result;
1602
+ if (result !== void 0) {
1603
+ if (typeof result === "object" && result !== null) {
1604
+ console.log(JSON.stringify(result, null, 2));
1605
+ } else {
1606
+ console.log(result);
1607
+ }
1608
+ } else {
1609
+ console.log("undefined");
1610
+ }
1611
+ } else {
1612
+ console.error(`\u9519\u8BEF: ${response.error}`);
1613
+ process.exit(1);
1548
1614
  }
1549
- if (!targetTabId) {
1550
- targetTabId = await ensureTabForOrigin(origin, hostname);
1615
+ }
1616
+ }
1617
+
1618
+ // packages/cli/src/commands/tab.ts
1619
+ function parseTabSubcommand(args, rawArgv) {
1620
+ let tabId;
1621
+ if (rawArgv) {
1622
+ const idIdx = rawArgv.indexOf("--id");
1623
+ if (idIdx >= 0 && rawArgv[idIdx + 1]) {
1624
+ tabId = parseInt(rawArgv[idIdx + 1], 10);
1625
+ if (isNaN(tabId)) {
1626
+ throw new Error(`\u65E0\u6548\u7684 tabId: ${rawArgv[idIdx + 1]}`);
1627
+ }
1551
1628
  }
1552
1629
  }
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}`);
1630
+ if (args.length === 0) {
1631
+ return { action: "tab_list" };
1558
1632
  }
1559
- const rawResult = evalResp.data?.result;
1560
- if (rawResult === void 0 || rawResult === null) {
1561
- throw new Error("Fetch \u672A\u8FD4\u56DE\u7ED3\u679C");
1633
+ const first = args[0];
1634
+ if (first === "new") {
1635
+ return { action: "tab_new", url: args[1] };
1562
1636
  }
1563
- let result;
1564
- try {
1565
- result = typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult;
1566
- } catch {
1567
- console.log(rawResult);
1568
- return;
1637
+ if (first === "select") {
1638
+ if (tabId !== void 0) {
1639
+ return { action: "tab_select", tabId };
1640
+ }
1641
+ throw new Error("tab select \u9700\u8981 --id \u53C2\u6570\uFF0C\u7528\u6CD5\uFF1Abb-browser tab select --id <tabId>");
1569
1642
  }
1570
- if (result.error) {
1571
- throw new Error(`Fetch error: ${result.error}`);
1643
+ if (first === "close") {
1644
+ if (tabId !== void 0) {
1645
+ return { action: "tab_close", tabId };
1646
+ }
1647
+ const indexArg = args[1];
1648
+ if (indexArg !== void 0) {
1649
+ const index2 = parseInt(indexArg, 10);
1650
+ if (isNaN(index2) || index2 < 0) {
1651
+ throw new Error(`\u65E0\u6548\u7684\u6807\u7B7E\u9875\u7D22\u5F15: ${indexArg}`);
1652
+ }
1653
+ return { action: "tab_close", index: index2 };
1654
+ }
1655
+ return { action: "tab_close" };
1572
1656
  }
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;
1657
+ const index = parseInt(first, 10);
1658
+ if (!isNaN(index) && index >= 0) {
1659
+ return { action: "tab_select", index };
1579
1660
  }
1580
- if (typeof result.body === "object") {
1581
- console.log(JSON.stringify(result.body, null, 2));
1661
+ throw new Error(`\u672A\u77E5\u7684 tab \u5B50\u547D\u4EE4: ${first}`);
1662
+ }
1663
+ function formatTabList(tabs, activeIndex) {
1664
+ const lines = [];
1665
+ lines.push(`\u6807\u7B7E\u9875\u5217\u8868\uFF08\u5171 ${tabs.length} \u4E2A\uFF0C\u5F53\u524D #${activeIndex}\uFF09\uFF1A`);
1666
+ for (const tab of tabs) {
1667
+ const prefix = tab.active ? "*" : " ";
1668
+ const title = tab.title || "(\u65E0\u6807\u9898)";
1669
+ lines.push(`${prefix} [${tab.index}] ${tab.url} - ${title}`);
1670
+ }
1671
+ return lines.join("\n");
1672
+ }
1673
+ async function tabCommand(args, options = {}) {
1674
+ await ensureDaemonRunning();
1675
+ const parsed = parseTabSubcommand(args, process.argv);
1676
+ const request = {
1677
+ id: generateId(),
1678
+ action: parsed.action,
1679
+ url: parsed.url,
1680
+ index: parsed.index,
1681
+ tabId: parsed.tabId
1682
+ };
1683
+ const response = await sendCommand(request);
1684
+ if (options.json) {
1685
+ console.log(JSON.stringify(response, null, 2));
1582
1686
  } else {
1583
- console.log(result.body);
1687
+ if (response.success) {
1688
+ switch (parsed.action) {
1689
+ case "tab_list": {
1690
+ const tabs = response.data?.tabs ?? [];
1691
+ const activeIndex = response.data?.activeIndex ?? 0;
1692
+ console.log(formatTabList(tabs, activeIndex));
1693
+ break;
1694
+ }
1695
+ case "tab_new": {
1696
+ const url = response.data?.url ?? "about:blank";
1697
+ console.log(`\u5DF2\u521B\u5EFA\u65B0\u6807\u7B7E\u9875: ${url}`);
1698
+ break;
1699
+ }
1700
+ case "tab_select": {
1701
+ const title = response.data?.title ?? "(\u65E0\u6807\u9898)";
1702
+ const url = response.data?.url ?? "";
1703
+ console.log(`\u5DF2\u5207\u6362\u5230\u6807\u7B7E\u9875 #${parsed.index}: ${title}`);
1704
+ console.log(` URL: ${url}`);
1705
+ break;
1706
+ }
1707
+ case "tab_close": {
1708
+ const closedTitle = response.data?.title ?? "(\u65E0\u6807\u9898)";
1709
+ console.log(`\u5DF2\u5173\u95ED\u6807\u7B7E\u9875: ${closedTitle}`);
1710
+ break;
1711
+ }
1712
+ }
1713
+ } else {
1714
+ console.error(`\u9519\u8BEF: ${response.error}`);
1715
+ process.exit(1);
1716
+ }
1584
1717
  }
1585
1718
  }
1586
1719
 
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;
1720
+ // packages/cli/src/commands/frame.ts
1721
+ async function frameCommand(selector, options = {}) {
1722
+ if (!selector) {
1723
+ throw new Error("\u7F3A\u5C11 selector \u53C2\u6570");
1602
1724
  }
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 {
1725
+ await ensureDaemonRunning();
1726
+ const request = {
1727
+ id: generateId(),
1728
+ action: "frame",
1729
+ selector,
1730
+ tabId: options.tabId
1731
+ };
1732
+ const response = await sendCommand(request);
1733
+ if (options.json) {
1734
+ console.log(JSON.stringify(response, null, 2));
1735
+ } else {
1736
+ if (response.success) {
1737
+ const frameInfo = response.data?.frameInfo;
1738
+ if (frameInfo?.url) {
1739
+ console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector} (${frameInfo.url})`);
1740
+ } else {
1741
+ console.log(`\u5DF2\u5207\u6362\u5230 frame: ${selector}`);
1742
+ }
1743
+ } else {
1744
+ console.error(`\u9519\u8BEF: ${response.error}`);
1745
+ process.exit(1);
1622
1746
  }
1623
1747
  }
1624
- const meta = {
1625
- name: defaultName,
1626
- description: "",
1627
- domain: "",
1628
- args: {},
1629
- filePath,
1630
- source
1748
+ }
1749
+ async function frameMainCommand(options = {}) {
1750
+ await ensureDaemonRunning();
1751
+ const request = {
1752
+ id: generateId(),
1753
+ action: "frame_main",
1754
+ tabId: options.tabId
1631
1755
  };
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;
1756
+ const response = await sendCommand(request);
1757
+ if (options.json) {
1758
+ console.log(JSON.stringify(response, null, 2));
1759
+ } else {
1760
+ if (response.success) {
1761
+ console.log("\u5DF2\u8FD4\u56DE\u4E3B frame");
1762
+ } else {
1763
+ console.error(`\u9519\u8BEF: ${response.error}`);
1764
+ process.exit(1);
1654
1765
  }
1655
1766
  }
1656
- return meta;
1657
1767
  }
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);
1768
+
1769
+ // packages/cli/src/commands/dialog.ts
1770
+ async function dialogCommand(subCommand, promptText, options = {}) {
1771
+ if (!subCommand || !["accept", "dismiss"].includes(subCommand)) {
1772
+ throw new Error("\u8BF7\u4F7F\u7528 'dialog accept [text]' \u6216 'dialog dismiss'");
1773
+ }
1774
+ await ensureDaemonRunning();
1775
+ const request = {
1776
+ id: generateId(),
1777
+ action: "dialog",
1778
+ dialogResponse: subCommand,
1779
+ promptText: subCommand === "accept" ? promptText : void 0,
1780
+ tabId: options.tabId
1781
+ };
1782
+ const response = await sendCommand(request);
1783
+ if (options.json) {
1784
+ console.log(JSON.stringify(response, null, 2));
1785
+ } else {
1786
+ if (response.success) {
1787
+ const dialogInfo = response.data?.dialogInfo;
1788
+ if (dialogInfo) {
1789
+ const action = subCommand === "accept" ? "\u5DF2\u63A5\u53D7" : "\u5DF2\u62D2\u7EDD";
1790
+ console.log(`${action}\u5BF9\u8BDD\u6846\uFF08${dialogInfo.type}\uFF09: "${dialogInfo.message}"`);
1791
+ } else {
1792
+ console.log("\u5BF9\u8BDD\u6846\u5DF2\u5904\u7406");
1675
1793
  }
1794
+ } else {
1795
+ console.error(`\u9519\u8BEF: ${response.error}`);
1796
+ process.exit(1);
1676
1797
  }
1677
1798
  }
1678
- walk(dir);
1679
- return sites;
1680
1799
  }
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
- }
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;
1800
+
1801
+ // packages/cli/src/commands/network.ts
1802
+ async function networkCommand(subCommand, urlOrFilter, options = {}) {
1803
+ const response = await sendCommand({
1804
+ id: crypto.randomUUID(),
1805
+ action: "network",
1806
+ networkCommand: subCommand,
1807
+ url: subCommand === "route" || subCommand === "unroute" ? urlOrFilter : void 0,
1808
+ filter: subCommand === "requests" ? urlOrFilter : void 0,
1809
+ routeOptions: subCommand === "route" ? {
1810
+ abort: options.abort,
1811
+ body: options.body
1812
+ } : void 0,
1813
+ withBody: subCommand === "requests" ? options.withBody : void 0,
1814
+ tabId: options.tabId
1815
+ });
1816
+ if (options.json) {
1817
+ console.log(JSON.stringify(response));
1818
+ return;
1819
+ }
1820
+ if (!response.success) {
1821
+ throw new Error(response.error || "Network command failed");
1822
+ }
1823
+ const data = response.data;
1824
+ switch (subCommand) {
1825
+ case "requests": {
1826
+ const requests = data?.networkRequests || [];
1827
+ if (requests.length === 0) {
1828
+ console.log("\u6CA1\u6709\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
1829
+ console.log("\u63D0\u793A: \u4F7F\u7528 network requests \u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1830
+ } else {
1831
+ console.log(`\u7F51\u7EDC\u8BF7\u6C42 (${requests.length} \u6761):
1832
+ `);
1833
+ for (const req of requests) {
1834
+ const status = req.failed ? `FAILED (${req.failureReason})` : req.status ? `${req.status} ${req.statusText || ""}` : "pending";
1835
+ console.log(`${req.method} ${req.url}`);
1836
+ console.log(` \u7C7B\u578B: ${req.type}, \u72B6\u6001: ${status}`);
1837
+ if (options.withBody) {
1838
+ const requestHeaderCount = req.requestHeaders ? Object.keys(req.requestHeaders).length : 0;
1839
+ const responseHeaderCount = req.responseHeaders ? Object.keys(req.responseHeaders).length : 0;
1840
+ console.log(` \u8BF7\u6C42\u5934: ${requestHeaderCount}, \u54CD\u5E94\u5934: ${responseHeaderCount}`);
1841
+ if (req.requestBody !== void 0) {
1842
+ const preview = req.requestBody.length > 200 ? `${req.requestBody.slice(0, 200)}...` : req.requestBody;
1843
+ console.log(` \u8BF7\u6C42\u4F53: ${preview}`);
1844
+ }
1845
+ if (req.responseBody !== void 0) {
1846
+ const preview = req.responseBody.length > 200 ? `${req.responseBody.slice(0, 200)}...` : req.responseBody;
1847
+ console.log(` \u54CD\u5E94\u4F53: ${preview}`);
1848
+ }
1849
+ if (req.bodyError) {
1850
+ console.log(` Body\u9519\u8BEF: ${req.bodyError}`);
1851
+ }
1852
+ }
1853
+ console.log("");
1854
+ }
1855
+ }
1856
+ break;
1857
+ }
1858
+ case "route": {
1859
+ console.log(`\u5DF2\u6DFB\u52A0\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
1860
+ if (options.abort) {
1861
+ console.log(" \u884C\u4E3A: \u963B\u6B62\u8BF7\u6C42");
1862
+ } else if (options.body) {
1863
+ console.log(" \u884C\u4E3A: \u8FD4\u56DE mock \u6570\u636E");
1864
+ } else {
1865
+ console.log(" \u884C\u4E3A: \u7EE7\u7EED\u8BF7\u6C42");
1866
+ }
1867
+ console.log(`\u5F53\u524D\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
1868
+ break;
1869
+ }
1870
+ case "unroute": {
1871
+ if (urlOrFilter) {
1872
+ console.log(`\u5DF2\u79FB\u9664\u62E6\u622A\u89C4\u5219: ${urlOrFilter}`);
1873
+ } else {
1874
+ console.log("\u5DF2\u79FB\u9664\u6240\u6709\u62E6\u622A\u89C4\u5219");
1875
+ }
1876
+ console.log(`\u5269\u4F59\u89C4\u5219\u6570: ${data?.routeCount || 0}`);
1877
+ break;
1878
+ }
1879
+ case "clear": {
1880
+ console.log("\u5DF2\u6E05\u7A7A\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55");
1881
+ break;
1882
+ }
1883
+ default:
1884
+ throw new Error(`\u672A\u77E5\u7684 network \u5B50\u547D\u4EE4: ${subCommand}`);
1695
1885
  }
1696
1886
  }
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}`);
1887
+
1888
+ // packages/cli/src/commands/console.ts
1889
+ async function consoleCommand(options = {}) {
1890
+ const response = await sendCommand({
1891
+ id: crypto.randomUUID(),
1892
+ action: "console",
1893
+ consoleCommand: options.clear ? "clear" : "get",
1894
+ tabId: options.tabId
1895
+ });
1896
+ if (options.json) {
1897
+ console.log(JSON.stringify(response));
1703
1898
  return;
1704
1899
  }
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));
1900
+ if (!response.success) {
1901
+ throw new Error(response.error || "Console command failed");
1902
+ }
1903
+ if (options.clear) {
1904
+ console.log("\u5DF2\u6E05\u7A7A\u63A7\u5236\u53F0\u6D88\u606F");
1713
1905
  return;
1714
1906
  }
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);
1907
+ const messages = response.data?.consoleMessages || [];
1908
+ if (messages.length === 0) {
1909
+ console.log("\u6CA1\u6709\u63A7\u5236\u53F0\u6D88\u606F");
1910
+ console.log("\u63D0\u793A: console \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1911
+ return;
1720
1912
  }
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}`);
1913
+ console.log(`\u63A7\u5236\u53F0\u6D88\u606F (${messages.length} \u6761):
1914
+ `);
1915
+ const typeColors = {
1916
+ log: "",
1917
+ info: "[INFO]",
1918
+ warn: "[WARN]",
1919
+ error: "[ERROR]",
1920
+ debug: "[DEBUG]"
1921
+ };
1922
+ for (const msg of messages) {
1923
+ const prefix = typeColors[msg.type] || `[${msg.type.toUpperCase()}]`;
1924
+ const location = msg.url ? ` (${msg.url}${msg.lineNumber ? `:${msg.lineNumber}` : ""})` : "";
1925
+ if (prefix) {
1926
+ console.log(`${prefix} ${msg.text}${location}`);
1927
+ } else {
1928
+ console.log(`${msg.text}${location}`);
1729
1929
  }
1730
1930
  }
1731
- console.log();
1732
1931
  }
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");
1932
+
1933
+ // packages/cli/src/commands/errors.ts
1934
+ async function errorsCommand(options = {}) {
1935
+ const response = await sendCommand({
1936
+ id: crypto.randomUUID(),
1937
+ action: "errors",
1938
+ errorsCommand: options.clear ? "clear" : "get",
1939
+ tabId: options.tabId
1940
+ });
1941
+ if (options.json) {
1942
+ console.log(JSON.stringify(response));
1742
1943
  return;
1743
1944
  }
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));
1945
+ if (!response.success) {
1946
+ throw new Error(response.error || "Errors command failed");
1947
+ }
1948
+ if (options.clear) {
1949
+ console.log("\u5DF2\u6E05\u7A7A JS \u9519\u8BEF\u8BB0\u5F55");
1751
1950
  return;
1752
1951
  }
1753
- for (const s of matches) {
1754
- const src = s.source === "local" ? " (local)" : "";
1755
- console.log(`${s.name.padEnd(24)} ${s.description}${src}`);
1952
+ const errors = response.data?.jsErrors || [];
1953
+ if (errors.length === 0) {
1954
+ console.log("\u6CA1\u6709 JS \u9519\u8BEF");
1955
+ console.log("\u63D0\u793A: errors \u547D\u4EE4\u4F1A\u81EA\u52A8\u5F00\u59CB\u76D1\u63A7");
1956
+ return;
1756
1957
  }
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);
1958
+ console.log(`JS \u9519\u8BEF (${errors.length} \u6761):
1959
+ `);
1960
+ for (const err of errors) {
1961
+ console.log(`[ERROR] ${err.message}`);
1962
+ if (err.url) {
1963
+ console.log(` \u4F4D\u7F6E: ${err.url}:${err.lineNumber || 0}:${err.columnNumber || 0}`);
1769
1964
  }
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);
1965
+ if (err.stackTrace) {
1966
+ console.log(` \u5806\u6808:`);
1967
+ console.log(err.stackTrace.split("\n").map((line) => ` ${line}`).join("\n"));
1779
1968
  }
1969
+ console.log("");
1780
1970
  }
1781
- const sites = scanSites(COMMUNITY_SITES_DIR, "community");
1782
- console.log(`\u5DF2\u5B89\u88C5 ${sites.length} \u4E2A\u793E\u533A adapter\u3002`);
1783
1971
  }
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}`);
1972
+
1973
+ // packages/cli/src/commands/trace.ts
1974
+ async function traceCommand(subCommand, options = {}) {
1975
+ const response = await sendCommand({
1976
+ id: crypto.randomUUID(),
1977
+ action: "trace",
1978
+ traceCommand: subCommand,
1979
+ tabId: options.tabId
1980
+ });
1981
+ if (options.json) {
1982
+ console.log(JSON.stringify(response));
1983
+ return;
1984
+ }
1985
+ if (!response.success) {
1986
+ throw new Error(response.error || "Trace command failed");
1987
+ }
1988
+ const data = response.data;
1989
+ switch (subCommand) {
1990
+ case "start": {
1991
+ const status = data?.traceStatus;
1992
+ console.log("\u5F00\u59CB\u5F55\u5236\u7528\u6237\u64CD\u4F5C");
1993
+ console.log(`\u6807\u7B7E\u9875 ID: ${status?.tabId || "N/A"}`);
1994
+ 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");
1995
+ break;
1996
+ }
1997
+ case "stop": {
1998
+ const events = data?.traceEvents || [];
1999
+ const status = data?.traceStatus;
2000
+ console.log(`\u5F55\u5236\u5B8C\u6210\uFF0C\u5171 ${events.length} \u4E2A\u4E8B\u4EF6
2001
+ `);
2002
+ if (events.length === 0) {
2003
+ console.log("\u6CA1\u6709\u5F55\u5236\u5230\u4EFB\u4F55\u64CD\u4F5C");
2004
+ break;
1794
2005
  }
1795
- } else {
1796
- console.error(" Try: bb-browser site list");
1797
- console.error(" Or: bb-browser site update");
2006
+ for (let i = 0; i < events.length; i++) {
2007
+ const event = events[i];
2008
+ const refStr = event.ref !== void 0 ? `@${event.ref}` : "";
2009
+ switch (event.type) {
2010
+ case "navigation":
2011
+ console.log(`${i + 1}. \u5BFC\u822A\u5230: ${event.url}`);
2012
+ break;
2013
+ case "click":
2014
+ console.log(`${i + 1}. \u70B9\u51FB ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
2015
+ break;
2016
+ case "fill":
2017
+ console.log(`${i + 1}. \u586B\u5145 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
2018
+ break;
2019
+ case "select":
2020
+ console.log(`${i + 1}. \u9009\u62E9 ${refStr} [${event.elementRole}] "${event.elementName || ""}" <- "${event.value}"`);
2021
+ break;
2022
+ case "check":
2023
+ console.log(`${i + 1}. ${event.checked ? "\u52FE\u9009" : "\u53D6\u6D88\u52FE\u9009"} ${refStr} [${event.elementRole}] "${event.elementName || ""}"`);
2024
+ break;
2025
+ case "press":
2026
+ console.log(`${i + 1}. \u6309\u952E ${event.key}`);
2027
+ break;
2028
+ case "scroll":
2029
+ console.log(`${i + 1}. \u6EDA\u52A8 ${event.direction} ${event.pixels}px`);
2030
+ break;
2031
+ default:
2032
+ console.log(`${i + 1}. ${event.type}`);
2033
+ }
2034
+ }
2035
+ console.log(`
2036
+ \u72B6\u6001: ${status?.recording ? "\u5F55\u5236\u4E2D" : "\u5DF2\u505C\u6B62"}`);
2037
+ break;
2038
+ }
2039
+ case "status": {
2040
+ const status = data?.traceStatus;
2041
+ if (status?.recording) {
2042
+ console.log(`\u5F55\u5236\u4E2D (\u6807\u7B7E\u9875 ${status.tabId})`);
2043
+ console.log(`\u5DF2\u5F55\u5236 ${status.eventCount} \u4E2A\u4E8B\u4EF6`);
2044
+ } else {
2045
+ console.log("\u672A\u5728\u5F55\u5236");
2046
+ }
2047
+ break;
2048
+ }
2049
+ default:
2050
+ throw new Error(`\u672A\u77E5\u7684 trace \u5B50\u547D\u4EE4: ${subCommand}`);
2051
+ }
2052
+ }
2053
+
2054
+ // packages/cli/src/commands/fetch.ts
2055
+ function matchTabOrigin2(tabUrl, targetHostname) {
2056
+ try {
2057
+ const tabHostname = new URL(tabUrl).hostname;
2058
+ return tabHostname === targetHostname || tabHostname.endsWith("." + targetHostname);
2059
+ } catch {
2060
+ return false;
2061
+ }
2062
+ }
2063
+ async function ensureTabForOrigin(origin, hostname) {
2064
+ const listReq = { id: generateId(), action: "tab_list" };
2065
+ const listResp = await sendCommand(listReq);
2066
+ if (listResp.success && listResp.data?.tabs) {
2067
+ const matchingTab = listResp.data.tabs.find(
2068
+ (tab) => matchTabOrigin2(tab.url, hostname)
2069
+ );
2070
+ if (matchingTab) {
2071
+ return matchingTab.tabId;
1798
2072
  }
1799
- process.exit(1);
1800
2073
  }
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
- }
2074
+ const newResp = await sendCommand({ id: generateId(), action: "tab_new", url: origin });
2075
+ if (!newResp.success) {
2076
+ throw new Error(`\u65E0\u6CD5\u6253\u5F00 ${origin}: ${newResp.error}`);
1814
2077
  }
1815
- let posIdx = 0;
1816
- for (const argName of argNames) {
1817
- if (!argMap[argName] && posIdx < positionalArgs.length) {
1818
- argMap[argName] = positionalArgs[posIdx++];
2078
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
2079
+ return newResp.data?.tabId;
2080
+ }
2081
+ function buildFetchScript(url, options) {
2082
+ const method = (options.method || "GET").toUpperCase();
2083
+ const hasBody = options.body && method !== "GET" && method !== "HEAD";
2084
+ let headersExpr = "{}";
2085
+ if (options.headers) {
2086
+ try {
2087
+ JSON.parse(options.headers);
2088
+ headersExpr = options.headers;
2089
+ } catch {
2090
+ throw new Error(`--headers must be valid JSON. Got: ${options.headers}`);
1819
2091
  }
1820
2092
  }
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);
2093
+ return `(async () => {
2094
+ try {
2095
+ const resp = await fetch(${JSON.stringify(url)}, {
2096
+ method: ${JSON.stringify(method)},
2097
+ credentials: 'include',
2098
+ headers: ${headersExpr}${hasBody ? `,
2099
+ body: ${JSON.stringify(options.body)}` : ""}
2100
+ });
2101
+ const contentType = resp.headers.get('content-type') || '';
2102
+ let body;
2103
+ if (contentType.includes('application/json') && resp.status !== 204) {
2104
+ try { body = await resp.json(); } catch { body = await resp.text(); }
2105
+ } else {
2106
+ body = await resp.text();
2107
+ }
2108
+ return JSON.stringify({
2109
+ status: resp.status,
2110
+ contentType,
2111
+ body
2112
+ });
2113
+ } catch (e) {
2114
+ return JSON.stringify({ error: e.message });
1831
2115
  }
2116
+ })()`;
2117
+ }
2118
+ async function fetchCommand(url, options = {}) {
2119
+ if (!url) {
2120
+ throw new Error(
2121
+ "\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"
2122
+ );
1832
2123
  }
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
2124
  await ensureDaemonRunning();
2125
+ const isAbsolute = url.startsWith("http://") || url.startsWith("https://");
1838
2126
  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
- }
2127
+ if (isAbsolute) {
2128
+ let origin;
2129
+ let hostname;
2130
+ try {
2131
+ const parsed = new URL(url);
2132
+ origin = parsed.origin;
2133
+ hostname = parsed.hostname;
2134
+ } catch {
2135
+ throw new Error(`\u65E0\u6548\u7684 URL: ${url}`);
1849
2136
  }
1850
2137
  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));
2138
+ targetTabId = await ensureTabForOrigin(origin, hostname);
1858
2139
  }
1859
2140
  }
2141
+ const script = buildFetchScript(url, options);
1860
2142
  const evalReq = { id: generateId(), action: "eval", script, tabId: targetTabId };
1861
2143
  const evalResp = await sendCommand(evalReq);
1862
2144
  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);
2145
+ throw new Error(`Fetch \u5931\u8D25: ${evalResp.error}`);
1871
2146
  }
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;
2147
+ const rawResult = evalResp.data?.result;
2148
+ if (rawResult === void 0 || rawResult === null) {
2149
+ throw new Error("Fetch \u672A\u8FD4\u56DE\u7ED3\u679C");
1880
2150
  }
1881
- let parsed;
2151
+ let result;
1882
2152
  try {
1883
- parsed = typeof result === "string" ? JSON.parse(result) : result;
2153
+ result = typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult;
1884
2154
  } catch {
1885
- parsed = result;
2155
+ console.log(rawResult);
2156
+ return;
1886
2157
  }
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);
2158
+ if (result.error) {
2159
+ throw new Error(`Fetch error: ${result.error}`);
1903
2160
  }
1904
- if (options.json) {
1905
- console.log(JSON.stringify({ id: evalReq.id, success: true, data: parsed }));
2161
+ if (options.output) {
2162
+ const { writeFileSync } = await import("fs");
2163
+ const content = typeof result.body === "object" ? JSON.stringify(result.body, null, 2) : String(result.body);
2164
+ writeFileSync(options.output, content, "utf-8");
2165
+ console.log(`\u5DF2\u5199\u5165 ${options.output} (${result.status}, ${content.length} bytes)`);
2166
+ return;
2167
+ }
2168
+ if (typeof result.body === "object") {
2169
+ console.log(JSON.stringify(result.body, null, 2));
1906
2170
  } else {
1907
- console.log(JSON.stringify(parsed, null, 2));
2171
+ console.log(result.body);
1908
2172
  }
1909
2173
  }
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
2174
 
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`);
2175
+ // packages/cli/src/commands/history.ts
2176
+ async function historyCommand(subCommand, options = {}) {
2177
+ const response = await sendCommand({
2178
+ id: crypto.randomUUID(),
2179
+ action: "history",
2180
+ historyCommand: subCommand,
2181
+ text: options.query,
2182
+ ms: options.days
2183
+ });
2184
+ if (options.json) {
2185
+ console.log(JSON.stringify(response));
1936
2186
  return;
1937
2187
  }
2188
+ if (!response.success) {
2189
+ throw new Error(response.error || "History command failed");
2190
+ }
2191
+ const data = response.data;
1938
2192
  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);
2193
+ case "search": {
2194
+ const items = data?.historyItems || [];
2195
+ console.log(`\u627E\u5230 ${items.length} \u6761\u5386\u53F2\u8BB0\u5F55
2196
+ `);
2197
+ if (items.length === 0) {
2198
+ console.log("\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u5386\u53F2\u8BB0\u5F55");
2199
+ break;
1947
2200
  }
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);
2201
+ for (let i = 0; i < items.length; i++) {
2202
+ const item = items[i];
2203
+ console.log(`${i + 1}. ${item.title || "(\u65E0\u6807\u9898)"}`);
2204
+ console.log(` ${item.url}`);
2205
+ console.log(` \u8BBF\u95EE\u6B21\u6570: ${item.visitCount}`);
1959
2206
  }
1960
- await siteRun(args[1], args.slice(2), options);
1961
2207
  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);
2208
+ }
2209
+ case "domains": {
2210
+ const domains = data?.historyDomains || [];
2211
+ console.log(`\u627E\u5230 ${domains.length} \u4E2A\u57DF\u540D
2212
+ `);
2213
+ if (domains.length === 0) {
2214
+ console.log("\u6CA1\u6709\u627E\u5230\u5386\u53F2\u8BB0\u5F55");
2215
+ break;
2216
+ }
2217
+ for (let i = 0; i < domains.length; i++) {
2218
+ const domain = domains[i];
2219
+ console.log(`${i + 1}. ${domain.domain}`);
2220
+ console.log(` \u8BBF\u95EE\u6B21\u6570: ${domain.visits}`);
1970
2221
  }
1971
2222
  break;
2223
+ }
2224
+ default:
2225
+ throw new Error(`\u672A\u77E5\u7684 history \u5B50\u547D\u4EE4: ${subCommand}`);
1972
2226
  }
1973
2227
  }
1974
2228
 
@@ -1977,74 +2231,56 @@ var VERSION = "0.3.0";
1977
2231
  var HELP_TEXT = `
1978
2232
  bb-browser - AI Agent \u6D4F\u89C8\u5668\u81EA\u52A8\u5316\u5DE5\u5177
1979
2233
 
2234
+ \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
2235
+ bb-browser site list \u67E5\u770B\u6240\u6709\u53EF\u7528\u547D\u4EE4
2236
+ bb-browser site twitter/search "AI" \u793A\u4F8B\uFF1A\u641C\u7D22\u63A8\u6587
2237
+ bb-browser site xueqiu/hot-stock 5 \u793A\u4F8B\uFF1A\u83B7\u53D6\u4EBA\u6C14\u80A1\u7968
2238
+
1980
2239
  \u7528\u6CD5\uFF1A
1981
2240
  bb-browser <command> [options]
1982
2241
 
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
2242
+ \u5F00\u59CB\u4F7F\u7528\uFF1A
2243
+ site recommend \u63A8\u8350\u4F60\u53EF\u80FD\u9700\u8981\u7684 adapter\uFF08\u57FA\u4E8E\u6D4F\u89C8\u5386\u53F2\uFF09
2244
+ site list \u5217\u51FA\u6240\u6709 adapter
2245
+ site info <name> \u67E5\u770B adapter \u7528\u6CD5\uFF08\u53C2\u6570\u3001\u8FD4\u56DE\u503C\u3001\u793A\u4F8B\uFF09
2246
+ site <name> [args] \u8FD0\u884C adapter
2247
+ site update \u66F4\u65B0\u793E\u533A adapter \u5E93
2248
+ guide \u5982\u4F55\u628A\u4EFB\u4F55\u7F51\u7AD9\u53D8\u6210 adapter
2008
2249
 
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
2250
+ \u6D4F\u89C8\u5668\u64CD\u4F5C\uFF1A
2251
+ open <url> [--tab] \u6253\u5F00 URL
2252
+ snapshot [-i] [-c] [-d <n>] \u83B7\u53D6\u9875\u9762\u5FEB\u7167
2253
+ click <ref> \u70B9\u51FB\u5143\u7D20
2254
+ hover <ref> \u60AC\u505C\u5143\u7D20
2255
+ fill <ref> <text> \u586B\u5145\u8F93\u5165\u6846\uFF08\u6E05\u7A7A\u540E\u586B\u5165\uFF09
2256
+ type <ref> <text> \u9010\u5B57\u7B26\u8F93\u5165\uFF08\u4E0D\u6E05\u7A7A\uFF09
2257
+ check/uncheck <ref> \u52FE\u9009/\u53D6\u6D88\u590D\u9009\u6846
2258
+ select <ref> <val> \u4E0B\u62C9\u6846\u9009\u62E9
2259
+ press <key> \u53D1\u9001\u6309\u952E
2260
+ scroll <dir> [px] \u6EDA\u52A8\u9875\u9762
2021
2261
 
2022
2262
  \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
2263
+ get text|url|title <ref> \u83B7\u53D6\u9875\u9762\u5185\u5BB9
2264
+ screenshot [path] \u622A\u56FE
2265
+ eval "<js>" \u6267\u884C JavaScript
2266
+ fetch <url> \u5E26\u767B\u5F55\u6001\u7684 HTTP \u8BF7\u6C42
2030
2267
 
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
2268
+ \u6807\u7B7E\u9875\uFF1A
2269
+ tab [list|new|close|<n>] \u7BA1\u7406\u6807\u7B7E\u9875
2039
2270
 
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
2271
+ \u5BFC\u822A\uFF1A
2272
+ back / forward / refresh \u540E\u9000 / \u524D\u8FDB / \u5237\u65B0
2273
+
2274
+ \u8C03\u8BD5\uFF1A
2275
+ network requests [filter] \u67E5\u770B\u7F51\u7EDC\u8BF7\u6C42
2276
+ console [--clear] \u67E5\u770B/\u6E05\u7A7A\u63A7\u5236\u53F0
2277
+ errors [--clear] \u67E5\u770B/\u6E05\u7A7A JS \u9519\u8BEF
2278
+ trace start|stop|status \u5F55\u5236\u7528\u6237\u64CD\u4F5C
2279
+ history search|domains \u67E5\u770B\u6D4F\u89C8\u5386\u53F2
2045
2280
 
2046
2281
  \u9009\u9879\uFF1A
2047
2282
  --json \u4EE5 JSON \u683C\u5F0F\u8F93\u51FA
2283
+ --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
2284
  -i, --interactive \u53EA\u8F93\u51FA\u53EF\u4EA4\u4E92\u5143\u7D20\uFF08snapshot \u547D\u4EE4\uFF09
2049
2285
  -c, --compact \u79FB\u9664\u7A7A\u7ED3\u6784\u8282\u70B9\uFF08snapshot \u547D\u4EE4\uFF09
2050
2286
  -d, --depth <n> \u9650\u5236\u6811\u6DF1\u5EA6\uFF08snapshot \u547D\u4EE4\uFF09
@@ -2075,6 +2311,13 @@ function parseArgs(argv) {
2075
2311
  }
2076
2312
  if (arg === "--json") {
2077
2313
  result.flags.json = true;
2314
+ } else if (arg === "--jq") {
2315
+ skipNext = true;
2316
+ const nextIdx = args.indexOf(arg) + 1;
2317
+ if (nextIdx < args.length) {
2318
+ result.flags.jq = args[nextIdx];
2319
+ result.flags.json = true;
2320
+ }
2078
2321
  } else if (arg === "--help" || arg === "-h") {
2079
2322
  result.flags.help = true;
2080
2323
  } else if (arg === "--version" || arg === "-v") {
@@ -2095,6 +2338,12 @@ function parseArgs(argv) {
2095
2338
  if (nextIdx < args.length) {
2096
2339
  result.flags.selector = args[nextIdx];
2097
2340
  }
2341
+ } else if (arg === "--days") {
2342
+ skipNext = true;
2343
+ const nextIdx = args.indexOf(arg) + 1;
2344
+ if (nextIdx < args.length) {
2345
+ result.flags.days = parseInt(args[nextIdx], 10);
2346
+ }
2098
2347
  } else if (arg === "--id") {
2099
2348
  skipNext = true;
2100
2349
  } else if (arg === "--tab") {
@@ -2110,6 +2359,7 @@ function parseArgs(argv) {
2110
2359
  }
2111
2360
  async function main() {
2112
2361
  const parsed = parseArgs(process.argv);
2362
+ setJqExpression(parsed.flags.jq);
2113
2363
  const tabArgIdx = process.argv.indexOf("--tab");
2114
2364
  const globalTabId = tabArgIdx >= 0 && process.argv[tabArgIdx + 1] ? parseInt(process.argv[tabArgIdx + 1], 10) : void 0;
2115
2365
  if (parsed.flags.version) {
@@ -2423,6 +2673,23 @@ async function main() {
2423
2673
  await traceCommand(subCmd, { json: parsed.flags.json, tabId: globalTabId });
2424
2674
  break;
2425
2675
  }
2676
+ case "history": {
2677
+ const subCmd = parsed.args[0];
2678
+ if (!subCmd || !["search", "domains"].includes(subCmd)) {
2679
+ console.error("\u9519\u8BEF\uFF1A\u7F3A\u5C11\u6216\u65E0\u6548\u7684\u5B50\u547D\u4EE4");
2680
+ console.error("\u7528\u6CD5\uFF1Abb-browser history <search|domains> [query] [--days <n>]");
2681
+ console.error("\u793A\u4F8B\uFF1Abb-browser history search github");
2682
+ console.error(" bb-browser history domains --days 7");
2683
+ process.exit(1);
2684
+ }
2685
+ const query = parsed.args.slice(1).join(" ");
2686
+ await historyCommand(subCmd, {
2687
+ json: parsed.flags.json,
2688
+ days: parsed.flags.days || 30,
2689
+ query
2690
+ });
2691
+ break;
2692
+ }
2426
2693
  case "fetch": {
2427
2694
  const fetchUrl = parsed.args[0];
2428
2695
  if (!fetchUrl) {
@@ -2450,7 +2717,12 @@ async function main() {
2450
2717
  break;
2451
2718
  }
2452
2719
  case "site": {
2453
- await siteCommand(parsed.args, { json: parsed.flags.json, tabId: globalTabId });
2720
+ await siteCommand(parsed.args, {
2721
+ json: parsed.flags.json,
2722
+ jq: parsed.flags.jq,
2723
+ days: parsed.flags.days,
2724
+ tabId: globalTabId
2725
+ });
2454
2726
  break;
2455
2727
  }
2456
2728
  case "guide": {