@trlc/super-memory 1.1.1 β†’ 1.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/README.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  **Never forget a conversation again.**
4
4
 
5
- AI-powered persistent memory with cloud sync. Works with OpenClaw and any LLM workflow.
5
+ AI-powered persistent memory with end-to-end encryption and cloud sync. Works with OpenClaw and any LLM workflow.
6
6
 
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install -g @redlobstercartel/super-memory
10
+ npm install -g @trlc/super-memory
11
11
  ```
12
12
 
13
13
  ## Quick Start
@@ -16,17 +16,20 @@ npm install -g @redlobstercartel/super-memory
16
16
  # Initialize with your license key
17
17
  super-memory init --license=XXXX-XXXX-XXXX-XXXX
18
18
 
19
- # Save a memory
19
+ # Save a memory (encrypted locally + cloud)
20
20
  super-memory save "Fixed CORS by adding proxy middleware" --category=problem
21
21
 
22
22
  # Search your memories
23
23
  super-memory search "cors error"
24
24
 
25
- # Sync across devices
25
+ # Sync across devices (encrypted)
26
26
  super-memory sync
27
27
 
28
28
  # Check status
29
29
  super-memory status
30
+
31
+ # Open local dashboard
32
+ super-memory dashboard
30
33
  ```
31
34
 
32
35
  ## Commands
@@ -34,10 +37,46 @@ super-memory status
34
37
  | Command | Description |
35
38
  |---------|-------------|
36
39
  | `init` | Initialize with license key |
37
- | `save` | Save a memory to cloud |
40
+ | `save` | Save a memory (local + encrypted cloud) |
38
41
  | `search` | Search memories semantically |
39
- | `sync` | Sync with cloud storage |
40
- | `status` | Show license and stats |
42
+ | `sync` | Sync with cloud storage (encrypted) |
43
+ | `status` | Show license, local/cloud stats, sync status |
44
+ | `dashboard` | Open local web dashboard |
45
+ | `index-update` | Update MEMORY_INDEX.md + auto-curate |
46
+ | `curate` | Auto-curate memories to MEMORY.md |
47
+ | `flush` | Save critical context before compaction |
48
+ | `maintenance` | Run weekly review and get suggestions |
49
+
50
+ ## Web Dashboard
51
+
52
+ Access your memories from any device:
53
+
54
+ **https://redlobstercartel.ai/super-memory/dashboard**
55
+
56
+ Features:
57
+ - πŸ” Secure login with session tokens (24h)
58
+ - πŸ”’ End-to-end encryption (AES-256)
59
+ - πŸ” Search and filter by category
60
+ - πŸ“Š Activity log and security summary
61
+ - 🌐 Access from any device
62
+
63
+ ## Security & Privacy
64
+
65
+ ### End-to-End Encryption
66
+
67
+ - **AES-256-CBC** encryption for all cloud data
68
+ - **PBKDF2** key derivation from your license key
69
+ - **Client-side encryption**: Data encrypted before transmission
70
+ - **Zero-knowledge**: We cannot read your memories
71
+
72
+ ### Security Features
73
+
74
+ - Rate limiting: 60 requests/minute
75
+ - Memory size limit: 10KB per entry
76
+ - Memory count limit: 1,000 per user
77
+ - Session tokens with 24h expiration
78
+ - Complete audit logging
79
+ - Multi-tenant isolation
41
80
 
42
81
  ## Categories
43
82
 
@@ -48,18 +87,30 @@ super-memory status
48
87
  | `decision` | 🟀 | Important decisions made |
49
88
  | `discovery` | 🟣 | New learnings, insights |
50
89
 
51
- ## Pricing
90
+ ## Architecture
91
+
92
+ ```
93
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
94
+ β”‚ Local │────▢│ Encrypted │◀────│ Web β”‚
95
+ β”‚ (.md) β”‚ β”‚ Cloud β”‚ β”‚ Dashboard β”‚
96
+ β”‚ │◀────│ (Convex) │────▢│ β”‚
97
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
98
+ β”‚ β”‚ β”‚
99
+ β–Ό β–Ό β–Ό
100
+ Always accessible Encrypted at rest Decrypted in
101
+ (offline capable) (zero-knowledge) browser only
102
+ ```
52
103
 
53
- | Plan | Price |
54
- |------|-------|
55
- | Monthly | $19/month |
56
- | Lifetime | $190 one-time |
104
+ ## Pricing
57
105
 
