@aitytech/agentkits-memory 1.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 (116) hide show
  1. package/README.md +250 -0
  2. package/dist/cache-manager.d.ts +134 -0
  3. package/dist/cache-manager.d.ts.map +1 -0
  4. package/dist/cache-manager.js +407 -0
  5. package/dist/cache-manager.js.map +1 -0
  6. package/dist/cli/save.d.ts +20 -0
  7. package/dist/cli/save.d.ts.map +1 -0
  8. package/dist/cli/save.js +94 -0
  9. package/dist/cli/save.js.map +1 -0
  10. package/dist/cli/setup.d.ts +18 -0
  11. package/dist/cli/setup.d.ts.map +1 -0
  12. package/dist/cli/setup.js +163 -0
  13. package/dist/cli/setup.js.map +1 -0
  14. package/dist/cli/viewer.d.ts +21 -0
  15. package/dist/cli/viewer.d.ts.map +1 -0
  16. package/dist/cli/viewer.js +182 -0
  17. package/dist/cli/viewer.js.map +1 -0
  18. package/dist/hnsw-index.d.ts +111 -0
  19. package/dist/hnsw-index.d.ts.map +1 -0
  20. package/dist/hnsw-index.js +781 -0
  21. package/dist/hnsw-index.js.map +1 -0
  22. package/dist/hooks/cli.d.ts +20 -0
  23. package/dist/hooks/cli.d.ts.map +1 -0
  24. package/dist/hooks/cli.js +102 -0
  25. package/dist/hooks/cli.js.map +1 -0
  26. package/dist/hooks/context.d.ts +31 -0
  27. package/dist/hooks/context.d.ts.map +1 -0
  28. package/dist/hooks/context.js +64 -0
  29. package/dist/hooks/context.js.map +1 -0
  30. package/dist/hooks/index.d.ts +16 -0
  31. package/dist/hooks/index.d.ts.map +1 -0
  32. package/dist/hooks/index.js +20 -0
  33. package/dist/hooks/index.js.map +1 -0
  34. package/dist/hooks/observation.d.ts +30 -0
  35. package/dist/hooks/observation.d.ts.map +1 -0
  36. package/dist/hooks/observation.js +79 -0
  37. package/dist/hooks/observation.js.map +1 -0
  38. package/dist/hooks/service.d.ts +102 -0
  39. package/dist/hooks/service.d.ts.map +1 -0
  40. package/dist/hooks/service.js +454 -0
  41. package/dist/hooks/service.js.map +1 -0
  42. package/dist/hooks/session-init.d.ts +30 -0
  43. package/dist/hooks/session-init.d.ts.map +1 -0
  44. package/dist/hooks/session-init.js +54 -0
  45. package/dist/hooks/session-init.js.map +1 -0
  46. package/dist/hooks/summarize.d.ts +30 -0
  47. package/dist/hooks/summarize.d.ts.map +1 -0
  48. package/dist/hooks/summarize.js +74 -0
  49. package/dist/hooks/summarize.js.map +1 -0
  50. package/dist/hooks/types.d.ts +193 -0
  51. package/dist/hooks/types.d.ts.map +1 -0
  52. package/dist/hooks/types.js +137 -0
  53. package/dist/hooks/types.js.map +1 -0
  54. package/dist/index.d.ts +173 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +564 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/mcp/index.d.ts +9 -0
  59. package/dist/mcp/index.d.ts.map +1 -0
  60. package/dist/mcp/index.js +9 -0
  61. package/dist/mcp/index.js.map +1 -0
  62. package/dist/mcp/server.d.ts +22 -0
  63. package/dist/mcp/server.d.ts.map +1 -0
  64. package/dist/mcp/server.js +368 -0
  65. package/dist/mcp/server.js.map +1 -0
  66. package/dist/mcp/tools.d.ts +14 -0
  67. package/dist/mcp/tools.d.ts.map +1 -0
  68. package/dist/mcp/tools.js +110 -0
  69. package/dist/mcp/tools.js.map +1 -0
  70. package/dist/mcp/types.d.ts +100 -0
  71. package/dist/mcp/types.d.ts.map +1 -0
  72. package/dist/mcp/types.js +9 -0
  73. package/dist/mcp/types.js.map +1 -0
  74. package/dist/migration.d.ts +77 -0
  75. package/dist/migration.d.ts.map +1 -0
  76. package/dist/migration.js +457 -0
  77. package/dist/migration.js.map +1 -0
  78. package/dist/sqljs-backend.d.ts +128 -0
  79. package/dist/sqljs-backend.d.ts.map +1 -0
  80. package/dist/sqljs-backend.js +623 -0
  81. package/dist/sqljs-backend.js.map +1 -0
  82. package/dist/types.d.ts +481 -0
  83. package/dist/types.d.ts.map +1 -0
  84. package/dist/types.js +73 -0
  85. package/dist/types.js.map +1 -0
  86. package/hooks.json +46 -0
  87. package/package.json +67 -0
  88. package/src/__tests__/index.test.ts +407 -0
  89. package/src/__tests__/sqljs-backend.test.ts +410 -0
  90. package/src/cache-manager.ts +515 -0
  91. package/src/cli/save.ts +109 -0
  92. package/src/cli/setup.ts +203 -0
  93. package/src/cli/viewer.ts +218 -0
  94. package/src/hnsw-index.ts +1013 -0
  95. package/src/hooks/__tests__/handlers.test.ts +298 -0
  96. package/src/hooks/__tests__/integration.test.ts +431 -0
  97. package/src/hooks/__tests__/service.test.ts +487 -0
  98. package/src/hooks/__tests__/types.test.ts +341 -0
  99. package/src/hooks/cli.ts +121 -0
  100. package/src/hooks/context.ts +77 -0
  101. package/src/hooks/index.ts +23 -0
  102. package/src/hooks/observation.ts +102 -0
  103. package/src/hooks/service.ts +582 -0
  104. package/src/hooks/session-init.ts +70 -0
  105. package/src/hooks/summarize.ts +89 -0
  106. package/src/hooks/types.ts +365 -0
  107. package/src/index.ts +755 -0
  108. package/src/mcp/__tests__/server.test.ts +181 -0
  109. package/src/mcp/index.ts +9 -0
  110. package/src/mcp/server.ts +441 -0
  111. package/src/mcp/tools.ts +113 -0
  112. package/src/mcp/types.ts +109 -0
  113. package/src/migration.ts +574 -0
  114. package/src/sql.js.d.ts +70 -0
  115. package/src/sqljs-backend.ts +789 -0
  116. package/src/types.ts +715 -0
