import type { Command } from "commander"; import { resolveBrowserControlUrl } from "../browser/client.js"; import { browserCookies, browserCookiesClear, browserCookiesSet, browserSetDevice, browserSetGeolocation, browserSetHeaders, browserSetHttpCredentials, browserSetLocale, browserSetMedia, browserSetOffline, browserSetTimezone, browserStorageClear, browserStorageGet, browserStorageSet, } from "../browser/client-actions.js"; import { browserAct } from "../browser/client-actions-core.js"; import { danger } from "../globals.js"; import { defaultRuntime } from "../runtime.js"; import type { BrowserParentOpts } from "./browser-cli-shared.js"; function parseOnOff(raw: string): boolean | null { const v = raw.trim().toLowerCase(); if (v === "on" || v === "true" || v === "1") return true; if (v === "off" || v === "false" || v === "0") return false; return null; } export function registerBrowserStateCommands( browser: Command, parentOpts: (cmd: Command) => BrowserParentOpts, ) { const cookies = browser.command("cookies").description("Read/write cookies"); cookies .option("--target-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, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(JSON.stringify(result.cookies ?? [], null, 2)); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); cookies .command("set") .description("Set a cookie (requires --url or domain+path)") .argument("", "Cookie name") .argument("", "Cookie value") .requiredOption("--url ", "Cookie URL scope (recommended)") .option("--target-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, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`cookie set: ${name}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); cookies .command("clear") .description("Clear all cookies") .option("--target-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, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log("cookies cleared"); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); const storage = browser .command("storage") .description("Read/write localStorage/sessionStorage"); function registerStorageKind(kind: "local" | "session") { const cmd = storage.command(kind).description(`${kind}Storage commands`); cmd .command("get") .description(`Get ${kind}Storage (all keys or one key)`) .argument("[key]", "Key (optional)") .option("--target-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, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(JSON.stringify(result.values ?? {}, null, 2)); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); cmd .command("set") .description(`Set a ${kind}Storage key`) .argument("", "Key") .argument("", "Value") .option("--target-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, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`${kind}Storage set: ${key}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); cmd .command("clear") .description(`Clear all ${kind}Storage keys`) .option("--target-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, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`${kind}Storage cleared`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); } registerStorageKind("local"); registerStorageKind("session"); const set = browser .command("set") .description("Browser environment settings"); set .command("viewport") .description("Set viewport size (alias for resize)") .argument("", "Viewport width", (v: string) => Number(v)) .argument("", "Viewport height", (v: string) => Number(v)) .option("--target-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")); defaultRuntime.exit(1); return; } try { const result = await browserAct( baseUrl, { kind: "resize", width, height, targetId: opts.targetId?.trim() || undefined, }, { profile }, ); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`viewport set: ${width}x${height}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); set .command("offline") .description("Toggle offline mode") .argument("", "on/off") .option("--target-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) { defaultRuntime.error(danger("Expected on|off")); defaultRuntime.exit(1); return; } try { const result = await browserSetOffline(baseUrl, { offline, targetId: opts.targetId?.trim() || undefined, profile, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`offline: ${offline}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); set .command("headers") .description("Set extra HTTP headers (JSON object)") .requiredOption("--json ", "JSON object of headers") .option("--target-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 parsed = JSON.parse(String(opts.json)) as unknown; if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("headers json must be an object"); } const headers: Record = {}; for (const [k, v] of Object.entries( parsed as Record, )) { if (typeof v === "string") headers[k] = v; } const result = await browserSetHeaders(baseUrl, { headers, targetId: opts.targetId?.trim() || undefined, profile, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log("headers set"); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); set .command("credentials") .description("Set HTTP basic auth credentials") .option("--clear", "Clear credentials", false) .argument("[username]", "Username") .argument("[password]", "Password") .option("--target-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; try { const result = await browserSetHttpCredentials(baseUrl, { username: username?.trim() || undefined, password, clear: Boolean(opts.clear), targetId: opts.targetId?.trim() || undefined, profile, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log( opts.clear ? "credentials cleared" : "credentials set", ); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }, ); set .command("geo") .description("Set geolocation (and grant permission)") .option("--clear", "Clear geolocation + permissions", false) .argument("[latitude]", "Latitude", (v: string) => Number(v)) .argument("[longitude]", "Longitude", (v: string) => Number(v)) .option("--accuracy ", "Accuracy in meters", (v: string) => Number(v)) .option("--origin ", "Origin to grant permissions for") .option("--target-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; try { 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, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log( opts.clear ? "geolocation cleared" : "geolocation set", ); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }, ); set .command("media") .description("Emulate prefers-color-scheme") .argument("", "dark/light/none") .option("--target-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 = v === "dark" ? "dark" : v === "light" ? "light" : v === "none" ? "none" : null; if (!colorScheme) { defaultRuntime.error(danger("Expected dark|light|none")); defaultRuntime.exit(1); return; } try { const result = await browserSetMedia(baseUrl, { colorScheme, targetId: opts.targetId?.trim() || undefined, profile, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`media colorScheme: ${colorScheme}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); set .command("timezone") .description("Override timezone (CDP)") .argument("", "Timezone ID (e.g. America/New_York)") .option("--target-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; try { const result = await browserSetTimezone(baseUrl, { timezoneId, targetId: opts.targetId?.trim() || undefined, profile, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`timezone: ${timezoneId}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); set .command("locale") .description("Override locale (CDP)") .argument("", "Locale (e.g. en-US)") .option("--target-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; try { const result = await browserSetLocale(baseUrl, { locale, targetId: opts.targetId?.trim() || undefined, profile, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`locale: ${locale}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); set .command("device") .description('Apply a Playwright device descriptor (e.g. "iPhone 14")') .argument("", "Device name (Playwright devices)") .option("--target-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; try { const result = await browserSetDevice(baseUrl, { name, targetId: opts.targetId?.trim() || undefined, profile, }); if (parent?.json) { defaultRuntime.log(JSON.stringify(result, null, 2)); return; } defaultRuntime.log(`device: ${name}`); } catch (err) { defaultRuntime.error(danger(String(err))); defaultRuntime.exit(1); } }); }