from __future__ import annotations import json from pathlib import Path import click from rich.console import Console from rich.table import Table from browser_cli.commands import client_from_ctx, handle_errors from browser_cli.constants import CONFIG_DIR console = Console() WORKSPACES_PATH = CONFIG_DIR / "workspaces.json" def _load() -> dict: try: return json.loads(WORKSPACES_PATH.read_text(encoding="utf-8")) except FileNotFoundError: return {} def _save(data: dict) -> None: WORKSPACES_PATH.parent.mkdir(parents=True, exist_ok=True) WORKSPACES_PATH.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") @click.group("workspace") def workspace_group(): """Named browser workspaces built on top of sessions.""" @workspace_group.command("save") @click.argument("name") @click.option("--session", "session_name", default=None, help="Session name to save/use (default: workspace name)") @click.option("--profile", default=None, help="Performance profile to remember") @handle_errors def workspace_save(name, session_name, profile): session_name = session_name or name result = client_from_ctx().session.save(session_name) data = _load() data[name] = {"session": session_name, "profile": profile} _save(data) console.print(f"[green]Workspace '{name}' saved[/green] ({result.get('tabs', 0) if isinstance(result, dict) else 0} tabs)") @workspace_group.command("load") @click.argument("name") @click.option("--lazy", is_flag=True, help="Lazy-restore tabs") @click.option("--eager-tabs", type=int, default=10, show_default=True) @handle_errors def workspace_load(name, lazy, eager_tabs): data = _load() ws = data.get(name) if not ws: raise click.ClickException(f"Workspace '{name}' not found") client = client_from_ctx() if ws.get("profile"): client.perf.set_profile(ws["profile"]) result = client.session.load(ws["session"], lazy=lazy, eager_tabs=eager_tabs) console.print(f"[green]Workspace '{name}' loaded[/green] ({result.get('tabs', 0) if isinstance(result, dict) else 0} tabs)") @workspace_group.command("switch") @click.argument("name") @click.option("--lazy", is_flag=True) @handle_errors def workspace_switch(name, lazy): """Load a workspace. Alias for workspace load.""" ctx = click.get_current_context() ctx.invoke(workspace_load, name=name, lazy=lazy, eager_tabs=10) @workspace_group.command("list") def workspace_list(): """List configured workspaces.""" data = _load() if not data: console.print("[yellow]No workspaces[/yellow]") return table = Table(show_header=True, header_style="bold cyan") table.add_column("Name") table.add_column("Session") table.add_column("Profile") for name, ws in sorted(data.items()): table.add_row(name, ws.get("session", ""), ws.get("profile") or "") console.print(table) @workspace_group.command("remove") @click.argument("name") def workspace_remove(name): data = _load() if name not in data: raise click.ClickException(f"Workspace '{name}' not found") del data[name] _save(data) console.print(f"[green]Workspace '{name}' removed[/green]")