6fa931aa36
Testing / remote-protocol-compat (0.9.5) (push) Successful in 56s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 59s
Testing / test (push) Successful in 1m1s
Build & Publish Package / publish (push) Successful in 33s
Package Extension / package-extension (push) Successful in 36s
- 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.
69 lines
2.7 KiB
Python
69 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any, cast
|
|
|
|
import click
|
|
from rich.console import Console
|
|
|
|
from browser_cli.command_security import assert_command_allowed
|
|
from browser_cli.commands import command_policy_from_options, command_policy_options, client_from_ctx, handle_errors
|
|
|
|
console = Console()
|
|
|
|
def _load_steps(path: Path):
|
|
text = path.read_text(encoding="utf-8")
|
|
if path.suffix.lower() in {".yaml", ".yml"}:
|
|
try:
|
|
yaml = cast(Any, importlib.import_module("yaml"))
|
|
except Exception as exc:
|
|
raise click.ClickException("YAML scripts require PyYAML; use JSON or install PyYAML") from exc
|
|
return yaml.safe_load(text)
|
|
return json.loads(text)
|
|
|
|
def _parse_step(step):
|
|
if isinstance(step, str):
|
|
return step, {}
|
|
if isinstance(step, dict):
|
|
if "command" in step:
|
|
return step["command"], step.get("args") or {}
|
|
if len(step) == 1:
|
|
command, args = next(iter(step.items()))
|
|
return command, args or {}
|
|
raise click.ClickException(f"Invalid script step: {step!r}")
|
|
|
|
@click.command("script")
|
|
@click.argument("file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
@click.option("--json", "json_output", is_flag=True, help="Print all step results as JSON")
|
|
@click.option("--continue-on-error", is_flag=True, help="Continue after failed steps")
|
|
@command_policy_options
|
|
@handle_errors
|
|
def cmd_script(file: Path, json_output: bool, continue_on_error: bool, allow_read_page: bool, allow_control: bool, allow_dangerous: bool, allow_keys: bool, allow_all: bool):
|
|
"""Run a JSON/YAML batch script of browser-cli wire commands."""
|
|
steps = _load_steps(file)
|
|
if not isinstance(steps, list):
|
|
raise click.ClickException("Script root must be a list")
|
|
client = client_from_ctx()
|
|
policy = command_policy_from_options(allow_read_page=allow_read_page, allow_control=allow_control, allow_dangerous=allow_dangerous, allow_keys=allow_keys, allow_all=allow_all)
|
|
results = []
|
|
for index, step in enumerate(steps, start=1):
|
|
command, args = _parse_step(step)
|
|
try:
|
|
assert_command_allowed(command, policy)
|
|
result = client.command(command, args)
|
|
results.append({"index": index, "command": command, "ok": True, "result": result})
|
|
if not json_output:
|
|
console.print(f"[green]✓[/green] {index}: {command}")
|
|
except Exception as exc:
|
|
results.append({"index": index, "command": command, "ok": False, "error": str(exc)})
|
|
if not continue_on_error:
|
|
if json_output:
|
|
click.echo(json.dumps(results, indent=2, default=str))
|
|
raise
|
|
if not json_output:
|
|
console.print(f"[red]✗[/red] {index}: {command}: {exc}")
|
|
if json_output:
|
|
click.echo(json.dumps(results, indent=2, default=str))
|