- Add safe-by-default policy gates for raw command surfaces: command, script, and serve-http /command.
- Require explicit opt-ins for page reads, browser control, and high-risk commands such as dom.eval, storage.*, and screenshots.
- Remove all cookies support from CLI, SDK, extension commands, permissions, constants, docs, and tests.
- Add diagnostic, events, watch, workspace, remote, raw command, script, HTTP gateway, tree-view, session import/export, and extension info/capability commands.
- Add Chrome Web Store packaging that strips manifest.key while keeping local packages with a stable native-messaging extension ID.
- Bump browser-cli and extension version to 0.14.1 and cover the new behavior with pytest and extension packaging tests.
BREAKING CHANGE: cookies commands and the b.cookies SDK namespace have been removed; generic raw command execution now blocks non-safe commands unless explicitly allowed.
- Change nav open and open-wait to avoid activating newly created tabs unless
--focus is explicitly requested.
- Send background=true for default opens so older or remote extensions also
avoid stealing focus even if they ignore the new focus flag.
- Remove the redundant --bg flag from navigation and search CLI commands now
that no-focus/background behavior is the default.
- Thread focus support through the sync SDK, async SDK, tab helpers, and
workflow decorators.
- Update README and demo usage to document the new default and --focus opt-in.
- Bump package and extension metadata to 0.12.3.
- Add regression coverage for CLI help, wire payloads, and extension behavior.
- Respect the globally selected browser when renaming client aliases.
- Pass the resolved local profile into sync and async local transports so
BROWSER_CLI_PROFILE is honored consistently.
- Stop tabs.active from explicitly focusing the OS browser window, avoiding
virtual-desktop jumps during tab activation.
- Make window merging skip audible, unmuted windows so video playback windows
are not selected as merge targets.
- Bump the Python package and extension manifest versions to 0.12.2.
- Add regression coverage for browser selection and focus-stealing behavior.
- Split client, native, remote, serve, markdown, and SDK internals into focused packages with direct imports.
- Move local and remote transport framing/protocol helpers behind clearer module boundaries.
- Break up the extension injected DOM logic into a separate content dispatch bundle and dedicated content modules.
- Add explicit client handling for passive remote discovery without noisy PQ warnings.
- Keep behavior covered with updated unit, integration, and extension tests.
Restructure the Python API and internals around composable namespaces and
a standalone transport/endpoint layer. Bump to 0.12.0.
Python API:
- Replace flat methods (b.tabs_list(), b.group_list()) with namespaces:
b.nav, b.tabs, b.groups, b.windows, b.dom, b.extract, b.page, b.storage,
b.cookies, b.session, b.perf, b.extension.
- Shrink browser_cli/__init__.py to a thin composition root; move all
behaviour into browser_cli/sdk/ (one module per namespace + factories,
base, routing).
Internals:
- Add browser_cli/transport.py and remote_transport.py to isolate IPC from
command logic; client.py now delegates instead of owning transport.
- Add browser_cli/endpoints.py for endpoint resolution and
browser_cli/errors.py for shared error types.
- Extract markdown rendering into browser_cli/markdown.py (out of extract).
- Add USER_AGENT to version_manager.
Tooling & tests:
- Add justfile with common dev tasks.
- Update CLI commands and demo to the namespaced API.
- Rework tests for the new layout; add test_transport.py and
test_refactor_boundaries.py to lock in module boundaries.
BREAKING CHANGE: flat API methods are removed in favour of namespaces
(e.g. b.tabs_list() -> b.tabs.list(), b.group_list() -> b.groups.list()).
Closing many tabs previously meant one IPC round-trip per tab
(tab.close() in a loop). Add a single batched path so callers can
close N tabs in one command, reusing the existing large-operation
throttle so the browser UI stays responsive.
- extension: tabs.close accepts tabIds: number[]; new branch feeds
the array through processInBatches/chrome.tabs.remove
- sdk: tabs_close(tab_ids=...) takes tab IDs or Tab objects; the
payload always carries "tabIds" (null when unused)
- tests: cover id-list and Tab-object batch close in test_api.py
- bump 0.10.3 -> 0.10.4 (pyproject.toml, manifest.json)
Restructure the MV3 background worker from a monolithic core.ts/index.ts
into a class-based command architecture. Behavior is identical — the 83
registered commands dispatch byte-for-byte the same as before.
Structure
- One class per command group, each extending CommandGroup and exporting a
`commands` map keyed by the full command id ("tabs.close"). Groups:
Navigation, TabsMutation, TabsQuery, Groups, Windows, Dom (dom/extract/
page), BrowserData (storage/cookies), Session (session/clients + autosave
+ lazy-tab activation), Perf (perf + jobs.status/cancel), Extension.
- CommandRegistry merges the group maps (throws on duplicate ids), routes
background specs to JobManager and paginates array results via
makePagedData. JobManager owns the job map + lifecycle. NativeConnection
owns the native-port lifecycle and the inbound message router.
- index.ts is now thin wiring: JobManager -> ctx -> assembleRegistry ->
onActivated -> NativeConnection.start().
- Infra classes live in classes/ (PascalCase, file = class name); command
groups in commands/; shared helpers split out of core.ts into core/
(errors, throttle, scripting, tab-helpers, group-helpers, storage); all
types moved into types/ (json, jobs, session, tabs, messages,
command-args) behind a barrel.
DRY cleanup
- resolveTabUrl(tabId) and assertScriptableUrl(url, action) collapse the
tab/URL-guard boilerplate duplicated across dom.ts and browser-data.ts.
- processInBatches() centralizes the throttled, cancellable batch loop
shared by tabs.close, group.close and tabs.merge_windows.
- captureCurrentSession() dedups the snapshot-and-signature block shared by
session.save and the autosave path.
- DomArgs type alias replaces 21 inline ContentArgs & { tabId? } copies.
- Drop fetchTabHtml's redundant retry loop (executeScript already retries
transient frame/tab errors), a dead tabInfo import, and two stale
comments referencing a removed asArgs helper.
Type safety & tests
- Full noImplicitAny; no `any`/`unknown` annotations remain in src.
- JS unit-test harness using node --test + node:assert (zero new deps),
bundled via the existing esbuild. Covers JobManager retention/lifecycle
and the autosave listener-wiring/debounce with an in-memory chrome mock.
- The structural pytest checks track the new file homes and the centralized
processInBatches helper.
Verification: npm run check:extension green (tsc + esbuild 84.5kb +
node --check + 18 JS tests); uv run pytest -q -> 409 passed, 105 skipped.
No version bump.
- Check Unix socket reachability with a real connection attempt instead of treating any existing path as active.
- Report ambiguous host-only remote aliases with actionable --remote/--browser examples.
- Update client tests to use listening Unix sockets and cover ambiguous remote alias errors.
- Bump package and extension versions to 0.10.2.
- Avoid resolving a saved remote alias when the requested profile is currently reachable as a local endpoint.
- Add a helper that checks the registry and local socket path before remote alias discovery.
- Cover the routing precedence with a client unit test.
- Bump package and extension versions to 0.10.1.
- Add throttled large-operation handling for tab, group, and session commands.
- Introduce performance profiles, audible-tab aware gentle mode, and job progress tracking.
- Support background session restores with status/cancel commands and lazy placeholders.
- Expose new perf and extension CLI groups plus matching Python SDK methods.
- Preserve pinned tabs during session snapshots and debounce auto-save updates.
- Bump browser-cli and extension versions to 0.10.0 and add pytest-cov to dev deps.
- Add coverage for performance controls, background jobs, lazy restores, and tab metadata.
- Position browser-cli as a CLI plus Python SDK in docs and package metadata.
- Add public target properties and a raw command escape hatch for unsupported commands.
- Add convenience helpers for opening, finding, closing, and accessing tabs.
- Add plural group aliases and a wait_for_selector DOM convenience alias.
- Extend bound Tab objects with screenshot, pin, refresh, load wait, and URL watch helpers.
- Preserve remote auth key configuration when binding remote Tab and Group objects.
- Bump project and extension versions to 0.9.9 and cover SDK additions with tests.
- Add shared browser error URL detection for Chrome, Edge, Brave, and Firefox-style about:error pages.
- Short-circuit read-only DOM and HTML commands with safe fallbacks when tabs are already on browser error pages.
- Fail navigation waits, DOM waits, polling, and URL watches with clearer error-page messages.
- Bump package and extension version to 0.9.8 and extend regression coverage for cross-browser error-page handling.
- Treat chrome error page script failures as transient during injection retries.
- Return safe fallback values for read-only DOM commands when tabs land on browser error pages.
- Improve URL watch handling by checking pending URLs and reporting last seen URL/status on timeout.
- Bump package and extension version to 0.9.6 and add regression coverage for error-page behavior.
- Domain-like --remote endpoints default to port 443; :443 is optional
- _normalize_endpoint strips :443 before storage in remotes.json
- _load_remotes normalises keys on load (backward compat migration)
- _remote_display_name omits :443 for domain endpoints
- _resolve_connect_endpoint adds :443 back for TCP connection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- compat.py → compat/ package: auth.py (auth-field normalizers),
commands.py (command-format shims), __init__.py (re-exports)
- Add _auth_0_9_3 transformer: normalizes pubkey to lowercase before auth
so clients < 0.9.3 sending uppercase hex are accepted
- adapt_auth() now called before auth check in serve.py; command extracted
after adapt_auth so future transformers can rename commands safely
- serve.py: deduplicate _recv_exact (import from client), unify
resp/resp_payload across Windows/Unix branches, require lowercase hex
pubkey (re.fullmatch), reorganize imports, drop unused os import
- client.py: move payload/framed construction inside branches (remote path
no longer serializes JSON it never uses); fix _is_valid_key_spec
operator precedence; import MAX_MSG_BYTES from version_manager
- auth.py: narrow except clause (ValueError instead of bare Exception)
- Bump version 0.9.2 → 0.9.3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- remote_browser_targets(), _auto_route_remote(), active_browser_targets()
now accept and forward the key parameter so pubkey auth works during
the initial browser-cli.targets discovery call
- _multi_browser_targets() in tabs/groups/windows/session commands now
reads key from ctx.obj and passes it through
- send_command() auto-saves the key spec (e.g. "agent") to remotes.json
on first explicit use; subsequent calls to the same remote reuse it
without requiring --key every time
- Added save_remote_key() / key_for_remote() helpers (mirrors token helpers)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Security:
- serve.py: server now sends nonce challenge before accepting any command;
clients sign nonce + SHA256(canonical_payload) with Ed25519 key
- New --authorized-keys FILE option for serve; token auth still works as fallback
- Connection limit: BoundedSemaphore(64) in serve.py
- Secure file creation with os.open(..., 0o600) for token/key files
- New auth.py module: keygen, file key load/save, SSH agent protocol (pure Python),
sign/verify helpers compatible with both file keys and agent-held keys (YubiKey,
TPM, gpg-agent)
Features:
- YubiKey support via SSH agent protocol — no new runtime deps, just $SSH_AUTH_SOCK
- New `browser-cli auth` command group: keygen, trust, show, keys
- Global --key PATH flag (or BROWSER_CLI_KEY env) selects signing key;
pass "agent" or "agent:<selector>" to use SSH agent key
- BrowserCLI Python API gains key= parameter
Bug fixes (11 issues across two review passes):
- client.py: check response is not None before json.loads
- native_host.py: _read_exact_stream loop handles EINTR short reads; fix Windows
Listener leak on accept error
- __init__.py: open_wait / tabs_watch_url raise RuntimeError instead of silent None
- extension/tabs.ts: dedupe skips tabs without URL; tabsSort uses pendingUrl fallback
- extension/session.ts: removeListener before addListener prevents duplicate handlers
Breaking: TCP serve protocol now sends a challenge frame first (v0.9.0)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exposes a local browser over a TCP socket so remote machines can
control it using the same CLI and Python API. Token auth (auto-generated
via secrets.token_urlsafe) is on by default; --no-auth disables it.
Profile routing via _route message field lets clients target specific
browser instances on the remote host. BROWSER_CLI_PROFILE is forwarded
automatically so --browser flag works transparently over remote.
- browser-cli serve [--host] [--port] [--token] [--no-auth]
- browser-cli --remote HOST:PORT --token TOKEN <command>
- BrowserCLI(remote="host:port", token="...").tabs_list()