superlocalmemory 2.3.6 → 2.4.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/CHANGELOG.md +65 -0
- package/README.md +25 -0
- package/hooks/memory-list-skill.js +13 -4
- package/hooks/memory-profile-skill.js +7 -18
- package/hooks/memory-recall-skill.js +6 -1
- package/mcp_server.py +74 -12
- package/package.json +1 -1
- package/src/__pycache__/auto_backup.cpython-312.pyc +0 -0
- package/src/__pycache__/memory_store_v2.cpython-312.pyc +0 -0
- package/src/__pycache__/pattern_learner.cpython-312.pyc +0 -0
- package/src/auto_backup.py +424 -0
- package/src/graph_engine.py +126 -39
- package/src/memory-profiles.py +321 -243
- package/src/memory_store_v2.py +131 -43
- package/src/pattern_learner.py +126 -44
- package/ui/app.js +526 -17
- package/ui/index.html +182 -1
- package/ui_server.py +340 -43
package/src/memory_store_v2.py
CHANGED
|
@@ -53,21 +53,41 @@ class MemoryStoreV2:
|
|
|
53
53
|
- Backward compatible with V1 API
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
|
-
def __init__(self, db_path: Optional[Path] = None):
|
|
56
|
+
def __init__(self, db_path: Optional[Path] = None, profile: Optional[str] = None):
|
|
57
57
|
"""
|
|
58
58
|
Initialize MemoryStore V2.
|
|
59
59
|
|
|
60
60
|
Args:
|
|
61
61
|
db_path: Optional custom database path (defaults to ~/.claude-memory/memory.db)
|
|
62
|
+
profile: Optional profile override. If None, reads from profiles.json config.
|
|
62
63
|
"""
|
|
63
64
|
self.db_path = db_path or DB_PATH
|
|
64
65
|
self.vectors_path = VECTORS_PATH
|
|
66
|
+
self._profile_override = profile
|
|
65
67
|
self._init_db()
|
|
66
68
|
self.vectorizer = None
|
|
67
69
|
self.vectors = None
|
|
68
70
|
self.memory_ids = []
|
|
69
71
|
self._load_vectors()
|
|
70
72
|
|
|
73
|
+
def _get_active_profile(self) -> str:
|
|
74
|
+
"""
|
|
75
|
+
Get the currently active profile name.
|
|
76
|
+
Reads from profiles.json config file. Falls back to 'default'.
|
|
77
|
+
"""
|
|
78
|
+
if self._profile_override:
|
|
79
|
+
return self._profile_override
|
|
80
|
+
|
|
81
|
+
config_file = MEMORY_DIR / "profiles.json"
|
|
82
|
+
if config_file.exists():
|
|
83
|
+
try:
|
|
84
|
+
with open(config_file, 'r') as f:
|
|
85
|
+
config = json.load(f)
|
|
86
|
+
return config.get('active_profile', 'default')
|
|
87
|
+
except (json.JSONDecodeError, IOError):
|
|
88
|
+
pass
|
|
89
|
+
return 'default'
|
|
90
|
+
|
|
71
91
|
def _init_db(self):
|
|
72
92
|
"""Initialize SQLite database with V2 schema extensions."""
|
|
73
93
|
conn = sqlite3.connect(self.db_path)
|
|
@@ -129,7 +149,8 @@ class MemoryStoreV2:
|
|
|
129
149
|
'last_accessed': 'TIMESTAMP',
|
|
130
150
|
'access_count': 'INTEGER DEFAULT 0',
|
|
131
151
|
'content_hash': 'TEXT',
|
|
132
|
-
'cluster_id': 'INTEGER'
|
|
152
|
+
'cluster_id': 'INTEGER',
|
|
153
|
+
'profile': 'TEXT DEFAULT "default"'
|
|
133
154
|
}
|
|
134
155
|
|
|
135
156
|
for col_name, col_type in v2_columns.items():
|
|
@@ -190,7 +211,8 @@ class MemoryStoreV2:
|
|
|
190
211
|
('idx_tree_path', 'tree_path'),
|
|
191
212
|
('idx_cluster', 'cluster_id'),
|
|
192
213
|
('idx_last_accessed', 'last_accessed'),
|
|
193
|
-
('idx_parent_id', 'parent_id')
|
|
214
|
+
('idx_parent_id', 'parent_id'),
|
|
215
|
+
('idx_profile', 'profile')
|
|
194
216
|
]
|
|
195
217
|
|
|
196
218
|
for idx_name, col_name in v2_indexes:
|
|
@@ -303,6 +325,7 @@ class MemoryStoreV2:
|
|
|
303
325
|
importance = max(1, min(10, importance)) # Clamp to valid range
|
|
304
326
|
|
|
305
327
|
content_hash = self._content_hash(content)
|
|
328
|
+
active_profile = self._get_active_profile()
|
|
306
329
|
|
|
307
330
|
conn = sqlite3.connect(self.db_path)
|
|
308
331
|
cursor = conn.cursor()
|
|
@@ -316,9 +339,9 @@ class MemoryStoreV2:
|
|
|
316
339
|
content, summary, project_path, project_name, tags, category,
|
|
317
340
|
parent_id, tree_path, depth,
|
|
318
341
|
memory_type, importance, content_hash,
|
|
319
|
-
last_accessed, access_count
|
|
342
|
+
last_accessed, access_count, profile
|
|
320
343
|
)
|
|
321
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
344
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
322
345
|
''', (
|
|
323
346
|
content,
|
|
324
347
|
summary,
|
|
@@ -333,7 +356,8 @@ class MemoryStoreV2:
|
|
|
333
356
|
importance,
|
|
334
357
|
content_hash,
|
|
335
358
|
datetime.now().isoformat(),
|
|
336
|
-
0
|
|
359
|
+
0,
|
|
360
|
+
active_profile
|
|
337
361
|
))
|
|
338
362
|
memory_id = cursor.lastrowid
|
|
339
363
|
|
|
@@ -350,6 +374,14 @@ class MemoryStoreV2:
|
|
|
350
374
|
# Rebuild vectors after adding
|
|
351
375
|
self._rebuild_vectors()
|
|
352
376
|
|
|
377
|
+
# Auto-backup check (non-blocking)
|
|
378
|
+
try:
|
|
379
|
+
from auto_backup import AutoBackup
|
|
380
|
+
backup = AutoBackup()
|
|
381
|
+
backup.check_and_backup()
|
|
382
|
+
except Exception:
|
|
383
|
+
pass # Backup failure must never break memory operations
|
|
384
|
+
|
|
353
385
|
return memory_id
|
|
354
386
|
|
|
355
387
|
except sqlite3.IntegrityError:
|
|
@@ -410,6 +442,7 @@ class MemoryStoreV2:
|
|
|
410
442
|
List of memory dictionaries with scores
|
|
411
443
|
"""
|
|
412
444
|
results = []
|
|
445
|
+
active_profile = self._get_active_profile()
|
|
413
446
|
|
|
414
447
|
# Method 1: TF-IDF semantic search
|
|
415
448
|
if SKLEARN_AVAILABLE and self.vectorizer is not None and self.vectors is not None:
|
|
@@ -432,8 +465,8 @@ class MemoryStoreV2:
|
|
|
432
465
|
category, parent_id, tree_path, depth,
|
|
433
466
|
memory_type, importance, created_at, cluster_id,
|
|
434
467
|
last_accessed, access_count
|
|
435
|
-
FROM memories WHERE id = ?
|
|
436
|
-
''', (memory_id,))
|
|
468
|
+
FROM memories WHERE id = ? AND profile = ?
|
|
469
|
+
''', (memory_id, active_profile))
|
|
437
470
|
row = cursor.fetchone()
|
|
438
471
|
|
|
439
472
|
if row and self._apply_filters(row, project_path, memory_type,
|
|
@@ -460,10 +493,10 @@ class MemoryStoreV2:
|
|
|
460
493
|
m.last_accessed, m.access_count
|
|
461
494
|
FROM memories m
|
|
462
495
|
JOIN memories_fts fts ON m.id = fts.rowid
|
|
463
|
-
WHERE memories_fts MATCH ?
|
|
496
|
+
WHERE memories_fts MATCH ? AND m.profile = ?
|
|
464
497
|
ORDER BY rank
|
|
465
498
|
LIMIT ?
|
|
466
|
-
''', (fts_query, limit))
|
|
499
|
+
''', (fts_query, active_profile, limit))
|
|
467
500
|
|
|
468
501
|
existing_ids = {r['id'] for r in results}
|
|
469
502
|
|
|
@@ -570,6 +603,7 @@ class MemoryStoreV2:
|
|
|
570
603
|
Returns:
|
|
571
604
|
List of memories with tree structure
|
|
572
605
|
"""
|
|
606
|
+
active_profile = self._get_active_profile()
|
|
573
607
|
conn = sqlite3.connect(self.db_path)
|
|
574
608
|
cursor = conn.cursor()
|
|
575
609
|
|
|
@@ -580,9 +614,9 @@ class MemoryStoreV2:
|
|
|
580
614
|
category, parent_id, tree_path, depth, memory_type, importance,
|
|
581
615
|
created_at, cluster_id, last_accessed, access_count
|
|
582
616
|
FROM memories
|
|
583
|
-
WHERE parent_id IS NULL AND depth <= ?
|
|
617
|
+
WHERE parent_id IS NULL AND depth <= ? AND profile = ?
|
|
584
618
|
ORDER BY tree_path
|
|
585
|
-
''', (max_depth,))
|
|
619
|
+
''', (max_depth, active_profile))
|
|
586
620
|
else:
|
|
587
621
|
# Get subtree under specific parent
|
|
588
622
|
cursor.execute('''
|
|
@@ -649,6 +683,7 @@ class MemoryStoreV2:
|
|
|
649
683
|
Returns:
|
|
650
684
|
List of memories in the cluster
|
|
651
685
|
"""
|
|
686
|
+
active_profile = self._get_active_profile()
|
|
652
687
|
conn = sqlite3.connect(self.db_path)
|
|
653
688
|
cursor = conn.cursor()
|
|
654
689
|
|
|
@@ -657,9 +692,9 @@ class MemoryStoreV2:
|
|
|
657
692
|
category, parent_id, tree_path, depth, memory_type, importance,
|
|
658
693
|
created_at, cluster_id, last_accessed, access_count
|
|
659
694
|
FROM memories
|
|
660
|
-
WHERE cluster_id = ?
|
|
695
|
+
WHERE cluster_id = ? AND profile = ?
|
|
661
696
|
ORDER BY importance DESC, created_at DESC
|
|
662
|
-
''', (cluster_id,))
|
|
697
|
+
''', (cluster_id, active_profile))
|
|
663
698
|
|
|
664
699
|
results = []
|
|
665
700
|
for row in cursor.fetchall():
|
|
@@ -675,10 +710,11 @@ class MemoryStoreV2:
|
|
|
675
710
|
self._rebuild_vectors()
|
|
676
711
|
|
|
677
712
|
def _rebuild_vectors(self):
|
|
678
|
-
"""Rebuild TF-IDF vectors from
|
|
713
|
+
"""Rebuild TF-IDF vectors from active profile memories (V1 compatible, backward compatible)."""
|
|
679
714
|
if not SKLEARN_AVAILABLE:
|
|
680
715
|
return
|
|
681
716
|
|
|
717
|
+
active_profile = self._get_active_profile()
|
|
682
718
|
conn = sqlite3.connect(self.db_path)
|
|
683
719
|
cursor = conn.cursor()
|
|
684
720
|
|
|
@@ -686,9 +722,13 @@ class MemoryStoreV2:
|
|
|
686
722
|
cursor.execute("PRAGMA table_info(memories)")
|
|
687
723
|
columns = {row[1] for row in cursor.fetchall()}
|
|
688
724
|
|
|
689
|
-
# Build SELECT query based on available columns
|
|
725
|
+
# Build SELECT query based on available columns, filtered by profile
|
|
726
|
+
has_profile = 'profile' in columns
|
|
690
727
|
if 'summary' in columns:
|
|
691
|
-
|
|
728
|
+
if has_profile:
|
|
729
|
+
cursor.execute('SELECT id, content, summary FROM memories WHERE profile = ?', (active_profile,))
|
|
730
|
+
else:
|
|
731
|
+
cursor.execute('SELECT id, content, summary FROM memories')
|
|
692
732
|
rows = cursor.fetchall()
|
|
693
733
|
texts = [f"{row[1]} {row[2] or ''}" for row in rows]
|
|
694
734
|
else:
|
|
@@ -720,7 +760,8 @@ class MemoryStoreV2:
|
|
|
720
760
|
json.dump(self.memory_ids, f)
|
|
721
761
|
|
|
722
762
|
def get_recent(self, limit: int = 10, project_path: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
723
|
-
"""Get most recent memories (V1 compatible)."""
|
|
763
|
+
"""Get most recent memories (V1 compatible, profile-aware)."""
|
|
764
|
+
active_profile = self._get_active_profile()
|
|
724
765
|
conn = sqlite3.connect(self.db_path)
|
|
725
766
|
cursor = conn.cursor()
|
|
726
767
|
|
|
@@ -730,19 +771,20 @@ class MemoryStoreV2:
|
|
|
730
771
|
category, parent_id, tree_path, depth, memory_type, importance,
|
|
731
772
|
created_at, cluster_id, last_accessed, access_count
|
|
732
773
|
FROM memories
|
|
733
|
-
WHERE project_path = ?
|
|
774
|
+
WHERE project_path = ? AND profile = ?
|
|
734
775
|
ORDER BY created_at DESC
|
|
735
776
|
LIMIT ?
|
|
736
|
-
''', (project_path, limit))
|
|
777
|
+
''', (project_path, active_profile, limit))
|
|
737
778
|
else:
|
|
738
779
|
cursor.execute('''
|
|
739
780
|
SELECT id, content, summary, project_path, project_name, tags,
|
|
740
781
|
category, parent_id, tree_path, depth, memory_type, importance,
|
|
741
782
|
created_at, cluster_id, last_accessed, access_count
|
|
742
783
|
FROM memories
|
|
784
|
+
WHERE profile = ?
|
|
743
785
|
ORDER BY created_at DESC
|
|
744
786
|
LIMIT ?
|
|
745
|
-
''', (limit
|
|
787
|
+
''', (active_profile, limit))
|
|
746
788
|
|
|
747
789
|
results = []
|
|
748
790
|
for row in cursor.fetchall():
|
|
@@ -789,7 +831,8 @@ class MemoryStoreV2:
|
|
|
789
831
|
return deleted
|
|
790
832
|
|
|
791
833
|
def list_all(self, limit: int = 50) -> List[Dict[str, Any]]:
|
|
792
|
-
"""List all memories with short previews (V1 compatible)."""
|
|
834
|
+
"""List all memories with short previews (V1 compatible, profile-aware)."""
|
|
835
|
+
active_profile = self._get_active_profile()
|
|
793
836
|
conn = sqlite3.connect(self.db_path)
|
|
794
837
|
cursor = conn.cursor()
|
|
795
838
|
|
|
@@ -798,9 +841,10 @@ class MemoryStoreV2:
|
|
|
798
841
|
category, parent_id, tree_path, depth, memory_type, importance,
|
|
799
842
|
created_at, cluster_id, last_accessed, access_count
|
|
800
843
|
FROM memories
|
|
844
|
+
WHERE profile = ?
|
|
801
845
|
ORDER BY created_at DESC
|
|
802
846
|
LIMIT ?
|
|
803
|
-
''', (limit
|
|
847
|
+
''', (active_profile, limit))
|
|
804
848
|
|
|
805
849
|
results = []
|
|
806
850
|
for row in cursor.fetchall():
|
|
@@ -817,35 +861,42 @@ class MemoryStoreV2:
|
|
|
817
861
|
return results
|
|
818
862
|
|
|
819
863
|
def get_stats(self) -> Dict[str, Any]:
|
|
820
|
-
"""Get memory store statistics (V1 compatible with V2 extensions)."""
|
|
864
|
+
"""Get memory store statistics (V1 compatible with V2 extensions, profile-aware)."""
|
|
865
|
+
active_profile = self._get_active_profile()
|
|
821
866
|
conn = sqlite3.connect(self.db_path)
|
|
822
867
|
cursor = conn.cursor()
|
|
823
868
|
|
|
824
|
-
cursor.execute('SELECT COUNT(*) FROM memories')
|
|
869
|
+
cursor.execute('SELECT COUNT(*) FROM memories WHERE profile = ?', (active_profile,))
|
|
825
870
|
total_memories = cursor.fetchone()[0]
|
|
826
871
|
|
|
827
|
-
cursor.execute('SELECT COUNT(DISTINCT project_path) FROM memories WHERE project_path IS NOT NULL')
|
|
872
|
+
cursor.execute('SELECT COUNT(DISTINCT project_path) FROM memories WHERE project_path IS NOT NULL AND profile = ?', (active_profile,))
|
|
828
873
|
total_projects = cursor.fetchone()[0]
|
|
829
874
|
|
|
830
|
-
cursor.execute('SELECT memory_type, COUNT(*) FROM memories GROUP BY memory_type')
|
|
875
|
+
cursor.execute('SELECT memory_type, COUNT(*) FROM memories WHERE profile = ? GROUP BY memory_type', (active_profile,))
|
|
831
876
|
by_type = dict(cursor.fetchall())
|
|
832
877
|
|
|
833
|
-
cursor.execute('SELECT category, COUNT(*) FROM memories WHERE category IS NOT NULL GROUP BY category')
|
|
878
|
+
cursor.execute('SELECT category, COUNT(*) FROM memories WHERE category IS NOT NULL AND profile = ? GROUP BY category', (active_profile,))
|
|
834
879
|
by_category = dict(cursor.fetchall())
|
|
835
880
|
|
|
836
|
-
cursor.execute('SELECT MIN(created_at), MAX(created_at) FROM memories')
|
|
881
|
+
cursor.execute('SELECT MIN(created_at), MAX(created_at) FROM memories WHERE profile = ?', (active_profile,))
|
|
837
882
|
date_range = cursor.fetchone()
|
|
838
883
|
|
|
839
|
-
cursor.execute('SELECT COUNT(DISTINCT cluster_id) FROM memories WHERE cluster_id IS NOT NULL')
|
|
884
|
+
cursor.execute('SELECT COUNT(DISTINCT cluster_id) FROM memories WHERE cluster_id IS NOT NULL AND profile = ?', (active_profile,))
|
|
840
885
|
total_clusters = cursor.fetchone()[0]
|
|
841
886
|
|
|
842
|
-
cursor.execute('SELECT MAX(depth) FROM memories')
|
|
887
|
+
cursor.execute('SELECT MAX(depth) FROM memories WHERE profile = ?', (active_profile,))
|
|
843
888
|
max_depth = cursor.fetchone()[0] or 0
|
|
844
889
|
|
|
890
|
+
# Total across all profiles
|
|
891
|
+
cursor.execute('SELECT COUNT(*) FROM memories')
|
|
892
|
+
total_all_profiles = cursor.fetchone()[0]
|
|
893
|
+
|
|
845
894
|
conn.close()
|
|
846
895
|
|
|
847
896
|
return {
|
|
848
897
|
'total_memories': total_memories,
|
|
898
|
+
'total_all_profiles': total_all_profiles,
|
|
899
|
+
'active_profile': active_profile,
|
|
849
900
|
'total_projects': total_projects,
|
|
850
901
|
'total_clusters': total_clusters,
|
|
851
902
|
'max_tree_depth': max_depth,
|
|
@@ -915,6 +966,25 @@ class MemoryStoreV2:
|
|
|
915
966
|
return ''.join(output)
|
|
916
967
|
|
|
917
968
|
|
|
969
|
+
def format_content(content: str, full: bool = False, threshold: int = 5000, preview_len: int = 2000) -> str:
|
|
970
|
+
"""
|
|
971
|
+
Smart content formatting with optional truncation.
|
|
972
|
+
|
|
973
|
+
Args:
|
|
974
|
+
content: Content to format
|
|
975
|
+
full: If True, always show full content
|
|
976
|
+
threshold: Max length before truncation (default 5000)
|
|
977
|
+
preview_len: Preview length when truncating (default 2000)
|
|
978
|
+
|
|
979
|
+
Returns:
|
|
980
|
+
Formatted content string
|
|
981
|
+
"""
|
|
982
|
+
if full or len(content) < threshold:
|
|
983
|
+
return content
|
|
984
|
+
else:
|
|
985
|
+
return f"{content[:preview_len]}..."
|
|
986
|
+
|
|
987
|
+
|
|
918
988
|
# CLI interface (V1 compatible + V2 extensions)
|
|
919
989
|
if __name__ == "__main__":
|
|
920
990
|
import sys
|
|
@@ -925,16 +995,18 @@ if __name__ == "__main__":
|
|
|
925
995
|
print("MemoryStore V2 CLI")
|
|
926
996
|
print("\nV1 Compatible Commands:")
|
|
927
997
|
print(" python memory_store_v2.py add <content> [--project <path>] [--tags tag1,tag2]")
|
|
928
|
-
print(" python memory_store_v2.py search <query>")
|
|
929
|
-
print(" python memory_store_v2.py list [limit]")
|
|
998
|
+
print(" python memory_store_v2.py search <query> [--full]")
|
|
999
|
+
print(" python memory_store_v2.py list [limit] [--full]")
|
|
930
1000
|
print(" python memory_store_v2.py get <id>")
|
|
931
|
-
print(" python memory_store_v2.py recent [limit]")
|
|
1001
|
+
print(" python memory_store_v2.py recent [limit] [--full]")
|
|
932
1002
|
print(" python memory_store_v2.py stats")
|
|
933
1003
|
print(" python memory_store_v2.py context <query>")
|
|
934
1004
|
print(" python memory_store_v2.py delete <id>")
|
|
935
1005
|
print("\nV2 Extensions:")
|
|
936
1006
|
print(" python memory_store_v2.py tree [parent_id]")
|
|
937
|
-
print(" python memory_store_v2.py cluster <cluster_id>")
|
|
1007
|
+
print(" python memory_store_v2.py cluster <cluster_id> [--full]")
|
|
1008
|
+
print("\nOptions:")
|
|
1009
|
+
print(" --full Show complete content (default: smart truncation at 5000 chars)")
|
|
938
1010
|
sys.exit(0)
|
|
939
1011
|
|
|
940
1012
|
command = sys.argv[1]
|
|
@@ -954,6 +1026,7 @@ if __name__ == "__main__":
|
|
|
954
1026
|
|
|
955
1027
|
elif command == "cluster" and len(sys.argv) >= 3:
|
|
956
1028
|
cluster_id = int(sys.argv[2])
|
|
1029
|
+
show_full = '--full' in sys.argv
|
|
957
1030
|
results = store.get_by_cluster(cluster_id)
|
|
958
1031
|
|
|
959
1032
|
if not results:
|
|
@@ -962,7 +1035,7 @@ if __name__ == "__main__":
|
|
|
962
1035
|
print(f"Cluster {cluster_id} - {len(results)} memories:")
|
|
963
1036
|
for r in results:
|
|
964
1037
|
print(f"\n[{r['id']}] Importance: {r['importance']}")
|
|
965
|
-
print(f" {r['content']
|
|
1038
|
+
print(f" {format_content(r['content'], full=show_full)}")
|
|
966
1039
|
|
|
967
1040
|
elif command == "stats":
|
|
968
1041
|
stats = store.get_stats()
|
|
@@ -996,10 +1069,11 @@ if __name__ == "__main__":
|
|
|
996
1069
|
elif command == "search":
|
|
997
1070
|
if len(sys.argv) < 3:
|
|
998
1071
|
print("Error: Search query required")
|
|
999
|
-
print("Usage: python memory_store_v2.py search <query>")
|
|
1072
|
+
print("Usage: python memory_store_v2.py search <query> [--full]")
|
|
1000
1073
|
sys.exit(1)
|
|
1001
1074
|
|
|
1002
1075
|
query = sys.argv[2]
|
|
1076
|
+
show_full = '--full' in sys.argv
|
|
1003
1077
|
results = store.search(query, limit=5)
|
|
1004
1078
|
|
|
1005
1079
|
if not results:
|
|
@@ -1011,11 +1085,18 @@ if __name__ == "__main__":
|
|
|
1011
1085
|
print(f"Project: {r['project_name']}")
|
|
1012
1086
|
if r.get('tags'):
|
|
1013
1087
|
print(f"Tags: {', '.join(r['tags'])}")
|
|
1014
|
-
print(f"Content: {r['content']
|
|
1088
|
+
print(f"Content: {format_content(r['content'], full=show_full)}")
|
|
1015
1089
|
print(f"Created: {r['created_at']}")
|
|
1016
1090
|
|
|
1017
1091
|
elif command == "recent":
|
|
1018
|
-
|
|
1092
|
+
show_full = '--full' in sys.argv
|
|
1093
|
+
# Parse limit (skip --full flag)
|
|
1094
|
+
limit = 10
|
|
1095
|
+
for i, arg in enumerate(sys.argv[2:], start=2):
|
|
1096
|
+
if arg != '--full' and arg.isdigit():
|
|
1097
|
+
limit = int(arg)
|
|
1098
|
+
break
|
|
1099
|
+
|
|
1019
1100
|
results = store.get_recent(limit)
|
|
1020
1101
|
|
|
1021
1102
|
if not results:
|
|
@@ -1027,17 +1108,24 @@ if __name__ == "__main__":
|
|
|
1027
1108
|
print(f"Project: {r['project_name']}")
|
|
1028
1109
|
if r.get('tags'):
|
|
1029
1110
|
print(f"Tags: {', '.join(r['tags'])}")
|
|
1030
|
-
print(f"Content: {r['content']
|
|
1111
|
+
print(f"Content: {format_content(r['content'], full=show_full)}")
|
|
1031
1112
|
|
|
1032
1113
|
elif command == "list":
|
|
1033
|
-
|
|
1114
|
+
show_full = '--full' in sys.argv
|
|
1115
|
+
# Parse limit (skip --full flag)
|
|
1116
|
+
limit = 10
|
|
1117
|
+
for i, arg in enumerate(sys.argv[2:], start=2):
|
|
1118
|
+
if arg != '--full' and arg.isdigit():
|
|
1119
|
+
limit = int(arg)
|
|
1120
|
+
break
|
|
1121
|
+
|
|
1034
1122
|
results = store.get_recent(limit)
|
|
1035
1123
|
|
|
1036
1124
|
if not results:
|
|
1037
1125
|
print("No memories found.")
|
|
1038
1126
|
else:
|
|
1039
1127
|
for r in results:
|
|
1040
|
-
print(f"[{r['id']}] {r['content']
|
|
1128
|
+
print(f"[{r['id']}] {format_content(r['content'], full=show_full)}")
|
|
1041
1129
|
|
|
1042
1130
|
elif command == "get":
|
|
1043
1131
|
if len(sys.argv) < 3:
|
|
@@ -1046,7 +1134,7 @@ if __name__ == "__main__":
|
|
|
1046
1134
|
sys.exit(1)
|
|
1047
1135
|
|
|
1048
1136
|
mem_id = int(sys.argv[2])
|
|
1049
|
-
memory = store.
|
|
1137
|
+
memory = store.get_by_id(mem_id)
|
|
1050
1138
|
|
|
1051
1139
|
if not memory:
|
|
1052
1140
|
print(f"Memory {mem_id} not found.")
|