CheddahBot/cheddahbot/config.py

99 lines
3.3 KiB
Python

"""Configuration loader: env vars → config.yaml → defaults."""
from __future__ import annotations
import os
from dataclasses import dataclass, field
from pathlib import Path
import yaml
from dotenv import load_dotenv
ROOT_DIR = Path(__file__).resolve().parent.parent
load_dotenv(ROOT_DIR / ".env")
@dataclass
class MemoryConfig:
max_context_messages: int = 50
flush_threshold: int = 40
embedding_model: str = "all-MiniLM-L6-v2"
search_top_k: int = 5
@dataclass
class SchedulerConfig:
heartbeat_interval_minutes: int = 30
poll_interval_seconds: int = 60
@dataclass
class ShellConfig:
blocked_commands: list[str] = field(default_factory=lambda: ["rm -rf /", "format", ":(){:|:&};:"])
require_approval: bool = False
@dataclass
class Config:
default_model: str = "claude-sonnet-4-20250514"
host: str = "0.0.0.0"
port: int = 7860
ollama_url: str = "http://localhost:11434"
lmstudio_url: str = "http://localhost:1234"
openrouter_api_key: str = ""
memory: MemoryConfig = field(default_factory=MemoryConfig)
scheduler: SchedulerConfig = field(default_factory=SchedulerConfig)
shell: ShellConfig = field(default_factory=ShellConfig)
# Derived paths
root_dir: Path = field(default_factory=lambda: ROOT_DIR)
data_dir: Path = field(default_factory=lambda: ROOT_DIR / "data")
identity_dir: Path = field(default_factory=lambda: ROOT_DIR / "identity")
memory_dir: Path = field(default_factory=lambda: ROOT_DIR / "memory")
skills_dir: Path = field(default_factory=lambda: ROOT_DIR / "skills")
db_path: Path = field(default_factory=lambda: ROOT_DIR / "data" / "cheddahbot.db")
def load_config() -> Config:
"""Load config from env vars → config.yaml → defaults."""
cfg = Config()
# Load YAML if exists
yaml_path = ROOT_DIR / "config.yaml"
if yaml_path.exists():
with open(yaml_path) as f:
data = yaml.safe_load(f) or {}
for key in ("default_model", "host", "port", "ollama_url", "lmstudio_url"):
if key in data:
setattr(cfg, key, data[key])
if "memory" in data and isinstance(data["memory"], dict):
for k, v in data["memory"].items():
if hasattr(cfg.memory, k):
setattr(cfg.memory, k, v)
if "scheduler" in data and isinstance(data["scheduler"], dict):
for k, v in data["scheduler"].items():
if hasattr(cfg.scheduler, k):
setattr(cfg.scheduler, k, v)
if "shell" in data and isinstance(data["shell"], dict):
for k, v in data["shell"].items():
if hasattr(cfg.shell, k):
setattr(cfg.shell, k, v)
# Env var overrides (CHEDDAH_ prefix)
cfg.openrouter_api_key = os.getenv("OPENROUTER_API_KEY", "")
if m := os.getenv("CHEDDAH_DEFAULT_MODEL"):
cfg.default_model = m
if h := os.getenv("CHEDDAH_HOST"):
cfg.host = h
if p := os.getenv("CHEDDAH_PORT"):
cfg.port = int(p)
# Ensure data directories exist
cfg.data_dir.mkdir(parents=True, exist_ok=True)
(cfg.data_dir / "uploads").mkdir(exist_ok=True)
(cfg.data_dir / "generated").mkdir(exist_ok=True)
cfg.memory_dir.mkdir(parents=True, exist_ok=True)
cfg.skills_dir.mkdir(parents=True, exist_ok=True)
return cfg