"""Configuration. Each field names a backend; the factory maps names -> classes. Defaults are all "mock" so the skeleton runs out of the box. Flip individual fields (via env or code) as real backends land. Env overrides (optional): WSAI_SOURCE, WSAI_VISION, WSAI_STT, WSAI_TTS, WSAI_BRAIN, WSAI_TEXT WSAI_CAPTURE_INTERVAL """ from __future__ import annotations import os from dataclasses import dataclass @dataclass class Settings: source: str | None = "mock" # mock | mss | None (eyes-free) vision: str | None = "mock" # mock | claude | None (eyes-free) stt: str | None = "mock" # mock | (whisper) | None tts: str | None = "mock" # mock | (melo) | None brain: str = "mock" # mock | claude text: str | None = None # None | (discord) capture_interval: float = 1.5 anthropic_model: str = "claude-sonnet-4-5" @classmethod def from_env(cls) -> "Settings": def opt(name: str, default): v = os.environ.get(name) return default if v is None else (None if v.lower() == "none" else v) return cls( source=opt("WSAI_SOURCE", "mock"), vision=opt("WSAI_VISION", "mock"), stt=opt("WSAI_STT", "mock"), tts=opt("WSAI_TTS", "mock"), brain=opt("WSAI_BRAIN", "mock"), text=opt("WSAI_TEXT", None), capture_interval=float(os.environ.get("WSAI_CAPTURE_INTERVAL", "1.5")), ) @classmethod def mock(cls) -> "Settings": return cls() @classmethod def live(cls) -> "Settings": """A realistic local config: capture this screen, Claude eyes+brain, mock voice (until STT/TTS backends are wired).""" return cls(source="mss", vision="claude", brain="claude", stt="mock", tts="mock") @classmethod def voice(cls) -> "Settings": """Eyes-free voice loop: no screen share, just STT -> Brain -> TTS. Screen capture is deferred, so source/vision are off. Backends default to mock so it runs out of the box; flip stt/tts/brain to real ones as they land.""" return cls(source=None, vision=None, stt="mock", tts="mock", brain="mock")