import { fetchBrowserJson } from "./client-fetch.js"; export type BrowserStatus = { enabled: boolean; profile?: string; running: boolean; cdpReady?: boolean; cdpHttp?: boolean; pid: number | null; cdpPort: number; cdpUrl?: string; chosenBrowser: string | null; detectedBrowser?: string | null; detectedExecutablePath?: string | null; detectError?: string | null; userDataDir: string | null; color: string; headless: boolean; noSandbox?: boolean; executablePath?: string | null; attachOnly: boolean; }; export type ProfileStatus = { name: string; cdpPort: number; cdpUrl: string; color: string; running: boolean; tabCount: number; isDefault: boolean; isRemote: boolean; }; export type BrowserResetProfileResult = { ok: true; moved: boolean; from: string; to?: string; }; export type BrowserTab = { targetId: string; title: string; url: string; wsUrl?: string; type?: string; }; export type SnapshotAriaNode = { ref: string; role: string; name: string; value?: string; description?: string; backendDOMNodeId?: number; depth: number; }; export type SnapshotResult = | { ok: true; format: "aria"; targetId: string; url: string; nodes: SnapshotAriaNode[]; } | { ok: true; format: "ai"; targetId: string; url: string; snapshot: string; truncated?: boolean; refs?: Record; stats?: { lines: number; chars: number; refs: number; interactive: number; }; labels?: boolean; labelsCount?: number; labelsSkipped?: number; imagePath?: string; imageType?: "png" | "jpeg"; }; function buildProfileQuery(profile?: string): string { return profile ? `?profile=${encodeURIComponent(profile)}` : ""; } function withBaseUrl(baseUrl: string | undefined, path: string): string { const trimmed = baseUrl?.trim(); if (!trimmed) return path; return `${trimmed.replace(/\/$/, "")}${path}`; } export async function browserStatus( baseUrl?: string, opts?: { profile?: string }, ): Promise { const q = buildProfileQuery(opts?.profile); return await fetchBrowserJson(withBaseUrl(baseUrl, `/${q}`), { timeoutMs: 1500, }); } export async function browserProfiles(baseUrl?: string): Promise { const res = await fetchBrowserJson<{ profiles: ProfileStatus[] }>( withBaseUrl(baseUrl, `/profiles`), { timeoutMs: 3000, }, ); return res.profiles ?? []; } export async function browserStart(baseUrl?: string, opts?: { profile?: string }): Promise { const q = buildProfileQuery(opts?.profile); await fetchBrowserJson(withBaseUrl(baseUrl, `/start${q}`), { method: "POST", timeoutMs: 15000, }); } export async function browserStop(baseUrl?: string, opts?: { profile?: string }): Promise { const q = buildProfileQuery(opts?.profile); await fetchBrowserJson(withBaseUrl(baseUrl, `/stop${q}`), { method: "POST", timeoutMs: 15000, }); } export async function browserResetProfile( baseUrl?: string, opts?: { profile?: string }, ): Promise { const q = buildProfileQuery(opts?.profile); return await fetchBrowserJson( withBaseUrl(baseUrl, `/reset-profile${q}`), { method: "POST", timeoutMs: 20000, }, ); } export type BrowserCreateProfileResult = { ok: true; profile: string; cdpPort: number; cdpUrl: string; color: string; isRemote: boolean; }; export async function browserCreateProfile( baseUrl: string | undefined, opts: { name: string; color?: string; cdpUrl?: string; driver?: "clawd" | "extension"; }, ): Promise { return await fetchBrowserJson( withBaseUrl(baseUrl, `/profiles/create`), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: opts.name, color: opts.color, cdpUrl: opts.cdpUrl, driver: opts.driver, }), timeoutMs: 10000, }, ); } export type BrowserDeleteProfileResult = { ok: true; profile: string; deleted: boolean; }; export async function browserDeleteProfile( baseUrl: string | undefined, profile: string, ): Promise { return await fetchBrowserJson( withBaseUrl(baseUrl, `/profiles/${encodeURIComponent(profile)}`), { method: "DELETE", timeoutMs: 20000, }, ); } export async function browserTabs( baseUrl?: string, opts?: { profile?: string }, ): Promise { const q = buildProfileQuery(opts?.profile); const res = await fetchBrowserJson<{ running: boolean; tabs: BrowserTab[] }>( withBaseUrl(baseUrl, `/tabs${q}`), { timeoutMs: 3000 }, ); return res.tabs ?? []; } export async function browserOpenTab( baseUrl: string | undefined, url: string, opts?: { profile?: string }, ): Promise { const q = buildProfileQuery(opts?.profile); return await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/open${q}`), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }), timeoutMs: 15000, }); } export async function browserFocusTab( baseUrl: string | undefined, targetId: string, opts?: { profile?: string }, ): Promise { const q = buildProfileQuery(opts?.profile); await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/focus${q}`), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ targetId }), timeoutMs: 5000, }); } export async function browserCloseTab( baseUrl: string | undefined, targetId: string, opts?: { profile?: string }, ): Promise { const q = buildProfileQuery(opts?.profile); await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/${encodeURIComponent(targetId)}${q}`), { method: "DELETE", timeoutMs: 5000, }); } export async function browserTabAction( baseUrl: string | undefined, opts: { action: "list" | "new" | "close" | "select"; index?: number; profile?: string; }, ): Promise { const q = buildProfileQuery(opts.profile); return await fetchBrowserJson(withBaseUrl(baseUrl, `/tabs/action${q}`), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: opts.action, index: opts.index, }), timeoutMs: 10_000, }); } export async function browserSnapshot( baseUrl: string | undefined, opts: { format: "aria" | "ai"; targetId?: string; limit?: number; maxChars?: number; refs?: "role" | "aria"; interactive?: boolean; compact?: boolean; depth?: number; selector?: string; frame?: string; labels?: boolean; mode?: "efficient"; profile?: string; }, ): Promise { const q = new URLSearchParams(); q.set("format", opts.format); if (opts.targetId) q.set("targetId", opts.targetId); if (typeof opts.limit === "number") q.set("limit", String(opts.limit)); if (typeof opts.maxChars === "number" && Number.isFinite(opts.maxChars)) { q.set("maxChars", String(opts.maxChars)); } if (opts.refs === "aria" || opts.refs === "role") q.set("refs", opts.refs); if (typeof opts.interactive === "boolean") q.set("interactive", String(opts.interactive)); if (typeof opts.compact === "boolean") q.set("compact", String(opts.compact)); if (typeof opts.depth === "number" && Number.isFinite(opts.depth)) q.set("depth", String(opts.depth)); if (opts.selector?.trim()) q.set("selector", opts.selector.trim()); if (opts.frame?.trim()) q.set("frame", opts.frame.trim()); if (opts.labels === true) q.set("labels", "1"); if (opts.mode) q.set("mode", opts.mode); if (opts.profile) q.set("profile", opts.profile); return await fetchBrowserJson(withBaseUrl(baseUrl, `/snapshot?${q.toString()}`), { timeoutMs: 20000, }); } // Actions beyond the basic read-only commands live in client-actions.ts.