add multi browser mode to arragate data from all browsers by tabs list, tabs count, group list, group count and windows list
remove (unnamed) into the group names just leave it a empty string, remove Focused on windows how should the browser know what windows are focused
This commit is contained in:
+95
-12
@@ -17,11 +17,19 @@ Usage:
|
||||
b = BrowserCLI(browser="brave")
|
||||
"""
|
||||
from collections.abc import Callable, Iterable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from browser_cli.client import BrowserNotConnected, send_command
|
||||
from browser_cli.client import BrowserNotConnected, active_browser_targets, send_command
|
||||
from browser_cli.models import Group, Tab
|
||||
|
||||
__all__ = ["BrowserCLI", "BrowserNotConnected", "Tab", "Group"]
|
||||
__all__ = ["BrowserCLI", "BrowserCounts", "BrowserNotConnected", "Tab", "Group"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BrowserCounts:
|
||||
"""Aggregated per-browser counts returned in implicit multi-browser mode."""
|
||||
total: int
|
||||
by_browser: dict[str, int]
|
||||
|
||||
|
||||
class BrowserCLI:
|
||||
@@ -36,9 +44,35 @@ class BrowserCLI:
|
||||
def _cmd(self, command: str, args: dict | None = None):
|
||||
return send_command(command, args, profile=self._browser)
|
||||
|
||||
def _multi_browser_targets(self):
|
||||
if self._browser is not None:
|
||||
return []
|
||||
targets = active_browser_targets()
|
||||
if len(targets) <= 1:
|
||||
return []
|
||||
return targets
|
||||
|
||||
def _collect_multi_browser(self, command: str, args: dict | None = None):
|
||||
results = []
|
||||
for target in self._multi_browser_targets():
|
||||
try:
|
||||
data = send_command(command, args, profile=target.profile)
|
||||
except (BrowserNotConnected, RuntimeError):
|
||||
continue
|
||||
results.append((target, data))
|
||||
if results:
|
||||
return results
|
||||
if self._multi_browser_targets():
|
||||
raise BrowserNotConnected(
|
||||
"Cannot resolve a browser socket automatically.\n"
|
||||
"Make sure the browser is running with the browser-cli extension enabled,\n"
|
||||
"or pass --browser <alias> / set BROWSER_CLI_PROFILE to a known alias."
|
||||
)
|
||||
return []
|
||||
|
||||
# ── Internal factories ────────────────────────────────────────────────
|
||||
|
||||
def _make_tab(self, data: dict) -> Tab:
|
||||
def _make_tab(self, data: dict, *, browser_profile: str | None = None, browser_name: str | None = None) -> Tab:
|
||||
tab = Tab(
|
||||
id=data["id"],
|
||||
window_id=data.get("windowId", 0),
|
||||
@@ -46,19 +80,21 @@ class BrowserCLI:
|
||||
title=data.get("title") or "",
|
||||
url=data.get("url") or "",
|
||||
group_id=data.get("groupId") or None,
|
||||
browser=browser_name,
|
||||
)
|
||||
tab._browser = self
|
||||
tab._browser = self if browser_profile is None else BrowserCLI(browser=browser_profile)
|
||||
return tab
|
||||
|
||||
def _make_group(self, data: dict) -> Group:
|
||||
def _make_group(self, data: dict, *, browser_profile: str | None = None, browser_name: str | None = None) -> Group:
|
||||
group = Group(
|
||||
id=data["id"],
|
||||
title=data.get("title") or "",
|
||||
color=data.get("color") or "",
|
||||
collapsed=data.get("collapsed", False),
|
||||
tab_count=data.get("tabCount", 0),
|
||||
browser=browser_name,
|
||||
)
|
||||
group._browser = self
|
||||
group._browser = self if browser_profile is None else BrowserCLI(browser=browser_profile)
|
||||
return group
|
||||
|
||||
# ── Navigation ────────────────────────────────────────────────────────
|
||||
@@ -99,7 +135,18 @@ class BrowserCLI:
|
||||
# ── Tabs ──────────────────────────────────────────────────────────────
|
||||
|
||||
def tabs_list(self) -> list[Tab]:
|
||||
"""Return all open tabs across all windows."""
|
||||
"""Return all open tabs across all windows.
|
||||
|
||||
When multiple browsers are active and no browser was specified, each Tab
|
||||
includes ``tab.browser`` naming its source browser.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("tabs.list", {})
|
||||
if multi_results:
|
||||
return [
|
||||
self._make_tab(tab, browser_profile=target.profile, browser_name=target.display_name)
|
||||
for target, tabs in multi_results
|
||||
for tab in (tabs or [])
|
||||
]
|
||||
return [self._make_tab(t) for t in (self._cmd("tabs.list", {}) or [])]
|
||||
|
||||
def tabs_close(self, tab_id: int | None = None, *, inactive: bool = False, duplicates: bool = False) -> int:
|
||||
@@ -127,8 +174,15 @@ class BrowserCLI:
|
||||
return [self._make_tab(t) for t in (self._cmd("tabs.filter", {"pattern": pattern_or_filter}) or [])]
|
||||
return self._apply_tab_filter(pattern_or_filter)
|
||||
|
||||
def tabs_count(self, pattern: str | None = None) -> int:
|
||||
"""Count open tabs, optionally filtered by URL pattern."""
|
||||
def tabs_count(self, pattern: str | None = None) -> int | BrowserCounts:
|
||||
"""Count open tabs, optionally filtered by URL pattern.
|
||||
|
||||
Returns ``BrowserCounts`` in implicit multi-browser mode.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("tabs.count", {"pattern": pattern})
|
||||
if multi_results:
|
||||
by_browser = {target.display_name: int(count or 0) for target, count in multi_results}
|
||||
return BrowserCounts(total=sum(by_browser.values()), by_browser=by_browser)
|
||||
return self._cmd("tabs.count", {"pattern": pattern})
|
||||
|
||||
def tabs_query(self, search: str) -> list[Tab]:
|
||||
@@ -166,15 +220,33 @@ class BrowserCLI:
|
||||
# ── Tab Groups ────────────────────────────────────────────────────────
|
||||
|
||||
def group_list(self) -> list[Group]:
|
||||
"""Return all tab groups."""
|
||||
"""Return all tab groups.
|
||||
|
||||
When multiple browsers are active and no browser was specified, each Group
|
||||
includes ``group.browser`` naming its source browser.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("group.list", {})
|
||||
if multi_results:
|
||||
return [
|
||||
self._make_group(group, browser_profile=target.profile, browser_name=target.display_name)
|
||||
for target, groups in multi_results
|
||||
for group in (groups or [])
|
||||
]
|
||||
return [self._make_group(g) for g in (self._cmd("group.list", {}) or [])]
|
||||
|
||||
def group_tabs(self, group_id: int) -> list[Tab]:
|
||||
"""Return all tabs inside a group."""
|
||||
return [self._make_tab(t) for t in (self._cmd("group.tabs", {"groupId": group_id}) or [])]
|
||||
|
||||
def group_count(self) -> int:
|
||||
"""Return the number of tab groups."""
|
||||
def group_count(self) -> int | BrowserCounts:
|
||||
"""Return the number of tab groups.
|
||||
|
||||
Returns ``BrowserCounts`` in implicit multi-browser mode.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("group.count", {})
|
||||
if multi_results:
|
||||
by_browser = {target.display_name: int(count or 0) for target, count in multi_results}
|
||||
return BrowserCounts(total=sum(by_browser.values()), by_browser=by_browser)
|
||||
return self._cmd("group.count", {})
|
||||
|
||||
def group_query(self, search: str) -> list[Group]:
|
||||
@@ -202,6 +274,17 @@ class BrowserCLI:
|
||||
# ── Windows ───────────────────────────────────────────────────────────
|
||||
|
||||
def windows_list(self) -> list[dict]:
|
||||
"""Return browser windows.
|
||||
|
||||
In implicit multi-browser mode each window dict includes a ``browser`` key.
|
||||
"""
|
||||
multi_results = self._collect_multi_browser("windows.list", {})
|
||||
if multi_results:
|
||||
return [
|
||||
{**window, "browser": target.display_name}
|
||||
for target, windows in multi_results
|
||||
for window in (windows or [])
|
||||
]
|
||||
return self._cmd("windows.list", {})
|
||||
|
||||
def windows_rename(self, window_id: int, name: str) -> None:
|
||||
|
||||
+5
-14
@@ -21,7 +21,7 @@ from browser_cli.commands.dom import dom_group
|
||||
from browser_cli.commands.extract import extract_group
|
||||
from browser_cli.commands.session import session_group
|
||||
from browser_cli.commands.search import search_group
|
||||
from browser_cli.client import send_command, BrowserNotConnected
|
||||
from browser_cli.client import send_command, BrowserNotConnected, REGISTRY_PATH, display_browser_name
|
||||
|
||||
console = Console()
|
||||
|
||||
@@ -85,13 +85,6 @@ def _print_version(ctx, param, value):
|
||||
click.echo(_project_version())
|
||||
ctx.exit()
|
||||
|
||||
|
||||
def _client_display_profile(profile_name: str, sock_path: str) -> str:
|
||||
if profile_name != "default":
|
||||
return profile_name
|
||||
return Path(sock_path).stem or profile_name
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option(
|
||||
"-V", "--version",
|
||||
@@ -109,6 +102,8 @@ def _client_display_profile(profile_name: str, sock_path: str) -> str:
|
||||
def main(ctx, browser):
|
||||
"""Control your running browser from the terminal via a Chrome extension."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["browser"] = browser
|
||||
ctx.obj["browser_explicit"] = browser is not None
|
||||
if browser:
|
||||
os.environ["BROWSER_CLI_PROFILE"] = browser
|
||||
|
||||
@@ -129,20 +124,16 @@ main.add_command(search_group)
|
||||
@main.command("clients")
|
||||
def cmd_clients():
|
||||
"""Show connected browser clients."""
|
||||
import json as _json
|
||||
from browser_cli.client import REGISTRY_PATH
|
||||
|
||||
# Build a map of profile → socket path from the registry
|
||||
profiles: dict[str, str] = {}
|
||||
if REGISTRY_PATH.exists():
|
||||
try:
|
||||
profiles = _json.loads(REGISTRY_PATH.read_text())
|
||||
profiles = json.loads(REGISTRY_PATH.read_text())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
all_clients = []
|
||||
for profile_name, sock_path in profiles.items():
|
||||
display_profile = _client_display_profile(profile_name, sock_path)
|
||||
display_profile = display_browser_name(profile_name, sock_path)
|
||||
try:
|
||||
result = send_command("clients.list", profile=profile_name)
|
||||
for c in (result or []):
|
||||
|
||||
+42
-17
@@ -13,6 +13,7 @@ import os
|
||||
import socket
|
||||
import struct
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -24,11 +25,37 @@ class BrowserNotConnected(Exception):
|
||||
"""Raised when the native host socket is not available."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BrowserTarget:
|
||||
profile: str
|
||||
display_name: str
|
||||
socket_path: str
|
||||
|
||||
|
||||
def _active_sockets(reg: dict) -> dict:
|
||||
"""Return only entries whose socket file exists on disk."""
|
||||
return {k: v for k, v in reg.items() if Path(v).exists()}
|
||||
|
||||
|
||||
def display_browser_name(profile_name: str, sock_path: str) -> str:
|
||||
if profile_name != "default":
|
||||
return profile_name
|
||||
return Path(sock_path).stem or profile_name
|
||||
|
||||
|
||||
def active_browser_targets() -> list[BrowserTarget]:
|
||||
if not REGISTRY_PATH.exists():
|
||||
return []
|
||||
try:
|
||||
reg = json.loads(REGISTRY_PATH.read_text())
|
||||
except Exception:
|
||||
return []
|
||||
return [
|
||||
BrowserTarget(profile=profile, display_name=display_browser_name(profile, sock_path), socket_path=sock_path)
|
||||
for profile, sock_path in _active_sockets(reg).items()
|
||||
]
|
||||
|
||||
|
||||
def _resolve_socket(profile: str | None = None) -> str:
|
||||
"""Return the socket path for the given profile (or auto-detect)."""
|
||||
target = profile or os.environ.get("BROWSER_CLI_PROFILE")
|
||||
@@ -45,23 +72,21 @@ def _resolve_socket(profile: str | None = None) -> str:
|
||||
return str(SOCKET_DIR / f"{safe}.sock")
|
||||
|
||||
# Auto-detect: error when multiple browser instances are active
|
||||
if REGISTRY_PATH.exists():
|
||||
try:
|
||||
reg = json.loads(REGISTRY_PATH.read_text())
|
||||
active = _active_sockets(reg)
|
||||
if len(active) > 1:
|
||||
aliases = list(active.keys())
|
||||
examples = "\n".join(f" browser-cli --browser {a} ..." for a in aliases)
|
||||
raise BrowserNotConnected(
|
||||
f"Multiple browser instances are active: {', '.join(aliases)}\n"
|
||||
f"Use --browser <alias> to select one:\n{examples}"
|
||||
)
|
||||
if active:
|
||||
return next(iter(active.values()))
|
||||
except BrowserNotConnected:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
active = active_browser_targets()
|
||||
if len(active) > 1:
|
||||
aliases = [target.profile for target in active]
|
||||
examples = "\n".join(f" browser-cli --browser {a} ..." for a in aliases)
|
||||
raise BrowserNotConnected(
|
||||
f"Multiple browser instances are active: {', '.join(aliases)}\n"
|
||||
f"Use --browser <alias> to select one:\n{examples}"
|
||||
)
|
||||
if active:
|
||||
return active[0].socket_path
|
||||
except BrowserNotConnected:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise BrowserNotConnected(
|
||||
"Cannot resolve a browser socket automatically.\n"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import click
|
||||
from browser_cli.client import send_command, BrowserNotConnected
|
||||
from browser_cli.client import BrowserNotConnected, active_browser_targets, send_command
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
def _handle(command, args=None):
|
||||
def _handle(command, args=None, profile=None):
|
||||
try:
|
||||
return send_command(command, args or {})
|
||||
return send_command(command, args or {}, profile=profile)
|
||||
except BrowserNotConnected as e:
|
||||
console.print(f"[red]Error:[/red] {e}")
|
||||
raise SystemExit(1)
|
||||
@@ -17,24 +17,45 @@ def _handle(command, args=None):
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def _print_groups(groups: list[dict]) -> None:
|
||||
def _handle_multi(command, args=None, profile=None):
|
||||
try:
|
||||
return send_command(command, args or {}, profile=profile)
|
||||
except (BrowserNotConnected, RuntimeError):
|
||||
return None
|
||||
|
||||
|
||||
def _multi_browser_targets():
|
||||
root = click.get_current_context().find_root()
|
||||
if root.obj.get("browser_explicit"):
|
||||
return []
|
||||
targets = active_browser_targets()
|
||||
if len(targets) <= 1:
|
||||
return []
|
||||
return targets
|
||||
|
||||
|
||||
def _print_groups(groups: list[dict], *, show_browser: bool = False) -> None:
|
||||
if not groups:
|
||||
console.print("[yellow]No groups found[/yellow]")
|
||||
return
|
||||
table = Table(show_header=True, header_style="bold cyan")
|
||||
if show_browser:
|
||||
table.add_column("Browser")
|
||||
table.add_column("ID", style="dim", no_wrap=True)
|
||||
table.add_column("Name")
|
||||
table.add_column("Color", width=10)
|
||||
table.add_column("Collapsed", width=10)
|
||||
table.add_column("Tabs", width=6)
|
||||
for g in groups:
|
||||
table.add_row(
|
||||
row = [
|
||||
g.get("browser", "") if show_browser else None,
|
||||
str(g.get("id", "")),
|
||||
g.get("title") or "(unnamed)",
|
||||
g.get("title") or "",
|
||||
g.get("color") or "",
|
||||
"yes" if g.get("collapsed") else "no",
|
||||
str(g.get("tabCount", "")),
|
||||
)
|
||||
]
|
||||
table.add_row(*[value for value in row if value is not None])
|
||||
console.print(table)
|
||||
|
||||
|
||||
@@ -46,6 +67,19 @@ def group_group():
|
||||
@group_group.command("list")
|
||||
def group_list():
|
||||
"""List all tab groups."""
|
||||
targets = _multi_browser_targets()
|
||||
if targets:
|
||||
groups = []
|
||||
for target in targets:
|
||||
result = _handle_multi("group.list", profile=target.profile)
|
||||
if result is None:
|
||||
continue
|
||||
groups.extend({**group, "browser": target.display_name} for group in result)
|
||||
if not groups:
|
||||
console.print("[red]Error:[/red] Cannot resolve a browser socket automatically.")
|
||||
raise SystemExit(1)
|
||||
_print_groups(groups, show_browser=True)
|
||||
return
|
||||
groups = _handle("group.list")
|
||||
_print_groups(groups or [])
|
||||
|
||||
@@ -62,6 +96,27 @@ def group_tabs(group_id):
|
||||
@group_group.command("count")
|
||||
def group_count():
|
||||
"""Count all tab groups."""
|
||||
targets = _multi_browser_targets()
|
||||
if targets:
|
||||
table = Table(show_header=True, header_style="bold cyan")
|
||||
table.add_column("Browser")
|
||||
table.add_column("Groups", justify="right")
|
||||
total = 0
|
||||
rows = 0
|
||||
for target in targets:
|
||||
count = _handle_multi("group.count", profile=target.profile)
|
||||
if count is None:
|
||||
continue
|
||||
count = int(count or 0)
|
||||
total += count
|
||||
rows += 1
|
||||
table.add_row(target.display_name, str(count))
|
||||
if rows == 0:
|
||||
console.print("[red]Error:[/red] Cannot resolve a browser socket automatically.")
|
||||
raise SystemExit(1)
|
||||
table.add_row("Total", str(total))
|
||||
console.print(table)
|
||||
return
|
||||
count = _handle("group.count")
|
||||
console.print(f"[bold]{count}[/bold] group(s)")
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import click
|
||||
from browser_cli.client import send_command, BrowserNotConnected
|
||||
from browser_cli.client import BrowserNotConnected, active_browser_targets, send_command
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
def _handle(command, args=None):
|
||||
def _handle(command, args=None, profile=None):
|
||||
try:
|
||||
return send_command(command, args or {})
|
||||
return send_command(command, args or {}, profile=profile)
|
||||
except BrowserNotConnected as e:
|
||||
console.print(f"[red]Error:[/red] {e}")
|
||||
raise SystemExit(1)
|
||||
@@ -17,11 +17,30 @@ def _handle(command, args=None):
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def _print_tabs(tabs: list[dict]) -> None:
|
||||
def _handle_multi(command, args=None, profile=None):
|
||||
try:
|
||||
return send_command(command, args or {}, profile=profile)
|
||||
except (BrowserNotConnected, RuntimeError):
|
||||
return None
|
||||
|
||||
|
||||
def _multi_browser_targets():
|
||||
root = click.get_current_context().find_root()
|
||||
if root.obj.get("browser_explicit"):
|
||||
return []
|
||||
targets = active_browser_targets()
|
||||
if len(targets) <= 1:
|
||||
return []
|
||||
return targets
|
||||
|
||||
|
||||
def _print_tabs(tabs: list[dict], *, show_browser: bool = False) -> None:
|
||||
if not tabs:
|
||||
console.print("[yellow]No tabs found[/yellow]")
|
||||
return
|
||||
table = Table(show_header=True, header_style="bold cyan")
|
||||
if show_browser:
|
||||
table.add_column("Browser", no_wrap=True)
|
||||
table.add_column("ID", style="dim", no_wrap=True)
|
||||
table.add_column("Window", no_wrap=True)
|
||||
table.add_column("Active", width=7)
|
||||
@@ -29,13 +48,15 @@ def _print_tabs(tabs: list[dict]) -> None:
|
||||
table.add_column("URL")
|
||||
for t in tabs:
|
||||
active = "[green]✓[/green]" if t.get("active") else ""
|
||||
table.add_row(
|
||||
row = [
|
||||
t.get("browser", "") if show_browser else None,
|
||||
str(t.get("id", "")),
|
||||
str(t.get("windowId", "")),
|
||||
active,
|
||||
(t.get("title") or "")[:60],
|
||||
(t.get("url") or "")[:80],
|
||||
)
|
||||
]
|
||||
table.add_row(*[value for value in row if value is not None])
|
||||
console.print(table)
|
||||
|
||||
|
||||
@@ -47,6 +68,19 @@ def tabs_group():
|
||||
@tabs_group.command("list")
|
||||
def tabs_list():
|
||||
"""List all open tabs across all windows."""
|
||||
targets = _multi_browser_targets()
|
||||
if targets:
|
||||
tabs = []
|
||||
for target in targets:
|
||||
result = _handle_multi("tabs.list", profile=target.profile)
|
||||
if result is None:
|
||||
continue
|
||||
tabs.extend({**tab, "browser": target.display_name} for tab in result)
|
||||
if not tabs:
|
||||
console.print("[red]Error:[/red] Cannot resolve a browser socket automatically.")
|
||||
raise SystemExit(1)
|
||||
_print_tabs(tabs, show_browser=True)
|
||||
return
|
||||
tabs = _handle("tabs.list")
|
||||
_print_tabs(tabs or [])
|
||||
|
||||
@@ -98,6 +132,27 @@ def tabs_filter(pattern):
|
||||
@click.argument("pattern", required=False)
|
||||
def tabs_count(pattern):
|
||||
"""Count open tabs, optionally filtered by URL PATTERN."""
|
||||
targets = _multi_browser_targets()
|
||||
if targets:
|
||||
table = Table(show_header=True, header_style="bold cyan")
|
||||
table.add_column("Browser")
|
||||
table.add_column("Tabs", justify="right")
|
||||
total = 0
|
||||
rows = 0
|
||||
for target in targets:
|
||||
count = _handle_multi("tabs.count", {"pattern": pattern}, profile=target.profile)
|
||||
if count is None:
|
||||
continue
|
||||
count = int(count or 0)
|
||||
total += count
|
||||
rows += 1
|
||||
table.add_row(target.display_name, str(count))
|
||||
if rows == 0:
|
||||
console.print("[red]Error:[/red] Cannot resolve a browser socket automatically.")
|
||||
raise SystemExit(1)
|
||||
table.add_row("Total", str(total))
|
||||
console.print(table)
|
||||
return
|
||||
count = _handle("tabs.count", {"pattern": pattern})
|
||||
label = f" matching '{pattern}'" if pattern else ""
|
||||
console.print(f"[bold]{count}[/bold] tab(s){label}")
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import click
|
||||
from browser_cli.client import send_command, BrowserNotConnected
|
||||
from browser_cli.client import BrowserNotConnected, active_browser_targets, send_command
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
def _handle(command, args=None):
|
||||
def _handle(command, args=None, profile=None):
|
||||
try:
|
||||
return send_command(command, args or {})
|
||||
return send_command(command, args or {}, profile=profile)
|
||||
except BrowserNotConnected as e:
|
||||
console.print(f"[red]Error:[/red] {e}")
|
||||
raise SystemExit(1)
|
||||
@@ -17,25 +17,43 @@ def _handle(command, args=None):
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def _print_windows(windows: list[dict]) -> None:
|
||||
def _handle_multi(command, args=None, profile=None):
|
||||
try:
|
||||
return send_command(command, args or {}, profile=profile)
|
||||
except (BrowserNotConnected, RuntimeError):
|
||||
return None
|
||||
|
||||
|
||||
def _multi_browser_targets():
|
||||
root = click.get_current_context().find_root()
|
||||
if root.obj.get("browser_explicit"):
|
||||
return []
|
||||
targets = active_browser_targets()
|
||||
if len(targets) <= 1:
|
||||
return []
|
||||
return targets
|
||||
|
||||
|
||||
def _print_windows(windows: list[dict], *, show_browser: bool = False) -> None:
|
||||
if not windows:
|
||||
console.print("[yellow]No windows found[/yellow]")
|
||||
return
|
||||
table = Table(show_header=True, header_style="bold cyan")
|
||||
if show_browser:
|
||||
table.add_column("Browser")
|
||||
table.add_column("ID", style="dim", no_wrap=True)
|
||||
table.add_column("Alias", width=20)
|
||||
table.add_column("Focused", width=8)
|
||||
table.add_column("Tabs", width=6)
|
||||
table.add_column("State", width=12)
|
||||
for w in windows:
|
||||
focused = "[green]✓[/green]" if w.get("focused") else ""
|
||||
table.add_row(
|
||||
row = [
|
||||
w.get("browser", "") if show_browser else None,
|
||||
str(w.get("id", "")),
|
||||
w.get("alias") or "",
|
||||
focused,
|
||||
str(w.get("tabCount", "")),
|
||||
w.get("state") or "",
|
||||
)
|
||||
]
|
||||
table.add_row(*[value for value in row if value is not None])
|
||||
console.print(table)
|
||||
|
||||
|
||||
@@ -47,6 +65,19 @@ def windows_group():
|
||||
@windows_group.command("list")
|
||||
def windows_list():
|
||||
"""List all browser windows."""
|
||||
targets = _multi_browser_targets()
|
||||
if targets:
|
||||
windows = []
|
||||
for target in targets:
|
||||
result = _handle_multi("windows.list", profile=target.profile)
|
||||
if result is None:
|
||||
continue
|
||||
windows.extend({**window, "browser": target.display_name} for window in result)
|
||||
if not windows:
|
||||
console.print("[red]Error:[/red] Cannot resolve a browser socket automatically.")
|
||||
raise SystemExit(1)
|
||||
_print_windows(windows, show_browser=True)
|
||||
return
|
||||
windows = _handle("windows.list")
|
||||
_print_windows(windows or [])
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ class Tab:
|
||||
title: str
|
||||
url: str
|
||||
group_id: int | None = None
|
||||
browser: str | None = None
|
||||
_browser: BrowserCLI | None = field(default=None, repr=False, compare=False, init=False)
|
||||
|
||||
def _b(self) -> BrowserCLI:
|
||||
@@ -103,6 +104,7 @@ class Group:
|
||||
color: str
|
||||
collapsed: bool
|
||||
tab_count: int
|
||||
browser: str | None = None
|
||||
_browser: BrowserCLI | None = field(default=None, repr=False, compare=False, init=False)
|
||||
|
||||
def _b(self) -> BrowserCLI:
|
||||
|
||||
Reference in New Issue
Block a user