From 3f3b5ce28fcda938c3dcc956589b0d8e8672be14 Mon Sep 17 00:00:00 2001 From: Mikkel Georgsen Date: Fri, 16 Jan 2026 18:38:06 +0000 Subject: [PATCH] feat(03-01): implement /projects and /project new handlers - /projects lists all projects with name, ID, models - /project new "Name" creates project with confirmation - Registered handlers in __init__.py Co-Authored-By: Claude Opus 4.5 --- src/moai/bot/handlers/__init__.py | 5 ++ src/moai/bot/handlers/projects.py | 86 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/moai/bot/handlers/projects.py diff --git a/src/moai/bot/handlers/__init__.py b/src/moai/bot/handlers/__init__.py index 27ac0fa..68c7fa6 100644 --- a/src/moai/bot/handlers/__init__.py +++ b/src/moai/bot/handlers/__init__.py @@ -7,6 +7,7 @@ project management, discussion commands, and export functionality. from telegram.ext import Application, CommandHandler from moai.bot.handlers.commands import help_command, start_command +from moai.bot.handlers.projects import project_command, projects_command from moai.bot.handlers.status import status_command @@ -22,3 +23,7 @@ def register_handlers(app: Application) -> None: # Status app.add_handler(CommandHandler("status", status_command)) + + # Project management + app.add_handler(CommandHandler("projects", projects_command)) + app.add_handler(CommandHandler("project", project_command)) diff --git a/src/moai/bot/handlers/projects.py b/src/moai/bot/handlers/projects.py new file mode 100644 index 0000000..21ec17e --- /dev/null +++ b/src/moai/bot/handlers/projects.py @@ -0,0 +1,86 @@ +"""Project management handlers for MoAI bot.""" + +import re + +from telegram import Update +from telegram.ext import ContextTypes + +from moai.core.services.project import create_project, list_projects + + +async def projects_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Handle /projects command - list all projects.""" + projects = await list_projects() + + if not projects: + await update.message.reply_text('No projects yet. Use /project new "Name" to create one.') + return + + lines = ["*Your Projects:*\n"] + for p in projects: + models_str = ", ".join(p.models) if p.models else "none" + lines.append(f"• *{p.name}*\n ID: `{p.id[:8]}...`\n Models: {models_str}") + + await update.message.reply_text("\n".join(lines), parse_mode="Markdown") + + +async def project_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: + """Handle /project subcommands. + + Subcommands: + new "Name" - Create new project + select - Switch to project (future) + delete - Delete project (future) + models - Set models (future) + info - Show current project (future) + """ + args = context.args or [] + + if not args: + await update.message.reply_text( + "Usage:\n" + '/project new "Name" - Create project\n' + "/project select - Switch project\n" + "/project info - Show current project" + ) + return + + subcommand = args[0].lower() + + if subcommand == "new": + await _handle_project_new(update, context, args[1:]) + else: + await update.message.reply_text( + f"Unknown subcommand: {subcommand}\nAvailable: new, select, delete, models, info" + ) + + +async def _handle_project_new( + update: Update, context: ContextTypes.DEFAULT_TYPE, args: list[str] +) -> None: + """Handle /project new "Name" command.""" + if not args: + await update.message.reply_text('Usage: /project new "Project Name"') + return + + # Join args and extract quoted name + text = " ".join(args) + match = re.match(r'^"([^"]+)"', text) or re.match(r"^'([^']+)'", text) + + if match: + name = match.group(1) + else: + # No quotes - use the first arg as name + name = args[0] + + project = await create_project(name) + models_str = ", ".join(project.models) + + await update.message.reply_text( + f"*Project Created*\n\n" + f"Name: {project.name}\n" + f"ID: `{project.id}`\n" + f"Models: {models_str}\n\n" + f"Use /project select {project.id[:8]} to switch to this project.", + parse_mode="Markdown", + )