文件历史

提交图

12 次代码提交

作者 SHA1 备注 提交日期
Saurav Panda 2ce5b870c1 helpers: use certifi CA bundle for http_get so it works on python.org macOS builds
http_get called urllib.request.urlopen with no explicit SSL context, so
it fell back to whatever trust store the interpreter shipped with. On
python.org's Python 3.11 for macOS the CA keychain is empty until the
user runs Install Certificates.command, which nobody does — every HTTPS
call raised CERTIFICATE_VERIFY_FAILED. Agents were then writing
domain-skills that suggested subprocess curl or, worse, disabling TLS
verification entirely.

Load certifi's bundle explicitly via ssl.create_default_context. Same
approach requests and httpx use, works across macOS/Linux/Windows and
across Python installers.
2026-04-18 21:39:04 -07:00
Magnus Müller e953fa6556 [codex] Add domain skill name helper (#42)
* Add domain skill name helper

* Inline domain skill lookup in goto
2026-04-17 23:49:05 -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 3193531a28 Split install and runtime docs (#20)
* Split install and runtime docs

* Refine install skill prompts
2026-04-17 20:57:29 -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
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