"""ClaudeTools Discord Bot - Main Entry Point.""" import asyncio import logging import sys from pathlib import Path import discord from discord.ext import commands from dotenv import load_dotenv from bot.config import settings from bot.claude.client import ClaudeClient from bot.handlers.message_handler import MessageHandler # Load environment variables load_dotenv() # Configure logging logging.basicConfig( level=getattr(logging, settings.log_level.upper()), format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ logging.StreamHandler(sys.stdout), ] ) # Add file handler if log file specified if settings.log_file: settings.log_file.parent.mkdir(parents=True, exist_ok=True) file_handler = logging.FileHandler(settings.log_file) file_handler.setFormatter( logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ) logging.getLogger().addHandler(file_handler) logger = logging.getLogger(__name__) # Discord bot setup with required intents intents = discord.Intents.default() intents.message_content = True # Required to read message content intents.guilds = True intents.members = True bot = commands.Bot(command_prefix="!", intents=intents) # Initialize Claude client claude_client = ClaudeClient() # Initialize message handler message_handler: MessageHandler = None @bot.event async def on_ready(): """Called when the bot successfully connects to Discord.""" global message_handler logger.info(f"Bot connected as {bot.user.name} (ID: {bot.user.id})") logger.info(f"Connected to {len(bot.guilds)} guild(s)") # Validate paths try: settings.validate_paths() logger.info("Path validation successful") logger.info(f"Vault path: {settings.vault_path}") logger.info(f"ClaudeTools root: {settings.claudetools_root}") logger.info(f"Git Bash: {settings.git_bash_path}") except FileNotFoundError as e: logger.error(f"Path validation failed: {e}") logger.error("Bot will continue but some features may not work") # Initialize message handler message_handler = MessageHandler(bot, claude_client) # Set bot status await bot.change_presence( activity=discord.Activity( type=discord.ActivityType.watching, name="for @mentions | ClaudeTools MSP Assistant" ) ) logger.info("Bot is ready and listening for mentions!") @bot.event async def on_message(message: discord.Message): """Called when a message is sent in a channel the bot can see.""" # Ignore messages from bots if message.author.bot: return # Check if bot was mentioned if bot.user in message.mentions: logger.info( f"Mentioned by {message.author.name} in #{message.channel.name}: " f"{message.content[:100]}" ) await message_handler.handle_mention(message) return # Process commands (for future slash commands) await bot.process_commands(message) @bot.event async def on_error(event: str, *args, **kwargs): """Called when an error occurs.""" logger.error(f"Error in {event}", exc_info=sys.exc_info()) async def main(): """Main entry point.""" try: logger.info("Starting ClaudeTools Discord Bot...") logger.info(f"Claude Model: {settings.claude_model}") logger.info(f"ClaudeTools API: {settings.claudetools_api_url}") # Start the bot async with bot: await bot.start(settings.discord_token) except KeyboardInterrupt: logger.info("Received keyboard interrupt, shutting down...") except Exception as e: logger.error(f"Fatal error: {e}", exc_info=True) raise finally: logger.info("Bot shut down complete") if __name__ == "__main__": asyncio.run(main())