@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 +75 -18
- package/dist/commands/save.js +98 -0
- package/dist/commands/status.js +116 -0
- package/dist/commands/sync.js +91 -0
- package/dist/index.js +190 -42
- package/dist/utils/crypto.js +42 -0
- package/package.json +4 -2
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 @
|
|
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
|
|
40
|
+
| `save` | Save a memory (local + encrypted cloud) |
|
|
38
41
|
| `search` | Search memories semantically |
|
|
39
|
-
| `sync` | Sync with cloud storage |
|
|
40
|
-
| `status` | Show license
|
|
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
|
-
##
|
|
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
|
-
|
|
54
|
-
|------|-------|
|
|
55
|
-
| Monthly | $19/month |
|
|
56
|
-
| Lifetime | $190 one-time |
|
|
104
|
+
## Pricing
|
|
57
105
|
|
|
58
|
-
|
|
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
|
|
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://
|
|
73
|
-
- π§ Support: support@
|
|
74
|
-
-
|
|
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
|
-
//
|
|
314
|
+
// Encrypt and sync to cloud
|
|
304
315
|
try {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
342
|
+
timestamp: Date.now(),
|
|
343
|
+
},
|
|
344
|
+
}),
|
|
314
345
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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(
|
|
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
|
-
|
|
400
|
-
|
|
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
|
|
403
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
console.log(`
|
|
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.
|
|
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.
|
|
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
|
}
|