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.
- package/ATTRIBUTION.md +140 -0
- package/CHANGELOG.md +1749 -0
- package/LICENSE +21 -0
- package/README.md +600 -0
- package/bin/aider-smart +72 -0
- package/bin/slm +202 -0
- package/bin/slm-npm +73 -0
- package/bin/slm.bat +195 -0
- package/bin/slm.cmd +10 -0
- package/bin/superlocalmemoryv2:list +3 -0
- package/bin/superlocalmemoryv2:profile +3 -0
- package/bin/superlocalmemoryv2:recall +3 -0
- package/bin/superlocalmemoryv2:remember +3 -0
- package/bin/superlocalmemoryv2:reset +3 -0
- package/bin/superlocalmemoryv2:status +3 -0
- package/completions/slm.bash +58 -0
- package/completions/slm.zsh +76 -0
- package/configs/antigravity-mcp.json +13 -0
- package/configs/chatgpt-desktop-mcp.json +7 -0
- package/configs/claude-desktop-mcp.json +15 -0
- package/configs/codex-mcp.toml +13 -0
- package/configs/cody-commands.json +29 -0
- package/configs/continue-mcp.yaml +14 -0
- package/configs/continue-skills.yaml +26 -0
- package/configs/cursor-mcp.json +15 -0
- package/configs/gemini-cli-mcp.json +11 -0
- package/configs/jetbrains-mcp.json +11 -0
- package/configs/opencode-mcp.json +12 -0
- package/configs/perplexity-mcp.json +9 -0
- package/configs/vscode-copilot-mcp.json +12 -0
- package/configs/windsurf-mcp.json +16 -0
- package/configs/zed-mcp.json +12 -0
- package/docs/ARCHITECTURE.md +877 -0
- package/docs/CLI-COMMANDS-REFERENCE.md +425 -0
- package/docs/COMPETITIVE-ANALYSIS.md +210 -0
- package/docs/COMPRESSION-README.md +390 -0
- package/docs/GRAPH-ENGINE.md +503 -0
- package/docs/MCP-MANUAL-SETUP.md +720 -0
- package/docs/MCP-TROUBLESHOOTING.md +787 -0
- package/docs/PATTERN-LEARNING.md +363 -0
- package/docs/PROFILES-GUIDE.md +453 -0
- package/docs/RESET-GUIDE.md +353 -0
- package/docs/SEARCH-ENGINE-V2.2.0.md +748 -0
- package/docs/SEARCH-INTEGRATION-GUIDE.md +502 -0
- package/docs/UI-SERVER.md +254 -0
- package/docs/UNIVERSAL-INTEGRATION.md +432 -0
- package/docs/V2.2.0-OPTIONAL-SEARCH.md +666 -0
- package/docs/WINDOWS-INSTALL-README.txt +34 -0
- package/docs/WINDOWS-POST-INSTALL.txt +45 -0
- package/docs/example_graph_usage.py +148 -0
- package/hooks/memory-list-skill.js +130 -0
- package/hooks/memory-profile-skill.js +284 -0
- package/hooks/memory-recall-skill.js +109 -0
- package/hooks/memory-remember-skill.js +127 -0
- package/hooks/memory-reset-skill.js +274 -0
- package/install-skills.sh +436 -0
- package/install.ps1 +417 -0
- package/install.sh +755 -0
- package/mcp_server.py +585 -0
- package/package.json +94 -0
- package/requirements-core.txt +24 -0
- package/requirements.txt +10 -0
- package/scripts/postinstall.js +126 -0
- package/scripts/preuninstall.js +57 -0
- package/skills/slm-build-graph/SKILL.md +423 -0
- package/skills/slm-list-recent/SKILL.md +348 -0
- package/skills/slm-recall/SKILL.md +325 -0
- package/skills/slm-remember/SKILL.md +194 -0
- package/skills/slm-status/SKILL.md +363 -0
- package/skills/slm-switch-profile/SKILL.md +442 -0
- package/src/__pycache__/cache_manager.cpython-312.pyc +0 -0
- package/src/__pycache__/embedding_engine.cpython-312.pyc +0 -0
- package/src/__pycache__/graph_engine.cpython-312.pyc +0 -0
- package/src/__pycache__/hnsw_index.cpython-312.pyc +0 -0
- package/src/__pycache__/hybrid_search.cpython-312.pyc +0 -0
- package/src/__pycache__/memory-profiles.cpython-312.pyc +0 -0
- package/src/__pycache__/memory-reset.cpython-312.pyc +0 -0
- package/src/__pycache__/memory_compression.cpython-312.pyc +0 -0
- package/src/__pycache__/memory_store_v2.cpython-312.pyc +0 -0
- package/src/__pycache__/migrate_v1_to_v2.cpython-312.pyc +0 -0
- package/src/__pycache__/pattern_learner.cpython-312.pyc +0 -0
- package/src/__pycache__/query_optimizer.cpython-312.pyc +0 -0
- package/src/__pycache__/search_engine_v2.cpython-312.pyc +0 -0
- package/src/__pycache__/setup_validator.cpython-312.pyc +0 -0
- package/src/__pycache__/tree_manager.cpython-312.pyc +0 -0
- package/src/cache_manager.py +520 -0
- package/src/embedding_engine.py +671 -0
- package/src/graph_engine.py +970 -0
- package/src/hnsw_index.py +626 -0
- package/src/hybrid_search.py +693 -0
- package/src/memory-profiles.py +518 -0
- package/src/memory-reset.py +485 -0
- package/src/memory_compression.py +999 -0
- package/src/memory_store_v2.py +1088 -0
- package/src/migrate_v1_to_v2.py +638 -0
- package/src/pattern_learner.py +898 -0
- package/src/query_optimizer.py +513 -0
- package/src/search_engine_v2.py +403 -0
- package/src/setup_validator.py +479 -0
- 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()
|