""" Configuration management for Webber CLI. Loads settings from ~/.webber/config.toml with environment variable overrides. """ import os import tomllib from dataclasses import dataclass, field from pathlib import Path from typing import Any # Config directory and file paths CONFIG_DIR = Path.home() / ".webber" CONFIG_FILE = CONFIG_DIR / "config.toml" # Default configuration template DEFAULT_CONFIG = """\ # Webber CLI Configuration # https://github.com/jpmschweitzer/webber [api] # API server URL (dev: 8095, prod: 8086) url = "http://localhost:8095" # API key for authentication (optional for dev mode) # key = "your-api-key" [cli] # Default permission mode: default, plan, auto_accept mode = "default" # Enable streaming by default stream = true [history] # Command history file location file = "~/.webber_history" """ @dataclass class ApiConfig: """API connection settings.""" url: str = "http://localhost:8095" key: str | None = None @dataclass class CliConfig: """CLI behavior settings.""" mode: str = "default" stream: bool = True @dataclass class HistoryConfig: """History settings.""" file: str = "~/.webber_history" @dataclass class Config: """Complete configuration.""" api: ApiConfig = field(default_factory=ApiConfig) cli: CliConfig = field(default_factory=CliConfig) history: HistoryConfig = field(default_factory=HistoryConfig) def load_config() -> Config: """ Load configuration from file with environment variable overrides. Priority (highest to lowest): 1. Environment variables (WEBBER_API_URL, WEBBER_API_KEY, WEBBER_MODE) 2. Config file (~/.webber/config.toml) 3. Built-in defaults Returns: Config object with merged settings """ config = Config() # Load from file if exists if CONFIG_FILE.exists(): try: with open(CONFIG_FILE, "rb") as f: data = tomllib.load(f) config = _parse_config(data) except Exception: # If config file is invalid, use defaults pass # Apply environment variable overrides if url := os.environ.get("WEBBER_API_URL"): config.api.url = url if key := os.environ.get("WEBBER_API_KEY"): config.api.key = key if mode := os.environ.get("WEBBER_MODE"): config.cli.mode = mode return config def _parse_config(data: dict[str, Any]) -> Config: """Parse config dict into Config object.""" config = Config() if api := data.get("api"): config.api.url = api.get("url", config.api.url) config.api.key = api.get("key", config.api.key) if cli := data.get("cli"): config.cli.mode = cli.get("mode", config.cli.mode) config.cli.stream = cli.get("stream", config.cli.stream) if history := data.get("history"): config.history.file = history.get("file", config.history.file) return config def init_config() -> Path: """ Initialize config directory and file with defaults. Returns: Path to the created config file """ CONFIG_DIR.mkdir(parents=True, exist_ok=True) if not CONFIG_FILE.exists(): CONFIG_FILE.write_text(DEFAULT_CONFIG) return CONFIG_FILE def get_config_path() -> Path | None: """Get path to config file if it exists.""" return CONFIG_FILE if CONFIG_FILE.exists() else None # Module-level cached config _config: Config | None = None def get_config() -> Config: """Get cached config, loading if necessary.""" global _config if _config is None: _config = load_config() return _config