""" 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()