Initial commit: Ajarbot with optimizations
Features: - Multi-platform bot (Slack, Telegram) - Memory system with SQLite FTS - Tool use capabilities (file ops, commands) - Scheduled tasks system - Dynamic model switching (/sonnet, /haiku) - Prompt caching for cost optimization Optimizations: - Default to Haiku 4.5 (12x cheaper) - Reduced context: 3 messages, 2 memory results - Optimized SOUL.md (48% smaller) - Automatic caching when using Sonnet (90% savings) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
165
config/config_loader.py
Normal file
165
config/config_loader.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
Configuration loader for adapter system.
|
||||
|
||||
Loads from YAML with environment variable override support.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
# Environment variable mappings: env var name -> (adapter, credential key)
|
||||
_ENV_OVERRIDES = {
|
||||
"AJARBOT_SLACK_BOT_TOKEN": ("slack", "bot_token"),
|
||||
"AJARBOT_SLACK_APP_TOKEN": ("slack", "app_token"),
|
||||
"AJARBOT_TELEGRAM_BOT_TOKEN": ("telegram", "bot_token"),
|
||||
}
|
||||
|
||||
|
||||
class ConfigLoader:
|
||||
"""Load adapter configuration from YAML files with env var support."""
|
||||
|
||||
def __init__(self, config_dir: Optional[str] = None) -> None:
|
||||
if config_dir is None:
|
||||
config_dir = str(Path(__file__).parent)
|
||||
self.config_dir = Path(config_dir)
|
||||
self.config: Dict[str, Any] = {}
|
||||
|
||||
def load(self, filename: str = "adapters.yaml") -> Dict[str, Any]:
|
||||
"""
|
||||
Load configuration from YAML file.
|
||||
|
||||
Looks for files in this order:
|
||||
1. {filename}.local.yaml (gitignored, for secrets)
|
||||
2. {filename}
|
||||
|
||||
Environment variables can override any setting:
|
||||
AJARBOT_SLACK_BOT_TOKEN -> adapters.slack.credentials.bot_token
|
||||
AJARBOT_TELEGRAM_BOT_TOKEN -> adapters.telegram.credentials.bot_token
|
||||
"""
|
||||
local_file = self.config_dir / f"{Path(filename).stem}.local.yaml"
|
||||
config_file = self.config_dir / filename
|
||||
|
||||
if local_file.exists():
|
||||
print(f"[Config] Loading from {local_file}")
|
||||
with open(local_file) as f:
|
||||
self.config = yaml.safe_load(f) or {}
|
||||
elif config_file.exists():
|
||||
print(f"[Config] Loading from {config_file}")
|
||||
with open(config_file) as f:
|
||||
self.config = yaml.safe_load(f) or {}
|
||||
else:
|
||||
print("[Config] No config file found, using defaults")
|
||||
self.config = {"adapters": {}}
|
||||
|
||||
self._apply_env_overrides()
|
||||
return self.config
|
||||
|
||||
def _apply_env_overrides(self) -> None:
|
||||
"""Apply environment variable overrides."""
|
||||
for env_var, (adapter, credential_key) in _ENV_OVERRIDES.items():
|
||||
value = os.getenv(env_var)
|
||||
if not value:
|
||||
continue
|
||||
|
||||
adapters = self.config.setdefault("adapters", {})
|
||||
adapter_config = adapters.setdefault(adapter, {})
|
||||
credentials = adapter_config.setdefault("credentials", {})
|
||||
credentials[credential_key] = value
|
||||
print(f"[Config] Using {env_var} from environment")
|
||||
|
||||
def get_adapter_config(
|
||||
self, platform: str
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get configuration for a specific platform."""
|
||||
return self.config.get("adapters", {}).get(platform)
|
||||
|
||||
def is_adapter_enabled(self, platform: str) -> bool:
|
||||
"""Check if an adapter is enabled."""
|
||||
adapter_config = self.get_adapter_config(platform)
|
||||
if not adapter_config:
|
||||
return False
|
||||
return adapter_config.get("enabled", False)
|
||||
|
||||
def get_user_mapping(self) -> Dict[str, str]:
|
||||
"""Get user ID to username mapping."""
|
||||
return self.config.get("user_mapping", {})
|
||||
|
||||
def save_template(
|
||||
self, filename: str = "adapters.local.yaml"
|
||||
) -> Path:
|
||||
"""Save a template configuration file."""
|
||||
template = {
|
||||
"adapters": {
|
||||
"slack": {
|
||||
"enabled": False,
|
||||
"credentials": {
|
||||
"bot_token": "xoxb-YOUR-BOT-TOKEN",
|
||||
"app_token": "xapp-YOUR-APP-TOKEN",
|
||||
},
|
||||
"settings": {},
|
||||
},
|
||||
"telegram": {
|
||||
"enabled": False,
|
||||
"credentials": {
|
||||
"bot_token": "YOUR-BOT-TOKEN",
|
||||
},
|
||||
"settings": {
|
||||
"allowed_users": [],
|
||||
"parse_mode": "Markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
"user_mapping": {},
|
||||
}
|
||||
|
||||
output_path = self.config_dir / filename
|
||||
with open(output_path, "w") as f:
|
||||
yaml.dump(
|
||||
template, f, default_flow_style=False, sort_keys=False
|
||||
)
|
||||
|
||||
print(f"[Config] Template saved to {output_path}")
|
||||
return output_path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "init":
|
||||
loader = ConfigLoader()
|
||||
path = loader.save_template()
|
||||
print(f"\nConfiguration template created at: {path}")
|
||||
print(
|
||||
"\nEdit this file with your credentials, "
|
||||
"then set enabled: true for each adapter you want to use."
|
||||
)
|
||||
else:
|
||||
loader = ConfigLoader()
|
||||
config = loader.load()
|
||||
|
||||
# Redact credentials before printing
|
||||
def redact_credentials(data):
|
||||
"""Redact sensitive credential values."""
|
||||
if isinstance(data, dict):
|
||||
redacted = {}
|
||||
for key, value in data.items():
|
||||
if key == "credentials" and isinstance(value, dict):
|
||||
redacted[key] = {
|
||||
k: f"{str(v)[:4]}****{str(v)[-4:]}" if v else None
|
||||
for k, v in value.items()
|
||||
}
|
||||
elif isinstance(value, (dict, list)):
|
||||
redacted[key] = redact_credentials(value)
|
||||
else:
|
||||
redacted[key] = value
|
||||
return redacted
|
||||
elif isinstance(data, list):
|
||||
return [redact_credentials(item) for item in data]
|
||||
return data
|
||||
|
||||
safe_config = redact_credentials(config)
|
||||
print("\nLoaded configuration (credentials redacted):")
|
||||
print(yaml.dump(safe_config, default_flow_style=False))
|
||||
Reference in New Issue
Block a user