Files
browser-use/browser_use/skill_cli/sessions.py
Gregor Žunič c4ffaca6e7 feat: Add fast CLI for browser automation (bu command)
Implements a fast, persistent browser automation CLI per CLI_SPEC.md:

- Fast CLI layer using stdlib only (<50ms startup)
- Session server with Unix socket IPC (TCP on Windows)
- Browser modes: chromium, real, remote
- Commands: open, click, type, input, scroll, back, screenshot,
  state, switch, close-tab, keys, select, eval, extract
- Python execution with persistent namespace (Jupyter-like REPL)
- Agent task execution (requires API key)
- JSON output mode

The CLI maintains browser sessions across commands, enabling complex
multi-step workflows. Includes Claude skill description for AI-assisted
browser automation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 01:05:35 -08:00

138 lines
3.3 KiB
Python

"""Session registry - manages BrowserSession instances."""
import logging
from dataclasses import dataclass, field
from typing import Any
from browser_use.browser.session import BrowserSession
from browser_use.skill_cli.python_session import PythonSession
logger = logging.getLogger(__name__)
@dataclass
class SessionInfo:
"""Information about a browser session."""
name: str
browser_mode: str
headed: bool
profile: str | None
browser_session: BrowserSession
python_session: PythonSession = field(default_factory=PythonSession)
class SessionRegistry:
"""Registry of active browser sessions.
Sessions are created on-demand when first accessed. Each named session
is isolated with its own BrowserSession and Python namespace.
"""
def __init__(self) -> None:
self._sessions: dict[str, SessionInfo] = {}
async def get_or_create(
self,
name: str,
browser_mode: str,
headed: bool,
profile: str | None,
) -> SessionInfo:
"""Get existing session or create new one."""
if name in self._sessions:
return self._sessions[name]
logger.info(f'Creating new session: {name} (mode={browser_mode}, headed={headed})')
browser_session = await create_browser_session(browser_mode, headed, profile)
await browser_session.start()
session_info = SessionInfo(
name=name,
browser_mode=browser_mode,
headed=headed,
profile=profile,
browser_session=browser_session,
)
self._sessions[name] = session_info
return session_info
def get(self, name: str) -> SessionInfo | None:
"""Get session by name."""
return self._sessions.get(name)
def list_sessions(self) -> list[dict[str, Any]]:
"""List all active sessions."""
return [
{
'name': s.name,
'browser_mode': s.browser_mode,
'headed': s.headed,
'profile': s.profile,
}
for s in self._sessions.values()
]
async def close_session(self, name: str) -> bool:
"""Close and remove a session."""
if name not in self._sessions:
return False
session = self._sessions.pop(name)
logger.info(f'Closing session: {name}')
try:
await session.browser_session.kill()
except Exception as e:
logger.warning(f'Error closing session {name}: {e}')
return True
async def close_all(self) -> None:
"""Close all sessions."""
for name in list(self._sessions.keys()):
await self.close_session(name)
async def create_browser_session(
mode: str,
headed: bool,
profile: str | None,
) -> BrowserSession:
"""Create BrowserSession based on mode.
Modes:
- chromium: Playwright-managed Chromium (default)
- real: User's Chrome with profile
- remote: Browser-Use Cloud (requires API key)
"""
if mode == 'chromium':
return BrowserSession(
headless=not headed,
)
elif mode == 'real':
from browser_use.skill_cli.utils import find_chrome_executable, get_chrome_profile_path
chrome_path = find_chrome_executable()
if not chrome_path:
raise RuntimeError('Could not find Chrome executable. Please install Chrome or specify --browser chromium')
user_data_dir = get_chrome_profile_path(profile)
return BrowserSession(
executable_path=chrome_path,
user_data_dir=user_data_dir,
headless=False, # Real browser always visible
)
elif mode == 'remote':
from browser_use.skill_cli.api_key import require_api_key
require_api_key('Remote browser')
return BrowserSession(
use_cloud=True,
)
else:
raise ValueError(f'Unknown browser mode: {mode}')