@crewx/memory 0.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.
@@ -0,0 +1,942 @@
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.CATEGORIES = exports.MemoryEngine = void 0;
7
+ exports.createMemoryEngine = createMemoryEngine;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const child_process_1 = require("child_process");
12
+ const nanoid_1 = require("nanoid");
13
+ const parser_1 = require("./parser");
14
+ const knowledge_core_1 = require("@crewx/knowledge-core");
15
+ function parseCrewxResponse(raw) {
16
+ const responseMatch = raw.match(/πŸ“„ Response:\s*─+\s*([\s\S]*?)(?=πŸ“ Working Directory|$)/);
17
+ if (responseMatch?.[1]) {
18
+ const captured = responseMatch[1].trim();
19
+ const capturedFirstLine = captured.split('\n')[0]?.trim() ?? '';
20
+ if (!capturedFirstLine.startsWith('{')) {
21
+ return captured;
22
+ }
23
+ return parseJsonlContent(captured);
24
+ }
25
+ return parseJsonlContent(raw);
26
+ }
27
+ function parseJsonlContent(raw) {
28
+ const lines = raw.split('\n').filter((l) => l.trim().startsWith('{'));
29
+ let assistantText = '';
30
+ let hasAssistantMessage = false;
31
+ for (const line of lines) {
32
+ try {
33
+ const obj = JSON.parse(line);
34
+ if (obj.type === 'message' && obj.role === 'assistant') {
35
+ hasAssistantMessage = true;
36
+ if (obj.content)
37
+ assistantText += obj.content;
38
+ }
39
+ }
40
+ catch (_e) {
41
+ }
42
+ }
43
+ if (hasAssistantMessage)
44
+ return assistantText.trim() || null;
45
+ const trimmed = raw.trim();
46
+ if (trimmed.length === 0)
47
+ return null;
48
+ const firstLine = trimmed.split('\n')[0]?.trim() ?? '';
49
+ if (firstLine.startsWith('{'))
50
+ return null;
51
+ return trimmed;
52
+ }
53
+ class MemoryEngine {
54
+ constructor(config = {}) {
55
+ this.dataDir =
56
+ config.dataDir ??
57
+ process.env['MEMORY_DATA_DIR'] ??
58
+ path_1.default.resolve(process.cwd(), 'memory');
59
+ this.summaryDays =
60
+ config.summaryDays ??
61
+ (parseInt(process.env['MEMORY_SUMMARY_DAYS'] ?? '', 10) || 7);
62
+ this.recentDays =
63
+ config.recentDays ??
64
+ (parseInt(process.env['MEMORY_RECENT_DAYS'] ?? '', 10) || 30);
65
+ this.shortTermHours = config.shortTermHours ?? 24;
66
+ }
67
+ getAgentDir(agentId) {
68
+ return path_1.default.join(this.dataDir, agentId);
69
+ }
70
+ getEntriesDir(agentId) {
71
+ return path_1.default.join(this.getAgentDir(agentId), 'entries');
72
+ }
73
+ getSummaryPath(agentId) {
74
+ return path_1.default.join(this.getAgentDir(agentId), 'summary.md');
75
+ }
76
+ getDirtySummaryPath(agentId) {
77
+ return path_1.default.join(this.getAgentDir(agentId), '.dirty-summary');
78
+ }
79
+ ensureDir(dir) {
80
+ if (!fs_1.default.existsSync(dir)) {
81
+ fs_1.default.mkdirSync(dir, { recursive: true });
82
+ }
83
+ }
84
+ generateId() {
85
+ return (0, nanoid_1.nanoid)(6);
86
+ }
87
+ getToday() {
88
+ return new Date().toISOString().slice(0, 10);
89
+ }
90
+ getLocalISO8601() {
91
+ const now = new Date();
92
+ const offset = -now.getTimezoneOffset();
93
+ const sign = offset >= 0 ? '+' : '-';
94
+ const offsetHours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
95
+ const offsetMins = String(Math.abs(offset) % 60).padStart(2, '0');
96
+ const y = now.getFullYear();
97
+ const m = String(now.getMonth() + 1).padStart(2, '0');
98
+ const d = String(now.getDate()).padStart(2, '0');
99
+ const h = String(now.getHours()).padStart(2, '0');
100
+ const min = String(now.getMinutes()).padStart(2, '0');
101
+ const s = String(now.getSeconds()).padStart(2, '0');
102
+ return `${y}-${m}-${d}T${h}:${min}:${s}${sign}${offsetHours}:${offsetMins}`;
103
+ }
104
+ getPrimaryTag(entry) {
105
+ return (entry.tags?.[0]) ?? 'general';
106
+ }
107
+ toEntryTimestamp(entry) {
108
+ if (entry['created_at'])
109
+ return String(entry['created_at']);
110
+ const d = entry['date'];
111
+ const dateStr = d && typeof d === 'object' && typeof d.toISOString === 'function' ? d.toISOString().slice(0, 10) : String(d ?? '');
112
+ if (dateStr.includes('T'))
113
+ return dateStr;
114
+ return dateStr + 'T09:00:00+09:00';
115
+ }
116
+ markSummaryDirty(agentId) {
117
+ this.ensureDir(this.getAgentDir(agentId));
118
+ fs_1.default.writeFileSync(this.getDirtySummaryPath(agentId), String(Date.now()), 'utf-8');
119
+ }
120
+ loadAllEntries(agentId) {
121
+ const entriesDir = this.getEntriesDir(agentId);
122
+ if (!fs_1.default.existsSync(entriesDir))
123
+ return [];
124
+ const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
125
+ const entries = [];
126
+ for (const file of files) {
127
+ const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
128
+ const { data, content } = (0, parser_1.parseFrontmatter)(raw);
129
+ const bodyText = content.replace(/^#[^\n]*\n*/m, '').trim();
130
+ const hasBody = Boolean(bodyText) && bodyText !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
131
+ const entry = data;
132
+ entries.push({ ...entry, file, hasBody });
133
+ }
134
+ entries.sort((a, b) => this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a)));
135
+ return entries;
136
+ }
137
+ findEntryById(agentId, memoryId) {
138
+ const entriesDir = this.getEntriesDir(agentId);
139
+ if (!fs_1.default.existsSync(entriesDir))
140
+ return null;
141
+ const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
142
+ for (const file of files) {
143
+ const filePath = path_1.default.join(entriesDir, file);
144
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
145
+ const { data, content } = (0, parser_1.parseFrontmatter)(raw);
146
+ if (data.id === memoryId) {
147
+ return {
148
+ file,
149
+ filePath,
150
+ data: data,
151
+ content,
152
+ };
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+ save(agentId, summary, category = 'general', options = {}) {
158
+ this.ensureDir(this.getEntriesDir(agentId));
159
+ const id = this.generateId();
160
+ const dateTime = this.getLocalISO8601();
161
+ const tags = Array.isArray(options.tags)
162
+ ? options.tags
163
+ : options.tags ? options.tags.split(',').map((t) => t.trim()) : [];
164
+ const frontmatter = (0, parser_1.stringifyFrontmatter)({
165
+ id,
166
+ type: 'memory',
167
+ date: dateTime,
168
+ category,
169
+ tags,
170
+ summary,
171
+ ...(options.important ? { important: true } : {}),
172
+ });
173
+ const filename = `${id}.md`;
174
+ const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
175
+ const body = options.body ?? '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
176
+ const content = `${frontmatter}\n\n# ${summary}\n\n${body}\n`;
177
+ fs_1.default.writeFileSync(filepath, content, 'utf-8');
178
+ console.log(`βœ… μ €μž₯ μ™„λ£Œ: data/${agentId}/entries/${filename}`);
179
+ console.log(` ID: ${id}`);
180
+ console.log(` Tags: ${tags.join(', ') || '(μ—†μŒ)'}`);
181
+ console.log(` Category: ${category}`);
182
+ try {
183
+ this.markSummaryDirty(agentId);
184
+ console.log(` πŸ“ summary.md κ°±μ‹  μ˜ˆμ•½λ¨`);
185
+ }
186
+ catch (_e) {
187
+ }
188
+ const saveCtx = {
189
+ commandId: 'memory.save',
190
+ params: { agentId, tags: tags.join(',') },
191
+ resultTags: tags.slice(0, 3),
192
+ resultCount: 1,
193
+ };
194
+ const saveActions = (0, knowledge_core_1.generateNextActions)(saveCtx);
195
+ const saveBlock = (0, knowledge_core_1.formatActions)(saveActions);
196
+ if (saveBlock)
197
+ console.log(saveBlock);
198
+ }
199
+ index(agentId, options = {}) {
200
+ const summaryPath = this.getSummaryPath(agentId);
201
+ const entries = this.loadAllEntries(agentId);
202
+ const topics = [...new Set(entries.map((e) => this.getPrimaryTag(e)))];
203
+ if (options.recent) {
204
+ const days = parseInt(String(options.recent), 10) || this.recentDays;
205
+ this.recent(agentId, days);
206
+ return;
207
+ }
208
+ const cutoff24h = new Date(Date.now() - this.shortTermHours * 60 * 60 * 1000).toISOString();
209
+ const shortTermEntries = entries.filter((e) => this.toEntryTimestamp(e) >= cutoff24h);
210
+ console.log(`# ${agentId} κΈ°μ–΅ μš”μ•½\n`);
211
+ const summaryLastUpdated = fs_1.default.existsSync(summaryPath)
212
+ ? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8')).data.last_updated ?? '-'
213
+ : '-';
214
+ console.log(`> ${entries.length}건 | ν† ν”½ ${topics.length}개 | κ°±μ‹ : ${summaryLastUpdated}`);
215
+ if (options.full) {
216
+ const byCat = {};
217
+ for (const entry of entries) {
218
+ const cat = entry.category ?? 'general';
219
+ byCat[cat] = (byCat[cat] ?? 0) + 1;
220
+ }
221
+ const catSummary = Object.entries(byCat)
222
+ .sort((a, b) => b[1] - a[1])
223
+ .map(([cat, count]) => `${cat}:${count}`)
224
+ .join(' ');
225
+ console.log(`> πŸ“Š ${catSummary}`);
226
+ }
227
+ console.log('');
228
+ if (shortTermEntries.length > 0) {
229
+ const sortedShortTerm = [...shortTermEntries].sort((a, b) => {
230
+ const aImp = a.important ? 1 : 0;
231
+ const bImp = b.important ? 1 : 0;
232
+ if (aImp !== bImp)
233
+ return bImp - aImp;
234
+ return this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a));
235
+ });
236
+ console.log(`## πŸ”₯ 졜근 μž‘μ—… (24h)\n`);
237
+ for (const e of sortedShortTerm) {
238
+ const star = e.important ? '⭐ ' : '';
239
+ console.log(`- ${star}[${e.id}] ${e.summary} (${this.getPrimaryTag(e)})`);
240
+ }
241
+ console.log('');
242
+ }
243
+ if (options.full) {
244
+ if (fs_1.default.existsSync(summaryPath)) {
245
+ const raw = fs_1.default.readFileSync(summaryPath, 'utf-8');
246
+ const { content: summaryBody } = (0, parser_1.parseFrontmatter)(raw);
247
+ if (entries.length > 0) {
248
+ const newestDate = entries[0]?.date;
249
+ const oldestDate = entries[entries.length - 1]?.date;
250
+ const newest = newestDate ? new Date(`${newestDate}T00:00:00Z`) : null;
251
+ const oldest = oldestDate ? new Date(`${oldestDate}T00:00:00Z`) : null;
252
+ if (newest &&
253
+ oldest &&
254
+ !Number.isNaN(newest.getTime()) &&
255
+ !Number.isNaN(oldest.getTime())) {
256
+ const msPerDay = 24 * 60 * 60 * 1000;
257
+ const diffDays = Math.max(1, Math.floor((newest.getTime() - oldest.getTime()) / msPerDay) + 1);
258
+ const toMD = (d) => `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
259
+ console.log('---');
260
+ console.log(`πŸ“‹ ν† ν”½ μš”μ•½ (${toMD(oldest)} ~ ${toMD(newest)}, ${diffDays}일간)\n`);
261
+ }
262
+ }
263
+ console.log(summaryBody.trim());
264
+ }
265
+ else {
266
+ for (const tag of topics.sort()) {
267
+ const count = entries.filter((e) => this.getPrimaryTag(e) === tag).length;
268
+ console.log(`- **${tag}** (${count}건)`);
269
+ }
270
+ }
271
+ }
272
+ const commandId = options.full ? 'memory.index-full' : 'memory.index';
273
+ const ctx = {
274
+ commandId,
275
+ params: { agentId },
276
+ resultTags: topics.slice(0, 3),
277
+ resultIds: entries.slice(0, 3).map(e => e.id),
278
+ resultCount: entries.length,
279
+ };
280
+ const actions = (0, knowledge_core_1.generateNextActions)(ctx);
281
+ const block = (0, knowledge_core_1.formatActions)(actions);
282
+ if (block)
283
+ console.log(block);
284
+ }
285
+ generateIndexContent(agentId) {
286
+ const entries = this.loadAllEntries(agentId);
287
+ const today = this.getToday();
288
+ const cutoff = new Date(Date.now() - this.recentDays * 24 * 60 * 60 * 1000)
289
+ .toISOString()
290
+ .slice(0, 10);
291
+ const byCategory = {};
292
+ const recent = [];
293
+ for (const entry of entries) {
294
+ if (entry.date >= cutoff)
295
+ recent.push(entry);
296
+ const category = entry.category ?? 'general';
297
+ if (!byCategory[category])
298
+ byCategory[category] = [];
299
+ byCategory[category].push(entry);
300
+ }
301
+ let md = `# ${agentId} λ©”λͺ¨λ¦¬\n\n`;
302
+ md += `> Last Updated: ${today}\n`;
303
+ md += `> Total: ${entries.length} memories\n\n`;
304
+ md += `## πŸ”₯ 졜근 ${this.recentDays}일\n\n`;
305
+ if (recent.length === 0) {
306
+ md += `(μ—†μŒ)\n\n`;
307
+ }
308
+ else {
309
+ md += `| ID | λ‚ μ§œ | μΉ΄ν…Œκ³ λ¦¬ | μš”μ•½ | 상세 |\n`;
310
+ md += `|----|------|----------|------|------|\n`;
311
+ for (const entry of recent) {
312
+ const detailIcon = entry.hasBody ? 'πŸ“„' : '-';
313
+ md += `| ${entry.id} | ${entry.date} | ${entry.category ?? '-'} | [${entry.summary}](entries/${entry.file}) | ${detailIcon} |\n`;
314
+ }
315
+ md += `\n`;
316
+ }
317
+ md += `## 🏷️ μΉ΄ν…Œκ³ λ¦¬λ³„\n\n`;
318
+ for (const [category, items] of Object.entries(byCategory).sort()) {
319
+ md += `- **${category}**: ${items.length}건\n`;
320
+ }
321
+ const olderCount = entries.length - recent.length;
322
+ const oldestDate = entries[entries.length - 1]?.date ?? '-';
323
+ const newestDate = entries[0]?.date ?? '-';
324
+ md += `\n## πŸ“Š 톡계\n\n`;
325
+ md += `- 전체: ${entries.length}건 (${oldestDate} ~ ${newestDate})\n`;
326
+ md += `- 졜근 ${this.recentDays}일: ${recent.length}건\n`;
327
+ if (olderCount > 0)
328
+ md += `- ${this.recentDays}일 이전: ${olderCount}건\n`;
329
+ return md;
330
+ }
331
+ topic(agentId, topicName) {
332
+ const entries = this.loadAllEntries(agentId);
333
+ const filtered = entries.filter((e) => this.getPrimaryTag(e) === topicName);
334
+ if (filtered.length === 0) {
335
+ console.log(`ν† ν”½ '${topicName}'에 ν•΄λ‹Ήν•˜λŠ” 기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
336
+ return;
337
+ }
338
+ console.log(`## πŸ“‚ ν† ν”½: ${topicName} (${filtered.length}건)\n`);
339
+ for (const entry of filtered) {
340
+ console.log(`### [${entry.date}] ${entry.summary}`);
341
+ console.log(`- 파일: data/${agentId}/entries/${entry.file}`);
342
+ console.log(`- μΉ΄ν…Œκ³ λ¦¬: ${entry.category ?? '-'}`);
343
+ console.log(`- νƒœκ·Έ: ${(entry.tags ?? []).join(', ') || '-'}`);
344
+ console.log('');
345
+ }
346
+ }
347
+ recent(agentId, days = 30) {
348
+ const entries = this.loadAllEntries(agentId);
349
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
350
+ .toISOString()
351
+ .slice(0, 10);
352
+ const filtered = entries.filter((e) => e.date >= cutoff);
353
+ if (filtered.length === 0) {
354
+ console.log(`졜근 ${days}일 λ‚΄ 기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
355
+ return;
356
+ }
357
+ console.log(`## πŸ”₯ 졜근 ${days}일 (${filtered.length}건)\n`);
358
+ for (const entry of filtered) {
359
+ const detailIcon = entry.hasBody ? ' πŸ“„' : '';
360
+ const timestamp = String(entry.date).includes('T')
361
+ ? String(entry.date).slice(0, 16).replace('T', ' ')
362
+ : entry.date;
363
+ console.log(`[${entry.id}] [${timestamp}] [${entry.category ?? '-'}] ${entry.summary}${detailIcon}`);
364
+ }
365
+ }
366
+ find(agentId, keyword) {
367
+ const entriesDir = this.getEntriesDir(agentId);
368
+ if (!fs_1.default.existsSync(entriesDir)) {
369
+ console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
370
+ return;
371
+ }
372
+ const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
373
+ const entries = [];
374
+ for (const file of files) {
375
+ const content = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
376
+ const { data, content: body } = (0, parser_1.parseFrontmatter)(content);
377
+ const entry = data;
378
+ entries.push({ ...entry, file, body });
379
+ }
380
+ if (entries.length === 0) {
381
+ console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
382
+ return;
383
+ }
384
+ const kcEntries = entries;
385
+ const results = (0, knowledge_core_1.searchEntries)(kcEntries, keyword, { summary: 3, tags: 2, body: 1 });
386
+ if (results.length === 0) {
387
+ console.log(`'${keyword}' κ΄€λ ¨ 기얡을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
388
+ return;
389
+ }
390
+ console.log(`## πŸ” 검색 κ²°κ³Ό: "${keyword}" (${results.length}건, BM25 점수순)\n`);
391
+ for (const entry of results) {
392
+ const score = entry._score != null ? ` [${entry._score}점]` : '';
393
+ console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${score} ${entry.summary}`);
394
+ }
395
+ const resultTags = [...new Set(results.flatMap(e => e.tags ?? []))].slice(0, 3);
396
+ const resultIds = results.slice(0, 3).map(e => e.id);
397
+ const findCtx = {
398
+ commandId: 'memory.find',
399
+ params: { agentId, query: keyword },
400
+ resultTags,
401
+ resultIds,
402
+ resultCount: results.length,
403
+ };
404
+ const findActions = (0, knowledge_core_1.generateNextActions)(findCtx);
405
+ const findBlock = (0, knowledge_core_1.formatActions)(findActions);
406
+ if (findBlock)
407
+ console.log(findBlock);
408
+ }
409
+ list(agentId, options = {}) {
410
+ const entries = this.loadAllEntries(agentId);
411
+ let filtered = entries;
412
+ if (options.category) {
413
+ filtered = filtered.filter(e => e.category === options.category);
414
+ }
415
+ if (options.tag) {
416
+ const requiredTags = options.tag.split(',').map(t => t.trim());
417
+ filtered = filtered.filter(e => requiredTags.every(t => (e.tags ?? []).includes(t)));
418
+ }
419
+ if (filtered.length === 0) {
420
+ const filters = [];
421
+ if (options.category)
422
+ filters.push(`category=${options.category}`);
423
+ if (options.tag)
424
+ filters.push(`tag=${options.tag}`);
425
+ console.log(`쑰건에 λ§žλŠ” 기얡이 μ—†μŠ΅λ‹ˆλ‹€. (${filters.join(', ') || '전체'})`);
426
+ return;
427
+ }
428
+ console.log(`## πŸ“‹ λͺ©λ‘: ${agentId} (${filtered.length}건)\n`);
429
+ for (const entry of filtered) {
430
+ console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}] ${entry.summary}`);
431
+ }
432
+ const resultTags = [...new Set(filtered.flatMap(e => e.tags ?? []))].slice(0, 3);
433
+ const resultIds = filtered.slice(0, 3).map(e => e.id);
434
+ const listCtx = {
435
+ commandId: 'memory.list',
436
+ params: { agentId, ...(options.category ? { category: options.category } : {}) },
437
+ resultTags,
438
+ resultIds,
439
+ resultCount: filtered.length,
440
+ };
441
+ const listActions = (0, knowledge_core_1.generateNextActions)(listCtx);
442
+ const listBlock = (0, knowledge_core_1.formatActions)(listActions);
443
+ if (listBlock)
444
+ console.log(listBlock);
445
+ }
446
+ stats(agentId) {
447
+ const entries = this.loadAllEntries(agentId);
448
+ if (entries.length === 0) {
449
+ console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
450
+ return;
451
+ }
452
+ const byCategory = {};
453
+ for (const entry of entries) {
454
+ const cat = entry.category ?? 'general';
455
+ byCategory[cat] = (byCategory[cat] ?? 0) + 1;
456
+ }
457
+ const byTag = {};
458
+ for (const entry of entries) {
459
+ for (const tag of entry.tags ?? []) {
460
+ byTag[tag] = (byTag[tag] ?? 0) + 1;
461
+ }
462
+ }
463
+ console.log(`# ${agentId} 톡계 (${entries.length}건)\n`);
464
+ console.log(`## μΉ΄ν…Œκ³ λ¦¬λ³„`);
465
+ for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
466
+ console.log(` ${cat}: ${count}건`);
467
+ }
468
+ console.log(`\n## νƒœκ·Έ Top 20`);
469
+ const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]).slice(0, 20);
470
+ for (const [tag, count] of sortedTags) {
471
+ console.log(` ${tag}: ${count}건`);
472
+ }
473
+ const topCategories = Object.entries(byCategory).sort((a, b) => b[1] - a[1]).map(t => t[0]);
474
+ const statsCtx = {
475
+ commandId: 'memory.stats',
476
+ params: { agentId },
477
+ resultTags: topCategories.slice(0, 3),
478
+ resultCount: entries.length,
479
+ };
480
+ const statsActions = (0, knowledge_core_1.generateNextActions)(statsCtx);
481
+ const statsBlock = (0, knowledge_core_1.formatActions)(statsActions);
482
+ if (statsBlock)
483
+ console.log(statsBlock);
484
+ }
485
+ tags(agentId) {
486
+ const entries = this.loadAllEntries(agentId);
487
+ if (entries.length === 0) {
488
+ console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
489
+ return;
490
+ }
491
+ const byTag = {};
492
+ for (const entry of entries) {
493
+ for (const tag of entry.tags ?? []) {
494
+ byTag[tag] = (byTag[tag] ?? 0) + 1;
495
+ }
496
+ }
497
+ const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]);
498
+ if (sortedTags.length === 0) {
499
+ console.log('νƒœκ·Έκ°€ μ—†μŠ΅λ‹ˆλ‹€.');
500
+ return;
501
+ }
502
+ console.log(`## 🏷️ 전체 νƒœκ·Έ (${sortedTags.length}개)\n`);
503
+ for (const [tag, count] of sortedTags) {
504
+ console.log(` ${tag}: ${count}건`);
505
+ }
506
+ }
507
+ get(agentId, memoryId) {
508
+ const entriesDir = this.getEntriesDir(agentId);
509
+ if (!fs_1.default.existsSync(entriesDir)) {
510
+ console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
511
+ return;
512
+ }
513
+ const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
514
+ for (const file of files) {
515
+ const filePath = path_1.default.join(entriesDir, file);
516
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
517
+ const { data, content } = (0, parser_1.parseFrontmatter)(raw);
518
+ if (data.id === memoryId) {
519
+ const d = data;
520
+ console.log(`## πŸ“„ ${d.summary}\n`);
521
+ console.log(`- ID: ${d.id}`);
522
+ console.log(`- λ‚ μ§œ: ${d.date}`);
523
+ console.log(`- μΉ΄ν…Œκ³ λ¦¬: ${d.category ?? '-'}`);
524
+ console.log(`- νƒœκ·Έ: ${(d.tags ?? []).join(', ') || '-'}`);
525
+ console.log(`- 파일: data/${agentId}/entries/${file}`);
526
+ console.log(`\n---\n`);
527
+ console.log(content.trim());
528
+ const graphPath = path_1.default.join(this.getAgentDir(agentId), 'graph.json');
529
+ if (fs_1.default.existsSync(graphPath)) {
530
+ try {
531
+ const graph = JSON.parse(fs_1.default.readFileSync(graphPath, 'utf-8'));
532
+ const relatedEdges = graph.edges
533
+ .filter((e) => e.from === memoryId || e.to === memoryId)
534
+ .sort((a, b) => b.weight - a.weight)
535
+ .slice(0, 5);
536
+ if (relatedEdges.length > 0) {
537
+ const nodeMap = {};
538
+ graph.nodes.forEach((n) => {
539
+ nodeMap[n.id] = n;
540
+ });
541
+ console.log(`\n---\n`);
542
+ console.log(`## πŸ”— μ—°κ΄€ κΈ°μ–΅ (${relatedEdges.length}개)\n`);
543
+ for (const edge of relatedEdges) {
544
+ const otherId = edge.from === memoryId ? edge.to : edge.from;
545
+ const other = nodeMap[otherId];
546
+ if (other) {
547
+ console.log(`[${edge.weight}] [${otherId}] ${other.summary.slice(0, 50)}...`);
548
+ }
549
+ }
550
+ }
551
+ }
552
+ catch (_e) {
553
+ }
554
+ }
555
+ const getCtx = {
556
+ commandId: 'memory.get',
557
+ params: { agentId, id: memoryId },
558
+ resultTags: (d.tags ?? []).slice(0, 3),
559
+ resultIds: [memoryId],
560
+ resultCount: 1,
561
+ };
562
+ const getActions = (0, knowledge_core_1.generateNextActions)(getCtx);
563
+ const getBlock = (0, knowledge_core_1.formatActions)(getActions);
564
+ if (getBlock)
565
+ console.log(getBlock);
566
+ return;
567
+ }
568
+ }
569
+ console.log(`ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
570
+ }
571
+ update(agentId, memoryId, options = {}) {
572
+ const entry = this.findEntryById(agentId, memoryId);
573
+ if (!entry) {
574
+ console.log(`❌ ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
575
+ process.exit(1);
576
+ }
577
+ const { filePath, data, content } = entry;
578
+ const existingBody = content.replace(/^#[^\n]*\n*/m, '').trim();
579
+ const tags = Array.isArray(options.tags)
580
+ ? options.tags
581
+ : options.tags ? options.tags.split(',').map((t) => t.trim()) : (data.tags ?? []);
582
+ const newData = {
583
+ ...data,
584
+ summary: options.summary ?? data.summary,
585
+ category: options.category ?? data.category,
586
+ tags,
587
+ updated: this.getToday(),
588
+ };
589
+ if (options.important) {
590
+ newData['important'] = true;
591
+ }
592
+ else if (options['no-important']) {
593
+ delete newData['important'];
594
+ }
595
+ const newBody = options.body ?? existingBody ?? '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
596
+ const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
597
+ const newContent = `${newFrontmatter}\n\n# ${String(newData['summary'])}\n\n${newBody}\n`;
598
+ fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
599
+ console.log(`βœ… μˆ˜μ • μ™„λ£Œ: ${entry.file}`);
600
+ console.log(` ID: ${memoryId}`);
601
+ if (options.summary)
602
+ console.log(` Summary: ${String(newData['summary'])}`);
603
+ if (options.category)
604
+ console.log(` Category: ${String(newData['category'])}`);
605
+ if (options.tags)
606
+ console.log(` Tags: ${newData['tags'].join(', ')}`);
607
+ if (options.body)
608
+ console.log(` Body: (μ—…λ°μ΄νŠΈλ¨)`);
609
+ }
610
+ delete(agentId, memoryId, options = {}) {
611
+ const entry = this.findEntryById(agentId, memoryId);
612
+ if (!entry) {
613
+ console.log(`❌ ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
614
+ process.exit(1);
615
+ }
616
+ if (!options.force) {
617
+ console.log(`⚠️ μ‚­μ œ λŒ€μƒ: [${entry.data.id}] ${entry.data.summary}`);
618
+ console.log(` 파일: ${entry.file}`);
619
+ console.log(`\n--force μ˜΅μ…˜μ„ μΆ”κ°€ν•˜λ©΄ μ‚­μ œλ©λ‹ˆλ‹€.`);
620
+ console.log(`예: memory delete ${agentId} ${memoryId} --force`);
621
+ return;
622
+ }
623
+ fs_1.default.unlinkSync(entry.filePath);
624
+ console.log(`πŸ—‘οΈ μ‚­μ œ μ™„λ£Œ: ${entry.file}`);
625
+ console.log(` ID: ${memoryId}`);
626
+ console.log(` Summary: ${entry.data.summary}`);
627
+ try {
628
+ this.markSummaryDirty(agentId);
629
+ console.log(` πŸ“ summary.md κ°±μ‹  μ˜ˆμ•½λ¨`);
630
+ }
631
+ catch (_e) {
632
+ }
633
+ }
634
+ merge(agentId, memoryId1, memoryId2, options = {}) {
635
+ const entry1 = this.findEntryById(agentId, memoryId1);
636
+ const entry2 = this.findEntryById(agentId, memoryId2);
637
+ if (!entry1) {
638
+ console.log(`❌ ID '${memoryId1}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
639
+ process.exit(1);
640
+ }
641
+ if (!entry2) {
642
+ console.log(`❌ ID '${memoryId2}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
643
+ process.exit(1);
644
+ }
645
+ const body1 = entry1.content.replace(/^#[^\n]*\n*/m, '').trim();
646
+ const body2 = entry2.content.replace(/^#[^\n]*\n*/m, '').trim();
647
+ const mergedSummary = options.summary ?? `${entry1.data.summary} + ${entry2.data.summary}`;
648
+ const mergedBody = `## λ³‘ν•©λœ κΈ°μ–΅ 1 (${entry1.data.id}, ${entry1.data.date})
649
+ ${entry1.data.summary}
650
+
651
+ ${body1 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body1 : '(상세 μ—†μŒ)'}
652
+
653
+ ---
654
+
655
+ ## λ³‘ν•©λœ κΈ°μ–΅ 2 (${entry2.data.id}, ${entry2.data.date})
656
+ ${entry2.data.summary}
657
+
658
+ ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
659
+ const mergedTags = [
660
+ ...new Set([...(entry1.data.tags ?? []), ...(entry2.data.tags ?? [])]),
661
+ ];
662
+ const newerDate = entry1.data.date > entry2.data.date ? entry1.data.date : entry2.data.date;
663
+ const newId = this.generateId();
664
+ const newData = {
665
+ id: newId,
666
+ type: 'memory',
667
+ date: newerDate,
668
+ category: entry1.data.category,
669
+ tags: mergedTags,
670
+ summary: mergedSummary,
671
+ merged_from: [memoryId1, memoryId2],
672
+ };
673
+ const filename = `${newId}.md`;
674
+ const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
675
+ const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
676
+ const newContent = `${newFrontmatter}\n\n# ${mergedSummary}\n\n${mergedBody}\n`;
677
+ fs_1.default.writeFileSync(filepath, newContent, 'utf-8');
678
+ fs_1.default.unlinkSync(entry1.filePath);
679
+ fs_1.default.unlinkSync(entry2.filePath);
680
+ console.log(`πŸ”€ 병합 μ™„λ£Œ!`);
681
+ console.log(` μƒˆ ID: ${newId}`);
682
+ console.log(` μƒˆ 파일: ${filename}`);
683
+ console.log(` λ³‘ν•©λœ κΈ°μ–΅: [${memoryId1}] + [${memoryId2}]`);
684
+ console.log(` Summary: ${mergedSummary}`);
685
+ }
686
+ buildFallbackSummary(entries) {
687
+ const byTag = {};
688
+ for (const entry of entries) {
689
+ const tag = this.getPrimaryTag(entry);
690
+ if (!byTag[tag])
691
+ byTag[tag] = [];
692
+ byTag[tag].push(entry);
693
+ }
694
+ const toMD = (dateStr) => {
695
+ if (!dateStr)
696
+ return '-';
697
+ const normalized = String(dateStr).includes('T') ? dateStr : `${dateStr}T00:00:00Z`;
698
+ const d = new Date(normalized);
699
+ if (Number.isNaN(d.getTime()))
700
+ return '-';
701
+ return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
702
+ };
703
+ let body = '';
704
+ for (const [topic, items] of Object.entries(byTag).sort()) {
705
+ const dates = items.map((i) => i.date).filter(Boolean).sort();
706
+ const oldest = dates[0];
707
+ const newest = dates[dates.length - 1];
708
+ const rangeLabel = oldest && newest ? `, ${toMD(oldest)}~${toMD(newest)}` : '';
709
+ body += `## ${topic} (${items.length}건${rangeLabel})\n`;
710
+ for (const e of items)
711
+ body += `- [${e.date}] ${e.summary}\n`;
712
+ for (const e of items)
713
+ body += `β†’ [entries/${e.file}](entries/${e.file})\n`;
714
+ body += `\n`;
715
+ }
716
+ return body;
717
+ }
718
+ callSummarizer(prompt) {
719
+ const tmpFile = path_1.default.join(os_1.default.tmpdir(), `memory-summarizer-${Date.now()}.txt`);
720
+ try {
721
+ fs_1.default.writeFileSync(tmpFile, prompt, 'utf-8');
722
+ const result = (0, child_process_1.execSync)(`./crewx q "@memory_summarizer $(cat '${tmpFile}')"`, { encoding: 'utf-8', timeout: 60000, stdio: ['pipe', 'pipe', 'pipe'] });
723
+ return parseCrewxResponse(result);
724
+ }
725
+ catch (_e) {
726
+ return null;
727
+ }
728
+ finally {
729
+ try {
730
+ fs_1.default.unlinkSync(tmpFile);
731
+ }
732
+ catch (_e) {
733
+ }
734
+ }
735
+ }
736
+ generateSummary(agentId, options = {}) {
737
+ const allEntries = this.loadAllEntries(agentId);
738
+ if (allEntries.length === 0)
739
+ return { status: 'empty' };
740
+ const cutoff = new Date(Date.now() - this.summaryDays * 24 * 60 * 60 * 1000)
741
+ .toISOString()
742
+ .slice(0, 10);
743
+ const entries = options.force
744
+ ? allEntries
745
+ : allEntries.filter((e) => e.date >= cutoff);
746
+ if (entries.length === 0)
747
+ return { status: 'empty', entryCount: allEntries.length };
748
+ const summaryPath = this.getSummaryPath(agentId);
749
+ const existing = fs_1.default.existsSync(summaryPath)
750
+ ? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8'))
751
+ : null;
752
+ const lastEntryId = existing?.data?.last_entry_id ?? null;
753
+ const existingBody = existing?.content.trim() ?? null;
754
+ const getNewSince = (arr, lastId) => {
755
+ if (!lastId)
756
+ return arr;
757
+ const idx = arr.findIndex((e) => e.id === lastId);
758
+ if (idx === -1)
759
+ return arr;
760
+ return arr.slice(0, idx);
761
+ };
762
+ const newEntries = getNewSince(entries, lastEntryId);
763
+ if (newEntries.length === 0 && !options.force) {
764
+ return { status: 'up-to-date', entryCount: allEntries.length };
765
+ }
766
+ const entriesToProcess = options.force ? entries : newEntries;
767
+ const enriched = [...entriesToProcess].reverse().map((e) => {
768
+ if (e.file) {
769
+ try {
770
+ const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), e.file), 'utf-8');
771
+ const { content } = (0, parser_1.parseFrontmatter)(raw);
772
+ const body = content.replace(/^#[^\n]*\n*/m, '').trim();
773
+ return { ...e, body };
774
+ }
775
+ catch (_e) {
776
+ }
777
+ }
778
+ return e;
779
+ });
780
+ const allTopics = [...new Set(entries.map((e) => this.getPrimaryTag(e)))].sort();
781
+ const buildPrompt = (existBody, newItems, topics) => {
782
+ let p = `You are a memory summarizer for an AI agent.\n\n`;
783
+ if (existBody) {
784
+ p += `## Existing summary:\n${existBody}\n\n## New entries to incorporate:\n`;
785
+ }
786
+ else {
787
+ p += `## All entries:\n`;
788
+ }
789
+ for (const entry of newItems) {
790
+ const importantTag = entry.important ? ' [important]' : '';
791
+ p += `- [${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${importantTag} ${entry.summary}`;
792
+ if (entry.body && entry.body !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)') {
793
+ p += ` β€” ${entry.body.slice(0, 200)}`;
794
+ }
795
+ p += ` (file: entries/${entry.file})\n`;
796
+ }
797
+ p += `\n## Instructions:\n`;
798
+ p += `- Group by topic, each topic as ## heading with entry count and date range, e.g. "## mcp-http (4건, 01/28~02/14)"\n`;
799
+ p += `- Write concise Korean summary per topic (2-5 sentences)\n`;
800
+ p += `- After each topic summary, list relevant entry file links as "β†’ [entries/filename](entries/filename)"\n`;
801
+ p += `- Highlight key decisions, insights, and action items\n`;
802
+ p += `- If there's an existing summary, merge new information into it\n`;
803
+ p += `- Topics to cover: ${topics.join(', ')}\n`;
804
+ p += `- Output ONLY the markdown body (topic sections), no frontmatter or top-level heading\n`;
805
+ return p;
806
+ };
807
+ const prompt = buildPrompt(options.force ? null : existingBody, enriched, allTopics);
808
+ const summaryText = this.callSummarizer(prompt);
809
+ const newestEntryId = entries[0]?.id;
810
+ if (!summaryText) {
811
+ const allEnriched = entries.map((e) => {
812
+ if (e.file) {
813
+ try {
814
+ const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), e.file), 'utf-8');
815
+ const { content } = (0, parser_1.parseFrontmatter)(raw);
816
+ const body = content.replace(/^#[^\n]*\n*/m, '').trim();
817
+ return { ...e, body };
818
+ }
819
+ catch (_e) {
820
+ }
821
+ }
822
+ return e;
823
+ });
824
+ const fallbackBody = this.buildFallbackSummary(allEnriched);
825
+ const fm = (0, parser_1.stringifyFrontmatter)({
826
+ last_entry_id: newestEntryId,
827
+ last_updated: this.getToday(),
828
+ entry_count: allEntries.length,
829
+ summary_days: this.summaryDays,
830
+ summarizer: 'fallback',
831
+ });
832
+ fs_1.default.writeFileSync(summaryPath, `${fm}\n\n${fallbackBody}`, 'utf-8');
833
+ return { status: 'fallback', entryCount: allEntries.length, newEntries: entriesToProcess.length };
834
+ }
835
+ const fm = (0, parser_1.stringifyFrontmatter)({
836
+ last_entry_id: newestEntryId,
837
+ last_updated: this.getToday(),
838
+ entry_count: allEntries.length,
839
+ summary_days: this.summaryDays,
840
+ summarizer: 'memory_summarizer',
841
+ });
842
+ fs_1.default.writeFileSync(summaryPath, `${fm}\n\n${summaryText}\n`, 'utf-8');
843
+ return { status: 'updated', entryCount: allEntries.length, newEntries: entriesToProcess.length };
844
+ }
845
+ summarize(agentId, options = {}) {
846
+ const entries = this.loadAllEntries(agentId);
847
+ if (entries.length === 0) {
848
+ console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
849
+ return;
850
+ }
851
+ console.log(`πŸ“ μš”μ•½ 생성 μ‹œμž‘ (${entries.length}개 μ—”νŠΈλ¦¬)\n`);
852
+ const result = this.generateSummary(agentId, { force: options.force });
853
+ if (result.status === 'up-to-date') {
854
+ console.log('βœ… summary.md μ΅œμ‹  μƒνƒœμž…λ‹ˆλ‹€.');
855
+ }
856
+ else if (result.status === 'fallback') {
857
+ console.log(`πŸ“‹ 폴백 μš”μ•½ 생성 μ™„λ£Œ (AI 호좜 μ‹€νŒ¨)`);
858
+ }
859
+ else if (result.status === 'updated') {
860
+ console.log(`πŸ€– μš”μ•½ μ™„λ£Œ (${result.newEntries}건 반영)`);
861
+ }
862
+ console.log(` 파일: data/${agentId}/summary.md`);
863
+ console.log(` μ—”νŠΈλ¦¬: ${result.entryCount}개`);
864
+ }
865
+ summarizeDirty() {
866
+ if (!fs_1.default.existsSync(this.dataDir)) {
867
+ console.log(`πŸ’‘ λ””λ°”μš΄μŠ€ cron 등둝:`);
868
+ console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
869
+ return;
870
+ }
871
+ const agentDirs = fs_1.default
872
+ .readdirSync(this.dataDir, { withFileTypes: true })
873
+ .filter((entry) => entry.isDirectory())
874
+ .map((entry) => entry.name);
875
+ for (const agentId of agentDirs) {
876
+ const dirtyPath = this.getDirtySummaryPath(agentId);
877
+ if (!fs_1.default.existsSync(dirtyPath))
878
+ continue;
879
+ try {
880
+ const result = this.generateSummary(agentId);
881
+ if (result.status === 'updated') {
882
+ console.log(`πŸ€– ${agentId} summary.md κ°±μ‹  (${result.newEntries}건 반영)`);
883
+ }
884
+ else if (result.status === 'fallback') {
885
+ console.log(`πŸ“‹ ${agentId} 폴백 μš”μ•½ 생성 μ™„λ£Œ (AI 호좜 μ‹€νŒ¨)`);
886
+ }
887
+ else if (result.status === 'up-to-date') {
888
+ console.log(`βœ… ${agentId} summary.md μ΅œμ‹  μƒνƒœμž…λ‹ˆλ‹€.`);
889
+ }
890
+ else if (result.status === 'empty') {
891
+ console.log(`ℹ️ ${agentId} μš”μ•½ν•  기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
892
+ }
893
+ fs_1.default.unlinkSync(dirtyPath);
894
+ }
895
+ catch (e) {
896
+ console.error(`❌ ${agentId} summary.md κ°±μ‹  μ‹€νŒ¨: ${e.message}`);
897
+ }
898
+ }
899
+ console.log(`πŸ’‘ λ””λ°”μš΄μŠ€ cron 등둝:`);
900
+ console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
901
+ }
902
+ search(agentId, query) {
903
+ const entries = this.loadAllEntries(agentId);
904
+ if (entries.length === 0) {
905
+ console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
906
+ return;
907
+ }
908
+ const memoryList = entries
909
+ .map((e) => `[${e.id}] [${e.date}] [${this.getPrimaryTag(e)}] ${e.summary}`)
910
+ .join('\n');
911
+ const task = `κΈ°μ–΅ λͺ©λ‘:\n${memoryList}\n\n질문: "${query}"`;
912
+ try {
913
+ console.log(`πŸ” "${query}" 검색 쀑... (@memory_searcher)\n`);
914
+ const result = (0, child_process_1.execSync)(`./crewx q "@memory_searcher ${task.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf-8', timeout: 300000, stdio: ['pipe', 'pipe', 'pipe'] });
915
+ const parsed = parseCrewxResponse(result);
916
+ if (parsed) {
917
+ console.log(`## 🧠 μ‹œλ§¨ν‹± 검색 κ²°κ³Ό\n`);
918
+ console.log(parsed);
919
+ }
920
+ else {
921
+ console.log(result.trim());
922
+ }
923
+ }
924
+ catch (error) {
925
+ const err = error;
926
+ if (err.code === 'ETIMEDOUT' || err.killed) {
927
+ console.log('검색 μ‹€νŒ¨: μ‹œκ°„ 초과 (μ΅œλŒ€ 5λΆ„)');
928
+ }
929
+ else {
930
+ console.log('검색 μ‹€νŒ¨:', err.message);
931
+ }
932
+ console.log('\nπŸ’‘ Tip: find λͺ…λ ΉμœΌλ‘œ ν‚€μ›Œλ“œ 검색을 μ‹œλ„ν•΄λ³΄μ„Έμš”.');
933
+ }
934
+ }
935
+ }
936
+ exports.MemoryEngine = MemoryEngine;
937
+ function createMemoryEngine(config) {
938
+ return new MemoryEngine(config);
939
+ }
940
+ var types_1 = require("./types");
941
+ Object.defineProperty(exports, "CATEGORIES", { enumerable: true, get: function () { return types_1.CATEGORIES; } });
942
+ //# sourceMappingURL=engine.js.map