Two service-worker leaks. Running jobs were only bounded by their watchdog, so
a command flood could hold many timers and results for up to JOB_TIMEOUT_MS;
cap concurrent jobs at 32 and reject beyond that, since a clear error beats an
unresponsive worker. Window aliases were never removed, so storage kept an
entry for every window the user had ever renamed.
Prune aliases against the live window set on list, rename, and close rather
than only on close, because windows also disappear without going through
windows.close.
Also extract the repeated job summary in perf.status into a helper.
- Resolve bare remote hosts through the remote registry when one explicit port is stored.
- Preserve bare host behavior when no unique explicit-port registry match exists.
- Route requested remote targets through the new registry resolver.
- Add registry tests for unique and ambiguous explicit-port matches.
- Bump package and extension versions to 0.16.6.
- Render explicit --remote clients results with profile-only labels.
- Suppress remote host group headers for scoped client queries.
- Keep global and mixed client listings grouped by host.
- Update client and CLI tests for scoped remote rendering.
- Bump browser-cli and extension versions to 0.16.5.
- Add SSH-style server identity keys and known-host verification for remote serve endpoints.
- Add remote add/list/remove commands for explicit endpoint persistence.
- Fix remote clients listing to fan out through target discovery instead of ambiguous auto-routing.
- Add URL glob matching for tabs filter and count with extension tests.
- Add n8n credential pinning for server public keys or SHA256 fingerprints.
- Remove obsolete compat shim behavior while keeping empty compat seams for future protocol changes.
- Bump browser-cli to 0.16.4 and n8n node to 0.3.1.
- Cover known-hosts, remote registry, compat seams, n8n protocol verification, and URL matching with tests.
- Add the n8n community node package with credentials, command mapping, direct serve TCP client, and browser-cli protocol crypto helpers.
- Cover Ed25519 signing, canonical JSON, PQ transport encryption, request mapping, and security behavior with unit tests.
- Harden serve-http with per-address rate limiting, an 8 MB request body cap, and clear warnings when binding plain HTTP beyond loopback.
- Stop one-shot --key overrides from being persisted automatically; document explicit remote trust and keep key-management behind the keys policy tier.
- Make HTML-to-Markdown conversion safer by bounding tree depth and dropping unsafe link/image URL schemes.
- Bump package and extension release metadata to 0.16.3.
- Gate TCP serve commands with safe-by-default policies, per-key allow tokens, per-key rate limiting, and audit labels.
- Reuse authenticated encrypted remote sessions and parallelize/caches multi-browser fanout to reduce repeated handshake roundtrips.
- Increase paged native-host batch size with extension-side byte budgeting to speed large tab listings safely.
- Point install output at public Chrome Web Store / Firefox AMO listings by default, with --dev preserving unpacked workflows.
- Share search-engine metadata between CLI and SDK and bump the package/extension version to 0.16.0.
- Cover the new security, pooling, paging, install, and fanout behavior with expanded Python and extension tests.
- Add browser source grouping metadata to SDK-created tabs, groups,
list results, and aggregate count results.
- Render grouped local/remote browser tables consistently for clients,
tabs, groups, windows, sessions, and remote status output.
- Document remote control, auth, HTTP gateway usage, and the refreshed
project structure in the README.
- Add coverage for grouped output and BrowserCounts browser_groups.
- Bump the Python package, extension manifest, and lockfile to 0.15.6.
- Add a just publish helper for building and publishing release artifacts.
- Allow remote host aliases passed via --browser to fan out for read-only
multi-browser SDK paths while preserving strict routing for mutating commands.
- Add remote host grouping and scoped profile labels to tabs tree output so
global views avoid repeated host prefixes.
- Carry browser family metadata through remote targets, tabs, and groups and
style tree browser labels by family.
- Split CLI rendering helpers into a typed rendering package with dedicated
common, label, tabs-tree, and windows-tree modules.
- Bump browser-cli and extension versions to 0.15.5.
- Cover the new routing and rendering behavior with unit and CLI tests.
- Add helper scripts for Chrome Web Store verified CRX uploads using a dedicated RSA upload key protected by GPG.
- Document the verified upload packaging flow and ignore local signing secrets.
- Add npm packaging entry point for signed webstore CRX artifacts.
- Chunk large SDK tab close batches to avoid native-host response timeouts.
- Bump project and extension versions to 0.15.4 with matching tests.
- Split auth into focused package modules for agent keys, file keys,
signing, and post-quantum transport helpers while keeping the public
browser_cli.auth import surface intact.
- Move transport encoding internals into a package with separate codec and
binary-hoisting helpers, preserving browser_cli.transport compatibility.
- Extract remote TCP auth/socket helpers and serve challenge setup out of the
runtime paths to make connection handling easier to reason about.
- Move the extension markdown extractor into a dedicated content/markdown
folder with separate root selection, code normalization, renderer, and utils.
- Centralize CLI Rich rendering helpers for tab/window tree and table output,
and add rendering tests for the shared builders.
- Remove local typing ignores in SDK/decorator/script plumbing and bump the
package and extension version to 0.15.3.
- Add shared rendering helpers for width-aware tree labels, truncation, and no-wrap Rich text.
- Preserve tab index and group window metadata through the extension and SDK factories.
- Render tab trees in browser/window/index order with grouped tab details and optional shortened URLs.
- Reuse the tab tree labels in window trees to keep output compact and consistent.
- Cover legacy missing-index responses, grouped/collapsed tabs, URL display, and rendering helpers with tests.
- Add a neutral WebExtension API adapter that uses Firefox browser.* or Chromium chrome.* without mutating globals.
- Switch extension runtime code to the adapter and add Firefox-specific typings for tabs, windows, tab groups, storage, scripting, and native messaging ports.
- Fix Firefox temporary add-on instructions to load the packaged manifest with background.scripts instead of the Chromium service worker manifest.
- Detect Firefox in clients.list via runtime.getBrowserInfo and keep Chromium user-agent fallback support.
- Make navigate.open wait briefly for Firefox to replace initial about:blank with the requested URL.
- Add JS coverage for API selection, clients.list browser detection, and Firefox navigate.open URL polling.
- Bump package and extension version to 0.15.2.
- Add Firefox as an install target with native messaging manifest support.
- Generate Firefox-specific extension packages with Gecko metadata and AMO-compatible manifest transforms.
- Keep tab group commands available in Firefox through dynamic tab group API helpers.
- Avoid Firefox linter warnings for static tab group API references and direct eval tokens.
- Add Firefox packaging and installer regression coverage.
- Bump the package and extension version to 0.15.1.
- Rename the PyPI distribution from browser-cli to real-browser-cli after PyPI rejected the original name as too similar to an existing project.
- Keep the installed console command as browser-cli so user-facing CLI usage remains unchanged.
- Add README-based package metadata, author information, and project URLs so PyPI renders a proper project description.
- Centralize the PyPI distribution name for importlib.metadata version lookups used by the CLI, doctor command, and remote user agent.
- Document uv tool install, optional fast extra installation, and upgrade commands.
- Bump package and extension metadata to 0.14.3 for the republished release.
- Drop the ServiceLink submodule, link-serve command, and serve --rpc mode.
- Remove the now-unused httpx dependency and regenerate uv.lock.
- Add a privacy policy for Chrome Web Store publication.
- Refresh extension icons and bump package/extension version to 0.14.2.
- Keep the native TCP serve path as the only remote-control endpoint.
- 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)
Patch bump after the class-based extension refactor (ba01be1) — no
behavior, command, or API change.
Bumped in pyproject.toml, extension/manifest.json and uv.lock.
Verification: uv run browser-cli -V -> 0.10.3; uv run pytest -q -> 409
passed, 105 skipped.
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>
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>