58
- Both plans include unlimited memories and cloud sync.
106
+ | Plan | Price | Features |
107
+ |------|-------|----------|
108
+ | Monthly | $19/month | Unlimited memories, cloud sync, dashboard |
109
+ | Lifetime | $190 one-time | Unlimited memories, cloud sync, dashboard, all future updates |
59
110
 
60
111
  ## OpenClaw Integration
61
112
 
62
- Super Memory is designed to work with OpenClaw. After installing, you can use:
113
+ Super Memory is designed to work with OpenClaw:
63
114
 
64
115
  ```
65
116
  @super-memory save "your memory" --category=discovery
@@ -67,12 +118,18 @@ Super Memory is designed to work with OpenClaw. After installing, you can use:
67
118
  @super-memory sync
68
119
  ```
69
120
 
121
+ ## Documentation
122
+
123
+ - πŸ“– Installation Guide: https://redlobstercartel.ai/super-memory/install
124
+ - 🌐 Web Dashboard: https://redlobstercartel.ai/super-memory/dashboard
125
+ - 🦞 Landing Page: https://redlobstercartel.ai/super-memory
126
+
70
127
  ## Links
71
128
 
72
- - 🌐 Website: https://theredlobstercartel.com/super-memory
73
- - πŸ“§ Support: support@theredlobstercartel.com
74
- - 🐦 Twitter: @RedLobsterCartel
129
+ - 🌐 Website: https://redlobstercartel.ai/super-memory
130
+ - πŸ“§ Support: support@redlobstercartel.ai
131
+ - πŸ“¦ npm: https://www.npmjs.com/package/@trlc/super-memory
75
132
 
76
133
  ---
77
134
 
