文件历史

提交图

45 次代码提交

作者 SHA1 备注 提交日期
Gregor Žunič d250809de1 Clarify remote daemon flow 2026-06-20 23:43:24 -07:00
Gregor Žunič 010a25205b Add release-ready browser harness packaging 2026-06-20 23:06:41 -07:00
Sarath Suresh a9f7b1d547 Remove -c script execution 2026-05-12 15:35:12 +05:30
MinJaeLee1 226876d56e docs: nudge agents to read matching domain skills 2026-05-07 21:16:03 +09:00
Alezander9 a2443d1d51 docs: standardize harness docs against canonical browser connection reference
Cross-checked AGENTS.md, README.md, SKILL.md, install.md, and profile-sync.md for accuracy. Tightened the sync_local_profile docstring and the chrome://inspect stderr message to match. Cloud-bootstrap test updated to set BU_AUTOSPAWN. All unit tests pass.
2026-05-02 18:02:36 -07:00
Alezander9 7e9a7db8c5 feat: gate domain skills behind BH_DOMAIN_SKILLS env (default off)
Domain skills auto-injected by goto_url() are community-contributed and quality varies; defaulting them off avoids polluting the average run while preserving the contribution loop. Set BH_DOMAIN_SKILLS=1 to opt in.
2026-05-01 20:34:01 -07:00
Claude ee1ff81eac Rename skill command from /browser-harness to /browser
Shortens the invocation to a cleaner /browser command.

