This commit removes deprecated modules and reorganizes code into logical directories: Deleted files (superseded by newer systems): - claude_code_server.py (replaced by agent-sdk direct integration) - heartbeat.py (superseded by scheduled_tasks.py) - pulse_brain.py (unused in production) - config/pulse_brain_config.py (obsolete config) Created directory structure: - examples/ (7 example files: example_*.py, demo_*.py) - tests/ (5 test files: test_*.py) Updated imports: - agent.py: Removed heartbeat module and all enable_heartbeat logic - bot_runner.py: Removed heartbeat parameter from Agent initialization - llm_interface.py: Updated deprecated claude_code_server message Preserved essential files: - hooks.py (for future use) - adapters/skill_integration.py (for future use) - All Google integration tools (Gmail, Calendar, Contacts) - GLM provider code (backward compatibility) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
206 lines
6.9 KiB
Python
206 lines
6.9 KiB
Python
"""
|
||
Unified launcher for ajarbot with pre-flight checks.
|
||
|
||
This launcher:
|
||
1. Performs environment checks (Node.js, Claude CLI auth)
|
||
2. Sets sensible defaults (agent-sdk mode)
|
||
3. Delegates to bot_runner.main() for actual execution
|
||
|
||
Usage:
|
||
python ajarbot.py # Run with default config
|
||
python ajarbot.py --config custom.yaml # Use custom config file
|
||
python ajarbot.py --init # Generate config template
|
||
python ajarbot.py --setup-google # Set up Google OAuth
|
||
python ajarbot.py --health # Run health check
|
||
|
||
Environment variables:
|
||
AJARBOT_LLM_MODE # LLM mode: "agent-sdk" or "api" (default: agent-sdk)
|
||
AJARBOT_SLACK_BOT_TOKEN # Slack bot token (xoxb-...)
|
||
AJARBOT_SLACK_APP_TOKEN # Slack app token (xapp-...)
|
||
AJARBOT_TELEGRAM_BOT_TOKEN # Telegram bot token
|
||
ANTHROPIC_API_KEY # Claude API key (only needed for api mode)
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import shutil
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
|
||
class PreflightCheck:
|
||
"""Performs environment checks before launching the bot."""
|
||
|
||
def __init__(self):
|
||
self.warnings = []
|
||
self.errors = []
|
||
|
||
def check_nodejs(self) -> bool:
|
||
"""Check if Node.js is available (required for agent-sdk mode)."""
|
||
try:
|
||
result = subprocess.run(
|
||
["node", "--version"],
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=5
|
||
)
|
||
if result.returncode == 0:
|
||
version = result.stdout.strip()
|
||
print(f"✓ Node.js found: {version}")
|
||
return True
|
||
else:
|
||
self.warnings.append("Node.js not found (required for agent-sdk mode)")
|
||
return False
|
||
except FileNotFoundError:
|
||
self.warnings.append("Node.js not found (required for agent-sdk mode)")
|
||
return False
|
||
except Exception as e:
|
||
self.warnings.append(f"Error checking Node.js: {e}")
|
||
return False
|
||
|
||
def check_claude_cli_auth(self) -> bool:
|
||
"""Check if Claude CLI is authenticated."""
|
||
try:
|
||
result = subprocess.run(
|
||
["claude", "auth", "status"],
|
||
capture_output=True,
|
||
text=True,
|
||
timeout=5
|
||
)
|
||
if result.returncode == 0 and "Authenticated" in result.stdout:
|
||
print("✓ Claude CLI authenticated")
|
||
return True
|
||
else:
|
||
self.warnings.append("Claude CLI not authenticated (run: claude auth login)")
|
||
return False
|
||
except FileNotFoundError:
|
||
self.warnings.append("Claude CLI not found (install from: https://claude.ai/download)")
|
||
return False
|
||
except Exception as e:
|
||
self.warnings.append(f"Error checking Claude CLI: {e}")
|
||
return False
|
||
|
||
def check_python_version(self) -> bool:
|
||
"""Check if Python version is compatible."""
|
||
version_info = sys.version_info
|
||
if version_info >= (3, 10):
|
||
print(f"✓ Python {version_info.major}.{version_info.minor}.{version_info.micro}")
|
||
return True
|
||
else:
|
||
self.errors.append(
|
||
f"Python 3.10+ required (found {version_info.major}.{version_info.minor}.{version_info.micro})"
|
||
)
|
||
return False
|
||
|
||
def check_env_file(self) -> bool:
|
||
"""Check if .env file exists (for API key storage)."""
|
||
env_path = Path(".env")
|
||
if env_path.exists():
|
||
print(f"✓ .env file found")
|
||
return True
|
||
else:
|
||
self.warnings.append(".env file not found (create one if using API mode)")
|
||
return False
|
||
|
||
def check_config_file(self) -> bool:
|
||
"""Check if adapter config exists."""
|
||
config_path = Path("config/adapters.local.yaml")
|
||
if config_path.exists():
|
||
print(f"✓ Config file found: {config_path}")
|
||
return True
|
||
else:
|
||
self.warnings.append(
|
||
"config/adapters.local.yaml not found (run: python ajarbot.py --init)"
|
||
)
|
||
return False
|
||
|
||
def set_default_llm_mode(self):
|
||
"""Set default LLM mode to agent-sdk if not specified."""
|
||
if "AJARBOT_LLM_MODE" not in os.environ:
|
||
os.environ["AJARBOT_LLM_MODE"] = "agent-sdk"
|
||
print("ℹ Using LLM mode: agent-sdk (default)")
|
||
else:
|
||
mode = os.environ["AJARBOT_LLM_MODE"]
|
||
print(f"ℹ Using LLM mode: {mode} (from environment)")
|
||
|
||
def run_all_checks(self) -> bool:
|
||
"""Run all pre-flight checks. Returns True if safe to proceed."""
|
||
print("=" * 60)
|
||
print("Ajarbot Pre-Flight Checks")
|
||
print("=" * 60)
|
||
print()
|
||
|
||
# Critical checks
|
||
self.check_python_version()
|
||
|
||
# LLM mode dependent checks
|
||
llm_mode = os.environ.get("AJARBOT_LLM_MODE", "agent-sdk")
|
||
|
||
if llm_mode == "agent-sdk":
|
||
print("\n[Agent SDK Mode Checks]")
|
||
self.check_nodejs()
|
||
self.check_claude_cli_auth()
|
||
elif llm_mode == "api":
|
||
print("\n[API Mode Checks]")
|
||
has_env = self.check_env_file()
|
||
if has_env:
|
||
if not os.environ.get("ANTHROPIC_API_KEY"):
|
||
self.errors.append("ANTHROPIC_API_KEY not set in .env file (required for API mode)")
|
||
else:
|
||
self.errors.append(".env file with ANTHROPIC_API_KEY required for API mode")
|
||
|
||
# Common checks
|
||
print("\n[Configuration Checks]")
|
||
self.check_config_file()
|
||
|
||
# Display results
|
||
print()
|
||
print("=" * 60)
|
||
|
||
if self.errors:
|
||
print("ERRORS (must fix before running):")
|
||
for error in self.errors:
|
||
print(f" ✗ {error}")
|
||
print()
|
||
return False
|
||
|
||
if self.warnings:
|
||
print("WARNINGS (optional, but recommended):")
|
||
for warning in self.warnings:
|
||
print(f" ⚠ {warning}")
|
||
print()
|
||
|
||
print("Pre-flight checks complete!")
|
||
print("=" * 60)
|
||
print()
|
||
return True
|
||
|
||
|
||
def main():
|
||
"""Main entry point with pre-flight checks."""
|
||
# Set default LLM mode before checks
|
||
checker = PreflightCheck()
|
||
checker.set_default_llm_mode()
|
||
|
||
# Special commands that bypass pre-flight checks
|
||
bypass_commands = ["--init", "--help", "-h"]
|
||
if any(arg in sys.argv for arg in bypass_commands):
|
||
# Import and run bot_runner directly
|
||
from bot_runner import main as bot_main
|
||
bot_main()
|
||
return
|
||
|
||
# Run pre-flight checks for normal operation
|
||
if not checker.run_all_checks():
|
||
print("\nPre-flight checks failed. Please fix the errors above.")
|
||
sys.exit(1)
|
||
|
||
# All checks passed - delegate to bot_runner
|
||
print("Launching ajarbot...\n")
|
||
from bot_runner import main as bot_main
|
||
bot_main()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|