文件历史

提交图

8 次代码提交

作者 SHA1 备注 提交日期
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
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
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
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
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
Magnus Müller acda8c72a0 Simplify helper surface (#23)
* Simplify helper surface

* Trim common module and restore dispatch key
2026-04-17 21:37:58 -07:00