{"id":44949952,"url":"https://github.com/idan-rubin/browserclaw","last_synced_at":"2026-02-18T10:08:33.736Z","repository":{"id":337352477,"uuid":"1153014783","full_name":"idan-rubin/browserclaw","owner":"idan-rubin","description":"AI-friendly browser automation with snapshot + ref targeting. Standalone wrapper around OpenClaw's browser module.","archived":false,"fork":false,"pushed_at":"2026-02-16T22:05:19.000Z","size":934,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-17T04:53:22.900Z","etag":null,"topics":["accessibility","ai","ai-agent","browser-automation","cdp","chrome","llm","openclaw","playwright","snapshot","typescript","web-scraping"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/idan-rubin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-08T19:16:06.000Z","updated_at":"2026-02-16T22:05:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/idan-rubin/browserclaw","commit_stats":null,"previous_names":["idan-rubin/browserclaw"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/idan-rubin/browserclaw","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idan-rubin%2Fbrowserclaw","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idan-rubin%2Fbrowserclaw/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idan-rubin%2Fbrowserclaw/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idan-rubin%2Fbrowserclaw/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/idan-rubin","download_url":"https://codeload.github.com/idan-rubin/browserclaw/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idan-rubin%2Fbrowserclaw/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29575395,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T08:38:15.585Z","status":"ssl_error","status_checked_at":"2026-02-18T08:38:14.917Z","response_time":162,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["accessibility","ai","ai-agent","browser-automation","cdp","chrome","llm","openclaw","playwright","snapshot","typescript","web-scraping"],"created_at":"2026-02-18T10:08:32.077Z","updated_at":"2026-02-18T10:08:33.728Z","avatar_url":"https://github.com/idan-rubin.png","language":"TypeScript","funding_links":[],"categories":["Utils"],"sub_categories":[],"readme":"\u003ch2 align=\"center\"\u003e🦞 BrowserClaw — Standalone OpenClaw browser module\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/browserclaw\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/browserclaw.svg\" alt=\"npm version\" /\u003e\u003c/a\u003e\n  \u003ca href=\"./LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/License-MIT-blue.svg\" alt=\"License: MIT\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nExtracted and refined from [OpenClaw](https://github.com/openclaw/openclaw)'s browser automation module. A standalone, typed library for AI-friendly browser control with **snapshot + ref targeting** — no CSS selectors, no XPath, no vision, just numbered refs that map to interactive elements.\n\n```typescript\nimport { BrowserClaw } from 'browserclaw';\n\nconst browser = await BrowserClaw.launch({ headless: false });\nconst page = await browser.open('https://example.com');\n\n// Snapshot — the core feature\nconst { snapshot, refs } = await page.snapshot();\n// snapshot: AI-readable text tree\n// refs: { \"e1\": { role: \"link\", name: \"More info\" }, \"e2\": { role: \"button\", name: \"Submit\" } }\n\nawait page.click('e1');         // Click by ref\nawait page.type('e3', 'hello'); // Type by ref\nawait browser.stop();\n```\n\n## Why browserclaw?\n\nMost browser automation tools were built for humans writing test scripts. AI agents need something different:\n\n- **Vision-based tools** (screenshot → click coordinates) are slow, expensive, and probabilistic\n- **Selector-based tools** (CSS/XPath) are brittle and meaningless to an LLM\n- **browserclaw** gives the AI a **text snapshot** with numbered refs — the AI reads text (what it's best at) and returns a ref ID (deterministic targeting)\n\nThe snapshot + ref pattern means:\n1. **Deterministic** — refs resolve to exact elements via Playwright locators, no guessing\n2. **Fast** — text snapshots are tiny compared to screenshots\n3. **Cheap** — no vision API calls, just text in/text out\n4. **Reliable** — built on Playwright, the most robust browser automation engine\n\n## Comparison with Other Tools\n\nThe AI browser automation space is moving fast. Here's how browserclaw compares to the major alternatives.\n\n| | [browserclaw](https://github.com/idan-rubin/browserclaw) | [browser-use](https://github.com/browser-use/browser-use) | [Stagehand](https://github.com/browserbase/stagehand) | [Skyvern](https://github.com/Skyvern-AI/skyvern) | [Playwright MCP](https://github.com/microsoft/playwright-mcp) |\n|:---|:---:|:---:|:---:|:---:|:---:|\n| Ref → exact element, no guessing | :white_check_mark: | :heavy_minus_sign: | :x: | :x: | :white_check_mark: |\n| No vision model in the loop | :white_check_mark: | :heavy_minus_sign: | :white_check_mark: | :x: | :white_check_mark: |\n| Survives redesigns (semantic, not pixel) | :white_check_mark: | :heavy_minus_sign: | :white_check_mark: | :x: | :white_check_mark: |\n| Fill 10 form fields in one call | :white_check_mark: | :x: | :x: | :x: | :x: |\n| Interact with cross-origin iframes | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: |\n| Playwright engine (auto-wait, locators) | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: |\n| Embeddable in your own agent loop | :white_check_mark: | :x: | :heavy_minus_sign: | :x: | :x: |\n\n:white_check_mark: = Yes\u0026ensp; :heavy_minus_sign: = Partial\u0026ensp; :x: = No\n\n**browserclaw is the only tool that checks every box.** It combines the precision of accessibility snapshots with Playwright's battle-tested engine, batch operations, cross-origin iframe access, and zero framework lock-in — in a single embeddable library.\n\n### How each tool works under the hood\n\n- **browserclaw** — Accessibility snapshot with numbered refs → Playwright locator (`aria-ref` in default mode, `getByRole()` in role mode). One ref, one element. No vision model, no LLM in the targeting loop.\n- **browser-use** — DOM element indexing via raw CDP + optional screenshots. [Dropped Playwright](https://browser-use.com/posts/playwright-to-cdp) to go \"closer to the metal\" — fast, but now reinvents auto-wait, retry logic, and cross-browser support from scratch.\n- **Stagehand** — Accessibility tree + natural language primitives (`page.act(\"click login\")`). Convenient, but the LLM re-interprets which element to target on every single call — non-deterministic by design.\n- **Skyvern** — Vision-first. Screenshots sent to a Vision LLM that guesses coordinates. Multi-agent architecture (Planner/Actor/Validator) adds self-correction, but at significant cost and latency.\n- **Playwright MCP** — Same snapshot philosophy as browserclaw, but locked to the MCP protocol. Great for chat-based agents, but not embeddable as a library — you can't compose it into your own agent loop or call it from application code.\n\n**Also in the space:** [LaVague](https://github.com/lavague-ai/LaVague) (generates Selenium code via RAG on HTML), [AgentQL](https://github.com/tinyfish-io/agentql) (semantic query language for the DOM), [Vercel agent-browser](https://github.com/vercel-labs/agent-browser) (element refs like `@e1` — a similar ref-based approach).\n\n### Why this matters for repeated complex UI tasks\n\nWhen you're running the same multi-step workflow hundreds of times — filling forms, navigating dashboards, processing queues — the differences compound:\n\n- **Cost**: ~4x fewer tokens per run than vision-based tools. A 20-step task repeated 100 times: ~3M tokens vs ~12M+.\n- **Speed**: No vision API round-trips. A 20-step workflow finishes in seconds, not minutes.\n- **Reliability**: Ref-based targeting is deterministic. Same page state → same refs → same result. No coordinate guessing, no LLM re-interpretation.\n- **Simplicity**: No framework opinions, no agent loop, no hosted platform. Just `snapshot()` → read refs → act. Compose it into whatever agent architecture you want.\n\n## Install\n\n```bash\nnpm install browserclaw\n```\n\nRequires a Chromium-based browser installed on the system (Chrome, Brave, Edge, or Chromium). browserclaw auto-detects your installed browser — no need to install Playwright browsers separately.\n\n## How It Works\n\n```\n┌─────────────┐     snapshot()     ┌─────────────────────────────────┐\n│  Web Page   │ ──────────────►    │  AI-readable text tree          │\n│             │                    │                                 │\n│  [buttons]  │                    │  - heading \"Example Domain\"     │\n│  [links]    │                    │  - paragraph \"This domain...\"   │\n│  [inputs]   │                    │  - link \"More information\" [e1] │\n└─────────────┘                    └──────────────┬──────────────────┘\n                                                  │\n                                          AI reads snapshot,\n                                          decides: click e1\n                                                  │\n┌─────────────┐     click('e1')    ┌──────────────▼──────────────────┐\n│  Web Page   │ ◄──────────────    │  Ref \"e1\" resolves to a         │\n│  (navigated)│                    │  Playwright locator — one ref,  │\n│             │                    │  one exact element              │\n└─────────────┘                    └─────────────────────────────────┘\n```\n\n1. **Snapshot** a page → get an AI-readable text tree with numbered refs (`e1`, `e2`, `e3`...)\n2. **AI reads** the snapshot text and picks a ref to act on\n3. **Actions target refs** → browserclaw resolves each ref to a Playwright locator and executes the action\n\n\u003e **Note:** Refs are scoped to the snapshot that created them. After navigation or DOM changes, old refs become invalid — actions will fail with an error (timeout in aria mode, `\"Unknown ref\"` in role mode). Always re-snapshot before acting on a changed page.\n\n## API\n\n### Launch \u0026 Connect\n\n```typescript\n// Launch a new Chrome instance (auto-detects Chrome/Brave/Edge/Chromium)\nconst browser = await BrowserClaw.launch({\n  headless: false,       // default: false (visible window)\n  executablePath: '...', // optional: specific browser path\n  cdpPort: 9222,         // default: 9222\n  noSandbox: false,      // default: false (set true for Docker/CI)\n  userDataDir: '...',    // optional: custom user data directory\n  profileName: 'browserclaw', // profile name in Chrome title bar\n  profileColor: '#FF4500',    // profile accent color (hex)\n  chromeArgs: ['--start-maximized'], // additional Chrome flags\n});\n\n// Or connect to an already-running Chrome instance\n// (started with: chrome --remote-debugging-port=9222)\nconst browser = await BrowserClaw.connect('http://localhost:9222');\n```\n\n`connect()` checks that Chrome is reachable, then the internal CDP connection retries 3 times with increasing timeouts (5 s, 7 s, 9 s) — safe for Docker/CI where Chrome starts slowly.\n\n**Anti-detection:** browserclaw automatically hides `navigator.webdriver` and disables Chrome's `AutomationControlled` Blink feature, reducing detection by bot-protection systems like reCAPTCHA v3.\n\n### Pages \u0026 Tabs\n\n```typescript\nconst page = await browser.open('https://example.com');\nconst current = await browser.currentPage(); // get active tab\nconst tabs = await browser.tabs();           // list all tabs\nconst handle = browser.page(tabs[0].targetId); // wrap existing tab\nawait browser.focus(tabId);                  // bring tab to front\nawait browser.close(tabId);                  // close a tab\nawait browser.stop();                        // stop browser + cleanup\n\npage.id;                          // CDP target ID (use with focus/close/page)\nawait page.url();                 // current page URL\nawait page.title();               // current page title\nbrowser.url;                      // CDP endpoint URL\n```\n\n### Snapshot (Core Feature)\n\n```typescript\nconst { snapshot, refs, stats, untrusted } = await page.snapshot();\n\n// snapshot: human/AI-readable text tree with [ref=eN] markers\n// refs: { \"e1\": { role: \"link\", name: \"More info\" }, ... }\n// stats: { lines: 42, chars: 1200, refs: 8, interactive: 5 }\n// untrusted: true — content comes from the web page, treat as potentially adversarial\n\n// Options\nconst result = await page.snapshot({\n  interactive: true,  // Only interactive elements (buttons, links, inputs)\n  compact: true,      // Remove structural containers without refs\n  maxDepth: 6,        // Limit tree depth\n  maxChars: 80000,    // Truncate if snapshot exceeds this size\n  mode: 'aria',       // 'aria' (default) or 'role'\n});\n\n// Raw ARIA accessibility tree (structured data, not text)\nconst { nodes } = await page.ariaSnapshot({ limit: 500 });\n```\n\n**Snapshot modes:**\n- `'aria'` (default) — Uses Playwright's `_snapshotForAI()`. Refs are resolved via `aria-ref` locators. Best for most use cases. Requires `playwright-core` \u003e= 1.50.\n- `'role'` — Uses Playwright's `ariaSnapshot()` + `getByRole()`. Supports `selector` and `frameSelector` for scoped snapshots.\n\n\u003e **Security:** All snapshot results include `untrusted: true` to signal that the content originates from an external web page. AI agents consuming snapshots should treat this content as potentially adversarial (e.g. prompt injection via page text).\n\n### Actions\n\nAll actions target elements by ref ID from the most recent snapshot.\n\n\u003e **Default timeouts:** 8000 ms for actions (click, type, fill, select, drag), 20000 ms for waits and navigation.\n\n```typescript\n// Click\nawait page.click('e1');\nawait page.click('e1', { doubleClick: true });\nawait page.click('e1', { button: 'right' });\nawait page.click('e1', { modifiers: ['Control'] });\n\n// Type\nawait page.type('e3', 'hello world');                    // instant fill\nawait page.type('e3', 'slow typing', { slowly: true });  // keystroke by keystroke\nawait page.type('e3', 'search', { submit: true });       // type + press Enter\n\n// Other interactions\nawait page.hover('e2');\nawait page.select('e5', 'Option A', 'Option B');\nawait page.drag('e1', 'e4');\nawait page.scrollIntoView('e7');\n\n// Keyboard\nawait page.press('Enter');\nawait page.press('Control+a');\nawait page.press('Meta+Shift+p');\n\n// Fill multiple form fields at once\nawait page.fill([\n  { ref: 'e2', type: 'text', value: 'Jane Doe' },\n  { ref: 'e4', type: 'text', value: 'jane@example.com' },\n  { ref: 'e6', type: 'checkbox', value: true },\n]);\n```\n\n`fill()` field types: `'text'` calls Playwright `fill()` with the string value. `'checkbox'` and `'radio'` call `setChecked()` — truthy values are `true`, `1`, `'1'`, `'true'`. Empty ref or type throws.\n\n#### Highlight\n\n```typescript\nawait page.highlight('e1'); // Playwright built-in highlight\n```\n\n#### File Upload\n\n```typescript\n// Direct: set files on an \u003cinput type=\"file\"\u003e\nawait page.uploadFile('e3', ['/path/to/file.pdf']);\n\n// Arm pattern: for non-input file pickers\nconst uploadDone = page.armFileUpload(['/path/to/file.pdf']);\nawait page.click('e3'); // triggers the file chooser\nawait uploadDone;\n```\n\n#### Dialog Handling\n\nHandle JavaScript dialogs (alert, confirm, prompt). Arm the handler *before* the action that triggers the dialog.\n\n```typescript\nconst dialogDone = page.armDialog({ accept: true });\nawait page.click('e5'); // triggers confirm()\nawait dialogDone;\n\n// With prompt text\nconst promptDone = page.armDialog({ accept: true, promptText: 'my answer' });\nawait page.click('e6'); // triggers prompt()\nawait promptDone;\n```\n\n### Navigation \u0026 Waiting\n\n```typescript\nawait page.goto('https://example.com');\nawait page.reload();                                     // reload the current page\nawait page.goBack();                                     // navigate back in history\nawait page.goForward();                                  // navigate forward in history\nawait page.waitFor({ loadState: 'networkidle' });\nawait page.waitFor({ text: 'Welcome' });\nawait page.waitFor({ textGone: 'Loading...' });\nawait page.waitFor({ url: '**/dashboard' });\nawait page.waitFor({ selector: '.loaded' });        // wait for CSS selector\nawait page.waitFor({ fn: '() =\u003e document.readyState === \"complete\"' }); // custom JS\nawait page.waitFor({ timeMs: 1000 });                // sleep\nawait page.waitFor({ text: 'Ready', timeoutMs: 5000 }); // custom timeout\n```\n\n### Capture\n\n```typescript\n// Screenshots\nconst screenshot = await page.screenshot();                   // viewport PNG → Buffer\nconst fullPage = await page.screenshot({ fullPage: true });   // full scrollable page\nconst element = await page.screenshot({ ref: 'e1' });         // specific element by ref\nconst bySelector = await page.screenshot({ element: '.hero' }); // by CSS selector\nconst jpeg = await page.screenshot({ type: 'jpeg' });         // JPEG format\n\n// PDF\nconst pdf = await page.pdf();                                  // PDF export (headless only)\n\n// Labeled screenshot — numbered badges on each ref for visual debugging\nconst { buffer, labels, skipped } = await page.screenshotWithLabels(['e1', 'e2', 'e3']);\n// buffer: PNG with numbered overlays\n// labels: [{ ref: 'e1', index: 1, box: { x, y, width, height } }, ...]\n// skipped: refs that couldn't be found or had no bounding box\n```\n\nBoth `screenshot()` and `pdf()` return a `Buffer`. Write to file with `fs.writeFileSync('out.png', screenshot)`.\n\n#### Trace Recording\n\nCapture Playwright traces (screenshots, DOM snapshots, network) for debugging.\n\n```typescript\nawait page.traceStart({ screenshots: true, snapshots: true });\n// ... perform actions ...\nawait page.traceStop('trace.zip');\n// Open with: npx playwright show-trace trace.zip\n```\n\n#### Response Body\n\nIntercept a network response and read its body.\n\n```typescript\nconst resp = await page.responseBody('/api/data');\nconsole.log(resp.status, resp.body);\n// { url, status, headers, body, truncated }\n```\n\nOptions: `timeoutMs` (default 30 s), `maxChars` (truncate body).\n\n### Activity Monitoring\n\nConsole messages, errors, and network requests are buffered automatically.\n\n```typescript\nconst logs = await page.consoleLogs();                            // all messages\nconst errors = await page.consoleLogs({ level: 'error' });        // errors only\nconst recent = await page.consoleLogs({ clear: true });           // read and clear buffer\nconst pageErrors = await page.pageErrors();                       // uncaught exceptions\nconst requests = await page.networkRequests({ filter: '/api' });  // filter by URL\nconst fresh = await page.networkRequests({ clear: true });        // read and clear buffer\n```\n\n### Storage\n\n```typescript\n// Cookies\nconst cookies = await page.cookies();\nawait page.setCookie({ name: 'token', value: 'abc', url: 'https://example.com' });\nawait page.clearCookies();\n\n// localStorage / sessionStorage\nconst values = await page.storageGet('local');\nconst token = await page.storageGet('local', 'authToken');\nawait page.storageSet('local', 'key', 'value');\nawait page.storageClear('session');\n```\n\n### Downloads\n\n```typescript\n// Click a download link and save the file\nconst result = await page.download('e7', '/tmp/report.pdf');\nconsole.log(result.suggestedFilename); // 'report.pdf'\n// Returns: { url, suggestedFilename, path }\n\n// Arm pattern: wait for next download (call before triggering)\nconst dlPromise = page.waitForDownload({ path: '/tmp/file.pdf' });\nawait page.click('e8'); // triggers download\nconst dl = await dlPromise;\n```\n\n### Emulation\n\n```typescript\n// Device emulation (viewport + user agent)\nawait page.setDevice('iPhone 13');\n\n// Color scheme\nawait page.emulateMedia({ colorScheme: 'dark' });\n\n// Geolocation\nawait page.setGeolocation({ latitude: 48.8566, longitude: 2.3522 }); // Paris\nawait page.setGeolocation({ clear: true }); // reset\n\n// Locale \u0026 timezone\nawait page.setLocale('fr-FR');\nawait page.setTimezone('Europe/Paris');\n\n// Network\nawait page.setOffline(true);\nawait page.setExtraHeaders({ 'X-Custom': 'value' });\nawait page.setHttpCredentials({ username: 'admin', password: 'secret' });\nawait page.setHttpCredentials({ clear: true }); // remove\n```\n\n### Evaluate\n\nRun JavaScript directly in the browser page context.\n\n```typescript\nconst title = await page.evaluate('() =\u003e document.title');\nconst text = await page.evaluate('(el) =\u003e el.textContent', { ref: 'e1' });\nconst count = await page.evaluate('() =\u003e document.querySelectorAll(\"img\").length');\n```\n\n#### `evaluateInAllFrames(fn)`\n\nRun JavaScript in ALL frames on the page, including cross-origin iframes. Playwright bypasses the same-origin policy via CDP, making this essential for interacting with embedded payment forms (Stripe, etc.).\n\n```typescript\nconst results = await page.evaluateInAllFrames(`() =\u003e {\n  const el = document.querySelector('input[name=\"cardnumber\"]');\n  return el ? 'found' : null;\n}`);\n// Returns: [{ frameUrl: '...', frameName: '...', result: 'found' }, ...]\n```\n\n### Viewport\n\n```typescript\nawait page.resize(1280, 720);\n```\n\n## Examples\n\nSee the [`examples/`](./examples) directory for runnable demos:\n\n- **[basic.ts](./examples/basic.ts)** — Navigate, snapshot, click a ref\n- **[form-fill.ts](./examples/form-fill.ts)** — Fill a multi-field form using refs\n- **[ai-agent.ts](./examples/ai-agent.ts)** — AI agent loop pattern with Claude/GPT\n\nRun from the source tree:\n\n```bash\nnpx tsx examples/basic.ts\n```\n\n## Requirements\n\n- **Node.js** \u003e= 18\n- **Chromium-based browser** installed (Chrome, Brave, Edge, or Chromium)\n- **playwright-core** \u003e= 1.50 (installed automatically as a dependency)\n\nNo need to install Playwright browsers — browserclaw uses your system's existing Chrome installation via CDP.\n\n## Contributing\n\nContributions welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b my-feature`)\n3. Make your changes\n4. Run `npm run typecheck \u0026\u0026 npm run build` to verify\n5. Submit a pull request\n\n## Acknowledgments\n\nbrowserclaw is extracted and refined from the browser automation module in [OpenClaw](https://github.com/openclaw/openclaw), built by [Peter Steinberger](https://github.com/steipete) and an [amazing community of contributors](https://github.com/openclaw/openclaw?tab=readme-ov-file#community). The snapshot + ref system, CDP connection management, and Playwright integration originate from that project.\n\n## License\n\n[MIT](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fidan-rubin%2Fbrowserclaw","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fidan-rubin%2Fbrowserclaw","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fidan-rubin%2Fbrowserclaw/lists"}