diff --git a/src/handlers/commands.ts b/src/handlers/commands.ts new file mode 100644 index 0000000..9db8404 --- /dev/null +++ b/src/handlers/commands.ts @@ -0,0 +1,175 @@ +import { type Context, InlineKeyboard } from "grammy"; +import { SessionManager } from "../services/session-manager.js"; +import { ClaudeService } from "../services/claude.js"; + +export function registerCommands( + manager: SessionManager, + claude: ClaudeService, + onCommand: Record Promise>, +): void { + onCommand["start"] = async (ctx) => { + const arg = ctx.match as string; + if (!arg) { + await ctx.reply("Usage: /start "); + return; + } + + const resolved = manager.resolveProjectPath(arg); + if (!resolved) { + await ctx.reply("Could not resolve project path."); + return; + } + + if (manager.getAllSessions()[resolved.name]) { + await ctx.reply( + `Session "${resolved.name}" already exists. Use /switch ${resolved.name}`, + ); + return; + } + + await ctx.reply(`Starting session "${resolved.name}" in ${resolved.path}...`); + + try { + const { sessionId, result } = await claude.createSession( + "You are now connected via Telegram. Briefly confirm you're ready and state the project directory.", + resolved.path, + ); + manager.createSession(resolved.name, resolved.path, sessionId); + await ctx.reply(`Session "${resolved.name}" created.\n\n${result}`); + } catch (err) { + await ctx.reply(`Failed to create session: ${err}`); + } + }; + + onCommand["stop"] = async (ctx) => { + const arg = ctx.match as string; + const name = arg || manager.activeSession; + if (!name) { + await ctx.reply("No active session. Nothing to stop."); + return; + } + if (manager.stopSession(name)) { + const active = manager.activeSession; + let msg = `Session "${name}" stopped.`; + if (active) msg += ` Switched to "${active}".`; + await ctx.reply(msg); + } else { + await ctx.reply(`Session "${name}" not found.`); + } + }; + + onCommand["stopall"] = async (ctx) => { + const count = manager.stopAll(); + await ctx.reply(`Stopped ${count} session(s).`); + }; + + onCommand["switch"] = async (ctx) => { + const arg = ctx.match as string; + if (!arg) { + await ctx.reply("Usage: /switch "); + return; + } + if (manager.switchSession(arg)) { + await ctx.reply(`Switched to "${arg}".`); + } else { + await ctx.reply(`Session "${arg}" not found. Use /sessions to see active sessions.`); + } + }; + + onCommand["sessions"] = async (ctx) => { + const sessions = manager.getAllSessions(); + const names = Object.keys(sessions); + if (names.length === 0) { + await ctx.reply("No active sessions."); + return; + } + + const active = manager.activeSession; + const lines = names.map((name) => { + const s = sessions[name]; + const marker = name === active ? "►" : " "; + return `${marker} ${name} — ${s.cwd} (${s.status})`; + }); + + const keyboard = new InlineKeyboard(); + for (const name of names) { + keyboard.text(name, `switch:${name}`); + } + + await ctx.reply(lines.join("\n"), { reply_markup: keyboard }); + }; + + onCommand["status"] = async (ctx) => { + const info = manager.activeSessionInfo; + if (!info) { + await ctx.reply("No active session."); + return; + } + const name = manager.activeSession!; + await ctx.reply( + `Session: ${name}\nPath: ${info.cwd}\nStatus: ${info.status}`, + ); + }; + + onCommand["save"] = async (ctx) => { + const arg = ctx.match as string; + const parts = arg.split(/\s+/, 2); + if (parts.length < 2) { + await ctx.reply("Usage: /save "); + return; + } + manager.saveProject(parts[0], parts[1]); + await ctx.reply(`Project "${parts[0]}" saved → ${parts[1]}`); + }; + + onCommand["remove"] = async (ctx) => { + const arg = ctx.match as string; + if (!arg) { + await ctx.reply("Usage: /remove "); + return; + } + if (manager.removeProject(arg)) { + await ctx.reply(`Project "${arg}" removed.`); + } else { + await ctx.reply(`Project "${arg}" not found.`); + } + }; + + onCommand["projects"] = async (ctx) => { + const projects = manager.getProjects(); + const names = Object.keys(projects); + if (names.length === 0) { + await ctx.reply("No saved projects."); + return; + } + + const lines = names.map((n) => `${n} — ${projects[n]}`); + const keyboard = new InlineKeyboard(); + for (const name of names) { + keyboard.text(`Start ${name}`, `start:${name}`); + } + + await ctx.reply(lines.join("\n"), { reply_markup: keyboard }); + }; + + onCommand["help"] = async (ctx) => { + await ctx.reply( + [ + "Session commands:", + " /start — start new session", + " /stop [name] — stop session", + " /stopall — stop all sessions", + " /switch — switch active session", + " /sessions — list sessions", + " /status — current session info", + "", + "Project bookmarks:", + " /save — save bookmark", + " /remove — remove bookmark", + " /projects — list bookmarks", + "", + "Any other text is sent to the active session.", + ].join("\n"), + ); + }; +}