<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sz-chrome-remote-cdp.git/src/cdp/discovery.ts, branch master</title>
<subtitle>pi-browser-harness, patched to drive a Chrome on another host over CDP, with castle_cdp_search web search
</subtitle>
<id>https://git.soarez.net/sz/sz-chrome-remote-cdp.git/atom?h=master</id>
<link rel='self' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/'/>
<updated>2026-10-17T10:20:04+00:00</updated>
<entry>
<title>Start a local Chrome when the target is an explicit local and none answers</title>
<updated>2026-10-17T10:20:04+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-17T10:20:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=f61c43418d704cccb67132540ed7feceacef1d2a'/>
<id>urn:sha1:f61c43418d704cccb67132540ed7feceacef1d2a</id>
<content type='text'>
Dedicated profile (~/.chrome-cdp-profile), macOS only, never for the
castle default. BU_LOCAL_CHROME_LAUNCH=off disables it.
</content>
</entry>
<entry>
<title>fix(cdp): give the castle default 1.5s, not 5s, inside upstream's connect budget</title>
<updated>2026-10-15T21:25:41+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-15T21:25:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=08c0c3a8b4fe755f7fe65ea8d258d77971503882'/>
<id>urn:sha1:08c0c3a8b4fe755f7fe65ea8d258d77971503882</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(cdp): /browser-target to switch browsers without leaving pi</title>
<updated>2026-10-14T11:19:26+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-07-29T09:38:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=08e558753e8ef9b2aa96114981a5a1dd37357bad'/>
<id>urn:sha1:08e558753e8ef9b2aa96114981a5a1dd37357bad</id>
<content type='text'>
Switching between the shared browser and this machine's own previously meant
leaving pi and relaunching with BU_CDP_HTTP — and, less obviously, killing the
daemon first, or the switch silently did not happen.

  /browser-target                 show the current target
  /browser-target local           this machine's browser
  /browser-target default         the built-in default
  /browser-target host:port       somewhere else

Precedence becomes env &gt; stored &gt; default. The environment still wins so a
one-off `BU_CDP_HTTP=… pi` is never silently overridden by a choice made days
ago; when it is set, the command says so rather than pretending to obey.
Clearing falls back to the default rather than to local — "use the usual
browser", not "go local".

The daemon is the reason this needed more than a settings write. It is
machine-wide, resolves its target once per process, and holds the WebSocket to
the old browser, so re-reading the store would leave tool calls going to the
old browser while /browser-status reported the new one. The command therefore
replaces the process: the daemon now honours an inbound control/shutdown (the
action already existed in the protocol, previously only sent daemon→client),
and stopDaemon() asks for it and polls until the process is really gone before
ensureDaemon() respawns it against the new target. /browser-status now reads
the store too, so it cannot disagree with what the tools will do.

Persistence mirrors the profile pin: atomic temp-file-plus-rename, and every
read failure degrades to "nothing stored" so a corrupt file falls back to the
default instead of breaking browser control. It is a separate file from the
pin, so damage to one cannot cost you the other. The stored value stays in
BU_CDP_HTTP's vocabulary so there is exactly one resolution path.

62 tests: the full precedence table including the stored tier, that a bad value
is blamed on the source that set it rather than sending you hunting through
shell config, that the command only accepts values resolution can actually use,
and eight ways of damaging the store file.
</content>
</entry>
<entry>
<title>feat(cdp): default to the shared browser, and say so in /browser-status</title>
<updated>2026-10-14T11:17:59+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-07-28T21:08:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=95ff5e8eaefb35d79109f7ddfa1de5bf45a8daff'/>
<id>urn:sha1:95ff5e8eaefb35d79109f7ddfa1de5bf45a8daff</id>
<content type='text'>
Two follow-ups to BU_CDP_HTTP.

Default the endpoint. The remote host is now a built-in default
(DEFAULT_REMOTE_CDP), so nothing has to export BU_CDP_HTTP just to get the
usual setup. The variable becomes an override rather than a requirement.

