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"; export function registerBrowserElementCommands( browser: Command, parentOpts: (cmd: Command) => BrowserParentOpts, ) { browser .command("click") .description("Click an element by ref from snapshot") .argument("", "Ref id from snapshot") .option("--target-id ", "CDP target id (or unique prefix)") .option("--double", "Double click", false) .option("--button ", "Mouse button to use") .option("--modifiers ", "Comma-separated modifiers (Shift,Alt,Meta)") .action(async (ref: string | undefined, opts, cmd) => { const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); if (!refValue) return; const modifiers = opts.modifiers ? String(opts.modifiers) .split(",") .map((v: string) => v.trim()) .filter(Boolean) : undefined; try { const result = await browserAct( baseUrl, { kind: "click", ref: refValue, targetId: opts.targetId?.trim() || undefined, doubleClick: Boolean(opts.double), button: opts.button?.trim() || undefined, modifiers, }, { profile }, ); 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 id from snapshot") .argument("", "Text to type") .option("--submit", "Press Enter after typing", false) .option("--slowly", "Type slowly (human-like)", false) .option("--target-id ", "CDP target id (or unique prefix)") .action(async (ref: string | undefined, text: string, opts, cmd) => { const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); if (!refValue) return; try { const result = await browserAct( baseUrl, { kind: "type", ref: refValue, text, submit: Boolean(opts.submit), slowly: Boolean(opts.slowly), targetId: opts.targetId?.trim() || undefined, }, { profile }, ); 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 to press (e.g. Enter)") .option("--target-id ", "CDP target id (or unique prefix)") .action(async (key: string, opts, cmd) => { const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await browserAct( baseUrl, { kind: "press", key, targetId: opts.targetId?.trim() || undefined }, { profile }, ); 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 id from snapshot") .option("--target-id ", "CDP target id (or unique prefix)") .action(async (ref: string, opts, cmd) => { const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await browserAct( baseUrl, { kind: "hover", ref, targetId: opts.targetId?.trim() || undefined }, { profile }, ); 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 id from snapshot") .option("--target-id ", "CDP target id (or unique prefix)") .option("--timeout-ms ", "How long to wait for scroll (default: 20000)", (v: string) => Number(v), ) .action(async (ref: string | undefined, opts, cmd) => { const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts); const refValue = requireRef(ref); if (!refValue) return; try { const result = await browserAct( baseUrl, { kind: "scrollIntoView", ref: refValue, targetId: opts.targetId?.trim() || undefined, timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined, }, { profile }, ); 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("", "Start ref id") .argument("", "End ref id") .option("--target-id ", "CDP target id (or unique prefix)") .action(async (startRef: string, endRef: string, opts, cmd) => { const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await browserAct( baseUrl, { kind: "drag", startRef, endRef, targetId: opts.targetId?.trim() || undefined, }, { profile }, ); 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 id from snapshot") .argument("", "Option values to select") .option("--target-id ", "CDP target id (or unique prefix)") .action(async (ref: string, values: string[], opts, cmd) => { const { parent, baseUrl, profile } = resolveBrowserActionContext(cmd, parentOpts); try { const result = await browserAct( baseUrl, { kind: "select", ref, values, targetId: opts.targetId?.trim() || undefined, }, { profile }, ); 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); } }); }