@trlc/super-memory 1.3.8 → 1.3.10
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/README.md +22 -1
- package/dist/index.js +64 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,12 +31,33 @@ super-memory status
|
|
|
31
31
|
|
|
32
32
|
## Commands
|
|
33
33
|
|
|
34
|
+
### Sync with Smart Tracking
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
super-memory sync
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Bidirectional sync with intelligent duplicate prevention:**
|
|
41
|
+
- ✅ **Upload**: New local memories → Cloud (skips already synced)
|
|
42
|
+
- ✅ **Download**: New cloud memories → Local
|
|
43
|
+
- ✅ **Smart tracking**: Adds `<!-- synced: timestamp -->` markers to local files
|
|
44
|
+
- ✅ **No duplicates**: Already synced memories are automatically skipped
|
|
45
|
+
|
|
46
|
+
**How it works:**
|
|
47
|
+
1. First sync: Uploads all local memories → marks with `\u003c!-- synced: ... --\u003e`
|
|
48
|
+
2. Save new memories: Added to local file (no marker)
|
|
49
|
+
3. Next sync: Uploads only NEW memories → skips marked ones
|
|
50
|
+
|
|
51
|
+
**Pro tip:** Set up a cron job to run `sync` automatically every few hours.
|
|
52
|
+
|
|
53
|
+
### Command Reference
|
|
54
|
+
|
|
34
55
|
| Command | Description |
|
|
35
56
|
|---------|-------------|
|
|
36
57
|
| `init` | Initialize with license key |
|
|
37
58
|
| `save` | Save a memory (local + encrypted cloud) |
|
|
38
59
|
| `search` | Search memories semantically |
|
|
39
|
-
| `sync` |
|
|
60
|
+
| `sync` | Bidirectional sync (smart tracking, no duplicates) |
|
|
40
61
|
| `status` | Show license, local/cloud stats, sync status |
|
|
41
62
|
| `index-update` | Update MEMORY_INDEX.md + auto-curate |
|
|
42
63
|
| `curate` | Auto-curate memories to MEMORY.md |
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import { homedir } from 'os';
|
|
|
22
22
|
import { join } from 'path';
|
|
23
23
|
import readline from 'readline';
|
|
24
24
|
const CONVEX_URL = 'https://clear-lemming-473.convex.cloud';
|
|
25
|
-
const VERSION = '1.3.
|
|
25
|
+
const VERSION = '1.3.10';
|
|
26
26
|
// Paths
|
|
27
27
|
const getBaseDir = () => join(homedir(), '.openclaw', 'workspace');
|
|
28
28
|
const getMemoryDir = () => join(getBaseDir(), 'memory');
|
|
@@ -497,6 +497,8 @@ async function cmdSync(args) {
|
|
|
497
497
|
console.log('📤 Checking for local memories to upload...');
|
|
498
498
|
const localMemories = getLocalMemoriesForSync();
|
|
499
499
|
let uploadedCount = 0;
|
|
500
|
+
// Track successful uploads to mark as synced (with offset adjustment)
|
|
501
|
+
const successfulUploads = [];
|
|
500
502
|
for (const mem of localMemories) {
|
|
501
503
|
try {
|
|
502
504
|
// Generate salt for this memory
|
|
@@ -527,6 +529,11 @@ async function cmdSync(args) {
|
|
|
527
529
|
const pushResult = await pushResponse.json();
|
|
528
530
|
if (pushResult.status === 'success') {
|
|
529
531
|
uploadedCount++;
|
|
532
|
+
// Track for marking as synced
|
|
533
|
+
successfulUploads.push({
|
|
534
|
+
filePath: mem.filePath,
|
|
535
|
+
lineIndex: mem.lineIndex
|
|
536
|
+
});
|
|
530
537
|
// Log audit
|
|
531
538
|
await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
532
539
|
method: 'POST',
|
|
@@ -548,6 +555,21 @@ async function cmdSync(args) {
|
|
|
548
555
|
console.warn(`⚠️ Error uploading memory: ${err}`);
|
|
549
556
|
}
|
|
550
557
|
}
|
|
558
|
+
// Mark all successfully uploaded memories as synced
|
|
559
|
+
// Process by file to handle line index offsets correctly
|
|
560
|
+
const uploadsByFile = new Map();
|
|
561
|
+
for (const upload of successfulUploads) {
|
|
562
|
+
const existing = uploadsByFile.get(upload.filePath) || [];
|
|
563
|
+
existing.push(upload.lineIndex);
|
|
564
|
+
uploadsByFile.set(upload.filePath, existing);
|
|
565
|
+
}
|
|
566
|
+
for (const [filePath, lineIndexes] of uploadsByFile) {
|
|
567
|
+
// Sort in reverse order to prevent index shifting issues
|
|
568
|
+
const sortedIndexes = lineIndexes.sort((a, b) => b - a);
|
|
569
|
+
for (const lineIndex of sortedIndexes) {
|
|
570
|
+
markMemoryAsSynced(filePath, lineIndex);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
551
573
|
if (uploadedCount > 0) {
|
|
552
574
|
console.log(`✅ Uploaded ${uploadedCount} memories to cloud\n`);
|
|
553
575
|
}
|
|
@@ -705,7 +727,11 @@ function getLocalStats() {
|
|
|
705
727
|
const memoryDir = getMemoryDir();
|
|
706
728
|
if (!existsSync(memoryDir))
|
|
707
729
|
return stats;
|
|
708
|
-
|
|
730
|
+
// Only count daily log files (YYYY-MM-DD.md format), not index files
|
|
731
|
+
const files = readdirSync(memoryDir).filter(f => {
|
|
732
|
+
// Match YYYY-MM-DD.md pattern only
|
|
733
|
+
return /^\d{4}-\d{2}-\d{2}\.md$/.test(f);
|
|
734
|
+
});
|
|
709
735
|
for (const file of files) {
|
|
710
736
|
const content = readFileSync(join(memoryDir, file), 'utf-8');
|
|
711
737
|
const lines = content.split('\n');
|
|
@@ -731,19 +757,30 @@ function getLocalStats() {
|
|
|
731
757
|
return stats;
|
|
732
758
|
}
|
|
733
759
|
// Helper: Get local memories for sync (push to cloud)
|
|
760
|
+
// Now tracks file/line and ignores already synced memories
|
|
734
761
|
function getLocalMemoriesForSync() {
|
|
735
762
|
const memories = [];
|
|
736
763
|
const memoryDir = getMemoryDir();
|
|
737
764
|
if (!existsSync(memoryDir))
|
|
738
765
|
return memories;
|
|
739
|
-
|
|
766
|
+
// Only sync daily log files (YYYY-MM-DD.md format), not index or other files
|
|
767
|
+
const files = readdirSync(memoryDir).filter(f => {
|
|
768
|
+
return /^\d{4}-\d{2}-\d{2}\.md$/.test(f);
|
|
769
|
+
});
|
|
740
770
|
for (const file of files) {
|
|
741
771
|
const filePath = join(memoryDir, file);
|
|
742
772
|
const content = readFileSync(filePath, 'utf-8');
|
|
743
773
|
const lines = content.split('\n');
|
|
744
|
-
for (
|
|
774
|
+
for (let i = 0; i < lines.length; i++) {
|
|
775
|
+
const line = lines[i];
|
|
745
776
|
const emojiMatch = line.match(/^(🔴|🟡|🟤|🟣)\s*\[?(GOTCHA|PROBLEM|DECISION|DISCOVERY)\]?\s*(.+)$/i);
|
|
746
777
|
if (emojiMatch) {
|
|
778
|
+
// Check if next line is a sync marker
|
|
779
|
+
const nextLine = lines[i + 1] || '';
|
|
780
|
+
if (nextLine.trim().startsWith('<!-- synced:')) {
|
|
781
|
+
// Already synced, skip this memory
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
747
784
|
const emoji = emojiMatch[1];
|
|
748
785
|
const categoryMap = {
|
|
749
786
|
'🔴': 'gotcha',
|
|
@@ -752,16 +789,37 @@ function getLocalMemoriesForSync() {
|
|
|
752
789
|
'🟣': 'discovery',
|
|
753
790
|
};
|
|
754
791
|
const category = categoryMap[emoji] || 'discovery';
|
|
755
|
-
const
|
|
792
|
+
const memContent = emojiMatch[3].trim();
|
|
756
793
|
// Extract timestamp from filename (YYYY-MM-DD.md)
|
|
757
794
|
const dateStr = file.replace('.md', '');
|
|
758
795
|
const timestamp = new Date(dateStr).getTime() || Date.now();
|
|
759
|
-
memories.push({
|
|
796
|
+
memories.push({
|
|
797
|
+
content: memContent,
|
|
798
|
+
category,
|
|
799
|
+
timestamp,
|
|
800
|
+
filePath,
|
|
801
|
+
lineIndex: i
|
|
802
|
+
});
|
|
760
803
|
}
|
|
761
804
|
}
|
|
762
805
|
}
|
|
763
806
|
return memories;
|
|
764
807
|
}
|
|
808
|
+
// Helper: Mark a memory as synced by adding marker after the line
|
|
809
|
+
function markMemoryAsSynced(filePath, lineIndex) {
|
|
810
|
+
try {
|
|
811
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
812
|
+
const lines = content.split('\n');
|
|
813
|
+
// Create sync marker with ISO timestamp
|
|
814
|
+
const syncMarker = `<!-- synced: ${new Date().toISOString()} -->`;
|
|
815
|
+
// Insert marker after the memory line
|
|
816
|
+
lines.splice(lineIndex + 1, 0, syncMarker);
|
|
817
|
+
writeFileSync(filePath, lines.join('\n'));
|
|
818
|
+
}
|
|
819
|
+
catch (err) {
|
|
820
|
+
console.warn(`⚠️ Failed to mark memory as synced: ${err}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
765
823
|
// COMMAND: help
|
|
766
824
|
function cmdHelp() {
|
|
767
825
|
console.log(`
|