import { ipcMain, dialog, type BrowserWindow } from "electron"; import * as projects from "../db/projects"; import * as sessions from "../db/sessions"; import * as claude from "../claude"; import * as settingsDb from "../db/settings"; import { createSessionBranch, ensureGitIgnore, ensureGitRepo, getCurrentBranch } from "../git"; import { discoverMcpTools } from "../mcp"; import type { UserPermissionMode, Phase } from "../claude/phases"; import { getDefaultSystemPromptTemplate } from "../claude/phases"; export function registerIpcHandlers(mainWindow: BrowserWindow): void { // Projects ipcMain.handle("projects:list", () => projects.listProjects()); ipcMain.handle("projects:create", (_, name: string, path: string) => projects.createProject(name, path) ); ipcMain.handle("projects:delete", (_, id: string) => projects.deleteProject(id) ); // Sessions ipcMain.handle("sessions:list", (_, projectId: string) => sessions.listSessions(projectId) ); ipcMain.handle("sessions:create", (_, projectId: string, name: string, phase?: Phase) => { const project = projects.getProject(projectId); if (!project) throw new Error("Project not found"); const session = sessions.createSession(projectId, name, phase); // Ensure .claude-flow/ is gitignored from day one. // Branch creation is deferred until the session advances to implement. try { ensureGitIgnore(project.path); } catch { /* non-fatal */ } return session; }); ipcMain.handle("sessions:delete", (_, id: string) => { const session = sessions.getSession(id); if (session) { // Clean up session artifacts from global storage claude.clearSessionArtifacts(session.project_id, id); } sessions.deleteSession(id); }); ipcMain.handle("sessions:rename", (_, id: string, name: string) => { sessions.updateSession(id, { name }); }); ipcMain.handle("sessions:get", (_, id: string) => sessions.getSession(id)); // Messages ipcMain.handle("messages:list", (_, sessionId: string) => sessions.listMessages(sessionId) ); // Chat ipcMain.handle( "chat:send", async (_, sessionId: string, message: string) => { const session = sessions.getSession(sessionId); if (!session) throw new Error("Session not found"); // Store user message sessions.addMessage(sessionId, "user", message); await claude.sendMessage({ session, message, onMessage: (msg) => { // Forward all messages to renderer mainWindow.webContents.send("claude:message", sessionId, msg); // Store assistant text messages if (msg.type === "assistant") { const content = (msg.message.content as Array<{ type: string; text?: string }>) .filter((c) => c.type === "text" && c.text) .map((c) => c.text!) .join("\n"); if (content) { sessions.addMessage(sessionId, "assistant", content); } } }, }); } ); ipcMain.handle("chat:interrupt", (_, sessionId: string) => { claude.interruptSession(sessionId); }); // Workflow ipcMain.handle("workflow:review", async (_, sessionId: string) => { const session = sessions.getSession(sessionId); if (!session) throw new Error("Session not found"); await claude.triggerReview(session, (msg) => { mainWindow.webContents.send("claude:message", sessionId, msg); }); }); ipcMain.handle("workflow:advance", (_, sessionId: string) => { const session = sessions.getSession(sessionId); if (!session) throw new Error("Session not found"); const nextPhase = claude.advancePhase(session); if (!nextPhase) return null; let git_branch = session.git_branch; if (nextPhase === "implement") { const project = projects.getProject(session.project_id); if (project) { const branchingSetting = settingsDb.getSetting("git.branchingEnabled"); const branchingEnabled = branchingSetting === "true"; // opt-in; default = off // Always ensure repo + gitignore so commits work regardless of mode try { ensureGitIgnore(project.path); } catch { /* non-fatal */ } try { ensureGitRepo(project.path); } catch { /* non-fatal */ } if (branchingEnabled) { // createSessionBranch internally calls ensureGitIgnore/ensureGitRepo again // (belt-and-suspenders), then checks out a new claude-flow/- branch const branchName = createSessionBranch(project.path, session.name, session.id); if (branchName) { sessions.updateSession(sessionId, { git_branch: branchName }); git_branch = branchName; } } else { // No new branch — commit to whatever branch is currently checked out. // Store the branch name so autoCommitTurn's boolean guard passes. const currentBranch = getCurrentBranch(project.path) ?? "main"; sessions.updateSession(sessionId, { git_branch: currentBranch }); git_branch = currentBranch; } } } return { phase: nextPhase, git_branch }; }); ipcMain.handle( "workflow:setPermissionMode", (_, sessionId: string, mode: string) => { sessions.updateSession(sessionId, { permission_mode: mode as UserPermissionMode, }); } ); // Session Artifacts (stored in ~/.claude-flow/) ipcMain.handle( "artifact:readSession", (_, projectId: string, sessionId: string, filename: string) => { return claude.readSessionArtifact(projectId, sessionId, filename); } ); ipcMain.handle( "artifact:writeSession", (_, projectId: string, sessionId: string, filename: string, content: string) => { claude.writeSessionArtifact(projectId, sessionId, filename, content); } ); // CLAUDE.md (stored in project) ipcMain.handle("claudemd:read", (_, projectPath: string) => { return claude.readClaudeMd(projectPath); }); ipcMain.handle("claudemd:write", (_, projectPath: string, content: string) => { claude.writeClaudeMd(projectPath, content); }); // Settings ipcMain.handle("settings:get", (_, keys: string[]) => settingsDb.getSettings(keys) ); ipcMain.handle("settings:set", (_, key: string, value: string) => settingsDb.setSetting(key, value) ); ipcMain.handle("settings:delete", (_, key: string) => settingsDb.deleteSetting(key) ); // Returns default prompt text (with {{artifactDir}} placeholder) for all phases ipcMain.handle("settings:getDefaultPrompts", () => ({ research: getDefaultSystemPromptTemplate("research"), plan: getDefaultSystemPromptTemplate("plan"), implement: getDefaultSystemPromptTemplate("implement"), })); // Dialogs ipcMain.handle("dialog:selectDirectory", async () => { const result = await dialog.showOpenDialog(mainWindow, { properties: ["openDirectory"], }); return result.canceled ? null : result.filePaths[0]; }); // MCP ipcMain.handle("mcp:discoverTools", async (_, config: { type: "stdio" | "sse" | "http"; command?: string; args?: string[]; env?: Record; url?: string; }) => { return discoverMcpTools(config); }); }