UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,270 lines (1,256 loc) 66.6 kB
import { Dt as theme, Yt as resolveStateDir, _ as defaultRuntime, a as parseBooleanValue, gt as info, ht as danger, lt as shortenHomePath } from "./entry.js"; import "./auth-profiles-DFa1zzNy.js"; import { t as formatCliCommand } from "./command-format-D3syQOZg.js"; import "./exec-CBKBIMpA.js"; import "./agent-scope-RzK9Zcks.js"; import "./github-copilot-token-DuFIqfeC.js"; import "./manifest-registry-DS2iK5AZ.js"; import { i as loadConfig } from "./config-B2kL1ciP.js"; import "./client-CDjZdZtI.js"; import "./call-DXhJGwEy.js"; import "./message-channel-CVHJDItx.js"; import "./pairing-token-Byh6drgn.js"; import "./redact-C2s6sr73.js"; import "./errors-CFEPAPWc.js"; import "./fs-safe-3YwsxSr5.js"; import { i as resolveExistingPathsWithinRoot, r as DEFAULT_UPLOAD_DIR } from "./paths-CXKNzNjU.js"; import { t as movePathToTrash } from "./trash-DVLJ0k6q.js"; import { t as formatDocsLink } from "./links-CW8Bx7rK.js"; import { n as runCommandWithRuntime } from "./cli-utils-CCaEbxAz.js"; import { t as formatHelpExamples } from "./help-format-B0pWGnZs.js"; import "./progress-BAHiAaDW.js"; import { n as inheritOptionFromParent } from "./command-options-CRqZtG5w.js"; import { n as callGatewayFromCli, t as addGatewayClientOptions } from "./gateway-rpc-WyCrzUHK.js"; import { t as copyToClipboard } from "./clipboard-33311YqP.js"; import path from "node:path"; import fs from "node:fs"; import { fileURLToPath } from "node:url"; //#region src/cli/browser-cli-shared.ts function normalizeQuery(query) { if (!query) return; const out = {}; for (const [key, value] of Object.entries(query)) { if (value === void 0) continue; out[key] = String(value); } return Object.keys(out).length ? out : void 0; } async function callBrowserRequest(opts, params, extra) { 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) : void 0; const resolvedTimeout = typeof resolvedTimeoutMs === "number" && Number.isFinite(resolvedTimeoutMs) ? resolvedTimeoutMs : void 0; 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 === void 0) throw new Error("Unexpected browser.request response"); return payload; } async function callBrowserResize(opts, params, extra) { return callBrowserRequest(opts, { method: "POST", path: "/act", query: params.profile ? { profile: params.profile } : void 0, body: { kind: "resize", width: params.width, height: params.height, targetId: params.targetId?.trim() || void 0 } }, extra); } //#endregion //#region src/cli/browser-cli-actions-input/shared.ts function resolveBrowserActionContext(cmd, parentOpts) { const parent = parentOpts(cmd); return { parent, profile: parent?.browserProfile }; } async function callBrowserAct(params) { return await callBrowserRequest(params.parent, { method: "POST", path: "/act", query: params.profile ? { profile: params.profile } : void 0, body: params.body }, { timeoutMs: params.timeoutMs ?? 2e4 }); } function requireRef(ref) { const refValue = typeof ref === "string" ? ref.trim() : ""; if (!refValue) { defaultRuntime.error(danger("ref is required")); defaultRuntime.exit(1); return null; } return refValue; } async function readFile(path) { return await (await import("node:fs/promises")).readFile(path, "utf8"); } async function readFields(opts) { const payload = opts.fieldsFile ? await readFile(opts.fieldsFile) : opts.fields ?? ""; if (!payload.trim()) throw new Error("fields are required"); const parsed = JSON.parse(payload); if (!Array.isArray(parsed)) throw new Error("fields must be an array"); return parsed.map((entry, index) => { if (!entry || typeof entry !== "object") throw new Error(`fields[${index}] must be an object`); const rec = entry; const ref = typeof rec.ref === "string" ? rec.ref.trim() : ""; const type = typeof rec.type === "string" ? rec.type.trim() : ""; if (!ref || !type) throw new Error(`fields[${index}] must include ref and type`); if (typeof rec.value === "string" || typeof rec.value === "number" || typeof rec.value === "boolean") return { ref, type, value: rec.value }; if (rec.value === void 0 || rec.value === null) return { ref, type }; throw new Error(`fields[${index}].value must be string, number, boolean, or null`); }); } //#endregion //#region src/cli/browser-cli-actions-input/register.element.ts function registerBrowserElementCommands(browser, parentOpts) { browser.command("click").description("Click an element by ref from snapshot").argument("<ref>", "Ref id from snapshot").option("--target-id <id>", "CDP target id (or unique prefix)").option("--double", "Double click", false).option("--button <left|right|middle>", "Mouse button to use").option("--modifiers <list>", "Comma-separated modifiers (Shift,Alt,Meta)").action(async (ref, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); if (!refValue) return; const modifiers = opts.modifiers ? String(opts.modifiers).split(",").map((v) => v.trim()).filter(Boolean) : void 0; try { const result = await callBrowserAct({ parent, profile, body: { kind: "click", ref: refValue, targetId: opts.targetId?.trim() || void 0, doubleClick: Boolean(opts.double), button: opts.button?.trim() || void 0, modifiers } }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } const suffix = result.url ? ` on ${result.url}` : ""; defaultRuntime.log(`clicked ref ${refValue}${suffix}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("type").description("Type into an element by ref from snapshot").argument("<ref>", "Ref id from snapshot").argument("<text>", "Text to type").option("--submit", "Press Enter after typing", false).option("--slowly", "Type slowly (human-like)", false).option("--target-id <id>", "CDP target id (or unique prefix)").action(async (ref, text, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); if (!refValue) return; try { const result = await callBrowserAct({ parent, profile, body: { kind: "type", ref: refValue, text, submit: Boolean(opts.submit), slowly: Boolean(opts.slowly), targetId: opts.targetId?.trim() || void 0 } }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`typed into ref ${refValue}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("press").description("Press a key").argument("<key>", "Key to press (e.g. Enter)").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (key, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await callBrowserAct({ parent, profile, body: { kind: "press", key, targetId: opts.targetId?.trim() || void 0 } }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`pressed ${key}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("hover").description("Hover an element by ai ref").argument("<ref>", "Ref id from snapshot").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (ref, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await callBrowserAct({ parent, profile, body: { kind: "hover", ref, targetId: opts.targetId?.trim() || void 0 } }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`hovered ref ${ref}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("scrollintoview").description("Scroll an element into view by ref from snapshot").argument("<ref>", "Ref id from snapshot").option("--target-id <id>", "CDP target id (or unique prefix)").option("--timeout-ms <ms>", "How long to wait for scroll (default: 20000)", (v) => Number(v)).action(async (ref, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); if (!refValue) return; try { const result = await callBrowserAct({ parent, profile, body: { kind: "scrollIntoView", ref: refValue, targetId: opts.targetId?.trim() || void 0, timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : void 0 }, timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : void 0 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`scrolled into view: ${refValue}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("drag").description("Drag from one ref to another").argument("<startRef>", "Start ref id").argument("<endRef>", "End ref id").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (startRef, endRef, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await callBrowserAct({ parent, profile, body: { kind: "drag", startRef, endRef, targetId: opts.targetId?.trim() || void 0 } }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`dragged ${startRef} → ${endRef}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("select").description("Select option(s) in a select element").argument("<ref>", "Ref id from snapshot").argument("<values...>", "Option values to select").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (ref, values, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await callBrowserAct({ parent, profile, body: { kind: "select", ref, values, targetId: opts.targetId?.trim() || void 0 } }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`selected ${values.join(", ")}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); } //#endregion //#region src/cli/browser-cli-actions-input/register.files-downloads.ts async function normalizeUploadPaths(paths) { const result = await resolveExistingPathsWithinRoot({ rootDir: DEFAULT_UPLOAD_DIR, requestedPaths: paths, scopeLabel: `uploads directory (${DEFAULT_UPLOAD_DIR})` }); if (!result.ok) throw new Error(result.error); return result.paths; } function registerBrowserFilesAndDownloadsCommands(browser, parentOpts) { const resolveTimeoutAndTarget = (opts) => { return { timeoutMs: Number.isFinite(opts.timeoutMs) ? Number(opts.timeoutMs) : void 0, targetId: typeof opts.targetId === "string" ? opts.targetId.trim() || void 0 : void 0 }; }; const runDownloadCommand = async (cmd, opts, request) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts); const result = await callBrowserRequest(parent, { method: "POST", path: request.path, query: profile ? { profile } : void 0, body: { ...request.body, targetId, timeoutMs } }, { timeoutMs: timeoutMs ?? 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`downloaded: ${shortenHomePath(result.download.path)}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }; browser.command("upload").description("Arm file upload for the next file chooser").argument("<paths...>", "File paths to upload (must be within OpenClaw temp uploads dir, e.g. /tmp/openclaw/uploads/file.pdf)").option("--ref <ref>", "Ref id from snapshot to click after arming").option("--input-ref <ref>", "Ref id for <input type=file> to set directly").option("--element <selector>", "CSS selector for <input type=file>").option("--target-id <id>", "CDP target id (or unique prefix)").option("--timeout-ms <ms>", "How long to wait for the next file chooser (default: 120000)", (v) => Number(v)).action(async (paths, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const normalizedPaths = await normalizeUploadPaths(paths); const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts); const result = await callBrowserRequest(parent, { method: "POST", path: "/hooks/file-chooser", query: profile ? { profile } : void 0, body: { paths: normalizedPaths, ref: opts.ref?.trim() || void 0, inputRef: opts.inputRef?.trim() || void 0, element: opts.element?.trim() || void 0, targetId, timeoutMs } }, { timeoutMs: timeoutMs ?? 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`upload armed for ${paths.length} file(s)`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("waitfordownload").description("Wait for the next download (and save it)").argument("[path]", "Save path within openclaw temp downloads dir (default: /tmp/openclaw/downloads/...; fallback: os.tmpdir()/openclaw/downloads/...)").option("--target-id <id>", "CDP target id (or unique prefix)").option("--timeout-ms <ms>", "How long to wait for the next download (default: 120000)", (v) => Number(v)).action(async (outPath, opts, cmd) => { await runDownloadCommand(cmd, opts, { path: "/wait/download", body: { path: outPath?.trim() || void 0 } }); }); browser.command("download").description("Click a ref and save the resulting download").argument("<ref>", "Ref id from snapshot to click").argument("<path>", "Save path within openclaw temp downloads dir (e.g. report.pdf or /tmp/openclaw/downloads/report.pdf)").option("--target-id <id>", "CDP target id (or unique prefix)").option("--timeout-ms <ms>", "How long to wait for the download to start (default: 120000)", (v) => Number(v)).action(async (ref, outPath, opts, cmd) => { await runDownloadCommand(cmd, opts, { path: "/download", body: { ref, path: outPath } }); }); browser.command("dialog").description("Arm the next modal dialog (alert/confirm/prompt)").option("--accept", "Accept the dialog", false).option("--dismiss", "Dismiss the dialog", false).option("--prompt <text>", "Prompt response text").option("--target-id <id>", "CDP target id (or unique prefix)").option("--timeout-ms <ms>", "How long to wait for the next dialog (default: 120000)", (v) => Number(v)).action(async (opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); const accept = opts.accept ? true : opts.dismiss ? false : void 0; if (accept === void 0) { defaultRuntime.error(danger("Specify --accept or --dismiss")); defaultRuntime.exit(1); return; } try { const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts); const result = await callBrowserRequest(parent, { method: "POST", path: "/hooks/dialog", query: profile ? { profile } : void 0, body: { accept, promptText: opts.prompt?.trim() || void 0, targetId, timeoutMs } }, { timeoutMs: timeoutMs ?? 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log("dialog armed"); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); } //#endregion //#region src/cli/browser-cli-actions-input/register.form-wait-eval.ts function registerBrowserFormWaitEvalCommands(browser, parentOpts) { browser.command("fill").description("Fill a form with JSON field descriptors").option("--fields <json>", "JSON array of field objects").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, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const fields = await readFields({ fields: opts.fields, fieldsFile: opts.fieldsFile }); const result = await callBrowserAct({ parent, profile, body: { kind: "fill", fields, targetId: opts.targetId?.trim() || void 0 } }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`filled ${fields.length} field(s)`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("wait").description("Wait for time, selector, URL, load state, or JS conditions").argument("[selector]", "CSS selector to wait for (visible)").option("--time <ms>", "Wait for N milliseconds", (v) => Number(v)).option("--text <value>", "Wait for text to appear").option("--text-gone <value>", "Wait for text to disappear").option("--url <pattern>", "Wait for URL (supports globs like **/dash)").option("--load <load|domcontentloaded|networkidle>", "Wait for load state").option("--fn <js>", "Wait for JS condition (passed to waitForFunction)").option("--timeout-ms <ms>", "How long to wait for each condition (default: 20000)", (v) => Number(v)).option("--target-id <id>", "CDP target id (or unique prefix)").action(async (selector, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const sel = selector?.trim() || void 0; const load = opts.load === "load" || opts.load === "domcontentloaded" || opts.load === "networkidle" ? opts.load : void 0; const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : void 0; const result = await callBrowserAct({ parent, profile, body: { kind: "wait", timeMs: Number.isFinite(opts.time) ? opts.time : void 0, text: opts.text?.trim() || void 0, textGone: opts.textGone?.trim() || void 0, selector: sel, url: opts.url?.trim() || void 0, loadState: load, fn: opts.fn?.trim() || void 0, targetId: opts.targetId?.trim() || void 0, timeoutMs }, timeoutMs }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log("wait complete"); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("evaluate").description("Evaluate a function against the page or a ref").option("--fn <code>", "Function source, e.g. (el) => el.textContent").option("--ref <id>", "Ref from snapshot").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); if (!opts.fn) { defaultRuntime.error(danger("Missing --fn")); defaultRuntime.exit(1); return; } try { const result = await callBrowserAct({ parent, profile, body: { kind: "evaluate", fn: opts.fn, ref: opts.ref?.trim() || void 0, targetId: opts.targetId?.trim() || void 0 } }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(JSON.stringify(result.result ?? null, null, 2)); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); } //#endregion //#region src/cli/browser-cli-resize.ts async function runBrowserResizeWithOutput(params) { const { width, height } = params; if (!Number.isFinite(width) || !Number.isFinite(height)) { defaultRuntime.error(danger("width and height must be numbers")); defaultRuntime.exit(1); return; } const result = await callBrowserResize(params.parent, { profile: params.profile, width, height, targetId: params.targetId }, { timeoutMs: params.timeoutMs ?? 2e4 }); if (params.parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(params.successMessage); } //#endregion //#region src/cli/browser-cli-actions-input/register.navigation.ts function registerBrowserNavigationCommands(browser, parentOpts) { browser.command("navigate").description("Navigate the current tab to a URL").argument("<url>", "URL to navigate to").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (url, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await callBrowserRequest(parent, { method: "POST", path: "/navigate", query: profile ? { profile } : void 0, body: { url, targetId: opts.targetId?.trim() || void 0 } }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`navigated to ${result.url ?? url}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("resize").description("Resize the viewport").argument("<width>", "Viewport width", (v) => Number(v)).argument("<height>", "Viewport height", (v) => Number(v)).option("--target-id <id>", "CDP target id (or unique prefix)").action(async (width, height, opts, cmd) => { const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts); try { await runBrowserResizeWithOutput({ parent, profile, width, height, targetId: opts.targetId, timeoutMs: 2e4, successMessage: `resized to ${width}x${height}` }); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); } //#endregion //#region src/cli/browser-cli-actions-input/register.ts function registerBrowserActionInputCommands(browser, parentOpts) { registerBrowserNavigationCommands(browser, parentOpts); registerBrowserElementCommands(browser, parentOpts); registerBrowserFilesAndDownloadsCommands(browser, parentOpts); registerBrowserFormWaitEvalCommands(browser, parentOpts); } //#endregion //#region src/cli/browser-cli-actions-observe.ts function runBrowserObserve(action) { return runCommandWithRuntime(defaultRuntime, action, (err) => { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); }); } function registerBrowserActionObserveCommands(browser, parentOpts) { browser.command("console").description("Get recent console messages").option("--level <level>", "Filter by level (error, warn, info)").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserObserve(async () => { const result = await callBrowserRequest(parent, { method: "GET", path: "/console", query: { level: opts.level?.trim() || void 0, targetId: opts.targetId?.trim() || void 0, profile } }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(JSON.stringify(result.messages, null, 2)); }); }); browser.command("pdf").description("Save page as PDF").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserObserve(async () => { const result = await callBrowserRequest(parent, { method: "POST", path: "/pdf", query: profile ? { profile } : void 0, body: { targetId: opts.targetId?.trim() || void 0 } }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`PDF: ${shortenHomePath(result.path)}`); }); }); browser.command("responsebody").description("Wait for a network response and return its body").argument("<url>", "URL (exact, substring, or glob like **/api)").option("--target-id <id>", "CDP target id (or unique prefix)").option("--timeout-ms <ms>", "How long to wait for the response (default: 20000)", (v) => Number(v)).option("--max-chars <n>", "Max body chars to return (default: 200000)", (v) => Number(v)).action(async (url, opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserObserve(async () => { const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : void 0; const maxChars = Number.isFinite(opts.maxChars) ? opts.maxChars : void 0; const result = await callBrowserRequest(parent, { method: "POST", path: "/response/body", query: profile ? { profile } : void 0, body: { url, targetId: opts.targetId?.trim() || void 0, timeoutMs, maxChars } }, { timeoutMs: timeoutMs ?? 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(result.response.body); }); }); } //#endregion //#region src/cli/browser-cli-debug.ts function runBrowserDebug(action) { return runCommandWithRuntime(defaultRuntime, action, (err) => { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); }); } function resolveDebugQuery(params) { return { targetId: typeof params.targetId === "string" ? params.targetId.trim() || void 0 : void 0, filter: typeof params.filter === "string" ? params.filter.trim() || void 0 : void 0, clear: Boolean(params.clear), profile: params.profile }; } function registerBrowserDebugCommands(browser, parentOpts) { browser.command("highlight").description("Highlight an element by ref").argument("<ref>", "Ref id from snapshot").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (ref, opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserDebug(async () => { const result = await callBrowserRequest(parent, { method: "POST", path: "/highlight", query: profile ? { profile } : void 0, body: { ref: ref.trim(), targetId: opts.targetId?.trim() || void 0 } }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`highlighted ${ref.trim()}`); }); }); browser.command("errors").description("Get recent page errors").option("--clear", "Clear stored errors after reading", false).option("--target-id <id>", "CDP target id (or unique prefix)").action(async (opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserDebug(async () => { const result = await callBrowserRequest(parent, { method: "GET", path: "/errors", query: resolveDebugQuery({ targetId: opts.targetId, clear: opts.clear, profile }) }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } if (!result.errors.length) { defaultRuntime.log("No page errors."); return; } defaultRuntime.log(result.errors.map((e) => `${e.timestamp} ${e.name ? `${e.name}: ` : ""}${e.message}`).join("\n")); }); }); browser.command("requests").description("Get recent network requests (best-effort)").option("--filter <text>", "Only show URLs that contain this substring").option("--clear", "Clear stored requests after reading", false).option("--target-id <id>", "CDP target id (or unique prefix)").action(async (opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserDebug(async () => { const result = await callBrowserRequest(parent, { method: "GET", path: "/requests", query: resolveDebugQuery({ targetId: opts.targetId, filter: opts.filter, clear: opts.clear, profile }) }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } if (!result.requests.length) { defaultRuntime.log("No requests recorded."); return; } defaultRuntime.log(result.requests.map((r) => { const status = typeof r.status === "number" ? ` ${r.status}` : ""; const ok = r.ok === true ? " ok" : r.ok === false ? " fail" : ""; const fail = r.failureText ? ` (${r.failureText})` : ""; return `${r.timestamp} ${r.method}${status}${ok} ${r.url}${fail}`; }).join("\n")); }); }); const trace = browser.command("trace").description("Record a Playwright trace"); trace.command("start").description("Start trace recording").option("--target-id <id>", "CDP target id (or unique prefix)").option("--no-screenshots", "Disable screenshots").option("--no-snapshots", "Disable snapshots").option("--sources", "Include sources (bigger traces)", false).action(async (opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserDebug(async () => { const result = await callBrowserRequest(parent, { method: "POST", path: "/trace/start", query: profile ? { profile } : void 0, body: { targetId: opts.targetId?.trim() || void 0, screenshots: Boolean(opts.screenshots), snapshots: Boolean(opts.snapshots), sources: Boolean(opts.sources) } }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log("trace started"); }); }); trace.command("stop").description("Stop trace recording and write a .zip").option("--out <path>", "Output path within openclaw temp dir (e.g. trace.zip or /tmp/openclaw/trace.zip)").option("--target-id <id>", "CDP target id (or unique prefix)").action(async (opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserDebug(async () => { const result = await callBrowserRequest(parent, { method: "POST", path: "/trace/stop", query: profile ? { profile } : void 0, body: { targetId: opts.targetId?.trim() || void 0, path: opts.out?.trim() || void 0 } }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`TRACE:${shortenHomePath(result.path)}`); }); }); } //#endregion //#region src/cli/browser-cli-examples.ts const browserCoreExamples = [ "openclaw browser status", "openclaw browser start", "openclaw browser stop", "openclaw browser tabs", "openclaw browser open https://example.com", "openclaw browser focus abcd1234", "openclaw browser close abcd1234", "openclaw browser screenshot", "openclaw browser screenshot --full-page", "openclaw browser screenshot --ref 12", "openclaw browser snapshot", "openclaw browser snapshot --format aria --limit 200", "openclaw browser snapshot --efficient", "openclaw browser snapshot --labels" ]; const browserActionExamples = [ "openclaw browser navigate https://example.com", "openclaw browser resize 1280 720", "openclaw browser click 12 --double", "openclaw browser type 23 \"hello\" --submit", "openclaw browser press Enter", "openclaw browser hover 44", "openclaw browser drag 10 11", "openclaw browser select 9 OptionA OptionB", "openclaw browser upload /tmp/openclaw/uploads/file.pdf", "openclaw browser fill --fields '[{\"ref\":\"1\",\"value\":\"Ada\"}]'", "openclaw browser dialog --accept", "openclaw browser wait --text \"Done\"", "openclaw browser evaluate --fn '(el) => el.textContent' --ref 7", "openclaw browser console --level error", "openclaw browser pdf" ]; //#endregion //#region src/cli/browser-cli-extension.ts function resolveBundledExtensionRootDir(here = path.dirname(fileURLToPath(import.meta.url))) { let current = here; while (true) { const candidate = path.join(current, "assets", "chrome-extension"); if (hasManifest(candidate)) return candidate; const parent = path.dirname(current); if (parent === current) break; current = parent; } return path.resolve(here, "../../assets/chrome-extension"); } function installedExtensionRootDir() { return path.join(resolveStateDir(), "browser", "chrome-extension"); } function hasManifest(dir) { return fs.existsSync(path.join(dir, "manifest.json")); } async function installChromeExtension(opts) { const src = opts?.sourceDir ?? resolveBundledExtensionRootDir(); if (!hasManifest(src)) throw new Error("Bundled Chrome extension is missing. Reinstall OpenClaw and try again."); const stateDir = opts?.stateDir ?? resolveStateDir(); const dest = path.join(stateDir, "browser", "chrome-extension"); fs.mkdirSync(path.dirname(dest), { recursive: true }); if (fs.existsSync(dest)) await movePathToTrash(dest).catch(() => { const backup = `${dest}.old-${Date.now()}`; fs.renameSync(dest, backup); }); await fs.promises.cp(src, dest, { recursive: true }); if (!hasManifest(dest)) throw new Error("Chrome extension install failed (manifest.json missing). Try again."); return { path: dest }; } function registerBrowserExtensionCommands(browser, parentOpts) { const ext = browser.command("extension").description("Chrome extension helpers"); ext.command("install").description("Install the Chrome extension to a stable local path").action(async (_opts, cmd) => { const parent = parentOpts(cmd); let installed; try { installed = await installChromeExtension(); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); return; } if (parent?.json) { defaultRuntime.log(JSON.stringify({ ok: true, path: installed.path }, null, 2)); return; } const displayPath = shortenHomePath(installed.path); defaultRuntime.log(displayPath); const copied = await copyToClipboard(installed.path).catch(() => false); defaultRuntime.error(info([ copied ? "Copied to clipboard." : "Copy to clipboard unavailable.", "Next:", `- Chrome → chrome://extensions → enable “Developer mode”`, `- “Load unpacked” → select: ${displayPath}`, `- Pin “OpenClaw Browser Relay”, then click it on the tab (badge shows ON)`, "", `${theme.muted("Docs:")} ${formatDocsLink("/tools/chrome-extension", "docs.openclaw.ai/tools/chrome-extension")}` ].join("\n"))); }); ext.command("path").description("Print the path to the installed Chrome extension (load unpacked)").action(async (_opts, cmd) => { const parent = parentOpts(cmd); const dir = installedExtensionRootDir(); if (!hasManifest(dir)) { defaultRuntime.error(danger([`Chrome extension is not installed. Run: "${formatCliCommand("openclaw browser extension install")}"`, `Docs: ${formatDocsLink("/tools/chrome-extension", "docs.openclaw.ai/tools/chrome-extension")}`].join("\n"))); defaultRuntime.exit(1); } if (parent?.json) { defaultRuntime.log(JSON.stringify({ path: dir }, null, 2)); return; } const displayPath = shortenHomePath(dir); defaultRuntime.log(displayPath); if (await copyToClipboard(dir).catch(() => false)) defaultRuntime.error(info("Copied to clipboard.")); }); } //#endregion //#region src/cli/browser-cli-inspect.ts function registerBrowserInspectCommands(browser, parentOpts) { browser.command("screenshot").description("Capture a screenshot (MEDIA:<path>)").argument("[targetId]", "CDP target id (or unique prefix)").option("--full-page", "Capture full scrollable page", false).option("--ref <ref>", "ARIA ref from ai snapshot").option("--element <selector>", "CSS selector for element screenshot").option("--type <png|jpeg>", "Output type (default: png)", "png").action(async (targetId, opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; try { const result = await callBrowserRequest(parent, { method: "POST", path: "/screenshot", query: profile ? { profile } : void 0, body: { targetId: targetId?.trim() || void 0, fullPage: Boolean(opts.fullPage), ref: opts.ref?.trim() || void 0, element: opts.element?.trim() || void 0, type: opts.type === "jpeg" ? "jpeg" : "png" } }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`MEDIA:${shortenHomePath(result.path)}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); browser.command("snapshot").description("Capture a snapshot (default: ai; aria is the accessibility tree)").option("--format <aria|ai>", "Snapshot format (default: ai)", "ai").option("--target-id <id>", "CDP target id (or unique prefix)").option("--limit <n>", "Max nodes (default: 500/800)", (v) => Number(v)).option("--mode <efficient>", "Snapshot preset (efficient)").option("--efficient", "Use the efficient snapshot preset", false).option("--interactive", "Role snapshot: interactive elements only", false).option("--compact", "Role snapshot: compact output", false).option("--depth <n>", "Role snapshot: max depth", (v) => Number(v)).option("--selector <sel>", "Role snapshot: scope to CSS selector").option("--frame <sel>", "Role snapshot: scope to an iframe selector").option("--labels", "Include viewport label overlay screenshot", false).option("--out <path>", "Write snapshot to a file").action(async (opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; const format = opts.format === "aria" ? "aria" : "ai"; const configMode = format === "ai" && loadConfig().browser?.snapshotDefaults?.mode === "efficient" ? "efficient" : void 0; const mode = opts.efficient === true || opts.mode === "efficient" ? "efficient" : configMode; try { const result = await callBrowserRequest(parent, { method: "GET", path: "/snapshot", query: { format, targetId: opts.targetId?.trim() || void 0, limit: Number.isFinite(opts.limit) ? opts.limit : void 0, interactive: opts.interactive ? true : void 0, compact: opts.compact ? true : void 0, depth: Number.isFinite(opts.depth) ? opts.depth : void 0, selector: opts.selector?.trim() || void 0, frame: opts.frame?.trim() || void 0, labels: opts.labels ? true : void 0, mode, profile } }, { timeoutMs: 2e4 }); if (opts.out) { const fs = await import("node:fs/promises"); if (result.format === "ai") await fs.writeFile(opts.out, result.snapshot, "utf8"); else { const payload = JSON.stringify(result, null, 2); await fs.writeFile(opts.out, payload, "utf8"); } if (parent?.json) defaultRuntime.log(JSON.stringify({ ok: true, out: opts.out, ...result.format === "ai" && result.imagePath ? { imagePath: result.imagePath } : {} }, null, 2)); else { defaultRuntime.log(shortenHomePath(opts.out)); if (result.format === "ai" && result.imagePath) defaultRuntime.log(`MEDIA:${shortenHomePath(result.imagePath)}`); } return; } if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } if (result.format === "ai") { defaultRuntime.log(result.snapshot); if (result.imagePath) defaultRuntime.log(`MEDIA:${shortenHomePath(result.imagePath)}`); return; } const nodes = "nodes" in result ? result.nodes : []; defaultRuntime.log(nodes.map((n) => { const indent = " ".repeat(Math.min(20, n.depth)); const name = n.name ? ` "${n.name}"` : ""; const value = n.value ? ` = "${n.value}"` : ""; return `${indent}- ${n.role}${name}${value}`; }).join("\n")); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); } //#endregion //#region src/cli/browser-cli-manage.ts async function fetchBrowserStatus(parent, profile) { return await callBrowserRequest(parent, { method: "GET", path: "/", query: profile ? { profile } : void 0 }, { timeoutMs: 1500 }); } async function runBrowserToggle(parent, params) { await callBrowserRequest(parent, { method: "POST", path: params.path, query: params.profile ? { profile: params.profile } : void 0 }, { timeoutMs: 15e3 }); const status = await fetchBrowserStatus(parent, params.profile); if (parent?.json) { defaultRuntime.log(JSON.stringify(status, null, 2)); return; } const name = status.profile ?? "openclaw"; defaultRuntime.log(info(`🦞 browser [${name}] running: ${status.running}`)); } function runBrowserCommand$1(action) { return runCommandWithRuntime(defaultRuntime, action, (err) => { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); }); } function logBrowserTabs(tabs, json) { if (json) { defaultRuntime.log(JSON.stringify({ tabs }, null, 2)); return; } if (tabs.length === 0) { defaultRuntime.log("No tabs (browser closed or no targets)."); return; } defaultRuntime.log(tabs.map((t, i) => `${i + 1}. ${t.title || "(untitled)"}\n ${t.url}\n id: ${t.targetId}`).join("\n")); } function registerBrowserManageCommands(browser, parentOpts) { browser.command("status").description("Show browser status").action(async (_opts, cmd) => { const parent = parentOpts(cmd); await runBrowserCommand$1(async () => { const status = await fetchBrowserStatus(parent, parent?.browserProfile); if (parent?.json) { defaultRuntime.log(JSON.stringify(status, null, 2)); return; } const detectedPath = status.detectedExecutablePath ?? status.executablePath; const detectedDisplay = detectedPath ? shortenHomePath(detectedPath) : "auto"; defaultRuntime.log([ `profile: ${status.profile ?? "openclaw"}`, `enabled: ${status.enabled}`, `running: ${status.running}`, `cdpPort: ${status.cdpPort}`, `cdpUrl: ${status.cdpUrl ?? `http://127.0.0.1:${status.cdpPort}`}`, `browser: ${status.chosenBrowser ?? "unknown"}`, `detectedBrowser: ${status.detectedBrowser ?? "unknown"}`, `detectedPath: ${detectedDisplay}`, `profileColor: ${status.color}`, ...status.detectError ? [`detectError: ${status.detectError}`] : [] ].join("\n")); }); }); browser.command("start").description("Start the browser (no-op if already running)").action(async (_opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { await runBrowserToggle(parent, { profile, path: "/start" }); }); }); browser.command("stop").description("Stop the browser (best-effort)").action(async (_opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { await runBrowserToggle(parent, { profile, path: "/stop" }); }); }); browser.command("reset-profile").description("Reset browser profile (moves it to Trash)").action(async (_opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { const result = await callBrowserRequest(parent, { method: "POST", path: "/reset-profile", query: profile ? { profile } : void 0 }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } if (!result.moved) { defaultRuntime.log(info(`🦞 browser profile already missing.`)); return; } const dest = result.to ?? result.from; defaultRuntime.log(info(`🦞 browser profile moved to Trash (${dest})`)); }); }); browser.command("tabs").description("List open tabs").action(async (_opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { logBrowserTabs((await callBrowserRequest(parent, { method: "GET", path: "/tabs", query: profile ? { profile } : void 0 }, { timeoutMs: 3e3 })).tabs ?? [], parent?.json); }); }); const tab = browser.command("tab").description("Tab shortcuts (index-based)").action(async (_opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { logBrowserTabs((await callBrowserRequest(parent, { method: "POST", path: "/tabs/action", query: profile ? { profile } : void 0, body: { action: "list" } }, { timeoutMs: 1e4 })).tabs ?? [], parent?.json); }); }); tab.command("new").description("Open a new tab (about:blank)").action(async (_opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { const result = await callBrowserRequest(parent, { method: "POST", path: "/tabs/action", query: profile ? { profile } : void 0, body: { action: "new" } }, { timeoutMs: 1e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log("opened new tab"); }); }); tab.command("select").description("Focus tab by index (1-based)").argument("<index>", "Tab index (1-based)", (v) => Number(v)).action(async (index, _opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; if (!Number.isFinite(index) || index < 1) { defaultRuntime.error(danger("index must be a positive number")); defaultRuntime.exit(1); return; } await runBrowserCommand$1(async () => { const result = await callBrowserRequest(parent, { method: "POST", path: "/tabs/action", query: profile ? { profile } : void 0, body: { action: "select", index: Math.floor(index) - 1 } }, { timeoutMs: 1e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`selected tab ${Math.floor(index)}`); }); }); tab.command("close").description("Close tab by index (1-based); default: first tab").argument("[index]", "Tab index (1-based)", (v) => Number(v)).action(async (index, _opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; const idx = typeof index === "number" && Number.isFinite(index) ? Math.floor(index) - 1 : void 0; if (typeof idx === "number" && idx < 0) { defaultRuntime.error(danger("index must be >= 1")); defaultRuntime.exit(1); return; } await runBrowserCommand$1(async () => { const result = await callBrowserRequest(parent, { method: "POST", path: "/tabs/action", query: profile ? { profile } : void 0, body: { action: "close", index: idx } }, { timeoutMs: 1e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log("closed tab"); }); }); browser.command("open").description("Open a URL in a new tab").argument("<url>", "URL to open").action(async (url, _opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { const tab = await callBrowserRequest(parent, { method: "POST", path: "/tabs/open", query: profile ? { profile } : void 0, body: { url } }, { timeoutMs: 15e3 }); if (parent?.json) { defaultRuntime.log(JSON.stringify(tab, null, 2)); return; } defaultRuntime.log(`opened: ${tab.url}\nid: ${tab.targetId}`); }); }); browser.command("focus").description("Focus a tab by target id (or unique prefix)").argument("<targetId>", "Target id or unique prefix").action(async (targetId, _opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { await callBrowserRequest(parent, { method: "POST", path: "/tabs/focus", query: profile ? { profile } : void 0, body: { targetId } }, { timeoutMs: 5e3 }); if (parent?.json) { defaultRuntime.log(JSON.stringify({ ok: true }, null, 2)); return; } defaultRuntime.log(`focused tab ${targetId}`); }); }); browser.command("close").description("Close a tab (target id optional)").argument("[targetId]", "Target id or unique prefix (optional)").action(async (targetId, _opts, cmd) => { const parent = parentOpts(cmd); const profile = parent?.browserProfile; await runBrowserCommand$1(async () => { if (targetId?.trim()) await callBrowserRequest(parent, { method: "DELETE", path: `/tabs/${encodeURIComponent(targetId.trim())}`, query: profile ? { profile } : void 0 }, { timeoutMs: 5e3 }); else await callBrowserRequest(parent, { method: "POST", path: "/act", query: profile ? { profile } : void 0, body: { kind: "close" } }, { timeoutMs: 2e4 }); if (parent?.json) { defaultRuntime.log(JSON.stringify({ ok: true }, null, 2)); return; } defaultRuntime.log("closed tab"); }); }); browser.command("profiles").description("List all browser profiles").action(async (_opts, cmd) => { const parent = parentOpts(cmd); await runBrowserCommand$1(async () => { const profiles = (await callBrowserRequest(parent, { method: "GET", path: "/profiles" }, { timeoutMs: 3e3 })).profiles ?? []; if (parent?.json) { defaultRuntime.log(JSON.stringify({ profiles }, null, 2)); return; } if (profiles.length === 0) { defaultRuntime.log("No profiles configu