@trlc/super-memory 1.1.0 → 1.2.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/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
|
@@ -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.2.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
|
}
|