"""Global configuration handling for SmartTools. Manages ~/.smarttools/config.yaml with registry settings, tokens, and preferences. """ import uuid from dataclasses import dataclass, field from pathlib import Path from typing import Optional import yaml # Default configuration directory CONFIG_DIR = Path.home() / ".smarttools" CONFIG_FILE = CONFIG_DIR / "config.yaml" # Default registry URL (canonical base path) DEFAULT_REGISTRY_URL = "https://gitea.brrd.tech/api/v1" @dataclass class RegistryConfig: """Registry-related configuration.""" url: str = DEFAULT_REGISTRY_URL token: Optional[str] = None def to_dict(self) -> dict: d = {"url": self.url} if self.token: d["token"] = self.token return d @classmethod def from_dict(cls, data: dict) -> "RegistryConfig": return cls( url=data.get("url", DEFAULT_REGISTRY_URL), token=data.get("token") ) @dataclass class Config: """Global SmartTools configuration.""" registry: RegistryConfig = field(default_factory=RegistryConfig) client_id: str = "" auto_fetch_from_registry: bool = True default_provider: Optional[str] = None def __post_init__(self): # Generate client_id if not set if not self.client_id: self.client_id = f"anon_{uuid.uuid4().hex[:16]}" def to_dict(self) -> dict: d = { "registry": self.registry.to_dict(), "client_id": self.client_id, "auto_fetch_from_registry": self.auto_fetch_from_registry, } if self.default_provider: d["default_provider"] = self.default_provider return d @classmethod def from_dict(cls, data: dict) -> "Config": registry_data = data.get("registry", {}) return cls( registry=RegistryConfig.from_dict(registry_data), client_id=data.get("client_id", ""), auto_fetch_from_registry=data.get("auto_fetch_from_registry", True), default_provider=data.get("default_provider") ) def get_config_dir() -> Path: """Get the config directory, creating it if needed.""" CONFIG_DIR.mkdir(parents=True, exist_ok=True) return CONFIG_DIR def load_config() -> Config: """Load configuration from disk, creating defaults if needed.""" config_path = get_config_dir() / "config.yaml" if not config_path.exists(): # Create default config config = Config() save_config(config) return config try: data = yaml.safe_load(config_path.read_text()) or {} return Config.from_dict(data) except Exception as e: print(f"Warning: Error loading config, using defaults: {e}") return Config() def save_config(config: Config) -> Path: """Save configuration to disk.""" config_path = get_config_dir() / "config.yaml" config_path.write_text(yaml.dump(config.to_dict(), default_flow_style=False, sort_keys=False)) return config_path def get_registry_url() -> str: """Get the configured registry URL.""" config = load_config() return config.registry.url def get_registry_token() -> Optional[str]: """Get the configured registry token.""" config = load_config() return config.registry.token def set_registry_token(token: str) -> None: """Set and save the registry token.""" config = load_config() config.registry.token = token save_config(config) def get_client_id() -> str: """Get the client ID for anonymous usage tracking.""" config = load_config() return config.client_id def is_auto_fetch_enabled() -> bool: """Check if auto-fetch from registry is enabled.""" config = load_config() return config.auto_fetch_from_registry