feat!: harden raw browser control and packaging
Testing / remote-protocol-compat (0.9.3) (push) Successful in 40s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 38s
Testing / test (push) Failing after 1m3s
Package Extension / package-extension (push) Successful in 29s
Build & Publish Package / publish (push) Successful in 33s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 40s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 38s
Testing / test (push) Failing after 1m3s
Package Extension / package-extension (push) Successful in 29s
Build & Publish Package / publish (push) Successful in 33s
- Add safe-by-default policy gates for raw command surfaces: command, script, and serve-http /command. - Require explicit opt-ins for page reads, browser control, and high-risk commands such as dom.eval, storage.*, and screenshots. - Remove all cookies support from CLI, SDK, extension commands, permissions, constants, docs, and tests. - Add diagnostic, events, watch, workspace, remote, raw command, script, HTTP gateway, tree-view, session import/export, and extension info/capability commands. - Add Chrome Web Store packaging that strips manifest.key while keeping local packages with a stable native-messaging extension ID. - Bump browser-cli and extension version to 0.14.1 and cover the new behavior with pytest and extension packaging tests. BREAKING CHANGE: cookies commands and the b.cookies SDK namespace have been removed; generic raw command execution now blocks non-safe commands unless explicitly allowed.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from dataclasses import asdict, is_dataclass
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
|
||||
from browser_cli.commands import client_from_ctx, handle_errors
|
||||
|
||||
console = Console()
|
||||
|
||||
def _snapshot(client):
|
||||
tabs = client.tabs.list()
|
||||
return {str(t.id): asdict(t) if is_dataclass(t) else dict(t) for t in tabs}
|
||||
|
||||
def _emit(event, json_output: bool):
|
||||
if json_output:
|
||||
click.echo(json.dumps(event, default=str), flush=True)
|
||||
else:
|
||||
kind = event.get("type")
|
||||
tab = event.get("tab") or {}
|
||||
console.print(f"[cyan]{kind}[/cyan] {tab.get('id', '')} {tab.get('title') or ''} [dim]{tab.get('url') or ''}[/dim]")
|
||||
|
||||
@click.command("events")
|
||||
@click.option("--interval", type=float, default=1.0, show_default=True, help="Polling interval in seconds")
|
||||
@click.option("--once", is_flag=True, help="Emit initial snapshot and exit")
|
||||
@click.option("--json", "json_output", is_flag=True, default=True, help="Emit JSON Lines (default)")
|
||||
@click.option("--pretty", is_flag=True, help="Render human-readable events instead of JSON")
|
||||
@handle_errors
|
||||
def cmd_events(interval: float, once: bool, json_output: bool, pretty: bool):
|
||||
"""Stream tab events as JSON Lines using a lightweight polling watcher."""
|
||||
json_output = json_output and not pretty
|
||||
client = client_from_ctx()
|
||||
previous = _snapshot(client)
|
||||
for tab in previous.values():
|
||||
_emit({"type": "tabs.snapshot", "tab": tab}, json_output)
|
||||
if once:
|
||||
return
|
||||
while True:
|
||||
time.sleep(interval)
|
||||
current = _snapshot(client)
|
||||
for tab_id, tab in current.items():
|
||||
if tab_id not in previous:
|
||||
_emit({"type": "tabs.created", "tab": tab}, json_output)
|
||||
elif tab != previous[tab_id]:
|
||||
_emit({"type": "tabs.updated", "tab": tab, "previous": previous[tab_id]}, json_output)
|
||||
for tab_id, tab in previous.items():
|
||||
if tab_id not in current:
|
||||
_emit({"type": "tabs.closed", "tab": tab}, json_output)
|
||||
previous = current
|
||||
Reference in New Issue
Block a user