aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/db
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-28 07:26:43 -0800
committerClawd <ai@clawd.bot>2026-02-28 07:26:43 -0800
commit332e5cec2992fefb302251962a3ceca38437a110 (patch)
tree7802469abb929e741ccbecd989828f1baf37145f /src/main/db
parentd2d6fd48d97b876c9a9a424c9d2cc1de5352b144 (diff)
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
Diffstat (limited to 'src/main/db')
-rw-r--r--src/main/db/index.ts31
-rw-r--r--src/main/db/projects.ts38
-rw-r--r--src/main/db/schema.ts35
-rw-r--r--src/main/db/sessions.ts106
4 files changed, 210 insertions, 0 deletions
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 @@
1import Database from "better-sqlite3";
2import { app } from "electron";
3import path from "node:path";
4import fs from "node:fs";
5import { initSchema } from "./schema";
6
7let db: Database.Database | null = null;
8
9export function getDb(): Database.Database {
10 if (db) return db;
11
12 const dbDir = app.getPath("userData");
13 if (!fs.existsSync(dbDir)) {
14 fs.mkdirSync(dbDir, { recursive: true });
15 }
16
17 const dbPath = path.join(dbDir, "claude-flow.db");
18 db = new Database(dbPath);
19 db.pragma("journal_mode = WAL");
20 db.pragma("foreign_keys = ON");
21
22 initSchema(db);
23 return db;
24}
25
26export function closeDb() {
27 if (db) {
28 db.close();
29 db = null;
30 }
31}
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 @@
1import { getDb } from "./index";
2import { v4 as uuid } from "uuid";
3
4export interface Project {
5 id: string;
6 name: string;
7 path: string;
8 created_at: number;
9 updated_at: number;
10}
11
12export function listProjects(): Project[] {
13 return getDb()
14 .prepare("SELECT * FROM projects ORDER BY updated_at DESC")
15 .all() as Project[];
16}
17
18export function getProject(id: string): Project | undefined {
19 return getDb()
20 .prepare("SELECT * FROM projects WHERE id = ?")
21 .get(id) as Project | undefined;
22}
23
24export function createProject(name: string, projectPath: string): Project {
25 const db = getDb();
26 const id = uuid();
27 const now = Math.floor(Date.now() / 1000);
28
29 db.prepare(
30 "INSERT INTO projects (id, name, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
31 ).run(id, name, projectPath, now, now);
32
33 return { id, name, path: projectPath, created_at: now, updated_at: now };
34}
35
36export function deleteProject(id: string): void {
37 getDb().prepare("DELETE FROM projects WHERE id = ?").run(id);
38}
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 @@
1import Database from "better-sqlite3";
2
3export function initSchema(db: Database.Database) {
4 db.exec(`
5 CREATE TABLE IF NOT EXISTS projects (
6 id TEXT PRIMARY KEY,
7 name TEXT NOT NULL,
8 path TEXT NOT NULL,
9 created_at INTEGER NOT NULL DEFAULT (unixepoch()),
10 updated_at INTEGER NOT NULL DEFAULT (unixepoch())
11 );
12
13 CREATE TABLE IF NOT EXISTS sessions (
14 id TEXT PRIMARY KEY,
15 project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
16 name TEXT NOT NULL,
17 phase TEXT NOT NULL DEFAULT 'research',
18 claude_session_id TEXT,
19 permission_mode TEXT NOT NULL DEFAULT 'acceptEdits',
20 created_at INTEGER NOT NULL DEFAULT (unixepoch()),
21 updated_at INTEGER NOT NULL DEFAULT (unixepoch())
22 );
23
24 CREATE TABLE IF NOT EXISTS messages (
25 id TEXT PRIMARY KEY,
26 session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
27 role TEXT NOT NULL,
28 content TEXT NOT NULL,
29 created_at INTEGER NOT NULL DEFAULT (unixepoch())
30 );
31
32 CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
33 CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
34 `);
35}
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 @@
1import { getDb } from "./index";
2import { v4 as uuid } from "uuid";
3
4export type Phase = "research" | "plan" | "implement";
5export type PermissionMode = "acceptEdits" | "bypassPermissions";
6
7export interface Session {
8 id: string;
9 project_id: string;
10 name: string;
11 phase: Phase;
12 claude_session_id: string | null;
13 permission_mode: PermissionMode;
14 created_at: number;
15 updated_at: number;
16}
17
18export interface Message {
19 id: string;
20 session_id: string;
21 role: "user" | "assistant";
22 content: string;
23 created_at: number;
24}
25
26export function listSessions(projectId: string): Session[] {
27 return getDb()
28 .prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY updated_at DESC")
29 .all(projectId) as Session[];
30}
31
32export function getSession(id: string): Session | undefined {
33 return getDb()
34 .prepare("SELECT * FROM sessions WHERE id = ?")
35 .get(id) as Session | undefined;
36}
37
38export function createSession(projectId: string, name: string): Session {
39 const db = getDb();
40 const id = uuid();
41 const now = Math.floor(Date.now() / 1000);
42
43 db.prepare(
44 `INSERT INTO sessions (id, project_id, name, phase, permission_mode, created_at, updated_at)
45 VALUES (?, ?, ?, 'research', 'acceptEdits', ?, ?)`
46 ).run(id, projectId, name, now, now);
47
48 return {
49 id,
50 project_id: projectId,
51 name,
52 phase: "research",
53 claude_session_id: null,
54 permission_mode: "acceptEdits",
55 created_at: now,
56 updated_at: now,
57 };
58}
59
60export function updateSession(
61 id: string,
62 updates: Partial<Pick<Session, "name" | "phase" | "claude_session_id" | "permission_mode">>
63): void {
64 const db = getDb();
65 const sets: string[] = [];
66 const values: any[] = [];
67
68 for (const [key, value] of Object.entries(updates)) {
69 if (value !== undefined) {
70 sets.push(`${key} = ?`);
71 values.push(value);
72 }
73 }
74
75 if (sets.length > 0) {
76 sets.push("updated_at = ?");
77 values.push(Math.floor(Date.now() / 1000));
78 values.push(id);
79 db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...values);
80 }
81}
82
83export function deleteSession(id: string): void {
84 getDb().prepare("DELETE FROM sessions WHERE id = ?").run(id);
85}
86
87// Messages
88export function listMessages(sessionId: string): Message[] {
89 return getDb()
90 .prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC")
91 .all(sessionId) as Message[];
92}
93
94export function addMessage(sessionId: string, role: Message["role"], content: string): Message {
95 const db = getDb();
96 const id = uuid();
97 const now = Math.floor(Date.now() / 1000);
98
99 db.prepare(
100 "INSERT INTO messages (id, session_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)"
101 ).run(id, sessionId, role, content, now);
102
103 db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId);
104
105 return { id, session_id: sessionId, role, content, created_at: now };
106}