synabun 1.3.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.
Files changed (187) hide show
  1. package/CHANGELOG.md +665 -0
  2. package/LICENSE +191 -0
  3. package/README.md +853 -0
  4. package/hooks/claude-code/post-plan.mjs +391 -0
  5. package/hooks/claude-code/post-remember.mjs +239 -0
  6. package/hooks/claude-code/pre-compact.mjs +241 -0
  7. package/hooks/claude-code/pre-websearch.mjs +90 -0
  8. package/hooks/claude-code/prompt-submit.mjs +861 -0
  9. package/hooks/claude-code/session-start.mjs +494 -0
  10. package/hooks/claude-code/shared.mjs +368 -0
  11. package/hooks/claude-code/stop.mjs +646 -0
  12. package/hooks/claude-code/test-user-learning.mjs +531 -0
  13. package/mcp-server/package.json +38 -0
  14. package/mcp-server/run.mjs +10 -0
  15. package/mcp-server/src/config.ts +70 -0
  16. package/mcp-server/src/http.ts +93 -0
  17. package/mcp-server/src/index.ts +203 -0
  18. package/mcp-server/src/preload-http.ts +25 -0
  19. package/mcp-server/src/preload.ts +29 -0
  20. package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
  21. package/mcp-server/src/services/categories.ts +429 -0
  22. package/mcp-server/src/services/discord.ts +702 -0
  23. package/mcp-server/src/services/file-checksums.ts +33 -0
  24. package/mcp-server/src/services/local-embeddings.ts +77 -0
  25. package/mcp-server/src/services/neural-interface.ts +344 -0
  26. package/mcp-server/src/services/sqlite.ts +823 -0
  27. package/mcp-server/src/tools/browser-advanced.ts +123 -0
  28. package/mcp-server/src/tools/browser-interact.ts +255 -0
  29. package/mcp-server/src/tools/browser-navigate.ts +77 -0
  30. package/mcp-server/src/tools/browser-observe.ts +1330 -0
  31. package/mcp-server/src/tools/browser.ts +107 -0
  32. package/mcp-server/src/tools/card-tools.ts +193 -0
  33. package/mcp-server/src/tools/card.ts +22 -0
  34. package/mcp-server/src/tools/category.ts +379 -0
  35. package/mcp-server/src/tools/discord-channel.ts +274 -0
  36. package/mcp-server/src/tools/discord-guild.ts +188 -0
  37. package/mcp-server/src/tools/discord-member.ts +158 -0
  38. package/mcp-server/src/tools/discord-message.ts +231 -0
  39. package/mcp-server/src/tools/discord-onboarding.ts +228 -0
  40. package/mcp-server/src/tools/discord-role.ts +209 -0
  41. package/mcp-server/src/tools/discord-thread.ts +110 -0
  42. package/mcp-server/src/tools/discord-webhook.ts +176 -0
  43. package/mcp-server/src/tools/discord.ts +40 -0
  44. package/mcp-server/src/tools/forget.ts +38 -0
  45. package/mcp-server/src/tools/git-tools.ts +141 -0
  46. package/mcp-server/src/tools/git.ts +13 -0
  47. package/mcp-server/src/tools/image-tools.ts +98 -0
  48. package/mcp-server/src/tools/image.ts +17 -0
  49. package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
  50. package/mcp-server/src/tools/leonardo.ts +28 -0
  51. package/mcp-server/src/tools/loop.ts +348 -0
  52. package/mcp-server/src/tools/memories.ts +118 -0
  53. package/mcp-server/src/tools/recall.ts +323 -0
  54. package/mcp-server/src/tools/reflect.ts +145 -0
  55. package/mcp-server/src/tools/remember.ts +115 -0
  56. package/mcp-server/src/tools/response.ts +26 -0
  57. package/mcp-server/src/tools/restore.ts +34 -0
  58. package/mcp-server/src/tools/sync.ts +114 -0
  59. package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
  60. package/mcp-server/src/tools/tictactoe.ts +13 -0
  61. package/mcp-server/src/tools/utils.ts +25 -0
  62. package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
  63. package/mcp-server/src/tools/whiteboard.ts +22 -0
  64. package/mcp-server/src/tui.ts +940 -0
  65. package/mcp-server/src/types.ts +85 -0
  66. package/mcp-server/tsconfig.json +19 -0
  67. package/neural-interface/package.json +27 -0
  68. package/neural-interface/public/apple-touch-icon.png +0 -0
  69. package/neural-interface/public/category-logos/claude.svg +1 -0
  70. package/neural-interface/public/category-logos/gemini-color.svg +1 -0
  71. package/neural-interface/public/category-logos/openai.svg +1 -0
  72. package/neural-interface/public/category-logos/synabun.png +0 -0
  73. package/neural-interface/public/claude-chat.css +878 -0
  74. package/neural-interface/public/claude-chat.html +92 -0
  75. package/neural-interface/public/claude-chat.js +1168 -0
  76. package/neural-interface/public/favicon-16x16.png +0 -0
  77. package/neural-interface/public/favicon-32x32.png +0 -0
  78. package/neural-interface/public/gallery.svg +6 -0
  79. package/neural-interface/public/icon-192.png +0 -0
  80. package/neural-interface/public/icon-512.png +0 -0
  81. package/neural-interface/public/index-legacy.html +14677 -0
  82. package/neural-interface/public/index.html +212 -0
  83. package/neural-interface/public/index2d-legacy.html +8193 -0
  84. package/neural-interface/public/index2d.html +127 -0
  85. package/neural-interface/public/invite.html +351 -0
  86. package/neural-interface/public/leonardoai.svg +1 -0
  87. package/neural-interface/public/logo-synabun.png +0 -0
  88. package/neural-interface/public/logoHD.png +0 -0
  89. package/neural-interface/public/manifest.json +14 -0
  90. package/neural-interface/public/offline.html +299 -0
  91. package/neural-interface/public/onboarding.html +2335 -0
  92. package/neural-interface/public/openclaw-logo-text.png +0 -0
  93. package/neural-interface/public/pin-svgrepo-com.svg +25 -0
  94. package/neural-interface/public/sessionicon.svg +8 -0
  95. package/neural-interface/public/shared/ansi-parser.js +305 -0
  96. package/neural-interface/public/shared/api.js +1043 -0
  97. package/neural-interface/public/shared/colors.js +153 -0
  98. package/neural-interface/public/shared/constants.js +141 -0
  99. package/neural-interface/public/shared/html-shell.js +777 -0
  100. package/neural-interface/public/shared/html-term-renderer.js +1022 -0
  101. package/neural-interface/public/shared/i18n.js +147 -0
  102. package/neural-interface/public/shared/registry.js +82 -0
  103. package/neural-interface/public/shared/state.js +86 -0
  104. package/neural-interface/public/shared/storage.js +150 -0
  105. package/neural-interface/public/shared/styles.css +21212 -0
  106. package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
  107. package/neural-interface/public/shared/ui-bookmarks.js +231 -0
  108. package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
  109. package/neural-interface/public/shared/ui-command-runner.js +534 -0
  110. package/neural-interface/public/shared/ui-cost-widget.js +602 -0
  111. package/neural-interface/public/shared/ui-detail.js +1454 -0
  112. package/neural-interface/public/shared/ui-explorer.js +1356 -0
  113. package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
  114. package/neural-interface/public/shared/ui-games.js +411 -0
  115. package/neural-interface/public/shared/ui-help.js +107 -0
  116. package/neural-interface/public/shared/ui-image-gallery.js +461 -0
  117. package/neural-interface/public/shared/ui-invite.js +519 -0
  118. package/neural-interface/public/shared/ui-keybinds.js +452 -0
  119. package/neural-interface/public/shared/ui-layouts.js +297 -0
  120. package/neural-interface/public/shared/ui-link.js +731 -0
  121. package/neural-interface/public/shared/ui-loading.js +255 -0
  122. package/neural-interface/public/shared/ui-menubar.js +499 -0
  123. package/neural-interface/public/shared/ui-multiselect.js +93 -0
  124. package/neural-interface/public/shared/ui-navbar.js +222 -0
  125. package/neural-interface/public/shared/ui-notifications.js +287 -0
  126. package/neural-interface/public/shared/ui-panels.js +309 -0
  127. package/neural-interface/public/shared/ui-resume.js +653 -0
  128. package/neural-interface/public/shared/ui-search.js +145 -0
  129. package/neural-interface/public/shared/ui-sessions.js +531 -0
  130. package/neural-interface/public/shared/ui-settings.js +5987 -0
  131. package/neural-interface/public/shared/ui-sidebar.js +1905 -0
  132. package/neural-interface/public/shared/ui-skills.js +2805 -0
  133. package/neural-interface/public/shared/ui-stats.js +38 -0
  134. package/neural-interface/public/shared/ui-sync.js +201 -0
  135. package/neural-interface/public/shared/ui-terminal.js +6385 -0
  136. package/neural-interface/public/shared/ui-tooltip.js +123 -0
  137. package/neural-interface/public/shared/ui-trash.js +439 -0
  138. package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
  139. package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
  140. package/neural-interface/public/shared/ui-tutorial.js +1117 -0
  141. package/neural-interface/public/shared/ui-update.js +244 -0
  142. package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
  143. package/neural-interface/public/shared/ui-workspaces.js +722 -0
  144. package/neural-interface/public/shared/utils.js +360 -0
  145. package/neural-interface/public/shared/vterm-buffer.js +1102 -0
  146. package/neural-interface/public/sw.js +52 -0
  147. package/neural-interface/public/synabun.png +0 -0
  148. package/neural-interface/public/toolbar-album.svg +8 -0
  149. package/neural-interface/public/toolbar-question.svg +6 -0
  150. package/neural-interface/public/toolbar-sessions.svg +6 -0
  151. package/neural-interface/public/variant/2d/background.js +113 -0
  152. package/neural-interface/public/variant/2d/context-menu.js +133 -0
  153. package/neural-interface/public/variant/2d/gfx.js +45 -0
  154. package/neural-interface/public/variant/2d/graph.js +1635 -0
  155. package/neural-interface/public/variant/2d/hulls.js +207 -0
  156. package/neural-interface/public/variant/2d/lasso.js +135 -0
  157. package/neural-interface/public/variant/2d/layout.js +594 -0
  158. package/neural-interface/public/variant/2d/main.js +521 -0
  159. package/neural-interface/public/variant/2d/minimap.js +312 -0
  160. package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
  161. package/neural-interface/public/variant/3d/background.js +884 -0
  162. package/neural-interface/public/variant/3d/camera.js +661 -0
  163. package/neural-interface/public/variant/3d/gfx.js +116 -0
  164. package/neural-interface/public/variant/3d/graph.js +2399 -0
  165. package/neural-interface/public/variant/3d/main.js +500 -0
  166. package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
  167. package/neural-interface/scripts/rebuild-pty.js +100 -0
  168. package/neural-interface/server.js +15000 -0
  169. package/neural-interface/templates/CLAUDE-template.md +211 -0
  170. package/package.json +53 -0
  171. package/postinstall.js +135 -0
  172. package/setup.js +260 -0
  173. package/skills/leonardo/SKILL.md +159 -0
  174. package/skills/leonardo/icon.svg +1 -0
  175. package/skills/leonardo/modules/browser-guide.md +378 -0
  176. package/skills/leonardo/modules/image-prompter.md +277 -0
  177. package/skills/leonardo/modules/model-advisor.md +100 -0
  178. package/skills/leonardo/modules/prompt-library.md +110 -0
  179. package/skills/leonardo/modules/style-guide.md +186 -0
  180. package/skills/leonardo/modules/video-prompter.md +311 -0
  181. package/skills/synabun/SKILL.md +185 -0
  182. package/skills/synabun/icon.png +0 -0
  183. package/skills/synabun/modules/audit.md +374 -0
  184. package/skills/synabun/modules/changelog.md +194 -0
  185. package/skills/synabun/modules/idea.md +144 -0
  186. package/skills/synabun/modules/memorize.md +188 -0
  187. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,506 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * One-time migration script: Qdrant → SQLite