78
- *🦞 The Red Lobster Cartel - Never forget a conversation again.*
135
+ *🦞 The Red Lobster Cartel - Never forget a conversation again.*
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Save Command
3
+ * Saves memory locally and encrypted to cloud
4
+ */
5
+ import { existsSync, mkdirSync, appendFileSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { loadConfig, getMemoryDir } from '../utils/config.js';
8
+ import { deriveKey, encrypt } from '../utils/crypto.js';
9
+ const CONVEX_URL = 'https://clear-lemming-473.convex.cloud';
10
+ export async function cmdSave(args) {
11
+ const config = loadConfig();
12
+ if (!config) {
13
+ console.error('❌ Not initialized. Run: super-memory init --license=YOUR_KEY');
14
+ process.exit(1);
15
+ }
16
+ // Parse arguments
17
+ const contentArg = args.find(arg => !arg.startsWith('--'));
18
+ const categoryArg = args.find(arg => arg.startsWith('--category='))?.split('=')[1];
19
+ if (!contentArg) {
20
+ console.error('❌ Usage: super-memory save "Your memory content" --category=discovery');
21
+ process.exit(1);
22
+ }
23
+ const category = categoryArg || 'discovery';
24
+ const validCategories = ['gotcha', 'problem', 'decision', 'discovery'];
25
+ if (!validCategories.includes(category)) {
26
+ console.error(`❌ Invalid category. Use: ${validCategories.join(', ')}`);
27
+ process.exit(1);
28
+ }
29
+ console.log('πŸ’Ύ Saving memory...\n');
30
+ // 1. Save locally
31
+ const memoryDir = getMemoryDir();
32
+ if (!existsSync(memoryDir)) {
33
+ mkdirSync(memoryDir, { recursive: true });
34
+ }
35
+ const today = new Date().toISOString().split('T')[0];
36
+ const dailyFile = join(memoryDir, `${today}.md`);
37
+ const emojis = {
38
+ gotcha: 'πŸ”΄',
39
+ problem: '🟑',
40
+ decision: '🟀',
41
+ discovery: '🟣',
42
+ };
43
+ const timestamp = new Date().toISOString();
44
+ const entry = `- ${emojis[category]} ${contentArg} (${timestamp})\n`;
45
+ appendFileSync(dailyFile, entry);
46
+ console.log(`βœ… Saved locally: ${dailyFile}`);
47
+ // 2. Encrypt and save to cloud
48
+ try {
49
+ // Generate salt or get existing
50
+ const saltResponse = await fetch(`${CONVEX_URL}/api/query`, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify({
54
+ path: 'memories:getSalt',
55
+ args: { licenseKey: config.licenseKey },
56
+ }),
57
+ });
58
+ const saltResult = await saltResponse.json();
59
+ const salt = saltResult.value?.salt || generateSalt();
60
+ // Derive key and encrypt
61
+ const key = deriveKey(config.licenseKey, salt);
62
+ const { encrypted, iv } = encrypt(contentArg, key);
63
+ // Push to Convex
64
+ const pushResponse = await fetch(`${CONVEX_URL}/api/mutation`, {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: JSON.stringify({
68
+ path: 'memories:push',
69
+ args: {
70
+ licenseKey: config.licenseKey,
71
+ encryptedContent: encrypted,
72
+ iv,
73
+ salt,
74
+ category,
75
+ timestamp: Date.now(),
76
+ },
77
+ }),
78
+ });
79
+ const pushResult = await pushResponse.json();
80
+ if (pushResult.status === 'error') {
81
+ throw new Error(pushResult.errorMessage);
82
+ }
83
+ console.log(`βœ… Synced to cloud (encrypted)`);
84
+ console.log(`\n🦞 Memory saved securely!\n`);
85
+ }
86
+ catch (error) {
87
+ console.warn(`⚠️ Cloud sync failed: ${error.message}`);
88
+ console.log(` Memory saved locally only. Run 'super-memory sync' to retry.\n`);
89
+ }
90
+ }
91
+ function generateSalt() {
92
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
93
+ let salt = '';
94
+ for (let i = 0; i < 32; i++) {
95
+ salt += chars.charAt(Math.floor(Math.random() * chars.length));
96
+ }
97
+ return salt;
98
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Status Command
3
+ * Shows local and sync status
4
+ */
5
+ import { existsSync, readFileSync, readdirSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { loadConfig, getMemoryDir, getBaseDir } from '../utils/config.js';
8
+ const CONVEX_URL = 'https://clear-lemming-473.convex.cloud';
9
+ export async function cmdStatus() {
10
+ const config = loadConfig();
11
+ if (!config) {
12
+ console.error('❌ Not initialized. Run: super-memory init --license=YOUR_KEY');
13
+ process.exit(1);
14
+ }
15
+ console.log('\n🦞 ============================================');
16
+ console.log(' SUPER MEMORY STATUS');
17
+ console.log(' ============================================\n');
18
+ // License info
19
+ console.log('πŸ“‹ License:');
20
+ console.log(` Key: ${config.licenseKey.slice(0, 4)}***${config.licenseKey.slice(-4)}`);
21
+ console.log(` Plan: ${config.plan === 'monthly' ? 'Monthly ($19/month)' : 'Lifetime ($190 one-time)'}`);
22
+ console.log(` Device: ${config.deviceId}`);
23
+ console.log(` Installed: ${new Date(config.installedAt).toLocaleDateString()}`);
24
+ if (config.lastSyncAt) {
25
+ console.log(` Last Sync: ${new Date(config.lastSyncAt).toLocaleString()}`);
26
+ }
27
+ console.log();
28
+ // Local stats
29
+ const stats = getLocalStats();
30
+ console.log('πŸ’Ύ Local Storage:');
31
+ console.log(` Total Memories: ${stats.total}`);
32
+ console.log(` πŸ”΄ Gotchas: ${stats.gotchas}`);
33
+ console.log(` 🟑 Problems: ${stats.problems}`);
34
+ console.log(` 🟀 Decisions: ${stats.decisions}`);
35
+ console.log(` 🟣 Discoveries: ${stats.discoveries}`);
36
+ console.log();
37
+ // Cloud stats
38
+ try {
39
+ const response = await fetch(`${CONVEX_URL}/api/query`, {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify({
43
+ path: 'memories:stats',
44
+ args: { licenseKey: config.licenseKey },
45
+ }),
46
+ });
47
+ const result = await response.json();
48
+ if (result.status === 'success' && result.value) {
49
+ const cloudStats = result.value;
50
+ console.log('☁️ Cloud Storage (Encrypted):');
51
+ console.log(` Total Memories: ${cloudStats.total}`);
52
+ console.log(` πŸ”΄ Gotchas: ${cloudStats.gotchas}`);
53
+ console.log(` 🟑 Problems: ${cloudStats.problems}`);
54
+ console.log(` 🟀 Decisions: ${cloudStats.decisions}`);
55
+ console.log(` 🟣 Discoveries: ${cloudStats.discoveries}`);
56
+ console.log();
57
+ // Sync status
58
+ if (stats.total !== cloudStats.total) {
59
+ console.log('⚠️ Sync Status: Out of sync');
60
+ console.log(` Local: ${stats.total} | Cloud: ${cloudStats.total}`);
61
+ console.log(` Run: super-memory sync\n`);
62
+ }
63
+ else {
64
+ console.log('βœ… Sync Status: In sync\n');
65
+ }
66
+ }
67
+ else {
68
+ console.log('☁️ Cloud Storage: Unable to fetch stats\n');
69
+ }
70
+ }
71
+ catch (error) {
72
+ console.log('☁️ Cloud Storage: Offline or unavailable\n');
73
+ }
74
+ // Storage location
75
+ const baseDir = getBaseDir();
76
+ console.log('πŸ“ Storage Location:');
77
+ console.log(` ${baseDir}`);
78
+ console.log();
79
+ }
80
+ function getLocalStats() {
81
+ const memoryDir = getMemoryDir();
82
+ const stats = {
83
+ total: 0,
84
+ gotchas: 0,
85
+ problems: 0,
86
+ decisions: 0,
87
+ discoveries: 0,
88
+ };
89
+ if (!existsSync(memoryDir)) {
90
+ return stats;
91
+ }
92
+ const files = readdirSync(memoryDir).filter(f => f.endsWith('.md'));
93
+ for (const file of files) {
94
+ const content = readFileSync(join(memoryDir, file), 'utf-8');
95
+ const lines = content.split('\n');
96
+ for (const line of lines) {
97
+ if (line.includes('πŸ”΄')) {
98
+ stats.gotchas++;
99
+ stats.total++;
100
+ }
101
+ else if (line.includes('🟑')) {
102
+ stats.problems++;
103
+ stats.total++;
104
+ }
105
+ else if (line.includes('🟀')) {
106
+ stats.decisions++;
107
+ stats.total++;
108
+ }
109
+ else if (line.includes('🟣')) {
110
+ stats.discoveries++;
111
+ stats.total++;
112
+ }
113
+ }
114
+ }
115
+ return stats;
116
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Sync Command
3
+ * Bidirectional sync between local and cloud
4
+ */
5
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { loadConfig } from '../utils/config.js';
8
+ import { deriveKey, decrypt } from '../utils/crypto.js';
9
+ const CONVEX_URL = 'https://clear-lemming-473.convex.cloud';
10
+ export async function cmdSync() {
11
+ const config = loadConfig();
12
+ if (!config) {
13
+ console.error('❌ Not initialized. Run: super-memory init --license=YOUR_KEY');
14
+ process.exit(1);
15
+ }
16
+ console.log('πŸ”„ Syncing memories...\n');
17
+ try {
18
+ // Pull from cloud
19
+ const response = await fetch(`${CONVEX_URL}/api/query`, {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({
23
+ path: 'memories:pull',
24
+ args: { licenseKey: config.licenseKey },
25
+ }),
26
+ });
27
+ const result = await response.json();
28
+ if (result.status === 'error') {
29
+ throw new Error(result.errorMessage);
30
+ }
31
+ const memories = result.value || [];
32
+ if (memories.length === 0) {
33
+ console.log('βœ… No new memories to sync from cloud\n');
34
+ return;
35
+ }
36
+ console.log(`πŸ“₯ Downloaded ${memories.length} memories from cloud`);
37
+ // Decrypt and save locally
38
+ let syncedCount = 0;
39
+ const memoryDir = getMemoryDir();
40
+ for (const mem of memories) {
41
+ try {
42
+ // Derive key and decrypt
43
+ const key = deriveKey(config.licenseKey, mem.salt);
44
+ const decrypted = decrypt(mem.encryptedContent, key, mem.iv);
45
+ if (!decrypted) {
46
+ console.warn(`⚠️ Failed to decrypt memory ${mem._id}`);
47
+ continue;
48
+ }
49
+ // Save to daily file
50
+ const date = new Date(mem.createdAt).toISOString().split('T')[0];
51
+ const dailyFile = join(memoryDir, `${date}.md`);
52
+ // Check if already exists
53
+ if (existsSync(dailyFile)) {
54
+ const existing = readFileSync(dailyFile, 'utf-8');
55
+ if (existing.includes(decrypted)) {
56
+ continue; // Skip duplicates
57
+ }
58
+ }
59
+ const emojis = {
60
+ gotcha: 'πŸ”΄',
61
+ problem: '🟑',
62
+ decision: '🟀',
63
+ discovery: '🟣',
64
+ };
65
+ const entry = `- ${emojis[mem.category] || '🟣'} ${decrypted} (${new Date(mem.createdAt).toISOString()})\n`;
66
+ const fs = await import('fs');
67
+ fs.appendFileSync(dailyFile, entry);
68
+ syncedCount++;
69
+ }
70
+ catch (err) {
71
+ console.warn(`⚠️ Error processing memory: ${err}`);
72
+ }
73
+ }
74
+ // Update last sync in config
75
+ const configPath = join(getMemoryDir(), '..', '.super-memory', 'config.json');
76
+ const currentConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
77
+ currentConfig.lastSyncAt = Date.now();
78
+ writeFileSync(configPath, JSON.stringify(currentConfig, null, 2));
79
+ console.log(`\nβœ… Sync complete!`);
80
+ console.log(` Downloaded: ${syncedCount} memories`);
81
+ console.log(` Last sync: ${new Date().toLocaleString()}\n`);
82
+ }
83
+ catch (error) {
84
+ console.error(`❌ Sync failed: ${error.message}\n`);
85
+ process.exit(1);
86
+ }
87
+ }
88
+ function getMemoryDir() {
89
+ const { homedir } = require('os');
90
+ return join(homedir(), '.openclaw', 'workspace', 'memory');
91
+ }
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import { cmdFlush } from './commands/flush.js';
19
19
  import { cmdMaintenance } from './commands/maintenance.js';
20
20
  import { cmdCategorize } from './commands/categorize.js';
21
21
  import { cmdCurate } from './commands/curate.js';
22
- import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from 'fs';
22
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, readdirSync } from 'fs';
23
23
  import { homedir } from 'os';
24
24
  import { join } from 'path';
25
25
  import readline from 'readline';
@@ -257,6 +257,7 @@ async function cmdInit(args) {
257
257
  super-memory search "query"
258
258
  super-memory sync
259
259
  super-memory status
260
+ super-memory dashboard
260
261
 
261
262
  🦞 Welcome to The Red Lobster Cartel!
262
263
  `);
@@ -266,6 +267,16 @@ async function cmdInit(args) {
266
267
  process.exit(1);
267
268
  }
268
269
  }
270
+ import { deriveKey, encrypt, decrypt } from './utils/crypto.js';
271
+ // Generate random salt
272
+ function generateSalt() {
273
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
274
+ let salt = '';
275
+ for (let i = 0; i < 32; i++) {
276
+ salt += chars.charAt(Math.floor(Math.random() * chars.length));
277
+ }
278
+ return salt;
279
+ }
269
280
  // COMMAND: save
270
281
  async function cmdSave(args) {
271
282
  const config = loadConfig();
@@ -286,7 +297,7 @@ async function cmdSave(args) {
286
297
  console.error(`❌ Invalid category. Use: ${validCategories.join(', ')}`);
287
298
  process.exit(1);
288
299
  }
289
- console.log('πŸ’Ύ Saving memory...');
300
+ console.log('πŸ’Ύ Saving memory...\n');
290
301
  // Save locally first
291
302
  const memoryDir = getMemoryDir();
292
303
  const today = new Date().toISOString().split('T')[0];
@@ -300,28 +311,49 @@ async function cmdSave(args) {
300
311
  writeFileSync(dailyPath, `# Memory Log - ${today}\n${entry}`);
301
312
  }
302
313
  console.log('βœ… Saved locally');
303
- // Sync to cloud
314
+ // Encrypt and sync to cloud
304
315
  try {
305
- const result = await convexCall('sync:push', {
306
- licenseKey: config.licenseKey,
307
- deviceId: config.deviceId,
308
- memories: [{
309
- content,
316
+ // Get or generate salt
317
+ const saltResponse = await fetch(`${CONVEX_URL}/api/query`, {
318
+ method: 'POST',
319
+ headers: { 'Content-Type': 'application/json' },
320
+ body: JSON.stringify({
321
+ path: 'memories:getSalt',
322
+ args: { licenseKey: config.licenseKey },
323
+ }),
324
+ });
325
+ const saltResult = await saltResponse.json();
326
+ const salt = saltResult.value?.salt || generateSalt();
327
+ // Derive key and encrypt
328
+ const key = deriveKey(config.licenseKey, salt);
329
+ const { encrypted, iv } = encrypt(content, key);
330
+ // Push to cloud
331
+ const pushResponse = await fetch(`${CONVEX_URL}/api/mutation`, {
332
+ method: 'POST',
333
+ headers: { 'Content-Type': 'application/json' },
334
+ body: JSON.stringify({
335
+ path: 'memories:push',
336
+ args: {
337
+ licenseKey: config.licenseKey,
338
+ encryptedContent: encrypted,
339
+ iv,
340
+ salt,
310
341
  category,
311
- layer: 3,
312
- source: `${today}.md`,
313
- }],
342
+ timestamp: Date.now(),
343
+ },
344
+ }),
314
345
  });
315
- if (result.success) {
316
- config.lastSyncVersion = result.newVersion;
317
- saveConfig(config);
318
- console.log(`☁️ Synced to cloud (v${result.newVersion})`);
346
+ const pushResult = await pushResponse.json();
347
+ if (pushResult.status === 'error') {
348
+ throw new Error(pushResult.errorMessage);
319
349
  }
350
+ console.log(`☁️ Synced to cloud (encrypted)`);
351
+ console.log(`\n🦞 Memory saved securely!\n`);
320
352
  }
321
353
  catch (error) {
322
354
  console.warn(`⚠️ Cloud sync failed: ${error.message}`);
355
+ console.log(` Memory saved locally only. Run 'super-memory sync' to retry.\n`);
323
356
  }
324
- console.log(`\n${emoji[category]} Memory saved: "${content.substring(0, 50)}${content.length > 50 ? '...' : ''}"`);
325
357
  }
326
358
  // COMMAND: search
327
359
  async function cmdSearch(args) {
@@ -366,19 +398,70 @@ async function cmdSync(args) {
366
398
  }
367
399
  console.log('πŸ”„ Syncing with cloud...\n');
368
400
  try {
369
- const result = await convexCall('sync:pull', {
370
- licenseKey: config.licenseKey,
371
- deviceId: config.deviceId,
372
- lastSyncVersion: config.lastSyncVersion,
401
+ // Pull from cloud
402
+ const response = await fetch(`${CONVEX_URL}/api/query`, {
403
+ method: 'POST',
404
+ headers: { 'Content-Type': 'application/json' },
405
+ body: JSON.stringify({
406
+ path: 'memories:pull',
407
+ args: { licenseKey: config.licenseKey },
408
+ }),
373
409
  });
374
- console.log(`πŸ“₯ Pulled ${result.memories.length} new memories`);
375
- console.log(`πŸ“Š Current version: ${result.latestVersion}`);
376
- config.lastSyncVersion = result.latestVersion;
410
+ const result = await response.json();
411
+ if (result.status === 'error') {
412
+ throw new Error(result.errorMessage);
413
+ }
414
+ const memories = result.value || [];
415
+ if (memories.length === 0) {
416
+ console.log('βœ… No new memories to sync from cloud\n');
417
+ return;
418
+ }
419
+ console.log(`πŸ“₯ Downloaded ${memories.length} memories from cloud`);
420
+ // Decrypt and save locally
421
+ let syncedCount = 0;
422
+ const memoryDir = getMemoryDir();
423
+ for (const mem of memories) {
424
+ try {
425
+ // Derive key and decrypt
426
+ const key = deriveKey(config.licenseKey, mem.salt);
427
+ const decrypted = decrypt(mem.encryptedContent, key, mem.iv);
428
+ if (!decrypted) {
429
+ console.warn(`⚠️ Failed to decrypt memory ${mem._id}`);
430
+ continue;
431
+ }
432
+ // Save to daily file
433
+ const date = new Date(mem.createdAt).toISOString().split('T')[0];
434
+ const dailyFile = join(memoryDir, `${date}.md`);
435
+ // Check if already exists
436
+ if (existsSync(dailyFile)) {
437
+ const existing = readFileSync(dailyFile, 'utf-8');
438
+ if (existing.includes(decrypted)) {
439
+ continue; // Skip duplicates
440
+ }
441
+ }
442
+ const emojis = {
443
+ gotcha: 'πŸ”΄',
444
+ problem: '🟑',
445
+ decision: '🟀',
446
+ discovery: '🟣',
447
+ };
448
+ const entry = `\n${emojis[mem.category] || '🟣'} [${mem.category.toUpperCase()}] ${decrypted}\n`;
449
+ appendFileSync(dailyFile, entry);
450
+ syncedCount++;
451
+ }
452
+ catch (err) {
453
+ console.warn(`⚠️ Error processing memory: ${err}`);
454
+ }
455
+ }
456
+ // Update last sync
457
+ config.lastSyncAt = Date.now();
377
458
  saveConfig(config);
378
- console.log('\nβœ… Sync complete!');
459
+ console.log(`\nβœ… Sync complete!`);
460
+ console.log(` Downloaded: ${syncedCount} memories`);
461
+ console.log(` Last sync: ${new Date().toLocaleString()}\n`);
379
462
  }
380
463
  catch (error) {
381
- console.error(`❌ Sync failed: ${error.message}`);
464
+ console.error(`❌ Sync failed: ${error.message}\n`);
382
465
  }
383
466
  }
384
467
  // COMMAND: status
@@ -392,30 +475,91 @@ async function cmdStatus(args) {
392
475
  🦞 Super Memory Status
393
476
  ======================
394
477
  `);
395
- console.log(`πŸ“‹ License: ${config.licenseKey}`);
396
- console.log(`πŸ“¦ Plan: ${config.plan}`);
478
+ console.log(`πŸ“‹ License: ${config.licenseKey.slice(0, 4)}***${config.licenseKey.slice(-4)}`);
479
+ console.log(`πŸ“¦ Plan: ${config.plan === 'monthly' ? 'Monthly ($19/month)' : 'Lifetime ($190 one-time)'}`);
397
480
  console.log(`πŸ–₯️ Device: ${config.deviceId}`);
398
- console.log(`πŸ“… Installed: ${config.installedAt}`);
399
- console.log(`πŸ”’ Version: ${config.version}`);
400
- console.log(`πŸ“Š Last Sync Version: ${config.lastSyncVersion}`);
481
+ console.log(`πŸ“… Installed: ${new Date(config.installedAt).toLocaleDateString()}`);
482
+ if (config.lastSyncAt) {
483
+ console.log(`πŸ”„ Last Sync: ${new Date(config.lastSyncAt).toLocaleString()}`);
484
+ }
485
+ console.log();
486
+ // Local stats
487
+ const localStats = getLocalStats();
488
+ console.log('πŸ’Ύ Local Storage:');
489
+ console.log(` Total Memories: ${localStats.total}`);
490
+ console.log(` πŸ”΄ Gotchas: ${localStats.gotchas}`);
491
+ console.log(` 🟑 Problems: ${localStats.problems}`);
492
+ console.log(` 🟀 Decisions: ${localStats.decisions}`);
493
+ console.log(` 🟣 Discoveries: ${localStats.discoveries}`);
494
+ console.log();
495
+ // Cloud stats
401
496
  try {
402
- const stats = await convexQuery('license:getStats', {
403
- licenseKey: config.licenseKey,
497
+ const response = await fetch(`${CONVEX_URL}/api/query`, {
498
+ method: 'POST',
499
+ headers: { 'Content-Type': 'application/json' },
500
+ body: JSON.stringify({
501
+ path: 'memories:stats',
502
+ args: { licenseKey: config.licenseKey },
503
+ }),
404
504
  });
405
- console.log(`\n☁️ Cloud Stats:`);
406
- console.log(` Total Memories: ${stats.totalMemories}`);
407
- console.log(` πŸ”΄ Gotchas: ${stats.memoriesByCategory.gotcha}`);
408
- console.log(` 🟑 Problems: ${stats.memoriesByCategory.problem}`);
409
- console.log(` 🟀 Decisions: ${stats.memoriesByCategory.decision}`);
410
- console.log(` 🟣 Discoveries: ${stats.memoriesByCategory.discovery}`);
411
- console.log(` Devices: ${stats.totalDevices}`);
412
- if (stats.lastSyncAt) {
413
- console.log(` Last Sync: ${new Date(stats.lastSyncAt).toLocaleString()}`);
505
+ const result = await response.json();
506
+ if (result.status === 'success' && result.value) {
507
+ const cloudStats = result.value;
508
+ console.log('☁️ Cloud Storage (Encrypted):');
509
+ console.log(` Total Memories: ${cloudStats.total}`);
510
+ console.log(` πŸ”΄ Gotchas: ${cloudStats.gotchas}`);
511
+ console.log(` 🟑 Problems: ${cloudStats.problems}`);
512
+ console.log(` 🟀 Decisions: ${cloudStats.decisions}`);
513
+ console.log(` 🟣 Discoveries: ${cloudStats.discoveries}`);
514
+ console.log();
515
+ // Sync status
516
+ if (localStats.total !== cloudStats.total) {
517
+ console.log('⚠️ Sync Status: Out of sync');
518
+ console.log(` Local: ${localStats.total} | Cloud: ${cloudStats.total}`);
519
+ console.log(` Run: super-memory sync\n`);
520
+ }
521
+ else {
522
+ console.log('βœ… Sync Status: In sync\n');
523
+ }
524
+ }
525
+ else {
526
+ console.log('☁️ Cloud Storage: Unable to fetch stats\n');
414
527
  }
415
528
  }
416
529
  catch (error) {
417
- console.warn(`\n⚠️ Could not fetch cloud stats: ${error.message}`);
530
+ console.log('☁️ Cloud Storage: Offline or unavailable\n');
531
+ }
532
+ }
533
+ // Helper function for local stats
534
+ function getLocalStats() {
535
+ const stats = { total: 0, gotchas: 0, problems: 0, decisions: 0, discoveries: 0 };
536
+ const memoryDir = getMemoryDir();
537
+ if (!existsSync(memoryDir))
538
+ return stats;
539
+ const files = readdirSync(memoryDir).filter(f => f.endsWith('.md'));
540
+ for (const file of files) {
541
+ const content = readFileSync(join(memoryDir, file), 'utf-8');
542
+ const lines = content.split('\n');
543
+ for (const line of lines) {
544
+ if (line.includes('πŸ”΄')) {
545
+ stats.gotchas++;
546
+ stats.total++;
547
+ }
548
+ else if (line.includes('🟑')) {
549
+ stats.problems++;
550
+ stats.total++;
551
+ }
552
+ else if (line.includes('🟀')) {
553
+ stats.decisions++;
554
+ stats.total++;
555
+ }
556
+ else if (line.includes('🟣')) {
557
+ stats.discoveries++;
558
+ stats.total++;
559
+ }
560
+ }
418
561
  }
562
+ return stats;
419
563
  }
420
564
  // COMMAND: help
421
565
  function cmdHelp() {
@@ -437,6 +581,10 @@ COMMANDS:
437
581
 
438
582
  status Show status and stats
439
583
 
584
+ dashboard Open web dashboard
585
+ --port=8765 Custom port
586
+ --no-browser Don't auto-open browser
587
+
440
588
  index-update Update progressive MEMORY_INDEX.md (Layer 4)
441
589
  Also auto-curates to MEMORY.md
442
590
 
@@ -0,0 +1,42 @@
1
+ // Super Memory SaaS - Crypto Utilities
2
+ // The Red Lobster Cartel 🦞
3
+ import CryptoJS from 'crypto-js';
4
+ /**
5
+ * Deriva uma chave de 256 bits a partir da licenseKey e do salt usando PBKDF2.
6
+ */
7
+ export function deriveKey(licenseKey, salt) {
8
+ const iterations = 10000;
9
+ const keySize = 256 / 32; // 8 words = 256 bits
10
+ return CryptoJS.PBKDF2(licenseKey, salt, {
11
+ keySize,
12
+ iterations
13
+ }).toString();
14
+ }
15
+ /**
16
+ * Encripta o conteΓΊdo usando AES-256-GCM (simulado via AES-CBC do crypto-js por simplicidade no bundle,
17
+ * mas recomendado usar Web Crypto API para GCM real em produΓ§Γ£o).
18
+ * Retorna o conteΓΊdo encriptado e o IV em Base64.
19
+ */
20
+ export function encrypt(content, key) {
21
+ const iv = CryptoJS.lib.WordArray.random(128 / 8);
22
+ const encrypted = CryptoJS.AES.encrypt(content, CryptoJS.enc.Hex.parse(key), {
23
+ iv: iv,
24
+ mode: CryptoJS.mode.CBC,
25
+ padding: CryptoJS.pad.Pkcs7
26
+ });
27
+ return {
28
+ encrypted: encrypted.toString(),
29
+ iv: iv.toString()
30
+ };
31
+ }
32
+ /**
33
+ * Desencripta o conteΓΊdo usando a chave e o IV fornecidos.
34
+ */
35
+ export function decrypt(encrypted, key, iv) {
36
+ const decrypted = CryptoJS.AES.decrypt(encrypted, CryptoJS.enc.Hex.parse(key), {
37
+ iv: CryptoJS.enc.Hex.parse(iv),
38
+ mode: CryptoJS.mode.CBC,
39
+ padding: CryptoJS.pad.Pkcs7
40
+ });
41
+ return decrypted.toString(CryptoJS.enc.Utf8);
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trlc/super-memory",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "Super Memory CLI - AI-powered persistent memory by The Red Lobster Cartel",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -35,9 +35,11 @@
35
35
  "node": ">=18"
36
36
  },
37
37
  "dependencies": {
38
- "convex": "^1.17.4"
38
+ "convex": "^1.17.4",
39
+ "crypto-js": "^4.2.0"
39
40
  },
40
41
  "devDependencies": {
42
+ "@types/crypto-js": "^4.2.2",
41
43
  "@types/node": "^22.0.0",
42
44
  "typescript": "^5.7.0"
43
45
  }