idea-manager 1.0.1 → 1.1.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  /** @type {import('next').NextConfig} */
2
2
  const nextConfig = {
3
- serverExternalPackages: ['better-sqlite3'],
3
+ serverExternalPackages: ['sql.js/dist/sql-asm.js'],
4
4
  };
5
5
 
6
6
  export default nextConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "idea-manager",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
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,
@@ -109,27 +111,46 @@ program
109
111
 
110
112
  program
111
113
  .command('start')
112
- .description('Start the web UI (Next.js dev server on port 3456)')
114
+ .description('Start the web UI on port 3456')
113
115
  .option('-p, --port <port>', 'Port number', '3456')
114
116
  .action(async (opts) => {
115
117
  const port = opts.port;
116
- console.log(`\n IM - Idea Manager v2`);
117
- console.log(` Starting on http://localhost:${port}\n`);
118
+ const fs = await import('fs');
118
119
 
119
- // Resolve next CLI directly — avoids .bin symlink issues on Windows
120
- // and npm global install hoisting issues
120
+ // Resolve next CLI
121
121
  let nextCli: string;
122
122
  try {
123
123
  nextCli = require.resolve('next/dist/bin/next', { paths: [PKG_ROOT] });
124
124
  } catch {
125
- // Fallback: try to find next package manually
126
125
  nextCli = path.join(PKG_ROOT, 'node_modules', 'next', 'dist', 'bin', 'next');
127
126
  }
128
127
 
129
- const child = spawn(process.execPath, [nextCli, 'dev', '-p', port], {
128
+ // Build if not already built
129
+ const buildDir = path.join(PKG_ROOT, '.next');
130
+ if (!fs.existsSync(buildDir)) {
131
+ console.log('\n IM - First run: building... (this may take a minute)\n');
132
+ const buildResult = spawn(process.execPath, [nextCli, 'build'], {
133
+ cwd: PKG_ROOT,
134
+ stdio: 'inherit',
135
+ env: { ...process.env, NODE_ENV: 'production' },
136
+ });
137
+ await new Promise<void>((resolve, reject) => {
138
+ buildResult.on('exit', (code) => {
139
+ if (code !== 0) reject(new Error(`Build failed with code ${code}`));
140
+ else resolve();
141
+ });
142
+ buildResult.on('error', reject);
143
+ });
144
+ }
145
+
146
+ console.log(`\n IM - Idea Manager`);
147
+ console.log(` Starting on http://localhost:${port}\n`);
148
+
149
+ // Run production server (next start)
150
+ const child = spawn(process.execPath, [nextCli, 'start', '-p', port], {
130
151
  cwd: PKG_ROOT,
131
152
  stdio: 'inherit',
132
- env: { ...process.env, NODE_ENV: 'development' },
153
+ env: { ...process.env, NODE_ENV: 'production' },
133
154
  });
134
155
 
135
156
  child.on('error', (err) => {
@@ -1,16 +1,190 @@
1
- import Database from 'better-sqlite3';
1
+ import fs from 'fs';
2
2
  import { getDbPath } from '../utils/paths';
3
3
  import { initSchema } from './schema';
4
4
 
5
- let db: Database.Database | null = null;
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
- export function getDb(): Database.Database {
8
- if (!db) {
9
- const dbPath = getDbPath();
10
- db = new Database(dbPath);
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
- return db;
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
  }
@@ -1,6 +1,5 @@
1
- import type Database from 'better-sqlite3';
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,
@@ -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
 
@@ -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
 
@@ -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
  }
@@ -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) {