@graphmemory/server 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +216 -0
  3. package/dist/api/index.js +473 -0
  4. package/dist/api/rest/code.js +78 -0
  5. package/dist/api/rest/docs.js +80 -0
  6. package/dist/api/rest/embed.js +47 -0
  7. package/dist/api/rest/files.js +64 -0
  8. package/dist/api/rest/graph.js +71 -0
  9. package/dist/api/rest/index.js +371 -0
  10. package/dist/api/rest/knowledge.js +239 -0
  11. package/dist/api/rest/skills.js +285 -0
  12. package/dist/api/rest/tasks.js +273 -0
  13. package/dist/api/rest/tools.js +157 -0
  14. package/dist/api/rest/validation.js +196 -0
  15. package/dist/api/rest/websocket.js +71 -0
  16. package/dist/api/tools/code/get-file-symbols.js +30 -0
  17. package/dist/api/tools/code/get-symbol.js +22 -0
  18. package/dist/api/tools/code/list-files.js +18 -0
  19. package/dist/api/tools/code/search-code.js +27 -0
  20. package/dist/api/tools/code/search-files.js +22 -0
  21. package/dist/api/tools/context/get-context.js +19 -0
  22. package/dist/api/tools/docs/cross-references.js +76 -0
  23. package/dist/api/tools/docs/explain-symbol.js +55 -0
  24. package/dist/api/tools/docs/find-examples.js +52 -0
  25. package/dist/api/tools/docs/get-node.js +24 -0
  26. package/dist/api/tools/docs/get-toc.js +22 -0
  27. package/dist/api/tools/docs/list-snippets.js +46 -0
  28. package/dist/api/tools/docs/list-topics.js +18 -0
  29. package/dist/api/tools/docs/search-files.js +22 -0
  30. package/dist/api/tools/docs/search-snippets.js +43 -0
  31. package/dist/api/tools/docs/search.js +27 -0
  32. package/dist/api/tools/file-index/get-file-info.js +21 -0
  33. package/dist/api/tools/file-index/list-all-files.js +28 -0
  34. package/dist/api/tools/file-index/search-all-files.js +24 -0
  35. package/dist/api/tools/knowledge/add-attachment.js +31 -0
  36. package/dist/api/tools/knowledge/create-note.js +20 -0
  37. package/dist/api/tools/knowledge/create-relation.js +29 -0
  38. package/dist/api/tools/knowledge/delete-note.js +19 -0
  39. package/dist/api/tools/knowledge/delete-relation.js +23 -0
  40. package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
  41. package/dist/api/tools/knowledge/get-note.js +20 -0
  42. package/dist/api/tools/knowledge/list-notes.js +18 -0
  43. package/dist/api/tools/knowledge/list-relations.js +17 -0
  44. package/dist/api/tools/knowledge/remove-attachment.js +19 -0
  45. package/dist/api/tools/knowledge/search-notes.js +25 -0
  46. package/dist/api/tools/knowledge/update-note.js +34 -0
  47. package/dist/api/tools/skills/add-attachment.js +31 -0
  48. package/dist/api/tools/skills/bump-usage.js +19 -0
  49. package/dist/api/tools/skills/create-skill-link.js +25 -0
  50. package/dist/api/tools/skills/create-skill.js +26 -0
  51. package/dist/api/tools/skills/delete-skill-link.js +23 -0
  52. package/dist/api/tools/skills/delete-skill.js +20 -0
  53. package/dist/api/tools/skills/find-linked-skills.js +25 -0
  54. package/dist/api/tools/skills/get-skill.js +21 -0
  55. package/dist/api/tools/skills/link-skill.js +23 -0
  56. package/dist/api/tools/skills/list-skills.js +20 -0
  57. package/dist/api/tools/skills/recall-skills.js +18 -0
  58. package/dist/api/tools/skills/remove-attachment.js +19 -0
  59. package/dist/api/tools/skills/search-skills.js +25 -0
  60. package/dist/api/tools/skills/update-skill.js +58 -0
  61. package/dist/api/tools/tasks/add-attachment.js +31 -0
  62. package/dist/api/tools/tasks/create-task-link.js +25 -0
  63. package/dist/api/tools/tasks/create-task.js +26 -0
  64. package/dist/api/tools/tasks/delete-task-link.js +23 -0
  65. package/dist/api/tools/tasks/delete-task.js +20 -0
  66. package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
  67. package/dist/api/tools/tasks/get-task.js +20 -0
  68. package/dist/api/tools/tasks/link-task.js +23 -0
  69. package/dist/api/tools/tasks/list-tasks.js +25 -0
  70. package/dist/api/tools/tasks/move-task.js +38 -0
  71. package/dist/api/tools/tasks/remove-attachment.js +19 -0
  72. package/dist/api/tools/tasks/search-tasks.js +25 -0
  73. package/dist/api/tools/tasks/update-task.js +58 -0
  74. package/dist/cli/index.js +617 -0
  75. package/dist/cli/indexer.js +275 -0
  76. package/dist/graphs/attachment-types.js +74 -0
  77. package/dist/graphs/code-types.js +10 -0
  78. package/dist/graphs/code.js +204 -0
  79. package/dist/graphs/docs.js +231 -0
  80. package/dist/graphs/file-index-types.js +10 -0
  81. package/dist/graphs/file-index.js +310 -0
  82. package/dist/graphs/file-lang.js +119 -0
  83. package/dist/graphs/knowledge-types.js +32 -0
  84. package/dist/graphs/knowledge.js +768 -0
  85. package/dist/graphs/manager-types.js +87 -0
  86. package/dist/graphs/skill-types.js +10 -0
  87. package/dist/graphs/skill.js +1016 -0
  88. package/dist/graphs/task-types.js +17 -0
  89. package/dist/graphs/task.js +972 -0
  90. package/dist/lib/access.js +67 -0
  91. package/dist/lib/embedder.js +235 -0
  92. package/dist/lib/events-log.js +401 -0
  93. package/dist/lib/file-import.js +328 -0
  94. package/dist/lib/file-mirror.js +461 -0
  95. package/dist/lib/frontmatter.js +17 -0
  96. package/dist/lib/jwt.js +146 -0
  97. package/dist/lib/mirror-watcher.js +637 -0
  98. package/dist/lib/multi-config.js +393 -0
  99. package/dist/lib/parsers/code.js +214 -0
  100. package/dist/lib/parsers/codeblock.js +33 -0
  101. package/dist/lib/parsers/docs.js +199 -0
  102. package/dist/lib/parsers/languages/index.js +15 -0
  103. package/dist/lib/parsers/languages/registry.js +68 -0
  104. package/dist/lib/parsers/languages/types.js +2 -0
  105. package/dist/lib/parsers/languages/typescript.js +306 -0
  106. package/dist/lib/project-manager.js +458 -0
  107. package/dist/lib/promise-queue.js +22 -0
  108. package/dist/lib/search/bm25.js +167 -0
  109. package/dist/lib/search/code.js +103 -0
  110. package/dist/lib/search/docs.js +106 -0
  111. package/dist/lib/search/file-index.js +31 -0
  112. package/dist/lib/search/files.js +61 -0
  113. package/dist/lib/search/knowledge.js +101 -0
  114. package/dist/lib/search/skills.js +104 -0
  115. package/dist/lib/search/tasks.js +103 -0
  116. package/dist/lib/team.js +89 -0
  117. package/dist/lib/watcher.js +67 -0
  118. package/dist/ui/assets/index-D6oxrVF7.js +1759 -0
  119. package/dist/ui/assets/index-kKd4mVrh.css +1 -0
  120. package/dist/ui/favicon.svg +1 -0
  121. package/dist/ui/icons.svg +24 -0
  122. package/dist/ui/index.html +14 -0
  123. package/package.json +89 -0
