#!/usr/bin/env -S uv run """ browser-cli — Control your running browser from the terminal. """ import click import os import shutil import re from importlib.metadata import PackageNotFoundError, version as package_version from pathlib import Path from rich.console import Console from browser_cli.commands.navigate import nav_group from browser_cli.commands.tabs import tabs_group from browser_cli.commands.groups import group_group from browser_cli.commands.windows import windows_group from browser_cli.commands.dom import dom_group from browser_cli.commands.extract import extract_group from browser_cli.commands.session import session_group from browser_cli.commands.search import search_group from browser_cli.commands.page import page_group from browser_cli.commands.storage import storage_group from browser_cli.commands.cookies import cookies_group from browser_cli.commands.perf import perf_group from browser_cli.commands.extension import extension_group from browser_cli.commands.serve import cmd_serve from browser_cli.commands.link_serve import cmd_link_serve from browser_cli.commands.auth import auth_group from browser_cli.commands.clients import clients_group from browser_cli.commands.completion import cmd_completion from browser_cli.commands.install import cmd_install console = Console() # Click's Group.shell_complete hardcodes no limit for get_short_help_str (defaults to 45 chars); # patch to use a wider limit so zsh completion descriptions aren't truncated. def _patched_group_shell_complete(self, ctx, incomplete): from click.shell_completion import CompletionItem results = [ CompletionItem(name, help=command.get_short_help_str(limit=shutil.get_terminal_size().columns)) for name, command in self.commands.items() if not command.hidden and name.startswith(incomplete) ] results.extend(click.Command.shell_complete(self, ctx, incomplete)) return results click.Group.shell_complete = _patched_group_shell_complete def _project_version() -> str: pyproject_path = Path(__file__).resolve().parent.parent / "pyproject.toml" try: content = pyproject_path.read_text(encoding="utf-8") match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE) if match: return match.group(1) except OSError: pass try: return package_version("browser-cli") except PackageNotFoundError: return "unknown" def _print_version(ctx, param, value): if not value or ctx.resilient_parsing: return click.echo(_project_version()) ctx.exit() @click.group() @click.option( "-V", "--version", is_flag=True, is_eager=True, expose_value=False, callback=_print_version, help="Show the browser-cli version and exit.", ) @click.option( "--browser", default=None, metavar="ALIAS", help="Browser profile alias to target (required when multiple browsers are active).", ) @click.option( "--remote", default=None, metavar="HOST[:PORT]", help="Connect to a remote browser exposed via 'browser-cli serve'. Domains default to port 443.", ) @click.option( "--key", default=None, metavar="PATH", help="Ed25519 private key PEM for pubkey auth with a remote serve instance.", ) @click.pass_context def main(ctx, browser, remote, key): """Control your running browser from the terminal via a Chrome extension.""" ctx.ensure_object(dict) ctx.obj["browser"] = browser ctx.obj["browser_explicit"] = browser is not None if browser: os.environ["BROWSER_CLI_PROFILE"] = browser ctx.call_on_close(lambda: os.environ.pop("BROWSER_CLI_PROFILE", None)) ctx.obj["remote"] = remote ctx.obj["key"] = key if remote: os.environ["BROWSER_CLI_REMOTE"] = remote ctx.call_on_close(lambda: os.environ.pop("BROWSER_CLI_REMOTE", None)) if key: os.environ["BROWSER_CLI_KEY"] = key ctx.call_on_close(lambda: os.environ.pop("BROWSER_CLI_KEY", None)) # ── Sub-command groups ───────────────────────────────────────────────────────── main.add_command(auth_group) main.add_command(nav_group) main.add_command(tabs_group) main.add_command(group_group) main.add_command(windows_group) main.add_command(dom_group) main.add_command(extract_group) main.add_command(session_group) main.add_command(search_group) main.add_command(page_group) main.add_command(storage_group) main.add_command(cookies_group) main.add_command(perf_group) main.add_command(extension_group) main.add_command(cmd_serve) main.add_command(cmd_link_serve) main.add_command(clients_group) main.add_command(cmd_completion) main.add_command(cmd_install) # ── native-host (hidden, called by Chrome via native messaging) ──────────────── @main.command("native-host", hidden=True) def cmd_native_host(): """Native messaging host — called by Chrome, not for direct use.""" from browser_cli.native.host import main as _main _main() if __name__ == "__main__": main()