Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions packages/cli/src/commands/cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* cookies 命令 - 查看当前页面的 cookies
*
* 用法:
* bb-browser cookies --tab <tabId>
* bb-browser cookies --tab <tabId> --filter <name-or-domain>
*/

import type { Request, Response } from "@bb-browser/shared";
import { sendCommand } from "../client.js";

export interface CookiesOptions {
json?: boolean;
tabId?: string | number;
filter?: string;
}

export async function cookiesCommand(
options: CookiesOptions = {}
): Promise<void> {
const request: Request = {
method: "cookies",
tabId: options.tabId,
filter: options.filter,
};

const response: Response = await sendCommand(request);

if (options.json) {
console.log(JSON.stringify(response));
return;
}

if (response.error) {
throw new Error(response.error.message || "Cookies command failed");
}

const data = response.result;
const cookies = (data as any)?.cookies || [];

if (cookies.length === 0) {
console.log("No cookies found");
return;
}

console.log(`Cookies (${cookies.length}):\n`);
for (const c of cookies) {
const flags: string[] = [];
if (c.httpOnly) flags.push("httpOnly");
if (c.secure) flags.push("secure");
const expiresStr = c.expires > 0
? `expires=${new Date(c.expires * 1000).toISOString().split("T")[0]}`
: "session";
const flagStr = flags.length > 0 ? ` ${flags.join(" ")}` : "";
console.log(`name=${c.name} domain=${c.domain} path=${c.path}${flagStr} ${expiresStr}`);
}
}
46 changes: 46 additions & 0 deletions packages/cli/src/commands/goto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* goto 命令 - 在当前 tab 中导航到新 URL(保持 tab 上下文)
*
* 用法:
* bb-browser goto <url> --tab <tabId>
*/

import type { Request, Response } from "@bb-browser/shared";
import { sendCommand } from "../client.js";
import { ensureDaemonRunning } from "../daemon-manager.js";

export interface GotoOptions {
json?: boolean;
tabId?: string | number;
}

