idea-manager 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/next.config.mjs +1 -1
- package/package.json +2 -3
- package/src/app/api/projects/[id]/brainstorm/route.ts +3 -0
- package/src/app/api/projects/[id]/git-sync/route.ts +2 -0
- package/src/app/api/projects/[id]/route.ts +4 -0
- package/src/app/api/projects/[id]/sub-projects/[subId]/route.ts +4 -0
- package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.ts +3 -0
- package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route.ts +3 -0
- package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route.ts +4 -0
- package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/route.ts +3 -0
- package/src/app/api/projects/[id]/sub-projects/route.ts +4 -0
- package/src/app/api/projects/route.ts +3 -0
- package/src/cli.ts +2 -0
- package/src/lib/db/index.ts +184 -10
- package/src/lib/db/schema.ts +2 -3
- package/src/lib/sync/exporter.ts +3 -2
- package/src/lib/sync/importer.ts +5 -3
- package/src/lib/sync/index.ts +4 -4
- package/src/lib/watcher.ts +2 -0
package/next.config.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idea-manager",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Turn free-form brainstorming into structured task trees with AI-generated prompts. Built-in MCP Server for autonomous AI agent execution. Local-first with SQLite, cross-PC sync via Git.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"brainstorm",
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
"@dnd-kit/sortable": "^10.0.0",
|
|
49
49
|
"@dnd-kit/utilities": "^3.2.2",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
51
|
-
"better-sqlite3": "^12.6.2",
|
|
52
51
|
"commander": "^14.0.3",
|
|
53
52
|
"nanoid": "^5.1.6",
|
|
54
53
|
"next": "16.1.6",
|
|
@@ -57,11 +56,11 @@
|
|
|
57
56
|
"react-dom": "19.2.3",
|
|
58
57
|
"react-markdown": "^10.1.0",
|
|
59
58
|
"remark-gfm": "^4.0.1",
|
|
59
|
+
"sql.js": "^1.14.1",
|
|
60
60
|
"tsx": "^4.21.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@tailwindcss/postcss": "^4",
|
|
64
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
65
64
|
"@types/node": "^20",
|
|
66
65
|
"@types/react": "^19",
|
|
67
66
|
"@types/react-dom": "^19",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { getBrainstorm, updateBrainstorm } from '@/lib/db/queries/brainstorms';
|
|
3
3
|
import { getProject } from '@/lib/db/queries/projects';
|
|
4
|
+
import { ensureDb } from '@/lib/db';
|
|
4
5
|
|
|
5
6
|
export async function GET(
|
|
6
7
|
_request: NextRequest,
|
|
7
8
|
{ params }: { params: Promise<{ id: string }> },
|
|
8
9
|
) {
|
|
10
|
+
await ensureDb();
|
|
9
11
|
const { id } = await params;
|
|
10
12
|
const project = getProject(id);
|
|
11
13
|
if (!project) {
|
|
@@ -20,6 +22,7 @@ export async function PUT(
|
|
|
20
22
|
request: NextRequest,
|
|
21
23
|
{ params }: { params: Promise<{ id: string }> },
|
|
22
24
|
) {
|
|
25
|
+
await ensureDb();
|
|
23
26
|
const { id } = await params;
|
|
24
27
|
const body = await request.json();
|
|
25
28
|
const { content } = body;
|
|
@@ -4,6 +4,7 @@ import { execFile } from 'child_process';
|
|
|
4
4
|
import { existsSync, readdirSync, statSync } from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import type { IGitSyncResult } from '@/types';
|
|
7
|
+
import { ensureDb } from '@/lib/db';
|
|
7
8
|
|
|
8
9
|
function gitPull(cwd: string): Promise<{ stdout: string; stderr: string }> {
|
|
9
10
|
return new Promise((resolve, reject) => {
|
|
@@ -68,6 +69,7 @@ export async function POST(
|
|
|
68
69
|
_request: NextRequest,
|
|
69
70
|
{ params }: { params: Promise<{ id: string }> },
|
|
70
71
|
) {
|
|
72
|
+
await ensureDb();
|
|
71
73
|
const { id } = await params;
|
|
72
74
|
const project = getProject(id);
|
|
73
75
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { getProject, updateProject, deleteProject } from '@/lib/db/queries/projects';
|
|
3
|
+
import { ensureDb } from '@/lib/db';
|
|
3
4
|
|
|
4
5
|
export async function GET(
|
|
5
6
|
_request: NextRequest,
|
|
6
7
|
{ params }: { params: Promise<{ id: string }> },
|
|
7
8
|
) {
|
|
9
|
+
await ensureDb();
|
|
8
10
|
const { id } = await params;
|
|
9
11
|
const project = getProject(id);
|
|
10
12
|
if (!project) {
|
|
@@ -17,6 +19,7 @@ export async function PUT(
|
|
|
17
19
|
request: NextRequest,
|
|
18
20
|
{ params }: { params: Promise<{ id: string }> },
|
|
19
21
|
) {
|
|
22
|
+
await ensureDb();
|
|
20
23
|
const { id } = await params;
|
|
21
24
|
const body = await request.json();
|
|
22
25
|
const project = updateProject(id, body);
|
|
@@ -30,6 +33,7 @@ export async function DELETE(
|
|
|
30
33
|
_request: NextRequest,
|
|
31
34
|
{ params }: { params: Promise<{ id: string }> },
|
|
32
35
|
) {
|
|
36
|
+
await ensureDb();
|
|
33
37
|
const { id } = await params;
|
|
34
38
|
const deleted = deleteProject(id);
|
|
35
39
|
if (!deleted) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { getSubProject, updateSubProject, deleteSubProject } from '@/lib/db/queries/sub-projects';
|
|
3
|
+
import { ensureDb } from '@/lib/db';
|
|
3
4
|
|
|
4
5
|
export async function GET(
|
|
5
6
|
_request: NextRequest,
|
|
6
7
|
{ params }: { params: Promise<{ id: string; subId: string }> },
|
|
7
8
|
) {
|
|
9
|
+
await ensureDb();
|
|
8
10
|
const { subId } = await params;
|
|
9
11
|
const sp = getSubProject(subId);
|
|
10
12
|
if (!sp) {
|
|
@@ -17,6 +19,7 @@ export async function PUT(
|
|
|
17
19
|
request: NextRequest,
|
|
18
20
|
{ params }: { params: Promise<{ id: string; subId: string }> },
|
|
19
21
|
) {
|
|
22
|
+
await ensureDb();
|
|
20
23
|
const { subId } = await params;
|
|
21
24
|
const body = await request.json();
|
|
22
25
|
const sp = updateSubProject(subId, body);
|
|
@@ -30,6 +33,7 @@ export async function DELETE(
|
|
|
30
33
|
_request: NextRequest,
|
|
31
34
|
{ params }: { params: Promise<{ id: string; subId: string }> },
|
|
32
35
|
) {
|
|
36
|
+
await ensureDb();
|
|
33
37
|
const { subId } = await params;
|
|
34
38
|
const deleted = deleteSubProject(subId);
|
|
35
39
|
if (!deleted) {
|
|
@@ -5,11 +5,13 @@ import { getTaskPrompt } from '@/lib/db/queries/task-prompts';
|
|
|
5
5
|
import { getBrainstorm } from '@/lib/db/queries/brainstorms';
|
|
6
6
|
import { getProject } from '@/lib/db/queries/projects';
|
|
7
7
|
import { runAgent } from '@/lib/ai/client';
|
|
8
|
+
import { ensureDb } from '@/lib/db';
|
|
8
9
|
|
|
9
10
|
export async function GET(
|
|
10
11
|
_request: NextRequest,
|
|
11
12
|
{ params }: { params: Promise<{ id: string; subId: string; taskId: string }> },
|
|
12
13
|
) {
|
|
14
|
+
await ensureDb();
|
|
13
15
|
const { taskId } = await params;
|
|
14
16
|
const conversations = getTaskConversations(taskId);
|
|
15
17
|
return NextResponse.json(conversations);
|
|
@@ -19,6 +21,7 @@ export async function POST(
|
|
|
19
21
|
request: NextRequest,
|
|
20
22
|
{ params }: { params: Promise<{ id: string; subId: string; taskId: string }> },
|
|
21
23
|
) {
|
|
24
|
+
await ensureDb();
|
|
22
25
|
const { id: projectId, taskId } = await params;
|
|
23
26
|
const body = await request.json();
|
|
24
27
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { getTaskPrompt, upsertTaskPrompt } from '@/lib/db/queries/task-prompts';
|
|
3
|
+
import { ensureDb } from '@/lib/db';
|
|
3
4
|
|
|
4
5
|
export async function GET(
|
|
5
6
|
_request: NextRequest,
|
|
6
7
|
{ params }: { params: Promise<{ id: string; subId: string; taskId: string }> },
|
|
7
8
|
) {
|
|
9
|
+
await ensureDb();
|
|
8
10
|
const { taskId } = await params;
|
|
9
11
|
const prompt = getTaskPrompt(taskId);
|
|
10
12
|
return NextResponse.json(prompt ?? { content: '', prompt_type: 'manual' });
|
|
@@ -14,6 +16,7 @@ export async function PUT(
|
|
|
14
16
|
request: NextRequest,
|
|
15
17
|
{ params }: { params: Promise<{ id: string; subId: string; taskId: string }> },
|
|
16
18
|
) {
|
|
19
|
+
await ensureDb();
|
|
17
20
|
const { taskId } = await params;
|
|
18
21
|
const body = await request.json();
|
|
19
22
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { getTask, updateTask, deleteTask } from '@/lib/db/queries/tasks';
|
|
3
|
+
import { ensureDb } from '@/lib/db';
|
|
3
4
|
|
|
4
5
|
export async function GET(
|
|
5
6
|
_request: NextRequest,
|
|
6
7
|
{ params }: { params: Promise<{ id: string; subId: string; taskId: string }> },
|
|
7
8
|
) {
|
|
9
|
+
await ensureDb();
|
|
8
10
|
const { taskId } = await params;
|
|
9
11
|
const task = getTask(taskId);
|
|
10
12
|
if (!task) {
|
|
@@ -17,6 +19,7 @@ export async function PUT(
|
|
|
17
19
|
request: NextRequest,
|
|
18
20
|
{ params }: { params: Promise<{ id: string; subId: string; taskId: string }> },
|
|
19
21
|
) {
|
|
22
|
+
await ensureDb();
|
|
20
23
|
const { taskId } = await params;
|
|
21
24
|
const body = await request.json();
|
|
22
25
|
const task = updateTask(taskId, body);
|
|
@@ -30,6 +33,7 @@ export async function DELETE(
|
|
|
30
33
|
_request: NextRequest,
|
|
31
34
|
{ params }: { params: Promise<{ id: string; subId: string; taskId: string }> },
|
|
32
35
|
) {
|
|
36
|
+
await ensureDb();
|
|
33
37
|
const { taskId } = await params;
|
|
34
38
|
const deleted = deleteTask(taskId);
|
|
35
39
|
if (!deleted) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { getTasks, createTask } from '@/lib/db/queries/tasks';
|
|
3
|
+
import { ensureDb } from '@/lib/db';
|
|
3
4
|
|
|
4
5
|
export async function GET(
|
|
5
6
|
_request: NextRequest,
|
|
6
7
|
{ params }: { params: Promise<{ id: string; subId: string }> },
|
|
7
8
|
) {
|
|
9
|
+
await ensureDb();
|
|
8
10
|
const { subId } = await params;
|
|
9
11
|
const tasks = getTasks(subId);
|
|
10
12
|
return NextResponse.json(tasks);
|
|
@@ -14,6 +16,7 @@ export async function POST(
|
|
|
14
16
|
request: NextRequest,
|
|
15
17
|
{ params }: { params: Promise<{ id: string; subId: string }> },
|
|
16
18
|
) {
|
|
19
|
+
await ensureDb();
|
|
17
20
|
const { id, subId } = await params;
|
|
18
21
|
const body = await request.json();
|
|
19
22
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { getSubProjectsWithStats, createSubProject, reorderSubProjects } from '@/lib/db/queries/sub-projects';
|
|
3
|
+
import { ensureDb } from '@/lib/db';
|
|
3
4
|
|
|
4
5
|
export async function GET(
|
|
5
6
|
_request: NextRequest,
|
|
6
7
|
{ params }: { params: Promise<{ id: string }> },
|
|
7
8
|
) {
|
|
9
|
+
await ensureDb();
|
|
8
10
|
const { id } = await params;
|
|
9
11
|
const subProjects = getSubProjectsWithStats(id);
|
|
10
12
|
return NextResponse.json(subProjects);
|
|
@@ -14,6 +16,7 @@ export async function POST(
|
|
|
14
16
|
request: NextRequest,
|
|
15
17
|
{ params }: { params: Promise<{ id: string }> },
|
|
16
18
|
) {
|
|
19
|
+
await ensureDb();
|
|
17
20
|
const { id } = await params;
|
|
18
21
|
const body = await request.json();
|
|
19
22
|
|
|
@@ -34,6 +37,7 @@ export async function PUT(
|
|
|
34
37
|
request: NextRequest,
|
|
35
38
|
{ params }: { params: Promise<{ id: string }> },
|
|
36
39
|
) {
|
|
40
|
+
await ensureDb();
|
|
37
41
|
const { id } = await params;
|
|
38
42
|
const body = await request.json();
|
|
39
43
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { listProjects, createProject } from '@/lib/db/queries/projects';
|
|
3
|
+
import { ensureDb } from '@/lib/db';
|
|
3
4
|
|
|
4
5
|
export async function GET() {
|
|
6
|
+
await ensureDb();
|
|
5
7
|
const projects = listProjects();
|
|
6
8
|
return NextResponse.json(projects);
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
export async function POST(request: NextRequest) {
|
|
12
|
+
await ensureDb();
|
|
10
13
|
const body = await request.json();
|
|
11
14
|
const { name, description } = body;
|
|
12
15
|
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
+
import { ensureDb } from './lib/db';
|
|
4
5
|
import { startMcpServer } from './lib/mcp/server';
|
|
5
6
|
import { listProjects, getProject } from './lib/db/queries/projects';
|
|
6
7
|
import { getSubProjects } from './lib/db/queries/sub-projects';
|
|
@@ -78,6 +79,7 @@ program
|
|
|
78
79
|
.command('mcp')
|
|
79
80
|
.description('Start MCP server (stdio mode)')
|
|
80
81
|
.action(async () => {
|
|
82
|
+
await ensureDb();
|
|
81
83
|
const ctx: McpToolContext = {
|
|
82
84
|
listProjects,
|
|
83
85
|
getProject,
|
package/src/lib/db/index.ts
CHANGED
|
@@ -1,16 +1,190 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
2
|
import { getDbPath } from '../utils/paths';
|
|
3
3
|
import { initSchema } from './schema';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Compatibility wrapper: mimics better-sqlite3 API on top of sql.js
|
|
6
|
+
class DatabaseWrapper {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
private db: any;
|
|
9
|
+
private dbPath: string;
|
|
10
|
+
private dirty = false;
|
|
11
|
+
private saveTimer: ReturnType<typeof setTimeout> | null = null;
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
db.pragma('journal_mode = WAL');
|
|
12
|
-
db.pragma('foreign_keys = ON');
|
|
13
|
-
initSchema(db);
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
constructor(db: any, dbPath: string) {
|
|
15
|
+
this.db = db;
|
|
16
|
+
this.dbPath = dbPath;
|
|
14
17
|
}
|
|
15
|
-
|
|
18
|
+
|
|
19
|
+
private save() {
|
|
20
|
+
if (!this.dirty) return;
|
|
21
|
+
const data = this.db.export();
|
|
22
|
+
fs.writeFileSync(this.dbPath, Buffer.from(data));
|
|
23
|
+
this.dirty = false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private scheduleSave() {
|
|
27
|
+
this.dirty = true;
|
|
28
|
+
if (this.saveTimer) return;
|
|
29
|
+
this.saveTimer = setTimeout(() => {
|
|
30
|
+
this.save();
|
|
31
|
+
this.saveTimer = null;
|
|
32
|
+
}, 100);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private immediatelySave() {
|
|
36
|
+
this.dirty = true;
|
|
37
|
+
if (this.saveTimer) {
|
|
38
|
+
clearTimeout(this.saveTimer);
|
|
39
|
+
this.saveTimer = null;
|
|
40
|
+
}
|
|
41
|
+
this.save();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private rowsToObjects(columns: string[], values: unknown[][]): Record<string, unknown>[] {
|
|
45
|
+
return values.map(row => {
|
|
46
|
+
const obj: Record<string, unknown> = {};
|
|
47
|
+
columns.forEach((col: string, i: number) => { obj[col] = row[i]; });
|
|
48
|
+
return obj;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
prepare(sql: string) {
|
|
53
|
+
const self = this;
|
|
54
|
+
const isWrite = /^\s*(INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)/i.test(sql);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
all(...params: unknown[]): any[] {
|
|
59
|
+
const stmt = self.db.prepare(sql);
|
|
60
|
+
if (params.length > 0) stmt.bind(params);
|
|
61
|
+
const columns: string[] = stmt.getColumnNames();
|
|
62
|
+
const rows: unknown[][] = [];
|
|
63
|
+
while (stmt.step()) {
|
|
64
|
+
rows.push(stmt.get());
|
|
65
|
+
}
|
|
66
|
+
stmt.free();
|
|
67
|
+
return self.rowsToObjects(columns, rows);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
get(...params: unknown[]): any {
|
|
72
|
+
const stmt = self.db.prepare(sql);
|
|
73
|
+
if (params.length > 0) stmt.bind(params);
|
|
74
|
+
let result: Record<string, unknown> | undefined;
|
|
75
|
+
if (stmt.step()) {
|
|
76
|
+
const columns = stmt.getColumnNames();
|
|
77
|
+
const row = stmt.get();
|
|
78
|
+
const obj: Record<string, unknown> = {};
|
|
79
|
+
columns.forEach((col: string, i: number) => { obj[col] = row[i]; });
|
|
80
|
+
result = obj;
|
|
81
|
+
}
|
|
82
|
+
stmt.free();
|
|
83
|
+
return result;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
run(...params: unknown[]) {
|
|
87
|
+
self.db.run(sql, params);
|
|
88
|
+
if (isWrite) self.scheduleSave();
|
|
89
|
+
const changes = self.db.getRowsModified();
|
|
90
|
+
return { changes };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exec(sql: string) {
|
|
96
|
+
this.db.exec(sql);
|
|
97
|
+
if (/^\s*(INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)/im.test(sql)) {
|
|
98
|
+
this.scheduleSave();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pragma(str: string) {
|
|
103
|
+
if (str.startsWith('table_info(')) {
|
|
104
|
+
const table = str.match(/table_info\((\w+)\)/)?.[1];
|
|
105
|
+
if (!table) return [];
|
|
106
|
+
const result = this.db.exec(`PRAGMA table_info(${table})`);
|
|
107
|
+
if (!result.length) return [];
|
|
108
|
+
return this.rowsToObjects(result[0].columns, result[0].values);
|
|
109
|
+
}
|
|
110
|
+
if (str.includes('journal_mode') || str.includes('wal_checkpoint')) {
|
|
111
|
+
this.immediatelySave();
|
|
112
|
+
return 'memory';
|
|
113
|
+
}
|
|
114
|
+
if (str.includes('foreign_keys')) {
|
|
115
|
+
try { this.db.run(`PRAGMA ${str}`); } catch { /* ignore */ }
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const result = this.db.exec(`PRAGMA ${str}`);
|
|
120
|
+
if (result.length > 0 && result[0].values.length > 0) {
|
|
121
|
+
return result[0].values[0][0];
|
|
122
|
+
}
|
|
123
|
+
} catch { /* ignore */ }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
transaction<T>(fn: () => T): () => T {
|
|
127
|
+
const self = this;
|
|
128
|
+
return () => {
|
|
129
|
+
self.db.run('BEGIN');
|
|
130
|
+
try {
|
|
131
|
+
const result = fn();
|
|
132
|
+
self.db.run('COMMIT');
|
|
133
|
+
self.immediatelySave();
|
|
134
|
+
return result;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
self.db.run('ROLLBACK');
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
close() {
|
|
143
|
+
this.immediatelySave();
|
|
144
|
+
this.db.close();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let wrapper: DatabaseWrapper | null = null;
|
|
149
|
+
let initPromise: Promise<DatabaseWrapper> | null = null;
|
|
150
|
+
|
|
151
|
+
async function initAsync(): Promise<DatabaseWrapper> {
|
|
152
|
+
if (wrapper) return wrapper;
|
|
153
|
+
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
155
|
+
const initSqlJs = require('sql.js/dist/sql-asm.js');
|
|
156
|
+
const SQL = await initSqlJs();
|
|
157
|
+
|
|
158
|
+
const dbPath = getDbPath();
|
|
159
|
+
let db;
|
|
160
|
+
if (fs.existsSync(dbPath)) {
|
|
161
|
+
const fileBuffer = fs.readFileSync(dbPath);
|
|
162
|
+
db = new SQL.Database(fileBuffer);
|
|
163
|
+
} else {
|
|
164
|
+
db = new SQL.Database();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
wrapper = new DatabaseWrapper(db, dbPath);
|
|
168
|
+
initSchema(wrapper as unknown as Parameters<typeof initSchema>[0]);
|
|
169
|
+
|
|
170
|
+
process.on('exit', () => wrapper?.close());
|
|
171
|
+
|
|
172
|
+
return wrapper;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Call once before using getDb(). Safe to call multiple times. */
|
|
176
|
+
export async function ensureDb(): Promise<DatabaseWrapper> {
|
|
177
|
+
if (wrapper) return wrapper;
|
|
178
|
+
if (!initPromise) {
|
|
179
|
+
initPromise = initAsync();
|
|
180
|
+
}
|
|
181
|
+
return initPromise;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Sync getter — only works after ensureDb() has resolved. */
|
|
185
|
+
export function getDb(): DatabaseWrapper {
|
|
186
|
+
if (!wrapper) {
|
|
187
|
+
throw new Error('Database not initialized. Call await ensureDb() first.');
|
|
188
|
+
}
|
|
189
|
+
return wrapper;
|
|
16
190
|
}
|
package/src/lib/db/schema.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export function initSchema(db: Database.Database): void {
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
|
+
export function initSchema(db: any): void {
|
|
4
3
|
db.exec(`
|
|
5
4
|
CREATE TABLE IF NOT EXISTS projects (
|
|
6
5
|
id TEXT PRIMARY KEY,
|
package/src/lib/sync/exporter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import { getDb } from '../db';
|
|
2
|
+
import { ensureDb, getDb } from '../db';
|
|
3
3
|
|
|
4
4
|
// v2 active tables
|
|
5
5
|
const V2_TABLES = ['projects', 'brainstorms', 'sub_projects', 'tasks', 'task_prompts', 'task_conversations'];
|
|
@@ -9,7 +9,8 @@ function getExistingTables(db: ReturnType<typeof getDb>): string[] {
|
|
|
9
9
|
return rows.map(r => r.name);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function exportToFile(filePath: string): { tables: Record<string, number> } {
|
|
12
|
+
export async function exportToFile(filePath: string): Promise<{ tables: Record<string, number> }> {
|
|
13
|
+
await ensureDb();
|
|
13
14
|
const db = getDb();
|
|
14
15
|
const existingTables = getExistingTables(db);
|
|
15
16
|
|
package/src/lib/sync/importer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import { getDb } from '../db';
|
|
2
|
+
import { ensureDb, getDb } from '../db';
|
|
3
3
|
import { getDbPath } from '../utils/paths';
|
|
4
4
|
|
|
5
5
|
// Import order: parents first, then children
|
|
@@ -7,7 +7,8 @@ const IMPORT_ORDER = ['projects', 'brainstorms', 'sub_projects', 'tasks', 'task_
|
|
|
7
7
|
// Delete order: children first, then parents
|
|
8
8
|
const DELETE_ORDER = [...IMPORT_ORDER].reverse();
|
|
9
9
|
|
|
10
|
-
export function backupDb(): string {
|
|
10
|
+
export async function backupDb(): Promise<string> {
|
|
11
|
+
await ensureDb();
|
|
11
12
|
const dbPath = getDbPath();
|
|
12
13
|
const db = getDb();
|
|
13
14
|
// Flush WAL before backup
|
|
@@ -18,7 +19,8 @@ export function backupDb(): string {
|
|
|
18
19
|
return backupPath;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function importFromFile(filePath: string): { tables: Record<string, number> } {
|
|
22
|
+
export async function importFromFile(filePath: string): Promise<{ tables: Record<string, number> }> {
|
|
23
|
+
await ensureDb();
|
|
22
24
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
23
25
|
const data = JSON.parse(raw);
|
|
24
26
|
|
package/src/lib/sync/index.ts
CHANGED
|
@@ -115,7 +115,7 @@ export async function syncInit() {
|
|
|
115
115
|
// Initial export + push if repo is empty
|
|
116
116
|
if (!fs.existsSync(path.join(syncDir, SYNC_FILE))) {
|
|
117
117
|
console.log(' Performing initial export...');
|
|
118
|
-
const { tables } = exportToFile(path.join(syncDir, SYNC_FILE));
|
|
118
|
+
const { tables } = await exportToFile(path.join(syncDir, SYNC_FILE));
|
|
119
119
|
printCounts('Exported', tables);
|
|
120
120
|
|
|
121
121
|
try {
|
|
@@ -142,7 +142,7 @@ export async function syncPush(message?: string) {
|
|
|
142
142
|
|
|
143
143
|
// Export
|
|
144
144
|
const filePath = path.join(syncDir, SYNC_FILE);
|
|
145
|
-
const { tables } = exportToFile(filePath);
|
|
145
|
+
const { tables } = await exportToFile(filePath);
|
|
146
146
|
printCounts('Exported', tables);
|
|
147
147
|
|
|
148
148
|
// Commit + push
|
|
@@ -194,12 +194,12 @@ export async function syncPull(opts: { backup?: boolean } = {}) {
|
|
|
194
194
|
|
|
195
195
|
// Backup
|
|
196
196
|
if (opts.backup !== false) {
|
|
197
|
-
const backupPath = backupDb();
|
|
197
|
+
const backupPath = await backupDb();
|
|
198
198
|
console.log(` Backup: ${backupPath}`);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
// Import
|
|
202
|
-
const { tables } = importFromFile(filePath);
|
|
202
|
+
const { tables } = await importFromFile(filePath);
|
|
203
203
|
printCounts('Imported', tables);
|
|
204
204
|
console.log(' Done. Refresh the browser to see updated data.\n');
|
|
205
205
|
}
|
package/src/lib/watcher.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
import { ensureDb } from './db';
|
|
2
3
|
import { runAgent } from './ai/client';
|
|
3
4
|
import { listProjects, getProject } from './db/queries/projects';
|
|
4
5
|
import { getSubProject } from './db/queries/sub-projects';
|
|
@@ -163,6 +164,7 @@ async function executeTask(task: ITask, project: IProject, options: WatcherOptio
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
export async function startWatcher(options: WatcherOptions): Promise<void> {
|
|
167
|
+
await ensureDb();
|
|
166
168
|
if (options.projectId) {
|
|
167
169
|
const project = getProject(options.projectId);
|
|
168
170
|
if (!project) {
|