* remote: stop_remote_daemon helper, skill updates from sub-agent testing
Three fresh sub-agents ran typical-user prompts against the skill
("start a remote browser with my logged-in data" / "Stripe only, no
Google" / "refresh my existing profile"). All three completed
end-to-end — cookies made the round trip, filters scoped cleanly,
refresh was idempotent. But they hit a few doc/code seams worth
closing:
admin.py:
- Add stop_remote_daemon(name="remote"). Sub-agents kept reaching
for restart_daemon() because there was no obvious way to end a
remote session; the new alias pairs symmetrically with
start_remote_daemon() and makes the intent explicit. Same
underlying implementation — it's just naming for callers.
run.py:
- Pre-import stop_remote_daemon so it's usable from
browser-harness <<'PY' ... PY without an extra import line.
interaction-skills/profile-sync.md:
- Drop the "close Chrome before syncing" trap at the top of the
Traps section. Obsolete on profile-use v1.0.5+ — the tool copies
the profile dir to a temp and syncs from the copy. Two sub-agents
verified sync works with Chrome open on v1.0.5. Kept a small
note at the bottom for anyone on older versions.
- Document the ♻️ / 📝 reuse-vs-create signal in sync_local_profile
output, so agents can confirm cloud_profile_id was accepted
without counting profiles.
- Clarify the API path convention for _browser_use and the raw
examples: paths are relative to BU_API, not absolute
/api/v3/... Sub-agents were copy-pasting the old doc form and
getting 404s.
- Add a one-liner for looking up an existing cloud profile's UUID.
- Surface stop_remote_daemon in the Python API overview.
Housekeeping:
- Stopped 5 sub-agent-leftover remote browsers from today's testing
burst (not committed; just an operations note).
* skill: UUID lookup pattern must handle 0 and >1 matches (cubic review)
cubic flagged the one-liner as unsafe: 'next(p["id"] for p in … if …)'
raises StopIteration on no match and silently picks the first duplicate
when names repeat. Profile names genuinely aren't unique — sub-3 in the
same testing round surfaced a duplicate on this account — so use a list
comprehension and require exactly one match before using the UUID.
* 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.
A bare `bh` invocation blocks forever on sys.stdin.read(). Detect a TTY
stdin and exit with a one-line usage hint instead, so the failure mode
is obvious.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>