Skip to content

About

Drive your real, logged-in Chrome from any AI agent: local CLI + MCP, native-messaging extension, session-owned tab groups and DOM/accessibility snapshots. Open source.

Topics

Resources

Stars

197 stars

Watchers

0 watching

Forks

 
 

Latest commit

 

History

1,710 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

chrome-use

English · 简体中文

Release GitHub stars CreepJS 0% bot Platforms License: Apache-2.0

⭐ If it saves you a re-login, a star helps other devs find it.

chrome-use

chrome-use demo: open Hacker News in your real Chrome and pull the top stories as structured JSON in one command
Point it at a page in your real Chrome → get structured data in one command. (regenerate: vhs assets/demo.tape)

Watch: Why chrome-use, a 5-minute video
Why chrome-use, in 5 minutes: English (MP4) · 中文 (MP4)

chrome-use drives your real, logged-in Chrome from any AI agent. It shares your existing login sessions and uses your existing browser profile; public detector results are documented, not a guarantee for every website. Part of the *-use family (iphone-use drives your real iPhone; bitwarden-use pulls passwords/2FA/passkeys from your Bitwarden vault so an agent can log in with credentials; chrome-use drives your real Chrome).

Originally based on vercel-labs/agent-browser (Apache-2.0); now a standalone project. The stealth/extension-relay architecture, anti-detection, humanize, multi-agent isolation, and CLI have diverged substantially.

📚 Documentation: chrome-use.leeguoo.com: full guides, workflows & command reference (中文 · English).

📖 Deep dive: Letting an agent click into cross-origin iframes: how chrome-use solves the hardest part of browser control · Driving your already-logged-in real Chrome (CreepJS scores it 0% bot), 中文

Give your AI agent the browser you already live in

No fresh Chrome. No re-login. No "are you a robot?" walls.

chrome-use points any agent (Claude Code, Cursor, Codex, your own scripts) at the Chrome you're already signed into everything on. It clicks in your window, so you watch it work and grab the wheel the moment it hits a 2FA prompt or captcha. And because it's literally your real browser (over a one-click extension, native messaging, no debug port), the documented CreepJS test reported: CreepJS scores it 0% bot.

A new browser context starts without your existing login sessions. Playwright and Puppeteer also support persistent profiles and existing-browser connections; compare the actual configuration. chrome-use connects to your existing Chrome. Your cookies, sessions, and browser fingerprint are all real, because it IS your real browser. Chrome 136 restricts remote debugging of the default profile; prompts and requirements depend on Chrome version and connection mode. Our extension uses native messaging instead: install once, then zero per-use confirmation.

Typical automation (Playwright · Puppeteer · browser-use) web-access / raw CDP port Claude in Chrome chrome-use
Works with any agent / CLI (not one app) ✅ ✅ ❌ Claude only ✅
Drives your real, logged-in Chrome Configurable; fresh contexts start empty ✅ ✅ ✅
Connect method / "Allow remote debugging?" popup — (own browser) --remote-debugging-port · version/mode dependent chrome.debugger · no native messaging · never ✅⁴
Real-browser fingerprint (CreepJS ~0%)¹ ❌ automation markers / headless ✅ ✅ ✅ verified 0%
No Runtime.enable CDP leak (rebrowser)² ❌ leaks ❌ leaks — ✅ off by default
Many agents on one real Chrome, isolated tab groups³ ❌ separate browsers ⚠️ shared tabs, no isolation ❌ single app ✅
Permissions footprint full control full CDP 16 incl. <all_urls> 12, no <all_urls>

