{"slug": "chrome-agent-llm-native-browser-automation-tool-written-in-rust", "title": "Chrome-agent: LLM-native browser automation tool written in Rust", "summary": "A new open-source tool called chrome-agent, built in Rust, provides an LLM-native browser automation tool that reduces page representation to ~50 tokens using the accessibility tree, compared to ~2,000 tokens from Playwright. The 3 MB binary requires no runtime dependencies and combines action and observation in a single call, aiming to minimize token usage for AI agents. The project is independent and not affiliated with Google or the Chrome team.", "body_md": "**Browser automation that speaks LLM.**\n\nDisclaimer:This is an independent, community-driven project. It is not affiliated with, endorsed by, or sponsored by Google or the Chrome team.\n\nYou're not the user. Your LLM is.\n\nYou don't need to read this README. Your agent does. Install it, run\n\n`chrome-agent --help`\n\n, and let the LLM figure it out. The CLI embeds its own usage guide, every error comes with a hint for the next action, and`--json`\n\nmode outputs structured data an agent can parse without you writing a single adapter. This page is here because GitHub expects one.\n\n[agent-browser](https://github.com/vercel-labs/agent-browser) (Vercel) is a feature-complete browser automation platform: dashboard, cloud providers, annotated screenshots, iOS support, AI chat, auth vault, 40K lines of Rust. It's excellent.\n\nchrome-agent is the opposite bet. Instead of adding features, it removes tokens.\n\n| chrome-agent | agent-browser | |\n|---|---|---|\nPage snapshot |\n~50 tokens (a11y noise stripped, 66% reduction) | ~200 tokens (full a11y tree) |\nElement IDs |\n`backendNodeId` — stable across inspects |\nSequential `@e1, @e2` — reassigned every snapshot |\nAction + observe |\n`click n12 --inspect` (1 call) |\n`click @e1` then `snapshot` (2 calls) |\nStealth |\n7 native CDP patches (incl. `Runtime.enable` skip) |\nDelegated to cloud providers |\nContent extraction |\n`read` (articles), `extract` (auto-detect lists/tables) |\nNone built-in |\nBinary |\n3 MB, zero runtime | 3 MB + Next.js dashboard + cloud SDKs |\nCodebase |\n~8.8K lines | 40K lines |\n\nagent-browser gives you a platform with monitoring, cloud browsers, and visual debugging. chrome-agent gives your LLM the smallest possible representation of a webpage and gets out of the way. If your agent needs a dashboard, use agent-browser. If your agent needs to spend tokens on reasoning instead of page parsing, use this.\n\nEvery token your agent spends understanding a page is a token it doesn't spend reasoning about the task. chrome-agent is built around one idea: **minimize the tokens between \"what does this page look like?\" and \"what should I do next?\"**\n\nThis means:\n\n**Accessibility tree over DOM.** Playwright returns ~2,000 tokens of raw HTML. chrome-agent returns ~50 tokens of a11y tree with stable element IDs. No CSS selectors to write, no DOM to parse.**One binary, zero runtime.** 3 MB Rust binary. No Node.js, no npm, no Playwright runtime.`npx chrome-agent`\n\njust works. Linux builds are fully static (musl) — no glibc dependency, runs on any distro.**Action + observation in one call.**`--inspect`\n\non any action command returns the page state after the action. One round-trip instead of two.**Errors are instructions.** Every error includes a`hint`\n\nfield telling the agent what to do next.`{\"ok\":false, \"error\":\"...\", \"hint\":\"run inspect\"}`\n\n.**Stealth by default intent.** 7 CDP patches including the detection vector nobody talks about (`Runtime.enable`\n\n). Connect to real Chrome for the hardest protections.**Content extraction without selectors.**`read`\n\nfor articles,`extract`\n\nfor repeating data,`network`\n\nfor API payloads. The agent never writes CSS selectors.\n\nThis is not a general-purpose browser testing framework. It's a tool that makes an LLM effective at browsing the web.\n\n```\nchrome-agent goto news.ycombinator.com --inspect\n\n# ~50 tokens instead of ~2,000:\nuid=n1 RootWebArea \"Hacker News\"\n  uid=n50 heading \"Hacker News\" level=1\n  uid=n82 link \"Show HN: A New Browser Tool\"\n  uid=n97 link \"Rust 2025 Edition Announced\"\n  ...\n\n# Click + see the new page in one call:\nchrome-agent click n82 --inspect\n```\n\nUIDs are based on Chrome's `backendNodeId`\n\n. They don't change between inspects. Click `n82`\n\nnow or five minutes from now.\n\n```\nchrome-agent (3 MB Rust binary)\n    | CDP over WebSocket\n    v\nChrome (headless, no Node.js, no runtime)\n```\n\n| If you've hit this... | chrome-agent does this instead |\n|---|---|\n| Playwright snapshots burn 2K tokens | a11y tree: ~50 tokens. 40x less context spent on page state. |\n| CSS selectors break after every deploy | UIDs from Chrome's `backendNodeId` . Stable as long as the DOM node exists. |\n| Click then inspect = 2 round-trips | `--inspect` on any command. One call, action + observation. |\n| 200MB of Node + npm + Playwright | 3 MB binary. `npx chrome-agent` works out of the box. |\n| Cloudflare blocks your headless Chrome | 7 CDP patches. `Runtime.enable` never called (the detection vector nobody talks about). |\n| Writing per-site scraping selectors | `read` for articles, `extract` for lists/tables/cards, `network` for API payloads. No selectors. |\n| Errors are stack traces | `{\"ok\":false, \"error\":\"...\", \"hint\":\"run inspect\"}` -- parseable, actionable. |\n| Each command launches a fresh browser | Sessions persist. Chrome stays alive between calls. ~10ms startup. |\n| Agent can't access your logged-in accounts | `--copy-cookies` grabs cookies from your real Chrome. Works with X.com, Gmail, dashboards. |\n| Infinite scroll shows 10 items | `inspect --scroll --limit 50` scrolls and collects. Tested on X.com: 50 tweets from a live timeline. |\n| Two agents sharing one browser = chaos | `--browser agent1` , `--browser agent2` . Separate Chrome instances. |\n\n```\n# For AI agents -- installs a SKILL.md your agent reads automatically\nnpx skills add sderosiaux/chrome-agent\n\n# Or just the binary\nnpm install -g chrome-agent    # prebuilt\nnpx chrome-agent --help        # no install\ncargo install chrome-agent     # from source\n# Navigate and see the page\nchrome-agent goto https://example.com --inspect\n\n# Click by uid\nchrome-agent click n12 --inspect\n\n# Fill a form\nchrome-agent fill --uid n20 \"user@test.com\"\n\n# CSS selectors work too\nchrome-agent click --selector \"button.submit\"\nchrome-agent fill --selector \"input[name=email]\" \"hello@test.com\"\n\n# Article content (Readability -- like Firefox Reader Mode)\nchrome-agent read\n\n# Visible text, scoped and capped\nchrome-agent text --selector \"main\" --truncate 500\n\n# Run JS\nchrome-agent eval \"document.title\"\n\n# Screenshot (returns a file path, not binary)\nchrome-agent screenshot\n```\n\n| Command | What it does |\n|---|---|\n`goto <url> [--inspect] [--max-depth N] [--header \"K: V\"]` |\nNavigate. Auto-prefixes `https://` . `--header` (repeatable) sends extra HTTP headers. |\n`back` |\nHistory back. |\n`forward` |\nHistory forward. |\n`close [--purge]` |\nStop browser. `--purge` deletes cookies/profile. |\n\n| Command | What it does |\n|---|---|\n`inspect [--verbose] [--max-depth N] [--uid nN] [--filter \"role,role\"] [--scroll] [--limit N] [--urls] [--max-chars N] [--offset K]` |\na11y tree with UIDs. `--scroll --limit` for infinite scroll. `--urls` resolves href on links. `--max-chars` /`--offset` cap and page the output. |\n`diff` |\nWhat changed since last inspect. |\n`screenshot [--filename name] [--format jpeg|png] [--quality N] [--max-width N] [--uid nN|--selector \"css\"]` |\nScreenshot to file. JPEG/quality/max-width shrink it; `--uid` /`--selector` clip to one element. |\n`pdf [--filename name] [--landscape] [--background]` |\nPrint the current page to a PDF file. |\n`tabs` |\nList open tabs. |\n\n| Command | What it does |\n|---|---|\n`click <uid> [--inspect]` |\nClick by uid. Falls back to JS `.click()` when no box model. |\n`click --selector \"css\" [--inspect]` |\nClick by CSS selector. |\n`click --xy 100,200` |\nClick by coordinates. |\n`dblclick <uid> [--inspect]` |\nDouble-click by uid, `--selector` , or `--xy` . |\n`fill --uid <uid> <value> [--inspect]` |\nFill input by uid. |\n`fill --selector \"css\" <value>` |\nFill by selector. |\n`fill-form <uid=val>...` |\nBatch fill. |\n`select --uid <uid> <value>` |\nSelect dropdown option by value or visible text. |\n`select --selector \"css\" <value>` |\nSelect by CSS selector. |\n`check <uid>` |\nEnsure checkbox/radio is checked. Idempotent. |\n`uncheck <uid>` |\nEnsure checkbox/radio is unchecked. Idempotent. |\n`upload --uid <uid> <file>...` |\nUpload file(s) to a file input. |\n`upload --selector \"css\" <file>...` |\nUpload by CSS selector. |\n`drag <from-uid> <to-uid>` |\nDrag element to another element. |\n`type <text> [--selector \"css\"]` |\nType into focused element. |\n`press <key>` |\nEnter, Tab, Escape, etc. |\n`scroll <down|up|uid>` |\nScroll page or element into view. |\n`hover <uid>` |\nHover. |\n`wait <text|url|selector> <pattern>` |\nWait for a condition. |\n`wait network-idle [--idle-ms N] [--timeout N]` |\nWait until the network is quiet for `--idle-ms` (default 500). Beats fixed sleeps for SPA/XHR settle. |\n\n| Command | What it does |\n|---|---|\n`read [--html] [--truncate N]` |\nArticle extraction via Mozilla Readability. |\n`text [uid] [--selector \"css\"] [--truncate N]` |\nVisible text from page or element. |\n`eval <expression> [--selector \"css\"]` |\nJS in page context. `el` = matched element. |\n`extract [--selector \"css\"] [--limit N] [--scroll] [--a11y]` |\nAuto-detect repeating data. `--a11y` for React SPAs (X.com). |\n`download <url> [--out path] [--timeout N]` |\nDownload a URL fetched in-page, so cookies/auth carry over (login-gated files). Returns `{path,bytes,mime}` . |\n\n| Command | What it does |\n|---|---|\n`network [--filter \"pattern\"] [--body] [--live N] [--abort \"pattern\"]` |\nNetwork requests and API responses. `--abort` blocks matching requests. |\n`console [--level error] [--clear]` |\nconsole.log/warn/error + JS exceptions. |\n\n| Command | What it does |\n|---|---|\n`frame <selector|main>` |\nSwitch `eval` /`inspect` into an iframe (or back to main). Persists only within a `pipe` /`batch` process. |\n`batch` |\nExecute multiple commands from a JSON array on stdin. |\n`pipe` |\nPersistent JSON stdin/stdout connection. |\n\n```\n--browser <name>         Named browser profile (default: \"default\")\n--page <name>            Named tab (default: \"default\")\n--connect [url]          Attach to a running Chrome\n--headed                 Show browser window (default: headless)\n--stealth                Anti-detection patches (Cloudflare, Turnstile)\n--copy-cookies           Use cookies from your real Chrome profile\n--timeout <seconds>      Command timeout (default: 30)\n--max-depth <N>          Limit inspect depth\n--ignore-https-errors    Accept self-signed certs\n--json                   Structured JSON output\n--dialog <mode>          JS dialog policy: accept (default), dismiss, or manual\n--dialog-text <text>     Text to submit for prompt() dialogs when --dialog accept\n```\n\nJS dialogs (`alert`\n\n/`confirm`\n\n/`prompt`\n\n/`beforeunload`\n\n) are auto-answered by default (`--dialog accept`\n\n) — a native dialog otherwise blocks the page with no DOM signal and the agent's next command hangs. Use `--dialog dismiss`\n\nto cancel them, or `--dialog manual`\n\nto opt out.\n\n```\nchrome-agent goto https://app.com/login --inspect\n# uid=n52 textbox \"Email\" focusable\n# uid=n58 textbox \"Password\" focusable\n# uid=n63 button \"Sign In\" focusable\n\nchrome-agent fill --uid n52 \"user@test.com\"\nchrome-agent fill --uid n58 \"password123\"\nchrome-agent click n63 --inspect\n# uid=n101 heading \"Dashboard\" level=1\n```\n\nUIDs stay the same between inspects as long as the DOM node exists.\n\nFrom least to most tokens:\n\n```\n# Articles (Readability, like Firefox Reader Mode)\nchrome-agent read\n\n# Repeating data -- products, search results, feeds. No selectors.\nchrome-agent extract\n# Uses MDR/DEPTA heuristics. Finds the pattern automatically.\n\n# React SPAs (X.com, etc.) -- uses a11y tree instead of DOM\nchrome-agent extract --a11y --scroll --limit 20\n\n# Scoped visible text\nchrome-agent text --selector \"[role=main]\" --truncate 1000\n\n# API responses -- skip the DOM\nchrome-agent network --filter \"api\" --body\n# Select dropdown by value or visible text\nchrome-agent select --uid n15 \"California\"\n\n# Idempotent checkbox control\nchrome-agent check n20     # no-op if already checked\nchrome-agent uncheck n20   # no-op if already unchecked\n\n# File upload\nchrome-agent upload --uid n30 /path/to/document.pdf\n\n# Double-click (text selection, special controls)\nchrome-agent dblclick n42\n```\n\nThe `frame`\n\nswitch binds `eval`\n\nand `inspect`\n\nto the iframe — but **only within one process**, so drive it through `pipe`\n\n(or `batch`\n\n), never as separate CLI calls:\n\n```\nprintf '%s\\n' \\\n  '{\"cmd\":\"frame\",\"target\":\"#payment-iframe\"}' \\\n  '{\"cmd\":\"inspect\"}' \\\n  '{\"cmd\":\"fill\",\"uid\":\"n42\",\"value\":\"4242424242424242\"}' \\\n  '{\"cmd\":\"frame\",\"target\":\"main\"}' | chrome-agent pipe\n```\n\n- Target the intended iframe precisely (e.g.\n`iframe[src*=\"checkout\"]`\n\n); a bare`iframe`\n\nmatches the first one in DOM order, often an ad`about:blank`\n\nslot. `frame`\n\nscopes`eval`\n\n/`inspect`\n\n; it does**not** scope`--selector`\n\ntargeting. Run`inspect`\n\nafter the switch to get iframe uids, then act by uid (uids resolve across frames).- Each standalone\n`chrome-agent <cmd>`\n\nopens a fresh connection, so`chrome-agent frame …`\n\nfollowed by a separate`chrome-agent inspect`\n\nloses the switch. Use`pipe`\n\n/`batch`\n\n.\n\nExecute a sequence of commands from stdin without per-command process startup:\n\n```\necho '[\n  {\"cmd\":\"goto\",\"url\":\"https://example.com\"},\n  {\"cmd\":\"inspect\",\"filter\":\"button\"},\n  {\"cmd\":\"click\",\"uid\":\"n42\"}\n]' | chrome-agent batch\n```\n\nEach command produces one JSON line. About 10x faster than spawning a process per command.\n\n`--stealth`\n\npatches 7 automation fingerprints via CDP:\n\n`navigator.webdriver`\n\nset to`undefined`\n\n`chrome.runtime`\n\nmocked- Permissions API fixed\n- WebGL renderer masked\n- User-Agent cleaned\n- Input coordinate leak patched\n`Runtime.enable`\n\nnever called\n\nThese are CDP-level patches (`Page.addScriptToEvaluateOnNewDocument`\n\n), not Chrome flags.\n\nFor sites with heavier protection (DataDome, Kasada) that fingerprint the Chromium binary itself, connect to your real Chrome:\n\n```\ngoogle-chrome --remote-debugging-port=9222 &\nchrome-agent --connect http://127.0.0.1:9222 goto https://www.leboncoin.fr --inspect\n```\n\n| Protection | Solution |\n|---|---|\n| None | `chrome-agent goto ...` |\n| Cloudflare/Turnstile | `chrome-agent --stealth goto ...` |\n| Logged-in sites | `chrome-agent --stealth --copy-cookies goto ...` |\n| DataDome/Kasada | `chrome-agent --connect` to real Chrome |\n\n`--copy-cookies`\n\ncopies the cookie database from your Chrome profile. Both Chrome instances use the same macOS Keychain, so encrypted cookies just work.\n\n```\nchrome-agent --stealth --copy-cookies goto x.com/home --inspect\n# Your timeline. Your DMs. No login flow.\n\nchrome-agent --copy-cookies goto mail.google.com --inspect\nchrome-agent --copy-cookies goto github.com/notifications --inspect\n```\n\nYour real Chrome is not affected.\n\n```\n# Resources already loaded (stealth-safe, uses Performance API)\nchrome-agent network --filter \"api\"\n\n# Live traffic with response bodies\nchrome-agent network --live 5 --body --filter \"graphql\"\n\n# Block tracking/ads (uses Fetch domain interception)\nchrome-agent network --abort \"*tracking*\" --live 30\n\n# Console output\nchrome-agent console --level error    # errors + exceptions only\n```\n\nConsole capture uses an injected interceptor, not `Runtime.enable`\n\n.\n\nFiles are written under `~/.chrome-agent/tmp`\n\n(or your `--out`\n\npath) with `0600`\n\nperms; the path is printed on stdout. Binary bytes never hit stdout.\n\n```\n# Download a file, fetched inside the page so cookies/auth carry over.\n# Ideal for login-gated exports (invoices, CSVs, PDFs behind an auth wall).\nchrome-agent download https://app.com/reports/2024.csv --out ./2024.csv\n# {\"ok\":true,\"path\":\"./2024.csv\",\"bytes\":48213,\"mime\":\"text/csv\"}\n\n# Print the current page to PDF.\nchrome-agent pdf --filename invoice.pdf --background\n\n# Screenshots that don't blow up your context window.\nchrome-agent screenshot --format jpeg --quality 60 --max-width 1024\nchrome-agent screenshot --uid n42            # capture a single element (or --selector \"css\")\n```\n\n`download`\n\nuses an in-page `fetch`\n\nwith `credentials:'include'`\n\n, so the request inherits the page's session. Click-triggered browser-native downloads are not handled — resolve the target href (`inspect --urls`\n\n) and download it directly.\n\n```\n# Resolve once no requests are in flight for 500ms (tunable), bounded by --timeout.\n# Replaces fragile fixed sleeps on SPAs / XHR-heavy pages.\nchrome-agent wait network-idle\nchrome-agent wait network-idle --idle-ms 800 --timeout 20\n```\n\nOpt-in (enables the Network domain), so it stays off the stealth hot path.\n\nFor agents that send many commands in sequence, pipe mode keeps a single connection open:\n\n```\necho '{\"cmd\":\"goto\",\"url\":\"https://example.com\",\"inspect\":true}\n{\"cmd\":\"click\",\"uid\":\"n12\",\"inspect\":true}\n{\"cmd\":\"read\"}' | chrome-agent pipe\n```\n\nOne JSON line per response. About 10x faster than spawning a process per command.\n\n```\nchrome-agent --json goto https://example.com --inspect\n# {\"ok\":true,\"url\":\"...\",\"title\":\"...\",\"snapshot\":\"uid=n1 heading...\"}\n\nchrome-agent --json eval \"1+1\"\n# {\"ok\":true,\"result\":2}\n\n# Errors exit 1 but JSON is still on stdout (parseable):\nchrome-agent --json click n99\n# {\"ok\":false,\"error\":\"Element uid=n99 not found.\",\"hint\":\"Run 'chrome-agent inspect'\"}\n```\n\nWhen deciding which link to click, the agent often needs the URL, not just the text:\n\n```\nchrome-agent inspect --urls --filter link\n# uid=n82 link \"Pricing\" url=\"https://example.com/pricing\"\n# uid=n97 link \"Docs\" url=\"https://docs.example.com\"\n# Multiple tabs in one browser\nchrome-agent --page main goto https://app.com\nchrome-agent --page docs goto https://docs.app.com\nchrome-agent --page main eval \"document.title\"   # \"App\"\n\n# Multiple agents, each with their own Chrome\nchrome-agent --browser agent1 goto https://example.com\nchrome-agent --browser agent2 goto https://other.com\n# Install the skill (Claude Code, Cursor, Copilot, etc.)\nnpx skills add sderosiaux/chrome-agent\n\n# Or tell your agent to run:\nchrome-agent --help\n# The help output includes a full LLM usage guide.\n```\n\nClaude Code permissions:\n\n```\n{\n  \"permissions\": {\n    \"allow\": [\"Bash(chrome-agent *)\"]\n  }\n}\n```\n\n| chrome-agent | agent-browser (Vercel) | Playwright MCP | |\n|---|---|---|---|\n| Language | Rust | Rust | TypeScript |\n| Binary | 3 MB, zero runtime | 3 MB CLI + dashboard + cloud providers | Node + Playwright |\n| Startup | ~10ms (session reuse) | daemon (fast after first) | cold start |\n| Token efficiency | ~50 tokens/page (a11y noise filtering) | ~200 tokens/page (a11y tree) | ~2,000 tokens (HTML) |\n| UID stability | `backendNodeId` (stable across inspects) |\nsequential `@e1, @e2` (reassigned per snapshot) |\nN/A (selectors) |\n| Action + observe | `--inspect` flag (1 call) |\nseparate snapshot call | separate call |\n| Stealth | 7 native CDP patches | delegated to cloud providers | none |\n| Reader mode | `read` (Readability.js) |\nnone | none |\n| Data extraction | `extract` (auto-detect repeating data) |\nnone | none |\n| Link URL resolution | `inspect --urls` |\n`snapshot -u` |\nN/A |\n| Dropdowns | `select` |\n`select` |\nvia selectors |\n| Checkboxes | `check` /`uncheck` (idempotent) |\n`check` /`uncheck` |\nvia selectors |\n| File upload | `upload` |\n`upload` |\nvia selectors |\n| Drag and drop | `drag` |\n`drag` |\nvia selectors |\n| Annotated screenshots | not yet | `screenshot --annotate` |\nnot yet |\n| Element/token-safe screenshots | `screenshot --uid/--selector` , `--format jpeg` , `--max-width` |\nvia options | via options |\n| PDF export | `pdf` (`Page.printToPDF` ) |\nnone | none |\n| File download | `download` (in-page fetch, auth-preserving) |\n`download` |\nvia events |\n| Extra request headers | `goto --header` |\nyes | via context |\n| Network-idle wait | `wait network-idle` |\nyes | `browser_wait_for` |\n| JS dialog handling | auto (`--dialog accept/dismiss/manual` ) |\nyes | `browser_handle_dialog` |\n| Live dashboard | no (lean) | yes (Next.js) | no |\n| Cloud providers | no (`--connect` to anything) |\n5 built-in | no |\n| iOS/Safari | no | yes (WebDriver) | no |\n| Network blocking | `network --abort` |\n`network route --abort` |\nno |\n| Iframe switching | `frame` |\n`frame` |\nvia selectors |\n| Batch execution | `batch` (JSON stdin) |\n`batch` (JSON or quoted) |\nN/A |\n| AI chat built-in | no (the agent IS the LLM) | yes (AI Gateway) | N/A |\n| Codebase | ~8.8K lines | ~40K lines | Playwright |\n| Design goal | minimal tokens, maximal autonomy | feature-complete platform | browser testing |\n\nMIT", "url": "https://wpnews.pro/news/chrome-agent-llm-native-browser-automation-tool-written-in-rust", "canonical_source": "https://github.com/sderosiaux/chrome-agent", "published_at": "2026-07-20 18:25:23+00:00", "updated_at": "2026-07-20 18:53:48.817677+00:00", "lang": "en", "topics": ["artificial-intelligence", "developer-tools", "large-language-models"], "entities": ["chrome-agent", "Vercel", "agent-browser", "Google", "Chrome", "Playwright", "Rust"], "alternates": {"html": "https://wpnews.pro/news/chrome-agent-llm-native-browser-automation-tool-written-in-rust", "markdown": "https://wpnews.pro/news/chrome-agent-llm-native-browser-automation-tool-written-in-rust.md", "text": "https://wpnews.pro/news/chrome-agent-llm-native-browser-automation-tool-written-in-rust.txt", "jsonld": "https://wpnews.pro/news/chrome-agent-llm-native-browser-automation-tool-written-in-rust.jsonld"}}