文件历史

提交图

10 次代码提交

作者 SHA1 备注 提交日期
Shawn Pana 17f1640881 trim docstrings on new helpers to match house style
Collapsed multi-line docstrings on fill/clickables/field_info/type_text/press_key
to one-liners, dropped block comments above CLICKABLE_SELECTOR/FORMAT_HINTS,
and removed inline how-it-works comments in press_key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:28:32 -07:00
Shawn Pana 0ea5d91282 add fill()/clickables()/field_info() — composable page inspection
Three small helpers that close the gap with verb-style CLIs on form-filling
benchmarks, without bundling them into a monolithic "state" command:

- fill(selector, value): focus + Input.insertText + Tab blur. Fires native
  beforeinput/input/change/blur events so React/Vue/Angular/Formik inputs
  all register the value. ~4 CDP calls regardless of value length (vs the
  per-char press_key loop which is 3 calls × char-count).

- clickables(): indexed interactive elements — cheaper than screenshot+Read
  for verification. CLICKABLE_SELECTOR module constant is overridable per
  call for sites using custom [data-*] patterns.

- field_info(selector): all validation attrs + format_hint for date/time
  inputs + live validity/error from HTML5 validity API. Codifies the
  "what format does this widget expect" table (FORMAT_HINTS) so agents
  don't guess YYYY-MM-DD vs mm/dd/yyyy.

type_text() gets a docstring noting it does NOT fire keydown/keyup events,
pointing users to fill() for framework-controlled inputs.

Benchmarked on a 38-task browser-automation eval: total cost dropped from
$8.91 → $6.51 (−27%), pass rate went from 33/38 → 38/38 (v2 had 5 tasks
hit the 30-turn cap on framework form fields; fill() brought them all in
under cap).
2026-04-17 16:56:12 -07:00
Shawn Pana e427772b23 fix press_key double-insertion for printable chars
Previously, printable chars like 'a' or '.' sent keyDown with text AND
char with text, each of which inserts the character. Typing 'test' gave
'tteesstt'. Control keys (Enter, Tab, arrows) weren't affected because
their text is '\r', '\t', or empty and doesn't visibly double-insert.

Fix: for printable chars, use rawKeyDown (no text) + char (with text) —
the canonical CDP pattern for keyboard input. Control keys unchanged.
2026-04-17 16:55:03 -07:00
MagMueller 13eed1c64e default list_tabs to include chrome pages 2026-04-17 16:43:24 -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