Files
browser-use/browser_use/skill_cli/api_key.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

126 lines
4.0 KiB
Python

"""API key management for browser-use CLI."""
import json
import os
import sys
from pathlib import Path
class APIKeyRequired(Exception):
"""Raised when API key is required but not provided."""
pass
def get_config_path() -> Path:
"""Get browser-use config file path."""
if sys.platform == 'win32':
base = Path(os.environ.get('APPDATA', Path.home()))
else:
base = Path(os.environ.get('XDG_CONFIG_HOME', Path.home() / '.config'))
return base / 'browser-use' / 'config.json'
def require_api_key(feature: str = 'this feature') -> str:
"""Get API key or raise helpful error.
Checks in order:
1. BROWSER_USE_API_KEY environment variable
2. Config file (~/.config/browser-use/config.json)
3. Interactive prompt (if TTY)
4. Raises APIKeyRequired with helpful message
"""
# 1. Check environment
key = os.environ.get('BROWSER_USE_API_KEY')
if key:
return key
# 2. Check config file
config_path = get_config_path()
if config_path.exists():
try:
config = json.loads(config_path.read_text())
if key := config.get('api_key'):
return key
except Exception:
pass
# 3. Interactive prompt (if TTY)
if sys.stdin.isatty() and sys.stdout.isatty():
return prompt_for_api_key(feature)
# 4. Error with helpful message
raise APIKeyRequired(
f"""
╭─────────────────────────────────────────────────────────────╮
│ 🔑 Browser-Use API Key Required │
│ │
{feature} requires an API key. │
│ │
│ Get yours at: https://browser-use.com/dashboard │
│ │
│ Then set it via: │
│ export BROWSER_USE_API_KEY=your_key_here │
│ │
│ Or add to {config_path}: │
{{"api_key": "your_key_here"}}
╰─────────────────────────────────────────────────────────────╯
"""
)
def prompt_for_api_key(feature: str) -> str:
"""Interactive prompt for API key."""
print(
f"""
╭─────────────────────────────────────────────────────────────╮
│ 🔑 Browser-Use API Key Required │
│ │
{feature} requires an API key. │
│ Get yours at: https://browser-use.com/dashboard │
╰─────────────────────────────────────────────────────────────╯
"""
)
try:
key = input('Enter API key: ').strip()
except (EOFError, KeyboardInterrupt):
raise APIKeyRequired('No API key provided')
if not key:
raise APIKeyRequired('No API key provided')
try:
save = input('Save to config? [y/N]: ').strip().lower()
if save == 'y':
save_api_key(key)
except (EOFError, KeyboardInterrupt):
pass
return key
def save_api_key(key: str) -> None:
"""Save API key to config file."""
config_path = get_config_path()
config_path.parent.mkdir(parents=True, exist_ok=True)
config: dict = {}
if config_path.exists():
try:
config = json.loads(config_path.read_text())
except Exception:
pass
config['api_key'] = key
config_path.write_text(json.dumps(config, indent=2))
print(f'Saved to {config_path}')
def get_api_key() -> str | None:
"""Get API key if available, without raising error."""
try:
return require_api_key('API key check')
except APIKeyRequired:
return None