"""Configuration management using pydantic-settings.
Loads configuration from environment variables with sensible defaults.
"""
from pathlib import Path
from typing import Literal
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
)
# Database settings
database_path: Path = Field(
default=Path.home() / ".mcp-task-aggregator" / "tasks.db",
description="Path to SQLite database file",
)
# Jira settings
jira_url: str = Field(
default="",
description="Jira instance URL (e.g., https://company.atlassian.net)",
)
jira_email: str = Field(
default="",
description="Jira account email for authentication",
)
jira_api_token: str = Field(
default="",
description="Jira API token for authentication",
)
jira_project_key: str = Field(
default="AGENTOPS",
description="Default Jira project key for syncing",
)
# GitHub settings (Phase 2)
github_token: str = Field(
default="",
description="GitHub personal access token",
)
github_repos: list[str] = Field(
default_factory=list,
description="Allowlist of GitHub repos to sync (e.g., ['org/repo1', 'org/repo2'])",
)
# Linear settings (Phase 2)
linear_api_key: str = Field(
default="",
description="Linear API key for authentication",
)
# Markdown settings
markdown_search_paths: list[Path] = Field(
default_factory=lambda: [Path.cwd()],
description="Directories to search for TO-DO.md and TODO.md files",
)
markdown_file_patterns: list[str] = Field(
default_factory=lambda: ["TO-DO.md", "TODO.md", "todo.md", "to-do.md"],
description="File name patterns to match for markdown todo files",
)
markdown_enabled: bool = Field(
default=True,
description="Enable markdown file syncing",
)
# STM (Simple Task Master) settings
stm_search_paths: list[Path] = Field(
default_factory=lambda: [Path.cwd()],
description="Directories to search for .simple-task-master workspaces",
)
stm_binary: str = Field(
default="stm",
description="Path to stm binary",
)
stm_enabled: bool = Field(
default=True,
description="Enable STM workspace syncing",
)
# Logging settings
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
default="INFO",
description="Logging level",
)
log_file: Path | None = Field(
default=None,
description="Optional log file path. If not set, logs to stderr only.",
)
# Sync settings
sync_batch_size: int = Field(
default=100,
description="Number of tasks to sync per batch",
)
# OpenPipe ART Training settings
openrouter_api_key: str = Field(
default="",
description="OpenRouter API key for LLM access during training",
)
training_base_model: str = Field(
default="Qwen/Qwen2.5-1.5B-Instruct",
description="Default HuggingFace model ID for training base model",
)
training_input_model: str = Field(
default="openrouter/openai/gpt-4o-mini",
description="Model for generating training inputs",
)
training_ruler_model: str = Field(
default="openrouter/openai/gpt-4o-mini",
description="Model for RULER scoring during training",
)
training_batch_size: int = Field(
default=10,
description="Number of inputs per training batch",
)
training_rollouts_per_group: int = Field(
default=4,
description="Number of rollouts per input for RULER comparison",
)
training_learning_rate: float = Field(
default=1e-5,
description="Default learning rate for training",
)
training_num_epochs: int = Field(
default=1,
description="Default number of training epochs",
)
training_art_path: str = Field(
default="./.art",
description="Path for ART backend storage",
)
training_enabled: bool = Field(
default=True,
description="Enable training functionality",
)
# Ollama local training settings
ollama_host: str = Field(
default="http://localhost:11434",
description="Ollama server host URL",
)
ollama_inference_model: str = Field(
default="gemma3:1b-it-qat",
description="Ollama model for inference/rollouts",
)
ollama_ruler_model: str = Field(
default="gemma3:4b-it-qat",
description="Ollama model for RULER scoring",
)
ollama_enabled: bool = Field(
default=True,
description="Enable Ollama for local training",
)
@property
def jira_configured(self) -> bool:
"""Check if Jira credentials are configured."""
return bool(self.jira_url and self.jira_email and self.jira_api_token)
@property
def github_configured(self) -> bool:
"""Check if GitHub credentials are configured."""
return bool(self.github_token and self.github_repos)
@property
def linear_configured(self) -> bool:
"""Check if Linear credentials are configured."""
return bool(self.linear_api_key)
@property
def markdown_configured(self) -> bool:
"""Check if markdown syncing is enabled and paths are configured."""
return self.markdown_enabled and bool(self.markdown_search_paths)
@property
def stm_configured(self) -> bool:
"""Check if STM syncing is enabled and paths are configured."""
return self.stm_enabled and bool(self.stm_search_paths)
@property
def training_configured(self) -> bool:
"""Check if training is enabled and API key is configured."""
return self.training_enabled and bool(self.openrouter_api_key)
@property
def ollama_configured(self) -> bool:
"""Check if Ollama local training is enabled."""
return self.ollama_enabled and bool(self.ollama_host)
# Global settings instance (lazy loaded)
_settings: Settings | None = None
def get_settings() -> Settings:
"""Get the global settings instance."""
global _settings
if _settings is None:
_settings = Settings()
return _settings
def reset_settings() -> None:
"""Reset the global settings instance (useful for testing)."""
global _settings
_settings = None