"""ClaudeTools Discord Bot - Main Entry Point.""" import asyncio import logging import sys import discord from discord.ext import commands from dotenv import load_dotenv from bot.config import settings from bot.claude.client import ClaudeAgentManager from bot.handlers.message_handler import MessageHandler load_dotenv() logging.basicConfig( level=getattr(logging, settings.log_level.upper()), format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[logging.StreamHandler(sys.stdout)], ) 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__) intents = discord.Intents.default() intents.message_content = True intents.guilds = True intents.members = True bot = commands.Bot(command_prefix="!", intents=intents) agent_manager = ClaudeAgentManager() message_handler: MessageHandler | None = None @bot.event async def on_ready(): global message_handler logger.info("[OK] Bot connected as %s (ID: %d)", bot.user.name, bot.user.id) logger.info("[INFO] Connected to %d guild(s)", len(bot.guilds)) try: settings.validate_paths() logger.info("[OK] ClaudeTools workspace: %s", settings.claudetools_root) except FileNotFoundError as e: logger.error("[ERROR] Path validation failed: %s", e) message_handler = MessageHandler(bot, agent_manager) await bot.change_presence( activity=discord.Activity( type=discord.ActivityType.watching, name="for @mentions | ClaudeTools Agent", ) ) logger.info("[OK] Bot is ready and listening for mentions") for guild in bot.guilds: logger.info("[DEBUG] Guild: %s (id=%d) channels=%d", guild.name, guild.id, len(guild.text_channels)) for ch in guild.text_channels[:10]: perms = ch.permissions_for(guild.me) logger.info("[DEBUG] #%s view=%s read=%s send=%s", ch.name, perms.view_channel, perms.read_message_history, perms.send_messages) @bot.event async def on_message(message: discord.Message): logger.info("[DEBUG] on_message fired: author=%s bot=%s channel=%s content_len=%d mentions=%d", message.author.name, message.author.bot, getattr(message.channel, 'name', 'DM'), len(message.content), len(message.mentions)) if message.author.bot: return is_mention = bot.user in message.mentions is_in_bot_thread = ( isinstance(message.channel, discord.Thread) and message.channel.owner_id == bot.user.id ) if is_mention or is_in_bot_thread: trigger = "mention" if is_mention else "thread-followup" logger.info( "[INFO] %s by %s in #%s: %s", trigger, message.author.name, message.channel.name, message.content[:100], ) await message_handler.handle_mention(message) return await bot.process_commands(message) @bot.event async def on_error(event: str, *args, **kwargs): logger.error("[ERROR] Error in %s", event, exc_info=sys.exc_info()) async def main(): try: logger.info("[INFO] Starting ClaudeTools Discord Bot") logger.info("[INFO] Claude model: %s", settings.claude_model) logger.info("[INFO] Workspace: %s", settings.claudetools_root) async with bot: await bot.start(settings.discord_token) except KeyboardInterrupt: logger.info("[INFO] Keyboard interrupt, shutting down") except Exception as e: logger.error("[ERROR] Fatal error: %s", e, exc_info=True) raise finally: await agent_manager.shutdown() logger.info("[OK] Bot shut down complete") if __name__ == "__main__": asyncio.run(main())