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 <noreply@anthropic.com>
This commit is contained in:
Mikkel Georgsen 2026-01-16 18:38:06 +00:00
parent 718dcea7dc
commit 3f3b5ce28f
2 changed files with 91 additions and 0 deletions

View file

@ -7,6 +7,7 @@ project management, discussion commands, and export functionality.
from telegram.ext import Application, CommandHandler from telegram.ext import Application, CommandHandler
from moai.bot.handlers.commands import help_command, start_command 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 from moai.bot.handlers.status import status_command
@ -22,3 +23,7 @@ def register_handlers(app: Application) -> None:
# Status # Status
app.add_handler(CommandHandler("status", status_command)) app.add_handler(CommandHandler("status", status_command))
# Project management
app.add_handler(CommandHandler("projects", projects_command))
app.add_handler(CommandHandler("project", project_command))

View file

@ -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 <id|name> - Switch to project (future)
delete <id> - Delete project (future)
models <list> - 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 <id> - 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",
)