claude-mem 3.0.2 → 3.0.4

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 (56) hide show
  1. package/.mcp.json +11 -0
  2. package/claude-mem +0 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +64 -0
  5. package/dist/commands/compress.d.ts +2 -0
  6. package/dist/commands/compress.js +59 -0
  7. package/dist/commands/install.d.ts +2 -0
  8. package/dist/commands/install.js +372 -0
  9. package/dist/commands/load-context.d.ts +2 -0
  10. package/dist/commands/load-context.js +330 -0
  11. package/dist/commands/logs.d.ts +2 -0
  12. package/dist/commands/logs.js +41 -0
  13. package/dist/commands/migrate.d.ts +9 -0
  14. package/dist/commands/migrate.js +174 -0
  15. package/dist/commands/status.d.ts +1 -0
  16. package/dist/commands/status.js +159 -0
  17. package/dist/commands/uninstall.d.ts +2 -0
  18. package/dist/commands/uninstall.js +105 -0
  19. package/dist/config.d.ts +6 -0
  20. package/dist/config.js +33 -0
  21. package/dist/constants.d.ts +516 -0
  22. package/dist/constants.js +522 -0
  23. package/dist/error-handler.d.ts +17 -0
  24. package/dist/error-handler.js +103 -0
  25. package/dist/mcp-server-cli.d.ts +34 -0
  26. package/dist/mcp-server-cli.js +158 -0
  27. package/dist/mcp-server.d.ts +103 -0
  28. package/dist/mcp-server.js +269 -0
  29. package/dist/types.d.ts +148 -0
  30. package/dist/types.js +78 -0
  31. package/dist/utils/HookDetector.d.ts +64 -0
  32. package/dist/utils/HookDetector.js +213 -0
  33. package/dist/utils/PathResolver.d.ts +16 -0
  34. package/dist/utils/PathResolver.js +55 -0
  35. package/dist/utils/SettingsManager.d.ts +63 -0
  36. package/dist/utils/SettingsManager.js +133 -0
  37. package/dist/utils/TranscriptCompressor.d.ts +111 -0
  38. package/dist/utils/TranscriptCompressor.js +486 -0
  39. package/dist/utils/common.d.ts +29 -0
  40. package/dist/utils/common.js +14 -0
  41. package/dist/utils/error-utils.d.ts +93 -0
  42. package/dist/utils/error-utils.js +238 -0
  43. package/dist/utils/index.d.ts +19 -0
  44. package/dist/utils/index.js +26 -0
  45. package/dist/utils/logger.d.ts +19 -0
  46. package/dist/utils/logger.js +42 -0
  47. package/dist/utils/mcp-client-factory.d.ts +51 -0
  48. package/dist/utils/mcp-client-factory.js +115 -0
  49. package/dist/utils/mcp-client.d.ts +75 -0
  50. package/dist/utils/mcp-client.js +120 -0
  51. package/dist/utils/memory-mcp-client.d.ts +135 -0
  52. package/dist/utils/memory-mcp-client.js +490 -0
  53. package/dist/utils/weaviate-mcp-adapter.d.ts +102 -0
  54. package/dist/utils/weaviate-mcp-adapter.js +587 -0
  55. package/package.json +3 -2
  56. package/src/claude-mem.js +0 -859
