项目文件夹

文件
Gregor Žunič 5ab5a958bb add remote browser support (Browser Use cloud) + multi-daemon + rename to bu (#1)
* add remote browser support via Browser Use cloud + multi-daemon

HARNESLESS_NAME suffixes socket/pid/log — daemons are independent, no
supervisor. start_remote_daemon() creates a Browser Use cloud browser and
launches a daemon attached to it; kill_daemon() stops both. Local Chrome
path is unchanged.

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

* rename env vars to BU_ prefix (shorter, less noisy in tool calls)

HARNESLESS_NAME → BU_NAME
HARNESLESS_CDP_WS → BU_CDP_WS
HARNESLESS_REMOTE_BROWSER_ID → BU_BROWSER_ID

Socket/pid/log files keep the harnesless- prefix on disk so they're
recognizable in /tmp. BROWSER_USE_API_KEY unchanged (external convention).

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

* rename project from harnesless to bu

Socket/pid/log paths now /tmp/bu-<name>.{sock,pid,log}. pyproject package
name updated, uv.lock regenerated. Slash command now /bu.

Note: the repo directory itself is still named harnesless on disk. Rename
manually (mv harnesless bu) so the absolute paths in docs line up.

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-17 00:28:02 -07:00

4.0 KiB

AGENTS.md

For agents modifying bu. For using it, see SKILL.md.

Philosophy

Both agent-browser (60+ verbs) and browser-use (~20 verbs) are walled gardens. bu inverts this: few helpers, LLM edits them at runtime. Every design decision flows from that.

Architecture

Chrome / Browser Use cloud ── CDP WS ─▶ daemon.py ── /tmp/bu-<NAME>.sock ─▶ run.py

Protocol: one JSON line per direction. Request: {method, params, session_id} (CDP passthrough) or {meta: ...}. Response: {result} / {error} / {events} / {session_id}.

Daemon attaches to the first real page at startup, buffers events in deque(maxlen=500).

BU_NAME (default default) suffixes socket/pid/log — multiple daemons coexist, no supervisor. BU_CDP_WS overrides the local DevToolsActivePort lookup (remote via start_remote_daemon()). BU_BROWSER_ID + BROWSER_USE_API_KEY → daemon stops the cloud browser on shutdown.

Design decisions worth preserving

  • Coordinate clicks default. Input.dispatchMouseEvent goes through iframes/shadow/cross-origin at the compositor level — no per-frame session juggling.
  • Connect to user's running Chrome, never launch one. User sees what's happening; cookies/logins are theirs.
  • cdp-use is used only for CDPClient.send_raw. The 36k lines of typed wrappers are IDE sugar; raw CDP strings tokenize better and the LLM knows method names from training.
  • run.py is 3 lines on purpose. No argparse, no subcommands. LLM writes Python.
  • helpers.py is editable at runtime. This is the whole point.

Rules when extending

  • Helpers ≤ 15 lines. No classes. No deps beyond stdlib + cdp-use + websockets.
  • Don't add meta verbs lightly; if it can be a helper calling cdp(), it's a helper.
  • Never add: CLI argparse, tests, logging framework, config files, session manager, retries, daemon supervisor. Multiple daemons are fine (one per BU_NAME) — just don't build a thing that manages them.
  • Taste test: could the LLM rewrite this from scratch after reading it once?

Known gotchas

  • Chrome 144+ chrome://inspect/#remote-debugging does NOT serve /json/version. Daemon reads <ChromeProfile>/DevToolsActivePort instead. Don't suggest the user launch with --remote-debugging-port — they don't want that.
  • Omnibox popups are type: "page" CDP targets with ~50px viewports. Filter by URL prefix (is_real_page in daemon.py, INTERNAL tuple shared with helpers.py).
  • type_in/clear uses Cmd+A (macOS). Linux/Windows: 2 instead of 4 for modifiers.
  • send_raw has no timeout — stuck call hangs forever. Add a wrapper if it bites.
  • Daemon's default session goes stale if user closes the attached tab manually. ensure_real_tab() re-attaches.
  • Two tuples named INTERNAL (daemon.py, helpers.py) — cross-process, can't share module. Keep in sync.
  • Browser Use API is camelCase on the wire (cdpUrl, proxyCountryCode). The SDKs rename — we don't.
  • Remote cdpUrl is HTTPS, not ws. cdp_ws_from_url() hits /json/versionwebSocketDebuggerUrl.
  • Stop a cloud browser with PATCH /browsers/{id} {"action":"stop"}. POST /sessions/{id}/stop is for agent sessions.

Session lessons

  • Half the original helpers were never called in practice. Dropped: get_dom, element_pos, click_element, type_in, save_cookies, load_cookies, set_viewport, screenshot_full, double_click, right_click, move_mouse, new_tab, close_tab, handle_dialog, back, reload. Every DOM interaction went through js("...") with a bespoke selector.
  • http_get + ThreadPoolExecutor beats the browser for static scrapes. 249 Netflix pages in 2.8s parallel.
  • wait(5) after goto is fragile. wait_for_load() polls document.readyState.
  • Auth-gated sites (Upwork, X) redirect to login. Not our problem; bail and ask the user.
  • Screenshots render at ~half viewport width in the transcript. Don't eyeball click coords off the image — use js("el.getBoundingClientRect()") for the real pixel location.