Precedence is deliberately asymmetric, in src/cdp/endpoint.ts:

  BU_CDP_HTTP=host:port   that endpoint; unreachable is a hard error
  BU_CDP_HTTP=local|off   upstream behaviour, this machine's own browser
  unset                   the default, falling back to local when unreachable

An explicitly configured endpoint is an instruction, so failing to reach it must
not quietly automate a different browser - that would drive the operator's own
signed-in Chrome instead of the shared one. The built-in default is only a
preference, so a laptop away from the network still gets the local browser the
extension was originally written for.

Resolution is pure, with no I/O, so the status command can report the
configured intent without touching the network and the daemon can resolve the
same way in its own process.

/browser-status no longer reports a bare "Disconnected". The connection is lazy
- nothing dials the browser until the first browser_* tool runs - so a fresh
session always showed a red light that reads as a fault. It now distinguishes
"not connected yet, connects on first use" from "target unreachable", and names
the target with a reachability check, so the common confusion answers itself.

26 tests for the resolution table, covering the aliases, malformed values that
must be rejected rather than coerced, and that the default constant is itself
well-formed - if it ever stopped parsing, every machine would silently fall
back to local.
</content>
</entry>
<entry>
<title>feat(cdp): BU_CDP_HTTP to attach to a browser on another host</title>
<updated>2026-10-14T11:17:36+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-07-28T20:46:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=233f29aedf004699a97dbd9aee087e239f4a9e47'/>
<id>urn:sha1:233f29aedf004699a97dbd9aee087e239f4a9e47</id>
<content type='text'>
Upstream can only drive a browser on localhost. The pi extension always goes
through the daemon (src/index.ts uses createDaemonTransport), the daemon
resolves Chrome with discoverWsUrl() (src/daemon/bridge.ts), and that probe is
hardcoded to 127.0.0.1 (src/cdp/discovery.ts). BU_CDP_WS is read only by the
in-process client (src/client.ts), which that path never touches, so it has no
effect on the extension - the only workaround is relaying the remote browser
onto localhost first.

BU_CDP_HTTP="host:port" points at the remote CDP HTTP endpoint. It is resolved
inside discoverEndpoint(), so the daemon and the client both honour it.

Deliberately an HTTP endpoint rather than a WebSocket URL: it is re-resolved on
every discovery call, so the browser UUID is re-read after the remote browser
restarts. Chrome re-mints that UUID on every launch, so a pinned WebSocket URL
goes stale as soon as the remote browser is recycled.

Also skips performSetup's local-process precondition when BU_CDP_HTTP is set:
isBrowserRunning() enumerates local processes and can never see a remote
browser. Step 4 still fails loudly if it is unreachable.

