The Fast start snippet used https://browser-use.com purely as an
illustrative "hello world". Pointing it at docs.browser-use.com
gives agents a more useful landing page on their first run.
Refs #102
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the site shape for centilebrain.org 'Generate Estimates' flow:
- Six Shiny iframes (modality x sex) at centilebrain-app.shinyapps.io
- Selectors for email, file input, compute, download
- Wait markers and three non-obvious traps (iframe target_id staleness,
MUI switch checkboxes, coordinate-vs-JS button click after scroll)
- End-to-end example using upload_file(..., target_id=...) with
iframe_target()
- Output zip schema
Requires the iframe target_id support added to upload_file().
Synthetic .click() on the visible Star button does not persist the
star — there's a hidden 0x0 fallback button that querySelector finds
first, and the visible React button swallows synthetic events. Submit
the form directly; CSRF is already embedded.
Field-tested while completing the install.md verification step.
Both skills share the post-article DOM surface (div[role="article"]
and the data-ad-*-preview message selectors), but differ in URL shape,
sort options, and rate-limit ceilings. Pages are public and tolerate a
higher rate; Groups gate content behind membership and are stricter.
Each file covers: URL patterns, DOM anchors with verification notes,
a collect-as-you-go scroll pattern (FB virtualizes the feed so
scroll-then-collect misses posts), the l.facebook.com/l.php redirector
decoder, a Firecrawl handoff example, rate-limit discipline, a
self-inspection JS block for detecting selector drift, and a full
end-to-end example that emits JSON on stdout for downstream tools.
The groups.md anchors were verified against a logged-in account on
2026-04-18. pages.md inherits the post-article anchors from groups.md
(shared React component) and adds Page-specific header/metadata
selectors; a gotchas log section invites confirmation on first live use.
Documents the Monaco + React-canvas seam that causes most 'automation
silently did nothing' failures in Framer:
- Double-click requires the full pointer+mouse event chain (detail:2 matters)
- Monaco paste must be clipboard + OS-level Cmd+A/Cmd+V, then wait before save
- Publish button only mounts when a page is selected in the Pages tab; it
rejects synthetic clicks and must be driven by screen-coord input
- Framer autolayout preempts programmatic Header position/left/right writes,
forcing nodes offscreen; delete-and-copy is the working workaround
Plus the list of canvas-level interactions that consistently reject
automation (drag-drop, variant switching, property binding, Page Settings)
so agents know to escalate to the human instead of retrying.
Stable data-testid selectors table for Pages/Layers/Assets tabs, and the
edge-cache + sitemap quirks on framer-hosted live domains.
restart_daemon's name is a long-standing misnomer — it only stops.
The 'restart' label came from the typical caller workflow of
restart_daemon() → next browser-harness invocation → ensure_daemon()
spawns a fresh one. The function itself never restarts anything.
A short comment in stop_remote_daemon now documents this, and
restart_daemon's own docstring is updated to lead with what it
actually does ('Best-effort daemon shutdown + socket/pid cleanup')
rather than implying a restart.
No behaviour change.
* remote: stop_remote_daemon helper, skill updates from sub-agent testing
Three fresh sub-agents ran typical-user prompts against the skill
("start a remote browser with my logged-in data" / "Stripe only, no
Google" / "refresh my existing profile"). All three completed
end-to-end — cookies made the round trip, filters scoped cleanly,
refresh was idempotent. But they hit a few doc/code seams worth
closing:
admin.py:
- Add stop_remote_daemon(name="remote"). Sub-agents kept reaching
for restart_daemon() because there was no obvious way to end a
remote session; the new alias pairs symmetrically with
start_remote_daemon() and makes the intent explicit. Same
underlying implementation — it's just naming for callers.
run.py:
- Pre-import stop_remote_daemon so it's usable from
browser-harness <<'PY' ... PY without an extra import line.
interaction-skills/profile-sync.md:
- Drop the "close Chrome before syncing" trap at the top of the
Traps section. Obsolete on profile-use v1.0.5+ — the tool copies
the profile dir to a temp and syncs from the copy. Two sub-agents
verified sync works with Chrome open on v1.0.5. Kept a small
note at the bottom for anyone on older versions.
- Document the ♻️ / 📝 reuse-vs-create signal in sync_local_profile
output, so agents can confirm cloud_profile_id was accepted
without counting profiles.
- Clarify the API path convention for _browser_use and the raw
examples: paths are relative to BU_API, not absolute
/api/v3/... Sub-agents were copy-pasting the old doc form and
getting 404s.
- Add a one-liner for looking up an existing cloud profile's UUID.
- Surface stop_remote_daemon in the Python API overview.
Housekeeping:
- Stopped 5 sub-agent-leftover remote browsers from today's testing
burst (not committed; just an operations note).
* skill: UUID lookup pattern must handle 0 and >1 matches (cubic review)
cubic flagged the one-liner as unsafe: 'next(p["id"] for p in … if …)'
raises StopIteration on no match and silently picks the first duplicate
when names repeat. Profile names genuinely aren't unique — sub-3 in the
same testing round surfaced a duplicate on this account — so use a list
comprehension and require exactly one match before using the UUID.
Add routes, filter URL format, GraphQL bootstrap, and the 'credentials: include'
pattern for reusing the tab's NextAuth session cookie without JWE juggling.
Auth quirk documented: injecting only the JWE into a fresh Chrome profile
triggers a login loop; UI needs the full cookie set (persistent profile),
while backend GraphQL works with the JWE alone.
* docs: suggest starring the repo, don't auto-star it
The verification task at the end of install was for the agent to
directly star the harness repo if the user was logged in to GitHub.
That conflates "verify the harness is attached" with "make a social
action on behalf of the user" — the latter shouldn't happen
without explicit user intent.
Reword install.md step 8 and the README setup prompt + example task
so the agent navigates to the repo (still verifies attach + activates
the tab so the user can see it) and *suggests* starring if the user
likes the project, instead of clicking the star itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: agent should ask permission, not redirect star action to user
Previous wording told the agent "suggest the user star it themselves;
don't click yourself." That dropped the agent's role in the demo
entirely. The intent is the opposite: the *agent* should offer to
star the repo for the user (as a live demo that the harness can
interact with the page), and only do it if the user agrees.
Also drop the "Example task: ..." tagline from README — point to
domain-skills/ for examples instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes that fell out of testing PR #84 against a real account.
1) list_cloud_profiles was hitting /profiles?pageSize=200 and getting 422
back — the API caps pageSize at 100. Anyone with more than 10 profiles
already saw a silent truncation before this (the request returned 10
items by default) and anyone bumping past 100 would hit the hard error.
Now paginates with pageSize=100 until totalItems is reached. My account
is at 18 and climbs every sync_local_profile() call, so this was going
to bite shortly.
2) sync_local_profile only exposed profile_name + browser. profile-use
v1.0.4 shipped three more flags that solve real pain:
--cloud-profile-id <uuid> → update an existing cloud profile
--domain <d> → only these domains (repeatable)
--exclude-domain <d> → drop these domains (repeatable)
Wired up as cloud_profile_id / include_domains / exclude_domains kwargs.
When cloud_profile_id is passed, profile-use prints "♻️ Using existing
cloud profile" instead of "Profile created: <uuid>" — special-cased the
regex path to just return the caller-supplied UUID.
Also drops the "Upstream limitations" section from profile-sync.md (both
limitations are fixed as of v1.0.4) and adds two worked examples:
- refresh the same cloud profile (cloud_profile_id=)
- push only Stripe cookies into it (include_domains=["stripe.com"])
Verified end-to-end:
sync_local_profile("browser-use.com", include_domains=["stripe.com"])
→ "Domain filter: 43 → 1 cookies (include=[stripe.com])"
→ cloud profile cookieDomains == ["m.stripe.com"]
→ second call with cloud_profile_id=uuid printed "Using existing cloud
profile" and returned the same UUID (idempotent).
* remote: Python API for remote browsers, profiles, and local-profile sync
No CLI, no new entrypoint. Every helper is a Python function callable from
inside a normal `browser-harness <<'PY'` block. run.py pre-imports them.
admin.py:
- start_remote_daemon(name, profileName=None, **create_kwargs)
Now forwards every documented POST /browsers kwarg (profileId, profileName,
proxyCountryCode, timeout, customProxy, browserScreenWidth/Height, ...).
profileName is resolved client-side via list_cloud_profiles — no browser-use
API change needed. Prints liveUrl and auto-opens it locally when a GUI is
detected (macOS/Windows always; Linux needs $DISPLAY / $WAYLAND_DISPLAY);
headless servers print only.
- list_cloud_profiles() — GET /api/v3/profiles + per-profile detail; returns
[{id, name, cookieDomains, lastUsedAt, userId}]. Agents should report
len(cookieDomains) not the full list — profiles can have 500 cookies across
dozens of domains.
- list_local_profiles(), sync_local_profile(name) — shell out to `profile-use`.
sync_local_profile returns the newly-created cloud UUID.
Profile-sync skill rewritten Python-first with the chat-driven flow (ask the
user which profile; summarize by domain count, never dump cookies) and calls
out the two upstream limitations (sync always creates a new cloud profile; no
per-domain filtering) that need a PR to browser-use/profile-use — they can't
be fixed in browser-harness because the Browser Use API has no cookie
upload/download endpoint.
SKILL.md remote-browsers section updated to match, leading with the parallel
sub-agent use case.
* remote: fix misleading 'no GUI' message when webbrowser.open raises
cubic flagged this on #84: if _has_local_gui() is True but webbrowser.open
raises (e.g. no default browser configured), the code fell through to the
final 'no local GUI — share the liveUrl' line, which is wrong on both counts.
Restructure so each branch produces exactly one accurate message.
pyproject.toml used >= ranges for browser-harness, cdp-use, and
websockets, so a resolver could pull newer minors/patches on any
fresh install. Move them to == at the currently-resolved versions so
bumps have to be explicit and show up in review.
uv.lock stays gitignored; transitives float by design.
* skill: make PATH invocation and new_tab-at-session-start unmissable
Two footguns that agents keep hitting on their first call:
1. They prefix the harness with `cd /path/to/browser-harness && uv run …`
even though `browser-harness` is installed on `$PATH` as a standalone
entrypoint. `uv run` from the wrong cwd actively fails, and the `cd`
bakes a brittle assumption about where the repo lives.
2. They `goto(url)` on the first call, which navigates the user's
currently-active tab and destroys whatever they were doing.
Fast start now:
- uses `browser-harness <<'PY'` (no `uv run`) in the example
- uses `new_tab(url)` instead of `goto(url)`
- adds an explicit two-point callout explaining *why* each rule matters
- mirrors both rules in the "What actually works" bullet list so a
scanning agent sees them even if they skip the intro
- drops the stray `uv run` from the remote-browser snippet
* skill: trim Fast start callout; add read-before-edit note
* skill: require reading full file before using the harness, not just editing
Passing the destination URL to Target.createTarget races with
Target.attachToTarget: by the time the new session is attached and
_mark_tab() runs, the brand-new page is briefly on about:blank with
document.readyState == "complete". A subsequent wait_for_load() then
returns immediately and page_info() reads the about:blank state.
Always create the tab on about:blank, attach, then route real URLs
through the existing goto(). Same contract as goto(url) — the caller
still calls wait_for_load() — so this is consistent with the rest of
the helpers and adds no new event-handling code.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* install: error-driven decision tree, drop unconditional chrome://inspect
The previous bootstrap implied that every attach failure (and any
not-running-Chrome case) needed a chrome://inspect navigation. In
practice the remote-debugging checkbox is per-profile sticky in Chrome,
so for any profile that has ever had it toggled on, just launching
Chrome and polling is enough — chrome://inspect is only needed the
first time per profile, when DevToolsActivePort is genuinely missing.
Restructure step 3 of install.md as an explicit error-keyed decision
tree (no Chrome process / DevToolsActivePort missing / port not live
yet / stale websocket) and add a matching gotcha to SKILL.md. Also fix
a stale `uv run bh` snippet in SKILL.md — the entrypoint is
`browser-harness`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update install.md
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* Update install.md
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
PubMed/NCBI (ESearch→ESummary/EFetch pipeline; count is string; ELink broken 2026; CollectiveName branch),
CrossRef (title/container-title always lists; abstract has JATS XML tags; type=proceedings-article not conference-paper),
OpenAlex (abstract_inverted_index reconstruction; cursor >10K pages; group_by not group-by; concepts deprecated use topics),
FRED (fredgraph.csv timeouts headlessly; API needs free key; BLS/WorldBank/AlphaVantage as keyless alternatives),
MusicBrainz (Mozilla/5.0 gets 403; recording length in ms; CAA front flag vs types array differ).
The merged tab indicator only marked on Page.loadEventFired,
but Page events weren't enabled for new sessions created by
switch_tab(). Added Page.enable + immediate mark in set_session
handler so the 🟢 appears on every tab switch and survives
goto() navigation.
6/6 tests pass: new_tab, switch_tab, goto, click link,
cmd+click, switch back.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* try WS handshake once, not 12 times
Each retry created a new CDPClient which opened a new WebSocket
connection, triggering Chrome's "Allow debugging" dialog again.
12 retries = 12 stacked popups the user has to dismiss.
Now tries once. If it fails, tells the user to click Allow and
retry — no popup spam.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* add tab title indicator for agent-controlled tab
Prepends 🟢 to the page title on switch_tab() so the user can
see which tab the agent controls. Unmarks the previous tab first
so only one tab is marked at a time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add Microsoft Edge support
Edge is Chromium, so the daemon's CDP bootstrap works unchanged once it
knows where Edge's DevToolsActivePort lives. This adds the macOS, Linux,
and Windows Edge profile paths to the discovery list, plus a one-line
note in install.md clarifying that `chrome://inspect/#remote-debugging`
and the rest of the setup flow apply to Edge too.
* Edge Beta/Dev/Canary, drop Edge from main README
Cover the non-stable Edge channels on macOS, Linux, and Windows so
users on insider builds don't fall through to 'DevToolsActivePort not
found'. Edge SxS (Side-by-Side) is Canary's install dir on Windows.
Keep README's Chrome-only pitch; Edge stays documented in install.md
where setup details belong.
* install.md: move Edge note to bottom, one line
Top-of-section carve-out distracts from the Chrome bootstrap steps.
A one-liner in the cold-start reminders is enough for users who need it.
---------
Co-authored-by: MagMueller <mamagnus00@gmail.com>
Each retry created a new CDPClient which opened a new WebSocket
connection, triggering Chrome's "Allow debugging" dialog again.
12 retries = 12 stacked popups the user has to dismiss.
Now tries once. If it fails, tells the user to click Allow and
retry — no popup spam.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
daemon.py: Page/DOM/Runtime/Network.enable calls now have a 5s
timeout. Previously they could hang indefinitely on heavy pages
(TikTok FYP), preventing the daemon from reaching its socket
listener.
SKILL.md: added one gotcha for when restart_daemon() itself hangs
(kill Chrome entirely and reconnect).
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Agents rarely know to check for open alert/confirm/prompt/beforeunload
dialogs — they freeze the JS thread, so page_info()'s Runtime.evaluate
returns a stale or misleading viewport dict while the page is blocked.
Track the latest unhandled Page.javascriptDialogOpening in the daemon
(cleared on Page.javascriptDialogClosed) and, when one is open, return
{dialog: {type, message, ...}} from page_info() instead of the usual
viewport dict. The dialogs skill now points agents at this signal.
* Reframe post-task ritual as the default "contribute back" procedure
Renames the section to "Always contribute back" and turns the guidance
into an imperative default. Adds concrete examples of what's worth a PR
(private APIs, framework quirks, stable selectors, URL patterns, waits,
traps), a schema for what a domain skill should capture (the durable
shape of the site, not the run narration), and an explicit do-not-write
list — most importantly banning raw pixel coordinates.
Narrows the scope to `domain-skills/` contributions only; no longer
nudges agents to update `interaction-skills/` or `helpers.py` as part
of this loop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Replace "hesitate" with clearer cost framing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>