"""Native Messaging host installation command.""" from __future__ import annotations import json import sys from pathlib import Path import click from rich.console import Console from browser_cli.constants import ( ALLOWED_EXTENSION_IDS, EXTENSION_ID, NATIVE_HOST_DIRS, NATIVE_HOST_NAME, SUPPORTED_BROWSERS, WEBSTORE_EXTENSION_ID, WINDOWS_NATIVE_HOST_REGISTRY_KEYS, ) from browser_cli.platform import install_base_dir, is_windows console = Console() def native_host_exe() -> Path: base = install_base_dir() if is_windows(): return base / "libexec" / "browser-cli-native-host.cmd" return base / "libexec" / "browser-cli-native-host" def write_native_host_exe(path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) if is_windows(): path.write_text( f'@echo off\r\n"{sys.executable}" -c "from browser_cli.native.host import main; main()" %*\r\n', encoding="utf-8", ) else: path.write_text(f'#!{sys.executable}\nfrom browser_cli.native.host import main\nmain()\n') path.chmod(path.stat().st_mode | 0o111) def _windows_registry_views(): import winreg return [0, getattr(winreg, "KEY_WOW64_32KEY", 0), getattr(winreg, "KEY_WOW64_64KEY", 0)] def _register_windows_native_host(browser: str, manifest_path: Path) -> list[str]: import winreg installed = [] for key_path in WINDOWS_NATIVE_HOST_REGISTRY_KEYS[browser]: full_key = f"{key_path}\\{NATIVE_HOST_NAME}" for view in _windows_registry_views(): try: access = winreg.KEY_WRITE | view key = winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER, full_key, 0, access) with key: winreg.SetValueEx(key, "", 0, winreg.REG_SZ, str(manifest_path)) installed.append(f"HKCU\\{full_key}") except OSError as e: console.print(f"[yellow]Could not write registry key {full_key}: {e}[/yellow]") return installed @click.command("install") @click.argument("browser", type=click.Choice(SUPPORTED_BROWSERS), default="chrome") def cmd_install(browser): """Register the native messaging host and print extension load instructions.""" host_exe = native_host_exe() write_native_host_exe(host_exe) ext_url = { "chrome": "chrome://extensions", "chromium": "chrome://extensions", "brave": "brave://extensions", "edge": "edge://extensions", "vivaldi": "vivaldi://extensions", }[browser] console.print("\n[bold]Step 1:[/bold] Load the extension in your browser") console.print(f" 1. Open [cyan]{ext_url}[/cyan]") console.print(" 2. Enable [bold]Developer mode[/bold] (top-right toggle)") console.print(f" 3. Click [bold]Load unpacked[/bold] → select: [cyan]{Path(__file__).parent.parent.parent / 'extension'}[/cyan]") console.print(f" 4. Testing extension ID will be [cyan]{EXTENSION_ID}[/cyan] (fixed by built-in key)") console.print(f" Chrome Web Store extension ID is [cyan]{WEBSTORE_EXTENSION_ID}[/cyan]\n") manifest = { "name": NATIVE_HOST_NAME, "description": "browser-cli native messaging host", "path": str(host_exe), "type": "stdio", "allowed_origins": [f"chrome-extension://{extension_id}/" for extension_id in ALLOWED_EXTENSION_IDS], } installed = _install_manifest(browser, host_exe, manifest) if not installed: console.print("[red]Failed to install native host manifest[/red]") sys.exit(1) for p in installed: label = "Registered native host" if is_windows() else "Wrote native host manifest" console.print(f"[green]✓[/green] {label}: {p}") console.print(f"[green]✓[/green] Installed native host: {host_exe}") console.print(f"\n[bold]Step 2:[/bold] Restart {browser.capitalize()} completely (quit app, then reopen)") console.print("\n[green bold]✓ Installation complete![/green bold]") console.print(" After restarting the browser, try: [cyan]browser-cli tabs list[/cyan]") def _install_manifest(browser: str, host_exe: Path, manifest: dict) -> list: if is_windows(): manifest_dir = host_exe.parent manifest_dir.mkdir(parents=True, exist_ok=True) manifest_path = manifest_dir / f"{NATIVE_HOST_NAME}.json" manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") return _register_windows_native_host(browser, manifest_path) platform = "darwin" if sys.platform == "darwin" else "linux" installed = [] for directory in NATIVE_HOST_DIRS[browser][platform]: try: directory.mkdir(parents=True, exist_ok=True) manifest_path = directory / f"{NATIVE_HOST_NAME}.json" manifest_path.write_text(json.dumps(manifest, indent=2)) installed.append(manifest_path) except Exception as e: console.print(f"[yellow]Could not write to {directory}: {e}[/yellow]") return installed