UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

655 lines (527 loc) 21.4 kB
/** * @claude-flow/browser - Agent Browser Adapter * Wraps agent-browser CLI for programmatic access */ import { spawn, execSync, execFileSync } from 'child_process'; import type { ActionResult, Snapshot, SnapshotOptions, OpenInput, ClickInput, FillInput, TypeInput, ScreenshotInput, WaitInput, EvalInput, GetInput, BrowserSession, NetworkRouteInput, } from '../domain/types.js'; export interface AgentBrowserAdapterOptions { session?: string; timeout?: number; headless?: boolean; executablePath?: string; proxy?: string; viewport?: { width: number; height: number }; debug?: boolean; } export class AgentBrowserAdapter { private session: string; private timeout: number; private headless: boolean; private executablePath?: string; private proxy?: string; private viewport?: { width: number; height: number }; private debug: boolean; constructor(options: AgentBrowserAdapterOptions = {}) { this.session = options.session || 'default'; this.timeout = options.timeout || 30000; this.headless = options.headless !== false; this.executablePath = options.executablePath; this.proxy = options.proxy; this.viewport = options.viewport; this.debug = options.debug || false; } // =========================================================================== // Core Command Execution // =========================================================================== private async exec<T = unknown>(args: string[], jsonOutput = true): Promise<ActionResult<T>> { const startTime = Date.now(); const fullArgs = [ '--session', this.session, ...(this.timeout ? ['--timeout', String(this.timeout)] : []), ...(!this.headless ? ['--headed'] : []), ...(this.executablePath ? ['--executable-path', this.executablePath] : []), ...(this.proxy ? ['--proxy-server', this.proxy] : []), ...(jsonOutput ? ['--json'] : []), ...args, ]; if (this.debug) { console.log(`[agent-browser] ${fullArgs.join(' ')}`); } return new Promise((resolve) => { try { const result = execFileSync('agent-browser', fullArgs, { encoding: 'utf-8', timeout: this.timeout + 5000, stdio: ['pipe', 'pipe', 'pipe'], }); const duration = Date.now() - startTime; if (jsonOutput) { try { const parsed = JSON.parse(result); resolve({ success: parsed.success !== false, data: (parsed.data || parsed) as T, duration, }); } catch { resolve({ success: true, data: result.trim() as T, duration }); } } else { resolve({ success: true, data: result.trim() as T, duration }); } } catch (error) { const duration = Date.now() - startTime; const message = error instanceof Error ? error.message : String(error); resolve({ success: false, error: message, duration }); } }); } // =========================================================================== // Navigation Commands // =========================================================================== async open(input: OpenInput): Promise<ActionResult> { const args = ['open', input.url]; if (input.waitUntil) args.push('--wait', input.waitUntil); if (input.headers) args.push('--headers', JSON.stringify(input.headers)); return this.exec(args); } async back(): Promise<ActionResult> { return this.exec(['back']); } async forward(): Promise<ActionResult> { return this.exec(['forward']); } async reload(): Promise<ActionResult> { return this.exec(['reload']); } async close(): Promise<ActionResult> { return this.exec(['close']); } // =========================================================================== // Interaction Commands // =========================================================================== async click(input: ClickInput): Promise<ActionResult> { const args = ['click', input.target]; if (input.button) args.push('--button', input.button); if (input.clickCount) args.push('--click-count', String(input.clickCount)); if (input.force) args.push('--force'); return this.exec(args); } async dblclick(target: string): Promise<ActionResult> { return this.exec(['dblclick', target]); } async fill(input: FillInput): Promise<ActionResult> { return this.exec(['fill', input.target, input.value]); } async type(input: TypeInput): Promise<ActionResult> { const args = ['type', input.target, input.text]; if (input.delay) args.push('--delay', String(input.delay)); return this.exec(args); } async press(key: string, delay?: number): Promise<ActionResult> { const args = ['press', key]; if (delay) args.push('--delay', String(delay)); return this.exec(args); } async hover(target: string): Promise<ActionResult> { return this.exec(['hover', target]); } async focus(target: string): Promise<ActionResult> { return this.exec(['focus', target]); } async select(target: string, value: string): Promise<ActionResult> { return this.exec(['select', target, value]); } async check(target: string): Promise<ActionResult> { return this.exec(['check', target]); } async uncheck(target: string): Promise<ActionResult> { return this.exec(['uncheck', target]); } async scroll(direction: 'up' | 'down' | 'left' | 'right', pixels?: number): Promise<ActionResult> { const args = ['scroll', direction]; if (pixels) args.push(String(pixels)); return this.exec(args); } async scrollIntoView(target: string): Promise<ActionResult> { return this.exec(['scrollintoview', target]); } async drag(source: string, target: string): Promise<ActionResult> { return this.exec(['drag', source, target]); } async upload(target: string, files: string[]): Promise<ActionResult> { return this.exec(['upload', target, ...files]); } // =========================================================================== // Information Retrieval // =========================================================================== async get(input: GetInput): Promise<ActionResult> { const args = ['get', input.type]; if (input.target) args.push(input.target); if (input.attribute && input.type === 'attr') args.push(input.attribute); return this.exec(args); } async getText(target: string): Promise<ActionResult<string>> { return this.exec<string>(['get', 'text', target]); } async getHtml(target: string): Promise<ActionResult<string>> { return this.exec<string>(['get', 'html', target]); } async getValue(target: string): Promise<ActionResult<string>> { return this.exec<string>(['get', 'value', target]); } async getAttr(target: string, attribute: string): Promise<ActionResult<string>> { return this.exec<string>(['get', 'attr', target, attribute]); } async getTitle(): Promise<ActionResult<string>> { return this.exec<string>(['get', 'title']); } async getUrl(): Promise<ActionResult<string>> { return this.exec<string>(['get', 'url']); } async getCount(selector: string): Promise<ActionResult<number>> { return this.exec<number>(['get', 'count', selector]); } async getBox(target: string): Promise<ActionResult<{ x: number; y: number; width: number; height: number }>> { return this.exec<{ x: number; y: number; width: number; height: number }>(['get', 'box', target]); } // =========================================================================== // State Checks // =========================================================================== async isVisible(target: string): Promise<ActionResult<boolean>> { return this.exec<boolean>(['is', 'visible', target]); } async isEnabled(target: string): Promise<ActionResult<boolean>> { return this.exec<boolean>(['is', 'enabled', target]); } async isChecked(target: string): Promise<ActionResult<boolean>> { return this.exec<boolean>(['is', 'checked', target]); } // =========================================================================== // Snapshot & Screenshot // =========================================================================== async snapshot(options: SnapshotOptions = {}): Promise<ActionResult<Snapshot>> { const args = ['snapshot']; if (options.interactive) args.push('-i'); if (options.compact) args.push('-c'); if (options.depth) args.push('-d', String(options.depth)); if (options.selector) args.push('-s', options.selector); return this.exec<Snapshot>(args); } async screenshot(input: ScreenshotInput = {}): Promise<ActionResult<string>> { const args = ['screenshot']; if (input.path) args.push(input.path); if (input.fullPage) args.push('--full'); return this.exec<string>(args); } async pdf(path: string): Promise<ActionResult> { return this.exec(['pdf', path]); } // =========================================================================== // Wait Commands // =========================================================================== async wait(input: WaitInput): Promise<ActionResult> { if (input.selector) { return this.exec(['wait', input.selector]); } if (typeof input.timeout === 'number' && !input.text && !input.url && !input.load && !input.fn) { return this.exec(['wait', String(input.timeout)]); } const args = ['wait']; if (input.text) args.push('--text', input.text); if (input.url) args.push('--url', input.url); if (input.load) args.push('--load', input.load); if (input.fn) args.push('--fn', input.fn); return this.exec(args); } async waitForSelector(selector: string, timeout?: number): Promise<ActionResult> { const args = ['wait', selector]; if (timeout) args.push('--timeout', String(timeout)); return this.exec(args); } async waitForText(text: string): Promise<ActionResult> { return this.exec(['wait', '--text', text]); } async waitForUrl(pattern: string): Promise<ActionResult> { return this.exec(['wait', '--url', pattern]); } async waitForLoad(state: 'load' | 'domcontentloaded' | 'networkidle'): Promise<ActionResult> { return this.exec(['wait', '--load', state]); } async waitForFunction(fn: string): Promise<ActionResult> { return this.exec(['wait', '--fn', fn]); } // =========================================================================== // JavaScript Execution // =========================================================================== async eval<T = unknown>(input: EvalInput): Promise<ActionResult<T>> { return this.exec<T>(['eval', input.script]); } // =========================================================================== // Mouse Control // =========================================================================== async mouseMove(x: number, y: number): Promise<ActionResult> { return this.exec(['mouse', 'move', String(x), String(y)]); } async mouseDown(button: 'left' | 'right' | 'middle' = 'left'): Promise<ActionResult> { return this.exec(['mouse', 'down', button]); } async mouseUp(button: 'left' | 'right' | 'middle' = 'left'): Promise<ActionResult> { return this.exec(['mouse', 'up', button]); } async mouseWheel(deltaY: number, deltaX = 0): Promise<ActionResult> { return this.exec(['mouse', 'wheel', String(deltaY), String(deltaX)]); } // =========================================================================== // Browser Settings // =========================================================================== async setViewport(width: number, height: number): Promise<ActionResult> { return this.exec(['set', 'viewport', String(width), String(height)]); } async setDevice(device: string): Promise<ActionResult> { return this.exec(['set', 'device', device]); } async setGeolocation(lat: number, lng: number): Promise<ActionResult> { return this.exec(['set', 'geo', String(lat), String(lng)]); } async setOffline(enabled: boolean): Promise<ActionResult> { return this.exec(['set', 'offline', enabled ? 'on' : 'off']); } async setHeaders(headers: Record<string, string>): Promise<ActionResult> { return this.exec(['set', 'headers', JSON.stringify(headers)]); } async setCredentials(username: string, password: string): Promise<ActionResult> { return this.exec(['set', 'credentials', username, password]); } async setMedia(scheme: 'dark' | 'light'): Promise<ActionResult> { return this.exec(['set', 'media', scheme]); } // =========================================================================== // Cookies & Storage // =========================================================================== async getCookies(): Promise<ActionResult> { return this.exec(['cookies']); } async setCookie(name: string, value: string): Promise<ActionResult> { return this.exec(['cookies', 'set', name, value]); } async clearCookies(): Promise<ActionResult> { return this.exec(['cookies', 'clear']); } async getLocalStorage(key?: string): Promise<ActionResult> { const args = ['storage', 'local']; if (key) args.push(key); return this.exec(args); } async setLocalStorage(key: string, value: string): Promise<ActionResult> { return this.exec(['storage', 'local', 'set', key, value]); } async clearLocalStorage(): Promise<ActionResult> { return this.exec(['storage', 'local', 'clear']); } async getSessionStorage(key?: string): Promise<ActionResult> { const args = ['storage', 'session']; if (key) args.push(key); return this.exec(args); } async setSessionStorage(key: string, value: string): Promise<ActionResult> { return this.exec(['storage', 'session', 'set', key, value]); } async clearSessionStorage(): Promise<ActionResult> { return this.exec(['storage', 'session', 'clear']); } // =========================================================================== // Network // =========================================================================== async networkRoute(input: NetworkRouteInput): Promise<ActionResult> { const args = ['network', 'route', input.urlPattern]; if (input.abort) args.push('--abort'); if (input.body) args.push('--body', typeof input.body === 'string' ? input.body : JSON.stringify(input.body)); if (input.status) args.push('--status', String(input.status)); if (input.headers) args.push('--headers', JSON.stringify(input.headers)); return this.exec(args); } async networkUnroute(urlPattern?: string): Promise<ActionResult> { const args = ['network', 'unroute']; if (urlPattern) args.push(urlPattern); return this.exec(args); } async networkRequests(filter?: string): Promise<ActionResult> { const args = ['network', 'requests']; if (filter) args.push('--filter', filter); return this.exec(args); } // =========================================================================== // Tabs & Windows // =========================================================================== async listTabs(): Promise<ActionResult> { return this.exec(['tab']); } async newTab(url?: string): Promise<ActionResult> { const args = ['tab', 'new']; if (url) args.push(url); return this.exec(args); } async switchTab(index: number): Promise<ActionResult> { return this.exec(['tab', String(index)]); } async closeTab(index?: number): Promise<ActionResult> { const args = ['tab', 'close']; if (index !== undefined) args.push(String(index)); return this.exec(args); } async newWindow(): Promise<ActionResult> { return this.exec(['window', 'new']); } // =========================================================================== // Frames // =========================================================================== async switchFrame(selector: string): Promise<ActionResult> { return this.exec(['frame', selector]); } async switchToMainFrame(): Promise<ActionResult> { return this.exec(['frame', 'main']); } // =========================================================================== // Dialogs // =========================================================================== async dialogAccept(text?: string): Promise<ActionResult> { const args = ['dialog', 'accept']; if (text) args.push(text); return this.exec(args); } async dialogDismiss(): Promise<ActionResult> { return this.exec(['dialog', 'dismiss']); } // =========================================================================== // Debug & Trace // =========================================================================== async traceStart(path?: string): Promise<ActionResult> { const args = ['trace', 'start']; if (path) args.push(path); return this.exec(args); } async traceStop(path?: string): Promise<ActionResult> { const args = ['trace', 'stop']; if (path) args.push(path); return this.exec(args); } async getConsole(): Promise<ActionResult> { return this.exec(['console']); } async clearConsole(): Promise<ActionResult> { return this.exec(['console', '--clear']); } async getErrors(): Promise<ActionResult> { return this.exec(['errors']); } async clearErrors(): Promise<ActionResult> { return this.exec(['errors', '--clear']); } async highlight(selector: string): Promise<ActionResult> { return this.exec(['highlight', selector]); } async saveState(path: string): Promise<ActionResult> { return this.exec(['state', 'save', path]); } async loadState(path: string): Promise<ActionResult> { return this.exec(['state', 'load', path]); } // =========================================================================== // Find Commands (Semantic Locators) // =========================================================================== async findByRole(role: string, action: string, options?: { name?: string; exact?: boolean }): Promise<ActionResult> { const args = ['find', 'role', role, action]; if (options?.name) args.push('--name', options.name); if (options?.exact) args.push('--exact'); return this.exec(args); } async findByText(text: string, action: string): Promise<ActionResult> { return this.exec(['find', 'text', text, action]); } async findByLabel(label: string, action: string, value?: string): Promise<ActionResult> { const args = ['find', 'label', label, action]; if (value) args.push(value); return this.exec(args); } async findByPlaceholder(placeholder: string, action: string, value?: string): Promise<ActionResult> { const args = ['find', 'placeholder', placeholder, action]; if (value) args.push(value); return this.exec(args); } async findByTestId(testId: string, action: string, value?: string): Promise<ActionResult> { const args = ['find', 'testid', testId, action]; if (value) args.push(value); return this.exec(args); } async findFirst(selector: string, action: string, value?: string): Promise<ActionResult> { const args = ['find', 'first', selector, action]; if (value) args.push(value); return this.exec(args); } async findLast(selector: string, action: string, value?: string): Promise<ActionResult> { const args = ['find', 'last', selector, action]; if (value) args.push(value); return this.exec(args); } async findNth(n: number, selector: string, action: string, value?: string): Promise<ActionResult> { const args = ['find', 'nth', String(n), selector, action]; if (value) args.push(value); return this.exec(args); } // =========================================================================== // Session Management // =========================================================================== async listSessions(): Promise<ActionResult> { return this.exec(['session', 'list']); } async getCurrentSession(): Promise<ActionResult> { return this.exec(['session']); } setSession(sessionId: string): void { this.session = sessionId; } getSession(): string { return this.session; } // =========================================================================== // CDP Connection // =========================================================================== async connect(port: number): Promise<ActionResult> { return this.exec(['connect', String(port)]); } // =========================================================================== // Setup // =========================================================================== static async install(withDeps = false): Promise<ActionResult> { const args = ['install']; if (withDeps) args.push('--with-deps'); return new Promise((resolve) => { try { const result = execFileSync('agent-browser', args, { encoding: 'utf-8', timeout: 300000, // 5 minutes for download }); resolve({ success: true, data: result }); } catch (error) { const message = error instanceof Error ? error.message : String(error); resolve({ success: false, error: message }); } }); } } export default AgentBrowserAdapter;