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.
* 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>
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>