mnemosyne-core 2.0.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 (47) hide show
  1. package/README.md +176 -0
  2. package/dist/Store-N3j0Vaj6.d.mts +320 -0
  3. package/dist/Store-N3j0Vaj6.d.ts +320 -0
  4. package/dist/cli/index.d.mts +8 -0
  5. package/dist/cli/index.d.ts +8 -0
  6. package/dist/cli/index.js +16352 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/cli/index.mjs +16348 -0
  9. package/dist/cli/index.mjs.map +1 -0
  10. package/dist/mcp/index.d.mts +94 -0
  11. package/dist/mcp/index.d.ts +94 -0
  12. package/dist/mcp/index.js +401 -0
  13. package/dist/mcp/index.js.map +1 -0
  14. package/dist/mcp/index.mjs +371 -0
  15. package/dist/mcp/index.mjs.map +1 -0
  16. package/dist/sdk/index.d.mts +102 -0
  17. package/dist/sdk/index.d.ts +102 -0
  18. package/dist/sdk/index.js +143 -0
  19. package/dist/sdk/index.js.map +1 -0
  20. package/dist/sdk/index.mjs +116 -0
  21. package/dist/sdk/index.mjs.map +1 -0
  22. package/dist/server/api.d.mts +14 -0
  23. package/dist/server/api.d.ts +14 -0
  24. package/dist/server/api.js +1699 -0
  25. package/dist/server/api.js.map +1 -0
  26. package/dist/server/api.mjs +1669 -0
  27. package/dist/server/api.mjs.map +1 -0
  28. package/dist/server/index.d.mts +111 -0
  29. package/dist/server/index.d.ts +111 -0
  30. package/dist/server/index.js +12446 -0
  31. package/dist/server/index.js.map +1 -0
  32. package/dist/server/index.mjs +12442 -0
  33. package/dist/server/index.mjs.map +1 -0
  34. package/dist/server/websocket.d.mts +23 -0
  35. package/dist/server/websocket.d.ts +23 -0
  36. package/dist/server/websocket.js +3807 -0
  37. package/dist/server/websocket.js.map +1 -0
  38. package/dist/server/websocket.mjs +3798 -0
  39. package/dist/server/websocket.mjs.map +1 -0
  40. package/dist/sharp-win32-x64-CXV3GA3G.node +0 -0
  41. package/dist/ws/index.d.mts +22 -0
  42. package/dist/ws/index.d.ts +22 -0
  43. package/dist/ws/index.js +3827 -0
  44. package/dist/ws/index.js.map +1 -0
  45. package/dist/ws/index.mjs +3817 -0
  46. package/dist/ws/index.mjs.map +1 -0
  47. package/package.json +69 -0
