superlocalmemory 2.3.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 (100) hide show
  1. package/ATTRIBUTION.md +140 -0
  2. package/CHANGELOG.md +1749 -0
  3. package/LICENSE +21 -0
  4. package/README.md +600 -0
  5. package/bin/aider-smart +72 -0
  6. package/bin/slm +202 -0
  7. package/bin/slm-npm +73 -0
  8. package/bin/slm.bat +195 -0
  9. package/bin/slm.cmd +10 -0
  10. package/bin/superlocalmemoryv2:list +3 -0
  11. package/bin/superlocalmemoryv2:profile +3 -0
  12. package/bin/superlocalmemoryv2:recall +3 -0
  13. package/bin/superlocalmemoryv2:remember +3 -0
  14. package/bin/superlocalmemoryv2:reset +3 -0
  15. package/bin/superlocalmemoryv2:status +3 -0
  16. package/completions/slm.bash +58 -0
  17. package/completions/slm.zsh +76 -0
  18. package/configs/antigravity-mcp.json +13 -0
  19. package/configs/chatgpt-desktop-mcp.json +7 -0
  20. package/configs/claude-desktop-mcp.json +15 -0
  21. package/configs/codex-mcp.toml +13 -0
  22. package/configs/cody-commands.json +29 -0
  23. package/configs/continue-mcp.yaml +14 -0
  24. package/configs/continue-skills.yaml +26 -0
  25. package/configs/cursor-mcp.json +15 -0
  26. package/configs/gemini-cli-mcp.json +11 -0
  27. package/configs/jetbrains-mcp.json +11 -0
  28. package/configs/opencode-mcp.json +12 -0
  29. package/configs/perplexity-mcp.json +9 -0
  30. package/configs/vscode-copilot-mcp.json +12 -0
  31. package/configs/windsurf-mcp.json +16 -0
  32. package/configs/zed-mcp.json +12 -0
  33. package/docs/ARCHITECTURE.md +877 -0
  34. package/docs/CLI-COMMANDS-REFERENCE.md +425 -0
  35. package/docs/COMPETITIVE-ANALYSIS.md +210 -0
  36. package/docs/COMPRESSION-README.md +390 -0
  37. package/docs/GRAPH-ENGINE.md +503 -0
  38. package/docs/MCP-MANUAL-SETUP.md +720 -0
  39. package/docs/MCP-TROUBLESHOOTING.md +787 -0
  40. package/docs/PATTERN-LEARNING.md +363 -0
  41. package/docs/PROFILES-GUIDE.md +453 -0
  42. package/docs/RESET-GUIDE.md +353 -0
  43. package/docs/SEARCH-ENGINE-V2.2.0.md +748 -0
  44. package/docs/SEARCH-INTEGRATION-GUIDE.md +502 -0
  45. package/docs/UI-SERVER.md +254 -0
  46. package/docs/UNIVERSAL-INTEGRATION.md +432 -0
  47. package/docs/V2.2.0-OPTIONAL-SEARCH.md +666 -0
  48. package/docs/WINDOWS-INSTALL-README.txt +34 -0
  49. package/docs/WINDOWS-POST-INSTALL.txt +45 -0
  50. package/docs/example_graph_usage.py +148 -0
  51. package/hooks/memory-list-skill.js +130 -0
  52. package/hooks/memory-profile-skill.js +284 -0
  53. package/hooks/memory-recall-skill.js +109 -0
  54. package/hooks/memory-remember-skill.js +127 -0
  55. package/hooks/memory-reset-skill.js +274 -0
  56. package/install-skills.sh +436 -0
  57. package/install.ps1 +417 -0
  58. package/install.sh +755 -0
  59. package/mcp_server.py +585 -0
  60. package/package.json +94 -0
  61. package/requirements-core.txt +24 -0
  62. package/requirements.txt +10 -0
  63. package/scripts/postinstall.js +126 -0
  64. package/scripts/preuninstall.js +57 -0
  65. package/skills/slm-build-graph/SKILL.md +423 -0
  66. package/skills/slm-list-recent/SKILL.md +348 -0
  67. package/skills/slm-recall/SKILL.md +325 -0
  68. package/skills/slm-remember/SKILL.md +194 -0
  69. package/skills/slm-status/SKILL.md +363 -0
  70. package/skills/slm-switch-profile/SKILL.md +442 -0
  71. package/src/__pycache__/cache_manager.cpython-312.pyc +0 -0
  72. package/src/__pycache__/embedding_engine.cpython-312.pyc +0 -0
  73. package/src/__pycache__/graph_engine.cpython-312.pyc +0 -0
  74. package/src/__pycache__/hnsw_index.cpython-312.pyc +0 -0
  75. package/src/__pycache__/hybrid_search.cpython-312.pyc +0 -0
  76. package/src/__pycache__/memory-profiles.cpython-312.pyc +0 -0
  77. package/src/__pycache__/memory-reset.cpython-312.pyc +0 -0
  78. package/src/__pycache__/memory_compression.cpython-312.pyc +0 -0
  79. package/src/__pycache__/memory_store_v2.cpython-312.pyc +0 -0
  80. package/src/__pycache__/migrate_v1_to_v2.cpython-312.pyc +0 -0
  81. package/src/__pycache__/pattern_learner.cpython-312.pyc +0 -0
  82. package/src/__pycache__/query_optimizer.cpython-312.pyc +0 -0
  83. package/src/__pycache__/search_engine_v2.cpython-312.pyc +0 -0
  84. package/src/__pycache__/setup_validator.cpython-312.pyc +0 -0
  85. package/src/__pycache__/tree_manager.cpython-312.pyc +0 -0
  86. package/src/cache_manager.py +520 -0
  87. package/src/embedding_engine.py +671 -0
  88. package/src/graph_engine.py +970 -0
  89. package/src/hnsw_index.py +626 -0
  90. package/src/hybrid_search.py +693 -0
  91. package/src/memory-profiles.py +518 -0
  92. package/src/memory-reset.py +485 -0
  93. package/src/memory_compression.py +999 -0
  94. package/src/memory_store_v2.py +1088 -0
  95. package/src/migrate_v1_to_v2.py +638 -0
  96. package/src/pattern_learner.py +898 -0
  97. package/src/query_optimizer.py +513 -0
  98. package/src/search_engine_v2.py +403 -0
  99. package/src/setup_validator.py +479 -0
  100. package/src/tree_manager.py +720 -0