@@ -0,0 +1,515 @@
1
+ /**
2
+ * Cache Manager
3
+ *
4
+ * High-performance LRU cache with TTL support, memory pressure handling,
5
+ * and write-through caching for the unified memory system.
6
+ *
7
+ * @module @agentkits/memory/cache-manager
8
+ */
9
+
10
+ import { EventEmitter } from 'node:events';
11
+ import {
12
+ CacheConfig,
13
+ CacheStats,
14
+ CachedEntry,
15
+ MemoryEntry,
16
+ } from './types.js';
17
+
18
+ /**
19
+ * Doubly-linked list node for LRU implementation
20
+ */
21
+ interface LRUNode<T> {
22
+ key: string;
23
+ value: CachedEntry<T>;
24
+ prev: LRUNode<T> | null;
25
+ next: LRUNode<T> | null;
26
+ }
27
+
28
+ /**
29
+ * High-performance LRU Cache with TTL support
30
+ *
31
+ * Features:
32
+ * - O(1) get, set, delete operations
33
+ * - LRU eviction policy
34
+ * - TTL-based expiration
35
+ * - Memory pressure handling
36
+ * - Write-through caching support
37
+ * - Performance statistics
38
+ */
39
+ export class CacheManager<T = MemoryEntry> extends EventEmitter {
40
+ private config: CacheConfig;
41
+ private cache: Map<string, LRUNode<T>> = new Map();
42
+ private head: LRUNode<T> | null = null;
43
+ private tail: LRUNode<T> | null = null;
44
+ private currentMemory: number = 0;
45
+
46
+ // Statistics
47
+ private stats: {
48
+ hits: number;
49
+ misses: number;
50
+ evictions: number;
51
+ expirations: number;
52
+ writes: number;
53
+ } = {
54
+ hits: 0,
55
+ misses: 0,
56
+ evictions: 0,
57
+ expirations: 0,
58
+ writes: 0,
59
+ };
60
+
61
+ // Cleanup timer
62
+ private cleanupInterval: NodeJS.Timeout | null = null;
63
+
64
+ constructor(config: Partial<CacheConfig> = {}) {
65
+ super();
66
+ this.config = this.mergeConfig(config);
67
+ this.startCleanupTimer();
68
+ }
69
+
70
+ /**
71
+ * Get a value from the cache
72
+ */
73
+ get(key: string): T | null {
74
+ const node = this.cache.get(key);
75
+
76
+ if (!node) {
77
+ this.stats.misses++;
78
+ this.emit('cache:miss', { key });
79
+ return null;
80
+ }
81
+
82
+ // Check if expired
83
+ if (this.isExpired(node.value)) {
84
+ this.delete(key);
85
+ this.stats.misses++;
86
+ this.stats.expirations++;
87
+ this.emit('cache:expired', { key });
88
+ return null;
89
+ }
90
+
91
+ // Update access time and count
92
+ node.value.lastAccessedAt = Date.now();
93
+ node.value.accessCount++;
94
+
95
+ // Move to front (most recently used)
96
+ this.moveToFront(node);
97
+
98
+ this.stats.hits++;
99
+ this.emit('cache:hit', { key });
100
+
101
+ return node.value.data;
102
+ }
103
+
104
+ /**
105
+ * Set a value in the cache
106
+ */
107
+ set(key: string, data: T, ttl?: number): void {
108
+ const now = Date.now();
109
+ const entryTtl = ttl || this.config.ttl;
110
+
111
+ // Check if key already exists
112
+ const existingNode = this.cache.get(key);
113
+ if (existingNode) {
114
+ // Update existing entry
115
+ existingNode.value.data = data;
116
+ existingNode.value.cachedAt = now;
117
+ existingNode.value.expiresAt = now + entryTtl;
118
+ existingNode.value.lastAccessedAt = now;
119
+
120
+ this.moveToFront(existingNode);
121
+ this.stats.writes++;
122
+ return;
123
+ }
124
+
125
+ // Calculate memory for new entry
126
+ const entryMemory = this.estimateSize(data);
127
+
128
+ // Evict entries if needed for memory pressure
129
+ if (this.config.maxMemory) {
130
+ while (
131
+ this.currentMemory + entryMemory > this.config.maxMemory &&
132
+ this.cache.size > 0
133
+ ) {
134
+ this.evictLRU();
135
+ }
136
+ }
137
+
138
+ // Evict entries if at capacity
139
+ while (this.cache.size >= this.config.maxSize) {
140
+ this.evictLRU();
141
+ }
142
+
143
+ // Create new node
144
+ const cachedEntry: CachedEntry<T> = {
145
+ data,
146
+ cachedAt: now,
147
+ expiresAt: now + entryTtl,
148
+ lastAccessedAt: now,
149
+ accessCount: 0,
150
+ };
151
+
152
+ const node: LRUNode<T> = {
153
+ key,
154
+ value: cachedEntry,
155
+ prev: null,
156
+ next: null,
157
+ };
158
+
159
+ // Add to cache
160
+ this.cache.set(key, node);
161
+ this.addToFront(node);
162
+ this.currentMemory += entryMemory;
163
+ this.stats.writes++;
164
+
165
+ this.emit('cache:set', { key, ttl: entryTtl });
166
+ }
167
+
168
+ /**
169
+ * Delete a value from the cache
170
+ */
171
+ delete(key: string): boolean {
172
+ const node = this.cache.get(key);
173
+ if (!node) {
174
+ return false;
175
+ }
176
+
177
+ this.removeNode(node);
178
+ this.cache.delete(key);
179
+ this.currentMemory -= this.estimateSize(node.value.data);
180
+
181
+ this.emit('cache:delete', { key });
182
+ return true;
183
+ }
184
+
185
+ /**
186
+ * Check if a key exists in the cache (without affecting LRU order)
187
+ */
188
+ has(key: string): boolean {
189
+ const node = this.cache.get(key);
190
+ if (!node) return false;
191
+ if (this.isExpired(node.value)) {
192
+ this.delete(key);
193
+ return false;
194
+ }
195
+ return true;
196
+ }
197
+
198
+ /**
199
+ * Clear all entries from the cache
200
+ */
201
+ clear(): void {
202
+ this.cache.clear();
203
+ this.head = null;
204
+ this.tail = null;
205
+ this.currentMemory = 0;
206
+
207
+ this.emit('cache:cleared', { previousSize: this.cache.size });
208
+ }
209
+
210
+ /**
211
+ * Get cache statistics
212
+ */
213
+ getStats(): CacheStats {
214
+ const total = this.stats.hits + this.stats.misses;
215
+ return {
216
+ size: this.cache.size,
217
+ hitRate: total > 0 ? this.stats.hits / total : 0,
218
+ hits: this.stats.hits,
219
+ misses: this.stats.misses,
220
+ evictions: this.stats.evictions,
221
+ memoryUsage: this.currentMemory,
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Get all keys in the cache
227
+ */
228
+ keys(): string[] {
229
+ return Array.from(this.cache.keys());
230
+ }
231
+
232
+ /**
233
+ * Get the size of the cache
234
+ */
235
+ get size(): number {
236
+ return this.cache.size;
237
+ }
238
+
239
+ /**
240
+ * Prefetch multiple keys in a single batch
241
+ */
242
+ async prefetch(
243
+ keys: string[],
244
+ loader: (keys: string[]) => Promise<Map<string, T>>,
245
+ ttl?: number
246
+ ): Promise<void> {
247
+ const missing = keys.filter((key) => !this.has(key));
248
+
249
+ if (missing.length === 0) {
250
+ return;
251
+ }
252
+
253
+ const data = await loader(missing);
254
+
255
+ for (const [key, value] of data) {
256
+ this.set(key, value, ttl);
257
+ }
258
+
259
+ this.emit('cache:prefetched', { keys: missing.length });
260
+ }
261
+
262
+ /**
263
+ * Get or set pattern - get from cache or load and cache
264
+ */
265
+ async getOrSet(
266
+ key: string,
267
+ loader: () => Promise<T>,
268
+ ttl?: number
269
+ ): Promise<T> {
270
+ const cached = this.get(key);
271
+ if (cached !== null) {
272
+ return cached;
273
+ }
274
+
275
+ const data = await loader();
276
+ this.set(key, data, ttl);
277
+ return data;
278
+ }
279
+
280
+ /**
281
+ * Warm the cache with initial data
282
+ */
283
+ warmUp(entries: Array<{ key: string; data: T; ttl?: number }>): void {
284
+ for (const entry of entries) {
285
+ this.set(entry.key, entry.data, entry.ttl);
286
+ }
287
+ this.emit('cache:warmedUp', { count: entries.length });
288
+ }
289
+
290
+ /**
291
+ * Invalidate entries matching a pattern
292
+ */
293
+ invalidatePattern(pattern: string | RegExp): number {
294
+ const regex = typeof pattern === 'string' ? new RegExp(pattern) : pattern;
295
+ let invalidated = 0;
296
+
297
+ for (const key of this.cache.keys()) {
298
+ if (regex.test(key)) {
299
+ this.delete(key);
300
+ invalidated++;
301
+ }
302
+ }
303
+
304
+ this.emit('cache:invalidated', { pattern: pattern.toString(), count: invalidated });
305
+ return invalidated;
306
+ }
307
+
308
+ /**
309
+ * Shutdown the cache manager
310
+ */
311
+ shutdown(): void {
312
+ if (this.cleanupInterval) {
313
+ clearInterval(this.cleanupInterval);
314
+ this.cleanupInterval = null;
315
+ }
316
+ this.clear();
317
+ this.emit('cache:shutdown');
318
+ }
319
+
320
+ // ===== Private Methods =====
321
+
322
+ private mergeConfig(config: Partial<CacheConfig>): CacheConfig {
323
+ return {
324
+ maxSize: config.maxSize || 10000,
325
+ ttl: config.ttl || 300000, // 5 minutes default
326
+ lruEnabled: config.lruEnabled !== false,
327
+ maxMemory: config.maxMemory,
328
+ writeThrough: config.writeThrough || false,
329
+ };
330
+ }
331
+
332
+ private isExpired(entry: CachedEntry<T>): boolean {
333
+ return Date.now() > entry.expiresAt;
334
+ }
335
+
336
+ private estimateSize(data: T): number {
337
+ try {
338
+ return JSON.stringify(data).length * 2; // Rough UTF-16 estimate
339
+ } catch {
340
+ return 1000; // Default for non-serializable objects
341
+ }
342
+ }
343
+
344
+ private addToFront(node: LRUNode<T>): void {
345
+ node.prev = null;
346
+ node.next = this.head;
347
+
348
+ if (this.head) {
349
+ this.head.prev = node;
350
+ }
351
+
352
+ this.head = node;
353
+
354
+ if (!this.tail) {
355
+ this.tail = node;
356
+ }
357
+ }
358
+
359
+ private removeNode(node: LRUNode<T>): void {
360
+ if (node.prev) {
361
+ node.prev.next = node.next;
362
+ } else {
363
+ this.head = node.next;
364
+ }
365
+
366
+ if (node.next) {
367
+ node.next.prev = node.prev;
368
+ } else {
369
+ this.tail = node.prev;
370
+ }
371
+ }
372
+
373
+ private moveToFront(node: LRUNode<T>): void {
374
+ if (node === this.head) return;
375
+
376
+ this.removeNode(node);
377
+ this.addToFront(node);
378
+ }
379
+
380
+ private evictLRU(): void {
381
+ if (!this.tail) return;
382
+
383
+ const evictedKey = this.tail.key;
384
+ const evictedSize = this.estimateSize(this.tail.value.data);
385
+
386
+ this.removeNode(this.tail);
387
+ this.cache.delete(evictedKey);
388
+ this.currentMemory -= evictedSize;
389
+ this.stats.evictions++;
390
+
391
+ this.emit('cache:eviction', { key: evictedKey });
392
+ }
393
+
394
+ private startCleanupTimer(): void {
395
+ // Clean up expired entries every minute
396
+ this.cleanupInterval = setInterval(() => {
397
+ this.cleanupExpired();
398
+ }, 60000);
399
+ }
400
+
401
+ private cleanupExpired(): void {
402
+ const now = Date.now();
403
+ let cleaned = 0;
404
+
405
+ for (const [key, node] of this.cache) {
406
+ if (node.value.expiresAt < now) {
407
+ this.delete(key);
408
+ cleaned++;
409
+ }
410
+ }
411
+
412
+ if (cleaned > 0) {
413
+ this.emit('cache:cleanup', { expired: cleaned });
414
+ }
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Multi-layer cache with L1 (memory) and L2 (storage) tiers
420
+ */
421
+ export class TieredCacheManager<T = MemoryEntry> extends EventEmitter {
422
+ private l1Cache: CacheManager<T>;
423
+ private l2Loader: ((key: string) => Promise<T | null>) | null = null;
424
+ private l2Writer: ((key: string, value: T) => Promise<void>) | null = null;
425
+
426
+ constructor(
427
+ l1Config: Partial<CacheConfig> = {},
428
+ l2Options?: {
429
+ loader: (key: string) => Promise<T | null>;
430
+ writer?: (key: string, value: T) => Promise<void>;
431
+ }
432
+ ) {
433
+ super();
434
+ this.l1Cache = new CacheManager<T>(l1Config);
435
+
436
+ if (l2Options) {
437
+ this.l2Loader = l2Options.loader;
438
+ this.l2Writer = l2Options.writer ?? null;
439
+ }
440
+
441
+ // Forward L1 events
442
+ this.l1Cache.on('cache:hit', (data) => this.emit('l1:hit', data));
443
+ this.l1Cache.on('cache:miss', (data) => this.emit('l1:miss', data));
444
+ this.l1Cache.on('cache:eviction', (data) => this.emit('l1:eviction', data));
445
+ }
446
+
447
+ /**
448
+ * Get from tiered cache
449
+ */
450
+ async get(key: string): Promise<T | null> {
451
+ // Try L1 first
452
+ const l1Result = this.l1Cache.get(key);
453
+ if (l1Result !== null) {
454
+ return l1Result;
455
+ }
456
+
457
+ // Try L2 if available
458
+ if (this.l2Loader) {
459
+ const l2Result = await this.l2Loader(key);
460
+ if (l2Result !== null) {
461
+ // Promote to L1
462
+ this.l1Cache.set(key, l2Result);
463
+ this.emit('l2:hit', { key });
464
+ return l2Result;
465
+ }
466
+ this.emit('l2:miss', { key });
467
+ }
468
+
469
+ return null;
470
+ }
471
+
472
+ /**
473
+ * Set in tiered cache
474
+ */
475
+ async set(key: string, value: T, ttl?: number): Promise<void> {
476
+ // Write to L1
477
+ this.l1Cache.set(key, value, ttl);
478
+
479
+ // Write-through to L2 if configured
480
+ if (this.l2Writer) {
481
+ await this.l2Writer(key, value);
482
+ this.emit('l2:write', { key });
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Delete from tiered cache
488
+ */
489
+ delete(key: string): boolean {
490
+ return this.l1Cache.delete(key);
491
+ }
492
+
493
+ /**
494
+ * Get L1 cache statistics
495
+ */
496
+ getStats(): CacheStats {
497
+ return this.l1Cache.getStats();
498
+ }
499
+
500
+ /**
501
+ * Clear L1 cache
502
+ */
503
+ clear(): void {
504
+ this.l1Cache.clear();
505
+ }
506
+
507
+ /**
508
+ * Shutdown tiered cache
509
+ */
510
+ shutdown(): void {
511
+ this.l1Cache.shutdown();
512
+ }
513
+ }
514
+
515
+ export default CacheManager;
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentKits Memory Save CLI
4
+ *
5
+ * Simple CLI to save entries to the memory database.
6
+ *
7
+ * Usage:
8
+ * npx agentkits-memory-save --content "..." [options]
9
+ *
10
+ * Options:
11
+ * --content=X Content to save (required)
12
+ * --category=X Category: decision, pattern, error, context, observation (default: context)
13
+ * --tags=X Comma-separated tags
14
+ * --importance=X low, medium, high, critical (default: medium)
15
+ * --project-dir=X Project directory (default: cwd or CLAUDE_PROJECT_DIR)
16
+ *
17
+ * @module @agentkits/memory/cli/save
18
+ */
19
+
20
+ import { ProjectMemoryService, DEFAULT_NAMESPACES, MemoryEntryInput } from '../index.js';
21
+
22
+ const args = process.argv.slice(2);
23
+
24
+ const CATEGORY_TO_NAMESPACE: Record<string, string> = {
25
+ decision: DEFAULT_NAMESPACES.DECISIONS,
26
+ pattern: DEFAULT_NAMESPACES.PATTERNS,
27
+ error: DEFAULT_NAMESPACES.ERRORS,
28
+ context: DEFAULT_NAMESPACES.CONTEXT,
29
+ observation: DEFAULT_NAMESPACES.ACTIVE,
30
+ };
31
+
32
+ const IMPORTANCE_MAP: Record<string, number> = {
33
+ low: 0.3,
34
+ medium: 0.5,
35
+ high: 0.7,
36
+ critical: 1.0,
37
+ };
38
+
39
+ function parseArgs(): Record<string, string> {
40
+ const parsed: Record<string, string> = {};
41
+ for (const arg of args) {
42
+ if (arg.startsWith('--')) {
43
+ const eqIndex = arg.indexOf('=');
44
+ if (eqIndex > 0) {
45
+ const key = arg.slice(2, eqIndex);
46
+ const value = arg.slice(eqIndex + 1);
47
+ parsed[key] = value;
48
+ }
49
+ }
50
+ }
51
+ return parsed;
52
+ }
53
+
54
+ async function main() {
55
+ const options = parseArgs();
56
+
57
+ const content = options.content;
58
+ if (!content) {
59
+ console.error('Error: --content is required');
60
+ process.exit(1);
61
+ }
62
+
63
+ const category = options.category || 'context';
64
+ const namespace = CATEGORY_TO_NAMESPACE[category] || DEFAULT_NAMESPACES.CONTEXT;
65
+ const importance = IMPORTANCE_MAP[options.importance || 'medium'] || 0.5;
66
+ const projectDir = options['project-dir'] || process.env.CLAUDE_PROJECT_DIR || process.cwd();
67
+
68
+ const tags = options.tags
69
+ ? options.tags.split(',').map((t) => t.trim())
70
+ : [];
71
+
72
+ try {
73
+ const service = new ProjectMemoryService({
74
+ baseDir: `${projectDir}/.claude/memory`,
75
+ dbFilename: 'memory.db',
76
+ });
77
+
78
+ await service.initialize();
79
+
80
+ const key = `${category}-${Date.now()}`;
81
+
82
+ const input: MemoryEntryInput = {
83
+ key,
84
+ content,
85
+ type: 'episodic',
86
+ namespace,
87
+ tags,
88
+ metadata: {
89
+ category,
90
+ importance,
91
+ source: 'cli',
92
+ },
93
+ };
94
+
95
+ await service.storeEntry(input);
96
+
97
+ await service.shutdown();
98
+
99
+ console.log(JSON.stringify({ success: true, key, namespace }));
100
+ } catch (error) {
101
+ console.error(JSON.stringify({
102
+ success: false,
103
+ error: error instanceof Error ? error.message : String(error),
104
+ }));
105
+ process.exit(1);
106
+ }
107
+ }
108
+
109
+ main();