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:
2026-02-13 19:06:28 -07:00
commit a99799bf3d
58 changed files with 11434 additions and 0 deletions

165
config/config_loader.py Normal file
View 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))