No userDataDir is returned for remote endpoints, since the profile registry
lives on the remote host - profile enumeration degrades to the pre-profile
behaviour rather than reading the wrong machine's Local State.
</content>
</entry>
<entry>
<title>fix(daemon): stop importing typebox in the daemon process (#38)</title>
<updated>2026-10-11T18:03:25+00:00</updated>
<author>
<name>Aman Kumar</name>
<email>145339995+amankumarsingh77@users.noreply.github.com</email>
</author>
<published>2026-10-11T18:03:25+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=0fe64593bcc3379032840b35833ea39f23aa9322'/>
<id>urn:sha1:0fe64593bcc3379032840b35833ea39f23aa9322</id>
<content type='text'>
pi installs packages with dev and peer dependencies omitted and supplies typebox only to the
extension host. The daemon is a separate Node process whose import graph reached typebox via
src/cdp/discovery.ts, so from a pi-managed install it crashed with "Cannot find module 'typebox'"
before binding its socket. Parse /json/version with the package's own guards, and add a test that
keeps every package reachable from the daemon a runtime dependency.

Fixes #34

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;</content>
</entry>
<entry>
<title>fix: make --browser-debug-clicks and BU_CDP_WS actually work</title>
<updated>2026-08-02T13:42:46+00:00</updated>
<author>
<name>amankumarsingh77</name>
<email>amankumarsingh7702@gmail.com</email>
</author>
<published>2026-07-28T16:18:35+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=6d300099a86cfdb92b7869dc2b4f33094554b24c'/>
<id>urn:sha1:6d300099a86cfdb92b7869dc2b4f33094554b24c</id>
<content type='text'>
Both were documented and inert.

--browser-debug-clicks was registered as a flag and never read; the only
real switch was the undocumented BH_DEBUG_CLICKS env var. The flag now
feeds util/debug.ts, which the click tool consults, and the env var keeps
working as an equivalent.

BU_CDP_WS was read in the client and handed to transport.connect(), but
the daemon transport discards that argument and always dials the daemon
socket — and the daemon's own discovery never looked at the variable, so
setting it did nothing. The override now lives in discoverEndpoint, which
is what both processes go through, and the client's redundant read is
gone. The variable must be set before the daemon spawns.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>refactor: tidy the whitespace the comment removal left behind</title>
<updated>2026-08-02T13:42:46+00:00</updated>
<author>
<name>amankumarsingh77</name>
<email>amankumarsingh7702@gmail.com</email>
</author>
<published>2026-07-28T15:04:49+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=a9bdbd8590801cd9c2fa0cda6203948ac19a779c'/>
<id>urn:sha1:a9bdbd8590801cd9c2fa0cda6203948ac19a779c</id>
<content type='text'>
Drops leading blank lines where a file header was removed, collapses
double blank lines, and closes the empty catch/handler blocks whose only
content had been a comment. Whitespace only.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>refactor(cdp): strip comment noise</title>
<updated>2026-08-02T13:42:46+00:00</updated>
<author>
<name>amankumarsingh77</name>
<email>amankumarsingh7702@gmail.com</email>
</author>
<published>2026-07-28T14:55:38+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=330767d862458386e804bde240a1f0d7835b9b8e'/>
<id>urn:sha1:330767d862458386e804bde240a1f0d7835b9b8e</id>
<content type='text'>
Removes module banners, section dividers, and comments restating the code.
Keeps one-line notes for the window.open/browserContextId constraint, the
seed-tab claim, the unverifiable-pin reuse rule, the SeedTargetProvider
rationale, the deliberate dialog retention, the discovery-time browser
quirks, and the guarded CDP decoder assertion.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>refactor: close the remaining unguarded type assertions</title>
<updated>2026-08-02T13:42:46+00:00</updated>
<author>
<name>amankumarsingh77</name>
<email>amankumarsingh7702@gmail.com</email>
</author>
<published>2026-07-27T19:49:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=f9dcdf7f6dcdf2979693a9f179eda2b9995d57ae'/>
<id>urn:sha1:f9dcdf7f6dcdf2979693a9f179eda2b9995d57ae</id>
<content type='text'>
Types the event-queue iterator's TReturn as undefined so the terminal
result needs no cast; declares the isError field the pi runtime reads by
duck-typing; binds socket to a local const in the daemon transport;
replaces the @ts-ignore on the optional sharp import with a non-literal
specifier so src/ still typechecks where sharp was not installed.

Adds src/util/guards.ts as the single home for narrowing over unknown —
isRecord plus scalar/array readers — and converts the two private
isObject copies in cdp/types.ts and daemon/protocol.ts along with every
inline `as Record&lt;string, unknown&gt;` in domains/js.ts and domains/render.ts.

Gives Accessibility.getFullAXTree a real result schema, closing the last
unvalidated CDP boundary: only nodeId is required, every object carries
additionalProperties, and each AX value stays Unknown, so the tree
decodes as a whole or fails as a whole rather than being filtered.

Guards the five tool-result `details` readers, the daemon attach
response, the persisted session state, the browser-namespace flag, and
the fs errno narrowing, each feeding an already-present failure path.

The daemon-transport socket binding is not a bug fix: send() runs
synchronously inside sendWithTimeout's promise executor, so socket cannot
be reassigned between the check and the write. It removes the assertion
and makes the write immune to a later refactor that adds an await.

Two assertions survive by design and are documented in the task report:
the never-&gt;unknown re-widening in util/tool.ts, which the type-erased
tool registry makes unavoidable, and the AsyncFunction constructor lookup
in domains/js.ts, which JavaScript exposes no typed binding for.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