4
+ *
5
+ * Reads all memories and session chunks from a running Qdrant instance,
6
+ * re-embeds them using the local Transformers.js model (384 dims),
7
+ * and inserts them into the SQLite database.
8
+ *
9
+ * Usage:
10
+ * cd mcp-server
11
+ * npx tsx src/scripts/migrate-qdrant-to-sqlite.ts
12
+ *
13
+ * Requirements:
14
+ * - Qdrant must still be running during migration
15
+ * - .env must contain the QDRANT__* and EMBEDDING__* variables
16
+ * - First run will download the embedding model (~23MB)
17
+ */
18
+
19
+ import { DatabaseSync } from 'node:sqlite';
20
+ import { readFileSync, existsSync, mkdirSync, statSync } from 'fs';
21
+ import path from 'path';
22
+ import { fileURLToPath } from 'url';
23
+
24
+ const __filename = fileURLToPath(import.meta.url);
25
+ const __dirname = path.dirname(__filename);
26
+ const ROOT = path.resolve(__dirname, '..', '..');
27
+ const DATA_DIR = path.join(ROOT, 'data');
28
+ const ENV_PATH = path.resolve(ROOT, '..', '.env');
29
+
30
+ // --- Load .env manually ---
31
+
32
+ function loadEnv(): Record<string, string> {
33
+ if (!existsSync(ENV_PATH)) {
34
+ console.error(`ERROR: .env not found at ${ENV_PATH}`);
35
+ process.exit(1);
36
+ }
37
+ const env: Record<string, string> = {};
38
+ const lines = readFileSync(ENV_PATH, 'utf-8').split('\n');
39
+ for (const line of lines) {
40
+ const trimmed = line.trim();
41
+ if (!trimmed || trimmed.startsWith('#')) continue;
42
+ const eq = trimmed.indexOf('=');
43
+ if (eq < 0) continue;
44
+ const key = trimmed.slice(0, eq).trim();
45
+ const val = trimmed.slice(eq + 1).trim();
46
+ env[key] = val;
47
+ }
48
+ return env;
49
+ }
50
+
51
+ // --- Qdrant HTTP client ---
52
+
53
+ interface QdrantConfig {
54
+ url: string;
55
+ apiKey: string;
56
+ collection: string;
57
+ }
58
+
59
+ function getQdrantConfig(env: Record<string, string>): QdrantConfig {
60
+ const activeId = env['QDRANT_ACTIVE'];
61
+ if (!activeId) {
62
+ console.error('ERROR: QDRANT_ACTIVE not set in .env');
63
+ process.exit(1);
64
+ }
65
+
66
+ const prefix = `QDRANT__${activeId}__`;
67
+ const port = env[`${prefix}PORT`] || '6333';
68
+ const apiKey = env[`${prefix}API_KEY`] || '';
69
+ const collection = env[`${prefix}COLLECTION`] || 'claude_memory';
70
+
71
+ return {
72
+ url: `http://localhost:${port}`,
73
+ apiKey,
74
+ collection,
75
+ };
76
+ }
77
+
78
+ async function qdrantFetch(cfg: QdrantConfig, endpoint: string, options: RequestInit = {}): Promise<any> {
79
+ const url = `${cfg.url}${endpoint}`;
80
+ const headers: Record<string, string> = {
81
+ 'Content-Type': 'application/json',
82
+ ...(options.headers as Record<string, string> || {}),
83
+ };
84
+ if (cfg.apiKey) {
85
+ headers['api-key'] = cfg.apiKey;
86
+ }
87
+
88
+ const res = await fetch(url, { ...options, headers });
89
+ if (!res.ok) {
90
+ const text = await res.text().catch(() => '');
91
+ throw new Error(`Qdrant ${options.method || 'GET'} ${endpoint} → ${res.status}: ${text}`);
92
+ }
93
+ return res.json();
94
+ }
95
+
96
+ // --- Scroll all points from Qdrant ---
97
+
98
+ const CATEGORIES_POINT_ID = '00000000-0000-0000-0000-000000000000';
99
+
100
+ interface QdrantPoint {
101
+ id: string;
102
+ payload: Record<string, any>;
103
+ vector?: number[];
104
+ }
105
+
106
+ async function scrollAll(cfg: QdrantConfig, collection: string, withVector = false): Promise<QdrantPoint[]> {
107
+ const points: QdrantPoint[] = [];
108
+ let offset: string | null = null;
109
+ const limit = 100;
110
+
111
+ while (true) {
112
+ const body: any = {
113
+ limit,
114
+ with_payload: true,
115
+ with_vector: withVector,
116
+ };
117
+ if (offset) body.offset = offset;
118
+
119
+ const data = await qdrantFetch(cfg, `/collections/${collection}/points/scroll`, {
120
+ method: 'POST',
121
+ body: JSON.stringify(body),
122
+ });
123
+
124
+ const result = data.result || data;
125
+ const batch = result.points || [];
126
+ points.push(...batch);
127
+
128
+ if (result.next_page_offset) {
129
+ offset = result.next_page_offset;
130
+ } else {
131
+ break;
132
+ }
133
+ }
134
+
135
+ return points;
136
+ }
137
+
138
+ // --- Local embeddings ---
139
+
140
+ let embeddingPipeline: any = null;
141
+
142
+ async function initEmbeddings(): Promise<void> {
143
+ console.log(' Loading embedding model (Xenova/all-MiniLM-L6-v2)...');
144
+ const { pipeline } = await import('@huggingface/transformers');
145
+ embeddingPipeline = await (pipeline as any)('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
146
+ dtype: 'fp32',
147
+ });
148
+ console.log(' Embedding model ready (384 dimensions)');
149
+ }
150
+
151
+ async function embed(text: string): Promise<number[]> {
152
+ const output = await embeddingPipeline(text, { pooling: 'mean', normalize: true });
153
+ return Array.from(output.data as Float32Array);
154
+ }
155
+
156
+ function encodeVector(vector: number[]): Uint8Array {
157
+ return new Uint8Array(new Float32Array(vector).buffer);
158
+ }
159
+
160
+ // --- SQLite setup ---
161
+
162
+ function initSqlite(): DatabaseSync {
163
+ if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
164
+ const dbPath = path.join(DATA_DIR, 'memory.db');
165
+ console.log(` SQLite database: ${dbPath}`);
166
+
167
+ const db = new DatabaseSync(dbPath);
168
+ db.exec('PRAGMA journal_mode = WAL');
169
+ db.exec('PRAGMA foreign_keys = ON');
170
+ db.exec('PRAGMA busy_timeout = 5000');
171
+
172
+ // Create schema
173
+ db.exec(`
174
+ CREATE TABLE IF NOT EXISTS memories (
175
+ id TEXT PRIMARY KEY,
176
+ vector BLOB NOT NULL,
177
+ content TEXT NOT NULL,
178
+ category TEXT NOT NULL,
179
+ subcategory TEXT,
180
+ project TEXT NOT NULL,
181
+ tags TEXT NOT NULL DEFAULT '[]',
182
+ importance INTEGER NOT NULL DEFAULT 5,
183
+ source TEXT NOT NULL DEFAULT 'self-discovered',
184
+ created_at TEXT NOT NULL,
185
+ updated_at TEXT NOT NULL,
186
+ accessed_at TEXT NOT NULL,
187
+ access_count INTEGER NOT NULL DEFAULT 0,
188
+ related_files TEXT,
189
+ related_memory_ids TEXT,
190
+ file_checksums TEXT,
191
+ trashed_at TEXT,
192
+ source_session_chunks TEXT
193
+ );
194
+
195
+ CREATE TABLE IF NOT EXISTS session_chunks (
196
+ id TEXT PRIMARY KEY,
197
+ vector BLOB NOT NULL,
198
+ content TEXT NOT NULL,
199
+ summary TEXT,
200
+ session_id TEXT,
201
+ project TEXT,
202
+ git_branch TEXT,
203
+ cwd TEXT,
204
+ chunk_index INTEGER DEFAULT 0,
205
+ start_timestamp TEXT,
206
+ end_timestamp TEXT,
207
+ tools_used TEXT DEFAULT '[]',
208
+ files_modified TEXT DEFAULT '[]',
209
+ files_read TEXT DEFAULT '[]',
210
+ user_messages TEXT DEFAULT '[]',
211
+ turn_count INTEGER DEFAULT 0,
212
+ related_memory_ids TEXT DEFAULT '[]',
213
+ dedup_memory_id TEXT,
214
+ indexed_at TEXT
215
+ );
216
+
217
+ CREATE TABLE IF NOT EXISTS categories (
218
+ name TEXT PRIMARY KEY,
219
+ description TEXT NOT NULL,
220
+ created_at TEXT NOT NULL,
221
+ parent TEXT,
222
+ color TEXT,
223
+ is_parent INTEGER DEFAULT 0
224
+ );
225
+
226
+ CREATE INDEX IF NOT EXISTS idx_mem_category ON memories(category);
227
+ CREATE INDEX IF NOT EXISTS idx_mem_project ON memories(project);
228
+ CREATE INDEX IF NOT EXISTS idx_mem_importance ON memories(importance);
229
+ CREATE INDEX IF NOT EXISTS idx_mem_trashed ON memories(trashed_at);
230
+ CREATE INDEX IF NOT EXISTS idx_mem_created ON memories(created_at);
231
+ CREATE INDEX IF NOT EXISTS idx_sc_session ON session_chunks(session_id);
232
+ CREATE INDEX IF NOT EXISTS idx_sc_project ON session_chunks(project);
233
+ `);
234
+
235
+ return db;
236
+ }
237
+
238
+ // --- JSON serialization helpers ---
239
+
240
+ function jsonStr(val: any): string | null {
241
+ if (val == null) return null;
242
+ if (Array.isArray(val) || typeof val === 'object') return JSON.stringify(val);
243
+ if (typeof val === 'string') return val;
244
+ return String(val);
245
+ }
246
+
247
+ // --- Main migration ---
248
+
249
+ async function main(): Promise<void> {
250
+ console.log('\n═══════════════════════════════════════════');
251
+ console.log(' SynaBun: Qdrant → SQLite Migration');
252
+ console.log('═══════════════════════════════════════════\n');
253
+
254
+ // Step 1: Load config
255
+ console.log('[1/6] Loading configuration...');
256
+ const env = loadEnv();
257
+ const cfg = getQdrantConfig(env);
258
+ console.log(` Qdrant: ${cfg.url} (collection: ${cfg.collection})`);
259
+
260
+ // Step 2: Test Qdrant connection
261
+ console.log('\n[2/6] Connecting to Qdrant...');
262
+ try {
263
+ const info = await qdrantFetch(cfg, `/collections/${cfg.collection}`);
264
+ const pointCount = info.result?.points_count ?? '?';
265
+ console.log(` Connected! Collection "${cfg.collection}" has ${pointCount} points`);
266
+ } catch (err: any) {
267
+ console.error(` ERROR: Cannot connect to Qdrant at ${cfg.url}`);
268
+ console.error(` Make sure Qdrant is running. Error: ${err.message}`);
269
+ process.exit(1);
270
+ }
271
+
272
+ // Check for session_chunks collection (try both naming conventions)
273
+ let hasSessionChunks = false;
274
+ let sessionChunksCollection = '';
275
+ for (const name of ['session_chunks', `${cfg.collection}_session_chunks`]) {
276
+ try {
277
+ const scInfo = await qdrantFetch(cfg, `/collections/${name}`);
278
+ const scCount = scInfo.result?.points_count ?? 0;
279
+ if (scCount > 0) {
280
+ hasSessionChunks = true;
281
+ sessionChunksCollection = name;
282
+ console.log(` Session chunks collection "${name}": ${scCount} chunks`);
283
+ break;
284
+ }
285
+ } catch {
286
+ // Try next name
287
+ }
288
+ }
289
+ if (!hasSessionChunks) {
290
+ console.log(' No session chunks collection found (will skip)');
291
+ }
292
+
293
+ // Step 3: Initialize embedding model
294
+ console.log('\n[3/6] Initializing local embedding model...');
295
+ await initEmbeddings();
296
+
297
+ // Step 4: Initialize SQLite
298
+ console.log('\n[4/6] Initializing SQLite database...');
299
+ const db = initSqlite();
300
+
301
+ // Check if SQLite already has data
302
+ const existingCount = (db.prepare('SELECT COUNT(*) as cnt FROM memories').get() as any).cnt;
303
+ if (existingCount > 0) {
304
+ console.log(` WARNING: SQLite already has ${existingCount} memories!`);
305
+ console.log(' Existing data will NOT be overwritten (INSERT OR IGNORE).');
306
+ }
307
+
308
+ // Step 5: Migrate memories
309
+ console.log('\n[5/6] Migrating memories...');
310
+ const memPoints = await scrollAll(cfg, cfg.collection);
311
+ const memories = memPoints.filter(p => p.id !== CATEGORIES_POINT_ID);
312
+ console.log(` Found ${memories.length} memories (${memPoints.length - memories.length} system points skipped)`);
313
+
314
+ // Extract categories from system metadata point
315
+ const catPoint = memPoints.find(p => p.id === CATEGORIES_POINT_ID);
316
+ if (catPoint?.payload?.categories) {
317
+ const cats: any[] = catPoint.payload.categories;
318
+ console.log(` Found ${cats.length} categories in system metadata`);
319
+ db.exec('BEGIN');
320
+ try {
321
+ const catStmt = db.prepare(
322
+ 'INSERT OR REPLACE INTO categories (name, description, created_at, parent, color, is_parent) VALUES (?, ?, ?, ?, ?, ?)'
323
+ );
324
+ for (const cat of cats) {
325
+ catStmt.run(
326
+ cat.name,
327
+ cat.description || '',
328
+ cat.created_at || new Date().toISOString(),
329
+ cat.parent || null,
330
+ cat.color || null,
331
+ cat.is_parent ? 1 : 0,
332
+ );
333
+ }
334
+ db.exec('COMMIT');
335
+ console.log(` Migrated ${cats.length} categories`);
336
+ } catch (err) {
337
+ db.exec('ROLLBACK');
338
+ console.error(' ERROR migrating categories:', err);
339
+ }
340
+ }
341
+
342
+ // Migrate memories in batches
343
+ const BATCH = 20;
344
+ let migrated = 0;
345
+ let skipped = 0;
346
+ let errors = 0;
347
+
348
+ const memStmt = db.prepare(`
349
+ INSERT OR IGNORE INTO memories
350
+ (id, vector, content, category, subcategory, project, tags, importance, source,
351
+ created_at, updated_at, accessed_at, access_count, related_files,
352
+ related_memory_ids, file_checksums, trashed_at, source_session_chunks)
353
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
354
+ `);
355
+
356
+ for (let i = 0; i < memories.length; i += BATCH) {
357
+ const batch = memories.slice(i, i + BATCH);
358
+ const texts = batch.map(p => (p.payload.content || '').slice(0, 2000));
359
+
360
+ // Re-embed batch
361
+ const vectors: number[][] = [];
362
+ for (const text of texts) {
363
+ try {
364
+ vectors.push(await embed(text));
365
+ } catch (err) {
366
+ vectors.push(new Array(384).fill(0));
367
+ errors++;
368
+ }
369
+ }
370
+
371
+ db.exec('BEGIN');
372
+ try {
373
+ for (let j = 0; j < batch.length; j++) {
374
+ const p = batch[j].payload;
375
+ const now = new Date().toISOString();
376
+
377
+ memStmt.run(
378
+ batch[j].id,
379
+ encodeVector(vectors[j]),
380
+ p.content || '',
381
+ p.category || 'general',
382
+ p.subcategory || null,
383
+ p.project || 'global',
384
+ jsonStr(p.tags) || '[]',
385
+ p.importance ?? 5,
386
+ p.source || 'self-discovered',
387
+ p.created_at || now,
388
+ p.updated_at || now,
389
+ p.accessed_at || now,
390
+ p.access_count ?? 0,
391
+ jsonStr(p.related_files),
392
+ jsonStr(p.related_memory_ids),
393
+ jsonStr(p.file_checksums),
394
+ p.trashed_at || null,
395
+ jsonStr(p.source_session_chunks),
396
+ );
397
+ migrated++;
398
+ }
399
+ db.exec('COMMIT');
400
+ } catch (err) {
401
+ db.exec('ROLLBACK');
402
+ errors += batch.length;
403
+ console.error(` ERROR on batch ${i}-${i + batch.length}:`, err);
404
+ }
405
+
406
+ process.stdout.write(`\r Migrated ${migrated}/${memories.length} memories...`);
407
+ }
408
+ console.log(`\r Migrated ${migrated}/${memories.length} memories (${skipped} skipped, ${errors} errors)`);
409
+
410
+ // Step 6: Migrate session chunks
411
+ if (hasSessionChunks) {
412
+ console.log('\n[6/6] Migrating session chunks...');
413
+ const chunks = await scrollAll(cfg, sessionChunksCollection);
414
+ console.log(` Found ${chunks.length} session chunks`);
415
+
416
+ let scMigrated = 0;
417
+ let scErrors = 0;
418
+
419
+ const scStmt = db.prepare(`
420
+ INSERT OR IGNORE INTO session_chunks
421
+ (id, vector, content, summary, session_id, project, git_branch, cwd,
422
+ chunk_index, start_timestamp, end_timestamp, tools_used, files_modified,
423
+ files_read, user_messages, turn_count, related_memory_ids, dedup_memory_id, indexed_at)
424
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
425
+ `);
426
+
427
+ for (let i = 0; i < chunks.length; i += BATCH) {
428
+ const batch = chunks.slice(i, i + BATCH);
429
+ const texts = batch.map(p => (p.payload.content || '').slice(0, 2000));
430
+
431
+ const vectors: number[][] = [];
432
+ for (const text of texts) {
433
+ try {
434
+ vectors.push(await embed(text));
435
+ } catch {
436
+ vectors.push(new Array(384).fill(0));
437
+ scErrors++;
438
+ }
439
+ }
440
+
441
+ db.exec('BEGIN');
442
+ try {
443
+ for (let j = 0; j < batch.length; j++) {
444
+ const p = batch[j].payload;
445
+
446
+ scStmt.run(
447
+ batch[j].id,
448
+ encodeVector(vectors[j]),
449
+ p.content || '',
450
+ p.summary || null,
451
+ p.session_id || null,
452
+ p.project || null,
453
+ p.git_branch || null,
454
+ p.cwd || null,
455
+ p.chunk_index ?? 0,
456
+ p.start_timestamp || null,
457
+ p.end_timestamp || null,
458
+ jsonStr(p.tools_used) || '[]',
459
+ jsonStr(p.files_modified) || '[]',
460
+ jsonStr(p.files_read) || '[]',
461
+ jsonStr(p.user_messages) || '[]',
462
+ p.turn_count ?? 0,
463
+ jsonStr(p.related_memory_ids) || '[]',
464
+ p.dedup_memory_id || null,
465
+ p.indexed_at || null,
466
+ );
467
+ scMigrated++;
468
+ }
469
+ db.exec('COMMIT');
470
+ } catch (err) {
471
+ db.exec('ROLLBACK');
472
+ scErrors += batch.length;
473
+ console.error(` ERROR on session chunk batch ${i}-${i + batch.length}:`, err);
474
+ }
475
+
476
+ process.stdout.write(`\r Migrated ${scMigrated}/${chunks.length} session chunks...`);
477
+ }
478
+ console.log(`\r Migrated ${scMigrated}/${chunks.length} session chunks (${scErrors} errors)`);
479
+ } else {
480
+ console.log('\n[6/6] Skipping session chunks (none found)');
481
+ }
482
+
483
+ // Summary
484
+ const finalMem = (db.prepare('SELECT COUNT(*) as cnt FROM memories').get() as any).cnt;
485
+ const finalSc = (db.prepare('SELECT COUNT(*) as cnt FROM session_chunks').get() as any).cnt;
486
+ const finalCat = (db.prepare('SELECT COUNT(*) as cnt FROM categories').get() as any).cnt;
487
+ const dbSize = statSync(path.join(DATA_DIR, 'memory.db')).size;
488
+
489
+ db.close();
490
+
491
+ console.log('\n═══════════════════════════════════════════');
492
+ console.log(' Migration Complete!');
493
+ console.log('═══════════════════════════════════════════');
494
+ console.log(` Memories: ${finalMem}`);
495
+ console.log(` Session chunks: ${finalSc}`);
496
+ console.log(` Categories: ${finalCat}`);
497
+ console.log(` Database size: ${(dbSize / 1024).toFixed(1)} KB`);
498
+ console.log(` Path: ${path.join(DATA_DIR, 'memory.db')}`);
499
+ console.log('\nYou can now stop the Qdrant Docker container:');
500
+ console.log(' docker stop synabun-qdrant && docker rm synabun-qdrant\n');
501
+ }
502
+
503
+ main().catch(err => {
504
+ console.error('\nFATAL ERROR:', err);
505
+ process.exit(1);
506
+ });