7cb2a8b618
Testing / remote-protocol-compat (0.9.5) (push) Successful in 1m4s
Testing / test (push) Successful in 1m22s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 1m7s
Package Extension / package-extension (push) Successful in 1m1s
Build & Publish Package / publish (push) Successful in 1m5s
- 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.
71 lines
2.9 KiB
Python
71 lines
2.9 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 CommandPolicy, assert_command_allowed
|
|
from browser_cli.commands import 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")
|
|
@click.option("--allow-read-page", is_flag=True, help="Allow page-content read commands such as extract.* and dom.text")
|
|
@click.option("--allow-control", is_flag=True, help="Allow browser-control commands such as nav.*, tabs.close, dom.click")
|
|
@click.option("--allow-dangerous", is_flag=True, help="Allow high-risk commands such as dom.eval, storage.*, screenshots")
|
|
@handle_errors
|
|
def cmd_script(file: Path, json_output: bool, continue_on_error: bool, allow_read_page: bool, allow_control: bool, allow_dangerous: 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 = CommandPolicy(allow_read_page=allow_read_page, allow_control=allow_control, allow_dangerous=allow_dangerous)
|
|
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))
|