@@ -0,0 +1,518 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SuperLocalMemory V2 - Intelligent Local Memory System
4
+ Copyright (c) 2026 Varun Pratap Bhardwaj
5
+ Licensed under MIT License
6
+
7
+ Repository: https://github.com/varun369/SuperLocalMemoryV2
8
+ Author: Varun Pratap Bhardwaj (Solution Architect)
9
+
10
+ NOTICE: This software is protected by MIT License.
11
+ Attribution must be preserved in all copies or derivatives.
12
+ """
13
+
14
+ """
15
+ SuperLocalMemory V2 - Profile Management System
16
+
17
+ Allows users to maintain separate memory databases for different contexts/personalities:
18
+ - Work profile: Professional coding memories
19
+ - Personal profile: Personal projects and learning
20
+ - Client-specific profiles: Different clients get isolated memories
21
+ - Experimentation profile: Testing and experiments
22
+
23
+ Each profile is completely isolated with its own:
24
+ - Database (memory.db)
25
+ - Graph data
26
+ - Learned patterns
27
+ - Compressed archives
28
+ """
29
+
30
+ import os
31
+ import sys
32
+ import json
33
+ import shutil
34
+ from pathlib import Path
35
+ from datetime import datetime
36
+ import argparse
37
+
38
+ MEMORY_DIR = Path.home() / ".claude-memory"
39
+ PROFILES_DIR = MEMORY_DIR / "profiles"
40
+ CURRENT_PROFILE_FILE = MEMORY_DIR / ".current_profile"
41
+ CONFIG_FILE = MEMORY_DIR / "profiles.json"
42
+
43
+
44
+ class ProfileManager:
45
+ def __init__(self):
46
+ self.memory_dir = MEMORY_DIR
47
+ self.profiles_dir = PROFILES_DIR
48
+ self.current_profile_file = CURRENT_PROFILE_FILE
49
+ self.config_file = CONFIG_FILE
50
+
51
+ # Ensure profiles directory exists
52
+ self.profiles_dir.mkdir(exist_ok=True)
53
+
54
+ # Load or create config
55
+ self.config = self._load_config()
56
+
57
+ def _load_config(self):
58
+ """Load profiles configuration."""
59
+ if self.config_file.exists():
60
+ with open(self.config_file, 'r') as f:
61
+ return json.load(f)
62
+ else:
63
+ # Default config
64
+ config = {
65
+ 'profiles': {
66
+ 'default': {
67
+ 'name': 'default',
68
+ 'description': 'Default memory profile',
69
+ 'created_at': datetime.now().isoformat(),
70
+ 'last_used': datetime.now().isoformat()
71
+ }
72
+ },
73
+ 'active_profile': 'default'
74
+ }
75
+ self._save_config(config)
76
+ return config
77
+
78
+ def _save_config(self, config=None):
79
+ """Save profiles configuration."""
80
+ if config is None:
81
+ config = self.config
82
+
83
+ with open(self.config_file, 'w') as f:
84
+ json.dump(config, f, indent=2)
85
+
86
+ def _get_profile_path(self, profile_name):
87
+ """Get directory path for a profile with security validation."""
88
+ import re
89
+
90
+ # SECURITY: Validate profile name to prevent path traversal
91
+ if not profile_name:
92
+ raise ValueError("Profile name cannot be empty")
93
+
94
+ if not re.match(r'^[a-zA-Z0-9_-]+$', profile_name):
95
+ raise ValueError("Invalid profile name. Use only letters, numbers, dash, underscore.")
96
+
97
+ if len(profile_name) > 50:
98
+ raise ValueError("Profile name too long (max 50 characters)")
99
+
100
+ if profile_name in ['.', '..', 'default']:
101
+ raise ValueError(f"Reserved profile name: {profile_name}")
102
+
103
+ path = (self.profiles_dir / profile_name).resolve()
104
+
105
+ # SECURITY: Ensure path stays within profiles directory
106
+ if not str(path).startswith(str(self.profiles_dir.resolve())):
107
+ raise ValueError("Invalid profile path - path traversal detected")
108
+
109
+ return path
110
+
111
+ def _get_main_files(self):
112
+ """Get list of main memory system files to copy."""
113
+ return [
114
+ 'memory.db',
115
+ 'config.json',
116
+ 'vectors'
117
+ ]
118
+
119
+ def list_profiles(self):
120
+ """List all available profiles."""
121
+ print("\n" + "="*60)
122
+ print("AVAILABLE MEMORY PROFILES")
123
+ print("="*60)
124
+
125
+ active = self.config.get('active_profile', 'default')
126
+
127
+ if not self.config['profiles']:
128
+ print("\n No profiles found. Create one with: create <name>")
129
+ return
130
+
131
+ print(f"\n{'Profile':20s} {'Description':35s} {'Status':10s}")
132
+ print("-" * 70)
133
+
134
+ for name, info in self.config['profiles'].items():
135
+ status = "ACTIVE" if name == active else ""
136
+ desc = info.get('description', 'No description')[:35]
137
+ marker = "→ " if name == active else " "
138
+ print(f"{marker}{name:18s} {desc:35s} {status:10s}")
139
+
140
+ print(f"\nTotal profiles: {len(self.config['profiles'])}")
141
+ print(f"Active profile: {active}")
142
+
143
+ def create_profile(self, name, description=None, from_current=False):
144
+ """Create a new profile."""
145
+ print(f"\nCreating profile: {name}")
146
+
147
+ # Check if profile exists
148
+ if name in self.config['profiles']:
149
+ print(f"❌ Error: Profile '{name}' already exists")
150
+ return False
151
+
152
+ # Create profile directory
153
+ profile_path = self._get_profile_path(name)
154
+ profile_path.mkdir(exist_ok=True)
155
+
156
+ if from_current:
157
+ # Copy current memory system to new profile
158
+ print(" Copying current memory system...")
159
+
160
+ for file in self._get_main_files():
161
+ src = self.memory_dir / file
162
+ dst = profile_path / file
163
+
164
+ if src.exists():
165
+ if src.is_dir():
166
+ shutil.copytree(src, dst, dirs_exist_ok=True)
167
+ else:
168
+ shutil.copy2(src, dst)
169
+ print(f" ✓ Copied {file}")
170
+ else:
171
+ # Initialize empty profile with V2 schema
172
+ print(" Initializing empty profile with V2 schema...")
173
+ self._initialize_empty_profile(profile_path)
174
+
175
+ # Add to config
176
+ self.config['profiles'][name] = {
177
+ 'name': name,
178
+ 'description': description or f"Memory profile: {name}",
179
+ 'created_at': datetime.now().isoformat(),
180
+ 'last_used': None,
181
+ 'created_from': 'current' if from_current else 'empty'
182
+ }
183
+ self._save_config()
184
+
185
+ print(f"✅ Profile '{name}' created successfully")
186
+ return True
187
+
188
+ def _initialize_empty_profile(self, profile_path):
189
+ """Initialize empty profile with V2 schema."""
190
+ # Import the migration script's initialization logic
191
+ import sqlite3
192
+
193
+ db_path = profile_path / "memory.db"
194
+ conn = sqlite3.connect(db_path)
195
+ cursor = conn.cursor()
196
+
197
+ # Create basic V2 schema (simplified version)
198
+ cursor.execute('''
199
+ CREATE TABLE IF NOT EXISTS memories (
200
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
201
+ content TEXT NOT NULL,
202
+ summary TEXT,
203
+ project_path TEXT,
204
+ project_name TEXT,
205
+ tags TEXT,
206
+ category TEXT,
207
+ memory_type TEXT DEFAULT 'session',
208
+ importance INTEGER DEFAULT 5,
209
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
210
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
211
+ last_accessed TIMESTAMP,
212
+ access_count INTEGER DEFAULT 0,
213
+ content_hash TEXT UNIQUE,
214
+ parent_id INTEGER,
215
+ tree_path TEXT,
216
+ depth INTEGER DEFAULT 0,
217
+ cluster_id INTEGER,
218
+ tier INTEGER DEFAULT 1
219
+ )
220
+ ''')
221
+
222
+ # Add other essential tables
223
+ tables = [
224
+ 'memory_tree', 'graph_nodes', 'graph_edges',
225
+ 'graph_clusters', 'identity_patterns',
226
+ 'pattern_examples', 'memory_archive'
227
+ ]
228
+
229
+ for table in tables:
230
+ cursor.execute(f'CREATE TABLE IF NOT EXISTS {table} (id INTEGER PRIMARY KEY)')
231
+
232
+ conn.commit()
233
+ conn.close()
234
+
235
+ # Create basic config
236
+ config = {
237
+ "version": "2.0.0",
238
+ "embedding_model": "local-tfidf",
239
+ "max_context_tokens": 4000
240
+ }
241
+
242
+ with open(profile_path / "config.json", 'w') as f:
243
+ json.dump(config, f, indent=2)
244
+
245
+ print(" ✓ Initialized V2 schema")
246
+
247
+ def switch_profile(self, name):
248
+ """Switch to a different profile."""
249
+ if name not in self.config['profiles']:
250
+ print(f"❌ Error: Profile '{name}' not found")
251
+ print(f" Available: {', '.join(self.config['profiles'].keys())}")
252
+ return False
253
+
254
+ current = self.config.get('active_profile')
255
+
256
+ if current == name:
257
+ print(f"Already using profile: {name}")
258
+ return True
259
+
260
+ print(f"\nSwitching from '{current}' to '{name}'...")
261
+
262
+ # Save current profile
263
+ if current and current in self.config['profiles']:
264
+ self._save_current_to_profile(current)
265
+
266
+ # Load new profile
267
+ self._load_profile_to_main(name)
268
+
269
+ # Update config
270
+ self.config['active_profile'] = name
271
+ self.config['profiles'][name]['last_used'] = datetime.now().isoformat()
272
+ self._save_config()
273
+
274
+ print(f"✅ Switched to profile: {name}")
275
+ print(f"\n⚠️ IMPORTANT: Restart Claude CLI to use new profile!")
276
+ return True
277
+
278
+ def _save_current_to_profile(self, profile_name):
279
+ """Save current main memory system to profile."""
280
+ print(f" Saving current state to profile '{profile_name}'...")
281
+
282
+ profile_path = self._get_profile_path(profile_name)
283
+ profile_path.mkdir(exist_ok=True)
284
+
285
+ for file in self._get_main_files():
286
+ src = self.memory_dir / file
287
+ dst = profile_path / file
288
+
289
+ if src.exists():
290
+ # Remove old version
291
+ if dst.exists():
292
+ if dst.is_dir():
293
+ shutil.rmtree(dst)
294
+ else:
295
+ dst.unlink()
296
+
297
+ # Copy current
298
+ if src.is_dir():
299
+ shutil.copytree(src, dst)
300
+ else:
301
+ shutil.copy2(src, dst)
302
+
303
+ print(f" ✓ Saved to {profile_path}")
304
+
305
+ def _load_profile_to_main(self, profile_name):
306
+ """Load profile to main memory system."""
307
+ print(f" Loading profile '{profile_name}'...")
308
+
309
+ profile_path = self._get_profile_path(profile_name)
310
+
311
+ if not profile_path.exists():
312
+ print(f" ⚠️ Profile directory not found, will create on switch")
313
+ return
314
+
315
+ for file in self._get_main_files():
316
+ src = profile_path / file
317
+ dst = self.memory_dir / file
318
+
319
+ if src.exists():
320
+ # Remove old version
321
+ if dst.exists():
322
+ if dst.is_dir():
323
+ shutil.rmtree(dst)
324
+ else:
325
+ dst.unlink()
326
+
327
+ # Copy profile
328
+ if src.is_dir():
329
+ shutil.copytree(src, dst)
330
+ else:
331
+ shutil.copy2(src, dst)
332
+
333
+ print(f" ✓ Loaded from {profile_path}")
334
+
335
+ def delete_profile(self, name, force=False):
336
+ """Delete a profile."""
337
+ if name not in self.config['profiles']:
338
+ print(f"❌ Error: Profile '{name}' not found")
339
+ return False
340
+
341
+ if name == 'default':
342
+ print(f"❌ Error: Cannot delete 'default' profile")
343
+ return False
344
+
345
+ if self.config.get('active_profile') == name:
346
+ print(f"❌ Error: Cannot delete active profile")
347
+ print(f" Switch to another profile first")
348
+ return False
349
+
350
+ if not force:
351
+ print(f"\n⚠️ WARNING: This will permanently delete profile '{name}'")
352
+ response = input(f"Type profile name '{name}' to confirm: ")
353
+
354
+ if response != name:
355
+ print("Cancelled.")
356
+ return False
357
+
358
+ # Delete profile directory
359
+ profile_path = self._get_profile_path(name)
360
+ if profile_path.exists():
361
+ shutil.rmtree(profile_path)
362
+ print(f" ✓ Deleted profile directory")
363
+
364
+ # Remove from config
365
+ del self.config['profiles'][name]
366
+ self._save_config()
367
+
368
+ print(f"✅ Profile '{name}' deleted")
369
+ return True
370
+
371
+ def show_current(self):
372
+ """Show current active profile."""
373
+ active = self.config.get('active_profile', 'default')
374
+
375
+ if active in self.config['profiles']:
376
+ info = self.config['profiles'][active]
377
+
378
+ print("\n" + "="*60)
379
+ print("CURRENT ACTIVE PROFILE")
380
+ print("="*60)
381
+ print(f"\nProfile: {active}")
382
+ print(f"Description: {info.get('description', 'N/A')}")
383
+ print(f"Created: {info.get('created_at', 'N/A')}")
384
+ print(f"Last used: {info.get('last_used', 'N/A')}")
385
+
386
+ # Show stats if database exists
387
+ db_path = self.memory_dir / "memory.db"
388
+ if db_path.exists():
389
+ import sqlite3
390
+ conn = sqlite3.connect(db_path)
391
+ cursor = conn.cursor()
392
+
393
+ cursor.execute('SELECT COUNT(*) FROM memories')
394
+ memory_count = cursor.fetchone()[0]
395
+
396
+ print(f"\nMemories: {memory_count}")
397
+ conn.close()
398
+ else:
399
+ print(f"⚠️ Current profile '{active}' not found in config")
400
+
401
+ def rename_profile(self, old_name, new_name):
402
+ """Rename a profile."""
403
+ if old_name not in self.config['profiles']:
404
+ print(f"❌ Error: Profile '{old_name}' not found")
405
+ return False
406
+
407
+ if new_name in self.config['profiles']:
408
+ print(f"❌ Error: Profile '{new_name}' already exists")
409
+ return False
410
+
411
+ if old_name == 'default':
412
+ print(f"❌ Error: Cannot rename 'default' profile")
413
+ return False
414
+
415
+ # Rename directory
416
+ old_path = self._get_profile_path(old_name)
417
+ new_path = self._get_profile_path(new_name)
418
+
419
+ if old_path.exists():
420
+ old_path.rename(new_path)
421
+
422
+ # Update config
423
+ self.config['profiles'][new_name] = self.config['profiles'][old_name]
424
+ self.config['profiles'][new_name]['name'] = new_name
425
+ del self.config['profiles'][old_name]
426
+
427
+ if self.config.get('active_profile') == old_name:
428
+ self.config['active_profile'] = new_name
429
+
430
+ self._save_config()
431
+
432
+ print(f"✅ Profile renamed: '{old_name}' → '{new_name}'")
433
+ return True
434
+
435
+
436
+ def main():
437
+ parser = argparse.ArgumentParser(
438
+ description='SuperLocalMemory V2 - Profile Management',
439
+ formatter_class=argparse.RawDescriptionHelpFormatter,
440
+ epilog='''
441
+ Examples:
442
+ # List all profiles
443
+ python memory-profiles.py list
444
+
445
+ # Show current profile
446
+ python memory-profiles.py current
447
+
448
+ # Create new empty profile
449
+ python memory-profiles.py create work --description "Work projects"
450
+
451
+ # Create profile from current memories
452
+ python memory-profiles.py create personal --from-current
453
+
454
+ # Switch to different profile
455
+ python memory-profiles.py switch work
456
+
457
+ # Delete a profile
458
+ python memory-profiles.py delete old-profile
459
+
460
+ # Rename profile
461
+ python memory-profiles.py rename old-name new-name
462
+ '''
463
+ )
464
+
465
+ parser.add_argument('command',
466
+ choices=['list', 'current', 'create', 'switch', 'delete', 'rename'],
467
+ help='Profile command')
468
+ parser.add_argument('name', nargs='?', help='Profile name')
469
+ parser.add_argument('name2', nargs='?', help='Second name (for rename)')
470
+ parser.add_argument('--description', help='Profile description')
471
+ parser.add_argument('--from-current', action='store_true',
472
+ help='Create from current memory system')
473
+ parser.add_argument('--force', action='store_true',
474
+ help='Force operation without confirmation')
475
+
476
+ args = parser.parse_args()
477
+
478
+ manager = ProfileManager()
479
+
480
+ if args.command == 'list':
481
+ manager.list_profiles()
482
+
483
+ elif args.command == 'current':
484
+ manager.show_current()
485
+
486
+ elif args.command == 'create':
487
+ if not args.name:
488
+ print("❌ Error: Profile name required")
489
+ print(" Usage: python memory-profiles.py create <name>")
490
+ sys.exit(1)
491
+
492
+ manager.create_profile(args.name, args.description, args.from_current)
493
+
494
+ elif args.command == 'switch':
495
+ if not args.name:
496
+ print("❌ Error: Profile name required")
497
+ sys.exit(1)
498
+
499
+ manager.switch_profile(args.name)
500
+
501
+ elif args.command == 'delete':
502
+ if not args.name:
503
+ print("❌ Error: Profile name required")
504
+ sys.exit(1)
505
+
506
+ manager.delete_profile(args.name, args.force)
507
+
508
+ elif args.command == 'rename':
509
+ if not args.name or not args.name2:
510
+ print("❌ Error: Both old and new names required")
511
+ print(" Usage: python memory-profiles.py rename <old> <new>")
512
+ sys.exit(1)
513
+
514
+ manager.rename_profile(args.name, args.name2)
515
+
516
+
517
+ if __name__ == '__main__':
518
+ main()