export async function gotoCommand(
url: string,
options: GotoOptions = {}
): Promise<void> {
if (!url) {
throw new Error("Missing URL parameter");
}

await ensureDaemonRunning();

const request: Request = {
method: "goto",
url,
tabId: options.tabId,
};

const response: Response = await sendCommand(request);

if (options.json) {
console.log(JSON.stringify(response, null, 2));
} else {
if (response.result) {
console.log(`tab: ${response.result.tab}`);
console.log(`url: ${response.result.url}`);
} else {
console.error(`Error: ${response.error?.message}`);
process.exit(1);
}
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/commands/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface NetworkOptions {
since?: string;
method?: string;
status?: string;
excludeStatic?: boolean;
}

export async function networkCommand(
Expand All @@ -37,6 +38,7 @@ export async function networkCommand(
body: options.body,
} : undefined,
withBody: subCommand === "requests" ? options.withBody : undefined,
excludeStatic: subCommand === "requests" ? options.excludeStatic : undefined,
since,
httpMethod: subCommand === "requests" ? options.method : undefined,
status: subCommand === "requests" ? options.status : undefined,
Expand Down
13 changes: 7 additions & 6 deletions packages/cli/src/commands/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,18 @@ export async function openCommand(
console.log(JSON.stringify(response, null, 2));
} else {
if (response.result) {
console.log(`已打开: ${response.result?.url ?? normalizedUrl}`);
if (response.result?.title) {
console.log(`标题: ${response.result.title}`);
const tab = response.result?.tab;
if (tab) {
console.log(`tab: ${tab}`);
}
if (response.result?.tabId) {
console.log(`Tab ID: ${response.result.tabId}`);
console.log(`url: ${response.result?.url ?? normalizedUrl}`);
if (response.result?.title) {
console.log(`title: ${response.result.title}`);
}
// 提示:如果该域名有 site adapter,引导使用
const siteHint = getSiteHintForDomain(normalizedUrl);
if (siteHint) {
console.log(`\n💡 ${siteHint}`);
console.log(`\nhint: ${siteHint}`);
}
} else {
console.error(`错误: ${response.error?.message}`);
Expand Down
58 changes: 58 additions & 0 deletions packages/cli/src/commands/source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* source 命令 - 搜索已加载的 JavaScript 源码
*
* 用法:
* bb-browser source grep <pattern> --tab <tabId>
*/

import type { Request, Response } from "@bb-browser/shared";
import { sendCommand } from "../client.js";

export interface SourceOptions {
json?: boolean;
tabId?: string | number;
}

export async function sourceCommand(
subCommand: string,
pattern: string,
options: SourceOptions = {}
): Promise<void> {
const request: Request = {
method: "source",
sourceCommand: subCommand,
sourcePattern: pattern,
tabId: options.tabId,
};

const response: Response = await sendCommand(request);

if (options.json) {
console.log(JSON.stringify(response));
return;
}

if (response.error) {
throw new Error(response.error.message || "Source command failed");
}

const data = response.result;
const results = (data as any)?.sourceResults || [];

if (results.length === 0) {
console.log(`No matches found for "${pattern}"`);
return;
}

let totalMatches = 0;
for (const result of results) {
totalMatches += result.matches.length;
console.log(`=== ${result.url} (${result.matches.length} matches) ===`);
for (const match of result.matches) {
console.log(` ${match}`);
}
console.log("");
}

console.log(`${totalMatches} matches in ${results.length} files`);
}
20 changes: 16 additions & 4 deletions packages/cli/src/commands/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface TraceOptions {
filter?: string;
limit?: number;
requestId?: string;
excludeStatic?: boolean;
}

export async function traceCommand(
Expand All @@ -31,6 +32,7 @@ export async function traceCommand(
if (options.type) request.traceType = options.type;
if (options.filter) request.filter = options.filter;
if (options.limit) request.limit = Number(options.limit);
if (options.excludeStatic) request.excludeStatic = true;
}

// body-specific params
Expand Down Expand Up @@ -137,10 +139,20 @@ export async function traceCommand(
console.error("No body data returned");
break;
}
if (body.base64Encoded) {
console.log(`[base64 encoded, ${body.body.length} chars]`);
} else {
console.log(body.body);
if (body.requestBody) {
console.log("=== Request Body ===");
console.log(body.requestBody);
console.log("");
}
if (body.body) {
if (body.requestBody) {
console.log("=== Response Body ===");
}
if (body.base64Encoded) {
console.log(`[base64 encoded, ${body.body.length} chars]`);
} else {
console.log(body.body);
}
}
break;
}
Expand Down
49 changes: 46 additions & 3 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { openCommand } from "./commands/open.js";
import { gotoCommand } from "./commands/goto.js";
import { snapshotCommand } from "./commands/snapshot.js";
import { clickCommand } from "./commands/click.js";
import { hoverCommand } from "./commands/hover.js";
Expand All @@ -24,6 +25,8 @@ import { networkCommand } from "./commands/network.js";
import { consoleCommand } from "./commands/console.js";
import { errorsCommand } from "./commands/errors.js";
import { traceCommand } from "./commands/trace.js";
import { cookiesCommand } from "./commands/cookies.js";
import { sourceCommand as sourceCmd } from "./commands/source.js";
import { siteCommand } from "./commands/site.js";
import { shutdownCommand, startCommand, statusCommand } from "./commands/daemon.js";
import { getDaemonPath } from "./daemon-manager.js";
Expand All @@ -39,6 +42,7 @@ const TAB_REQUIRED_COMMANDS = new Set([
"click", "hover", "fill", "type", "check", "uncheck", "select",
"press", "scroll", "back", "forward", "reload", "close",
"frame", "dialog", "network", "console", "errors",
"goto", "cookies", "source",
]);

// eval requires --tab unless --domain is provided
Expand Down Expand Up @@ -67,7 +71,8 @@ bb-browser - AI Agent 浏览器自动化工具
star ⭐ Star bb-browser on GitHub

浏览器操作:
open <url> [--tab] 打开 URL
open <url> [--tab] 打开 URL(新 tab 或指定 tab)
goto <url> --tab <id> 在当前 tab 导航到新 URL(保持上下文)
snap [-i] [-c] [-d <n>] 获取页面快照 (--tab required)
click <ref> 点击元素 (--tab required)
hover <ref> 悬停元素 (--tab required)
Expand All @@ -93,6 +98,8 @@ bb-browser - AI Agent 浏览器自动化工具

调试:
network requests [filter] 查看网络请求 (--tab required)
cookies [--filter <str>] 查看页面 cookies (--tab required)
source grep <pattern> 搜索已加载 JS 源码 (--tab required)
console [--clear] 查看/清空控制台 (--tab required)
errors [--clear] 查看/清空 JS 错误 (--tab required)
trace start [--tab <id>] 录制操作+网络的统一时间线
Expand All @@ -114,7 +121,8 @@ bb-browser - AI Agent 浏览器自动化工具
--tab <tabId> 指定操作的标签页 ID
--since <seq> 增量查询(network/console/errors/trace events)
--type <t> 过滤事件类型(trace events: action/request/response)
--filter <str> URL 或文字过滤(trace events/network requests)
--filter <str> URL 或文字过滤(trace events/network requests/cookies)
--exclude-static 排除静态资源(trace events/network requests)
--limit <n> 限制返回条数
--request-id <id> 请求 ID(trace body)
--help, -h 显示帮助信息
Expand Down Expand Up @@ -253,6 +261,8 @@ function parseArgs(argv: string[]): ParsedArgs {
if (nextIdx < args.length) {
(result.flags as Record<string, unknown>)["request-id"] = args[nextIdx];
}
} else if (arg === "--exclude-static") {
(result.flags as Record<string, unknown>).excludeStatic = true;
} else if (arg.startsWith("-")) {
// Unknown flags, ignore
} else if (result.command === null) {
Expand Down Expand Up @@ -329,6 +339,16 @@ async function main(): Promise<void> {
break;
}

case "goto": {
const gotoUrl = parsed.args[0];
if (!gotoUrl) {
console.error("用法:bb-browser goto <url> --tab <tabId>");
process.exit(1);
}
await gotoCommand(gotoUrl, { json: parsed.flags.json, tabId: globalTabId });
break;
}

case "snap": {
await snapshotCommand({
json: parsed.flags.json,
Expand Down Expand Up @@ -575,7 +595,8 @@ async function main(): Promise<void> {
const method = methodIndex >= 0 ? process.argv[methodIndex + 1] : undefined;
const statusIndex = process.argv.findIndex(a => a === "--status");
const statusFilter = statusIndex >= 0 ? process.argv[statusIndex + 1] : undefined;
await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId, since: globalSince, method, status: statusFilter });
const excludeStatic = process.argv.includes("--exclude-static");
await networkCommand(subCommand, urlOrFilter, { json: parsed.flags.json, abort, body, withBody, tabId: globalTabId, since: globalSince, method, status: statusFilter, excludeStatic });
break;
}

Expand All @@ -591,6 +612,27 @@ async function main(): Promise<void> {
break;
}

case "cookies": {
const f = parsed.flags as Record<string, unknown>;
await cookiesCommand({
json: parsed.flags.json,
tabId: globalTabId,
filter: f.filter as string | undefined,
});
break;
}

case "source": {
const srcSubCmd = parsed.args[0];
const srcPattern = parsed.args[1];
if (!srcSubCmd || !srcPattern) {
console.error("用法:bb-browser source grep <pattern> --tab <tabId>");
process.exit(1);
}
await sourceCmd(srcSubCmd, srcPattern, { json: parsed.flags.json, tabId: globalTabId });
break;
}

case "trace": {
const subCmd = parsed.args[0] as 'start' | 'stop' | 'status' | 'events' | 'body' | undefined;
if (!subCmd || !['start', 'stop', 'status', 'events', 'body'].includes(subCmd)) {
Expand All @@ -606,6 +648,7 @@ async function main(): Promise<void> {
filter: f.filter as string | undefined,
limit: f.limit as number | undefined,
requestId: (f["request-id"] as string | undefined) || parsed.args[1],
excludeStatic: f.excludeStatic as boolean | undefined,
});
break;
}
Expand Down
Loading
Loading