""" Standalone init command for browser-use template generation. This module provides a minimal command-line interface for generating browser-use templates without requiring heavy TUI dependencies. """ import json import shutil import sys from pathlib import Path from typing import Any from urllib import request from urllib.error import URLError import click from InquirerPy import inquirer from InquirerPy.base.control import Choice from InquirerPy.utils import InquirerPyStyle from rich.console import Console from rich.panel import Panel from rich.text import Text # Rich console for styled output console = Console() # GitHub template repository URL (for runtime fetching) TEMPLATE_REPO_URL = 'https://raw.githubusercontent.com/browser-use/template-library/main' # Export for backward compatibility with cli.py # Templates are fetched at runtime via _get_template_list() INIT_TEMPLATES: dict[str, Any] = {} def _fetch_template_list() -> dict[str, Any] | None: """ Fetch template list from GitHub templates.json. Returns template dict if successful, None if failed. """ try: url = f'{TEMPLATE_REPO_URL}/templates.json' with request.urlopen(url, timeout=5) as response: data = response.read().decode('utf-8') return json.loads(data) except (URLError, TimeoutError, json.JSONDecodeError, Exception): return None def _get_template_list() -> dict[str, Any]: """ Get template list from GitHub. Raises FileNotFoundError if GitHub fetch fails. """ templates = _fetch_template_list() if templates is not None: return templates raise FileNotFoundError('Could not fetch templates from GitHub. Check your internet connection.') def _fetch_from_github(file_path: str) -> str | None: """ Fetch template file from GitHub. Returns file content if successful, None if failed. """ try: url = f'{TEMPLATE_REPO_URL}/{file_path}' with request.urlopen(url, timeout=5) as response: return response.read().decode('utf-8') except (URLError, TimeoutError, Exception): return None def _fetch_binary_from_github(file_path: str) -> bytes | None: """ Fetch binary file from GitHub. Returns file content if successful, None if failed. """ try: url = f'{TEMPLATE_REPO_URL}/{file_path}' with request.urlopen(url, timeout=5) as response: return response.read() except (URLError, TimeoutError, Exception): return None def _get_template_content(file_path: str) -> str: """ Get template file content from GitHub. Raises exception if fetch fails. """ content = _fetch_from_github(file_path) if content is not None: return content raise FileNotFoundError(f'Could not fetch template from GitHub: {file_path}') # InquirerPy style for template selection (browser-use orange theme) inquirer_style = InquirerPyStyle( { 'pointer': '#fe750e bold', 'highlighted': '#fe750e bold', 'question': 'bold', 'answer': '#fe750e bold', 'questionmark': '#fe750e bold', } ) def _get_terminal_width() -> int: """Get current terminal width in columns.""" return shutil.get_terminal_size().columns def _format_choice(name: str, metadata: dict[str, Any], width: int, is_default: bool = False) -> str: """ Format a template choice with responsive display based on terminal width. Styling: - Featured templates get [FEATURED] prefix - Author name included when width allows (except for default templates) - Everything turns orange when highlighted (InquirerPy's built-in behavior) Args: name: Template name metadata: Template metadata (description, featured, author) width: Terminal width in columns is_default: Whether this is a default template (default, advanced, tools) Returns: Formatted choice string """ is_featured = metadata.get('featured', False) description = metadata.get('description', '') author_name = metadata.get('author', {}).get('name', '') if isinstance(metadata.get('author'), dict) else '' # Build the choice string based on terminal width if width > 100: # Wide: show everything including author (except for default templates) if is_featured: if author_name: return f'[FEATURED] {name} by {author_name} - {description}' else: return f'[FEATURED] {name} - {description}' else: # Non-featured templates if author_name and not is_default: return f'{name} by {author_name} - {description}' else: return f'{name} - {description}' elif width > 60: # Medium: show name and description, no author if is_featured: return f'[FEATURED] {name} - {description}' else: return f'{name} - {description}' else: # Narrow: show name only return name def _write_init_file(output_path: Path, content: str, force: bool = False) -> bool: """Write content to a file, with safety checks.""" # Check if file already exists if output_path.exists() and not force: console.print(f'[yellow]⚠[/yellow] File already exists: [cyan]{output_path}[/cyan]') if not click.confirm('Overwrite?', default=False): console.print('[red]✗[/red] Cancelled') return False # Ensure parent directory exists output_path.parent.mkdir(parents=True, exist_ok=True) # Write file try: output_path.write_text(content, encoding='utf-8') return True except Exception as e: console.print(f'[red]✗[/red] Error writing file: {e}') return False @click.command('browser-use-init') @click.option( '--template', '-t', type=str, help='Template to use', ) @click.option( '--output', '-o', type=click.Path(), help='Output file path (default: browser_use_