Structure webber into three independent subprojects: - webber-api/: FastAPI backend server with all agent code - webber-cli/: Standalone CLI client (renamed from cli/ to webber_cli/) - webber-sandbox/: Test project for functional testing Key changes: - Each subproject has its own .venv (Python 3.12+) - Added sandbox.sh for managing test project templates - Created sandbox-templates/ with calculator-cli and empty starter - Updated CI/CD for prefixed tags (api/v*, cli/v*) - Added comprehensive AGENTS.md with operational instructions - Added gitignore filtering to glob and grep tools - Created pyproject.toml for each subproject Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
100 lines
2.8 KiB
Python
100 lines
2.8 KiB
Python
"""
|
|
Application configuration via Pydantic Settings.
|
|
|
|
All settings loaded from environment variables or .env file.
|
|
Project metadata (name, version, description) sourced from pyproject.toml.
|
|
"""
|
|
import tomllib
|
|
from dataclasses import dataclass
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProjectMeta:
|
|
"""Project metadata from pyproject.toml (single source of truth)."""
|
|
name: str
|
|
version: str
|
|
description: str
|
|
|
|
|
|
def _load_project_meta() -> ProjectMeta:
|
|
"""Load project metadata from pyproject.toml."""
|
|
pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
|
|
try:
|
|
with open(pyproject_path, "rb") as f:
|
|
data = tomllib.load(f)
|
|
project = data.get("project", {})
|
|
return ProjectMeta(
|
|
name=str(project.get("name", "webber")).title(),
|
|
version=str(project.get("version", "0.0.0")),
|
|
description=str(project.get("description", "")),
|
|
)
|
|
except FileNotFoundError:
|
|
return ProjectMeta(name="Webber", version="0.0.0", description="")
|
|
|
|
|
|
PROJECT = _load_project_meta()
|
|
__version__ = PROJECT.version
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Application settings loaded from environment."""
|
|
|
|
# Application (from pyproject.toml)
|
|
app_name: str = PROJECT.name
|
|
app_version: str = PROJECT.version
|
|
app_description: str = PROJECT.description
|
|
debug: bool = False
|
|
|
|
# Server
|
|
host: str = "0.0.0.0"
|
|
port: int = 8086
|
|
|
|
# Logging
|
|
log_level: str = "INFO"
|
|
|
|
# CORS
|
|
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:8080"]
|
|
cors_credentials: bool = True
|
|
cors_methods: list[str] = ["*"]
|
|
cors_headers: list[str] = ["*"]
|
|
|
|
# LLM - Ollama (always hot in VRAM on tower-of-joy)
|
|
ollama_url: str = "http://192.168.86.149:11434"
|
|
ollama_agent_model: str = "mistral-nemo-large:latest"
|
|
ollama_embed_model: str = "nomic-embed-text:latest"
|
|
|
|
# Auth - Tatlock integration
|
|
tatlock_api_url: str | None = "http://192.168.86.149:8000"
|
|
internal_api_key: str | None = None
|
|
|
|
# Tool execution
|
|
tool_timeout_seconds: int = 120
|
|
sandbox_enabled: bool = True
|
|
allowed_paths: list[str] | None = None
|
|
|
|
# Sessions
|
|
session_ttl_hours: int = 24
|
|
max_context_tokens: int = 128000
|
|
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env",
|
|
case_sensitive=False,
|
|
extra="ignore",
|
|
env_parse_none_str="", # Treat empty string as None
|
|
)
|
|
|
|
@property
|
|
def effective_allowed_paths(self) -> list[str]:
|
|
"""Return allowed_paths or empty list if None."""
|
|
return self.allowed_paths or []
|
|
|
|
|
|
@lru_cache
|
|
def get_settings() -> Settings:
|
|
"""Cached settings singleton."""
|
|
return Settings()
|