import type { Command } from "commander"; import { browserSnapshot, resolveBrowserControlUrl } from "../browser/client.js"; import { browserScreenshotAction } from "../browser/client-actions.js"; import { danger } from "../globals.js"; import { defaultRuntime } from "../runtime.js"; import type { BrowserParentOpts } from "./browser-cli-shared.js"; export function registerBrowserInspectCommands( browser: Command, parentOpts: (cmd: Command) => BrowserParentOpts, ) { browser .command("screenshot") .description("Capture a screenshot (MEDIA:)") .argument("[targetId]", "CDP target id (or unique prefix)") .option("--full-page", "Capture full scrollable page", false) .option("--ref ", "ARIA ref from ai snapshot") .option("--element ", "CSS selector for element screenshot") .option("--type ", "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, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`MEDIA:${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 ", "Snapshot format (default: ai)", "ai") .option("--target-id ", "CDP target id (or unique prefix)") .option("--limit ", "Max nodes (default: 500/800)", (v: string) => Number(v)) .option("--mode ", "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 ", "Role snapshot: max depth", (v: string) => Number(v)) .option("--selector ", "Role snapshot: scope to CSS selector") .option("--frame ", "Role snapshot: scope to an iframe selector") .option("--labels", "Include viewport label overlay screenshot", false) .option("--out ", "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 mode = opts.efficient === true || opts.mode === "efficient" ? "efficient" : undefined; try { const result = await browserSnapshot(baseUrl, { format, targetId: opts.targetId?.trim() || undefined, limit: Number.isFinite(opts.limit) ? opts.limit : undefined, interactive: Boolean(opts.interactive) || undefined, compact: Boolean(opts.compact) || undefined, depth: Number.isFinite(opts.depth) ? opts.depth : undefined, selector: opts.selector?.trim() || undefined, frame: opts.frame?.trim() || undefined, labels: Boolean(opts.labels) || undefined, mode, profile, }); 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(opts.out); if (result.format === "ai" && result.imagePath) { defaultRuntime.log(`MEDIA:${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:${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); } }); }