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.
- ly-com: read Vue ref via stable .value with _rawValue fallback
rather than the brittle internal _rawValue path.
- wehotel: ancestor walk now identifies the hotel-name anchor by
identity/innerText instead of an href-based negation against text
(查看详情 lives on the link body, not in href), which previously
let the loop stop on the detail anchor itself.
- ctrip: raise an explicit error when the destination input or 搜索
button isn't found, instead of dereferencing None and crashing
with a confusing TypeError.
- write_text calls now pin encoding="utf-8" so non-ASCII transcripts
don't depend on the locale default.
- share-export.md schema now lists source_url, matching what the script
actually emits.
* domain-skills: add bilibili navigation and structure skill
Covers site architecture, URL patterns, content channels, video
interaction features (三连/投币/弹幕), and navigation quirks for
the Chinese video platform.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* domain-skills: fix PR review — correct history URL and ranking tab count
- Fix history URL from bilibili.com/history to https://www.bilibili.com/account/history
- Remove incorrect "also contains 稍后再看 tab" note (watch later is a separate SPA)
- Fix ranking category tab count from 23 to 24
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* domain-skills: add BOSS直聘 (zhipin.com) navigation and job search skills
Verified content only — navigation structure and job card extraction
confirmed via browser CDP.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* domain-skills: verify zhipin job search with API-first approach
Browser-verified: the /wapi/zpgeek/pc/recommend/job/list.json API
returns real salary numbers ("18-22K") unlike the DOM which uses
font-encoded PUA characters. Added full API reference for job list,
job detail, and filter conditions endpoints.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* domain-skills: verify zhipin chat with WebSocket + REST API
Browser-verified: conversation list loads via WebSocket (ws6.zhipin.com),
message history via /wapi/zpchat/geek/historyMsg API. Documented message
body types (text=1, job card=8, system=16), DOM selectors, pagination,
and bossId extraction.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* domain-skills: add zhipin chat filter tabs with clickable UI control
Browser-verified: 全部/未读/新招呼/更多/有交换/有面试/不感兴趣/AI筛选
filters with click_filter() and click_more_filter() functions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* domain-skills: browser-verify job-search.md and fix API field names
Verified all functions against live zhipin.com. Fixed brandInfo→brandComInfo
with correct sub-fields (brandName/logo/industryName), corrected city codes
(101280100=Guangzhou not Shenzhen), documented page-based pagination as
unreliable, added encryptExpectId default params, and noted anti-bot timing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* domain-skills: fix PR review — add API response validation, narrow bossId extraction, clarify city slug
- Guard all d.zpData accesses with d.code !== 0 checks across job-search.md and chat.md
- Filter bossId extraction to /wapi/zpchat/geek/historyMsg URLs only
- Correct "Chinese" to "pinyin" for city slug and add API code cross-reference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#266 made `browser-harness -c '...'` auto-provision a Browser Use cloud browser
whenever BROWSER_USE_API_KEY was set and no local Chrome / daemon was alive.
That gating signal is too broad — the API key is commonly set for unrelated
reasons (profile sync, cloud API calls, parent agents managing their own
session), so users with the key in env quietly got a *new* (billed) browser
spawned every time they ran a script.
Make auto-bootstrap opt-in via BU_AUTOSPAWN. Setting it alongside
BROWSER_USE_API_KEY restores #266's behaviour for fresh headless boxes;
anyone else is unaffected.
Refs #181, #183, #266.
Three improvements to the cross-platform IPC layer, lifted from #104:
- meta:'ping' handshake replaces bare TCP connect in daemon_alive() and already_running(). A connect-only check on Windows can succeed against an unrelated process that grabbed our ephemeral port after a daemon crash; the ping/pong response confirms the listener is actually our daemon.
- Per-daemon random token (secrets.token_hex(32)) gates every request on Windows. AF_UNIX + chmod 600 is the boundary on POSIX, but TCP loopback has no chmod-equivalent; without a token any local process could connect and issue CDP commands.
- Atomic .port write (write .port.tmp, os.replace) so a concurrent reader never sees a half-written file.
Adds rohitdutt108 to VOUCHED.td.
Co-authored-by: Rohit Dutt <rohit.dutt@iyc.ishafoundation.org>
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.
bug-report.yml: 4 required fields plus a 4-box preflight (searched issues, ran --doctor, read install.md, this is a bug not a question/FR/cloud issue). feature-request.yml: 3 required fields plus a 2-box preflight. config.yml disables blank issues and links to Discussions Q&A and install.md.
Same behavior matrix: HTTPError 404 with a ws_path returns the file's ws URL immediately; 404 without ws_path or any other error keeps polling until the 30s deadline. Removes the flag, the break, the post-loop check, and shrinks the explanatory comment.
Captures durable selectors and gotchas for exporting transcripts from
claude.ai/share/<uuid> URLs:
- [data-testid=user-message] for user turns, .font-claude-response for
assistant turns, [data-testid=page-header] for the chat title
- Container-walk pattern (no semantic wrapper — turns are alternating
siblings of a common ancestor)
- The H2.sr-only "Claude responded:" trap that pollutes innerText if you
select the outer turn div instead of .font-claude-response
- Auth requirement: share pages render the conversation only for
signed-in viewers; logged-out sessions get an empty app shell
Includes a working extract-share-transcript.py that emits both JSON and
LLM-friendly Markdown ({Human|Assistant} headers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cubic flagged that the original socket.create_connection probe matches any process on 9222/9223, not just Chrome. Mirror daemon.py's fallback by hitting /json/version, so a stale or unrelated listener does not skip the cloud bootstrap.
Drop the three boolean-table tests that mocked every collaborator and re-asserted the literal if-condition. Add a focused test for _local_chrome_listening that covers the false-positive case directly.
On headless servers (VPS, Docker) with no local Chrome, ensure_daemon() fires
before any user script runs and raises immediately — start_remote_daemon() can
never be reached from within a -c script.
Add a pre-check in main(): if no daemon is alive, Chrome is not listening on
known debugging ports (9222/9223), and BROWSER_USE_API_KEY is set, auto-
provision a Browser Use cloud browser before falling through to ensure_daemon().
_local_chrome_listening() probes ports 9222/9223 with a 0.3s timeout rather
than relying on _is_local_chrome_mode(), which only checks for absence of
BU_CDP_WS and would incorrectly trigger cloud bootstrap on a local machine
where Chrome is running but BROWSER_USE_API_KEY is also set (e.g. for profile
sync).
Fixes the behaviour reported in issues #181 and #183.
Tested on a headless Hostinger VPS running hermes-agent in Docker — browser-
harness -c '...' now works without any manual daemon setup when BROWSER_USE_API_KEY
is set.
Chrome 147+ silently disables /json/* HTTP discovery when remote debugging
runs on the default user-data-dir (IsUsingDefaultDataDirectory). The websocket
itself still works and DevToolsActivePort still holds the live ws path, but
get_ws_url() polls /json/version for 30s and dies with a misleading
"DevTools is not live yet" error.
PR #260 deliberately stopped trusting the path stored in DevToolsActivePort
because a stale port file (left behind after switching --user-data-dir on the
same port) had a dead UUID. That fix is correct, but it never falls back to
the file's UUID even when Chrome just wrote the file in the current run.
Read both port and ws path. Try /json/version first (preserves #260's
stale-UUID behavior). On HTTP 404 specifically — which is distinct from
connection-refused/timeout meaning "Chrome still starting" — fall back to
ws://127.0.0.1:{port}{ws_path} from the file. The file's UUID is fresh
because Chrome rewrites it on every launch, and the only persistent reason
/json/version 404s on a live port is the default-profile lockdown. If the WS
upgrade then 404s (true stale UUID), the existing CDP handshake error
surfaces unchanged.
Closes#264.
PR #247 added these under domain-skills/ at the repo root, but the
canonical location for domain skills is agent-workspace/domain-skills/
(referenced in README.md, SKILL.md, and helpers.py).
Chrome leaves DevToolsActivePort behind across runs. If the user previously
launched Chrome with one --user-data-dir on a debug port, then later launches
Chrome with a different --user-data-dir on the same port, the file in the
first profile keeps a stale browser UUID. The TCP probe to that port
succeeds against the new Chrome, but the WS upgrade returns HTTP 404
because the cached UUID is dead -- daemon dies with
'CDP WS handshake failed: HTTP 404'.
Resolve the WS URL via /json/version on the port from DevToolsActivePort
instead of trusting the path stored alongside it. /json/version is
authoritative for whatever Chrome is currently bound to that port, so
stale paths no longer matter. The 9222/9223 fallback below already used
this approach -- now both paths agree.
Reproduced on Windows 11 with one Chrome running on port 9222 with a custom
--user-data-dir while a stale DevToolsActivePort sat in
~/AppData/Local/Google/Chrome/User Data from a prior debug session.
Field-tested 2026-04-29. First batch covering Chinese hotel-booking sites:
- ly-com (同程旅行): hotels.md — prices gated behind login; Nuxt SSR
state holds metadata. Documents the ¥? placeholder pattern, Nuxt data
key randomization, login flow via passport.ly.com.
- ctrip (携程): hotels.md — anonymous price extraction works *only*
with the canonical 6-param URL schema. Documents the schema gate,
homepage-form fallback, phantom-¥ regex trap, no-href-on-cards
reality.
- wehotel (锦江): hotels.md — fully anonymous; covers all Jin Jiang
sub-brands (J酒店/锦江/维也纳/7天/IU etc). Documents the
inconsistent checkinDate vs checkInDate capitalization between list
and detail URLs.
All three include URL patterns, list/detail extraction JS, traps, and
quick-start snippets per the SKILL.md "durable site map" guidance.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR #229 (src layout refactor) updated SKILL.md and run.py to the -c form
when the CLI dropped stdin support, but missed install.md (steps 2 and 7)
and interaction-skills/profile-sync.md. Sync them up.
Picks up the still-relevant hunks from #215 (closes#215, refs #213).
Co-authored-by: Andres Gonzalez <62394570+FVTVLIX@users.noreply.github.com>
Rebased from #170 (authored by @mvanhorn) onto current main.
The original branch was based on a stale README; only the two
intentional lines from that PR are reapplied here.
When the browser dies but the daemon process keeps running, target_id and session stay cached on the Bridge. Pre-fix, connection_status swallowed the Target.getTargetInfo failure and returned the cached IDs with page=null, so admin counted the daemon as healthy. Now it returns {error: cdp_disconnected} and admin's existing error-check skips it. Also returns {error: not_attached} when target_id is unset.