Core Usage
Site Adapters
For task-specific CLI guidance, load chrome-use skills get core/site-adapters. Ordinary clicks and form actions are already covered by core.
To read "structured data from site X," the cheapest path is to skip snapshot + click.
Before you open, page through, and click your way across GitHub / Reddit / Bilibili, check whether a ready-made
site adapter exists — a small community-written JS snippet that hits the site's own JSON API directly from your
logged-in tab and hands you clean structured data in one shot. No clicking, no scraping, no screenshots.
What it is
A site adapter is a community-written JS function: it calls the site's own JSON API directly
from your currently logged-in tab and returns clean, structured data. Essentially it's a packaged,
per-site eval — except you don't have to write it yourself, and you don't
have to reverse-engineer each site's endpoints.
Because it runs inside the tab you're already logged into and makes requests as you, feeds that
require a session — like bilibili/feed or twitter/… — read fine too. That's exactly what
hand-crafting URLs or anonymous scraping can't do.
eval to hit a site's internal API, a site adapter is
that same code distilled into a command — maintained by the community and organized per site. You skip the grunt
work once, and everyone else gets to reuse it.
How to use it
Four actions: pull the sources once (update), see what's installed (list), see how a given adapter is
called (info), and run it (site <name>/<cmd>).
$ chrome-use site update # one-time: pull community, official, and extra sources
$ chrome-use site list # what's installed (github/issues, reddit/search…)
$ chrome-use site info github/issues # an adapter's params + which domain it runs on
$ chrome-use site info github # every adapter in the pack, with params + usage
$ chrome-use site github/issues owner/repo --json # run → JSON (navigates there for you)
site update: pulls adapters from the bb-sites community source, the chrome-use-sites official source, and any configured extras. Only needed once; after that chrome-use keeps them in sync automatically (see "Auto-triggering" below).site list: lists every<name>/<cmd>installed locally, e.g.github/issues,reddit/search.site info <name>/<cmd>: shows which params an adapter declares and which domain it runs on — check this before calling, and you'll know what to pass.site info <name>: the same for every adapter in one pack, each with ausageline showing its required args.site <name>/<cmd> [args] --json: actually runs it. Add--jsonfor one line of parseable structured output.
With --json, that last command returns the site's own data, already cleaned into a parseable structure
(excerpted and indented below):
{
"success": true,
"data": {
"domain": "github.com",
"origin": "https://github.com/",
"result": {
"count": 30,
"repo": "epiral/bb-sites",
"state": "open",
"issues": [
{
"number": 85,
"title": "feat(youtube/transcript): migrate to API-based subtitles",
"author": "kerrmoulton",
"state": "open",
"is_pr": true,
"comments": 0,
"labels": [],
"created_at": "2026-06-16T07:54:47Z",
"url": "https://github.com/epiral/bb-sites/pull/85"
}
// … 29 more with the same shape; count is the total returned
]
}
},
"error": null
}
site info <name>/<cmd> first to see its declared
params and target domain, then pass them accordingly. Cheaper than blindly running once and reading the error.
Params and navigation
Adapter params are filled positionally in declaration order; to override one by name, use --key value.
The two forms mix freely: positional args fill the slots, and --key pins a specific override.
# positional args fill in declaration order
$ chrome-use site github/issues owner/repo --json
# --key overrides by name (order doesn't matter)
$ chrome-use site reddit/search --query "chrome-use" --sort new --json
When it runs, it navigates to the domain the adapter declares — and if your current tab is already on that
domain, it reuses the current tab instead of opening a new one. The request still happens inside the tab you're
logged into, so signed-in feeds like bilibili/feed or twitter/… return normally (why they do is covered under
"What it is" above).
open the target site manually first. site <name>/<cmd> navigates to the adapter's
domain and then executes; but you do need to be already logged in to that site for logged-in feeds to be readable.
Long text, local files, long runs
Adapters that write (publish an article, upload a video) also run as one command: the body is read from a file, a local file is handed to the adapter, and waiting for an upload or a publish is no longer bound by the ~8s budget of a single command.
# Long text from a file or stdin, instead of "$(cat post.md)"
$ chrome-use site csdn/article-publish --title "Hello" --markdown @post.md
$ cat post.md | chrome-use site csdn/article-publish --title "Hello" --markdown @-
# An arg the adapter declares as "type": "file" takes a local path (shown by site info)
$ chrome-use site douyin-creator/video-publish --video ./clip.mp4 --title "Hello"
# Keep going until the upload / publish is really done
$ chrome-use site bilibili-creator/video-publish --title "Hello" --tags a,b --until-done --timeout 10m
# Writing an adapter: declare the file arg in @meta.args; optional "timeout": 600 (seconds) in @meta
# "video": {"required": true, "type": "file", "input": "input[type=file]"}
# In the function: put the file on the page's file input, report progress, read the time this run has
# await args.video.setOn(); args.progress('uploading'); args.budgetMs
--key @pathreads the value from a file,--key @-from stdin, and--key-file pathis the same thing spelled out.@namethat is not a file stays literal (--user @jack);@x.mdor@dir/xthat looks like a path but does not exist is an error, so a typo is never posted as text. For a literal value starting with@, write--key \@text.- A
"type": "file"arg takes a local path. The adapter puts it on the page's file input itself withargs.video.setOn(), so there is no separateuploadstep and no selector to know. The page can only name the selector, never the path. - An adapter runs in the background of the page and chrome-use polls it, so the ~8s budget of one relay command does not apply.
--timeout <300|90s|10m>sets the total time; the default is the adapter's@meta.timeout, else 120s. --until-done: when the adapter returnsstatus: "incomplete"/"uploading", or a page navigation ended the run, it is rerun until it finishes or the timeout passes (default 600s). Without the flag that status is returned as is, and you rerun the same command.- Progress the adapter reports with
args.progress(...)is printed to stderr, and returned asprogressin--jsonalong withattemptsandelapsedMs. - Write adapters (publish, delete, comment, follow) can have chrome-use do their input with
args.ui(1.5.253+):args.ui.click(el),hover,type(el, text, {clear}),press('Enter'),scroll(el | pixels). It sends real CDP input (isTrusted: true) with a curved pointer path and varied key timing, wheels the element into view first and lands inside it, and fails with a reason when the element is gone, has no size or is covered. An adapter's ownel.click()/dispatchEventisisTrusted: false, which a site can see. - With
"world": "isolated"in@meta(1.5.259+) the adapter runs in the tab's isolated world: same DOM, cookies and storage, its own JS, so nothing the page wrapped (querySelector,fetch, ...) is ever called. What only page JS has (a React fiber or other element expando, an app store, the page's own signedfetch) comes throughargs.page(fn, ...args):fnis sent by its source and run in the main world with the JSONargs, and its JSON result comes back; it cannot see the adapter's variables and takes a selector, not an element. Older chrome-use ignores the key and runs in the main world:const page = args.page || ((fn, ...a) => fn(...a)).
status: "timeout" only means the adapter was still running when time ran out; the run in the page may finish afterwards. Check the page before rerunning a publish command, so nothing is published twice.
Auto-triggering — see it, use it
You don't have to remember which sites have adapters. chrome-use keeps the adapter sources in sync automatically
(on first use + weekly), and whenever you reach a page whose domain has adapters it tells you:
on every open / navigate / snapshot, and on any other command that lands you on a different
site (tab new, switching tabs, back / forward, a click that navigates, the first command on a tab
you didn't open). Staying on one site, you hear about it once. The hint is:
- a line on stderr:
site adapters for <domain>; - a
siteAdapters: {domain, commands}field in--jsonoutput.
$ chrome-use open https://github.com/epiral/bb-sites
# … as the page opens, a line pops up on stderr:
site adapters for github.com — prefer these for structured data:
github/issues, github/pulls, github/repo …
e.g. chrome-use site github/issues <repo> --json
args: chrome-use site info github
site <name>/<cmd> it listssite adapters for <domain>, don't snapshot + click to read the data —
just use the site <name>/<cmd> it names: cheaper, more reliable, and already installed. You also
don't need to run site update yourself first — just use the command it gives you.
A site you use a lot has no adapter
When you drive the same site a lot (30+ actions this session, or on 3+ days) and no adapter covers it, chrome-use
adds siteAdapterSuggestion: {domain, actionsThisSession, daysUsed, message} to one response (stderr:
site adapter suggestion: …), so the AI asks you whether to turn the steps it keeps repeating into an adapter.
It comes once per site per session and not again for two weeks; local hosts and IPs are skipped. If you say yes, the
AI writes it to ~/.chrome-use/my-sites/<name>/<cmd>.js, registers the folder once with
chrome-use site add ~/.chrome-use/my-sites, and runs site update.
AGENT_BROWSER_SITES_NO_SUGGEST=1 turns it off.
Adding OpenCLI-format adapters
site add detects OpenCLI-format sources: an OpenCLI plugin repo (e.g. chrome-use site add darinouyang/opencli-plugin-shipxy), a zip, or a local dir such as your existing ~/.opencli/clis.
Their commands run through the same OpenCLI runtime over your chrome-use session and show as (opencli: <source>) in site list. .js modules are supported, and .ts ones on Node 22.13+; YAML-only plugins are not (OpenCLI itself stopped loading YAML in 1.8).
OpenCLI adapters are Node code running on your computer with your user's permissions, not inside the page like chrome-use adapters. So a local dir is added directly, while a remote source is pinned to the commit (zip: SHA-256) you trusted: at a terminal site add lists its modules and asks, and scripts or agents must pass --trust-node-code.
site update re-copies local dirs but never moves a remote pin; run site add again to take a newer version. On a shared name: our adapters, then your OpenCLI sources, then the OpenCLI package.
OpenCLI is a last resort
With Node.js 20+ on PATH, site update also installs OpenCLI (about 180 sites and 1,300 commands; a pinned version, installed with --ignore-scripts).
A name/cmd that neither adapter pack has runs through OpenCLI's own runtime, driving your current chrome-use session and its logins, e.g.
chrome-use site hackernews/best --limit 5 --json. They show as (opencli) in site list, site info shows their args, and they come last in the site hint.
On a shared name ours always win (priority: a site add source, then the official chrome-use-sites pack, then the bb-sites community pack, then OpenCLI); if ours fails and OpenCLI has a read command of the same name, that runs instead (writes never retry). AGENT_BROWSER_SITES_NO_OPENCLI=1 turns them off.
douyin/delete, douyin/update, twitter/delete, twitter/like, twitter/follow, bilibili/user-videos and bilibili/creator-stats used to come from OpenCLI and are now maintained in chrome-use-sites (ported with attribution, Apache-2.0); OpenCLI never runs them, and a cache that predates them is synced on first use. Delete, like and follow open their page first, so run them with --until-done.
OpenCLI is third-party code chrome-use does not maintain, and its commands have shipped broken. Every OpenCLI run prints a stderr note: … third-party OpenCLI command, not maintained by chrome-use; --json adds maintainedBy: "opencli", readOnly and notice. Use our own adapter whenever one exists; for posting, liking, following, deleting or publishing on your real accounts, don't rely on OpenCLI: port the command into chrome-use-sites.
When the site is not signed in
An adapter that finds the site signed out returns {"loginRequired": true} (with any error code, plus an optional loginUrl, the sign-in page relative to the site).
error: "login_required" or "not_logged_in" means the same. chrome-use also counts a failed run as signed out when one of the adapter's fetch calls got HTTP 401 or was redirected to a sign-in page, or the tab ended on one.
Then the command fails with a login wall: <host> is not signed in … line on stderr and loginWall in --json, pointing at auth login --bwu instead of the adapter's own message.
The first time for a site it asks whether to sign in from the vault: this time, always for that site, or never. A terminal gets a prompt; an agent gets loginWall.ask with the question and one command per answer, and must relay it to you rather than choose. With chrome-use auth autologin always <host> it opens loginUrl, signs in with the vault login for the site and runs the command once more. auth autologin status shows the decisions and auth autologin off <host> forgets one.
A write adapter is rerun only when it reported the login itself; one whose failure was inferred from a 401 or redirect may have half-run, so it is left for you to check.
Writing your own: analyze and verify
Do the action that loads the data on the page first (search, scroll, open the list), then run chrome-use site analyze. It lists the API calls the page made, the state it embeds (__NEXT_DATA__, __INITIAL_STATE__, …) and any anti-bot vendor, and recommends a data source.
In order of preference: a public API, the site's own JSON API called from the page with its cookies, embedded page state, then the DOM. Each step down breaks more often.
Once it works, chrome-use site verify <name>/<cmd> [args] --write-fixture records the shape of a good result; later, site verify fails (exit 1) when a field disappears, changes type, or a list comes back empty. The fixture keeps the shape only, never values.
site <name>/<cmd> report "not installed." In that case run site update once and re-run the command.
When to use it vs extract --schema vs snapshot
There's often more than one path to the same page of data. Site adapters are the first choice for structured data from a known site; fall back down the ladder when no suitable adapter exists. The table below helps you pick.
| Your situation | Use | Why |
|---|---|---|
| Structured data from a known site (GitHub issues, Reddit/HN search, Bilibili / Twitter feeds), especially once logged in | chrome-use site <name>/<cmd> |
Hits the site's JSON API directly, runs as you, skips snapshot + click — cheapest and most reliable |
| Structured data from any page with no community adapter — tables, search results, feed rows, dashboards | extract --schema '{rows,fields}' |
Give it a shape, get clean JSON in one call — no writing eval, no dumping the whole page's HTML |
You need to reason over it with a model, or need @refs to interact |
snapshot -i --json |
A structured snapshot with stable refs — readable and drives follow-up click / type |
| A one-off extraction needing a custom shape with special logic | eval --stdin |
Write JS directly in the page's MAIN world — most flexible (an adapter is just this, distilled) |
extract --schema; only when neither fits
fall back to the usual manual snapshot / eval loop. For the full picture of data extraction, see
Extracting Data.
Source and license
Adapters come from two defaults: the bb-sites community source and
the chrome-use-sites official source.
chrome-use fetches and executes them at runtime — this repo does not bundle or redistribute
any adapter code, and site update fetches the latest version from upstream each time.
site list reflects the version you synced to locally this time.
Next steps
Site adapters are the cheapest tier of data extraction; the methods below cover the rest.