@@ -0,0 +1,461 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.mirrorNoteCreate = mirrorNoteCreate;
40
+ exports.mirrorNoteUpdate = mirrorNoteUpdate;
41
+ exports.mirrorTaskCreate = mirrorTaskCreate;
42
+ exports.mirrorTaskUpdate = mirrorTaskUpdate;
43
+ exports.mirrorSkillCreate = mirrorSkillCreate;
44
+ exports.mirrorSkillUpdate = mirrorSkillUpdate;
45
+ exports.mirrorNoteRelation = mirrorNoteRelation;
46
+ exports.mirrorTaskRelation = mirrorTaskRelation;
47
+ exports.mirrorSkillRelation = mirrorSkillRelation;
48
+ exports.mirrorAttachmentEvent = mirrorAttachmentEvent;
49
+ exports.deleteMirrorDir = deleteMirrorDir;
50
+ exports.sanitizeFilename = sanitizeFilename;
51
+ exports.writeAttachment = writeAttachment;
52
+ exports.deleteAttachment = deleteAttachment;
53
+ exports.getAttachmentPath = getAttachmentPath;
54
+ const fs = __importStar(require("fs"));
55
+ const path = __importStar(require("path"));
56
+ const crypto_1 = __importDefault(require("crypto"));
57
+ const frontmatter_1 = require("./frontmatter");
58
+ const events_log_1 = require("./events-log");
59
+ /** Write to a temp file then rename — atomic on same filesystem. */
60
+ function atomicWriteFileSync(filePath, data, encoding) {
61
+ const tmp = `${filePath}.${crypto_1.default.randomBytes(4).toString('hex')}.tmp`;
62
+ fs.writeFileSync(tmp, data, encoding);
63
+ fs.renameSync(tmp, filePath);
64
+ }
65
+ function tsToIso(ts) {
66
+ if (ts == null || ts === 0)
67
+ return null;
68
+ return new Date(ts).toISOString();
69
+ }
70
+ function buildOutgoingRelations(entityId, relations) {
71
+ return relations
72
+ .filter(r => r.fromId === entityId)
73
+ .map(r => {
74
+ const entry = { to: r.toId, kind: r.kind };
75
+ if (r.targetGraph)
76
+ entry.graph = r.targetGraph;
77
+ return entry;
78
+ });
79
+ }
80
+ /** Append a 'created' event + write content.md + regenerate note.md snapshot. */
81
+ function mirrorNoteCreate(notesDir, noteId, attrs, relations) {
82
+ try {
83
+ const entityDir = path.join(notesDir, noteId);
84
+ fs.mkdirSync(entityDir, { recursive: true });
85
+ const eventsPath = path.join(entityDir, 'events.jsonl');
86
+ if (!fs.existsSync(eventsPath)) {
87
+ const event = {
88
+ op: 'created',
89
+ id: noteId,
90
+ title: attrs.title,
91
+ tags: attrs.tags,
92
+ createdAt: attrs.createdAt,
93
+ };
94
+ if (attrs.createdBy)
95
+ event.createdBy = attrs.createdBy;
96
+ (0, events_log_1.appendEvent)(eventsPath, event);
97
+ }
98
+ atomicWriteFileSync(path.join(entityDir, 'content.md'), attrs.content, 'utf-8');
99
+ _regenerateNoteSnapshot(notesDir, noteId, attrs, relations);
100
+ (0, events_log_1.ensureGitignore)(notesDir, '*/note.md');
101
+ (0, events_log_1.ensureGitattributes)(notesDir);
102
+ }
103
+ catch (err) {
104
+ process.stderr.write(`[file-mirror] failed to mirror note create ${noteId}: ${err}\n`);
105
+ }
106
+ }
107
+ /** Append an 'update' event + (if content changed) write content.md + regenerate note.md. */
108
+ function mirrorNoteUpdate(notesDir, noteId, patch, attrs, relations) {
109
+ try {
110
+ const entityDir = path.join(notesDir, noteId);
111
+ fs.mkdirSync(entityDir, { recursive: true });
112
+ const eventsPath = path.join(entityDir, 'events.jsonl');
113
+ const delta = { op: 'update' };
114
+ if (patch.title !== undefined)
115
+ delta.title = patch.title;
116
+ if (patch.tags !== undefined)
117
+ delta.tags = patch.tags;
118
+ if (patch.by !== undefined)
119
+ delta.by = patch.by;
120
+ else if (attrs.updatedBy)
121
+ delta.by = attrs.updatedBy;
122
+ if (Object.keys(delta).length > 1)
123
+ (0, events_log_1.appendEvent)(eventsPath, delta);
124
+ if (patch.content !== undefined) {
125
+ atomicWriteFileSync(path.join(entityDir, 'content.md'), patch.content, 'utf-8');
126
+ }
127
+ _regenerateNoteSnapshot(notesDir, noteId, attrs, relations);
128
+ }
129
+ catch (err) {
130
+ process.stderr.write(`[file-mirror] failed to mirror note update ${noteId}: ${err}\n`);
131
+ }
132
+ }
133
+ function _regenerateNoteSnapshot(notesDir, noteId, attrs, relations) {
134
+ const outgoing = buildOutgoingRelations(noteId, relations);
135
+ const fm = {
136
+ id: noteId,
137
+ tags: attrs.tags,
138
+ createdAt: tsToIso(attrs.createdAt),
139
+ updatedAt: tsToIso(attrs.updatedAt),
140
+ version: attrs.version,
141
+ };
142
+ if (attrs.createdBy)
143
+ fm.createdBy = attrs.createdBy;
144
+ if (attrs.updatedBy)
145
+ fm.updatedBy = attrs.updatedBy;
146
+ if (outgoing.length > 0)
147
+ fm.relations = outgoing;
148
+ const body = `# ${attrs.title}\n\n${attrs.content}`;
149
+ const entityDir = path.join(notesDir, noteId);
150
+ fs.mkdirSync(entityDir, { recursive: true });
151
+ atomicWriteFileSync(path.join(entityDir, 'note.md'), (0, frontmatter_1.serializeMarkdown)(fm, body));
152
+ }
153
+ /** Append a 'created' event + write description.md + regenerate task.md snapshot. */
154
+ function mirrorTaskCreate(tasksDir, taskId, attrs, relations) {
155
+ try {
156
+ const entityDir = path.join(tasksDir, taskId);
157
+ fs.mkdirSync(entityDir, { recursive: true });
158
+ const eventsPath = path.join(entityDir, 'events.jsonl');
159
+ if (!fs.existsSync(eventsPath)) {
160
+ const event = {
161
+ op: 'created',
162
+ id: taskId,
163
+ title: attrs.title,
164
+ status: attrs.status,
165
+ priority: attrs.priority,
166
+ tags: attrs.tags,
167
+ dueDate: attrs.dueDate,
168
+ estimate: attrs.estimate,
169
+ completedAt: attrs.completedAt,
170
+ createdAt: attrs.createdAt,
171
+ };
172
+ if (attrs.createdBy)
173
+ event.createdBy = attrs.createdBy;
174
+ (0, events_log_1.appendEvent)(eventsPath, event);
175
+ }
176
+ atomicWriteFileSync(path.join(entityDir, 'description.md'), attrs.description, 'utf-8');
177
+ _regenerateTaskSnapshot(tasksDir, taskId, attrs, relations);
178
+ (0, events_log_1.ensureGitignore)(tasksDir, '*/task.md');
179
+ (0, events_log_1.ensureGitattributes)(tasksDir);
180
+ }
181
+ catch (err) {
182
+ process.stderr.write(`[file-mirror] failed to mirror task create ${taskId}: ${err}\n`);
183
+ }
184
+ }
185
+ /** Append an 'update' event + (if description changed) write description.md + regenerate task.md. */
186
+ function mirrorTaskUpdate(tasksDir, taskId, patch, attrs, relations) {
187
+ try {
188
+ const entityDir = path.join(tasksDir, taskId);
189
+ fs.mkdirSync(entityDir, { recursive: true });
190
+ const eventsPath = path.join(entityDir, 'events.jsonl');
191
+ const delta = { op: 'update' };
192
+ if (patch.title !== undefined)
193
+ delta.title = patch.title;
194
+ if (patch.status !== undefined)
195
+ delta.status = patch.status;
196
+ if (patch.priority !== undefined)
197
+ delta.priority = patch.priority;
198
+ if (patch.tags !== undefined)
199
+ delta.tags = patch.tags;
200
+ if ('dueDate' in patch)
201
+ delta.dueDate = patch.dueDate;
202
+ if ('estimate' in patch)
203
+ delta.estimate = patch.estimate;
204
+ if ('completedAt' in patch)
205
+ delta.completedAt = patch.completedAt;
206
+ if (patch.by !== undefined)
207
+ delta.by = patch.by;
208
+ else if (attrs.updatedBy)
209
+ delta.by = attrs.updatedBy;
210
+ if (Object.keys(delta).length > 1)
211
+ (0, events_log_1.appendEvent)(eventsPath, delta);
212
+ if (patch.description !== undefined) {
213
+ atomicWriteFileSync(path.join(entityDir, 'description.md'), patch.description, 'utf-8');
214
+ }
215
+ _regenerateTaskSnapshot(tasksDir, taskId, attrs, relations);
216
+ }
217
+ catch (err) {
218
+ process.stderr.write(`[file-mirror] failed to mirror task update ${taskId}: ${err}\n`);
219
+ }
220
+ }
221
+ function _regenerateTaskSnapshot(tasksDir, taskId, attrs, relations) {
222
+ const outgoing = buildOutgoingRelations(taskId, relations);
223
+ const fm = {
224
+ id: taskId,
225
+ status: attrs.status,
226
+ priority: attrs.priority,
227
+ tags: attrs.tags,
228
+ assignee: attrs.assignee ?? null,
229
+ dueDate: tsToIso(attrs.dueDate),
230
+ estimate: attrs.estimate,
231
+ completedAt: tsToIso(attrs.completedAt),
232
+ createdAt: tsToIso(attrs.createdAt),
233
+ updatedAt: tsToIso(attrs.updatedAt),
234
+ version: attrs.version,
235
+ };
236
+ if (attrs.createdBy)
237
+ fm.createdBy = attrs.createdBy;
238
+ if (attrs.updatedBy)
239
+ fm.updatedBy = attrs.updatedBy;
240
+ if (outgoing.length > 0)
241
+ fm.relations = outgoing;
242
+ const body = `# ${attrs.title}\n\n${attrs.description}`;
243
+ const entityDir = path.join(tasksDir, taskId);
244
+ fs.mkdirSync(entityDir, { recursive: true });
245
+ atomicWriteFileSync(path.join(entityDir, 'task.md'), (0, frontmatter_1.serializeMarkdown)(fm, body));
246
+ }
247
+ /** Append a 'created' event + write description.md + regenerate skill.md snapshot. */
248
+ function mirrorSkillCreate(skillsDir, skillId, attrs, relations) {
249
+ try {
250
+ const entityDir = path.join(skillsDir, skillId);
251
+ fs.mkdirSync(entityDir, { recursive: true });
252
+ const eventsPath = path.join(entityDir, 'events.jsonl');
253
+ if (!fs.existsSync(eventsPath)) {
254
+ const event = {
255
+ op: 'created',
256
+ id: skillId,
257
+ title: attrs.title,
258
+ tags: attrs.tags,
259
+ steps: attrs.steps,
260
+ triggers: attrs.triggers,
261
+ inputHints: attrs.inputHints,
262
+ filePatterns: attrs.filePatterns,
263
+ source: attrs.source,
264
+ confidence: attrs.confidence,
265
+ usageCount: attrs.usageCount,
266
+ lastUsedAt: attrs.lastUsedAt,
267
+ createdAt: attrs.createdAt,
268
+ };
269
+ if (attrs.createdBy)
270
+ event.createdBy = attrs.createdBy;
271
+ (0, events_log_1.appendEvent)(eventsPath, event);
272
+ }
273
+ atomicWriteFileSync(path.join(entityDir, 'description.md'), attrs.description, 'utf-8');
274
+ _regenerateSkillSnapshot(skillsDir, skillId, attrs, relations);
275
+ (0, events_log_1.ensureGitignore)(skillsDir, '*/skill.md');
276
+ (0, events_log_1.ensureGitattributes)(skillsDir);
277
+ }
278
+ catch (err) {
279
+ process.stderr.write(`[file-mirror] failed to mirror skill create ${skillId}: ${err}\n`);
280
+ }
281
+ }
282
+ /** Append an 'update' event + (if description changed) write description.md + regenerate skill.md. */
283
+ function mirrorSkillUpdate(skillsDir, skillId, patch, attrs, relations) {
284
+ try {
285
+ const entityDir = path.join(skillsDir, skillId);
286
+ fs.mkdirSync(entityDir, { recursive: true });
287
+ const eventsPath = path.join(entityDir, 'events.jsonl');
288
+ const delta = { op: 'update' };
289
+ if (patch.title !== undefined)
290
+ delta.title = patch.title;
291
+ if (patch.tags !== undefined)
292
+ delta.tags = patch.tags;
293
+ if (patch.steps !== undefined)
294
+ delta.steps = patch.steps;
295
+ if (patch.triggers !== undefined)
296
+ delta.triggers = patch.triggers;
297
+ if (patch.inputHints !== undefined)
298
+ delta.inputHints = patch.inputHints;
299
+ if (patch.filePatterns !== undefined)
300
+ delta.filePatterns = patch.filePatterns;
301
+ if (patch.source !== undefined)
302
+ delta.source = patch.source;
303
+ if (patch.confidence !== undefined)
304
+ delta.confidence = patch.confidence;
305
+ if (patch.usageCount !== undefined)
306
+ delta.usageCount = patch.usageCount;
307
+ if ('lastUsedAt' in patch)
308
+ delta.lastUsedAt = patch.lastUsedAt;
309
+ if (patch.by !== undefined)
310
+ delta.by = patch.by;
311
+ else if (attrs.updatedBy)
312
+ delta.by = attrs.updatedBy;
313
+ if (Object.keys(delta).length > 1)
314
+ (0, events_log_1.appendEvent)(eventsPath, delta);
315
+ if (patch.description !== undefined) {
316
+ atomicWriteFileSync(path.join(entityDir, 'description.md'), patch.description, 'utf-8');
317
+ }
318
+ _regenerateSkillSnapshot(skillsDir, skillId, attrs, relations);
319
+ }
320
+ catch (err) {
321
+ process.stderr.write(`[file-mirror] failed to mirror skill update ${skillId}: ${err}\n`);
322
+ }
323
+ }
324
+ function _regenerateSkillSnapshot(skillsDir, skillId, attrs, relations) {
325
+ const outgoing = buildOutgoingRelations(skillId, relations);
326
+ const fm = {
327
+ id: skillId,
328
+ source: attrs.source,
329
+ confidence: attrs.confidence,
330
+ triggers: attrs.triggers,
331
+ inputHints: attrs.inputHints,
332
+ filePatterns: attrs.filePatterns,
333
+ tags: attrs.tags,
334
+ createdAt: tsToIso(attrs.createdAt),
335
+ updatedAt: tsToIso(attrs.updatedAt),
336
+ version: attrs.version,
337
+ };
338
+ if (attrs.createdBy)
339
+ fm.createdBy = attrs.createdBy;
340
+ if (attrs.updatedBy)
341
+ fm.updatedBy = attrs.updatedBy;
342
+ if (outgoing.length > 0)
343
+ fm.relations = outgoing;
344
+ const stepsBlock = attrs.steps.length > 0
345
+ ? `\n\n## Steps\n${attrs.steps.map((s, i) => `${i + 1}. ${s}`).join('\n')}`
346
+ : '';
347
+ const body = `# ${attrs.title}\n\n${attrs.description}${stepsBlock}`;
348
+ const entityDir = path.join(skillsDir, skillId);
349
+ fs.mkdirSync(entityDir, { recursive: true });
350
+ atomicWriteFileSync(path.join(entityDir, 'skill.md'), (0, frontmatter_1.serializeMarkdown)(fm, body));
351
+ }
352
+ // ---------------------------------------------------------------------------
353
+ // Relation + attachment event append helpers
354
+ // ---------------------------------------------------------------------------
355
+ /** Append a relation add/remove event and regenerate snapshot. */
356
+ function mirrorNoteRelation(notesDir, noteId, action, kind, to, attrs, relations, graph) {
357
+ try {
358
+ const entityDir = path.join(notesDir, noteId);
359
+ const eventsPath = path.join(entityDir, 'events.jsonl');
360
+ const event = { op: 'relation', action, kind, to };
361
+ if (graph)
362
+ event.graph = graph;
363
+ (0, events_log_1.appendEvent)(eventsPath, event);
364
+ _regenerateNoteSnapshot(notesDir, noteId, attrs, relations);
365
+ }
366
+ catch (err) {
367
+ process.stderr.write(`[file-mirror] failed to mirror note relation ${noteId}: ${err}\n`);
368
+ }
369
+ }
370
+ function mirrorTaskRelation(tasksDir, taskId, action, kind, to, attrs, relations, graph) {
371
+ try {
372
+ const entityDir = path.join(tasksDir, taskId);
373
+ const eventsPath = path.join(entityDir, 'events.jsonl');
374
+ const event = { op: 'relation', action, kind, to };
375
+ if (graph)
376
+ event.graph = graph;
377
+ (0, events_log_1.appendEvent)(eventsPath, event);
378
+ _regenerateTaskSnapshot(tasksDir, taskId, attrs, relations);
379
+ }
380
+ catch (err) {
381
+ process.stderr.write(`[file-mirror] failed to mirror task relation ${taskId}: ${err}\n`);
382
+ }
383
+ }
384
+ function mirrorSkillRelation(skillsDir, skillId, action, kind, to, attrs, relations, graph) {
385
+ try {
386
+ const entityDir = path.join(skillsDir, skillId);
387
+ const eventsPath = path.join(entityDir, 'events.jsonl');
388
+ const event = { op: 'relation', action, kind, to };
389
+ if (graph)
390
+ event.graph = graph;
391
+ (0, events_log_1.appendEvent)(eventsPath, event);
392
+ _regenerateSkillSnapshot(skillsDir, skillId, attrs, relations);
393
+ }
394
+ catch (err) {
395
+ process.stderr.write(`[file-mirror] failed to mirror skill relation ${skillId}: ${err}\n`);
396
+ }
397
+ }
398
+ /** Append an attachment add/remove event. */
399
+ function mirrorAttachmentEvent(entityDir, action, file) {
400
+ try {
401
+ const eventsPath = path.join(entityDir, 'events.jsonl');
402
+ (0, events_log_1.appendEvent)(eventsPath, { op: 'attachment', action, file });
403
+ }
404
+ catch (err) {
405
+ process.stderr.write(`[file-mirror] failed to mirror attachment event: ${err}\n`);
406
+ }
407
+ }
408
+ // ---------------------------------------------------------------------------
409
+ // Delete
410
+ // ---------------------------------------------------------------------------
411
+ /** Delete the entire mirror directory for a note, task or skill (including attachments). */
412
+ function deleteMirrorDir(dir, id) {
413
+ try {
414
+ fs.rmSync(path.join(dir, id), { recursive: true, force: true });
415
+ }
416
+ catch (err) {
417
+ if (err.code !== 'ENOENT') {
418
+ process.stderr.write(`[file-mirror] failed to delete ${id}/: ${err}\n`);
419
+ }
420
+ }
421
+ }
422
+ // ---------------------------------------------------------------------------
423
+ // Attachment file helpers (paths now go through attachments/ subdir)
424
+ // ---------------------------------------------------------------------------
425
+ /** Sanitize a filename: strip path separators, .., and null bytes. */
426
+ function sanitizeFilename(name) {
427
+ const sanitized = name
428
+ .replace(/\0/g, '')
429
+ .replace(/\.\./g, '')
430
+ .replace(/[/\\]/g, '')
431
+ .trim();
432
+ return sanitized; // empty string is a valid return — callers must check
433
+ }
434
+ /** Write an attachment file to the entity's attachments/ subdirectory. */
435
+ function writeAttachment(baseDir, entityId, filename, data) {
436
+ const safe = sanitizeFilename(filename);
437
+ if (!safe)
438
+ throw new Error('Attachment filename is empty after sanitization');
439
+ const attachmentsDir = path.join(baseDir, entityId, 'attachments');
440
+ fs.mkdirSync(attachmentsDir, { recursive: true });
441
+ fs.writeFileSync(path.join(attachmentsDir, safe), data);
442
+ }
443
+ /** Delete an attachment file from attachments/ subdir. Returns true if it existed. */
444
+ function deleteAttachment(baseDir, entityId, filename) {
445
+ const filePath = path.join(baseDir, entityId, 'attachments', sanitizeFilename(filename));
446
+ try {
447
+ fs.unlinkSync(filePath);
448
+ return true;
449
+ }
450
+ catch (err) {
451
+ if (err.code !== 'ENOENT') {
452
+ process.stderr.write(`[file-mirror] failed to delete attachment ${filename}: ${err}\n`);
453
+ }
454
+ return false;
455
+ }
456
+ }
457
+ /** Get the absolute path of an attachment in attachments/ subdir, or null if not found. */
458
+ function getAttachmentPath(baseDir, entityId, filename) {
459
+ const filePath = path.join(baseDir, entityId, 'attachments', sanitizeFilename(filename));
460
+ return fs.existsSync(filePath) ? filePath : null;
461
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeMarkdown = serializeMarkdown;
4
+ exports.parseMarkdown = parseMarkdown;
5
+ const yaml_1 = require("yaml");
6
+ function serializeMarkdown(frontmatter, body) {
7
+ const yamlStr = (0, yaml_1.stringify)(frontmatter, { lineWidth: 0 }).trimEnd();
8
+ return `---\n${yamlStr}\n---\n\n${body}\n`;
9
+ }
10
+ function parseMarkdown(raw) {
11
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
12
+ if (!match)
13
+ return { frontmatter: {}, body: raw };
14
+ const frontmatter = (0, yaml_1.parse)(match[1], { maxAliasCount: 10 }) ?? {};
15
+ const body = match[2].replace(/\n$/, '');
16
+ return { frontmatter, body };
17
+ }
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hashPassword = hashPassword;
7
+ exports.verifyPassword = verifyPassword;
8
+ exports.parseTtl = parseTtl;
9
+ exports.signAccessToken = signAccessToken;
10
+ exports.signRefreshToken = signRefreshToken;
11
+ exports.verifyToken = verifyToken;
12
+ exports.setAuthCookies = setAuthCookies;
13
+ exports.clearAuthCookies = clearAuthCookies;
14
+ exports.getAccessToken = getAccessToken;
15
+ exports.getRefreshToken = getRefreshToken;
16
+ exports.resolveUserByEmail = resolveUserByEmail;
17
+ const crypto_1 = __importDefault(require("crypto"));
18
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
19
+ // ---------------------------------------------------------------------------
20
+ // Password hashing (scrypt)
21
+ // ---------------------------------------------------------------------------
22
+ const SCRYPT_KEYLEN = 64;
23
+ const SCRYPT_COST = 16384; // N
24
+ const SCRYPT_BLOCK = 8; // r
25
+ const SCRYPT_PARALLEL = 1; // p
26
+ /**
27
+ * Hash a password using scrypt. Returns a string: `$scrypt$N$r$p$salt$hash`
28
+ */
29
+ async function hashPassword(password) {
30
+ const salt = crypto_1.default.randomBytes(16).toString('hex');
31
+ const derived = await new Promise((resolve, reject) => {
32
+ crypto_1.default.scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_COST, r: SCRYPT_BLOCK, p: SCRYPT_PARALLEL }, (err, key) => {
33
+ if (err)
34
+ reject(err);
35
+ else
36
+ resolve(key);
37
+ });
38
+ });
39
+ return `$scrypt$${SCRYPT_COST}$${SCRYPT_BLOCK}$${SCRYPT_PARALLEL}$${salt}$${derived.toString('hex')}`;
40
+ }
41
+ /**
42
+ * Verify a password against a stored hash.
43
+ */
44
+ async function verifyPassword(password, storedHash) {
45
+ const parts = storedHash.split('$');
46
+ // Format: $scrypt$N$r$p$salt$hash → ['', 'scrypt', N, r, p, salt, hash]
47
+ if (parts.length !== 7 || parts[1] !== 'scrypt')
48
+ return false;
49
+ const N = parseInt(parts[2], 10);
50
+ const r = parseInt(parts[3], 10);
51
+ const p = parseInt(parts[4], 10);
52
+ const salt = parts[5];
53
+ const expectedHash = parts[6];
54
+ const derived = await new Promise((resolve, reject) => {
55
+ crypto_1.default.scrypt(password, salt, SCRYPT_KEYLEN, { N, r, p }, (err, key) => {
56
+ if (err)
57
+ reject(err);
58
+ else
59
+ resolve(key);
60
+ });
61
+ });
62
+ const derivedHex = derived.toString('hex');
63
+ // Timing-safe comparison
64
+ if (derivedHex.length !== expectedHash.length)
65
+ return false;
66
+ return crypto_1.default.timingSafeEqual(Buffer.from(derivedHex), Buffer.from(expectedHash));
67
+ }
68
+ /**
69
+ * Parse a TTL string like "15m", "1h", "7d" into seconds.
70
+ */
71
+ function parseTtl(ttl) {
72
+ const match = ttl.match(/^(\d+)(s|m|h|d)$/);
73
+ if (!match)
74
+ throw new Error(`Invalid TTL format: "${ttl}". Expected e.g. "15m", "1h", "7d"`);
75
+ const value = parseInt(match[1], 10);
76
+ if (value <= 0)
77
+ throw new Error(`TTL must be positive, got "${ttl}"`);
78
+ switch (match[2]) {
79
+ case 's': return value;
80
+ case 'm': return value * 60;
81
+ case 'h': return value * 3600;
82
+ case 'd': return value * 86400;
83
+ default: throw new Error(`Invalid TTL unit: ${match[2]}`);
84
+ }
85
+ }
86
+ function signAccessToken(userId, secret, ttl) {
87
+ const payload = { userId, type: 'access' };
88
+ return jsonwebtoken_1.default.sign(payload, secret, { expiresIn: parseTtl(ttl) });
89
+ }
90
+ function signRefreshToken(userId, secret, ttl) {
91
+ const payload = { userId, type: 'refresh' };
92
+ return jsonwebtoken_1.default.sign(payload, secret, { expiresIn: parseTtl(ttl) });
93
+ }
94
+ function verifyToken(token, secret) {
95
+ try {
96
+ const decoded = jsonwebtoken_1.default.verify(token, secret);
97
+ if (!decoded.userId || !decoded.type)
98
+ return null;
99
+ return { userId: decoded.userId, type: decoded.type };
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Cookie helpers
107
+ // ---------------------------------------------------------------------------
108
+ const ACCESS_COOKIE = 'mgm_access';
109
+ const REFRESH_COOKIE = 'mgm_refresh';
110
+ function setAuthCookies(res, accessToken, refreshToken, accessTtl, refreshTtl) {
111
+ const secure = process.env.NODE_ENV === 'production';
112
+ res.cookie(ACCESS_COOKIE, accessToken, {
113
+ httpOnly: true,
114
+ secure,
115
+ sameSite: 'strict',
116
+ path: '/api',
117
+ maxAge: parseTtl(accessTtl) * 1000,
118
+ });
119
+ res.cookie(REFRESH_COOKIE, refreshToken, {
120
+ httpOnly: true,
121
+ secure,
122
+ sameSite: 'strict',
123
+ path: '/api/auth/refresh',
124
+ maxAge: parseTtl(refreshTtl) * 1000,
125
+ });
126
+ }
127
+ function clearAuthCookies(res) {
128
+ res.clearCookie(ACCESS_COOKIE, { path: '/api' });
129
+ res.clearCookie(REFRESH_COOKIE, { path: '/api/auth/refresh' });
130
+ }
131
+ function getAccessToken(req) {
132
+ return req.cookies?.[ACCESS_COOKIE];
133
+ }
134
+ function getRefreshToken(req) {
135
+ return req.cookies?.[REFRESH_COOKIE];
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // User lookup by email
139
+ // ---------------------------------------------------------------------------
140
+ function resolveUserByEmail(email, users) {
141
+ for (const [userId, user] of Object.entries(users)) {
142
+ if (user.email === email)
143
+ return { userId, user };
144
+ }
145
+ return undefined;
146
+ }