memshell 0.2.0 → 0.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/bin/mem.js +109 -34
- package/bin/memshell.js +177 -0
- package/package.json +4 -3
- package/server.js +50 -13
- package/src/index.js +287 -47
package/bin/mem.js
CHANGED
|
@@ -1,89 +1,163 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
4
6
|
const mem = require('../src/index');
|
|
5
7
|
|
|
6
8
|
const args = process.argv.slice(2);
|
|
7
9
|
const cmd = args[0];
|
|
8
|
-
const rest = args.slice(1).join(' ');
|
|
9
10
|
|
|
10
11
|
const HELP = `
|
|
11
|
-
|
|
12
|
+
\x1b[1mmem.sh\x1b[0m — persistent memory for AI agents
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
\x1b[36mUsage:\x1b[0m
|
|
15
|
+
memshell set <text> Store a memory
|
|
16
|
+
memshell recall <query> Semantic recall
|
|
17
|
+
memshell list List all memories
|
|
18
|
+
memshell forget <id> Delete a memory by ID
|
|
19
|
+
memshell clear Wipe all memories
|
|
20
|
+
memshell important <id> Boost memory importance
|
|
21
|
+
memshell stats Show memory statistics
|
|
22
|
+
memshell export Export all memories as JSON
|
|
23
|
+
memshell import <file.json> Import memories from JSON
|
|
24
|
+
memshell serve [--port N] Start API server
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
\x1b[36mOptions:\x1b[0m
|
|
22
27
|
--agent <name> Agent namespace
|
|
23
28
|
--api <url> Use remote API instead of local
|
|
24
29
|
--key <key> API key for remote server
|
|
30
|
+
--tags <t1,t2> Tags (comma-separated)
|
|
31
|
+
--top <N> Return top N results only
|
|
32
|
+
--embeddings Enable OpenAI embeddings (needs OPENAI_API_KEY)
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
\x1b[36mExamples:\x1b[0m
|
|
35
|
+
memshell set "user prefers dark mode" --tags preferences,ui
|
|
36
|
+
memshell recall "what theme?" --tags preferences --top 3
|
|
37
|
+
memshell important 5
|
|
38
|
+
memshell stats
|
|
39
|
+
memshell export > backup.json
|
|
40
|
+
memshell import backup.json
|
|
31
41
|
`;
|
|
32
42
|
|
|
33
43
|
// Parse flags
|
|
34
44
|
function flag(name) {
|
|
35
45
|
const i = args.indexOf('--' + name);
|
|
36
46
|
if (i === -1) return null;
|
|
37
|
-
|
|
47
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) return args[i + 1];
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function hasFlag(name) {
|
|
52
|
+
return args.includes('--' + name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getTextArgs() {
|
|
56
|
+
return args.slice(1).filter(a => !a.startsWith('--') && (args.indexOf(a) === 0 || !['--agent', '--api', '--key', '--tags', '--top', '--port'].includes(args[args.indexOf(a) - 1]))).join(' ').replace(/^["']|["']$/g, '');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Smarter text extraction: skip flag values
|
|
60
|
+
function getText() {
|
|
61
|
+
const skip = new Set(['--agent', '--api', '--key', '--tags', '--top', '--port']);
|
|
62
|
+
const parts = [];
|
|
63
|
+
let i = 1; // skip command
|
|
64
|
+
while (i < args.length) {
|
|
65
|
+
if (skip.has(args[i])) { i += 2; continue; }
|
|
66
|
+
if (args[i] === '--embeddings') { i++; continue; }
|
|
67
|
+
if (args[i].startsWith('--')) { i++; continue; }
|
|
68
|
+
parts.push(args[i]);
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
return parts.join(' ').replace(/^["']|["']$/g, '');
|
|
38
72
|
}
|
|
39
73
|
|
|
40
74
|
async function main() {
|
|
41
75
|
const agent = flag('agent') || 'default';
|
|
42
76
|
const api = flag('api');
|
|
43
77
|
const key = flag('key');
|
|
78
|
+
const tags = flag('tags') || '';
|
|
79
|
+
const top = flag('top') ? parseInt(flag('top')) : null;
|
|
80
|
+
const useEmbeddings = hasFlag('embeddings');
|
|
44
81
|
|
|
45
|
-
|
|
46
|
-
|
|
82
|
+
const configOpts = { agent };
|
|
83
|
+
if (api) { configOpts.api = api; configOpts.key = key; }
|
|
84
|
+
if (useEmbeddings) { configOpts.openaiKey = process.env.OPENAI_API_KEY; }
|
|
47
85
|
|
|
48
|
-
|
|
86
|
+
mem.configure(configOpts);
|
|
87
|
+
|
|
88
|
+
const opts = { agent, tags, top };
|
|
49
89
|
|
|
50
90
|
switch (cmd) {
|
|
51
91
|
case 'set': case 's': case 'save': case 'remember': {
|
|
52
|
-
const text =
|
|
53
|
-
if (!text) return console.log('Usage:
|
|
54
|
-
const r = await mem.set(text, opts);
|
|
55
|
-
console.log(
|
|
92
|
+
const text = getText();
|
|
93
|
+
if (!text) return console.log('Usage: memshell set <text>');
|
|
94
|
+
const r = await mem.set(text, { ...opts, tags });
|
|
95
|
+
console.log(`\x1b[32m✓\x1b[0m Stored (id: \x1b[1m${r.id}\x1b[0m)${tags ? ` [tags: ${tags}]` : ''}`);
|
|
56
96
|
break;
|
|
57
97
|
}
|
|
58
98
|
case 'recall': case 'r': case 'search': case 'q': {
|
|
59
|
-
const query =
|
|
60
|
-
if (!query) return console.log('Usage:
|
|
99
|
+
const query = getText();
|
|
100
|
+
if (!query) return console.log('Usage: memshell recall <query>');
|
|
61
101
|
const results = await mem.recall(query, opts);
|
|
62
|
-
if (!results.length) return console.log('
|
|
102
|
+
if (!results.length) return console.log('\x1b[33mNo memories found.\x1b[0m');
|
|
63
103
|
for (const r of results) {
|
|
64
|
-
|
|
104
|
+
const tagStr = r.tags ? ` \x1b[35m[${r.tags}]\x1b[0m` : '';
|
|
105
|
+
console.log(` \x1b[36m[${r.id}]\x1b[0m ${r.text} \x1b[33m(score: ${r.score})\x1b[0m${tagStr}`);
|
|
65
106
|
}
|
|
66
107
|
break;
|
|
67
108
|
}
|
|
68
109
|
case 'list': case 'ls': case 'l': {
|
|
69
110
|
const all = await mem.list(opts);
|
|
70
|
-
if (!all.length) return console.log('
|
|
111
|
+
if (!all.length) return console.log('\x1b[33mNo memories stored.\x1b[0m');
|
|
71
112
|
for (const r of all) {
|
|
72
|
-
|
|
113
|
+
const tagStr = r.tags ? ` \x1b[35m[${r.tags}]\x1b[0m` : '';
|
|
114
|
+
const imp = r.importance !== 1.0 ? ` \x1b[33m★${r.importance.toFixed(1)}\x1b[0m` : '';
|
|
115
|
+
console.log(` \x1b[36m[${r.id}]\x1b[0m ${r.text}${tagStr}${imp} \x1b[2m(${r.created_at})\x1b[0m`);
|
|
73
116
|
}
|
|
74
|
-
console.log(`\n ${all.length} memor${all.length === 1 ? 'y' : 'ies'}`);
|
|
117
|
+
console.log(`\n \x1b[1m${all.length}\x1b[0m memor${all.length === 1 ? 'y' : 'ies'}`);
|
|
75
118
|
break;
|
|
76
119
|
}
|
|
77
120
|
case 'forget': case 'delete': case 'rm': {
|
|
78
121
|
const id = args[1];
|
|
79
|
-
if (!id) return console.log('Usage:
|
|
122
|
+
if (!id) return console.log('Usage: memshell forget <id>');
|
|
80
123
|
await mem.forget(id);
|
|
81
|
-
console.log(
|
|
124
|
+
console.log(`\x1b[32m✓\x1b[0m Forgotten (id: ${id})`);
|
|
82
125
|
break;
|
|
83
126
|
}
|
|
84
127
|
case 'clear': case 'wipe': case 'reset': {
|
|
85
128
|
await mem.clear(opts);
|
|
86
|
-
console.log('
|
|
129
|
+
console.log('\x1b[32m✓\x1b[0m All memories cleared');
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'important': case 'boost': {
|
|
133
|
+
const id = args[1];
|
|
134
|
+
if (!id) return console.log('Usage: memshell important <id>');
|
|
135
|
+
const r = await mem.important(Number(id));
|
|
136
|
+
if (!r) return console.log('\x1b[31mMemory not found.\x1b[0m');
|
|
137
|
+
console.log(`\x1b[32m✓\x1b[0m Boosted memory ${r.id} → importance: \x1b[1m${r.importance.toFixed(1)}\x1b[0m`);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case 'stats': {
|
|
141
|
+
const s = await mem.stats(opts);
|
|
142
|
+
console.log(`\n \x1b[1m🧠 Memory Stats\x1b[0m`);
|
|
143
|
+
console.log(` Total: \x1b[36m${s.total}\x1b[0m`);
|
|
144
|
+
console.log(` Oldest: ${s.oldest || 'n/a'}`);
|
|
145
|
+
console.log(` Newest: ${s.newest || 'n/a'}`);
|
|
146
|
+
console.log(` Avg importance: \x1b[33m${s.avg_importance}\x1b[0m\n`);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'export': {
|
|
150
|
+
const data = await mem.exportAll(opts);
|
|
151
|
+
console.log(JSON.stringify(data, null, 2));
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case 'import': {
|
|
155
|
+
const file = args[1];
|
|
156
|
+
if (!file) return console.log('Usage: memshell import <file.json>');
|
|
157
|
+
const raw = fs.readFileSync(path.resolve(file), 'utf8');
|
|
158
|
+
const data = JSON.parse(raw);
|
|
159
|
+
const r = await mem.importAll(Array.isArray(data) ? data : data.memories || []);
|
|
160
|
+
console.log(`\x1b[32m✓\x1b[0m Imported ${r.imported} memories`);
|
|
87
161
|
break;
|
|
88
162
|
}
|
|
89
163
|
case 'serve': case 'server': {
|
|
@@ -91,6 +165,7 @@ async function main() {
|
|
|
91
165
|
const authKey = flag('key') || process.env.MEM_KEY || '';
|
|
92
166
|
process.env.MEM_PORT = port;
|
|
93
167
|
if (authKey) process.env.MEM_KEY = authKey;
|
|
168
|
+
if (useEmbeddings) process.env.MEM_USE_EMBEDDINGS = '1';
|
|
94
169
|
require('../server');
|
|
95
170
|
break;
|
|
96
171
|
}
|
|
@@ -99,4 +174,4 @@ async function main() {
|
|
|
99
174
|
}
|
|
100
175
|
}
|
|
101
176
|
|
|
102
|
-
main().catch(e => { console.error('
|
|
177
|
+
main().catch(e => { console.error('\x1b[31mError:\x1b[0m', e.message); process.exit(1); });
|
package/bin/memshell.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const mem = require('../src/index');
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const cmd = args[0];
|
|
10
|
+
|
|
11
|
+
const HELP = `
|
|
12
|
+
\x1b[1mmem.sh\x1b[0m — persistent memory for AI agents
|
|
13
|
+
|
|
14
|
+
\x1b[36mUsage:\x1b[0m
|
|
15
|
+
memshell set <text> Store a memory
|
|
16
|
+
memshell recall <query> Semantic recall
|
|
17
|
+
memshell list List all memories
|
|
18
|
+
memshell forget <id> Delete a memory by ID
|
|
19
|
+
memshell clear Wipe all memories
|
|
20
|
+
memshell important <id> Boost memory importance
|
|
21
|
+
memshell stats Show memory statistics
|
|
22
|
+
memshell export Export all memories as JSON
|
|
23
|
+
memshell import <file.json> Import memories from JSON
|
|
24
|
+
memshell serve [--port N] Start API server
|
|
25
|
+
|
|
26
|
+
\x1b[36mOptions:\x1b[0m
|
|
27
|
+
--agent <name> Agent namespace
|
|
28
|
+
--api <url> Use remote API instead of local
|
|
29
|
+
--key <key> API key for remote server
|
|
30
|
+
--tags <t1,t2> Tags (comma-separated)
|
|
31
|
+
--top <N> Return top N results only
|
|
32
|
+
--embeddings Enable OpenAI embeddings (needs OPENAI_API_KEY)
|
|
33
|
+
|
|
34
|
+
\x1b[36mExamples:\x1b[0m
|
|
35
|
+
memshell set "user prefers dark mode" --tags preferences,ui
|
|
36
|
+
memshell recall "what theme?" --tags preferences --top 3
|
|
37
|
+
memshell important 5
|
|
38
|
+
memshell stats
|
|
39
|
+
memshell export > backup.json
|
|
40
|
+
memshell import backup.json
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
// Parse flags
|
|
44
|
+
function flag(name) {
|
|
45
|
+
const i = args.indexOf('--' + name);
|
|
46
|
+
if (i === -1) return null;
|
|
47
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) return args[i + 1];
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function hasFlag(name) {
|
|
52
|
+
return args.includes('--' + name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getTextArgs() {
|
|
56
|
+
return args.slice(1).filter(a => !a.startsWith('--') && (args.indexOf(a) === 0 || !['--agent', '--api', '--key', '--tags', '--top', '--port'].includes(args[args.indexOf(a) - 1]))).join(' ').replace(/^["']|["']$/g, '');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Smarter text extraction: skip flag values
|
|
60
|
+
function getText() {
|
|
61
|
+
const skip = new Set(['--agent', '--api', '--key', '--tags', '--top', '--port']);
|
|
62
|
+
const parts = [];
|
|
63
|
+
let i = 1; // skip command
|
|
64
|
+
while (i < args.length) {
|
|
65
|
+
if (skip.has(args[i])) { i += 2; continue; }
|
|
66
|
+
if (args[i] === '--embeddings') { i++; continue; }
|
|
67
|
+
if (args[i].startsWith('--')) { i++; continue; }
|
|
68
|
+
parts.push(args[i]);
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
return parts.join(' ').replace(/^["']|["']$/g, '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function main() {
|
|
75
|
+
const agent = flag('agent') || 'default';
|
|
76
|
+
const api = flag('api');
|
|
77
|
+
const key = flag('key');
|
|
78
|
+
const tags = flag('tags') || '';
|
|
79
|
+
const top = flag('top') ? parseInt(flag('top')) : null;
|
|
80
|
+
const useEmbeddings = hasFlag('embeddings');
|
|
81
|
+
|
|
82
|
+
const configOpts = { agent };
|
|
83
|
+
if (api) { configOpts.api = api; configOpts.key = key; }
|
|
84
|
+
if (useEmbeddings) { configOpts.openaiKey = process.env.OPENAI_API_KEY; }
|
|
85
|
+
|
|
86
|
+
mem.configure(configOpts);
|
|
87
|
+
|
|
88
|
+
const opts = { agent, tags, top };
|
|
89
|
+
|
|
90
|
+
switch (cmd) {
|
|
91
|
+
case 'set': case 's': case 'save': case 'remember': {
|
|
92
|
+
const text = getText();
|
|
93
|
+
if (!text) return console.log('Usage: memshell set <text>');
|
|
94
|
+
const r = await mem.set(text, { ...opts, tags });
|
|
95
|
+
console.log(`\x1b[32m✓\x1b[0m Stored (id: \x1b[1m${r.id}\x1b[0m)${tags ? ` [tags: ${tags}]` : ''}`);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'recall': case 'r': case 'search': case 'q': {
|
|
99
|
+
const query = getText();
|
|
100
|
+
if (!query) return console.log('Usage: memshell recall <query>');
|
|
101
|
+
const results = await mem.recall(query, opts);
|
|
102
|
+
if (!results.length) return console.log('\x1b[33mNo memories found.\x1b[0m');
|
|
103
|
+
for (const r of results) {
|
|
104
|
+
const tagStr = r.tags ? ` \x1b[35m[${r.tags}]\x1b[0m` : '';
|
|
105
|
+
console.log(` \x1b[36m[${r.id}]\x1b[0m ${r.text} \x1b[33m(score: ${r.score})\x1b[0m${tagStr}`);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'list': case 'ls': case 'l': {
|
|
110
|
+
const all = await mem.list(opts);
|
|
111
|
+
if (!all.length) return console.log('\x1b[33mNo memories stored.\x1b[0m');
|
|
112
|
+
for (const r of all) {
|
|
113
|
+
const tagStr = r.tags ? ` \x1b[35m[${r.tags}]\x1b[0m` : '';
|
|
114
|
+
const imp = r.importance !== 1.0 ? ` \x1b[33m★${r.importance.toFixed(1)}\x1b[0m` : '';
|
|
115
|
+
console.log(` \x1b[36m[${r.id}]\x1b[0m ${r.text}${tagStr}${imp} \x1b[2m(${r.created_at})\x1b[0m`);
|
|
116
|
+
}
|
|
117
|
+
console.log(`\n \x1b[1m${all.length}\x1b[0m memor${all.length === 1 ? 'y' : 'ies'}`);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case 'forget': case 'delete': case 'rm': {
|
|
121
|
+
const id = args[1];
|
|
122
|
+
if (!id) return console.log('Usage: memshell forget <id>');
|
|
123
|
+
await mem.forget(id);
|
|
124
|
+
console.log(`\x1b[32m✓\x1b[0m Forgotten (id: ${id})`);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'clear': case 'wipe': case 'reset': {
|
|
128
|
+
await mem.clear(opts);
|
|
129
|
+
console.log('\x1b[32m✓\x1b[0m All memories cleared');
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'important': case 'boost': {
|
|
133
|
+
const id = args[1];
|
|
134
|
+
if (!id) return console.log('Usage: memshell important <id>');
|
|
135
|
+
const r = await mem.important(Number(id));
|
|
136
|
+
if (!r) return console.log('\x1b[31mMemory not found.\x1b[0m');
|
|
137
|
+
console.log(`\x1b[32m✓\x1b[0m Boosted memory ${r.id} → importance: \x1b[1m${r.importance.toFixed(1)}\x1b[0m`);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case 'stats': {
|
|
141
|
+
const s = await mem.stats(opts);
|
|
142
|
+
console.log(`\n \x1b[1m🧠 Memory Stats\x1b[0m`);
|
|
143
|
+
console.log(` Total: \x1b[36m${s.total}\x1b[0m`);
|
|
144
|
+
console.log(` Oldest: ${s.oldest || 'n/a'}`);
|
|
145
|
+
console.log(` Newest: ${s.newest || 'n/a'}`);
|
|
146
|
+
console.log(` Avg importance: \x1b[33m${s.avg_importance}\x1b[0m\n`);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'export': {
|
|
150
|
+
const data = await mem.exportAll(opts);
|
|
151
|
+
console.log(JSON.stringify(data, null, 2));
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case 'import': {
|
|
155
|
+
const file = args[1];
|
|
156
|
+
if (!file) return console.log('Usage: memshell import <file.json>');
|
|
157
|
+
const raw = fs.readFileSync(path.resolve(file), 'utf8');
|
|
158
|
+
const data = JSON.parse(raw);
|
|
159
|
+
const r = await mem.importAll(Array.isArray(data) ? data : data.memories || []);
|
|
160
|
+
console.log(`\x1b[32m✓\x1b[0m Imported ${r.imported} memories`);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case 'serve': case 'server': {
|
|
164
|
+
const port = flag('port') || 3456;
|
|
165
|
+
const authKey = flag('key') || process.env.MEM_KEY || '';
|
|
166
|
+
process.env.MEM_PORT = port;
|
|
167
|
+
if (authKey) process.env.MEM_KEY = authKey;
|
|
168
|
+
if (useEmbeddings) process.env.MEM_USE_EMBEDDINGS = '1';
|
|
169
|
+
require('../server');
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
console.log(HELP);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
main().catch(e => { console.error('\x1b[31mError:\x1b[0m', e.message); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memshell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Persistent memory for AI agents. Like localStorage but for LLMs.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"memshell": "
|
|
7
|
+
"memshell": "bin/memshell.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node server.js",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"author": "justedv",
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"express": "^4.18.2"
|
|
28
|
+
"express": "^4.18.2",
|
|
29
|
+
"sql.js": "^1.11.0"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
package/server.js
CHANGED
|
@@ -6,12 +6,20 @@ const { LocalStore } = require('./src/index');
|
|
|
6
6
|
const app = express();
|
|
7
7
|
app.use(express.json());
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const storeOpts = {};
|
|
10
|
+
if (process.env.MEM_USE_EMBEDDINGS && process.env.OPENAI_API_KEY) {
|
|
11
|
+
storeOpts.openaiKey = process.env.OPENAI_API_KEY;
|
|
12
|
+
}
|
|
13
|
+
const store = new LocalStore(undefined, storeOpts);
|
|
10
14
|
const PORT = process.env.MEM_PORT || 3456;
|
|
11
15
|
const AUTH_KEY = process.env.MEM_KEY || '';
|
|
12
16
|
|
|
17
|
+
// Ensure store is initialized
|
|
18
|
+
let initPromise = store.init();
|
|
19
|
+
|
|
13
20
|
// Auth middleware
|
|
14
|
-
app.use('/mem', (req, res, next) => {
|
|
21
|
+
app.use('/mem', async (req, res, next) => {
|
|
22
|
+
await initPromise;
|
|
15
23
|
if (AUTH_KEY && req.headers['x-mem-key'] !== AUTH_KEY) {
|
|
16
24
|
return res.status(401).json({ error: 'Invalid API key' });
|
|
17
25
|
}
|
|
@@ -20,43 +28,72 @@ app.use('/mem', (req, res, next) => {
|
|
|
20
28
|
});
|
|
21
29
|
|
|
22
30
|
// Store a memory
|
|
23
|
-
app.post('/mem', (req, res) => {
|
|
24
|
-
const { text, metadata } = req.body;
|
|
31
|
+
app.post('/mem', async (req, res) => {
|
|
32
|
+
const { text, tags, importance, metadata } = req.body;
|
|
25
33
|
if (!text) return res.status(400).json({ error: 'text is required' });
|
|
26
|
-
const result = store.set(text, { agent: req.agent, metadata });
|
|
34
|
+
const result = await store.set(text, { agent: req.agent, tags: tags || '', importance, metadata });
|
|
27
35
|
res.json(result);
|
|
28
36
|
});
|
|
29
37
|
|
|
30
38
|
// Semantic recall
|
|
31
|
-
app.get('/mem/recall', (req, res) => {
|
|
39
|
+
app.get('/mem/recall', async (req, res) => {
|
|
32
40
|
const q = req.query.q;
|
|
33
41
|
if (!q) return res.status(400).json({ error: 'q parameter is required' });
|
|
34
42
|
const limit = parseInt(req.query.limit) || 10;
|
|
35
|
-
const
|
|
43
|
+
const tags = req.query.tags || '';
|
|
44
|
+
const top = req.query.top ? parseInt(req.query.top) : null;
|
|
45
|
+
const results = await store.recall(q, { agent: req.agent, limit, tags, top });
|
|
36
46
|
res.json(results);
|
|
37
47
|
});
|
|
38
48
|
|
|
39
49
|
// List all
|
|
40
|
-
app.get('/mem/list', (req, res) => {
|
|
41
|
-
const results = store.list({ agent: req.agent });
|
|
50
|
+
app.get('/mem/list', async (req, res) => {
|
|
51
|
+
const results = await store.list({ agent: req.agent });
|
|
42
52
|
res.json(results);
|
|
43
53
|
});
|
|
44
54
|
|
|
55
|
+
// Stats
|
|
56
|
+
app.get('/mem/stats', async (req, res) => {
|
|
57
|
+
const stats = await store.stats({ agent: req.agent });
|
|
58
|
+
res.json(stats);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Export
|
|
62
|
+
app.get('/mem/export', async (req, res) => {
|
|
63
|
+
const data = await store.exportAll({ agent: req.query.agent });
|
|
64
|
+
res.json(data);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Import
|
|
68
|
+
app.post('/mem/import', async (req, res) => {
|
|
69
|
+
const memories = Array.isArray(req.body) ? req.body : req.body.memories || [];
|
|
70
|
+
const result = await store.importAll(memories);
|
|
71
|
+
res.json(result);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Boost importance
|
|
75
|
+
app.post('/mem/:id/important', async (req, res) => {
|
|
76
|
+
const result = await store.important(req.params.id);
|
|
77
|
+
if (!result) return res.status(404).json({ error: 'Memory not found' });
|
|
78
|
+
res.json(result);
|
|
79
|
+
});
|
|
80
|
+
|
|
45
81
|
// Delete by id
|
|
46
|
-
app.delete('/mem/:id', (req, res) => {
|
|
47
|
-
store.forget(req.params.id);
|
|
82
|
+
app.delete('/mem/:id', async (req, res) => {
|
|
83
|
+
await store.forget(req.params.id);
|
|
48
84
|
res.json({ ok: true, id: req.params.id });
|
|
49
85
|
});
|
|
50
86
|
|
|
51
87
|
// Clear all for agent
|
|
52
|
-
app.delete('/mem', (req, res) => {
|
|
53
|
-
store.clear({ agent: req.agent });
|
|
88
|
+
app.delete('/mem', async (req, res) => {
|
|
89
|
+
await store.clear({ agent: req.agent });
|
|
54
90
|
res.json({ ok: true });
|
|
55
91
|
});
|
|
56
92
|
|
|
57
93
|
app.listen(PORT, () => {
|
|
58
94
|
console.log(`\n 🧠 mem.sh server running on http://localhost:${PORT}`);
|
|
59
95
|
console.log(` Auth: ${AUTH_KEY ? 'enabled' : 'disabled'}`);
|
|
96
|
+
console.log(` Embeddings: ${storeOpts.openaiKey ? 'OpenAI' : 'TF-IDF'}`);
|
|
60
97
|
console.log();
|
|
61
98
|
});
|
|
62
99
|
|
package/src/index.js
CHANGED
|
@@ -64,75 +64,299 @@ class TfIdf {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// ──
|
|
67
|
+
// ── OpenAI Embeddings ──────────────────────────────────────────
|
|
68
|
+
class OpenAIEmbedder {
|
|
69
|
+
constructor(apiKey) {
|
|
70
|
+
this.apiKey = apiKey;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async embed(text) {
|
|
74
|
+
const body = JSON.stringify({
|
|
75
|
+
model: 'text-embedding-3-small',
|
|
76
|
+
input: text
|
|
77
|
+
});
|
|
78
|
+
const res = await fetch('https://api.openai.com/v1/embeddings', {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
83
|
+
},
|
|
84
|
+
body
|
|
85
|
+
});
|
|
86
|
+
if (!res.ok) throw new Error(`OpenAI API error: ${res.status}`);
|
|
87
|
+
const data = await res.json();
|
|
88
|
+
return data.data[0].embedding;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
cosine(a, b) {
|
|
92
|
+
let dot = 0, magA = 0, magB = 0;
|
|
93
|
+
for (let i = 0; i < a.length; i++) {
|
|
94
|
+
dot += a[i] * b[i];
|
|
95
|
+
magA += a[i] * a[i];
|
|
96
|
+
magB += b[i] * b[i];
|
|
97
|
+
}
|
|
98
|
+
if (!magA || !magB) return 0;
|
|
99
|
+
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── SQLite Store (sql.js) ──────────────────────────────────────
|
|
68
104
|
class LocalStore {
|
|
69
|
-
constructor(dir) {
|
|
105
|
+
constructor(dir, opts = {}) {
|
|
70
106
|
this.dir = dir || path.join(os.homedir(), '.mem');
|
|
71
|
-
this.dbPath = path.join(this.dir, 'mem.
|
|
107
|
+
this.dbPath = path.join(this.dir, 'mem.db');
|
|
72
108
|
this.tfidf = new TfIdf();
|
|
73
|
-
this.
|
|
109
|
+
this._db = null;
|
|
110
|
+
this._SQL = null;
|
|
111
|
+
this._openaiKey = opts.openaiKey || process.env.OPENAI_API_KEY || null;
|
|
112
|
+
this._embedder = this._openaiKey ? new OpenAIEmbedder(this._openaiKey) : null;
|
|
74
113
|
}
|
|
75
114
|
|
|
76
|
-
|
|
77
|
-
if (this.
|
|
115
|
+
_initDb() {
|
|
116
|
+
if (this._db) return this._db;
|
|
117
|
+
const initSqlJs = require('sql.js');
|
|
118
|
+
// sql.js returns a promise, but we need sync init for backward compat
|
|
119
|
+
// Use the sync factory if available, otherwise we cache
|
|
120
|
+
if (!this._SQL) {
|
|
121
|
+
throw new Error('Must call await store.init() before using the store');
|
|
122
|
+
}
|
|
123
|
+
return this._db;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async init() {
|
|
127
|
+
if (this._db) return;
|
|
128
|
+
const initSqlJs = require('sql.js');
|
|
129
|
+
this._SQL = await initSqlJs();
|
|
78
130
|
fs.mkdirSync(this.dir, { recursive: true });
|
|
79
131
|
try {
|
|
80
|
-
|
|
132
|
+
const buf = fs.readFileSync(this.dbPath);
|
|
133
|
+
this._db = new this._SQL.Database(buf);
|
|
81
134
|
} catch {
|
|
82
|
-
this.
|
|
135
|
+
this._db = new this._SQL.Database();
|
|
83
136
|
}
|
|
84
|
-
|
|
137
|
+
this._db.run(`CREATE TABLE IF NOT EXISTS memories (
|
|
138
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
139
|
+
text TEXT NOT NULL,
|
|
140
|
+
agent TEXT DEFAULT 'default',
|
|
141
|
+
embedding TEXT,
|
|
142
|
+
tags TEXT DEFAULT '',
|
|
143
|
+
created_at TEXT NOT NULL,
|
|
144
|
+
importance REAL DEFAULT 1.0
|
|
145
|
+
)`);
|
|
146
|
+
this._save();
|
|
85
147
|
}
|
|
86
148
|
|
|
87
149
|
_save() {
|
|
88
|
-
|
|
150
|
+
const data = this._db.export();
|
|
151
|
+
const buf = Buffer.from(data);
|
|
152
|
+
fs.writeFileSync(this.dbPath, buf);
|
|
89
153
|
}
|
|
90
154
|
|
|
91
|
-
|
|
92
|
-
const
|
|
155
|
+
_applyDecay(row) {
|
|
156
|
+
const created = new Date(row.created_at);
|
|
157
|
+
const now = new Date();
|
|
158
|
+
const days = (now - created) / (1000 * 60 * 60 * 24);
|
|
159
|
+
if (days > 30) {
|
|
160
|
+
const decay = (days - 30) * 0.01;
|
|
161
|
+
const decayed = Math.max(0.1, row.importance - decay);
|
|
162
|
+
return decayed;
|
|
163
|
+
}
|
|
164
|
+
return row.importance;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async set(text, opts = {}) {
|
|
168
|
+
await this.init();
|
|
93
169
|
const agent = opts.agent || 'default';
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
170
|
+
const tags = opts.tags || '';
|
|
171
|
+
const importance = opts.importance || 1.0;
|
|
172
|
+
const created_at = new Date().toISOString();
|
|
173
|
+
|
|
174
|
+
let embedding = null;
|
|
175
|
+
if (this._embedder) {
|
|
176
|
+
try {
|
|
177
|
+
const emb = await this._embedder.embed(text);
|
|
178
|
+
embedding = JSON.stringify(emb);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// fallback: no embedding
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this._db.run(
|
|
185
|
+
'INSERT INTO memories (text, agent, embedding, tags, created_at, importance) VALUES (?, ?, ?, ?, ?, ?)',
|
|
186
|
+
[text, agent, embedding, tags, created_at, importance]
|
|
187
|
+
);
|
|
188
|
+
const id = this._db.exec('SELECT last_insert_rowid() as id')[0].values[0][0];
|
|
102
189
|
this._save();
|
|
103
|
-
return { id
|
|
190
|
+
return { id, text, agent, tags };
|
|
104
191
|
}
|
|
105
192
|
|
|
106
|
-
recall(query, opts = {}) {
|
|
107
|
-
|
|
193
|
+
async recall(query, opts = {}) {
|
|
194
|
+
await this.init();
|
|
108
195
|
const agent = opts.agent || 'default';
|
|
109
196
|
const limit = opts.limit || 10;
|
|
110
|
-
const
|
|
111
|
-
|
|
197
|
+
const top = opts.top || null;
|
|
198
|
+
const filterTags = opts.tags ? opts.tags.split(',').map(t => t.trim()) : null;
|
|
199
|
+
|
|
200
|
+
const stmt = this._db.exec(
|
|
201
|
+
'SELECT id, text, agent, embedding, tags, created_at, importance FROM memories WHERE agent = ?',
|
|
202
|
+
[agent]
|
|
203
|
+
);
|
|
204
|
+
if (!stmt.length) return [];
|
|
205
|
+
|
|
206
|
+
const cols = stmt[0].columns;
|
|
207
|
+
let rows = stmt[0].values.map(v => {
|
|
208
|
+
const obj = {};
|
|
209
|
+
cols.forEach((c, i) => obj[c] = v[i]);
|
|
210
|
+
return obj;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Filter by tags if specified
|
|
214
|
+
if (filterTags) {
|
|
215
|
+
rows = rows.filter(r => {
|
|
216
|
+
const rTags = (r.tags || '').split(',').map(t => t.trim()).filter(Boolean);
|
|
217
|
+
return filterTags.some(ft => rTags.includes(ft));
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let scored;
|
|
222
|
+
|
|
223
|
+
// Try OpenAI embeddings first
|
|
224
|
+
if (this._embedder) {
|
|
225
|
+
try {
|
|
226
|
+
const qEmb = await this._embedder.embed(query);
|
|
227
|
+
scored = rows.map(row => {
|
|
228
|
+
let similarity = 0;
|
|
229
|
+
if (row.embedding) {
|
|
230
|
+
const emb = JSON.parse(row.embedding);
|
|
231
|
+
similarity = this._embedder.cosine(qEmb, emb);
|
|
232
|
+
}
|
|
233
|
+
const effectiveImportance = this._applyDecay(row);
|
|
234
|
+
const maxImportance = Math.max(...rows.map(r => this._applyDecay(r)), 1);
|
|
235
|
+
const normImportance = effectiveImportance / maxImportance;
|
|
236
|
+
const finalScore = similarity * 0.7 + normImportance * 0.3;
|
|
237
|
+
return { id: row.id, text: row.text, agent: row.agent, tags: row.tags, created_at: row.created_at, importance: row.importance, score: Math.round(finalScore * 1000) / 1000 };
|
|
238
|
+
}).filter(d => d.score > 0.01).sort((a, b) => b.score - a.score);
|
|
239
|
+
} catch {
|
|
240
|
+
scored = null; // fall through to TF-IDF
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!scored) {
|
|
245
|
+
// TF-IDF fallback
|
|
246
|
+
const tfidfResults = this.tfidf.rank(query, rows);
|
|
247
|
+
scored = tfidfResults.map(r => {
|
|
248
|
+
const effectiveImportance = this._applyDecay(r);
|
|
249
|
+
const maxImportance = Math.max(...rows.map(row => this._applyDecay(row)), 1);
|
|
250
|
+
const normImportance = effectiveImportance / maxImportance;
|
|
251
|
+
const similarity = r.score;
|
|
252
|
+
const finalScore = similarity * 0.7 + normImportance * 0.3;
|
|
253
|
+
return { ...r, score: Math.round(finalScore * 1000) / 1000 };
|
|
254
|
+
}).sort((a, b) => b.score - a.score);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const resultLimit = top || limit;
|
|
258
|
+
const results = scored.slice(0, resultLimit);
|
|
259
|
+
|
|
260
|
+
// Bump importance for recalled memories
|
|
261
|
+
for (const r of results) {
|
|
262
|
+
this._db.run('UPDATE memories SET importance = importance + 0.1 WHERE id = ?', [r.id]);
|
|
263
|
+
}
|
|
264
|
+
this._save();
|
|
265
|
+
|
|
266
|
+
return results;
|
|
112
267
|
}
|
|
113
268
|
|
|
114
|
-
list(opts = {}) {
|
|
115
|
-
|
|
269
|
+
async list(opts = {}) {
|
|
270
|
+
await this.init();
|
|
116
271
|
const agent = opts.agent || 'default';
|
|
117
|
-
|
|
272
|
+
const stmt = this._db.exec(
|
|
273
|
+
'SELECT id, text, agent, tags, created_at, importance FROM memories WHERE agent = ? ORDER BY id DESC',
|
|
274
|
+
[agent]
|
|
275
|
+
);
|
|
276
|
+
if (!stmt.length) return [];
|
|
277
|
+
const cols = stmt[0].columns;
|
|
278
|
+
return stmt[0].values.map(v => {
|
|
279
|
+
const obj = {};
|
|
280
|
+
cols.forEach((c, i) => obj[c] = v[i]);
|
|
281
|
+
obj.importance = this._applyDecay(obj);
|
|
282
|
+
return obj;
|
|
283
|
+
});
|
|
118
284
|
}
|
|
119
285
|
|
|
120
|
-
forget(id) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
data.memories = data.memories.filter(m => m.id !== numId);
|
|
286
|
+
async forget(id) {
|
|
287
|
+
await this.init();
|
|
288
|
+
this._db.run('DELETE FROM memories WHERE id = ?', [Number(id)]);
|
|
289
|
+
const changes = this._db.getRowsModified();
|
|
125
290
|
this._save();
|
|
126
|
-
return { changes
|
|
291
|
+
return { changes };
|
|
127
292
|
}
|
|
128
293
|
|
|
129
|
-
clear(opts = {}) {
|
|
130
|
-
|
|
294
|
+
async clear(opts = {}) {
|
|
295
|
+
await this.init();
|
|
131
296
|
const agent = opts.agent || 'default';
|
|
132
|
-
|
|
133
|
-
|
|
297
|
+
this._db.run('DELETE FROM memories WHERE agent = ?', [agent]);
|
|
298
|
+
const changes = this._db.getRowsModified();
|
|
134
299
|
this._save();
|
|
135
|
-
return { changes
|
|
300
|
+
return { changes };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async important(id, boost = 0.5) {
|
|
304
|
+
await this.init();
|
|
305
|
+
this._db.run('UPDATE memories SET importance = importance + ? WHERE id = ?', [boost, Number(id)]);
|
|
306
|
+
this._save();
|
|
307
|
+
const stmt = this._db.exec('SELECT id, text, importance FROM memories WHERE id = ?', [Number(id)]);
|
|
308
|
+
if (!stmt.length) return null;
|
|
309
|
+
const cols = stmt[0].columns;
|
|
310
|
+
const v = stmt[0].values[0];
|
|
311
|
+
const obj = {};
|
|
312
|
+
cols.forEach((c, i) => obj[c] = v[i]);
|
|
313
|
+
return obj;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async stats(opts = {}) {
|
|
317
|
+
await this.init();
|
|
318
|
+
const agent = opts.agent || 'default';
|
|
319
|
+
const stmt = this._db.exec(
|
|
320
|
+
`SELECT COUNT(*) as total, MIN(created_at) as oldest, MAX(created_at) as newest, AVG(importance) as avg_importance FROM memories WHERE agent = ?`,
|
|
321
|
+
[agent]
|
|
322
|
+
);
|
|
323
|
+
if (!stmt.length || !stmt[0].values[0][0]) return { total: 0, oldest: null, newest: null, avg_importance: 0 };
|
|
324
|
+
const [total, oldest, newest, avg_importance] = stmt[0].values[0];
|
|
325
|
+
return { total, oldest, newest, avg_importance: Math.round(avg_importance * 100) / 100 };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async exportAll(opts = {}) {
|
|
329
|
+
await this.init();
|
|
330
|
+
const agent = opts.agent;
|
|
331
|
+
let query = 'SELECT id, text, agent, tags, created_at, importance FROM memories';
|
|
332
|
+
const params = [];
|
|
333
|
+
if (agent) {
|
|
334
|
+
query += ' WHERE agent = ?';
|
|
335
|
+
params.push(agent);
|
|
336
|
+
}
|
|
337
|
+
query += ' ORDER BY id ASC';
|
|
338
|
+
const stmt = this._db.exec(query, params);
|
|
339
|
+
if (!stmt.length) return [];
|
|
340
|
+
const cols = stmt[0].columns;
|
|
341
|
+
return stmt[0].values.map(v => {
|
|
342
|
+
const obj = {};
|
|
343
|
+
cols.forEach((c, i) => obj[c] = v[i]);
|
|
344
|
+
return obj;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async importAll(memories) {
|
|
349
|
+
await this.init();
|
|
350
|
+
let count = 0;
|
|
351
|
+
for (const m of memories) {
|
|
352
|
+
this._db.run(
|
|
353
|
+
'INSERT INTO memories (text, agent, tags, created_at, importance) VALUES (?, ?, ?, ?, ?)',
|
|
354
|
+
[m.text, m.agent || 'default', m.tags || '', m.created_at || new Date().toISOString(), m.importance || 1.0]
|
|
355
|
+
);
|
|
356
|
+
count++;
|
|
357
|
+
}
|
|
358
|
+
this._save();
|
|
359
|
+
return { imported: count };
|
|
136
360
|
}
|
|
137
361
|
}
|
|
138
362
|
|
|
@@ -164,11 +388,21 @@ class ApiClient {
|
|
|
164
388
|
});
|
|
165
389
|
}
|
|
166
390
|
|
|
167
|
-
|
|
168
|
-
|
|
391
|
+
async init() {} // no-op for API client
|
|
392
|
+
set(text, opts = {}) { return this._req('POST', '/mem', { text, tags: opts.tags, importance: opts.importance, metadata: opts.metadata }); }
|
|
393
|
+
recall(query, opts = {}) {
|
|
394
|
+
let url = `/mem/recall?q=${encodeURIComponent(query)}&limit=${opts.limit || 10}`;
|
|
395
|
+
if (opts.tags) url += `&tags=${encodeURIComponent(opts.tags)}`;
|
|
396
|
+
if (opts.top) url += `&top=${opts.top}`;
|
|
397
|
+
return this._req('GET', url);
|
|
398
|
+
}
|
|
169
399
|
list() { return this._req('GET', '/mem/list'); }
|
|
170
400
|
forget(id) { return this._req('DELETE', `/mem/${id}`); }
|
|
171
401
|
clear() { return this._req('DELETE', '/mem?confirm=true'); }
|
|
402
|
+
important(id) { return this._req('POST', `/mem/${id}/important`); }
|
|
403
|
+
stats() { return this._req('GET', '/mem/stats'); }
|
|
404
|
+
exportAll() { return this._req('GET', '/mem/export'); }
|
|
405
|
+
importAll(memories) { return this._req('POST', '/mem/import', memories); }
|
|
172
406
|
}
|
|
173
407
|
|
|
174
408
|
// ── Exports ────────────────────────────────────────────────────
|
|
@@ -177,18 +411,24 @@ let _store = null;
|
|
|
177
411
|
|
|
178
412
|
function getStore() {
|
|
179
413
|
if (!_store) {
|
|
180
|
-
_store = _config.api
|
|
414
|
+
_store = _config.api
|
|
415
|
+
? new ApiClient(_config)
|
|
416
|
+
: new LocalStore(_config.dir, { openaiKey: _config.openaiKey });
|
|
181
417
|
}
|
|
182
418
|
return _store;
|
|
183
419
|
}
|
|
184
420
|
|
|
185
421
|
module.exports = {
|
|
186
422
|
configure(opts) { _config = opts; _store = null; },
|
|
187
|
-
set(text, opts) {
|
|
188
|
-
recall(query, opts) {
|
|
189
|
-
list(opts) {
|
|
190
|
-
forget(id) {
|
|
191
|
-
clear(opts) {
|
|
423
|
+
async set(text, opts) { const s = getStore(); await s.init(); return s.set(text, opts); },
|
|
424
|
+
async recall(query, opts) { const s = getStore(); await s.init(); return s.recall(query, opts); },
|
|
425
|
+
async list(opts) { const s = getStore(); await s.init(); return s.list(opts); },
|
|
426
|
+
async forget(id) { const s = getStore(); await s.init(); return s.forget(id); },
|
|
427
|
+
async clear(opts) { const s = getStore(); await s.init(); return s.clear(opts); },
|
|
428
|
+
async important(id, boost) { const s = getStore(); await s.init(); return s.important(id, boost); },
|
|
429
|
+
async stats(opts) { const s = getStore(); await s.init(); return s.stats(opts); },
|
|
430
|
+
async exportAll(opts) { const s = getStore(); await s.init(); return s.exportAll(opts); },
|
|
431
|
+
async importAll(memories) { const s = getStore(); await s.init(); return s.importAll(memories); },
|
|
192
432
|
TfIdf,
|
|
193
433
|
LocalStore,
|
|
194
434
|
ApiClient
|