@@ -0,0 +1,490 @@
1
+ /**
2
+ * Memory MCP Client for claude-mem
3
+ *
4
+ * This client implements the IMCPClient interface using the traditional JSONL file-based
5
+ * storage system. It maintains backward compatibility with the existing claude-mem
6
+ * architecture while providing a clean interface for MCP operations.
7
+ *
8
+ * Key Features:
9
+ * - 100% backward compatibility with existing JSONL index files
10
+ * - Implements full IMCPClient interface
11
+ * - Thread-safe file operations with locking
12
+ * - Efficient search through in-memory indexing
13
+ * - Atomic writes to prevent corruption
14
+ */
15
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
16
+ import { join } from 'path';
17
+ import { homedir } from 'os';
18
+ /**
19
+ * Memory MCP Client - JSONL file-based implementation
20
+ *
21
+ * This client provides the IMCPClient interface while using the existing
22
+ * JSONL file storage system for backward compatibility.
23
+ */
24
+ export class MemoryMCPClient {
25
+ static instance = null;
26
+ isConnected = false;
27
+ cache = null;
28
+ // File paths
29
+ configDir = join(homedir(), '.claude-mem');
30
+ indexDir = join(this.configDir, 'index');
31
+ indexPath = join(this.indexDir, 'claude_mem_index.jsonl');
32
+ lockPath = join(this.indexDir, 'index.lock');
33
+ /**
34
+ * Singleton pattern - ensures only one instance manages file operations
35
+ */
36
+ static getInstance() {
37
+ if (!MemoryMCPClient.instance) {
38
+ MemoryMCPClient.instance = new MemoryMCPClient();
39
+ }
40
+ return MemoryMCPClient.instance;
41
+ }
42
+ constructor() {
43
+ // Public constructor but singleton pattern enforced via getInstance()
44
+ }
45
+ /**
46
+ * Connect to the memory backend (initialize file system)
47
+ */
48
+ async connect() {
49
+ if (this.isConnected) {
50
+ return;
51
+ }
52
+ try {
53
+ // Ensure directories exist
54
+ this.ensureDirectories();
55
+ // Initialize or load cache
56
+ await this.loadCache();
57
+ this.isConnected = true;
58
+ }
59
+ catch (error) {
60
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
61
+ throw new Error(`Failed to connect to memory backend: ${errorMessage}`);
62
+ }
63
+ }
64
+ /**
65
+ * Disconnect from the memory backend (cleanup)
66
+ */
67
+ async disconnect() {
68
+ this.isConnected = false;
69
+ this.cache = null;
70
+ // Remove lock file if it exists
71
+ if (existsSync(this.lockPath)) {
72
+ try {
73
+ require('fs').unlinkSync(this.lockPath);
74
+ }
75
+ catch (error) {
76
+ // Ignore lock cleanup errors
77
+ }
78
+ }
79
+ }
80
+ /**
81
+ * Create entities in the memory backend
82
+ */
83
+ async createEntities(entities) {
84
+ if (!this.isConnected) {
85
+ await this.connect();
86
+ }
87
+ await this.withFileLock(async () => {
88
+ // Create a new index entry for this batch of entities
89
+ const entry = {
90
+ id: this.generateId(),
91
+ timestamp: new Date().toISOString(),
92
+ project: this.extractProjectFromEntities(entities),
93
+ session: this.generateSessionId(),
94
+ summary: this.generateSummaryFromEntities(entities),
95
+ nodes: entities.map(e => e.name),
96
+ keywords: this.extractKeywords(entities),
97
+ relations: [],
98
+ entities: entities.map(e => ({
99
+ name: e.name,
100
+ type: e.entityType,
101
+ observations: e.observations
102
+ }))
103
+ };
104
+ // Append to JSONL file
105
+ this.appendToIndex(entry);
106
+ // Update cache
107
+ entities.forEach(entity => {
108
+ this.cache.entities.set(entity.name, entity);
109
+ });
110
+ });
111
+ }
112
+ /**
113
+ * Create relations in the memory backend
114
+ */
115
+ async createRelations(relations) {
116
+ if (!this.isConnected) {
117
+ await this.connect();
118
+ }
119
+ await this.withFileLock(async () => {
120
+ // Create a new index entry for this batch of relations
121
+ const entry = {
122
+ id: this.generateId(),
123
+ timestamp: new Date().toISOString(),
124
+ project: this.extractProjectFromRelations(relations),
125
+ session: this.generateSessionId(),
126
+ summary: this.generateSummaryFromRelations(relations),
127
+ nodes: this.getUniqueNodesFromRelations(relations),
128
+ keywords: this.extractKeywordsFromRelations(relations),
129
+ relations: relations.map(r => ({
130
+ from: r.from,
131
+ to: r.to,
132
+ type: r.relationType
133
+ })),
134
+ entities: []
135
+ };
136
+ // Append to JSONL file
137
+ this.appendToIndex(entry);
138
+ // Update cache
139
+ relations.forEach(relation => {
140
+ const key = `${relation.from}->${relation.to}:${relation.relationType}`;
141
+ this.cache.relations.set(key, relation);
142
+ });
143
+ });
144
+ }
145
+ /**
146
+ * Search nodes using text-based matching
147
+ */
148
+ async searchNodes(query) {
149
+ if (!this.isConnected) {
150
+ await this.connect();
151
+ }
152
+ await this.ensureCacheLoaded();
153
+ const results = {
154
+ entities: [],
155
+ relations: []
156
+ };
157
+ const queryLower = query.toLowerCase();
158
+ const isEntityTypeQuery = /^[A-Z][a-z]+_/.test(query);
159
+ // Search entities
160
+ for (const [name, entity] of this.cache.entities) {
161
+ if (this.matchesEntity(entity, queryLower, isEntityTypeQuery)) {
162
+ results.entities.push({
163
+ name: entity.name,
164
+ entityType: entity.entityType,
165
+ observations: entity.observations
166
+ });
167
+ }
168
+ }
169
+ // Search relations (unless it's a specific entity type search)
170
+ if (!isEntityTypeQuery) {
171
+ for (const [key, relation] of this.cache.relations) {
172
+ if (this.matchesRelation(relation, queryLower)) {
173
+ results.relations.push({
174
+ from: relation.from,
175
+ to: relation.to,
176
+ relationType: relation.relationType
177
+ });
178
+ }
179
+ }
180
+ }
181
+ // Sort results by relevance (exact matches first)
182
+ results.entities = results.entities.sort((a, b) => {
183
+ const aExact = a.name.toLowerCase().includes(queryLower) ? 0 : 1;
184
+ const bExact = b.name.toLowerCase().includes(queryLower) ? 0 : 1;
185
+ return aExact - bExact;
186
+ });
187
+ return results;
188
+ }
189
+ /**
190
+ * Open specific nodes by exact name matching
191
+ */
192
+ async openNodes(names) {
193
+ if (!this.isConnected) {
194
+ await this.connect();
195
+ }
196
+ await this.ensureCacheLoaded();
197
+ const results = {
198
+ entities: [],
199
+ relations: []
200
+ };
201
+ // Find exact entity matches
202
+ for (const name of names) {
203
+ const entity = this.cache.entities.get(name);
204
+ if (entity) {
205
+ results.entities.push({
206
+ name: entity.name,
207
+ entityType: entity.entityType,
208
+ observations: entity.observations
209
+ });
210
+ }
211
+ // Find relations involving this entity
212
+ for (const [key, relation] of this.cache.relations) {
213
+ if (relation.from === name || relation.to === name) {
214
+ results.relations.push({
215
+ from: relation.from,
216
+ to: relation.to,
217
+ relationType: relation.relationType
218
+ });
219
+ }
220
+ }
221
+ }
222
+ return results;
223
+ }
224
+ // =============================================================================
225
+ // PRIVATE HELPER METHODS
226
+ // =============================================================================
227
+ /**
228
+ * Ensure required directories exist
229
+ */
230
+ ensureDirectories() {
231
+ if (!existsSync(this.configDir)) {
232
+ mkdirSync(this.configDir, { recursive: true });
233
+ }
234
+ if (!existsSync(this.indexDir)) {
235
+ mkdirSync(this.indexDir, { recursive: true });
236
+ }
237
+ }
238
+ /**
239
+ * Load cache from JSONL files
240
+ */
241
+ async loadCache() {
242
+ this.cache = {
243
+ entities: new Map(),
244
+ relations: new Map(),
245
+ lastModified: 0
246
+ };
247
+ if (!existsSync(this.indexPath)) {
248
+ return; // No index file yet
249
+ }
250
+ try {
251
+ const content = readFileSync(this.indexPath, 'utf-8');
252
+ const lines = content.trim().split('\n').filter(line => line.trim());
253
+ for (const line of lines) {
254
+ try {
255
+ const entry = JSON.parse(line);
256
+ // Load entities
257
+ entry.entities?.forEach(entity => {
258
+ this.cache.entities.set(entity.name, {
259
+ name: entity.name,
260
+ entityType: entity.type,
261
+ observations: entity.observations
262
+ });
263
+ });
264
+ // Load relations
265
+ entry.relations?.forEach(relation => {
266
+ const key = `${relation.from}->${relation.to}:${relation.type}`;
267
+ this.cache.relations.set(key, {
268
+ from: relation.from,
269
+ to: relation.to,
270
+ relationType: relation.type
271
+ });
272
+ });
273
+ }
274
+ catch (error) {
275
+ // Skip malformed lines
276
+ console.warn(`Skipping malformed index line: ${line}`);
277
+ }
278
+ }
279
+ // Update last modified time
280
+ const stats = require('fs').statSync(this.indexPath);
281
+ this.cache.lastModified = stats.mtime.getTime();
282
+ }
283
+ catch (error) {
284
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
285
+ console.warn(`Failed to load memory cache: ${errorMessage}`);
286
+ }
287
+ }
288
+ /**
289
+ * Ensure cache is loaded and up to date
290
+ */
291
+ async ensureCacheLoaded() {
292
+ if (!this.cache) {
293
+ await this.loadCache();
294
+ return;
295
+ }
296
+ // Check if index file has been modified
297
+ if (existsSync(this.indexPath)) {
298
+ const stats = require('fs').statSync(this.indexPath);
299
+ if (stats.mtime.getTime() > this.cache.lastModified) {
300
+ await this.loadCache();
301
+ }
302
+ }
303
+ }
304
+ /**
305
+ * Execute operation with file locking
306
+ */
307
+ async withFileLock(operation) {
308
+ const maxRetries = 5;
309
+ const retryDelay = 100;
310
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
311
+ try {
312
+ // Check for existing lock
313
+ if (existsSync(this.lockPath)) {
314
+ if (attempt === maxRetries) {
315
+ throw new Error('File is locked by another process');
316
+ }
317
+ await this.sleep(retryDelay * attempt);
318
+ continue;
319
+ }
320
+ // Create lock
321
+ writeFileSync(this.lockPath, process.pid.toString());
322
+ try {
323
+ return await operation();
324
+ }
325
+ finally {
326
+ // Remove lock
327
+ if (existsSync(this.lockPath)) {
328
+ require('fs').unlinkSync(this.lockPath);
329
+ }
330
+ }
331
+ }
332
+ catch (error) {
333
+ if (attempt === maxRetries) {
334
+ throw error;
335
+ }
336
+ await this.sleep(retryDelay * attempt);
337
+ }
338
+ }
339
+ throw new Error('Failed to acquire file lock');
340
+ }
341
+ /**
342
+ * Append entry to JSONL index file
343
+ */
344
+ appendToIndex(entry) {
345
+ const line = JSON.stringify(entry) + '\n';
346
+ if (existsSync(this.indexPath)) {
347
+ require('fs').appendFileSync(this.indexPath, line, 'utf-8');
348
+ }
349
+ else {
350
+ writeFileSync(this.indexPath, line, 'utf-8');
351
+ }
352
+ }
353
+ /**
354
+ * Check if entity matches search query
355
+ */
356
+ matchesEntity(entity, queryLower, isEntityTypeQuery) {
357
+ if (isEntityTypeQuery) {
358
+ // For entity type queries like "Component_", match the entityType
359
+ const type = queryLower.replace('_', '').toLowerCase();
360
+ return entity.entityType.toLowerCase() === type;
361
+ }
362
+ // General text search
363
+ const searchableText = [
364
+ entity.name,
365
+ entity.entityType,
366
+ ...entity.observations
367
+ ].join(' ').toLowerCase();
368
+ return searchableText.includes(queryLower);
369
+ }
370
+ /**
371
+ * Check if relation matches search query
372
+ */
373
+ matchesRelation(relation, queryLower) {
374
+ const searchableText = [
375
+ relation.from,
376
+ relation.to,
377
+ relation.relationType
378
+ ].join(' ').toLowerCase();
379
+ return searchableText.includes(queryLower);
380
+ }
381
+ /**
382
+ * Generate unique ID for index entries
383
+ */
384
+ generateId() {
385
+ return `mem_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
386
+ }
387
+ /**
388
+ * Generate session ID (simplified)
389
+ */
390
+ generateSessionId() {
391
+ return `session_${Date.now()}`;
392
+ }
393
+ /**
394
+ * Extract project name from entities
395
+ */
396
+ extractProjectFromEntities(entities) {
397
+ if (entities.length === 0)
398
+ return 'unknown';
399
+ // Extract project from entity name format: project_entityType_name
400
+ const parts = entities[0].name.split('_');
401
+ return parts.length > 0 ? parts[0] : 'unknown';
402
+ }
403
+ /**
404
+ * Extract project name from relations
405
+ */
406
+ extractProjectFromRelations(relations) {
407
+ if (relations.length === 0)
408
+ return 'unknown';
409
+ const parts = relations[0].from.split('_');
410
+ return parts.length > 0 ? parts[0] : 'unknown';
411
+ }
412
+ /**
413
+ * Generate summary from entities
414
+ */
415
+ generateSummaryFromEntities(entities) {
416
+ if (entities.length === 1) {
417
+ return `Created ${entities[0].entityType}: ${entities[0].name.split('_').pop()}`;
418
+ }
419
+ return `Created ${entities.length} ${entities[0].entityType} entities`;
420
+ }
421
+ /**
422
+ * Generate summary from relations
423
+ */
424
+ generateSummaryFromRelations(relations) {
425
+ if (relations.length === 1) {
426
+ const rel = relations[0];
427
+ const fromName = rel.from.split('_').pop();
428
+ const toName = rel.to.split('_').pop();
429
+ return `${fromName} ${rel.relationType} ${toName}`;
430
+ }
431
+ return `Created ${relations.length} relations`;
432
+ }
433
+ /**
434
+ * Extract keywords from entities
435
+ */
436
+ extractKeywords(entities) {
437
+ const keywords = new Set();
438
+ entities.forEach(entity => {
439
+ keywords.add(entity.entityType);
440
+ keywords.add(entity.name.split('_').pop() || entity.name);
441
+ entity.observations.forEach(obs => {
442
+ // Extract key terms from observations
443
+ const words = obs.split(/\s+/)
444
+ .filter(word => word.length > 3)
445
+ .slice(0, 3); // Limit to first 3 significant words
446
+ words.forEach(word => keywords.add(word.toLowerCase()));
447
+ });
448
+ });
449
+ return Array.from(keywords).slice(0, 10); // Limit total keywords
450
+ }
451
+ /**
452
+ * Extract keywords from relations
453
+ */
454
+ extractKeywordsFromRelations(relations) {
455
+ const keywords = new Set();
456
+ relations.forEach(relation => {
457
+ keywords.add(relation.relationType);
458
+ keywords.add(relation.from.split('_').pop() || relation.from);
459
+ keywords.add(relation.to.split('_').pop() || relation.to);
460
+ });
461
+ return Array.from(keywords);
462
+ }
463
+ /**
464
+ * Get unique nodes from relations
465
+ */
466
+ getUniqueNodesFromRelations(relations) {
467
+ const nodes = new Set();
468
+ relations.forEach(relation => {
469
+ nodes.add(relation.from);
470
+ nodes.add(relation.to);
471
+ });
472
+ return Array.from(nodes);
473
+ }
474
+ /**
475
+ * Utility method for async delays
476
+ */
477
+ sleep(ms) {
478
+ return new Promise(resolve => setTimeout(resolve, ms));
479
+ }
480
+ }
481
+ /**
482
+ * Factory function to create MemoryMCPClient instance
483
+ */
484
+ export function createMemoryMCPClient() {
485
+ return MemoryMCPClient.getInstance();
486
+ }
487
+ /**
488
+ * Default export for convenience
489
+ */
490
+ export default MemoryMCPClient;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Embedded Weaviate Adapter for claude-mem
3
+ *
4
+ * This adapter provides a direct interface to embedded Weaviate using weaviate-ts-embedded,
5
+ * eliminating external dependencies and simplifying deployment.
6
+ *
7
+ * Key Features:
8
+ * - 100% backward compatibility with existing MCPClient interface
9
+ * - Embedded Weaviate instance (no external server required)
10
+ * - Maps entities to Weaviate objects with searchable text generation
11
+ * - Maps relations to separate Weaviate collection
12
+ * - Hybrid search (semantic + keyword) for better search quality
13
+ * - Automatic instance management (start/stop)
14
+ * - Retry logic with exponential backoff
15
+ */
16
+ import { IMCPClient, MCPEntity, MCPRelation, MCPSearchResult } from '../types.js';
17
+ /**
18
+ * Interface matching the existing MCPClient for 100% compatibility
19
+ */
20
+ export interface IWeaviateMCPAdapter extends IMCPClient {
21
+ }
22
+ /**
23
+ * WeaviateMCPAdapter - Direct interface to embedded Weaviate
24
+ *
25
+ * This adapter maintains 100% API compatibility with the existing MCPClient
26
+ * while providing superior search capabilities through embedded Weaviate's hybrid search.
27
+ */
28
+ export declare class WeaviateMCPAdapter implements IMCPClient {
29
+ private isConnected;
30
+ private client;
31
+ private maxRetries;
32
+ private retryDelay;
33
+ constructor();
34
+ /**
35
+ * Connect to embedded Weaviate
36
+ */
37
+ connect(): Promise<void>;
38
+ /**
39
+ * Disconnect from embedded Weaviate
40
+ */
41
+ disconnect(): Promise<void>;
42
+ /**
43
+ * Create entities in Weaviate using the weaviate-insert-one tool
44
+ */
45
+ createEntities(entities: MCPEntity[]): Promise<void>;
46
+ /**
47
+ * Create relations in Weaviate using the weaviate-insert-one tool
48
+ */
49
+ createRelations(relations: MCPRelation[]): Promise<void>;
50
+ /**
51
+ * Search nodes using Weaviate's hybrid search capabilities
52
+ *
53
+ * This method detects entity type prefix searches (e.g., "Component_") and
54
+ * uses appropriate search strategies for optimal results.
55
+ */
56
+ searchNodes(query: string): Promise<MCPSearchResult>;
57
+ /**
58
+ * Open specific nodes by exact name matching
59
+ */
60
+ openNodes(names: string[]): Promise<MCPSearchResult>;
61
+ /**
62
+ * Ensure required Weaviate collections exist
63
+ */
64
+ private ensureCollections;
65
+ /**
66
+ * Generate searchable text for an entity to improve hybrid search
67
+ */
68
+ private generateEntitySearchText;
69
+ /**
70
+ * Generate searchable text for a relation to improve hybrid search
71
+ */
72
+ private generateRelationSearchText;
73
+ /**
74
+ * Enhance query based on search type for better results
75
+ */
76
+ private enhanceQuery;
77
+ /**
78
+ * Format Weaviate results to match the memory MCP structure
79
+ */
80
+ private formatSearchResults;
81
+ /**
82
+ * Call a Weaviate MCP tool with error handling and retries
83
+ */
84
+ private callTool;
85
+ /**
86
+ * Execute actual Weaviate operations using the embedded client
87
+ */
88
+ private executeWeaviateOperation;
89
+ /**
90
+ * Utility method for async delays
91
+ */
92
+ private sleep;
93
+ }
94
+ /**
95
+ * Factory function to create WeaviateMCPAdapter instance
96
+ * Provides clean interface for creating embedded Weaviate adapters
97
+ */
98
+ export declare function createWeaviateMCPAdapter(): IMCPClient;
99
+ /**
100
+ * Default export for convenience
101
+ */
102
+ export default WeaviateMCPAdapter;