https://claude.ai/code/session_014BWe8AkViicHviYPP843t5
2026-04-28 22:19:53 +00:00
Sarath S Menon fb1a51dd9b refactor: move to src layout, agent-workspace, and fix SKILL.md invocation format (#229)
* refactor(tests): reorganize into tests/unit and tests/integration

Moves all root-level test_*.py files into a structured tests/ directory:
- tests/unit/ — admin, helpers (was test_screenshot), run
- tests/integration/ — js expression tests
- tests/conftest.py — shared fake_png pytest fixture, eliminating duplication

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

* refactor: move to src layout, agent-workspace, and fix SKILL.md invocation format

- Move package to src/browser_harness/ and domain-skills/interaction-skills to agent-workspace/
- Fix all browser-harness <<'PY' heredoc examples in SKILL.md and run.py HELP string to use the correct -c '...' flag format (heredoc was never supported by the CLI)
- Update SKILL.md path references from domain-skills/ to agent-workspace/domain-skills/

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 15:38:11 +05:30
Abraham 74afacf28c domain-skills: add Polymarket scraping skill
Markdown-style domain skill for Polymarket scraping via Gamma API
(api-first per repo doctrine) with DOM leaf-div-disambiguation fallback
for CSS-module SPAs. Covers market outcomes, metadata, and comments.

Live-tested against gamma-api.polymarket.com and a live event page:
  - 9 outcomes extracted (e.g. April 7: YES 99.95 / NO 0.05, vol $45.7M)
  - Metadata: title, end_date, total_volume, category, market_count
  - 38 comments fetched (40 raw, 2 deleted skipped)

Gotcha documented: Gamma API comment envelopes for deleted comments
preserve id/createdAt/profile/media/parentCommentID but drop the body
field entirely — naive dict access throws KeyError. Guard with
'if "body" not in c: continue'.

DOM fallback pattern documented (not primary path): Polymarket has
zero data-testid attributes and CSS-module-hashed classes. Leaf-div
disambiguation (children.length === 0 + nearest-common-ancestor
grouping) is the only robust approach.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:33:17 -03:00
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 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 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
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
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
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 bf0a36a61a skill: surface remote browser liveUrl and tell agents to share it (#83)
* skill: surface remote browser liveUrl and tell agents to share it

* skill: trim liveUrl callout to one short sentence
2026-04-18 18:32:21 -07:00
Magnus Müller 7b457139ba skill: make PATH invocation and new_tab-at-session-start unmissable (#77)
* 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
2026-04-18 18:07:40 -07:00
Magnus Müller dcd802cdb1 install: error-driven decision tree, drop unconditional chrome://inspect (#74)
* 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>
2026-04-18 17:44:48 -07:00
Magnus Müller 6a6081538a add 5s timeout on domain enable calls, add nuclear recovery gotcha (#66)
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>
2026-04-18 15:06:33 -07:00
Magnus Müller f014990f84 SKILL.md: reframe post-task ritual as "Always contribute back" (#61)
* 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>
2026-04-18 11:25:53 -07:00
Magnus Müller 23ce2ec464 Fix daemon attaching to invisible omnibox popup (#60)
* fix daemon attaching to invisible omnibox popup on fresh Chrome

When Chrome opens fresh, the only page targets are chrome://
internal pages and the omnibox popup (1px invisible viewport).
The daemon's attach_first_page() fell back to the popup, making
all subsequent work invisible to the user.

Fix: when no real pages exist, create an about:blank tab via
Target.createTarget instead of attaching to the omnibox popup.

Tested configurations:
- Fresh start with no real tabs → creates about:blank (1112x817)
- Navigate without AppleScript → works, tab visible
- Recovery from stale socket → auto-reconnects
- Chrome restart from scratch → creates about:blank

Also adds interaction-skills/connection.md documenting the
omnibox popup problem and startup sequence.

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

* add connection skill reference to main SKILL.md

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 11:07:43 -07:00
Magnus Müller 0e12dc8f17 [codex] Refine domain skill guidance (#41)
* Refine domain skill guidance

* Drop unintended skill regressions
2026-04-17 23:27:35 -07:00
Magnus Müller 239a2a45ea [codex] Document domain-skill PR ritual (#39)
* Document domain-skill PR ritual

* Tighten domain-skill PR wording

* Expand shared domain-skill guidance
2026-04-17 23:19:18 -07:00
Magnus Müller ffc0aceaba Emphasize screenshots for verification and exploration (#38) 2026-04-17 23:09:23 -07:00
Magnus Müller bfc07d37e8 Trim JS-heavy guidance from SKILL (#37) 2026-04-17 22:59:35 -07:00
Magnus Müller f87fdde0af Remove default ensure_real_tab from docs (#36) 2026-04-17 22:46:33 -07:00
Magnus Müller becec42ca4 [codex] Rename CLI to browser-harness run (#35)
* Rename CLI to browser-harness run

* Simplify browser-harness CLI
2026-04-17 22:33:05 -07:00
Magnus Müller c9910d426d Top-load browser harness usage guidance (#33)
* Top-load browser harness usage guidance

* Refine skill fast-start copy
2026-04-17 22:26:46 -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
Magnus Müller 0702b83759 Clarify Chrome remote debugging bootstrap (#22) 2026-04-17 21:12:50 -07:00
Magnus Müller 3193531a28 Split install and runtime docs (#20)
* Split install and runtime docs

* Refine install skill prompts
2026-04-17 20:57:29 -07:00
Gregor Žunič 29c4dfa8e7 changed name 2026-04-17 20:50:33 -07:00
Magnus Müller 16ddd3e630 Clarify Chrome profile setup flow (#18)
* Clarify Chrome profile setup flow

* Refine Chrome setup decision flow
2026-04-17 20:36:50 -07:00
Magnus Müller 35ae22bdec [codex] Make setup work from anywhere (#17)
* Make setup work from anywhere

* Rename launcher to bh

* Remove extra helper commands

* Document global skill setup
2026-04-17 19:32:18 -07:00
Magnus Müller fc3be06d78 add global bu launcher and shared skill guidance (#14) 2026-04-17 18:21:50 -07:00
Magnus Müller c83d8b6e74 simplify skill setup flow (#12) 2026-04-17 18:07:29 -07:00
MagMueller 552aed7087 merge AGENTS into SKILL 2026-04-17 17:16:01 -07:00
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
Gregor Žunič 4552e7b42f prune eval-specific helpers; keep primitives + document gotchas
Previous commit added fill_form / mui_select_first / is_success which are
eval-harness logic, not harnesless primitives (task-specific defaults like
"dumbledore"/"Harry Potter", MUI-only shim). Per AGENTS.md — "could the LLM
rewrite this from scratch after reading it once" — the LLM should pick field
values + the submit strategy per-task, not inherit eval defaults.

Removed: fill_form, _FILL_JS, mui_select_first, is_success (~100 lines)
Kept: dispatch_key, upload_file, capture_dialogs/dialogs (universal needs)

Insights from the eval captured as gotchas in SKILL.md instead:
- React controlled inputs need native-setter + input event
- Radios/checkboxes: el.click() over el.checked=true for React
- MUI / UI-library overlays: real CDP click, not JS .click()
- CDP char event ≠ DOM keypress for special keys → dispatch_key
- Same-origin iframes: contentDocument walk, not CDP targets
- Shadow DOM: querySelector doesn't pierce, walk .shadowRoot
- Form success signals vary: element / alert / body text

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:42:29 -07:00
Gregor Žunič 2366c226e9 self-managing daemon + document post-task self-improvement ritual
Connection management (addresses "shit ton of daemons" problem):
- Socket is the lock. Daemon refuses to start if another is already
  listening (5-line check).
- PID file at /tmp/harnesless.pid written on start, removed on exit.
- ensure_daemon() / kill_daemon() / daemon_alive() helpers.
- run.py auto-calls ensure_daemon() before exec — users never manage
  the daemon manually.
- kill_daemon uses PID file (robust) instead of pkill pattern matching
  (was silently missing because the process command line didn't contain
  the "harnesless/" prefix).

Post-task ritual added to SKILL.md: after every browser task, extract
ONE generalizable friction point and make the simplest possible
improvement (2-line helper, one-line gotcha, recipe correction).
This is how the harness sharpens itself over time.

Verified end-to-end: kill_daemon → 0 processes + files cleaned → next
run.py → auto-starts exactly one daemon. Second `uv run daemon.py`
exits with "daemon already running" message instead of racing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:00:38 -07:00
Gregor Žunič df92b0c4e7 handle stale sessions + add iframe support (Azure billing admin task)
Observed friction during an Azure portal task:
- Daemon's default session went stale (user closed the attached tab),
  which broke every subsequent call including browser-level Target.*.
- new_tab() had been removed in the previous simplification pass but
  was needed to recover.
- Azure portal renders blade panels in iframes; js() on the main page
  returned nothing for picker contents.

Changes:
- daemon: browser-level Target.* calls now bypass self.session entirely
  (so a stale session doesn't poison them). On "Session with given id
  not found" for session-scoped calls, clear + re-attach + retry once.
  Merged start() and the new recovery path into attach_first_page().
- helpers: add new_tab(); js() accepts target_id for iframe queries;
  iframe_target(substr) to find blade iframes; ensure_real_tab() now
  resilient to stale-session exceptions.
- SKILL.md: one-line note on iframe-site workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:56:35 -07:00
Gregor Žunič da8257c586 simplify: drop 14 unused helpers, compress daemon, tighten docs
Session of 7 real tasks (Upwork, X, Google Flights, HN, Netflix, iPhone
comparison) used only half the original helper surface — every DOM
interaction went through js() + a bespoke selector, never through the
indexed-DOM helpers. Dropped 14 that never fired: get_dom,
click_element, type_in, element_pos, save_cookies, load_cookies,
set_viewport, screenshot_full (folded into screenshot full=True),
double_click, right_click, move_mouse, new_tab, close_tab,
handle_dialog, back, reload.

Also:
- daemon: shorter identifiers, collapsed boilerplate, TOCTOU fix via
  try/except on read_text, is_real_page module-level, no variable
  shadowing of `url`.
- helpers: one shared INTERNAL_URL_SCHEMES tuple instead of three
  subtly-different ones; current_tab() collapsed from 4 round-trips
  with dead fallback to one; wait_for_load() stops draining the shared
  event buffer.
- SKILL.md / AGENTS.md: cut recipes and anything the LLM already knows
  (what click does, what CDP is). Kept only project-specific context.

Net: 8600 -> 4030 tokens across the project (53% reduction); the file
the LLM reads every skill invocation (helpers.py) dropped from 2193
to 1219 tokens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:41:27 -07:00
Gregor Žunič 2b8be642e0 initial commit: harnesless — LLM-first browser control via CDP
Three-process architecture: daemon.py holds one persistent CDP WebSocket
to the user's running Chrome (via chrome://inspect), short-lived run.py
processes talk to it over a Unix socket, helpers.py is the transparent
layer the LLM reads and edits at will.

Philosophy: no CLI, no fixed API surface. The LLM writes Python blocks
against ~13 tiny helpers (cdp, click, type_text, screenshot, get_dom,
etc.) and edits helpers.py on the fly when a pattern repeats. Coordinate
clicks default because they pass through iframes/shadow DOM/cross-origin
at the compositor level.

Uses cdp-use internally for send_raw only (ignores its 36k lines of
typed wrappers — raw CDP strings tokenize better than typed calls).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:51:31 -07:00