¹ All three real-Chrome tools score ~0% on CreepJS (it's a real browser); we've measured ours. ² rebrowser's runtimeEnableLeak: verified clean on our relay path; Claude in Chrome not independently tested (—). ³ web-access can run parallel sub-agents on one browser, but without per-session isolation; each --session here gets its own colored, command-isolated tab group. ⁴ No consent dialog, but the extension still uses chrome.debugger, so Chrome shows its "started debugging this browser" bar while an agent tab is attached. See Setup for removing it. See Anti-detection for the measured numbers.

How it works

how it works

Your chrome-use CLI talks to a tiny browser extension over Chrome native messaging: a local inter-process channel, no network socket, no token, no remote server. The extension uses chrome.debugger to drive the tabs you target in your own, already-logged-in Chrome, then hands results back to the CLI. Everything stays on your machine.

architecture

Each --session gets its own colored Chrome tab group, so multiple agents can share one real browser concurrently without stepping on each other, or your own tabs. When --session is omitted, chrome-use derives a stable per-agent session from supported runner IDs, including Codex's CODEX_THREAD_ID. Explicit --session / AGENT_BROWSER_SESSION always win. Session naming, session list / stop / prune, ownership handoff, and daemon recovery are covered in the sessions guide.

Install

macOS / Linux

curl -fsSL https://raw.githubusercontent.com/leeguooooo/chrome-use/main/install.sh | sh

Windows (PowerShell)

irm https://raw.githubusercontent.com/leeguooooo/chrome-use/main/install.ps1 | iex

Downloads the prebuilt binary for your platform from the latest GitHub Release and installs chrome-use. No npm, no tokens.

Other ways to install
  • Pin a version: curl -fsSL https://raw.githubusercontent.com/leeguooooo/chrome-use/main/install.sh | AGENT_BROWSER_VERSION=v1.5.188 sh
  • Custom location: curl -fsSL … | AGENT_BROWSER_BIN_DIR=$HOME/bin sh
  • Windows, pin a version or location: $env:AGENT_BROWSER_VERSION = 'v1.5.139' or $env:AGENT_BROWSER_BIN_DIR = 'D:\tools' before the irm … | iex line. It installs to %LOCALAPPDATA%\Programs\chrome-use by default and adds that to your user PATH, keeping the existing entries exactly as they were (opt out with $env:AGENT_BROWSER_NO_PATH = 1). No admin rights needed.
  • Windows, by hand: download chrome-use-win32-x64.tar.gz and its .sha256 from the Releases page, check that (Get-FileHash chrome-use-win32-x64.tar.gz -Algorithm SHA256).Hash matches the .sha256 file, extract with tar -xzf chrome-use-win32-x64.tar.gz, and put chrome-use.exe on your PATH. Check the hash before running it: an interrupted download still extracts into an .exe, which then fails at launch with an access violation (exit code -1073741819, 0xC0000005) rather than anything that says the download was incomplete.

Install with Nix

Run it once, no install: nix run github:leeguooooo/chrome-use -- --help. The flake also ships a home-manager module and a NixOS module (programs.chrome-use.enable = true); on NixOS the native-messaging host is registered per-user, so run chrome-use extension connect once after switching (it registers the host the same way extension install does, then attaches to the extension relay). Dev shell: nix develop (rust toolchain + node 24 + pnpm + chromium + vhs). Full snippets: install guide.

Install the AI agent skill

With a current CLI, the installers install and verify its bundled discovery skill without Node, npx, Git or another download. To install or refresh it manually:

chrome-use skills install
chrome-use skills install --project

Global installation covers ~/.agents/skills, Claude Code, Codex and Cursor, plus existing Pi, OpenCode, Windsurf, CodeBuddy and Trae configurations. It respects CLAUDE_CONFIG_DIR and XDG_CONFIG_HOME. Project installation uses .agents/skills and .claude/skills, plus existing .pi, .windsurf, .codebuddy and .trae configurations. Restart your agent or reload its skills afterward. A failed write is an error, even if other destinations succeeded. See installation details.

Claude Code, plugin marketplace (recommended): installs the skill globally (all projects), auto-updates, and lists the rest of the *-use family:

/plugin marketplace add leeguooooo/plugins
/plugin install chrome-use@leeguooooo-plugins

Additional runners: skills.sh remains available for runners outside the built-in mappings. This alternative requires Node and its own dependencies:

npx skills add leeguooooo/chrome-use -g

The install one-liners already install the bundled skill (opt out with AGENT_BROWSER_NO_SKILL=1). PowerShell also extracts it from older pinned CLI releases, bypassing their npx installer. Installation errors stop completion; the final message does not claim that the Chrome extension is connected.

Codex users: Codex ships its own browser plugin and picks it for browser tasks. Measured on a machine with many skills installed, Codex also trims every skill description to a few characters (or none), so the skill's description cannot win the routing, and naming chrome-use in the prompt was not enough either. What worked was one line in the project's AGENTS.md:

Use the `chrome-use` CLI from the shell for every browser task; start with `chrome-use skills get core`. Do not use the built-in Chrome plugin for browser work here.

Either way the agent gets the right usage patterns and pre-approved bash permissions for chrome-use; the skill self-heals a missing binary by re-running the install one-liner above for its platform. Specialized guides (electron, slack, agentcore, …) are served by the binary itself via chrome-use skills get <name>, so instructions always match the installed version.

The installed discovery skill should direct the agent to chrome-use skills get core; it should not carry a second command manual. If an older installed copy contains its own workflow without that handoff, refresh it before comparing core-guide changes.

Upgrading the binary does not move a SKILL.md already copied into a runner; that copy lives outside the binary. chrome-use upgrade handles both: it installs the latest GitHub Release, then refreshes each installed copy of the skill it finds (Claude Code plugin, a git checkout, or the folders its installer writes; for an npx skills add copy it prints npx skills update chrome-use). chrome-use upgrade --check (or --json) changes nothing and reports current vs latest plus where the skill is installed; exit code 2 means the check failed. Other commands check for a newer release at most once a day, in the background, and while one exists print one line to stderr on each run; CHROME_USE_NO_UPDATE_CHECK=1 or the family-wide USE_NO_UPDATE_CHECK=1 turns that off. To refresh only the skill, run chrome-use skills update (refresh and install are the same command; add --project to install into ./ instead of globally).

Use from an MCP client (Claude Desktop, etc.)

For hosts that speak MCP but can't run arbitrary shell commands (Claude Desktop, ChatGPT connectors, n8n/Dify), run chrome-use as an MCP stdio server by wiring it into Claude Desktop's claude_desktop_config.json:

{
  "mcpServers": {
    "chrome-use": { "command": "chrome-use", "args": ["mcp"] }
  }
}

Local agent side panel

Click the extension toolbar icon to open the side panel; Settings opens connection options. On macOS/Linux, choose Codex, Claude Code, OpenCode or Pi and use the installed CLI's existing login and provider configuration. The agent menu follows the connected host's capabilities; older hosts need an upgrade to offer newer adapters. Windows currently supports agent discovery and configuration reads only.

Codex uses its app-server protocol, Claude Code uses stream-json, OpenCode uses ACP, and Pi uses JSONL RPC. Each conversation has its own browser session. The current webpage appears as a tag above the composer and is attached to the next message so the agent can target that tab. Click × to leave it out of that message.

The model menu comes from the selected CLI's catalog. Default model keeps the CLI's configured model; a Custom model ID in the + menu overrides the menu choice. Reload models from CLI refreshes the catalog. CLI version, account state and model listings do not prove execution access. Agents & models discovers programs and supports configuration previews, verified writes and restore that preserves later edits. Saving a default does not change a conversation already running.

The permission switch applies to one conversation. New conversations start with Full access enabled unless you have saved a different choice:

  • Codex / Claude Code: turn it off for Ask first and review approval requests. Full access runs without those permission prompts and automatically answers supported approvals; agent questions still need your response.
  • OpenCode: turning it off selects CLI permissions. OpenCode's own rules may allow tools without asking. Requests it does send appear in the panel; full access answers them with Allow once and does not change its stored permission rules.
  • Pi: there is no per-tool approval mechanism. Turning full access off selects Browser only, exposing only the chrome_use MCP tools, without shell or file tools. Turning it on enables the CLI's full tool set.

History streams replies, expandable tool rows and requests. Tool output does not prove the browser task succeeded. Messages sent during a reply enter a queue; edit or remove them before they start, using Load full text for truncated previews. Failure or interruption pauses the queue until Resume queue. Stop & send or Ctrl/Cmd+Enter waits for confirmed interruption or process exit before sending ahead of the ordinary queue, which remains paused. Reconnect reads existing history without resending. An unconfirmed send can be retried explicitly with its original message ID; clearing its warning does not cancel a task that may already have arrived.

To delete a conversation, stop its response, click the header's delete button and confirm. Deletion removes panel history and that conversation's browser session, plus Codex rollout files, Claude session files/directories, OpenCode history through session/delete, or Pi session files inside the conversation folder. Codex's SQLite cache is left untouched. The panel reports cleanup failures or unconfirmed tab closure.

Panel history is stored locally under ~/.chrome-use/agent-panel, scoped to the Chrome profile. The selected CLI may send tasks and page content to its configured model service, whose privacy policy applies. The extension adds no webpage content script; opening the side panel can change the page viewport. Custom programs require an absolute path.

Setup: connect to your Chrome

Install the chrome-use extension from the Chrome Web Store, then register the local bridge once:

chrome-use extension install      # register the native-messaging host (one-time)
chrome-use open https://x.com/home
chrome-use status                 # relay, profile, extension, and session health

chrome-use open then drives your real, logged-in Chrome over native messaging: no debug port, no token, and no "Allow remote debugging?" dialog, ever. The raw remote-debugging-port alternative (which pops a consent dialog) is described in the real Chrome guide.

extension install is the one-time setup. chrome-use extension connect (alias chrome-use reconnect) re-attaches to an already-installed extension's relay without reinstalling anything. Chrome still shows its "chrome-use started debugging this browser" bar on attached agent tabs; chrome-use extension connect --silent removes it by restarting Chrome once with --silent-debugger-extension-api (tabs and logins are restored; it refuses when several Chrome instances are running). --keep-banner leaves Chrome alone, and without either flag it asks first in an interactive terminal.

All AI sessions in a Chrome profile share one background agent window, with one tab group per session; your own window stays separate. Existing windows with verified agent ownership are reused, including fullscreen windows. Unknown ownership or failed placeholder cleanup refuses instead of creating more windows. User tabs are never moved or deleted to reclaim a window. If you open or drag your own tabs into the agent window, that window becomes yours: agent tabs already there keep working, and new agent tabs go to one fresh agent window that later sessions share.

Your Chrome profile must already have a window open before automation: with no window it stops with profile not open (code profile_not_open), so open a window in that profile first and rerun. If it cannot tell whether a window is open, it stops with profile window unavailable (code profile_window_unavailable); unknown means nothing is created. See Troubleshooting for the first-tab cleanup codes.

Multi-profile Chrome: ChooseBrowser rules

If you keep several Chrome profiles (work, personal, a client's), you already know which account each site belongs to, and you tell us with --browser every time. ChooseBrowser is a macOS link router that stores that mapping. When it is installed, chrome-use open <url> without an explicit --browser follows the rule you already wrote for that site, and says so:

$ chrome-use open https://github.com/my-org/repo
· using Chrome profile Profile 14 — a ChooseBrowser rule routes this site there
  (github.com|/my-org*). Override with --browser <id|email>, or skip with
  --no-choosebrowser.

Read-only, and invisible if you do not use it: no rules file means no behaviour change and no message. A rule is binding: chrome-use never opens the site in a different profile. If the rule's profile is not connected, the command fails and names chrome-use connect --browser <profile>. If the session is already bound to another profile, it fails and suggests a new --session or --no-choosebrowser. --browser and config routes still win. The check covers batch steps, MCP tool calls and script steps as well as direct commands. A rule whose profile no longer exists only warns. chrome-use doctor lists each rule and whether its profile is connected. Add --remember to an explicit --browser and chrome-use asks ChooseBrowser to write the rule back, behind its own confirmation dialog.

ChooseBrowser: every Chrome profile gets a row

ChooseBrowser is a macOS link router by the chrome-use author. Set it as your default browser once, and every link you click asks which browser, or which Chrome profile, it should open in.

  • A row for every profile in Chrome, Edge, Brave, Vivaldi or Chromium; work, personal and client accounts stay apart.
  • Rules that match a path, not just a domain, so one site can route to two browsers.
  • ⌘1–⌘9 opens instantly, ⌥↵ teaches it once, and it learns which browser you prefer per site.
  • No accounts, no tracking, no analytics. Optional sync through your own iCloud.

Free for 7 days, then US$4.99 one-time for up to 3 Macs. Notarized .dmg; macOS 26 or later. Download · Full guide. chrome-use does not depend on it.


Usage

A wait-condition timeout alone does not diagnose a browser connection failure. wait --text matches a case-sensitive substring: use the actual page wording, and do not wait again after the requested receipt is already visible.

chrome-use skills get core includes the everyday action loop and ordinary form commands. Load core/reading, core/connection, or core/site-adapters only when that task needs the detail; an ordinary click does not require another reference. Use the cheapest state check that answers the next question, and stop once an authoritative page signal confirms the goal.

The core loop: open, read, act, re-read only what changed.

chrome-use open https://example.com    # connect to your Chrome and navigate
chrome-use snapshot -i                 # the start of every interaction: interactive elements with @refs
chrome-use click @e3 --observe         # act, and watch for the page's reaction
chrome-use snapshot -i --diff          # only what changed since the last snapshot

For scoped, depth-limited, regex-filtered or budgeted snapshots, JSON refs contains only references rendered in the returned excerpt. The session keeps its complete live ref map; narrowing output does not invalidate a previously captured ref. Sparse-page detection still uses the complete map.

Interactive snapshots add context only to repeated controls with the same role and name when named parent lines do not already distinguish them. Context omits the control’s own name and punctuation, stays within about 100 characters (60 for unnamed controls), and must differ between controls. Unique controls have no context; use a scoped or full snapshot for missing details such as a unique product’s price.

Prefer snapshot -i -c for compact controls, scoped reads for a known region, and --diff only when the last observation leaves a question. Use batch for known action sequences (a step takes its own --observe: batch "fill @e1 Ada" "click @e2 --observe"; pick and hover observe like click), script for bounded observe/decide/act/verify flows, and form fill --map for several fields. Verify the requested result at the end: observed.status: complete means the capture is complete, not the task, so wait for the page's own final signal (wait --text). On a small page (tree at most 4 KB and 60 lines) an observation carries the whole current tree as observed.snapshot plus observed.changes (+/- lines only); larger pages keep delta. observed.text lists visible text lines that changed in any frame (receipts, logs, page counters), never input, textarea, select or editable content, and nothing at all while any password field holds a value (textStatus: redacted); a frame it could not read is listed in textFrames and makes the observation partial. observed.target names the tab and target on every JSON reply. An element screenshot is screenshot <selector> <path> or screenshot <path> --selector <selector>. Add --with-screenshot <path> only when pixels answer a question the tree cannot.

If only the post-action capture fails, observed.refs says what became of the refs: kept-unverified keeps the previous snapshot's refs, but each is confirmed live (same tab, frame, document and element) before a later command uses it and refused otherwise, never re-anchored by role or name (DOM-walk refs are always refused then); dropped means the document may have changed. Either way, run snapshot -i and do not repeat the action.

Semantic find role/text/label/placeholder/alt/title/testid requires exactly one visible match, including locate-only queries. Ambiguity returns up to eight candidates with visible state and selector/context hints; no action is dispatched and input values are omitted. Narrow --name/--exact, or use --within <CSS|@ref> to restrict the query to exactly one container. A scope must belong to the active tab's main document; cross-frame refs and an explicitly selected iframe are refused; use direct frame refs or frame main. find first/last/nth explicitly selects an order and keeps its existing behavior. Plain CSS actions are unchanged. Roles and label names use Chrome accessibility data, including aria-labelledby; a semantic query re-resolves on each call. A detached target before dispatch fails safely; uncertain actions are never replayed.

Semantic clicks report the covering element with a bounded tag, id/class and text description. If the hit-test cannot confirm the target, the click fails without guessing a blocker or dispatching input.

The existing chrome_use_find tool in mcp --tools all accepts within with the same unique-scope rules; tool count is unchanged. text for fill/type is passed literally, including --name --observe, rather than parsed as CLI options.

Semantic find retains the existing --observe unsupported warning; use one batch containing the scoped find action and a task-specific get text receipt when both are known. The MCP find tool does not advertise an observe field.

Discover a scope from the page first: find query "Beta account" returns candidate selector anchors; choose the article/container corresponding to that heading, then use find role button click --name Save --exact --within "<returned-selector>". Inspect an ambiguous scope with snapshot -s "<selector>" and narrow it rather than accepting the first container. Unknown semantic-find options and duplicate --within are refused; use find label Email fill -- --name to enter literal flag-looking text. --exact false requests substring matching.

After three identical observed click, dblclick, or press attempts on the same target and screen, with no gap over 60 seconds, observed.noProgress advises checking state or waiting for a task-specific condition. It requires complete, settled evidence with no detected tree, request, resource, or frame activity. The hint does not change action success, prove a write failed, or retry it.

Ordinary CLI/MCP connection preparation preserves this streak only on successful reuse of the same browser connection, target and session, without loading storage state. A new browser, rebind, failed preparation or storage-state load still clears the count.

Scripts retain these hints in advisories (top-level in CLI script --json; data.advisories in the daemon envelope) (at most 20), including nested scripts and runs that later fail. JSON op-list scripts also keep noProgress on the relevant steps entry; text output prints the aggregate advisories once. A failed JS script retains ok:false, return:null, error, logs, and advisories. Nested script daemon-envelope data.ok:false fails the parent script even when transport success is true; dispatch success does not establish program success.

Ordinary CLI --json replies use a success/data/timing envelope; --no-timing (or AGENT_BROWSER_TIMING=0) leaves timing out for a compact reply, and timing.jsonl is still written. batch --json prints an array of {command,success,result,error} entries; script --json prints the bare program result (ok, return, logs, error, advisories, and other program fields). Batch and script CLI output have no top-level timing.

JSON command timing includes cdpMs (sum of completed foreground CDP request durations), cdpBusyMs (their interval union within command wall time), and nonCdpMs (wall time minus that union). Concurrent requests can make cdpMs exceed wall time; these are elapsed durations, not CPU measurements. Background tasks do not inherit the recorder; nonCdpMs is not pure daemon processing time. Tool/HTTP calls are not model round trips; only caller traces establish those. See task measurement for task measurement.

The agent operates in your Chrome: you'll see tabs opening, pages loading, clicks happening in real time. You can take over at any point (e.g. solve a CAPTCHA), then let the agent continue.

For an authorized task, the bundled skill tells the agent to inspect and attempt ordinary CAPTCHAs before handing off: solve-slider for ordinary and rotating Yidun puzzles, screenshot-guided ordered clicks for readable icon challenges, then verify the site's result and continue. Load chrome-use skills get core/captcha. Attempts are bounded; unavailable or ambiguous challenges still need a handoff. This workflow does not guarantee every provider or challenge can be solved. Activate the target before capturing coordinates; a provider success or frozen resend countdown does not establish site acceptance.

Command Purpose
--window dedicated (default) / --window user Extension relay: all AI sessions share one background window per profile, with separate tab groups; user opts into the user window
chrome-use open <url> Connect to your Chrome and navigate. If load has not arrived after 25s but the tab's page is clearly usable, it succeeds with a ⚠ warning; when that page can't be tied to this request (always over the extension relay) the reply carries commit: "unverified" and the tab's real URL. A page that isn't usable fails with navigation_incomplete:, saying what is known (commit, readyState, candidate resources)
chrome-use snapshot -i Read the page; the start of every interaction (-u adds each link's absolute URL)
chrome-use read --links The page as text, plus a bounded ## Links list of [text](absolute URL) (first 100, --max-links <n> up to 1000)
chrome-use click "Post" · click @e3 · click 449 320 Click by text, by snapshot ref, or on a raw viewport coordinate
chrome-use fill "Title" "Hello World" · type @e3 "text" fill replaces a whole value and type appends, both with trusted input events; a ⚠ warning says when the page did not react (e.g. its Save stayed disabled)
chrome-use extract --schema '{"rows":".item","fields":{"title":"a","url":{"sel":"a","get":"@href"}}}' Structured rows in one call; "@href" alone reads the row element's own attribute. A spec it does not understand is an error, and a field empty in every row is named in a ⚠ warning, never a silent null
chrome-use hover @e3 Move the real pointer onto the element (trusted input); ✓ only when a trusted pointer event landed on the element, and a covered target is refused instead of hovered
chrome-use network request <id> Read the recorded response body from its originating renderer, including cross-origin frames; unavailable bodies carry responseBodyError
chrome-use screenshot ./page.png Save visual evidence; use it for image challenges and canvas targets, and refs for ordinary controls
chrome-use solve-slider 1 · skills get core/captcha Attempt a Yidun puzzle (nonzero exit if unsolved); load ordered clicks and verification
chrome-use find "edit web service settings button" Ranked, non-acting candidates from a natural-language description
chrome-use actions @e15 · do @e15 expand What this element supports right now, and perform one of exactly those
chrome-use click @e2 --follow A tab the click opened (target=_blank, window.open) is reported as openedTab and joins the session; --follow moves to it. In your own Chrome only tabs opened by the session's tabs are taken, attached by tab id (ab-connect 0.5.30+); otherwise openedTabWarning / openedTabStatus (unadopted, unknown) say why. A tab the page opens brings Chrome to the front over your app, and openedTabWarning says so; on macOS chrome-use puts your app back in front (focusRestored; AGENT_BROWSER_RESTORE_FOCUS=0 turns it off). Opt-in AGENT_BROWSER_BACKGROUND_LINKS=cross-site (read when the session's daemon starts) opens cross-site target=_blank links in a background tab instead (openedTabMode: background); cost: a link that redirects back to the page's site loses its SameSite=Strict cookies
chrome-use tab list · tab select t2 · tab adopt <url-substring|targetId> List tabs; select a created or adopted tab; attach an already-open tab through the extension or direct CDP without navigating it
chrome-use tab new [url] --activate · tab select t2 --activate · tab adopt <targetId> --activate Raise the target before initialization or the liveness probe; --front is an alias
chrome-use tab list --all [--limit <n>] Every tab in the connected Chrome profile, across windows, the user's own and other sessions' included: handle, window, active, title, url, owner (this session / session <name> / the user). Read-only even on the first call: no daemon, window, tab or page attachment is created. Urls are as Chrome reports them and may carry tokens
chrome-use --tab <handle> --force <command> · tab close <handle> --force Act on (or close) a tab the session does not own, such as the user's own, in place and in the background; only when the user asked. Results carry forced: true with the tab's title and url; close never closes a forced tab
chrome-use dialog status · dialog accept|dismiss Handle a native confirm() / prompt() opened by a click
chrome-use download @e2 ./video.mp4 Download with the same cookies as the logged-in browser, without navigating the current tab
chrome-use network route "*/api/me" --body '{"vip":true}' Mock a response, rewrite an outgoing request, or block one
chrome-use site github/issues epiral/bb-browser --json Run a site adapter and get clean JSON from the site's own API
chrome-use site info github/issues · site info github Show one adapter's args and domain, or every adapter in a pack with its args and a usage line
chrome-use tab close t2 · close Close a tab, or end the session and close every tab it created. Both read the tabs back before reporting them closed (tabsClosed, verifiedAbsent: true); a tab still open or not confirmed gone fails with close incomplete and keeps the session and its tab ownership for a retry. tab close on the session's last tab ends the session (sessionClosed: true). Tabs the session did not create are never closed. Confirming over the relay needs ab-connect 0.5.33+
chrome-use session list · session stop [name] Manage session workers
chrome-use auth login --bwu [--item <id|name>] Fill the current login page from Bitwarden; handles TOTP and supported passkey second factors
chrome-use auth login --bwu --passkey Sign in with a vault passkey in --launch mode (bwu 0.9.0+)
chrome-use status Relay transport, recent debugger timeouts, duplicate installs, extension version skew, and session health. With ab-connect 0.5.50+, host disconnect schedules a first retry after 1s, with repeated failures backing off to 30s. Dead-socket command/event waits fail; old replies are not replayed. Failed sends/lost replies report action_outcome_unknown with retryable: false. Check the page before repeating an interrupted write
chrome-use close --forget-untracked Explicit recovery for stale ownership records with no tab identity. Requires two valid registry absence reads; reports ownershipDropped and does not claim those Chrome tabs were closed. Cannot be combined with a tab or --all

Upgrading installed files does not replace an already-running native host. With Chrome open during an upgrade, reload the chrome-use extension at chrome://extensions when agent sessions are idle. Reconnecting a session daemon does not replace the host. Status reports health.nativeHostVersion from the live handshake (null when unavailable), separately from the installed CLI and hostLauncherHealthy; unavailable or mismatched runtime versions produce warnings.

After a refused first open, tab list --all can still inspect the session's bound profile or configured CDP endpoint passively. It creates no driving connection and does not bypass mixed-window guards.

status, extension status, and doctor's relay check read ABExt.state over one diagnostic connection with a total 10-second budget. An error reply falls back to ABExt.inspectTab on that same connection. They send no debugger commands. The debugger summary passively counts answered commands (including non-timeout errors) and timeouts with “in the last 10 min” for a full reporting window, otherwise “since the extension worker started ago” (whole seconds below 120 seconds, such as “45s”; whole minutes otherwise, such as “3 min”). No samples means “not exercised”; an older extension without health data reports that explicitly. Counts use bounded one-second buckets and reset when the service worker restarts. If commands keep failing, reload the chrome-use extension at chrome://extensions or restart Chrome.

Extension 0.5.48 adds a guarded native-tab-group operation used by Thermo. It accepts only an explicitly reviewed list, rechecks every tab, preserves existing/current/pinned/audible/private/agent tabs, keeps windows and profiles separate, journals before mutation, verifies the result, and supports one-shot undo without replaying an uncertain action. Ordinary chrome-use sessions still cannot mutate tabs they do not own.

New native hosts acknowledge diagnostic connections and skip attachAll. A still-running older host performs its usual attachAll once; the diagnostic notice recommends reloading the extension after upgrading to start the new host. Possible duplicate extensions are identified from sidecar identities and concurrent TCP liveness checks with a one-second limit and no WebSocket handshake. Matching account/browser with distinct extension ids does not prove a shared Chrome profile: disable one only if both are installed in the same profile.

Concurrent requests attaching the same tab share one complete attachment. Replies from an old native-host connection are never replayed onto its replacement; reconnect re-announces the current tab state. Automatic detach recovery skips tabs discarded by Chrome. Optional idle debugger release (ab_idle_detach_secs, disabled by default) preserves active tabs in every window and rechecks command activity before releasing a background tab.

JSON health contains transportResponsive, hostDiagnostic, nativeHostVersion, debugger, and extensionHealth (windowMs, workerAgeMs, answered, timedOut, lastTimeout, or null). connectedProfiles and warnings remain available. extension status --json retains relayUp as endpoint presence; status --json uses an extension transport reply. Update an older extension when a newer version is published; if the live extension is newer than this CLI expects, run chrome-use upgrade. Only installType: development identifies a development install. On macOS/Linux, doctor shows one-minute system load and CPU count; load above twice the CPU count warns and adds a hint to relay/CDP timeout errors.

A field that shows your text is not proof the page saved it. fill warns when the form's Save/Submit was disabled before the edit and still is, click refuses a disabled control and reports dispatch: dom when it had to fall back to an untrusted element.click(), and snapshot marks a button that wraps a checkbox as [toggles=checkbox(checked=true)], because clicking it flips a setting (in LinkedIn's profile-language dialog, it deletes that language's profile).

Tab creation, selection, and adoption stay in the background by default, and no default command raises Chrome or changes the tab you are looking at. --activate (alias --front) and bringToFront are the explicit exceptions: they change the visible tab, focus that Chrome window and leave the tab in the foreground, so agents are told to use them only when you ask. If new-tab initialization fails, chrome-use retains the target and reports its ID. Use chrome-use tab select <targetId> --activate, then chrome-use snapshot -i to verify recovery, keeping the same session and connection endpoint. Do not repeat tab new or automatically replay an action whose outcome is unknown.

Tab ids and labels survive a reconnect (a relay restart, for example): each stays bound to the same Chrome tab, and one whose tab is gone is refused rather than given to another tab. If the tab the agent was driving is gone, commands on the current tab are refused until it picks one with tab select <ref>, tab new or --tab <ref>, so nothing runs in a tab it never chose.

Bitwarden login

Open the site's login page, then run chrome-use auth login --bwu. When several accounts match and none is named exactly the site's host, select one with --item <id|name>. Add --passkey to use only a vault passkey in a --launch browser (bitwarden-use 0.9.0+); passwords, TOTP and custom fields are not read in that mode. On the extension relay, passkeys are unsupported: passkey-only login fails immediately, while ordinary login keeps the password/TOTP flow. Only synced passkeys with signature counter 0 are supported; nonzero counters need vault write-back and are refused. The temporary WebAuthn authenticator is removed after the attempt. A temporary page guard blocks ordinary passkey registration calls; retained native references can bypass it. Unexpected credential creation or unconfirmed cleanup aborts the command. If WebAuthn is unavailable, ordinary login keeps the password flow; passkey-only login fails.

A login is reported only when the site took it: after submitting, auth login --bwu waits until the sign-in form is gone from the page and returns signedIn: true with the evidence, or fails with the sign-in was not confirmed: … and what the page shows instead. It checks that its Enter reached the page; one that did not is replaced by a click on the form's sign-in button.

Login option Effect
--bwu Use the vault account for the current page (bwu 0.7.0+)
--item <id|name> Select one matching account
--passkey Sign in with a vault passkey in --launch mode
--no-submit Fill only; skips TOTP and passkey authenticators; incompatible with --passkey
chrome-use open https://github.com/login
chrome-use auth login --bwu --item github.com
chrome-use --session passkey-demo --launch open https://github.com/login
chrome-use --session passkey-demo --launch auth login --bwu --item github.com --passkey
chrome-use --session passkey-demo --launch snapshot -i

Check the authenticated destination after login. A passkey assertion means Chrome signed the request; it does not establish that the site accepted it.

A command that lands on a sign-in page, or a site adapter that finds the site signed out (loginRequired: true, or a failed run after HTTP 401), prints login wall: … and returns loginWall in --json. The first time for a site it asks whether to sign in from the vault: this time, always for that site, or never (a prompt in a terminal; loginWall.ask with one command per answer for an agent, which relays the question to you). chrome-use auth autologin status shows the decisions and auth autologin off <host> forgets one. See Login & Credentials.

Agent loop (experimental)

jev run drives a goal end to end: TypeSafe's Jev picks each step's operation and target from an indexed element table, and a small model writes text only when a field needs typing. It needs TYPESAFE_API_KEY (or ~/.config/typesafe/key).

chrome-use jev run --goal "find a flight from Zurich to London" --url https://www.google.com/travel/flights

A run reports where its time went — jev_ms (model), act_ms, observe_ms, fresh_ms — because the answer is usually "the model", not the browser: one measured run was 64% model round trips, 29% real page load, 6% our own commands.

--terminal-shadow adds one question to the same request, asking whether the chosen action ends the goal, and records that claim against what the closing decision then decided (terminal_predicted, terminal_condition_observed, terminal_confirmed_done). It does not change the completion control flow: the closing decision is still made and still decides. It exists to measure whether skipping that decision could ever be safe — on its own, a cheap local check is not a completion test, since a checkout bounced to /login changes the page exactly as a success would.

JEV_TRACE=<file> appends one JSON line per decision: the request that was sent, the candidates the model was shown (id, kind, label, current value, checked state), its choice, and Jev's raw answers with probabilities. It is off unless set. It records the goal, the page text and field values, which includes anything already typed into the form, so treat the file as sensitive. The run report also splits act_ms into act_read_ms, cmd_click_ms, cmd_press_ms and cmd_insert_ms.

Anti-detection

stealth shield

When connected to your real Chrome, we inject zero JavaScript patches. Your browser's fingerprint is completely genuine. The guiding rule is native CDP/Chrome overrides over JS lies: a re-defined getter is itself detectable; a native override isn't.

  • navigator.webdriver = false via Emulation.setAutomationOverride (native, undetectable by CreepJS-style lie tests).
  • Runtime.enable is left OFF by default. A live Runtime domain is a detectable CDP signal (the patchright/rebrowser "runtime leak"), even when attached to your real Chrome. We only enable it when you opt into console/error capture. click, fill, eval, etc. work without it.

Test results (connected to real Chrome):

Test site Result
CreepJS 0% stealth · 0% headless (no override traces at all)
bot.incolumitas.com all checks OK: overflowTest, overrideTest, puppeteerExtraStealthUsed, worker consistency
bot.sannysoft.com all green
BrowserScan Webdriver · User-Agent · CDP all clean
Cloudflare managed challenge passed, no interaction

0% stealth on CreepJS is the key number: because the connect path patches nothing, there is no override for a lie-detector to catch. (Dashboards that read navigator.languages order or IP geolocation may show a soft "navigator"/"location" flag. That tracks your real Chrome's language list and network, not an automation tell.)

When using --launch mode (standalone browser), a full suite of stealth patches is applied instead, and it passes the suite above, with one caveat: CreepJS reports ~20% stealth because the srcdoc-iframe contentWindow patch trips its hasIframeProxy probe (the proxy that hides automation is itself a tell). Everything else is clean (0% headless, sannysoft/browserscan green, Cloudflare passed). Set AGENT_BROWSER_DISABLE_IFRAME_PROXY=1 to drop that patch for a clean 0% stealth (trades the niche srcdoc-iframe masking). The extension-connect path (your real Chrome) injects zero JS and is unaffected; it's the genuine 0% path.

Verify it yourself

Don't take our word for it. Point your connected Chrome at the toughest public detectors and compare:

We deliberately don't ship our own bot detector. The strongest, most honest benchmark is the market's best detectors run against your real browser.

More in the docs

The *-use family

Small, composable CLIs that give an AI agent hands on one real thing. Same shape everywhere: curl … install.sh | sh to install, npx skills add leeguooooo/<name> to teach your agent, JSON on stdout.

Repo Gives your agent
mail-use Email: read, search, send, triage across Gmail / QQ / 163 / any IMAP
iphone-use A real iPhone: tap, type, screenshot, pull on-device data
wechat-use WeChat on macOS: send messages, query contacts and history
discord-use Discord: messages, channels, forums, webhooks (REST-only, Rust)
cookie-use Many logged-in accounts per site: capture, switch, apply sessions
profile-use Your personal profile, safely: fill signup / KYC / checkout forms
bitwarden-use Bitwarden / Vaultwarden: headless passkey (FIDO2) login
chatgpt-use Your ChatGPT subscription as a coding-agent backend, no API key
computer-use The macOS desktop itself
pixcake-use Read-only PixCake probing: snapshot / diff / SQLite inspection

Remote developer checks

Configure an SSH build host once with git config --local chromeuse.remoteHost <ssh-alias>. pnpm build:190 and pnpm test:190 run Cargo remotely; pnpm build:native also builds remotely and retrieves a checksum-verified native binary. These commands do not fall back to compiling on your workstation.

The runner snapshots Git-listed working-tree files, including uncommitted edits. Use git add -N <path> for a new source file before running it. Receipts under cli/target/remote-build-receipts/ record the input hash, remote toolchain, command, exit status and artifact checksum. See remote build instructions.

Contributing

AGENTS.md carries the conventions for this codebase: where docs live, how to build and test, and two hard-won rules about never shipping a silent success and about measuring performance honestly. Open work is tracked in issues.

Thanks to everyone who has contributed to chrome-use!

Contributors

License

Apache-2.0


Built by leeguooooo. Field notes on AI agents, reverse engineering & Cloudflare Workers at blog.leeguoo.com · follow on X @leeguooooo

About

Drive your real, logged-in Chrome from any AI agent: local CLI + MCP, native-messaging extension, session-owned tab groups and DOM/accessibility snapshots. Open source.

Topics

Resources

Stars

197 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages