From 332e5cec2992fefb302251962a3ceca38437a110 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 28 Feb 2026 07:26:43 -0800 Subject: Phase 2: Claude integration layer - Add @anthropic-ai/claude-agent-sdk dependency - Implement src/main/claude/phases.ts with phase configs (research/plan/implement) - Implement src/main/claude/index.ts with SDK wrapper - query() integration with session management - Session resume support - Artifact read/write utilities - Phase advancement logic --- src/main/db/index.ts | 31 ++++++++++++++ src/main/db/projects.ts | 38 +++++++++++++++++ src/main/db/schema.ts | 35 ++++++++++++++++ src/main/db/sessions.ts | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 210 insertions(+) create mode 100644 src/main/db/index.ts create mode 100644 src/main/db/projects.ts create mode 100644 src/main/db/schema.ts create mode 100644 src/main/db/sessions.ts (limited to 'src/main/db') diff --git a/src/main/db/index.ts b/src/main/db/index.ts new file mode 100644 index 0000000..a77cdd4 --- /dev/null +++ b/src/main/db/index.ts @@ -0,0 +1,31 @@ +import Database from "better-sqlite3"; +import { app } from "electron"; +import path from "node:path"; +import fs from "node:fs"; +import { initSchema } from "./schema"; + +let db: Database.Database | null = null; + +export function getDb(): Database.Database { + if (db) return db; + + const dbDir = app.getPath("userData"); + if (!fs.existsSync(dbDir)) { + fs.mkdirSync(dbDir, { recursive: true }); + } + + const dbPath = path.join(dbDir, "claude-flow.db"); + db = new Database(dbPath); + db.pragma("journal_mode = WAL"); + db.pragma("foreign_keys = ON"); + + initSchema(db); + return db; +} + +export function closeDb() { + if (db) { + db.close(); + db = null; + } +} diff --git a/src/main/db/projects.ts b/src/main/db/projects.ts new file mode 100644 index 0000000..88ef2f6 --- /dev/null +++ b/src/main/db/projects.ts @@ -0,0 +1,38 @@ +import { getDb } from "./index"; +import { v4 as uuid } from "uuid"; + +export interface Project { + id: string; + name: string; + path: string; + created_at: number; + updated_at: number; +} + +export function listProjects(): Project[] { + return getDb() + .prepare("SELECT * FROM projects ORDER BY updated_at DESC") + .all() as Project[]; +} + +export function getProject(id: string): Project | undefined { + return getDb() + .prepare("SELECT * FROM projects WHERE id = ?") + .get(id) as Project | undefined; +} + +export function createProject(name: string, projectPath: string): Project { + const db = getDb(); + const id = uuid(); + const now = Math.floor(Date.now() / 1000); + + db.prepare( + "INSERT INTO projects (id, name, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?)" + ).run(id, name, projectPath, now, now); + + return { id, name, path: projectPath, created_at: now, updated_at: now }; +} + +export function deleteProject(id: string): void { + getDb().prepare("DELETE FROM projects WHERE id = ?").run(id); +} diff --git a/src/main/db/schema.ts b/src/main/db/schema.ts new file mode 100644 index 0000000..c2093f9 --- /dev/null +++ b/src/main/db/schema.ts @@ -0,0 +1,35 @@ +import Database from "better-sqlite3"; + +export function initSchema(db: Database.Database) { + db.exec(` + CREATE TABLE IF NOT EXISTS projects ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + path TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (unixepoch()), + updated_at INTEGER NOT NULL DEFAULT (unixepoch()) + ); + + CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + name TEXT NOT NULL, + phase TEXT NOT NULL DEFAULT 'research', + claude_session_id TEXT, + permission_mode TEXT NOT NULL DEFAULT 'acceptEdits', + created_at INTEGER NOT NULL DEFAULT (unixepoch()), + updated_at INTEGER NOT NULL DEFAULT (unixepoch()) + ); + + CREATE TABLE IF NOT EXISTS messages ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, + role TEXT NOT NULL, + content TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (unixepoch()) + ); + + CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id); + CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id); + `); +} diff --git a/src/main/db/sessions.ts b/src/main/db/sessions.ts new file mode 100644 index 0000000..684bb9e --- /dev/null +++ b/src/main/db/sessions.ts @@ -0,0 +1,106 @@ +import { getDb } from "./index"; +import { v4 as uuid } from "uuid"; + +export type Phase = "research" | "plan" | "implement"; +export type PermissionMode = "acceptEdits" | "bypassPermissions"; + +export interface Session { + id: string; + project_id: string; + name: string; + phase: Phase; + claude_session_id: string | null; + permission_mode: PermissionMode; + created_at: number; + updated_at: number; +} + +export interface Message { + id: string; + session_id: string; + role: "user" | "assistant"; + content: string; + created_at: number; +} + +export function listSessions(projectId: string): Session[] { + return getDb() + .prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY updated_at DESC") + .all(projectId) as Session[]; +} + +export function getSession(id: string): Session | undefined { + return getDb() + .prepare("SELECT * FROM sessions WHERE id = ?") + .get(id) as Session | undefined; +} + +export function createSession(projectId: string, name: string): Session { + const db = getDb(); + const id = uuid(); + const now = Math.floor(Date.now() / 1000); + + db.prepare( + `INSERT INTO sessions (id, project_id, name, phase, permission_mode, created_at, updated_at) + VALUES (?, ?, ?, 'research', 'acceptEdits', ?, ?)` + ).run(id, projectId, name, now, now); + + return { + id, + project_id: projectId, + name, + phase: "research", + claude_session_id: null, + permission_mode: "acceptEdits", + created_at: now, + updated_at: now, + }; +} + +export function updateSession( + id: string, + updates: Partial> +): void { + const db = getDb(); + const sets: string[] = []; + const values: any[] = []; + + for (const [key, value] of Object.entries(updates)) { + if (value !== undefined) { + sets.push(`${key} = ?`); + values.push(value); + } + } + + if (sets.length > 0) { + sets.push("updated_at = ?"); + values.push(Math.floor(Date.now() / 1000)); + values.push(id); + db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...values); + } +} + +export function deleteSession(id: string): void { + getDb().prepare("DELETE FROM sessions WHERE id = ?").run(id); +} + +// Messages +export function listMessages(sessionId: string): Message[] { + return getDb() + .prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC") + .all(sessionId) as Message[]; +} + +export function addMessage(sessionId: string, role: Message["role"], content: string): Message { + const db = getDb(); + const id = uuid(); + const now = Math.floor(Date.now() / 1000); + + db.prepare( + "INSERT INTO messages (id, session_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)" + ).run(id, sessionId, role, content, now); + + db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId); + + return { id, session_id: sessionId, role, content, created_at: now }; +} -- cgit v1.2.3