mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 21:51:36 +00:00
refactor: route browser control via gateway/node
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import type { Command } from "commander";
|
||||
import { browserAct } from "../../browser/client-actions.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import type { BrowserParentOpts } from "../browser-cli-shared.js";
|
||||
import { requireRef, resolveBrowserActionContext } from "./shared.js";
|
||||
import { callBrowserAct, requireRef, resolveBrowserActionContext } from "./shared.js";
|
||||
|
||||
export function registerBrowserElementCommands(
|
||||
browser: Command,
|
||||
@@ -18,7 +17,7 @@ export function registerBrowserElementCommands(
|
||||
.option("--button <left|right|middle>", "Mouse button to use")
|
||||
.option("--modifiers <list>", "Comma-separated modifiers (Shift,Alt,Meta)")
|
||||
.action(async (ref: string | undefined, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const refValue = requireRef(ref);
|
||||
if (!refValue) return;
|
||||
const modifiers = opts.modifiers
|
||||
@@ -28,9 +27,10 @@ export function registerBrowserElementCommands(
|
||||
.filter(Boolean)
|
||||
: undefined;
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "click",
|
||||
ref: refValue,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
@@ -38,8 +38,7 @@ export function registerBrowserElementCommands(
|
||||
button: opts.button?.trim() || undefined,
|
||||
modifiers,
|
||||
},
|
||||
{ profile },
|
||||
);
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -61,13 +60,14 @@ export function registerBrowserElementCommands(
|
||||
.option("--slowly", "Type slowly (human-like)", false)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (ref: string | undefined, text: string, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const refValue = requireRef(ref);
|
||||
if (!refValue) return;
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "type",
|
||||
ref: refValue,
|
||||
text,
|
||||
@@ -75,8 +75,7 @@ export function registerBrowserElementCommands(
|
||||
slowly: Boolean(opts.slowly),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
{ profile },
|
||||
);
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -94,13 +93,13 @@ export function registerBrowserElementCommands(
|
||||
.argument("<key>", "Key to press (e.g. Enter)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (key: string, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{ kind: "press", key, targetId: opts.targetId?.trim() || undefined },
|
||||
{ profile },
|
||||
);
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: { kind: "press", key, targetId: opts.targetId?.trim() || undefined },
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -118,13 +117,13 @@ export function registerBrowserElementCommands(
|
||||
.argument("<ref>", "Ref id from snapshot")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (ref: string, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{ kind: "hover", ref, targetId: opts.targetId?.trim() || undefined },
|
||||
{ profile },
|
||||
);
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: { kind: "hover", ref, targetId: opts.targetId?.trim() || undefined },
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -145,20 +144,21 @@ export function registerBrowserElementCommands(
|
||||
Number(v),
|
||||
)
|
||||
.action(async (ref: string | undefined, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const refValue = requireRef(ref);
|
||||
if (!refValue) return;
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "scrollIntoView",
|
||||
ref: refValue,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
},
|
||||
{ profile },
|
||||
);
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -177,18 +177,18 @@ export function registerBrowserElementCommands(
|
||||
.argument("<endRef>", "End ref id")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (startRef: string, endRef: string, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "drag",
|
||||
startRef,
|
||||
endRef,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
{ profile },
|
||||
);
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -207,18 +207,18 @@ export function registerBrowserElementCommands(
|
||||
.argument("<values...>", "Option values to select")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (ref: string, values: string[], opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "select",
|
||||
ref,
|
||||
values,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
{ profile },
|
||||
);
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import {
|
||||
browserArmDialog,
|
||||
browserArmFileChooser,
|
||||
browserDownload,
|
||||
browserWaitForDownload,
|
||||
} from "../../browser/client-actions.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import type { BrowserParentOpts } from "../browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "../browser-cli-shared.js";
|
||||
import { resolveBrowserActionContext } from "./shared.js";
|
||||
import { shortenHomePath } from "../../utils.js";
|
||||
|
||||
@@ -29,17 +23,26 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
(v: string) => Number(v),
|
||||
)
|
||||
.action(async (paths: string[], opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await browserArmFileChooser(baseUrl, {
|
||||
paths,
|
||||
ref: opts.ref?.trim() || undefined,
|
||||
inputRef: opts.inputRef?.trim() || undefined,
|
||||
element: opts.element?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
profile,
|
||||
});
|
||||
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined;
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/hooks/file-chooser",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
paths,
|
||||
ref: opts.ref?.trim() || undefined,
|
||||
inputRef: opts.inputRef?.trim() || undefined,
|
||||
element: opts.element?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -62,14 +65,23 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
(v: string) => Number(v),
|
||||
)
|
||||
.action(async (outPath: string | undefined, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await browserWaitForDownload(baseUrl, {
|
||||
path: outPath?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
profile,
|
||||
});
|
||||
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined;
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/wait/download",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
path: outPath?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -93,15 +105,24 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
(v: string) => Number(v),
|
||||
)
|
||||
.action(async (ref: string, outPath: string, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await browserDownload(baseUrl, {
|
||||
ref,
|
||||
path: outPath,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
profile,
|
||||
});
|
||||
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined;
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/download",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
ref,
|
||||
path: outPath,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -126,7 +147,7 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
(v: string) => Number(v),
|
||||
)
|
||||
.action(async (opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const accept = opts.accept ? true : opts.dismiss ? false : undefined;
|
||||
if (accept === undefined) {
|
||||
defaultRuntime.error(danger("Specify --accept or --dismiss"));
|
||||
@@ -134,13 +155,22 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await browserArmDialog(baseUrl, {
|
||||
accept,
|
||||
promptText: opts.prompt?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
profile,
|
||||
});
|
||||
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined;
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/hooks/dialog",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
accept,
|
||||
promptText: opts.prompt?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Command } from "commander";
|
||||
import { browserAct } from "../../browser/client-actions.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import type { BrowserParentOpts } from "../browser-cli-shared.js";
|
||||
import { readFields, resolveBrowserActionContext } from "./shared.js";
|
||||
import { callBrowserAct, readFields, resolveBrowserActionContext } from "./shared.js";
|
||||
|
||||
export function registerBrowserFormWaitEvalCommands(
|
||||
browser: Command,
|
||||
@@ -16,21 +15,21 @@ export function registerBrowserFormWaitEvalCommands(
|
||||
.option("--fields-file <path>", "Read JSON array from a file")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const fields = await readFields({
|
||||
fields: opts.fields,
|
||||
fieldsFile: opts.fieldsFile,
|
||||
});
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "fill",
|
||||
fields,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
{ profile },
|
||||
);
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -59,16 +58,18 @@ export function registerBrowserFormWaitEvalCommands(
|
||||
)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (selector: string | undefined, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const sel = selector?.trim() || undefined;
|
||||
const load =
|
||||
opts.load === "load" || opts.load === "domcontentloaded" || opts.load === "networkidle"
|
||||
? (opts.load as "load" | "domcontentloaded" | "networkidle")
|
||||
: undefined;
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{
|
||||
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined;
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "wait",
|
||||
timeMs: Number.isFinite(opts.time) ? opts.time : undefined,
|
||||
text: opts.text?.trim() || undefined,
|
||||
@@ -78,10 +79,10 @@ export function registerBrowserFormWaitEvalCommands(
|
||||
loadState: load,
|
||||
fn: opts.fn?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
timeoutMs,
|
||||
},
|
||||
{ profile },
|
||||
);
|
||||
timeoutMs,
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -100,23 +101,23 @@ export function registerBrowserFormWaitEvalCommands(
|
||||
.option("--ref <id>", "Ref from snapshot")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
if (!opts.fn) {
|
||||
defaultRuntime.error(danger("Missing --fn"));
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
{
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "evaluate",
|
||||
fn: opts.fn,
|
||||
ref: opts.ref?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
{ profile },
|
||||
);
|
||||
});
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { browserAct, browserNavigate } from "../../browser/client-actions.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import type { BrowserParentOpts } from "../browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "../browser-cli-shared.js";
|
||||
import { requireRef, resolveBrowserActionContext } from "./shared.js";
|
||||
|
||||
export function registerBrowserNavigationCommands(
|
||||
@@ -15,13 +14,21 @@ export function registerBrowserNavigationCommands(
|
||||
.argument("<url>", "URL to navigate to")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (url: string, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await browserNavigate(baseUrl, {
|
||||
url,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{ url?: string }>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/navigate",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
url,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -40,22 +47,27 @@ export function registerBrowserNavigationCommands(
|
||||
.argument("<height>", "Viewport height", (v: string) => Number(v))
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (width: number, height: number, opts, cmd) => {
|
||||
const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
if (!Number.isFinite(width) || !Number.isFinite(height)) {
|
||||
defaultRuntime.error(danger("width and height must be numbers"));
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
kind: "resize",
|
||||
width,
|
||||
height,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
method: "POST",
|
||||
path: "/act",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
kind: "resize",
|
||||
width,
|
||||
height,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ profile },
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import type { Command } from "commander";
|
||||
import { resolveBrowserControlUrl } from "../../browser/client.js";
|
||||
import type { BrowserFormField } from "../../browser/client-actions-core.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import type { BrowserParentOpts } from "../browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "../browser-cli-shared.js";
|
||||
|
||||
export type BrowserActionContext = {
|
||||
parent: BrowserParentOpts;
|
||||
baseUrl: string;
|
||||
profile: string | undefined;
|
||||
};
|
||||
|
||||
@@ -16,9 +14,26 @@ export function resolveBrowserActionContext(
|
||||
parentOpts: (cmd: Command) => BrowserParentOpts,
|
||||
): BrowserActionContext {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
return { parent, baseUrl, profile };
|
||||
return { parent, profile };
|
||||
}
|
||||
|
||||
export async function callBrowserAct<T = unknown>(params: {
|
||||
parent: BrowserParentOpts;
|
||||
profile?: string;
|
||||
body: Record<string, unknown>;
|
||||
timeoutMs?: number;
|
||||
}): Promise<T> {
|
||||
return await callBrowserRequest<T>(
|
||||
params.parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/act",
|
||||
query: params.profile ? { profile: params.profile } : undefined,
|
||||
body: params.body,
|
||||
},
|
||||
{ timeoutMs: params.timeoutMs ?? 20000 },
|
||||
);
|
||||
}
|
||||
|
||||
export function requireRef(ref: string | undefined) {
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { resolveBrowserControlUrl } from "../browser/client.js";
|
||||
import {
|
||||
browserConsoleMessages,
|
||||
browserPdfSave,
|
||||
browserResponseBody,
|
||||
} from "../browser/client-actions.js";
|
||||
import { danger } from "../globals.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { runCommandWithRuntime } from "./cli-utils.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
|
||||
@@ -29,14 +23,21 @@ export function registerBrowserActionObserveCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserObserve(async () => {
|
||||
const result = await browserConsoleMessages(baseUrl, {
|
||||
level: opts.level?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{ messages: unknown[] }>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/console",
|
||||
query: {
|
||||
level: opts.level?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -51,13 +52,18 @@ export function registerBrowserActionObserveCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserObserve(async () => {
|
||||
const result = await browserPdfSave(baseUrl, {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{ path: string }>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/pdf",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: { targetId: opts.targetId?.trim() || undefined },
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -81,16 +87,25 @@ export function registerBrowserActionObserveCommands(
|
||||
)
|
||||
.action(async (url: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserObserve(async () => {
|
||||
const result = await browserResponseBody(baseUrl, {
|
||||
url,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
maxChars: Number.isFinite(opts.maxChars) ? opts.maxChars : undefined,
|
||||
profile,
|
||||
});
|
||||
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined;
|
||||
const maxChars = Number.isFinite(opts.maxChars) ? opts.maxChars : undefined;
|
||||
const result = await callBrowserRequest<{ response: { body: string } }>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/response/body",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
url,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs,
|
||||
maxChars,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
import { resolveBrowserControlUrl } from "../browser/client.js";
|
||||
import {
|
||||
browserHighlight,
|
||||
browserPageErrors,
|
||||
browserRequests,
|
||||
browserTraceStart,
|
||||
browserTraceStop,
|
||||
} from "../browser/client-actions.js";
|
||||
import { danger } from "../globals.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { runCommandWithRuntime } from "./cli-utils.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
|
||||
@@ -32,14 +24,21 @@ export function registerBrowserDebugCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (ref: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserDebug(async () => {
|
||||
const result = await browserHighlight(baseUrl, {
|
||||
ref: ref.trim(),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/highlight",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
ref: ref.trim(),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -55,14 +54,23 @@ export function registerBrowserDebugCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserDebug(async () => {
|
||||
const result = await browserPageErrors(baseUrl, {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
clear: Boolean(opts.clear),
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{
|
||||
errors: Array<{ timestamp: string; name?: string; message: string }>;
|
||||
}>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/errors",
|
||||
query: {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
clear: Boolean(opts.clear),
|
||||
profile,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -87,15 +95,31 @@ export function registerBrowserDebugCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserDebug(async () => {
|
||||
const result = await browserRequests(baseUrl, {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
filter: opts.filter?.trim() || undefined,
|
||||
clear: Boolean(opts.clear),
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{
|
||||
requests: Array<{
|
||||
timestamp: string;
|
||||
method: string;
|
||||
status?: number;
|
||||
ok?: boolean;
|
||||
url: string;
|
||||
failureText?: string;
|
||||
}>;
|
||||
}>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/requests",
|
||||
query: {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
filter: opts.filter?.trim() || undefined,
|
||||
clear: Boolean(opts.clear),
|
||||
profile,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -128,16 +152,23 @@ export function registerBrowserDebugCommands(
|
||||
.option("--sources", "Include sources (bigger traces)", false)
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserDebug(async () => {
|
||||
const result = await browserTraceStart(baseUrl, {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
screenshots: Boolean(opts.screenshots),
|
||||
snapshots: Boolean(opts.snapshots),
|
||||
sources: Boolean(opts.sources),
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/trace/start",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
screenshots: Boolean(opts.screenshots),
|
||||
snapshots: Boolean(opts.snapshots),
|
||||
sources: Boolean(opts.sources),
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -153,14 +184,21 @@ export function registerBrowserDebugCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserDebug(async () => {
|
||||
const result = await browserTraceStop(baseUrl, {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
path: opts.out?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{ path: string }>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/trace/stop",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
path: opts.out?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
import { browserSnapshot, resolveBrowserControlUrl } from "../browser/client.js";
|
||||
import { browserScreenshotAction } from "../browser/client-actions.js";
|
||||
import type { SnapshotResult } from "../browser/client.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { danger } from "../globals.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
|
||||
export function registerBrowserInspectCommands(
|
||||
browser: Command,
|
||||
@@ -22,17 +21,24 @@ export function registerBrowserInspectCommands(
|
||||
.option("--type <png|jpeg>", "Output type (default: png)", "png")
|
||||
.action(async (targetId: string | undefined, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
try {
|
||||
const result = await browserScreenshotAction(baseUrl, {
|
||||
targetId: targetId?.trim() || undefined,
|
||||
fullPage: Boolean(opts.fullPage),
|
||||
ref: opts.ref?.trim() || undefined,
|
||||
element: opts.element?.trim() || undefined,
|
||||
type: opts.type === "jpeg" ? "jpeg" : "png",
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{ path: string }>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/screenshot",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId: targetId?.trim() || undefined,
|
||||
fullPage: Boolean(opts.fullPage),
|
||||
ref: opts.ref?.trim() || undefined,
|
||||
element: opts.element?.trim() || undefined,
|
||||
type: opts.type === "jpeg" ? "jpeg" : "png",
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -61,7 +67,6 @@ export function registerBrowserInspectCommands(
|
||||
.option("--out <path>", "Write snapshot to a file")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
const format = opts.format === "aria" ? "aria" : "ai";
|
||||
const configMode =
|
||||
@@ -70,19 +75,28 @@ export function registerBrowserInspectCommands(
|
||||
: undefined;
|
||||
const mode = opts.efficient === true || opts.mode === "efficient" ? "efficient" : configMode;
|
||||
try {
|
||||
const result = await browserSnapshot(baseUrl, {
|
||||
const query: Record<string, string | number | boolean | undefined> = {
|
||||
format,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
limit: Number.isFinite(opts.limit) ? opts.limit : undefined,
|
||||
interactive: Boolean(opts.interactive) || undefined,
|
||||
compact: Boolean(opts.compact) || undefined,
|
||||
interactive: opts.interactive ? true : undefined,
|
||||
compact: opts.compact ? true : undefined,
|
||||
depth: Number.isFinite(opts.depth) ? opts.depth : undefined,
|
||||
selector: opts.selector?.trim() || undefined,
|
||||
frame: opts.frame?.trim() || undefined,
|
||||
labels: Boolean(opts.labels) || undefined,
|
||||
labels: opts.labels ? true : undefined,
|
||||
mode,
|
||||
profile,
|
||||
});
|
||||
};
|
||||
const result = await callBrowserRequest<SnapshotResult>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/snapshot",
|
||||
query,
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
|
||||
if (opts.out) {
|
||||
const fs = await import("node:fs/promises");
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import type { Command } from "commander";
|
||||
import type { BrowserTab } from "../browser/client.js";
|
||||
import {
|
||||
browserCloseTab,
|
||||
browserCreateProfile,
|
||||
browserDeleteProfile,
|
||||
browserFocusTab,
|
||||
browserOpenTab,
|
||||
browserProfiles,
|
||||
browserResetProfile,
|
||||
browserStart,
|
||||
browserStatus,
|
||||
browserStop,
|
||||
browserTabAction,
|
||||
browserTabs,
|
||||
resolveBrowserControlUrl,
|
||||
import type {
|
||||
BrowserCreateProfileResult,
|
||||
BrowserDeleteProfileResult,
|
||||
BrowserResetProfileResult,
|
||||
BrowserStatus,
|
||||
BrowserTab,
|
||||
ProfileStatus,
|
||||
} from "../browser/client.js";
|
||||
import { browserAct } from "../browser/client-actions-core.js";
|
||||
import { danger, info } from "../globals.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { runCommandWithRuntime } from "./cli-utils.js";
|
||||
|
||||
function runBrowserCommand(action: () => Promise<void>) {
|
||||
@@ -38,11 +29,18 @@ export function registerBrowserManageCommands(
|
||||
.description("Show browser status")
|
||||
.action(async (_opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
await runBrowserCommand(async () => {
|
||||
const status = await browserStatus(baseUrl, {
|
||||
profile: parent?.browserProfile,
|
||||
});
|
||||
const status = await callBrowserRequest<BrowserStatus>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/",
|
||||
query: parent?.browserProfile ? { profile: parent.browserProfile } : undefined,
|
||||
},
|
||||
{
|
||||
timeoutMs: 1500,
|
||||
},
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(status, null, 2));
|
||||
return;
|
||||
@@ -54,7 +52,6 @@ export function registerBrowserManageCommands(
|
||||
`profile: ${status.profile ?? "clawd"}`,
|
||||
`enabled: ${status.enabled}`,
|
||||
`running: ${status.running}`,
|
||||
`controlUrl: ${status.controlUrl}`,
|
||||
`cdpPort: ${status.cdpPort}`,
|
||||
`cdpUrl: ${status.cdpUrl ?? `http://127.0.0.1:${status.cdpPort}`}`,
|
||||
`browser: ${status.chosenBrowser ?? "unknown"}`,
|
||||
@@ -72,11 +69,26 @@ export function registerBrowserManageCommands(
|
||||
.description("Start the browser (no-op if already running)")
|
||||
.action(async (_opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
await browserStart(baseUrl, { profile });
|
||||
const status = await browserStatus(baseUrl, { profile });
|
||||
await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/start",
|
||||
query: profile ? { profile } : undefined,
|
||||
},
|
||||
{ timeoutMs: 15000 },
|
||||
);
|
||||
const status = await callBrowserRequest<BrowserStatus>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/",
|
||||
query: profile ? { profile } : undefined,
|
||||
},
|
||||
{ timeoutMs: 1500 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(status, null, 2));
|
||||
return;
|
||||
@@ -91,11 +103,26 @@ export function registerBrowserManageCommands(
|
||||
.description("Stop the browser (best-effort)")
|
||||
.action(async (_opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
await browserStop(baseUrl, { profile });
|
||||
const status = await browserStatus(baseUrl, { profile });
|
||||
await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/stop",
|
||||
query: profile ? { profile } : undefined,
|
||||
},
|
||||
{ timeoutMs: 15000 },
|
||||
);
|
||||
const status = await callBrowserRequest<BrowserStatus>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/",
|
||||
query: profile ? { profile } : undefined,
|
||||
},
|
||||
{ timeoutMs: 1500 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(status, null, 2));
|
||||
return;
|
||||
@@ -110,10 +137,17 @@ export function registerBrowserManageCommands(
|
||||
.description("Reset browser profile (moves it to Trash)")
|
||||
.action(async (_opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserResetProfile(baseUrl, { profile });
|
||||
const result = await callBrowserRequest<BrowserResetProfileResult>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/reset-profile",
|
||||
query: profile ? { profile } : undefined,
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -132,10 +166,18 @@ export function registerBrowserManageCommands(
|
||||
.description("List open tabs")
|
||||
.action(async (_opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const tabs = await browserTabs(baseUrl, { profile });
|
||||
const result = await callBrowserRequest<{ running: boolean; tabs: BrowserTab[] }>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/tabs",
|
||||
query: profile ? { profile } : undefined,
|
||||
},
|
||||
{ timeoutMs: 3000 },
|
||||
);
|
||||
const tabs = result.tabs ?? [];
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify({ tabs }, null, 2));
|
||||
return;
|
||||
@@ -159,13 +201,20 @@ export function registerBrowserManageCommands(
|
||||
.description("Tab shortcuts (index-based)")
|
||||
.action(async (_opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = (await browserTabAction(baseUrl, {
|
||||
action: "list",
|
||||
profile,
|
||||
})) as { ok: true; tabs: BrowserTab[] };
|
||||
const result = await callBrowserRequest<{ ok: true; tabs: BrowserTab[] }>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/tabs/action",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
action: "list",
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 10_000 },
|
||||
);
|
||||
const tabs = result.tabs ?? [];
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify({ tabs }, null, 2));
|
||||
@@ -190,13 +239,18 @@ export function registerBrowserManageCommands(
|
||||
.description("Open a new tab (about:blank)")
|
||||
.action(async (_opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserTabAction(baseUrl, {
|
||||
action: "new",
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/tabs/action",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: { action: "new" },
|
||||
},
|
||||
{ timeoutMs: 10_000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -211,7 +265,6 @@ export function registerBrowserManageCommands(
|
||||
.argument("<index>", "Tab index (1-based)", (v: string) => Number(v))
|
||||
.action(async (index: number, _opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
if (!Number.isFinite(index) || index < 1) {
|
||||
defaultRuntime.error(danger("index must be a positive number"));
|
||||
@@ -219,11 +272,16 @@ export function registerBrowserManageCommands(
|
||||
return;
|
||||
}
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserTabAction(baseUrl, {
|
||||
action: "select",
|
||||
index: Math.floor(index) - 1,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/tabs/action",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: { action: "select", index: Math.floor(index) - 1 },
|
||||
},
|
||||
{ timeoutMs: 10_000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -238,7 +296,6 @@ export function registerBrowserManageCommands(
|
||||
.argument("[index]", "Tab index (1-based)", (v: string) => Number(v))
|
||||
.action(async (index: number | undefined, _opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
const idx =
|
||||
typeof index === "number" && Number.isFinite(index) ? Math.floor(index) - 1 : undefined;
|
||||
@@ -248,11 +305,16 @@ export function registerBrowserManageCommands(
|
||||
return;
|
||||
}
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserTabAction(baseUrl, {
|
||||
action: "close",
|
||||
index: idx,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/tabs/action",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: { action: "close", index: idx },
|
||||
},
|
||||
{ timeoutMs: 10_000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -267,10 +329,18 @@ export function registerBrowserManageCommands(
|
||||
.argument("<url>", "URL to open")
|
||||
.action(async (url: string, _opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const tab = await browserOpenTab(baseUrl, url, { profile });
|
||||
const tab = await callBrowserRequest<BrowserTab>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/tabs/open",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: { url },
|
||||
},
|
||||
{ timeoutMs: 15000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(tab, null, 2));
|
||||
return;
|
||||
@@ -285,10 +355,18 @@ export function registerBrowserManageCommands(
|
||||
.argument("<targetId>", "Target id or unique prefix")
|
||||
.action(async (targetId: string, _opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
await browserFocusTab(baseUrl, targetId, { profile });
|
||||
await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/tabs/focus",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: { targetId },
|
||||
},
|
||||
{ timeoutMs: 5000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify({ ok: true }, null, 2));
|
||||
return;
|
||||
@@ -303,13 +381,29 @@ export function registerBrowserManageCommands(
|
||||
.argument("[targetId]", "Target id or unique prefix (optional)")
|
||||
.action(async (targetId: string | undefined, _opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
if (targetId?.trim()) {
|
||||
await browserCloseTab(baseUrl, targetId.trim(), { profile });
|
||||
await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/tabs/${encodeURIComponent(targetId.trim())}`,
|
||||
query: profile ? { profile } : undefined,
|
||||
},
|
||||
{ timeoutMs: 5000 },
|
||||
);
|
||||
} else {
|
||||
await browserAct(baseUrl, { kind: "close" }, { profile });
|
||||
await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/act",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: { kind: "close" },
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
}
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify({ ok: true }, null, 2));
|
||||
@@ -325,9 +419,16 @@ export function registerBrowserManageCommands(
|
||||
.description("List all browser profiles")
|
||||
.action(async (_opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
await runBrowserCommand(async () => {
|
||||
const profiles = await browserProfiles(baseUrl);
|
||||
const result = await callBrowserRequest<{ profiles: ProfileStatus[] }>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/profiles",
|
||||
},
|
||||
{ timeoutMs: 3000 },
|
||||
);
|
||||
const profiles = result.profiles ?? [];
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify({ profiles }, null, 2));
|
||||
return;
|
||||
@@ -361,14 +462,21 @@ export function registerBrowserManageCommands(
|
||||
.action(
|
||||
async (opts: { name: string; color?: string; cdpUrl?: string; driver?: string }, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserCreateProfile(baseUrl, {
|
||||
name: opts.name,
|
||||
color: opts.color,
|
||||
cdpUrl: opts.cdpUrl,
|
||||
driver: opts.driver === "extension" ? "extension" : undefined,
|
||||
});
|
||||
const result = await callBrowserRequest<BrowserCreateProfileResult>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/profiles/create",
|
||||
body: {
|
||||
name: opts.name,
|
||||
color: opts.color,
|
||||
cdpUrl: opts.cdpUrl,
|
||||
driver: opts.driver === "extension" ? "extension" : undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 10_000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -391,9 +499,15 @@ export function registerBrowserManageCommands(
|
||||
.requiredOption("--name <name>", "Profile name to delete")
|
||||
.action(async (opts: { name: string }, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserDeleteProfile(baseUrl, opts.name);
|
||||
const result = await callBrowserRequest<BrowserDeleteProfileResult>(
|
||||
parent,
|
||||
{
|
||||
method: "DELETE",
|
||||
path: `/profiles/${encodeURIComponent(opts.name)}`,
|
||||
},
|
||||
{ timeoutMs: 20_000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { danger, info } from "../globals.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveBrowserConfig, resolveProfile } from "../browser/config.js";
|
||||
import { startBrowserBridgeServer, stopBrowserBridgeServer } from "../browser/bridge-server.js";
|
||||
import { ensureChromeExtensionRelayServer } from "../browser/extension-relay.js";
|
||||
|
||||
function isLoopbackBindHost(host: string) {
|
||||
const h = host.trim().toLowerCase();
|
||||
return h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "[::1]";
|
||||
}
|
||||
|
||||
function parsePort(raw: unknown): number | null {
|
||||
const v = typeof raw === "string" ? raw.trim() : "";
|
||||
if (!v) return null;
|
||||
const n = Number.parseInt(v, 10);
|
||||
if (!Number.isFinite(n) || n < 0 || n > 65535) return null;
|
||||
return n;
|
||||
}
|
||||
|
||||
export function registerBrowserServeCommands(
|
||||
browser: Command,
|
||||
_parentOpts: (cmd: Command) => unknown,
|
||||
) {
|
||||
browser
|
||||
.command("serve")
|
||||
.description("Run a standalone browser control server (for remote gateways)")
|
||||
.option("--bind <host>", "Bind host (default: 127.0.0.1)")
|
||||
.option("--port <port>", "Bind port (default: from browser.controlUrl)")
|
||||
.option(
|
||||
"--token <token>",
|
||||
"Require Authorization: Bearer <token> (required when binding non-loopback)",
|
||||
)
|
||||
.action(async (opts: { bind?: string; port?: string; token?: string }) => {
|
||||
const cfg = loadConfig();
|
||||
const resolved = resolveBrowserConfig(cfg.browser);
|
||||
if (!resolved.enabled) {
|
||||
defaultRuntime.error(
|
||||
danger("Browser control is disabled. Set browser.enabled=true and try again."),
|
||||
);
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
|
||||
const host = (opts.bind ?? "127.0.0.1").trim();
|
||||
const port = parsePort(opts.port) ?? resolved.controlPort;
|
||||
|
||||
const envToken = process.env.CLAWDBOT_BROWSER_CONTROL_TOKEN?.trim();
|
||||
const authToken = (opts.token ?? envToken ?? resolved.controlToken)?.trim();
|
||||
if (!isLoopbackBindHost(host) && !authToken) {
|
||||
defaultRuntime.error(
|
||||
danger(
|
||||
`Refusing to bind browser control on ${host} without --token (or CLAWDBOT_BROWSER_CONTROL_TOKEN, or browser.controlToken).`,
|
||||
),
|
||||
);
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
|
||||
const bridge = await startBrowserBridgeServer({
|
||||
resolved,
|
||||
host,
|
||||
port,
|
||||
...(authToken ? { authToken } : {}),
|
||||
});
|
||||
|
||||
// If any profile uses the Chrome extension relay, start the local relay server eagerly
|
||||
// so the extension can connect before the first browser action.
|
||||
for (const name of Object.keys(resolved.profiles)) {
|
||||
const profile = resolveProfile(resolved, name);
|
||||
if (!profile || profile.driver !== "extension") continue;
|
||||
await ensureChromeExtensionRelayServer({ cdpUrl: profile.cdpUrl }).catch((err) => {
|
||||
defaultRuntime.error(
|
||||
danger(`Chrome extension relay init failed for profile "${name}": ${String(err)}`),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
defaultRuntime.log(
|
||||
info(
|
||||
[
|
||||
`🦞 Browser control listening on ${bridge.baseUrl}/`,
|
||||
authToken ? "Auth: Bearer token required." : "Auth: off (loopback only).",
|
||||
"",
|
||||
"Paste on the Gateway (clawdbot.json):",
|
||||
JSON.stringify(
|
||||
{
|
||||
browser: {
|
||||
enabled: true,
|
||||
controlUrl: bridge.baseUrl,
|
||||
...(authToken ? { controlToken: authToken } : {}),
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
...(authToken
|
||||
? [
|
||||
"",
|
||||
"Or use env on the Gateway (instead of controlToken in config):",
|
||||
`export CLAWDBOT_BROWSER_CONTROL_TOKEN=${JSON.stringify(authToken)}`,
|
||||
]
|
||||
: []),
|
||||
].join("\n"),
|
||||
),
|
||||
);
|
||||
|
||||
let shuttingDown = false;
|
||||
const shutdown = async (signal: string) => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
defaultRuntime.log(info(`Shutting down (${signal})...`));
|
||||
await stopBrowserBridgeServer(bridge.server).catch(() => {});
|
||||
process.exit(0);
|
||||
};
|
||||
process.once("SIGINT", () => void shutdown("SIGINT"));
|
||||
process.once("SIGTERM", () => void shutdown("SIGTERM"));
|
||||
|
||||
await new Promise(() => {});
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,58 @@
|
||||
export type BrowserParentOpts = {
|
||||
url?: string;
|
||||
import type { GatewayRpcOpts } from "./gateway-rpc.js";
|
||||
import { callGatewayFromCli } from "./gateway-rpc.js";
|
||||
|
||||
export type BrowserParentOpts = GatewayRpcOpts & {
|
||||
json?: boolean;
|
||||
browserProfile?: string;
|
||||
};
|
||||
|
||||
type BrowserRequestParams = {
|
||||
method: "GET" | "POST" | "DELETE";
|
||||
path: string;
|
||||
query?: Record<string, string | number | boolean | undefined>;
|
||||
body?: unknown;
|
||||
};
|
||||
|
||||
function normalizeQuery(query: BrowserRequestParams["query"]): Record<string, string> | undefined {
|
||||
if (!query) return undefined;
|
||||
const out: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (value === undefined) continue;
|
||||
out[key] = String(value);
|
||||
}
|
||||
return Object.keys(out).length ? out : undefined;
|
||||
}
|
||||
|
||||
export async function callBrowserRequest<T>(
|
||||
opts: BrowserParentOpts,
|
||||
params: BrowserRequestParams,
|
||||
extra?: { timeoutMs?: number; progress?: boolean },
|
||||
): Promise<T> {
|
||||
const resolvedTimeoutMs =
|
||||
typeof extra?.timeoutMs === "number" && Number.isFinite(extra.timeoutMs)
|
||||
? Math.max(1, Math.floor(extra.timeoutMs))
|
||||
: typeof opts.timeout === "string"
|
||||
? Number.parseInt(opts.timeout, 10)
|
||||
: undefined;
|
||||
const resolvedTimeout =
|
||||
typeof resolvedTimeoutMs === "number" && Number.isFinite(resolvedTimeoutMs)
|
||||
? resolvedTimeoutMs
|
||||
: undefined;
|
||||
const timeout = typeof resolvedTimeout === "number" ? String(resolvedTimeout) : opts.timeout;
|
||||
const payload = await callGatewayFromCli(
|
||||
"browser.request",
|
||||
{ ...opts, timeout },
|
||||
{
|
||||
method: params.method,
|
||||
path: params.path,
|
||||
query: normalizeQuery(params.query),
|
||||
body: params.body,
|
||||
timeoutMs: resolvedTimeout,
|
||||
},
|
||||
{ progress: extra?.progress },
|
||||
);
|
||||
if (payload === undefined) {
|
||||
throw new Error("Unexpected browser.request response");
|
||||
}
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
import { resolveBrowserControlUrl } from "../browser/client.js";
|
||||
import {
|
||||
browserCookies,
|
||||
browserCookiesClear,
|
||||
browserCookiesSet,
|
||||
browserStorageClear,
|
||||
browserStorageGet,
|
||||
browserStorageSet,
|
||||
} from "../browser/client-actions.js";
|
||||
import { danger } from "../globals.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
|
||||
export function registerBrowserCookiesAndStorageCommands(
|
||||
browser: Command,
|
||||
@@ -23,13 +14,20 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
try {
|
||||
const result = await browserCookies(baseUrl, {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{ cookies?: unknown[] }>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/cookies",
|
||||
query: {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -50,14 +48,21 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (name: string, value: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
try {
|
||||
const result = await browserCookiesSet(baseUrl, {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
cookie: { name, value, url: opts.url },
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/cookies/set",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
cookie: { name, value, url: opts.url },
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -75,13 +80,20 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
try {
|
||||
const result = await browserCookiesClear(baseUrl, {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/cookies/clear",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -105,15 +117,21 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (key: string | undefined, opts, cmd2) => {
|
||||
const parent = parentOpts(cmd2);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
try {
|
||||
const result = await browserStorageGet(baseUrl, {
|
||||
kind,
|
||||
key: key?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest<{ values?: Record<string, string> }>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: `/storage/${kind}`,
|
||||
query: {
|
||||
key: key?.trim() || undefined,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -133,16 +151,22 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (key: string, value: string, opts, cmd2) => {
|
||||
const parent = parentOpts(cmd2);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
try {
|
||||
const result = await browserStorageSet(baseUrl, {
|
||||
kind,
|
||||
key,
|
||||
value,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: `/storage/${kind}/set`,
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
key,
|
||||
value,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -160,14 +184,20 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd2) => {
|
||||
const parent = parentOpts(cmd2);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
try {
|
||||
const result = await browserStorageClear(baseUrl, {
|
||||
kind,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: `/storage/${kind}/clear`,
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
import type { Command } from "commander";
|
||||
|
||||
import { resolveBrowserControlUrl } from "../browser/client.js";
|
||||
import {
|
||||
browserSetDevice,
|
||||
browserSetGeolocation,
|
||||
browserSetHeaders,
|
||||
browserSetHttpCredentials,
|
||||
browserSetLocale,
|
||||
browserSetMedia,
|
||||
browserSetOffline,
|
||||
browserSetTimezone,
|
||||
} from "../browser/client-actions.js";
|
||||
import { browserAct } from "../browser/client-actions-core.js";
|
||||
import { danger } from "../globals.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { parseBooleanValue } from "../utils/boolean.js";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { registerBrowserCookiesAndStorageCommands } from "./browser-cli-state.cookies-storage.js";
|
||||
import { runCommandWithRuntime } from "./cli-utils.js";
|
||||
|
||||
@@ -47,7 +35,6 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (width: number, height: number, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
if (!Number.isFinite(width) || !Number.isFinite(height)) {
|
||||
defaultRuntime.error(danger("width and height must be numbers"));
|
||||
@@ -55,15 +42,20 @@ export function registerBrowserStateCommands(
|
||||
return;
|
||||
}
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserAct(
|
||||
baseUrl,
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
kind: "resize",
|
||||
width,
|
||||
height,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
method: "POST",
|
||||
path: "/act",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
kind: "resize",
|
||||
width,
|
||||
height,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ profile },
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
@@ -80,7 +72,6 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (value: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
const offline = parseOnOff(value);
|
||||
if (offline === null) {
|
||||
@@ -89,11 +80,19 @@ export function registerBrowserStateCommands(
|
||||
return;
|
||||
}
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserSetOffline(baseUrl, {
|
||||
offline,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/offline",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
offline,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -109,7 +108,6 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const parsed = JSON.parse(String(opts.json)) as unknown;
|
||||
@@ -120,11 +118,19 @@ export function registerBrowserStateCommands(
|
||||
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
||||
if (typeof v === "string") headers[k] = v;
|
||||
}
|
||||
const result = await browserSetHeaders(baseUrl, {
|
||||
headers,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/headers",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
headers,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -142,16 +148,23 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (username: string | undefined, password: string | undefined, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserSetHttpCredentials(baseUrl, {
|
||||
username: username?.trim() || undefined,
|
||||
password,
|
||||
clear: Boolean(opts.clear),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/credentials",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
username: username?.trim() || undefined,
|
||||
password,
|
||||
clear: Boolean(opts.clear),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -171,18 +184,25 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (latitude: number | undefined, longitude: number | undefined, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserSetGeolocation(baseUrl, {
|
||||
latitude: Number.isFinite(latitude) ? latitude : undefined,
|
||||
longitude: Number.isFinite(longitude) ? longitude : undefined,
|
||||
accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined,
|
||||
origin: opts.origin?.trim() || undefined,
|
||||
clear: Boolean(opts.clear),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/geolocation",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
latitude: Number.isFinite(latitude) ? latitude : undefined,
|
||||
longitude: Number.isFinite(longitude) ? longitude : undefined,
|
||||
accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined,
|
||||
origin: opts.origin?.trim() || undefined,
|
||||
clear: Boolean(opts.clear),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -198,7 +218,6 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (value: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
const v = value.trim().toLowerCase();
|
||||
const colorScheme =
|
||||
@@ -209,11 +228,19 @@ export function registerBrowserStateCommands(
|
||||
return;
|
||||
}
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserSetMedia(baseUrl, {
|
||||
colorScheme,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/media",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
colorScheme,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -229,14 +256,21 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (timezoneId: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserSetTimezone(baseUrl, {
|
||||
timezoneId,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/timezone",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
timezoneId,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -252,14 +286,21 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (locale: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserSetLocale(baseUrl, {
|
||||
locale,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/locale",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
locale,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
@@ -275,14 +316,21 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (name: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const baseUrl = resolveBrowserControlUrl(parent?.url);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await browserSetDevice(baseUrl, {
|
||||
name,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
profile,
|
||||
});
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/device",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
name,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
|
||||
@@ -13,15 +13,14 @@ import { browserActionExamples, browserCoreExamples } from "./browser-cli-exampl
|
||||
import { registerBrowserExtensionCommands } from "./browser-cli-extension.js";
|
||||
import { registerBrowserInspectCommands } from "./browser-cli-inspect.js";
|
||||
import { registerBrowserManageCommands } from "./browser-cli-manage.js";
|
||||
import { registerBrowserServeCommands } from "./browser-cli-serve.js";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { registerBrowserStateCommands } from "./browser-cli-state.js";
|
||||
import { addGatewayClientOptions } from "./gateway-rpc.js";
|
||||
|
||||
export function registerBrowserCli(program: Command) {
|
||||
const browser = program
|
||||
.command("browser")
|
||||
.description("Manage clawd's dedicated browser (Chrome/Chromium)")
|
||||
.option("--url <url>", "Override browser control URL (default from ~/.clawdbot/clawdbot.json)")
|
||||
.option("--browser-profile <name>", "Browser profile name (default from config)")
|
||||
.option("--json", "Output machine-readable JSON", false)
|
||||
.addHelpText(
|
||||
@@ -43,11 +42,12 @@ export function registerBrowserCli(program: Command) {
|
||||
defaultRuntime.exit(1);
|
||||
});
|
||||
|
||||
addGatewayClientOptions(browser);
|
||||
|
||||
const parentOpts = (cmd: Command) => cmd.parent?.opts?.() as BrowserParentOpts;
|
||||
|
||||
registerBrowserManageCommands(browser, parentOpts);
|
||||
registerBrowserExtensionCommands(browser, parentOpts);
|
||||
registerBrowserServeCommands(browser, parentOpts);
|
||||
registerBrowserInspectCommands(browser, parentOpts);
|
||||
registerBrowserActionInputCommands(browser, parentOpts);
|
||||
registerBrowserActionObserveCommands(browser, parentOpts);
|
||||
|
||||
Reference in New Issue
Block a user