@@ -0,0 +1,1699 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/config.ts
34
+ function parseValue(v) {
35
+ const trimmed = v.trim();
36
+ if (trimmed === "true") return true;
37
+ if (trimmed === "false") return false;
38
+ if (trimmed === "null" || trimmed === "~") return null;
39
+ if (/^-?\d+$/.test(trimmed)) return parseInt(trimmed, 10);
40
+ if (/^-?\d+\.\d+$/.test(trimmed)) return parseFloat(trimmed);
41
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
42
+ return trimmed.slice(1, -1);
43
+ }
44
+ return trimmed;
45
+ }
46
+ function parseYaml(text) {
47
+ const lines = text.split("\n");
48
+ const root = {};
49
+ const stack = [{ obj: root, indent: -1, isArray: false }];
50
+ for (const rawLine of lines) {
51
+ const commentIdx = rawLine.indexOf("#");
52
+ const line = commentIdx >= 0 ? rawLine.slice(0, commentIdx) : rawLine;
53
+ if (!line.trim()) continue;
54
+ const indent = line.length - line.trimStart().length;
55
+ const trimmed = line.trim();
56
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
57
+ stack.pop();
58
+ }
59
+ const parent = stack[stack.length - 1];
60
+ if (trimmed.startsWith("- ")) {
61
+ const valueStr2 = trimmed.slice(2).trim();
62
+ if (!parent.isArray) {
63
+ }
64
+ let target = parent.obj;
65
+ if (!Array.isArray(target)) {
66
+ target = [];
67
+ }
68
+ if (valueStr2.includes(":")) {
69
+ const item = {};
70
+ const colonIdx2 = valueStr2.indexOf(":");
71
+ const k = valueStr2.slice(0, colonIdx2).trim();
72
+ const v = valueStr2.slice(colonIdx2 + 1).trim();
73
+ item[k] = parseValue(v);
74
+ target.push(item);
75
+ } else {
76
+ target.push(parseValue(valueStr2));
77
+ }
78
+ continue;
79
+ }
80
+ const colonIdx = trimmed.indexOf(":");
81
+ if (colonIdx === -1) continue;
82
+ const key = trimmed.slice(0, colonIdx).trim();
83
+ const valueStr = trimmed.slice(colonIdx + 1).trim();
84
+ if (!valueStr) {
85
+ const newObj = {};
86
+ parent.obj[key] = newObj;
87
+ stack.push({ obj: newObj, indent, isArray: false });
88
+ } else {
89
+ parent.obj[key] = parseValue(valueStr);
90
+ }
91
+ }
92
+ return root;
93
+ }
94
+ function loadConfig() {
95
+ const configPath = (0, import_path.resolve)(process.cwd(), "config.yaml");
96
+ if (!(0, import_fs.existsSync)(configPath)) {
97
+ console.warn("[Config] config.yaml not found, using defaults");
98
+ return defaultConfig();
99
+ }
100
+ try {
101
+ const raw = (0, import_fs.readFileSync)(configPath, "utf-8");
102
+ const parsed = parseYaml(raw);
103
+ return mergeDeep(defaultConfig(), parsed);
104
+ } catch (err) {
105
+ console.error("[Config] Failed to parse config.yaml:", err.message);
106
+ return defaultConfig();
107
+ }
108
+ }
109
+ function defaultConfig() {
110
+ return {
111
+ server: { port: 7321, host: "localhost", version: "2.0.0" },
112
+ database: { path: "data/nexus.db", wal_mode: true, vec_extension_path: "data/vec0" },
113
+ storage: { files_dir: "data/files", max_file_size_mb: 50, backups_dir: "data/backups", backup_interval_hours: 24, max_backups: 7 },
114
+ limits: { max_atoms_per_project: 1e4, rate_limit_requests: 100, rate_limit_window_ms: 6e4 },
115
+ embeddings: { model: "Xenova/all-MiniLM-L6-v2", dimension: 384, max_text_length: 1e4 },
116
+ features: { mcp_enabled: true, auto_index_enabled: true, file_processing_enabled: true },
117
+ index: { debounce_ms: 3e4 },
118
+ search: { semantic_weight: 0.6, fts_weight: 0.4 },
119
+ bonds: { default_type: "relates" }
120
+ };
121
+ }
122
+ function mergeDeep(target, source) {
123
+ if (!source || typeof source !== "object") return target;
124
+ const output = { ...target };
125
+ for (const key of Object.keys(source)) {
126
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
127
+ output[key] = mergeDeep(target[key] || {}, source[key]);
128
+ } else {
129
+ output[key] = source[key];
130
+ }
131
+ }
132
+ return output;
133
+ }
134
+ var import_fs, import_path, CONFIG;
135
+ var init_config = __esm({
136
+ "src/config.ts"() {
137
+ "use strict";
138
+ import_fs = require("fs");
139
+ import_path = require("path");
140
+ CONFIG = loadConfig();
141
+ }
142
+ });
143
+
144
+ // src/server/embedder.ts
145
+ var embedder_exports = {};
146
+ __export(embedder_exports, {
147
+ embedText: () => embedText,
148
+ embedTexts: () => embedTexts,
149
+ isReady: () => isReady
150
+ });
151
+ async function getExtractor() {
152
+ if (extractor) return extractor;
153
+ if (loadError) return null;
154
+ if (loading) {
155
+ while (loading) await new Promise((r) => setTimeout(r, 100));
156
+ return extractor;
157
+ }
158
+ loading = true;
159
+ try {
160
+ extractor = await (0, import_transformers.pipeline)("feature-extraction", CONFIG.embeddings.model, {
161
+ quantized: true
162
+ // Use quantized model for faster download
163
+ });
164
+ console.log(`[Embedder] Model loaded: ${CONFIG.embeddings.model}`);
165
+ return extractor;
166
+ } catch (err) {
167
+ loadError = err;
168
+ console.error("[Embedder] Failed to load model:", err.message);
169
+ return null;
170
+ } finally {
171
+ loading = false;
172
+ }
173
+ }
174
+ async function embedText(text) {
175
+ const ext = await getExtractor();
176
+ if (!ext) return null;
177
+ const truncated = text.slice(0, CONFIG.embeddings.max_text_length);
178
+ if (!truncated.trim()) return null;
179
+ try {
180
+ const output = await ext(truncated, { pooling: "mean", normalize: true });
181
+ return Array.from(output.data);
182
+ } catch (err) {
183
+ console.error("[Embedder] Embedding failed:", err.message);
184
+ return null;
185
+ }
186
+ }
187
+ async function embedTexts(texts) {
188
+ const ext = await getExtractor();
189
+ if (!ext) return texts.map(() => null);
190
+ const results = [];
191
+ for (const text of texts) {
192
+ const truncated = text.slice(0, CONFIG.embeddings.max_text_length);
193
+ if (!truncated.trim()) {
194
+ results.push(null);
195
+ continue;
196
+ }
197
+ try {
198
+ const output = await ext(truncated, { pooling: "mean", normalize: true });
199
+ results.push(Array.from(output.data));
200
+ } catch {
201
+ results.push(null);
202
+ }
203
+ }
204
+ return results;
205
+ }
206
+ function isReady() {
207
+ return extractor !== null;
208
+ }
209
+ var import_transformers, extractor, loading, loadError;
210
+ var init_embedder = __esm({
211
+ "src/server/embedder.ts"() {
212
+ "use strict";
213
+ import_transformers = require("@xenova/transformers");
214
+ init_config();
215
+ extractor = null;
216
+ loading = false;
217
+ loadError = null;
218
+ }
219
+ });
220
+
221
+ // src/server/api.ts
222
+ var api_exports = {};
223
+ __export(api_exports, {
224
+ API: () => ApiRouter
225
+ });
226
+ module.exports = __toCommonJS(api_exports);
227
+
228
+ // src/mcp/tools.ts
229
+ var TOOLS = [
230
+ {
231
+ name: "nexus_list_projects",
232
+ description: "List all projects in the Mnemosyne knowledge base.",
233
+ inputSchema: { type: "object", properties: {} },
234
+ handler: (_args, store) => {
235
+ return { projects: store.getProjects().map((p) => ({ id: p.id, name: p.name, description: p.description, atomCount: store.getAtomsByProject(p.id).length })) };
236
+ }
237
+ },
238
+ {
239
+ name: "nexus_search",
240
+ description: "Search atoms and blocks by keyword. Returns ranked results.",
241
+ inputSchema: {
242
+ type: "object",
243
+ properties: {
244
+ query: { type: "string", description: "Search query" },
245
+ project: { type: "string", description: "Project name or ID (optional)" },
246
+ limit: { type: "number", description: "Max results (default 10)" }
247
+ },
248
+ required: ["query"]
249
+ },
250
+ handler: (args, store) => {
251
+ const projectId = args.project ? store.getProjectByName(args.project)?.id || args.project : "";
252
+ const results = store.search(projectId, args.query, args.limit || 10);
253
+ return { query: args.query, count: results.length, results };
254
+ }
255
+ },
256
+ {
257
+ name: "nexus_read_atom",
258
+ description: "Read an atom by ID or title. Returns atom details, blocks, children, and bonds.",
259
+ inputSchema: {
260
+ type: "object",
261
+ properties: {
262
+ id: { type: "string", description: "Atom UUID" },
263
+ title: { type: "string", description: "Atom title (alternative to id)" },
264
+ project: { type: "string", description: "Project name (required if searching by title)" }
265
+ },
266
+ required: []
267
+ },
268
+ handler: (args, store) => {
269
+ let atom = args.id ? store.getAtom(args.id) : void 0;
270
+ if (!atom && args.title && args.project) {
271
+ const projectId = store.getProjectByName(args.project)?.id || args.project;
272
+ atom = store.getAtomsByProject(projectId).find((a) => a.title === args.title);
273
+ }
274
+ if (!atom) throw new Error("Atom not found");
275
+ const children = store.getAtomChildren(atom.id).map((a) => ({ id: a.id, title: a.title, type: a.metadata?.type || "text" }));
276
+ const blocks = store.getBlocksByAtom(atom.id);
277
+ const bonds = store.getBondsByAtom(atom.id);
278
+ return { atom, children, blocks, bonds };
279
+ }
280
+ },
281
+ {
282
+ name: "nexus_create_atom",
283
+ description: "Create a new atom in a project.",
284
+ inputSchema: {
285
+ type: "object",
286
+ properties: {
287
+ project: { type: "string", description: "Project name or ID" },
288
+ title: { type: "string", description: "Atom title/path" },
289
+ type: { type: "string", description: "Atom type (text, memory, stream, thought, task)" },
290
+ parent_id: { type: "string", description: "Parent atom ID (optional)" },
291
+ tags: { type: "array", items: { type: "string" }, description: "Tags (optional)" },
292
+ assistant_id: { type: "string", description: "Creator assistant ID" }
293
+ },
294
+ required: ["project", "title"]
295
+ },
296
+ handler: (args, store) => {
297
+ const project = store.getProjectByName(args.project) || store.getProject(args.project);
298
+ if (!project) throw new Error("Project not found");
299
+ const assistantId = args.assistant_id || "mcp-assistant";
300
+ const atom = store.createAtom(project.id, args.title, args.type || "text", assistantId, {
301
+ parentId: args.parent_id,
302
+ tags: args.tags
303
+ });
304
+ return { success: true, atom };
305
+ }
306
+ },
307
+ {
308
+ name: "nexus_update_atom",
309
+ description: "Update an atom's title or parent.",
310
+ inputSchema: {
311
+ type: "object",
312
+ properties: {
313
+ id: { type: "string", description: "Atom UUID" },
314
+ title: { type: "string", description: "New title" },
315
+ parent_id: { type: ["string", "null"], description: "New parent ID or null for root" },
316
+ assistant_id: { type: "string", description: "Updater assistant ID" }
317
+ },
318
+ required: ["id"]
319
+ },
320
+ handler: (args, store) => {
321
+ const assistantId = args.assistant_id || "mcp-assistant";
322
+ if (args.parent_id !== void 0) {
323
+ const updated2 = store.updateAtomParent(args.id, args.parent_id, assistantId);
324
+ if (!updated2) throw new Error("Invalid parent or atom not found");
325
+ }
326
+ const updated = store.updateAtom(args.id, { title: args.title }, assistantId);
327
+ return { success: true, atom: updated || store.getAtom(args.id) };
328
+ }
329
+ },
330
+ {
331
+ name: "nexus_create_bond",
332
+ description: "Create a bond (graph connection) between two atoms.",
333
+ inputSchema: {
334
+ type: "object",
335
+ properties: {
336
+ source_id: { type: "string", description: "Source atom ID" },
337
+ target_id: { type: "string", description: "Target atom ID" },
338
+ label: { type: "string", description: "Bond label (default: connects)" },
339
+ color: { type: "string", description: "Hex color (optional)" }
340
+ },
341
+ required: ["source_id", "target_id"]
342
+ },
343
+ handler: (args, store) => {
344
+ const bond = store.createBond(args.source_id, args.target_id, args.label || "connects", args.color);
345
+ return { success: true, bond };
346
+ }
347
+ },
348
+ {
349
+ name: "nexus_request_checkout",
350
+ description: "Request an exclusive checkout (lock) on an atom.",
351
+ inputSchema: {
352
+ type: "object",
353
+ properties: {
354
+ atom_id: { type: "string", description: "Atom UUID" },
355
+ assistant_id: { type: "string", description: "Assistant requesting checkout" },
356
+ reason: { type: "string", description: "Reason for checkout" }
357
+ },
358
+ required: ["atom_id", "assistant_id"]
359
+ },
360
+ handler: (args, store) => {
361
+ const result = store.checkout(args.atom_id, args.assistant_id, "exclusive", args.reason || "");
362
+ return result;
363
+ }
364
+ },
365
+ {
366
+ name: "nexus_release_checkout",
367
+ description: "Release a checkout (unlock) on an atom.",
368
+ inputSchema: {
369
+ type: "object",
370
+ properties: {
371
+ atom_id: { type: "string", description: "Atom UUID" },
372
+ assistant_id: { type: "string", description: "Assistant releasing checkout" }
373
+ },
374
+ required: ["atom_id", "assistant_id"]
375
+ },
376
+ handler: (args, store) => {
377
+ const success = store.releaseCheckout(args.atom_id, args.assistant_id);
378
+ return { success };
379
+ }
380
+ },
381
+ {
382
+ name: "nexus_subscribe_to_atom",
383
+ description: "Subscribe to real-time updates for an atom via WebSocket.",
384
+ inputSchema: {
385
+ type: "object",
386
+ properties: {
387
+ atom_id: { type: "string", description: "Atom UUID to subscribe to" }
388
+ },
389
+ required: ["atom_id"]
390
+ },
391
+ handler: (args, _store) => {
392
+ return { success: true, wsEndpoint: "ws://localhost:7321/ws", atom_id: args.atom_id, message: "Connect via WebSocket and send { type: 'subscribe', atomId: '<id>' }" };
393
+ }
394
+ },
395
+ {
396
+ name: "nexus_batch",
397
+ description: "Execute multiple MCP tools in a single request. Reduces round-trip token waste.",
398
+ inputSchema: {
399
+ type: "object",
400
+ properties: {
401
+ operations: {
402
+ type: "array",
403
+ items: {
404
+ type: "object",
405
+ properties: {
406
+ tool: { type: "string", description: "Tool name to call" },
407
+ params: { type: "object", description: "Parameters for the tool" }
408
+ },
409
+ required: ["tool"]
410
+ },
411
+ description: "Ordered list of tool calls"
412
+ },
413
+ stop_on_error: { type: "boolean", description: "If true, stop at first error. Default: false", default: false }
414
+ },
415
+ required: ["operations"]
416
+ },
417
+ handler: (args, store) => {
418
+ const operations = args.operations || [];
419
+ const stopOnError = args.stop_on_error ?? false;
420
+ const results = [];
421
+ const toolMap = /* @__PURE__ */ new Map();
422
+ for (const t of TOOLS) toolMap.set(t.name, t);
423
+ for (let i = 0; i < operations.length; i++) {
424
+ const op = operations[i];
425
+ const tool = toolMap.get(op.tool);
426
+ if (!tool) {
427
+ results.push({ index: i, tool: op.tool, error: `Tool not found: ${op.tool}` });
428
+ if (stopOnError) break;
429
+ continue;
430
+ }
431
+ try {
432
+ const result = tool.handler(op.params || {}, store);
433
+ results.push({ index: i, tool: op.tool, success: true, result });
434
+ } catch (err) {
435
+ results.push({ index: i, tool: op.tool, error: err.message });
436
+ if (stopOnError) break;
437
+ }
438
+ }
439
+ return { count: operations.length, completed: results.length, results };
440
+ }
441
+ }
442
+ ];
443
+
444
+ // src/mcp/server.ts
445
+ var McpServer = class {
446
+ store;
447
+ toolMap = /* @__PURE__ */ new Map();
448
+ constructor(store) {
449
+ this.store = store;
450
+ for (const t of TOOLS) this.toolMap.set(t.name, t);
451
+ }
452
+ /**
453
+ * Returns `null` for notifications (no response needed).
454
+ */
455
+ handleRequest(req) {
456
+ if (req.id === null || req.id === void 0) {
457
+ return null;
458
+ }
459
+ try {
460
+ switch (req.method) {
461
+ case "initialize":
462
+ return this.ok(req.id, {
463
+ protocolVersion: "2024-11-05",
464
+ capabilities: { tools: {}, resources: {}, prompts: {} },
465
+ serverInfo: { name: "mnemosyne-mcp", version: "2.0.0" }
466
+ });
467
+ case "tools/list":
468
+ return this.ok(req.id, {
469
+ tools: TOOLS.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }))
470
+ });
471
+ case "tools/call": {
472
+ const { name, arguments: args } = req.params || {};
473
+ const tool = this.toolMap.get(name);
474
+ if (!tool) return this.err(req.id, -32601, `Tool not found: ${name}`);
475
+ const result = tool.handler(args || {}, this.store);
476
+ return this.ok(req.id, { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] });
477
+ }
478
+ case "resources/list":
479
+ return this.ok(req.id, { resources: [] });
480
+ case "prompts/list":
481
+ return this.ok(req.id, { prompts: [] });
482
+ default:
483
+ return this.err(req.id, -32601, `Method not found: ${req.method}`);
484
+ }
485
+ } catch (e) {
486
+ return this.err(req.id, -32603, e.message);
487
+ }
488
+ }
489
+ getManifest() {
490
+ return {
491
+ name: "Mnemosyne",
492
+ version: "2.0.0",
493
+ description: "Knowledge base MCP server for projects, atoms, blocks, and bonds.",
494
+ protocol: "mcp",
495
+ transport: ["stdio", "sse"],
496
+ endpoints: {
497
+ sse: "/mcp/sse",
498
+ messages: "/mcp/messages"
499
+ },
500
+ tools: TOOLS.map((t) => ({ name: t.name, description: t.description }))
501
+ };
502
+ }
503
+ ok(id, result) {
504
+ return { jsonrpc: "2.0", id, result };
505
+ }
506
+ err(id, code, message) {
507
+ return { jsonrpc: "2.0", id, error: { code, message } };
508
+ }
509
+ };
510
+
511
+ // src/mcp/sse.ts
512
+ var McpSseTransport = class {
513
+ server;
514
+ sessions = /* @__PURE__ */ new Map();
515
+ sessionCounter = 0;
516
+ constructor(server) {
517
+ this.server = server;
518
+ }
519
+ handleSse(req, res) {
520
+ const id = `sess_${++this.sessionCounter}_${Date.now()}`;
521
+ res.writeHead(200, {
522
+ "Content-Type": "text/event-stream",
523
+ "Cache-Control": "no-cache",
524
+ "Connection": "keep-alive"
525
+ });
526
+ this.sessions.set(id, { id, res });
527
+ this.sendSse(id, "endpoint", "/mcp/messages?session_id=" + id);
528
+ req.on("close", () => this.sessions.delete(id));
529
+ }
530
+ handleMessage(req, res, body) {
531
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
532
+ const sessionId = url.searchParams.get("session_id") || "";
533
+ const request = body;
534
+ if (!request || request.jsonrpc !== "2.0") {
535
+ res.writeHead(400, { "Content-Type": "application/json" });
536
+ res.end(JSON.stringify({ error: "invalid_jsonrpc" }));
537
+ return;
538
+ }
539
+ const response = this.server.handleRequest(request);
540
+ if (!response) {
541
+ res.writeHead(200, { "Content-Type": "application/json" });
542
+ res.end("{}");
543
+ return;
544
+ }
545
+ res.writeHead(200, { "Content-Type": "application/json" });
546
+ res.end(JSON.stringify(response));
547
+ if (sessionId) this.sendSse(sessionId, "message", JSON.stringify(response));
548
+ }
549
+ sendSse(sessionId, event, data) {
550
+ const session = this.sessions.get(sessionId);
551
+ if (!session) return;
552
+ session.res.write(`event: ${event}
553
+ data: ${data}
554
+
555
+ `);
556
+ }
557
+ };
558
+
559
+ // src/api/middleware.ts
560
+ var RateLimiter = class {
561
+ rates = /* @__PURE__ */ new Map();
562
+ limit;
563
+ windowMs;
564
+ constructor(limit, windowMs) {
565
+ this.limit = limit;
566
+ this.windowMs = windowMs;
567
+ setInterval(() => this.cleanup(), 6e4);
568
+ }
569
+ check(token) {
570
+ const now = Date.now();
571
+ let window = this.rates.get(token);
572
+ if (!window || window.resetAt < now) {
573
+ window = { count: 0, resetAt: now + this.windowMs };
574
+ this.rates.set(token, window);
575
+ }
576
+ if (window.count >= this.limit) return false;
577
+ window.count++;
578
+ return true;
579
+ }
580
+ cleanup() {
581
+ const now = Date.now();
582
+ for (const [key, window] of this.rates.entries()) {
583
+ if (window.resetAt < now) this.rates.delete(key);
584
+ }
585
+ }
586
+ };
587
+ function setCorsHeaders(res) {
588
+ res.setHeader("Access-Control-Allow-Origin", "*");
589
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS");
590
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
591
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
592
+ res.setHeader("Pragma", "no-cache");
593
+ res.setHeader("Expires", "0");
594
+ }
595
+ function authenticate(store, req) {
596
+ const token = req.headers.authorization?.replace("Bearer ", "");
597
+ return token ? store.getAssistant(token) : void 0;
598
+ }
599
+
600
+ // src/api/utils.ts
601
+ function json(res, status, data) {
602
+ res.writeHead(status, { "Content-Type": "application/json" });
603
+ res.end(JSON.stringify(data));
604
+ }
605
+ function readBody(req) {
606
+ return new Promise((resolve4) => {
607
+ let body = "";
608
+ req.on("data", (chunk) => body += chunk);
609
+ req.on("end", () => {
610
+ try {
611
+ resolve4(JSON.parse(body));
612
+ } catch {
613
+ resolve4({});
614
+ }
615
+ });
616
+ });
617
+ }
618
+ function resolveProjectId(store, idOrName) {
619
+ if (!idOrName) return "";
620
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(idOrName)) return idOrName;
621
+ const p = store.getProjectByName(idOrName);
622
+ return p?.id || idOrName;
623
+ }
624
+ function serializeAtom(store, atom, compact = false) {
625
+ if (compact) {
626
+ return {
627
+ id: atom.id,
628
+ title: atom.title,
629
+ type: atom.metadata?.type || "memory",
630
+ status: atom.status
631
+ };
632
+ }
633
+ const blockCount = atom.block_count ?? store.getBlocksByAtom(atom.id).length;
634
+ return {
635
+ id: atom.id,
636
+ project_id: atom.project_id,
637
+ parent_id: atom.parent_id,
638
+ title: atom.title,
639
+ summary: atom.summary,
640
+ type: atom.metadata?.type || "memory",
641
+ icon: atom.icon,
642
+ color: atom.color,
643
+ metadata: atom.metadata,
644
+ auto_path: atom.auto_path,
645
+ path_overridden: atom.path_overridden,
646
+ status: atom.status,
647
+ status_updated_at: atom.status_updated_at,
648
+ block_count: atom.block_count,
649
+ bond_count: atom.bond_count,
650
+ template_id: atom.template_id,
651
+ embedding_status: atom.embedding_status,
652
+ state: {
653
+ lock: atom.locked_by ? { assistantId: atom.locked_by, reason: atom.locked_reason } : null,
654
+ queue: [],
655
+ blockCount,
656
+ lastActivity: atom.updated_at
657
+ }
658
+ };
659
+ }
660
+ function pickFields(obj, fields) {
661
+ const result = {};
662
+ for (const f of fields) {
663
+ if (f.includes(".")) {
664
+ const [head, ...tail] = f.split(".");
665
+ if (obj[head] != null) {
666
+ result[head] = result[head] || (Array.isArray(obj[head]) ? [] : {});
667
+ if (Array.isArray(obj[head])) {
668
+ result[head] = obj[head].map((item) => pickFields(item, [tail.join(".")]));
669
+ } else {
670
+ const nested = pickFields(obj[head], [tail.join(".")]);
671
+ Object.assign(result[head], nested);
672
+ }
673
+ }
674
+ } else if (obj[f] !== void 0) {
675
+ result[f] = obj[f];
676
+ }
677
+ }
678
+ return result;
679
+ }
680
+ function exportAtomMarkdown(store, atom) {
681
+ const blocks = store.getBlocksByAtom(atom.id).sort((a, b) => (a.order_index || 0) - (b.order_index || 0));
682
+ const bonds = store.getBondsByAtom(atom.id);
683
+ const parent = atom.parent_id ? store.getAtom(atom.parent_id) : null;
684
+ let md = `---
685
+ `;
686
+ md += `title: "${atom.title}"
687
+ `;
688
+ md += `type: ${atom.type || "memory"}
689
+ `;
690
+ md += `status: ${atom.status || "draft"}
691
+ `;
692
+ md += `path: ${atom.auto_path || "/"}
693
+ `;
694
+ if (atom.summary) md += `summary: "${atom.summary}"
695
+ `;
696
+ if (atom.metadata?.tags?.length) md += `tags: [${atom.metadata.tags.map((t) => `"${t}"`).join(", ")}]
697
+ `;
698
+ if (parent) md += `parent: "${parent.title}"
699
+ `;
700
+ md += `exported_at: ${(/* @__PURE__ */ new Date()).toISOString()}
701
+ `;
702
+ md += `---
703
+
704
+ `;
705
+ md += `# ${atom.title}
706
+
707
+ `;
708
+ if (atom.summary) md += `${atom.summary}
709
+
710
+ `;
711
+ for (const b of blocks) {
712
+ const content = b.content || "";
713
+ if (b.type === "text") {
714
+ try {
715
+ const p = JSON.parse(content);
716
+ md += `${p.text || content}
717
+
718
+ `;
719
+ } catch {
720
+ md += `${content}
721
+
722
+ `;
723
+ }
724
+ } else if (b.type === "code") {
725
+ const lines = content.split("\n");
726
+ const first = lines[0]?.trim() || "";
727
+ let lang = "";
728
+ let code = content;
729
+ if (first.startsWith("```")) {
730
+ lang = first.replace(/```/g, "").trim();
731
+ code = lines.slice(1).join("\n").replace(/```$/, "").trim();
732
+ }
733
+ md += `\`\`\`${lang}
734
+ ${code}
735
+ \`\`\`
736
+
737
+ `;
738
+ } else if (b.type === "checklist") {
739
+ try {
740
+ const items = JSON.parse(content);
741
+ if (Array.isArray(items)) {
742
+ for (const item of items) md += `- [${item.checked ? "x" : " "}] ${item.text}
743
+ `;
744
+ md += "\n";
745
+ }
746
+ } catch {
747
+ md += `${content}
748
+
749
+ `;
750
+ }
751
+ } else if (b.type === "table") {
752
+ try {
753
+ const rows = JSON.parse(content);
754
+ if (Array.isArray(rows) && rows.length) {
755
+ const headers = Object.keys(rows[0]);
756
+ md += `| ${headers.join(" | ")} |
757
+ `;
758
+ md += `| ${headers.map(() => "---").join(" | ")} |
759
+ `;
760
+ for (const row of rows) md += `| ${headers.map((h) => String(row[h] ?? "")).join(" | ")} |
761
+ `;
762
+ md += "\n";
763
+ }
764
+ } catch {
765
+ md += `${content}
766
+
767
+ `;
768
+ }
769
+ } else if (b.type === "image" || b.type === "file") {
770
+ try {
771
+ const meta = JSON.parse(content);
772
+ if (b.type === "image") md += `![${meta.file_name || "image"}](${meta.hash})
773
+
774
+ `;
775
+ else md += `[${meta.file_name || "file"}](${meta.hash})
776
+
777
+ `;
778
+ } catch {
779
+ md += `${content}
780
+
781
+ `;
782
+ }
783
+ } else if (b.type === "link") {
784
+ const url = content.trim().startsWith("http") ? content.trim() : "https://" + content.trim();
785
+ md += `[${content.trim()}](${url})
786
+
787
+ `;
788
+ } else {
789
+ md += `${content}
790
+
791
+ `;
792
+ }
793
+ }
794
+ const allBonds = [...bonds.outgoing || [], ...bonds.incoming || []];
795
+ if (allBonds.length) {
796
+ md += `---
797
+
798
+ ## Connections
799
+
800
+ `;
801
+ for (const b of allBonds) {
802
+ const otherId = b.source_id === atom.id ? b.target_id : b.source_id;
803
+ const other = store.getAtom(otherId);
804
+ const dir = b.source_id === atom.id ? "\u2192" : "\u2190";
805
+ md += `- ${dir} [[${other ? other.title : otherId}]] (${b.label})
806
+ `;
807
+ }
808
+ md += "\n";
809
+ }
810
+ return md;
811
+ }
812
+ function exportMarkdown(store, projectId) {
813
+ const project = projectId ? store.getProject(projectId) : void 0;
814
+ const atoms = projectId ? store.getAtomsByProject(projectId) : [];
815
+ const bonds = projectId ? store.getGraph(projectId).bonds : [];
816
+ const blockMap = /* @__PURE__ */ new Map();
817
+ for (const atom of atoms) {
818
+ for (const b of store.getBlocksByAtom(atom.id)) {
819
+ blockMap.set(b.id, b);
820
+ }
821
+ }
822
+ let md = `# ${project ? project.name : "Mnemosyne Export"}
823
+
824
+ `;
825
+ md += `*Exported on ${(/* @__PURE__ */ new Date()).toISOString()}*
826
+
827
+ `;
828
+ const atomMap = new Map(atoms.map((a) => [a.id, a]));
829
+ const rootAtoms = atoms.filter((a) => !a.parent_id);
830
+ function renderAtom(atom, depth) {
831
+ const indent = " ".repeat(depth);
832
+ md += `${indent}## ${atom.title}
833
+
834
+ `;
835
+ if (atom.summary) md += `${indent}${atom.summary}
836
+
837
+ `;
838
+ if (atom.metadata?.tags?.length) md += `${indent}*Tags: ${atom.metadata.tags.join(", ")}*
839
+
840
+ `;
841
+ const atomBlocks = Array.from(blockMap.values()).filter((b) => b.atom_id === atom.id).sort((a, b) => a.order_index - b.order_index);
842
+ for (const b of atomBlocks) {
843
+ try {
844
+ const parsed = JSON.parse(b.content || "{}");
845
+ if (parsed.text) md += `${indent}${parsed.text}
846
+
847
+ `;
848
+ else md += `${indent}\`\`\`json
849
+ ${indent}${JSON.stringify(parsed, null, 2).split("\n").join("\n" + indent)}
850
+ ${indent}\`\`\`
851
+
852
+ `;
853
+ } catch {
854
+ md += `${indent}${b.content || ""}
855
+
856
+ `;
857
+ }
858
+ }
859
+ const children = atoms.filter((a) => a.parent_id === atom.id);
860
+ for (const child of children) renderAtom(child, depth + 1);
861
+ }
862
+ for (const root of rootAtoms) renderAtom(root, 0);
863
+ if (bonds.length) {
864
+ md += `---
865
+
866
+ ## Bonds
867
+
868
+ `;
869
+ for (const b of bonds) {
870
+ const src = atomMap.get(b.source_id);
871
+ const tgt = atomMap.get(b.target_id);
872
+ md += `- [[${src ? src.title : b.source_id}]] \u2014${b.label}\u2192 [[${tgt ? tgt.title : b.target_id}]]
873
+ `;
874
+ }
875
+ md += "\n";
876
+ }
877
+ return md;
878
+ }
879
+
880
+ // src/api/routes/assistants.ts
881
+ function handleAssistants(store, pathname, method, res, body) {
882
+ if (pathname === "/api/v1/assistants/register" && method === "POST") {
883
+ const a = body;
884
+ const id = a.id || a.name || crypto.randomUUID();
885
+ const existing = store.getAssistant(id);
886
+ if (existing) {
887
+ json(res, 200, { success: true, token: existing.id, assistant: existing });
888
+ return true;
889
+ }
890
+ const newAssistant = store.registerAssistant({ id, name: a.name || id, role: a.role || "developer", capabilities: a.capabilities || [], description: a.description, provider: a.provider, instructions: a.instructions, config: a.config });
891
+ json(res, 200, { success: true, token: newAssistant.id, assistant: newAssistant });
892
+ return true;
893
+ }
894
+ if (pathname.startsWith("/api/v1/assistants/") && pathname.endsWith("/heartbeat") && method === "POST") {
895
+ const id = pathname.split("/")[4];
896
+ store.heartbeat(id);
897
+ json(res, 200, { success: true });
898
+ return true;
899
+ }
900
+ if (pathname === "/api/v1/assistants" && method === "GET") {
901
+ const compact = new URL(pathname, "http://localhost").searchParams.get("compact") === "true";
902
+ const assistants = store.getAllAssistants().map(
903
+ (a) => compact ? { id: a.id, name: a.name, role: a.role, status: a.status } : a
904
+ );
905
+ json(res, 200, { assistants });
906
+ return true;
907
+ }
908
+ if (pathname.match(/^\/api\/v1\/assistants\/[^\/]+$/) && method === "PATCH") {
909
+ const id = pathname.replace("/api/v1/assistants/", "");
910
+ const a = body;
911
+ const updated = store.updateAssistant(id, a);
912
+ if (!updated) {
913
+ json(res, 404, { error: "assistant_not_found" });
914
+ return true;
915
+ }
916
+ json(res, 200, { assistant: updated });
917
+ return true;
918
+ }
919
+ if (pathname.match(/^\/api\/v1\/assistants\/[^\/]+$/) && method === "DELETE") {
920
+ const id = pathname.replace("/api/v1/assistants/", "");
921
+ const ok = store.deleteAssistant(id);
922
+ json(res, ok ? 200 : 404, { success: ok });
923
+ return true;
924
+ }
925
+ return false;
926
+ }
927
+
928
+ // src/api/routes/projects.ts
929
+ function handleProjects(store, pathname, method, res, body, searchParams) {
930
+ if (pathname === "/api/v1/projects" && method === "GET") {
931
+ const compact = searchParams.get("compact") === "true";
932
+ const projects = store.getProjects().map(
933
+ (p) => compact ? { id: p.id, name: p.name, description: p.description } : p
934
+ );
935
+ json(res, 200, { projects });
936
+ return true;
937
+ }
938
+ if (pathname === "/api/v1/projects" && method === "POST") {
939
+ const { name, description } = body;
940
+ if (!name) {
941
+ json(res, 400, { error: "name_required" });
942
+ return true;
943
+ }
944
+ const project = store.createProject(name, description);
945
+ json(res, 200, { success: true, project });
946
+ return true;
947
+ }
948
+ const projectIndexMatch = pathname.match(/^\/api\/v1\/projects\/([^/]+)\/index$/);
949
+ if (projectIndexMatch && method === "GET") {
950
+ const projectId = resolveProjectId(store, projectIndexMatch[1]);
951
+ const indexAtom = store.getProjectIndexAtom(projectId);
952
+ if (!indexAtom) {
953
+ json(res, 404, { error: "index_not_found" });
954
+ return true;
955
+ }
956
+ const blocks = store.getBlocksByAtom(indexAtom.id);
957
+ const manifestBlock = blocks.find((b) => b.type === "text");
958
+ let manifest = {};
959
+ try {
960
+ if (manifestBlock) manifest = JSON.parse(manifestBlock.content || "{}");
961
+ } catch {
962
+ }
963
+ json(res, 200, {
964
+ project_id: projectId,
965
+ atom_id: indexAtom.id,
966
+ generated_at: indexAtom.updated_at,
967
+ manifest
968
+ });
969
+ return true;
970
+ }
971
+ return false;
972
+ }
973
+
974
+ // src/api/routes/atoms.ts
975
+ function handleAtoms(store, pathname, method, res, body, searchParams, assistant) {
976
+ if (pathname === "/api/v1/atoms" && method === "POST") {
977
+ const { project_id, title, type, parent_id, tags, template, status, auto_path, path_overridden } = body;
978
+ if (!project_id || !title) {
979
+ json(res, 400, { error: "project_id_and_title_required" });
980
+ return true;
981
+ }
982
+ const resolvedProjectId = resolveProjectId(store, project_id);
983
+ if (!resolvedProjectId) {
984
+ json(res, 400, { error: "project_not_found" });
985
+ return true;
986
+ }
987
+ if (!assistant || !store.can(assistant.role, "create", void 0, assistant.id)) {
988
+ json(res, 403, { error: "permission_denied" });
989
+ return true;
990
+ }
991
+ const atom = store.createAtom(resolvedProjectId, title, type || "text", assistant.id, { parentId: parent_id, tags, template, status, auto_path, path_overridden });
992
+ json(res, 200, { atom: serializeAtom(store, atom) });
993
+ return true;
994
+ }
995
+ if (pathname === "/api/v1/atoms" && method === "GET") {
996
+ const projectId = resolveProjectId(store, searchParams.get("project_id") || "");
997
+ const compact = searchParams.get("compact") === "true";
998
+ const atoms = projectId ? store.getAtomsByProject(projectId).map((d) => serializeAtom(store, d, compact)) : [];
999
+ json(res, 200, { atoms });
1000
+ return true;
1001
+ }
1002
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+$/) && method === "GET" && !pathname.includes("/blocks") && !pathname.includes("/bonds") && !pathname.includes("/checkout") && !pathname.includes("/queue")) {
1003
+ const atomId = pathname.replace("/api/v1/atoms/", "");
1004
+ const atom = store.getAtom(atomId);
1005
+ if (!atom) {
1006
+ json(res, 404, { error: "atom_not_found" });
1007
+ return true;
1008
+ }
1009
+ const fieldsParam = searchParams.get("fields");
1010
+ const children = store.getAtomChildren(atomId).map((a) => ({ id: a.id, title: a.title, type: a.metadata?.type || "memory", blockCount: store.getBlocksByAtom(a.id).length }));
1011
+ const full = {
1012
+ atom: serializeAtom(store, atom),
1013
+ children,
1014
+ blocks: store.getBlocksByAtom(atomId),
1015
+ bonds: store.getBondsByAtom(atomId)
1016
+ };
1017
+ const response = fieldsParam ? pickFields(full, fieldsParam.split(",")) : full;
1018
+ json(res, 200, response);
1019
+ return true;
1020
+ }
1021
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+$/) && (method === "PATCH" || method === "POST") && !pathname.includes("/blocks") && !pathname.includes("/bonds")) {
1022
+ const atomId = pathname.replace("/api/v1/atoms/", "");
1023
+ const { parent_id, title, summary, tags, status, auto_path, path_overridden } = body;
1024
+ if (!assistant || !store.can(assistant.role, "update", atomId, assistant.id)) {
1025
+ json(res, 403, { error: "permission_denied" });
1026
+ return true;
1027
+ }
1028
+ if (parent_id !== void 0) {
1029
+ const updated = store.updateAtomParent(atomId, parent_id ?? null, assistant.id);
1030
+ if (!updated) {
1031
+ json(res, 400, { error: "invalid_parent" });
1032
+ return true;
1033
+ }
1034
+ json(res, 200, { atom: serializeAtom(store, updated) });
1035
+ return true;
1036
+ }
1037
+ try {
1038
+ const updated = store.updateAtom(atomId, { title, summary, tags, status, auto_path, path_overridden }, assistant.id);
1039
+ if (!updated) {
1040
+ json(res, 404, { error: "atom_not_found" });
1041
+ return true;
1042
+ }
1043
+ json(res, 200, { atom: serializeAtom(store, updated) });
1044
+ } catch (err) {
1045
+ json(res, 400, { error: err.message });
1046
+ }
1047
+ return true;
1048
+ }
1049
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+$/) && method === "DELETE" && !pathname.includes("/blocks") && !pathname.includes("/bonds") && !pathname.includes("/checkout")) {
1050
+ const atomId = pathname.replace("/api/v1/atoms/", "");
1051
+ if (!assistant || !store.can(assistant.role, "delete", atomId, assistant.id)) {
1052
+ json(res, 403, { error: "permission_denied" });
1053
+ return true;
1054
+ }
1055
+ const ok = store.deleteAtom(atomId, assistant.id);
1056
+ json(res, ok ? 200 : 404, { success: ok });
1057
+ return true;
1058
+ }
1059
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/checkout$/) && method === "POST") {
1060
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/checkout", "");
1061
+ const { mode, reason } = body;
1062
+ if (!assistant || !store.can(assistant.role, "lock", atomId, assistant.id)) {
1063
+ json(res, 403, { error: "permission_denied" });
1064
+ return true;
1065
+ }
1066
+ const result = store.checkout(atomId, assistant.id, mode || "exclusive", reason || "");
1067
+ json(res, result.success ? 200 : 423, result);
1068
+ return true;
1069
+ }
1070
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/checkout$/) && method === "DELETE") {
1071
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/checkout", "");
1072
+ if (!assistant) {
1073
+ json(res, 403, { error: "unauthorized" });
1074
+ return true;
1075
+ }
1076
+ const success = store.releaseCheckout(atomId, assistant.id);
1077
+ json(res, success ? 200 : 400, { success });
1078
+ return true;
1079
+ }
1080
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/queue$/) && method === "GET") {
1081
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/queue", "");
1082
+ const status = store.getQueueStatus(atomId);
1083
+ json(res, status ? 200 : 404, status || { error: "atom_not_found" });
1084
+ return true;
1085
+ }
1086
+ const queueGrantMatch = pathname.match(/^\/api\/v1\/atoms\/([^\/]+)\/queue\/([^\/]+)\/grant$/);
1087
+ if (queueGrantMatch && method === "POST") {
1088
+ const atomId = queueGrantMatch[1];
1089
+ const queueId = queueGrantMatch[2];
1090
+ const ok = store.grantQueueItem(atomId, queueId);
1091
+ json(res, ok ? 200 : 400, { success: ok });
1092
+ return true;
1093
+ }
1094
+ const queueRejectMatch = pathname.match(/^\/api\/v1\/atoms\/([^\/]+)\/queue\/([^\/]+)\/reject$/);
1095
+ if (queueRejectMatch && method === "POST") {
1096
+ const atomId = queueRejectMatch[1];
1097
+ const queueId = queueRejectMatch[2];
1098
+ const ok = store.rejectQueueItem(atomId, queueId);
1099
+ json(res, ok ? 200 : 400, { success: ok });
1100
+ return true;
1101
+ }
1102
+ const queueBumpMatch = pathname.match(/^\/api\/v1\/atoms\/([^\/]+)\/queue\/([^\/]+)\/bump$/);
1103
+ if (queueBumpMatch && method === "POST") {
1104
+ const atomId = queueBumpMatch[1];
1105
+ const queueId = queueBumpMatch[2];
1106
+ const ok = store.bumpQueueItem(atomId, queueId);
1107
+ json(res, ok ? 200 : 400, { success: ok });
1108
+ return true;
1109
+ }
1110
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/permissions$/) && method === "GET") {
1111
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/permissions", "");
1112
+ const perms = store.getAtomPermissions(atomId);
1113
+ json(res, 200, { atom_id: atomId, permissions: perms });
1114
+ return true;
1115
+ }
1116
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/permissions$/) && method === "POST") {
1117
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/permissions", "");
1118
+ const { assistant_id, level } = body;
1119
+ if (!assistant_id || !level) {
1120
+ json(res, 400, { error: "assistant_id_and_level_required" });
1121
+ return true;
1122
+ }
1123
+ store.setAtomPermission(atomId, assistant_id, level, assistant?.id);
1124
+ json(res, 200, { success: true });
1125
+ return true;
1126
+ }
1127
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/permissions\/[^\/]+$/) && method === "DELETE") {
1128
+ const parts = pathname.replace("/api/v1/atoms/", "").split("/");
1129
+ const atomId = parts[0];
1130
+ const assistantId = parts[2];
1131
+ const ok = store.removeAtomPermission(atomId, assistantId);
1132
+ json(res, ok ? 200 : 404, { success: ok });
1133
+ return true;
1134
+ }
1135
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/history$/) && method === "GET") {
1136
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/history", "");
1137
+ const history = store.getAtomHistory(atomId);
1138
+ json(res, 200, { atom_id: atomId, history });
1139
+ return true;
1140
+ }
1141
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/export$/) && method === "GET") {
1142
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/export", "");
1143
+ const format = searchParams.get("format") || "markdown";
1144
+ const atom = store.getAtom(atomId);
1145
+ if (!atom) {
1146
+ json(res, 404, { error: "atom_not_found" });
1147
+ return true;
1148
+ }
1149
+ if (format === "markdown") {
1150
+ const md = exportAtomMarkdown(store, atom);
1151
+ const safeName = (atom.title || "memory").replace(/[^a-z0-9\-_]/gi, "_").toLowerCase();
1152
+ res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8", "Content-Disposition": `attachment; filename="${safeName}.md"` });
1153
+ res.end(md);
1154
+ return true;
1155
+ }
1156
+ json(res, 400, { error: "unsupported_format" });
1157
+ return true;
1158
+ }
1159
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/force-release$/) && method === "POST") {
1160
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/force-release", "");
1161
+ const atom = store.getAtom(atomId);
1162
+ if (!atom) {
1163
+ json(res, 404, { error: "atom_not_found" });
1164
+ return true;
1165
+ }
1166
+ if (!atom.locked_by) {
1167
+ json(res, 200, { success: true, note: "not_locked" });
1168
+ return true;
1169
+ }
1170
+ const ok = store.forceRelease(atomId, assistant?.id || "system");
1171
+ json(res, ok ? 200 : 400, { success: ok });
1172
+ return true;
1173
+ }
1174
+ return false;
1175
+ }
1176
+
1177
+ // src/api/routes/bonds.ts
1178
+ function handleBonds(store, pathname, method, res, body) {
1179
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/bonds$/) && method === "GET") {
1180
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/bonds", "");
1181
+ json(res, 200, store.getBondsByAtom(atomId));
1182
+ return true;
1183
+ }
1184
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/bonds$/) && method === "POST") {
1185
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/bonds", "");
1186
+ const { target_id, label, color } = body;
1187
+ if (!target_id) {
1188
+ json(res, 400, { error: "target_id_required" });
1189
+ return true;
1190
+ }
1191
+ const bond = store.createBond(atomId, target_id, label || "connects", color);
1192
+ json(res, 200, { bond });
1193
+ return true;
1194
+ }
1195
+ if (pathname === "/api/v1/bonds" && method === "DELETE") {
1196
+ const { bond_id } = body;
1197
+ const ok = store.deleteBond(bond_id);
1198
+ json(res, ok ? 200 : 404, { success: ok });
1199
+ return true;
1200
+ }
1201
+ if (pathname.match(/^\/api\/v1\/bonds\/[^\/]+$/) && method === "DELETE") {
1202
+ const bondId = pathname.replace("/api/v1/bonds/", "");
1203
+ const ok = store.deleteBond(bondId);
1204
+ json(res, ok ? 200 : 404, { success: ok });
1205
+ return true;
1206
+ }
1207
+ if (pathname.match(/^\/api\/v1\/bonds\/[^\/]+$/) && method === "PATCH") {
1208
+ const bondId = pathname.replace("/api/v1/bonds/", "");
1209
+ const { label, color } = body;
1210
+ const updated = store.updateBond(bondId, { label, color });
1211
+ if (!updated) {
1212
+ json(res, 404, { error: "bond_not_found" });
1213
+ return true;
1214
+ }
1215
+ json(res, 200, { bond: updated });
1216
+ return true;
1217
+ }
1218
+ return false;
1219
+ }
1220
+
1221
+ // src/api/routes/blocks.ts
1222
+ function handleBlocks(store, pathname, method, res, body, assistant) {
1223
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/blocks$/) && method === "POST") {
1224
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/blocks", "");
1225
+ const { type, content, tags } = body;
1226
+ const isSuggestion = type === "suggestion";
1227
+ const requiredAction = isSuggestion ? "suggest" : "write";
1228
+ if (!assistant || !store.can(assistant.role, requiredAction, atomId, assistant.id)) {
1229
+ json(res, 403, { error: "permission_denied" });
1230
+ return true;
1231
+ }
1232
+ const block = store.createBlock(atomId, type || "text", content || "", assistant.id, { tags });
1233
+ if (type === "file" || type === "image") {
1234
+ store.processFileBlock(atomId, content || "").catch(() => {
1235
+ });
1236
+ }
1237
+ json(res, 200, { block });
1238
+ return true;
1239
+ }
1240
+ if (pathname.match(/^\/api\/v1\/blocks\/[^\/]+$/) && method === "GET") {
1241
+ const blockId = pathname.replace("/api/v1/blocks/", "");
1242
+ const block = store.getBlock(blockId);
1243
+ if (!block) {
1244
+ json(res, 404, { error: "block_not_found" });
1245
+ return true;
1246
+ }
1247
+ json(res, 200, { block });
1248
+ return true;
1249
+ }
1250
+ if (pathname.match(/^\/api\/v1\/blocks\/[^\/]+$/) && method === "PATCH") {
1251
+ const blockId = pathname.replace("/api/v1/blocks/", "");
1252
+ const { content, type, metadata } = body;
1253
+ const block = store.getBlock(blockId);
1254
+ if (!block) {
1255
+ json(res, 404, { error: "block_not_found" });
1256
+ return true;
1257
+ }
1258
+ if (content !== void 0) {
1259
+ store.updateBlock(blockId, content, assistant?.id || "system");
1260
+ }
1261
+ if (type !== void 0 || metadata !== void 0) {
1262
+ const updates = [];
1263
+ const vals = [];
1264
+ if (type !== void 0) {
1265
+ updates.push("type = ?");
1266
+ vals.push(type);
1267
+ }
1268
+ if (metadata !== void 0) {
1269
+ updates.push("metadata = ?");
1270
+ vals.push(JSON.stringify(metadata));
1271
+ }
1272
+ vals.push(blockId);
1273
+ store["stmt"](`UPDATE blocks SET ${updates.join(", ")} WHERE id = ?`).run(...vals);
1274
+ }
1275
+ json(res, 200, { block: store.getBlock(blockId) });
1276
+ return true;
1277
+ }
1278
+ if (pathname.match(/^\/api\/v1\/blocks\/[^\/]+$/) && method === "DELETE") {
1279
+ const blockId = pathname.replace("/api/v1/blocks/", "");
1280
+ const block = store.getBlock(blockId);
1281
+ if (!block) {
1282
+ json(res, 404, { error: "block_not_found" });
1283
+ return true;
1284
+ }
1285
+ const ok = store.deleteBlock(blockId, assistant?.id || "system");
1286
+ json(res, ok ? 200 : 400, { success: ok });
1287
+ return true;
1288
+ }
1289
+ return false;
1290
+ }
1291
+
1292
+ // src/api/routes/search.ts
1293
+ async function handleSearch(store, pathname, method, res, searchParams) {
1294
+ if (pathname === "/api/v1/search" && method === "GET") {
1295
+ const projectId = resolveProjectId(store, searchParams.get("project_id") || "");
1296
+ const q = (searchParams.get("q") || "").toLowerCase();
1297
+ const limit = parseInt(searchParams.get("limit") || "20", 10);
1298
+ const semantic = searchParams.get("semantic") === "true";
1299
+ if (!q) {
1300
+ json(res, 200, { results: [], count: 0 });
1301
+ return true;
1302
+ }
1303
+ let results = [];
1304
+ if (semantic) {
1305
+ const { embedText: embedText2 } = await Promise.resolve().then(() => (init_embedder(), embedder_exports));
1306
+ const queryEmbedding = await embedText2(q);
1307
+ if (queryEmbedding) {
1308
+ results = store.searchHybrid(projectId, q, queryEmbedding, limit);
1309
+ } else {
1310
+ results = store.search(projectId, q, limit);
1311
+ }
1312
+ } else {
1313
+ results = store.search(projectId, q, limit);
1314
+ }
1315
+ json(res, 200, { query: q, semantic, count: results.length, results });
1316
+ return true;
1317
+ }
1318
+ return false;
1319
+ }
1320
+
1321
+ // src/server/files.ts
1322
+ var import_crypto = require("crypto");
1323
+ var import_fs2 = require("fs");
1324
+ var import_path2 = require("path");
1325
+ init_config();
1326
+ var FILES_DIR = (0, import_path2.resolve)(process.cwd(), CONFIG.storage.files_dir);
1327
+ function ensureDir(path) {
1328
+ if (!(0, import_fs2.existsSync)(path)) (0, import_fs2.mkdirSync)(path, { recursive: true });
1329
+ }
1330
+ function hashPath(hash) {
1331
+ return (0, import_path2.resolve)(FILES_DIR, hash.slice(0, 2), hash.slice(2));
1332
+ }
1333
+ function computeHash(buffer) {
1334
+ return (0, import_crypto.createHash)("sha256").update(buffer).digest("hex");
1335
+ }
1336
+ function saveFile(buffer, _fileName) {
1337
+ const hash = computeHash(buffer);
1338
+ const dest = hashPath(hash);
1339
+ if (!(0, import_fs2.existsSync)(dest)) {
1340
+ ensureDir((0, import_path2.dirname)(dest));
1341
+ (0, import_fs2.writeFileSync)(dest, buffer);
1342
+ }
1343
+ return { hash, size: buffer.length, path: dest };
1344
+ }
1345
+ function getFile(hash) {
1346
+ try {
1347
+ return (0, import_fs2.readFileSync)(hashPath(hash));
1348
+ } catch {
1349
+ return null;
1350
+ }
1351
+ }
1352
+
1353
+ // src/api/routes/files.ts
1354
+ function handleFiles(store, pathname, method, req, res, maxFileSizeMb) {
1355
+ if (pathname === "/api/v1/files/upload" && method === "POST") {
1356
+ const fileName = req.headers["x-file-name"] || "upload";
1357
+ const mimeType = req.headers["content-type"] || "application/octet-stream";
1358
+ const chunks = [];
1359
+ req.on("data", (chunk) => chunks.push(chunk));
1360
+ req.on("end", () => {
1361
+ const buffer = Buffer.concat(chunks);
1362
+ const maxBytes = maxFileSizeMb * 1024 * 1024;
1363
+ if (buffer.length > maxBytes) {
1364
+ json(res, 413, { error: "file_too_large", max: `${maxFileSizeMb}MB` });
1365
+ return;
1366
+ }
1367
+ const hash = computeHash(buffer);
1368
+ const existing = store.getAttachmentByHash(hash);
1369
+ if (existing) {
1370
+ json(res, 200, { success: true, hash, file_name: existing.file_name, size: existing.file_size, mime_type: existing.mime_type, dedup: true });
1371
+ return;
1372
+ }
1373
+ const saved = saveFile(buffer, fileName);
1374
+ const attachment = store.createAttachment(hash, fileName, saved.size, mimeType);
1375
+ json(res, 200, { success: true, hash, file_name: fileName, size: saved.size, mime_type: mimeType });
1376
+ });
1377
+ return true;
1378
+ }
1379
+ if (pathname.match(/^\/api\/v1\/files\/[^\/]+$/) && method === "GET") {
1380
+ const hash = pathname.replace("/api/v1/files/", "");
1381
+ const data = getFile(hash);
1382
+ if (!data) {
1383
+ json(res, 404, { error: "file_not_found" });
1384
+ return true;
1385
+ }
1386
+ const attachment = store.getAttachmentByHash(hash);
1387
+ const mime = attachment?.mime_type || "application/octet-stream";
1388
+ res.writeHead(200, { "Content-Type": mime, "Content-Length": data.length });
1389
+ res.end(data);
1390
+ return true;
1391
+ }
1392
+ return false;
1393
+ }
1394
+
1395
+ // src/server/export-format.ts
1396
+ var import_adm_zip = __toESM(require("adm-zip"));
1397
+ var import_crypto2 = require("crypto");
1398
+ var import_fs3 = require("fs");
1399
+ var import_path3 = require("path");
1400
+ var DB_PATH = (0, import_path3.resolve)(process.cwd(), "data", "nexus.db");
1401
+ var FILES_DIR2 = (0, import_path3.resolve)(process.cwd(), "data", "files");
1402
+ function sha256File(path) {
1403
+ return (0, import_crypto2.createHash)("sha256").update((0, import_fs3.readFileSync)(path)).digest("hex");
1404
+ }
1405
+ function sha256Dir(dir) {
1406
+ const hash = (0, import_crypto2.createHash)("sha256");
1407
+ function walk(p) {
1408
+ const stat = (0, import_fs3.statSync)(p);
1409
+ if (stat.isFile()) {
1410
+ hash.update((0, import_fs3.readFileSync)(p));
1411
+ } else if (stat.isDirectory()) {
1412
+ for (const child of (0, import_fs3.readdirSync)(p).sort()) walk((0, import_path3.resolve)(p, child));
1413
+ }
1414
+ }
1415
+ walk(dir);
1416
+ return hash.digest("hex");
1417
+ }
1418
+ function buildMnemosyneExport(projectId, projectName) {
1419
+ const zip = new import_adm_zip.default();
1420
+ zip.addLocalFile(DB_PATH, "", "nexus.db");
1421
+ if ((0, import_fs3.existsSync)(FILES_DIR2)) {
1422
+ zip.addLocalFolder(FILES_DIR2, "files");
1423
+ }
1424
+ const dbChecksum = sha256File(DB_PATH);
1425
+ const filesChecksum = (0, import_fs3.existsSync)(FILES_DIR2) ? sha256Dir(FILES_DIR2) : "";
1426
+ const manifest = {
1427
+ version: "1.1",
1428
+ app: "Mnemosyne",
1429
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
1430
+ project: projectId ? { id: projectId, name: projectName || "" } : null,
1431
+ checksums: { db: dbChecksum, files: filesChecksum }
1432
+ };
1433
+ zip.addFile("manifest.json", Buffer.from(JSON.stringify(manifest, null, 2)));
1434
+ return zip.toBuffer();
1435
+ }
1436
+ function parseMnemosyneImport(zipBuffer) {
1437
+ try {
1438
+ const zip = new import_adm_zip.default(zipBuffer);
1439
+ const manifestEntry = zip.getEntry("manifest.json");
1440
+ if (!manifestEntry) return { valid: false, error: "Missing manifest.json" };
1441
+ const manifest = JSON.parse(manifestEntry.getData().toString("utf-8"));
1442
+ if (manifest.version !== "1.0" && manifest.version !== "1.1") return { valid: false, error: `Unsupported version: ${manifest.version}` };
1443
+ if (!zip.getEntry("nexus.db")) return { valid: false, error: "Missing nexus.db" };
1444
+ return { valid: true, manifest };
1445
+ } catch (err) {
1446
+ return { valid: false, error: err.message };
1447
+ }
1448
+ }
1449
+
1450
+ // src/api/routes/export-import.ts
1451
+ function handleExportImport(store, pathname, method, req, res, body, searchParams, assistant) {
1452
+ if (pathname === "/api/v1/export" && method === "GET") {
1453
+ const format = searchParams.get("format") || "mnemosyne";
1454
+ const projectId = resolveProjectId(store, searchParams.get("project_id") || "");
1455
+ if (format === "markdown") {
1456
+ const md = exportMarkdown(store, projectId);
1457
+ res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8", "Content-Disposition": "attachment; filename=mnemosyne-export.md" });
1458
+ res.end(md);
1459
+ return true;
1460
+ }
1461
+ if (format === "mnemosyne") {
1462
+ const project = projectId ? store.getProject(projectId) : void 0;
1463
+ const zipBuffer = buildMnemosyneExport(projectId, project?.name);
1464
+ res.writeHead(200, {
1465
+ "Content-Type": "application/zip",
1466
+ "Content-Disposition": `attachment; filename="${project?.name || "mnemosyne"}.mnemosyne"`,
1467
+ "Content-Length": zipBuffer.length
1468
+ });
1469
+ res.end(zipBuffer);
1470
+ return true;
1471
+ }
1472
+ if (!assistant || !store.can(assistant.role, "export", "/system")) {
1473
+ json(res, 403, { error: "permission_denied" });
1474
+ return true;
1475
+ }
1476
+ const snapshot = store.exportSnapshot();
1477
+ json(res, 200, snapshot);
1478
+ return true;
1479
+ }
1480
+ if (pathname === "/api/v1/import" && method === "POST") {
1481
+ if (!assistant || !store.can(assistant.role, "import", "/system")) {
1482
+ json(res, 403, { error: "permission_denied" });
1483
+ return true;
1484
+ }
1485
+ const format = searchParams.get("format") || "mnemosyne";
1486
+ if (format === "mnemosyne") {
1487
+ const contentType = req.headers["content-type"] || "";
1488
+ if (contentType.includes("application/zip") || contentType.includes("multipart") || contentType.includes("application/octet-stream")) {
1489
+ const chunks = [];
1490
+ req.on("data", (chunk) => chunks.push(chunk));
1491
+ req.on("end", () => {
1492
+ const buffer = Buffer.concat(chunks);
1493
+ const parsed = parseMnemosyneImport(buffer);
1494
+ if (!parsed.valid) {
1495
+ json(res, 400, { success: false, error: parsed.error });
1496
+ return;
1497
+ }
1498
+ const result = store.importMnemosyneZip(buffer, assistant.id);
1499
+ if (result.success) {
1500
+ json(res, 200, { success: true, atomsRestored: result.atomsRestored });
1501
+ } else {
1502
+ json(res, 400, { success: false, error: result.error });
1503
+ }
1504
+ });
1505
+ return true;
1506
+ }
1507
+ json(res, 400, { success: false, error: "expected_zip_body" });
1508
+ return true;
1509
+ }
1510
+ if (format === "markdown") {
1511
+ const projectId = resolveProjectId(store, searchParams.get("project_id") || "");
1512
+ if (!projectId) {
1513
+ json(res, 400, { error: "project_id_required" });
1514
+ return true;
1515
+ }
1516
+ const md = typeof body === "string" ? body : body.content || "";
1517
+ const title = body.title || void 0;
1518
+ const result = store.importMarkdown(md, projectId, assistant.id, { title });
1519
+ if (result.success) {
1520
+ json(res, 200, { success: true, atomId: result.atomId, bondsCreated: result.bondsCreated });
1521
+ } else {
1522
+ json(res, 400, { success: false, error: result.error });
1523
+ }
1524
+ return true;
1525
+ }
1526
+ json(res, 400, { success: false, error: "unsupported_import_format" });
1527
+ return true;
1528
+ }
1529
+ return false;
1530
+ }
1531
+
1532
+ // src/api/routes/events.ts
1533
+ function handleEvents(store, pathname, method, res, searchParams) {
1534
+ if (pathname === "/api/v1/events" && method === "GET") {
1535
+ const since = parseInt(searchParams.get("since") || "0", 10);
1536
+ const projectId = searchParams.get("project_id") || void 0;
1537
+ const limit = parseInt(searchParams.get("limit") || "50", 10);
1538
+ json(res, 200, { events: store.getEvents(since, projectId, limit) });
1539
+ return true;
1540
+ }
1541
+ if (pathname.match(/^\/api\/v1\/atoms\/[^\/]+\/events$/) && method === "GET") {
1542
+ const atomId = pathname.replace("/api/v1/atoms/", "").replace("/events", "");
1543
+ const limit = parseInt(searchParams.get("limit") || "50", 10);
1544
+ json(res, 200, { events: store.getEventsByAtom(atomId, limit) });
1545
+ return true;
1546
+ }
1547
+ return false;
1548
+ }
1549
+
1550
+ // src/api/routes/health.ts
1551
+ function handleHealth(store, pathname, method, res) {
1552
+ if (pathname === "/health" && method === "GET") {
1553
+ const stats = store.getStats();
1554
+ json(res, 200, {
1555
+ status: "ok",
1556
+ version: "2.0.0",
1557
+ storage: "sqlite",
1558
+ database: store.config.database.path,
1559
+ uptime: process.uptime(),
1560
+ counts: stats
1561
+ });
1562
+ return true;
1563
+ }
1564
+ return false;
1565
+ }
1566
+
1567
+ // src/core/types.ts
1568
+ var TEMPLATES = {
1569
+ blank: { name: "Blank", blocks: [] },
1570
+ "meeting-notes": { name: "Meeting Notes", blocks: [
1571
+ { type: "Attendees", content: "" },
1572
+ { type: "Agenda", content: "" },
1573
+ { type: "Notes", content: "" },
1574
+ { type: "Action Items", content: "" }
1575
+ ] },
1576
+ "decision-record": { name: "Decision Record", blocks: [
1577
+ { type: "Context", content: "" },
1578
+ { type: "Options", content: "" },
1579
+ { type: "Decision", content: "" },
1580
+ { type: "Consequences", content: "" }
1581
+ ] },
1582
+ "bug-report": { name: "Bug Report", blocks: [
1583
+ { type: "Repro Steps", content: "" },
1584
+ { type: "Expected", content: "" },
1585
+ { type: "Actual", content: "" },
1586
+ { type: "Environment", content: "" }
1587
+ ] },
1588
+ "api-design": { name: "API Design", blocks: [
1589
+ { type: "Endpoint", content: "" },
1590
+ { type: "Request", content: "" },
1591
+ { type: "Response", content: "" },
1592
+ { type: "Auth", content: "" },
1593
+ { type: "Errors", content: "" }
1594
+ ] },
1595
+ persona: { name: "Persona", blocks: [
1596
+ { type: "Name", content: "" },
1597
+ { type: "Role", content: "" },
1598
+ { type: "Goals", content: "" },
1599
+ { type: "Pain Points", content: "" },
1600
+ { type: "Quotes", content: "" }
1601
+ ] },
1602
+ "project-spec": { name: "Project Spec", blocks: [
1603
+ { type: "Overview", content: "" },
1604
+ { type: "Goals", content: "" },
1605
+ { type: "Non-Goals", content: "" },
1606
+ { type: "Timeline", content: "" }
1607
+ ] },
1608
+ "daily-journal": { name: "Daily Journal", blocks: [
1609
+ { type: "Date", content: "" },
1610
+ { type: "Wins", content: "" },
1611
+ { type: "Blockers", content: "" },
1612
+ { type: "Tomorrow", content: "" }
1613
+ ] }
1614
+ };
1615
+
1616
+ // src/api/routes/tree.ts
1617
+ function handleTree(store, pathname, method, res, searchParams) {
1618
+ if (pathname === "/api/v1/tree" && method === "GET") {
1619
+ const projectId = resolveProjectId(store, searchParams.get("project_id") || "");
1620
+ const graph = store.getGraph(projectId);
1621
+ json(res, 200, { project_id: projectId, ...graph });
1622
+ return true;
1623
+ }
1624
+ if (pathname === "/api/v1/templates" && method === "GET") {
1625
+ const templates = Object.entries(TEMPLATES).map(([id, t]) => ({ id, name: t.name }));
1626
+ json(res, 200, { templates });
1627
+ return true;
1628
+ }
1629
+ return false;
1630
+ }
1631
+
1632
+ // src/api/router.ts
1633
+ var ApiRouter = class {
1634
+ store;
1635
+ rateLimiter;
1636
+ mcpServer;
1637
+ mcpTransport;
1638
+ constructor(store) {
1639
+ this.store = store;
1640
+ this.rateLimiter = new RateLimiter(
1641
+ store.config.limits.rate_limit_requests,
1642
+ store.config.limits.rate_limit_window_ms
1643
+ );
1644
+ this.mcpServer = new McpServer(store);
1645
+ this.mcpTransport = new McpSseTransport(this.mcpServer);
1646
+ }
1647
+ async handle(req, res) {
1648
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
1649
+ const pathname = url.pathname;
1650
+ const method = req.method || "GET";
1651
+ setCorsHeaders(res);
1652
+ if (method === "OPTIONS") {
1653
+ res.writeHead(204);
1654
+ res.end();
1655
+ return;
1656
+ }
1657
+ try {
1658
+ const assistant = authenticate(this.store, req);
1659
+ const token = req.headers.authorization?.replace("Bearer ", "");
1660
+ if (token && !this.rateLimiter.check(token)) {
1661
+ json(res, 429, { error: "rate_limit" });
1662
+ return;
1663
+ }
1664
+ const filesResult = handleFiles(this.store, pathname, method, req, res, this.store.config.storage.max_file_size_mb);
1665
+ if (filesResult === true) return;
1666
+ const body = method !== "GET" && method !== "DELETE" ? await readBody(req) : {};
1667
+ if (pathname === "/mcp/manifest" && method === "GET") {
1668
+ json(res, 200, this.mcpServer.getManifest());
1669
+ return;
1670
+ }
1671
+ if (pathname === "/mcp/sse" && method === "GET") {
1672
+ this.mcpTransport.handleSse(req, res);
1673
+ return;
1674
+ }
1675
+ if (pathname === "/mcp/messages" && method === "POST") {
1676
+ this.mcpTransport.handleMessage(req, res, body);
1677
+ return;
1678
+ }
1679
+ if (handleAssistants(this.store, pathname, method, res, body)) return;
1680
+ if (handleProjects(this.store, pathname, method, res, body, url.searchParams)) return;
1681
+ if (handleAtoms(this.store, pathname, method, res, body, url.searchParams, assistant)) return;
1682
+ if (handleBonds(this.store, pathname, method, res, body)) return;
1683
+ if (handleBlocks(this.store, pathname, method, res, body, assistant)) return;
1684
+ if (await handleSearch(this.store, pathname, method, res, url.searchParams)) return;
1685
+ if (handleExportImport(this.store, pathname, method, req, res, body, url.searchParams, assistant)) return;
1686
+ if (handleEvents(this.store, pathname, method, res, url.searchParams)) return;
1687
+ if (handleTree(this.store, pathname, method, res, url.searchParams)) return;
1688
+ if (handleHealth(this.store, pathname, method, res)) return;
1689
+ json(res, 404, { error: "not_found", path: pathname, method });
1690
+ } catch (err) {
1691
+ json(res, 500, { error: err.message });
1692
+ }
1693
+ }
1694
+ };
1695
+ // Annotate the CommonJS export names for ESM import in node:
1696
+ 0 && (module.exports = {
1697
+ API
1698
+ });
1699
+ //# sourceMappingURL=api.js.map