提交

提交图

156 次代码提交

作者 SHA1 备注 提交日期
Sarath Suresh 6c9ddf56fe feat(debug): add --debug-clicks mode with DPR-aware overlay
When BH_DEBUG_CLICKS=1 (or --debug-clicks CLI flag), click_at_xy
captures a screenshot before each click and draws a red crosshair
at the exact click location, scaled by window.devicePixelRatio so
the marker aligns correctly on HiDPI/Retina displays.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 16:54:29 +05:30
Sarath S Menon 05d6eb8c3e refactor(run): replace heredoc stdin with -c flag (#188)
* fix(js): auto-wrap top-level return expressions in an IIFE

Agents naturally write js() calls with top-level `return`, which is a
syntax error in Runtime.evaluate. js() now detects `return ` and wraps
the expression in an IIFE so both styles work without callers needing
to know the CDP constraint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(run): replace heredoc stdin with -c flag

Drops the <<'PY' heredoc pattern entirely. Agents now pass code via
`browser-harness -c "..."`, which is simpler to construct programmatically
and avoids heredoc quoting pitfalls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 15:53:37 +05:30
Sarath S Menon 932347c7a2 fix(js): auto-wrap top-level return expressions in an IIFE (#187)
Agents naturally write js() calls with top-level `return`, which is a
syntax error in Runtime.evaluate. js() now detects `return ` and wraps
the expression in an IIFE so both styles work without callers needing
to know the CDP constraint.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 15:48:19 +05:30
Sarath S Menon 0c4af63b1b docs: move setup/maintenance content from SKILL.md to install.md (#186)
SKILL.md now covers day-to-day usage only. Maintenance commands
(--doctor, --setup, --update) and the architecture section move to
install.md where setup and break-fix content belongs.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 15:19:50 +05:30
Sarath S Menon 724c1c7a16 docs(SKILL.md): remove inline backticks; fix install.md goto_url rename (#185)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 14:45:28 +05:30
Sarath S Menon 361c90e0a7 Merge pull request #178 from browser-use/refactor/remove-playwright-name-overlap
refactor: rename goto/click/screenshot to avoid Playwright name overlap
2026-04-24 11:26:22 +05:30
Sarath Suresh fbd9146df5 refactor: rename goto/click/screenshot to avoid Playwright name overlap
- goto()       → goto_url()
- click()      → click_at_xy()
- screenshot() → capture_screenshot()

These three shared exact names with Playwright but different argument
shapes, causing agent confusion. Updated helpers.py and all markdown
skill files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 11:26:07 +05:30
Sarath S Menon d08ca526bf Merge pull request #177 from browser-use/docs/remove-bold-formatting
docs(SKILL.md): remove bold formatting
2026-04-24 10:53:29 +05:30
Sarath Suresh 3dfb08a73d docs(SKILL.md): remove bold formatting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 10:52:30 +05:30
Gregor Žunič ba8b22f1ff fix(run): exec scoping for comprehensions + sharpen click guidance (#133)
* fix(run): pass globals to exec so comprehensions resolve free vars

exec(sys.stdin.read()) inside main() passes different dicts for globals
and locals. Python comprehensions and generator expressions compile to a
nested function whose free-variable lookups can only see the globals
dict, so code like

    for it in items:
        low = it['text'].lower()
        hit = any(k in low for k in KEYS)   # NameError: 'low'

fails at module level under exec. `low` is stored in the exec locals
dict which the generator's implicit function cannot see.

Passing globals() as the only extra arg makes exec use the same dict
for both globals and locals, and comprehensions resolve cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(skill): eyeball click coords from screenshots; no getBoundingClientRect

The previous Clicking bullet said "screenshot -> look -> click(x,y)" but
did not rule out roundtripping through js("...getBoundingClientRect()")
to compute coords. Agents coming from Playwright / Selenium habits tend
to locate-first-click-second even when the screenshot already shows the
target, which is slower and more brittle (hidden inputs placed at
x=-9999, CSS-transformed elements, pseudo-elements) than just reading
the pixel off the image.

Rewrite the bullet to explicitly suppress that reflex and scope the
DOM-fallback to elements with no visible geometry. Also replace the
loose "compositor level" phrasing with the actual mechanism: Chrome's
browser-process hit-testing, which is why clicks pass through iframes
/ shadow DOM / cross-origin without extra work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:06:16 -07:00
Gregor Žunič e8941a5f35 fix(admin): ensure_daemon self-heals cold-start failures (#161)
Reuses the existing `_open_chrome_inspect()` helper so a bare
`browser-harness <<PY ... PY` invocation recovers from the three
cold-start failures that previously forced a manual restart_daemon()
or a separate `browser-harness --setup` run:

  1. Stale daemon — previous run's Chrome was killed but the daemon
     still listens on the unix socket. Probe with a real CDP call
     (`Target.getTargets`) and require "result" in the reply. A
     `{"meta":"session"}` probe would not work because the daemon
     answers meta requests from a cached Python dict even when its
     CDP WebSocket to Chrome is dead.
  2. Cold Chrome — log tail says "DevToolsActivePort not found" or
     "not live yet". `_open_chrome_inspect()` uses AppleScript
     `activate` which launches Chrome if absent, then opens the
     inspect page.
  3. Missing Allow on chrome://inspect — log tail says
     "WS handshake failed ... 403". Same recovery: open the inspect
     page, print one stderr hint ("click Allow..."), retry spawn once.

Net: +37/-22 lines, one function touched, no new helpers.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:00:41 -07:00
Magnus Müller e405d8da2d domain-skills: linkedin — invitation-manager.md (#149)
Captures the durable shape of LinkedIn's received-invitations page:

- URL filter slugs (PEOPLE_WITH_MUTUAL_CONNECTION, PEOPLE_WITH_MUTUAL_SCHOOL)
  and the chip counter as authoritative remaining-count.
- Accept/Ignore aria-label formats — they differ from each other, so you
  cannot derive one from the other.
- The "follows you" trap: Accept renders as <a href=current-URL>, not
  <button>; .click() follows href and no click path (MouseEvent, CDP
  Input.dispatchMouseEvent) triggers the accept handler. Route these to
  Ignore or skip.
- Pagination: list renders ~10 rows, replaced by "is now a connection"
  acknowledgments after accepts; reload the URL to fetch the next slice,
  scrolling does nothing.
- "Take care when connecting" safety modal appears intermittently.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 10:19:43 -07:00
Aitor 71f1b3b051 Merge pull request #131 from browser-use/feat/versioning-and-fetch-use
feat: self-update CLI and fetch-use routing
2026-04-22 00:52:43 +02:00
Saurav Panda e5f846caca Merge pull request #137 from sontianye/feat/youtube-domain-skill
feat(domain-skills): add YouTube scraping skill
2026-04-21 14:58:56 -07:00
Saurav Panda 677f3594c1 Merge pull request #138 from browser-use/readme-free-remote-stealth
readme: name stealth, proxies, and captcha solving in free remote browsers
2026-04-21 10:25:00 -07:00
Saurav Panda a2898c40aa Merge pull request #141 from opensesamenext1-netizen/fix/flatpak-browser-profiles
Support Flatpak browser profile paths
2026-04-21 10:24:29 -07:00
opensesamenext1-netizen 761da289c8 Support Flatpak browser profile paths 2026-04-21 14:59:36 +00:00
Luka Secilmis 1797d8d55c readme: name stealth, proxies, and captcha solving in free remote browsers 2026-04-21 12:31:13 +02:00
songty1 5517717a51 fix(youtube/scraping): update stale age-restricted video docs
After the previous commit added early raises for LOGIN_REQUIRED and
ERROR in scrape_video(), two doc entries still described the old
behavior ("will succeed but status is LOGIN_REQUIRED"). Updated both
the Gotchas section and the 'What Requires a Browser' list to reflect
that scrape_video() now raises ValueError on age-restricted videos.
2026-04-21 15:58:57 +08:00
songty1 77a0f46854 fix(youtube/scraping): address code review findings
Two issues raised by automated review on PR #137:

1. max_results limit could be exceeded
   The `break` in youtube_search() only exits the inner loop over
   itemSectionRenderer contents. If YouTube returns multiple sections,
   the outer loop would continue appending results beyond max_results.
   Fixed by replacing `break` with `return results` to exit both loops
   immediately once the limit is reached.

2. Regex match not null-checked before .group(1)
   scrape_video() called m.group(1) directly after re.search(), which
   raises AttributeError if the pattern is not found (e.g. private video,
   deleted video, region-blocked content, or YouTube HTML structure change).
   Fixed by adding an explicit None check with a descriptive ValueError,
   and an early playabilityStatus check that surfaces LOGIN_REQUIRED and
   ERROR states with clear messages before attempting to parse videoDetails.
2026-04-21 15:48:29 +08:00
songty1 8ae50e76fd feat(domain-skills): add YouTube scraping skill
Adds domain-skills/youtube/scraping.md with four verified approaches
for extracting YouTube data without a browser or API key.

## What's included

**Approach 1 — oEmbed API (fastest)**
- Single HTTP call, ~0.3s per video
- Returns title, author, channel URL, thumbnail, embed HTML
- Bulk fetching via ThreadPoolExecutor with real timing data
- Verified on multiple video IDs

**Approach 2 — Watch page ytInitialPlayerResponse**
- Full video metadata: title, author, channel_id, duration, view_count,
  publish_date, upload_date, category, like_count, keywords, is_live,
  is_private, is_unlisted, available_countries (249 codes), embed_url
- Correct regex pattern (non-greedy with lookahead) to parse the JSON blob
- Real output values verified and included as comments
- Accurate gotchas: viewCount/lengthSeconds are strings not ints,
  likeCount lives in microformat not videoDetails

**Approach 3 — Search results (no API key)**
- Parses ytInitialData from /results?search_query= (server-side rendered)
- Returns up to ~14-20 results with videoId, title, channel, duration,
  views, published, description snippet, thumbnail URL
- Verified: 15 results returned for "python tutorial"

**Approach 4 — Channel metadata**
- Handles both @handle and channel ID (UC...) URL formats
- Extracts channel_id, title, description, subscriber count, avatar,
  banner from pageHeaderViewModel + channelMetadataRenderer
- Verified on @RickAstleyYT: "4.48m subscribers"

**Utilities**
- thumbnail_urls(): all 5 sizes with availability notes (maxres may 404)
- extract_video_id(): handles watch, youtu.be, /shorts/, /embed/ formats

**What requires a browser**
- Clear list of what http_get cannot access: trending, playlists,
  comments, caption text, age-restricted videos

**URL patterns reference table**

**Gotchas (all verified)**
- ytInitialPlayerResponse regex non-greedy requirement
- viewCount/lengthSeconds are string types, not int
- likeCount location (microformat, not videoDetails)
- oEmbed 404 on private/deleted videos
- Caption baseUrl returns empty in all tested conditions
  (plain http_get, XHR, and fetch with cookies) — not a session issue
- Search result count varies (~14-20), never assume fixed count
- Subscriber count is a rounded string, not an integer
2026-04-21 15:37:39 +08:00
reformedot dacd8bd842 fix: don't flag updates when installed version is unknown
run_doctor() assigned `cur = _version() or "(unknown)"` and then fed
that placeholder into `_version_tuple()`. The parser ignores
non-leading-digit characters, so `"(unknown)"` parsed as `(0,)` and
any known `latest` compared as newer — falsely flagging an update.

run_update() short-circuited on `latest and not newer` without
checking `cur`. When cur was empty, `newer` was False via the
short-circuit in check_for_update, so it wrongly printed
`up to date ()` and returned 0 instead of attempting the update.

Keep `_version()` for the comparison and use the placeholder only
for display. In run_update, require `cur` before treating
`not newer` as up-to-date; otherwise proceed with the update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:46:32 -07:00
Saurav Panda d1d6b59951 Merge pull request #130 from robertguss/add-reddit-and-medium-hydration-skills
domain-skills: reddit + medium article hydration
2026-04-20 17:28:24 -07:00
reformedot 8c6fb6af72 chore: remove release workflow
No automated release flow for now; tagging stays manual and local.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:54:25 -07:00
reformedot 1934e0ccee chore(release): drop PyPI publish step
Keeps tag-version verification and `uv build` on v* tag pushes but no
longer publishes to PyPI. Removes the OIDC id-token permission and the
`pypi` environment block that existed only for trusted publishing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:53:48 -07:00
reformedot 8a0c981082 feat: self-update CLI, release workflow, and fetch-use routing
- Add --version, --doctor, --setup, --update[-y] commands in run.py;
  logic lives in admin.py (install-mode detection, GitHub-releases cache
  with 24h TTL, dirty-worktree guard, interactive Chrome-attach flow).
- Print a once-per-day startup banner telling agents to run
  `browser-harness --update -y` when a newer release is available.
- Rename project to browser-harness in pyproject.toml so PyPI installs
  (uv tool install browser-harness) work via the public package name.
- Add .github/workflows/release.yml: on v* tag push, verify the tag
  matches pyproject.toml, uv build, and publish to PyPI via trusted
  publishing.
- Wire helpers.http_get through fetch_use.fetch_sync(...).text when
  BROWSER_USE_API_KEY is set; falls back to the original urllib path
  otherwise, preserving the existing str return contract.
- Document the new commands and the agent's self-update duty in
  SKILL.md and install.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:49:07 -07:00
Robert Guss a603b7e997 domain-skills/medium: article body via DOM (logged-in fallback) 2026-04-20 15:23:36 -04:00
Robert Guss 06fd196941 domain-skills: reddit — shreddit-* DOM extraction and JSON API 2026-04-20 15:23:31 -04:00
Saurav Panda 8aa28ee5ca Merge pull request #116 from johnmarktaylor91/docs/centilebrain-domain-skill
docs(skill): centilebrain — generate normative z-scores
2026-04-19 21:08:13 -07:00
Gregor Žunič 92f16a5109 docs: point onboarding fast-start at docs.browser-use.com (#118)
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>
2026-04-19 16:14:18 -07:00
JohnMark Taylor 1aff1e14de docs(skill): centilebrain -- generate normative z-scores
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().
2026-04-19 17:21:05 -04:00
Saurav Panda 84935b3738 Merge pull request #103 from forrest-motz/docs/github-form-actions
domain-skills/github: repo actions (star, watch) via form.submit()
2026-04-19 10:40:49 -07:00
Saurav Panda f3bfd9425d Merge pull request #101 from harrisboatworks/feat/facebook-domain-skill
domain-skills: add facebook/ (groups + pages)
2026-04-19 10:39:41 -07:00
Saurav Panda 859e1645b1 Merge pull request #99 from trwpang/narrow/trello-boards-and-lists-589ffe
trello: boards-and-lists
2026-04-19 10:38:48 -07:00
Saurav Panda e109cecd49 Merge pull request #105 from SybrenGL/fix/linux-chromium-devtoolsactiveport
fix: detect Chromium profiles on Linux
2026-04-19 10:37:49 -07:00
Sybren Geel ca2ee56bf8 fix: detect Chromium profiles on Linux 2026-04-19 17:43:35 +02:00
Forrest Motz f6a4724186 domain-skills/github: repo actions (star, watch) via form.submit()
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.
2026-04-19 13:24:32 +01:00
Jay Harris a66a92f3c4 feat(domain-skills): add facebook/ with groups and pages playbooks
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.
2026-04-19 06:15:36 -04:00
trwpang b87d6436c2 trello: boards-and-lists (via narrow) 2026-04-19 10:43:29 +01:00
sergeclaesen 4ec625595a domain-skills: framer — web editor (#98)
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.
2026-04-19 02:15:53 -07:00
Magnus Müller 1eb04006b3 admin: explain why stop_remote_daemon calls restart_daemon (#97)
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.
2026-04-19 00:36:09 -07:00
Magnus Müller 82a1f2f540 remote: stop_remote_daemon helper, skill updates from sub-agent testing (#96)
* 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.
2026-04-19 00:23:28 -07:00
sergeclaesen 93c630f5ae domain-skills: atlas — my.recruitwithatlas.com (#94)
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.
2026-04-18 23:45:55 -07:00
Magnus Müller b0d78177a6 docs: agent offers to star the repo as a demo, asks first (#95)
* 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>
2026-04-18 23:06:56 -07:00
Magnus Müller 759f44ff13 remote: paginate list_cloud_profiles; expose profile-use v1.0.4 sync flags (#93)
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).
2026-04-18 22:38:43 -07:00
Magnus Müller 96ccb7692b remote: Python API for remote browsers, profiles, and local-profile sync (#84)
* 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.
2026-04-18 22:19:47 -07:00
Saurav Panda 4f1e3cd5f8 Merge pull request #92 from browser-use/pin-deps
pyproject: pin dependencies and commit uv.lock
2026-04-18 21:53:14 -07:00
Saurav Panda 8bb719b05b pyproject: pin direct dependencies to exact versions
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.
2026-04-18 21:51:07 -07:00
Saurav Panda 1973fc78be Merge pull request #86 from browser-use/feat/domain-skills-batch18
Add domain skills: World Bank, REST Countries, NASA, Wayback Machine, arXiv bulk
2026-04-18 18:48:10 -07:00
Saurav Panda 01e5009d11 Merge pull request #73 from browser-use/feat/domain-skills-batch11
Add domain skills: PubMed, CrossRef, OpenAlex, FRED, MusicBrainz
2026-04-18 18:43:36 -07:00