@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.
Files changed (3) hide show
  1. package/README.md +22 -1
  2. package/dist/index.js +64 -6
  3. 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` | Sync with cloud storage (encrypted) |
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.8';
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
- const files = readdirSync(memoryDir).filter(f => f.endsWith('.md'));
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
- const files = readdirSync(memoryDir).filter(f => f.endsWith('.md') && !f.includes('INDEX'));
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 (const line of lines) {
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 content = emojiMatch[3].trim();
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({ content, category, timestamp });
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(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trlc/super-memory",
3
- "version": "1.3.8",
3
+ "version": "1.3.10",
4
4
  "description": "Super Memory CLI - AI-powered persistent memory by The Red Lobster Cartel",
5
5
  "main": "dist/index.js",
6
6
  "bin": {