9b8cefcd72
- 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.
138 lines
5.4 KiB
Python
138 lines
5.4 KiB
Python
"""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,
|
|
FIREFOX_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",
|
|
"firefox": "about:debugging#/runtime/this-firefox",
|
|
}[browser]
|
|
console.print("\n[bold]Step 1:[/bold] Load the extension in your browser")
|
|
console.print(f" 1. Open [cyan]{ext_url}[/cyan]")
|
|
if browser == "firefox":
|
|
console.print(" 2. Click [bold]Load Temporary Add-on...[/bold]")
|
|
console.print(f" 3. Select: [cyan]{Path(__file__).parent.parent.parent / 'extension' / 'manifest.json'}[/cyan]")
|
|
console.print(f" 4. Firefox extension ID is [cyan]{FIREFOX_EXTENSION_ID}[/cyan]")
|
|
console.print(" Note: Firefox support is experimental; tab-group commands require browser tab group APIs.\n")
|
|
else:
|
|
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 = _native_host_manifest(browser, host_exe)
|
|
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 _native_host_manifest(browser: str, host_exe: Path) -> dict:
|
|
base = {
|
|
"name": NATIVE_HOST_NAME,
|
|
"description": "browser-cli native messaging host",
|
|
"path": str(host_exe),
|
|
"type": "stdio",
|
|
}
|
|
if browser == "firefox":
|
|
return {**base, "allowed_extensions": [FIREFOX_EXTENSION_ID]}
|
|
return {
|
|
**base,
|
|
"allowed_origins": [f"chrome-extension://{extension_id}/" for extension_id in ALLOWED_EXTENSION_IDS],
|
|
}
|
|
|
|
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
|