memshell 0.2.1 → 0.4.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 +127 -31
- package/bin/mem.js +246 -34
- package/bin/memshell.js +246 -34
- package/package.json +3 -2
- package/server.js +63 -13
- package/src/index.js +293 -47
- package/src/ingest.js +348 -0
package/bin/memshell.js
CHANGED
|
@@ -1,89 +1,300 @@
|
|
|
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');
|
|
7
|
+
const { LocalStore } = require('../src/index');
|
|
5
8
|
|
|
6
9
|
const args = process.argv.slice(2);
|
|
7
10
|
const cmd = args[0];
|
|
8
|
-
const rest = args.slice(1).join(' ');
|
|
9
11
|
|
|
10
12
|
const HELP = `
|
|
11
|
-
|
|
13
|
+
\x1b[1mmem.sh\x1b[0m — persistent memory for AI agents
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
\x1b[36mCore Commands:\x1b[0m
|
|
16
|
+
memshell set <text> Store a memory
|
|
17
|
+
memshell recall <query> Semantic recall
|
|
18
|
+
memshell list List all memories
|
|
19
|
+
memshell forget <id> Delete a memory by ID
|
|
20
|
+
memshell clear Wipe all memories
|
|
21
|
+
memshell important <id> Boost memory importance
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
\x1b[36mAuto-Ingest:\x1b[0m
|
|
24
|
+
memshell ingest <file> Extract facts from a file
|
|
25
|
+
memshell ingest --stdin Extract facts from piped text
|
|
26
|
+
memshell ingest --watch <dir> Watch a directory for new files
|
|
27
|
+
|
|
28
|
+
\x1b[36mIntegrations:\x1b[0m
|
|
29
|
+
memshell connect openclaw Watch OpenClaw session transcripts
|
|
30
|
+
memshell daemon Run continuous ingestion daemon
|
|
31
|
+
|
|
32
|
+
\x1b[36mManagement:\x1b[0m
|
|
33
|
+
memshell config set <key> <val> Set config value
|
|
34
|
+
memshell config get [key] Show config
|
|
35
|
+
memshell stats Show memory statistics
|
|
36
|
+
memshell export Export all memories as JSON
|
|
37
|
+
memshell import <file.json> Import memories from JSON
|
|
38
|
+
memshell serve [--port N] Start API server
|
|
39
|
+
|
|
40
|
+
\x1b[36mOptions:\x1b[0m
|
|
22
41
|
--agent <name> Agent namespace
|
|
23
42
|
--api <url> Use remote API instead of local
|
|
24
43
|
--key <key> API key for remote server
|
|
44
|
+
--tags <t1,t2> Tags (comma-separated)
|
|
45
|
+
--top <N> Return top N results only
|
|
46
|
+
--embeddings Enable OpenAI embeddings (needs OPENAI_API_KEY)
|
|
25
47
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
\x1b[36mExamples:\x1b[0m
|
|
49
|
+
memshell set "user prefers dark mode" --tags preferences,ui
|
|
50
|
+
memshell recall "what theme?" --tags preferences --top 3
|
|
51
|
+
echo "User likes vim and dark mode" | memshell ingest --stdin
|
|
52
|
+
memshell connect openclaw
|
|
53
|
+
memshell config set apiKey sk-...
|
|
31
54
|
`;
|
|
32
55
|
|
|
33
56
|
// Parse flags
|
|
34
57
|
function flag(name) {
|
|
35
58
|
const i = args.indexOf('--' + name);
|
|
36
59
|
if (i === -1) return null;
|
|
37
|
-
|
|
60
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) return args[i + 1];
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasFlag(name) {
|
|
65
|
+
return args.includes('--' + name);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Smarter text extraction: skip flag values
|
|
69
|
+
function getText() {
|
|
70
|
+
const skip = new Set(['--agent', '--api', '--key', '--tags', '--top', '--port', '--watch']);
|
|
71
|
+
const parts = [];
|
|
72
|
+
let i = 1;
|
|
73
|
+
while (i < args.length) {
|
|
74
|
+
if (skip.has(args[i])) { i += 2; continue; }
|
|
75
|
+
if (args[i] === '--embeddings' || args[i] === '--stdin' || args[i] === '--force') { i++; continue; }
|
|
76
|
+
if (args[i].startsWith('--')) { i++; continue; }
|
|
77
|
+
parts.push(args[i]);
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
return parts.join(' ').replace(/^["']|["']$/g, '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readStdin() {
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
let data = '';
|
|
86
|
+
process.stdin.setEncoding('utf8');
|
|
87
|
+
process.stdin.on('data', chunk => data += chunk);
|
|
88
|
+
process.stdin.on('end', () => resolve(data));
|
|
89
|
+
// If nothing after 100ms and stdin is a TTY, resolve empty
|
|
90
|
+
if (process.stdin.isTTY) resolve('');
|
|
91
|
+
});
|
|
38
92
|
}
|
|
39
93
|
|
|
40
94
|
async function main() {
|
|
41
95
|
const agent = flag('agent') || 'default';
|
|
42
96
|
const api = flag('api');
|
|
43
97
|
const key = flag('key');
|
|
98
|
+
const tags = flag('tags') || '';
|
|
99
|
+
const top = flag('top') ? parseInt(flag('top')) : null;
|
|
100
|
+
const useEmbeddings = hasFlag('embeddings');
|
|
101
|
+
|
|
102
|
+
const configOpts = { agent };
|
|
103
|
+
if (api) { configOpts.api = api; configOpts.key = key; }
|
|
104
|
+
if (useEmbeddings) { configOpts.openaiKey = process.env.OPENAI_API_KEY; }
|
|
44
105
|
|
|
45
|
-
|
|
46
|
-
else mem.configure({ agent });
|
|
106
|
+
mem.configure(configOpts);
|
|
47
107
|
|
|
48
|
-
const opts = { agent };
|
|
108
|
+
const opts = { agent, tags, top };
|
|
49
109
|
|
|
50
110
|
switch (cmd) {
|
|
51
111
|
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(
|
|
112
|
+
const text = getText();
|
|
113
|
+
if (!text) return console.log('Usage: memshell set <text>');
|
|
114
|
+
const r = await mem.set(text, { ...opts, tags });
|
|
115
|
+
console.log(`\x1b[32m+\x1b[0m Stored (id: \x1b[1m${r.id}\x1b[0m)${tags ? ` [tags: ${tags}]` : ''}`);
|
|
56
116
|
break;
|
|
57
117
|
}
|
|
58
118
|
case 'recall': case 'r': case 'search': case 'q': {
|
|
59
|
-
const query =
|
|
60
|
-
if (!query) return console.log('Usage:
|
|
119
|
+
const query = getText();
|
|
120
|
+
if (!query) return console.log('Usage: memshell recall <query>');
|
|
61
121
|
const results = await mem.recall(query, opts);
|
|
62
|
-
if (!results.length) return console.log('
|
|
122
|
+
if (!results.length) return console.log('\x1b[33mNo memories found.\x1b[0m');
|
|
63
123
|
for (const r of results) {
|
|
64
|
-
|
|
124
|
+
const tagStr = r.tags ? ` \x1b[35m[${r.tags}]\x1b[0m` : '';
|
|
125
|
+
const srcStr = r.source && r.source !== 'manual' ? ` \x1b[2m(src: ${r.source})\x1b[0m` : '';
|
|
126
|
+
const recallStr = r.recall_count ? ` \x1b[2m(recalled ${r.recall_count}x)\x1b[0m` : '';
|
|
127
|
+
console.log(` \x1b[36m[${r.id}]\x1b[0m ${r.text} \x1b[33m(score: ${r.score})\x1b[0m${tagStr}${srcStr}${recallStr}`);
|
|
65
128
|
}
|
|
66
129
|
break;
|
|
67
130
|
}
|
|
68
131
|
case 'list': case 'ls': case 'l': {
|
|
69
132
|
const all = await mem.list(opts);
|
|
70
|
-
if (!all.length) return console.log('
|
|
133
|
+
if (!all.length) return console.log('\x1b[33mNo memories stored.\x1b[0m');
|
|
71
134
|
for (const r of all) {
|
|
72
|
-
|
|
135
|
+
const tagStr = r.tags ? ` \x1b[35m[${r.tags}]\x1b[0m` : '';
|
|
136
|
+
const imp = r.importance !== 1.0 ? ` \x1b[33m*${r.importance.toFixed(1)}\x1b[0m` : '';
|
|
137
|
+
const srcStr = r.source && r.source !== 'manual' ? ` \x1b[2m[${r.source}]\x1b[0m` : '';
|
|
138
|
+
console.log(` \x1b[36m[${r.id}]\x1b[0m ${r.text}${tagStr}${imp}${srcStr} \x1b[2m(${r.created_at})\x1b[0m`);
|
|
73
139
|
}
|
|
74
|
-
console.log(`\n ${all.length} memor${all.length === 1 ? 'y' : 'ies'}`);
|
|
140
|
+
console.log(`\n \x1b[1m${all.length}\x1b[0m memor${all.length === 1 ? 'y' : 'ies'}`);
|
|
75
141
|
break;
|
|
76
142
|
}
|
|
77
143
|
case 'forget': case 'delete': case 'rm': {
|
|
78
144
|
const id = args[1];
|
|
79
|
-
if (!id) return console.log('Usage:
|
|
145
|
+
if (!id) return console.log('Usage: memshell forget <id>');
|
|
80
146
|
await mem.forget(id);
|
|
81
|
-
console.log(
|
|
147
|
+
console.log(`\x1b[32m+\x1b[0m Forgotten (id: ${id})`);
|
|
82
148
|
break;
|
|
83
149
|
}
|
|
84
150
|
case 'clear': case 'wipe': case 'reset': {
|
|
85
151
|
await mem.clear(opts);
|
|
86
|
-
console.log('
|
|
152
|
+
console.log('\x1b[32m+\x1b[0m All memories cleared');
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case 'important': case 'boost': {
|
|
156
|
+
const id = args[1];
|
|
157
|
+
if (!id) return console.log('Usage: memshell important <id>');
|
|
158
|
+
const r = await mem.important(Number(id));
|
|
159
|
+
if (!r) return console.log('\x1b[31mMemory not found.\x1b[0m');
|
|
160
|
+
console.log(`\x1b[32m+\x1b[0m Boosted memory ${r.id} -> importance: \x1b[1m${r.importance.toFixed(1)}\x1b[0m`);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case 'stats': {
|
|
164
|
+
const s = await mem.stats(opts);
|
|
165
|
+
console.log(`\n \x1b[1mMemory Stats\x1b[0m`);
|
|
166
|
+
console.log(` Total: \x1b[36m${s.total}\x1b[0m`);
|
|
167
|
+
console.log(` Oldest: ${s.oldest || 'n/a'}`);
|
|
168
|
+
console.log(` Newest: ${s.newest || 'n/a'}`);
|
|
169
|
+
console.log(` Avg importance: \x1b[33m${s.avg_importance}\x1b[0m\n`);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case 'export': {
|
|
173
|
+
const data = await mem.exportAll(opts);
|
|
174
|
+
console.log(JSON.stringify(data, null, 2));
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'import': {
|
|
178
|
+
const file = args[1];
|
|
179
|
+
if (!file) return console.log('Usage: memshell import <file.json>');
|
|
180
|
+
const raw = fs.readFileSync(path.resolve(file), 'utf8');
|
|
181
|
+
const data = JSON.parse(raw);
|
|
182
|
+
const r = await mem.importAll(Array.isArray(data) ? data : data.memories || []);
|
|
183
|
+
console.log(`\x1b[32m+\x1b[0m Imported ${r.imported} memories`);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case 'ingest': {
|
|
187
|
+
const { ingestFile, ingest: ingestText } = require('../src/ingest');
|
|
188
|
+
const store = new LocalStore(undefined, useEmbeddings ? { openaiKey: process.env.OPENAI_API_KEY } : {});
|
|
189
|
+
await store.init();
|
|
190
|
+
|
|
191
|
+
if (hasFlag('stdin')) {
|
|
192
|
+
const text = await readStdin();
|
|
193
|
+
if (!text.trim()) return console.log('No input received via stdin.');
|
|
194
|
+
console.log(' Extracting facts from stdin...');
|
|
195
|
+
const result = await ingestText(text, store, { agent });
|
|
196
|
+
console.log(`\x1b[32m+\x1b[0m Extracted: ${result.extracted}, Stored: ${result.stored}, Duplicates: ${result.duplicates}`);
|
|
197
|
+
} else if (hasFlag('watch')) {
|
|
198
|
+
const dir = flag('watch');
|
|
199
|
+
if (!dir || dir === true) return console.log('Usage: memshell ingest --watch <directory>');
|
|
200
|
+
const { watchDirectory } = require('../src/ingest');
|
|
201
|
+
console.log(' Starting directory watcher (Ctrl+C to stop)...');
|
|
202
|
+
watchDirectory(dir, store, { agent });
|
|
203
|
+
// Keep process alive
|
|
204
|
+
process.on('SIGINT', () => { console.log('\n Stopped.'); process.exit(0); });
|
|
205
|
+
} else {
|
|
206
|
+
const file = getText();
|
|
207
|
+
if (!file) return console.log('Usage: memshell ingest <file> | --stdin | --watch <dir>');
|
|
208
|
+
console.log(` Ingesting: ${file}`);
|
|
209
|
+
const result = await ingestFile(file, store, { agent, force: hasFlag('force') });
|
|
210
|
+
if (result.skipped) {
|
|
211
|
+
console.log(` Skipped: ${result.file} (already processed, use --force to re-ingest)`);
|
|
212
|
+
} else {
|
|
213
|
+
console.log(`\x1b[32m+\x1b[0m Extracted: ${result.extracted}, Stored: ${result.stored}, Duplicates: ${result.duplicates}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case 'connect': {
|
|
219
|
+
const target = args[1];
|
|
220
|
+
if (target !== 'openclaw') return console.log('Usage: memshell connect openclaw');
|
|
221
|
+
|
|
222
|
+
const { watchOpenClaw, defaultOpenClawPath, setConfigValue } = require('../src/ingest');
|
|
223
|
+
const store = new LocalStore(undefined, useEmbeddings ? { openaiKey: process.env.OPENAI_API_KEY } : {});
|
|
224
|
+
await store.init();
|
|
225
|
+
|
|
226
|
+
const sessionsPath = args[2] || defaultOpenClawPath();
|
|
227
|
+
setConfigValue('watch.openclaw', sessionsPath);
|
|
228
|
+
console.log(` OpenClaw integration configured.`);
|
|
229
|
+
console.log(` Sessions path: ${sessionsPath}`);
|
|
230
|
+
console.log(' Watching for new transcripts (Ctrl+C to stop)...\n');
|
|
231
|
+
watchOpenClaw(sessionsPath, store, { agent });
|
|
232
|
+
process.on('SIGINT', () => { console.log('\n Stopped.'); process.exit(0); });
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case 'daemon': {
|
|
236
|
+
const { loadConfig, watchDirectory, watchOpenClaw } = require('../src/ingest');
|
|
237
|
+
const store = new LocalStore(undefined, useEmbeddings ? { openaiKey: process.env.OPENAI_API_KEY } : {});
|
|
238
|
+
await store.init();
|
|
239
|
+
|
|
240
|
+
const config = loadConfig();
|
|
241
|
+
const watchers = config.watch || {};
|
|
242
|
+
let activeWatchers = 0;
|
|
243
|
+
|
|
244
|
+
console.log(' \x1b[1mmem.sh daemon\x1b[0m starting...\n');
|
|
245
|
+
|
|
246
|
+
if (watchers.openclaw) {
|
|
247
|
+
watchOpenClaw(watchers.openclaw, store, { agent });
|
|
248
|
+
activeWatchers++;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Support array of dir watchers
|
|
252
|
+
if (Array.isArray(watchers.dirs)) {
|
|
253
|
+
for (const dir of watchers.dirs) {
|
|
254
|
+
watchDirectory(typeof dir === 'string' ? dir : dir.path, store, { agent });
|
|
255
|
+
activeWatchers++;
|
|
256
|
+
}
|
|
257
|
+
} else if (watchers.dir) {
|
|
258
|
+
watchDirectory(watchers.dir, store, { agent });
|
|
259
|
+
activeWatchers++;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (activeWatchers === 0) {
|
|
263
|
+
console.log(' No watchers configured. Use:');
|
|
264
|
+
console.log(' memshell config set watch.openclaw ~/.openclaw/agents/main/sessions/');
|
|
265
|
+
console.log(' memshell config set watch.dir /path/to/watch');
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(`\n ${activeWatchers} watcher(s) active. Ctrl+C to stop.\n`);
|
|
270
|
+
process.on('SIGINT', () => { console.log('\n Daemon stopped.'); process.exit(0); });
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case 'config': {
|
|
274
|
+
const { loadConfig, setConfigValue } = require('../src/ingest');
|
|
275
|
+
const subCmd = args[1];
|
|
276
|
+
|
|
277
|
+
if (subCmd === 'set') {
|
|
278
|
+
const configKey = args[2];
|
|
279
|
+
const configVal = args.slice(3).join(' ');
|
|
280
|
+
if (!configKey || !configVal) return console.log('Usage: memshell config set <key> <value>');
|
|
281
|
+
const result = setConfigValue(configKey, configVal);
|
|
282
|
+
console.log(`\x1b[32m+\x1b[0m Set ${configKey} = ${configVal}`);
|
|
283
|
+
} else if (subCmd === 'get') {
|
|
284
|
+
const config = loadConfig();
|
|
285
|
+
const configKey = args[2];
|
|
286
|
+
if (configKey) {
|
|
287
|
+
const parts = configKey.split('.');
|
|
288
|
+
let val = config;
|
|
289
|
+
for (const p of parts) val = val?.[p];
|
|
290
|
+
console.log(val !== undefined ? JSON.stringify(val, null, 2) : 'Not set');
|
|
291
|
+
} else {
|
|
292
|
+
console.log(JSON.stringify(config, null, 2));
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
const config = loadConfig();
|
|
296
|
+
console.log(JSON.stringify(config, null, 2));
|
|
297
|
+
}
|
|
87
298
|
break;
|
|
88
299
|
}
|
|
89
300
|
case 'serve': case 'server': {
|
|
@@ -91,6 +302,7 @@ async function main() {
|
|
|
91
302
|
const authKey = flag('key') || process.env.MEM_KEY || '';
|
|
92
303
|
process.env.MEM_PORT = port;
|
|
93
304
|
if (authKey) process.env.MEM_KEY = authKey;
|
|
305
|
+
if (useEmbeddings) process.env.MEM_USE_EMBEDDINGS = '1';
|
|
94
306
|
require('../server');
|
|
95
307
|
break;
|
|
96
308
|
}
|
|
@@ -99,4 +311,4 @@ async function main() {
|
|
|
99
311
|
}
|
|
100
312
|
}
|
|
101
313
|
|
|
102
|
-
main().catch(e => { console.error('
|
|
314
|
+
main().catch(e => { console.error('\x1b[31mError:\x1b[0m', e.message); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memshell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Persistent memory for AI agents. Like localStorage but for LLMs.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -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
|
}
|
|
@@ -19,44 +27,86 @@ app.use('/mem', (req, res, next) => {
|
|
|
19
27
|
next();
|
|
20
28
|
});
|
|
21
29
|
|
|
30
|
+
// Ingest raw text
|
|
31
|
+
app.post('/mem/ingest', async (req, res) => {
|
|
32
|
+
const { text, source } = req.body;
|
|
33
|
+
if (!text) return res.status(400).json({ error: 'text is required' });
|
|
34
|
+
try {
|
|
35
|
+
const { ingest } = require('./src/ingest');
|
|
36
|
+
const result = await ingest(text, store, { agent: req.agent, source: source || 'api' });
|
|
37
|
+
res.json(result);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
res.status(500).json({ error: e.message });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
22
43
|
// Store a memory
|
|
23
|
-
app.post('/mem', (req, res) => {
|
|
24
|
-
const { text, metadata } = req.body;
|
|
44
|
+
app.post('/mem', async (req, res) => {
|
|
45
|
+
const { text, tags, importance, metadata } = req.body;
|
|
25
46
|
if (!text) return res.status(400).json({ error: 'text is required' });
|
|
26
|
-
const result = store.set(text, { agent: req.agent, metadata });
|
|
47
|
+
const result = await store.set(text, { agent: req.agent, tags: tags || '', importance, metadata });
|
|
27
48
|
res.json(result);
|
|
28
49
|
});
|
|
29
50
|
|
|
30
51
|
// Semantic recall
|
|
31
|
-
app.get('/mem/recall', (req, res) => {
|
|
52
|
+
app.get('/mem/recall', async (req, res) => {
|
|
32
53
|
const q = req.query.q;
|
|
33
54
|
if (!q) return res.status(400).json({ error: 'q parameter is required' });
|
|
34
55
|
const limit = parseInt(req.query.limit) || 10;
|
|
35
|
-
const
|
|
56
|
+
const tags = req.query.tags || '';
|
|
57
|
+
const top = req.query.top ? parseInt(req.query.top) : null;
|
|
58
|
+
const results = await store.recall(q, { agent: req.agent, limit, tags, top });
|
|
36
59
|
res.json(results);
|
|
37
60
|
});
|
|
38
61
|
|
|
39
62
|
// List all
|
|
40
|
-
app.get('/mem/list', (req, res) => {
|
|
41
|
-
const results = store.list({ agent: req.agent });
|
|
63
|
+
app.get('/mem/list', async (req, res) => {
|
|
64
|
+
const results = await store.list({ agent: req.agent });
|
|
42
65
|
res.json(results);
|
|
43
66
|
});
|
|
44
67
|
|
|
68
|
+
// Stats
|
|
69
|
+
app.get('/mem/stats', async (req, res) => {
|
|
70
|
+
const stats = await store.stats({ agent: req.agent });
|
|
71
|
+
res.json(stats);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Export
|
|
75
|
+
app.get('/mem/export', async (req, res) => {
|
|
76
|
+
const data = await store.exportAll({ agent: req.query.agent });
|
|
77
|
+
res.json(data);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Import
|
|
81
|
+
app.post('/mem/import', async (req, res) => {
|
|
82
|
+
const memories = Array.isArray(req.body) ? req.body : req.body.memories || [];
|
|
83
|
+
const result = await store.importAll(memories);
|
|
84
|
+
res.json(result);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Boost importance
|
|
88
|
+
app.post('/mem/:id/important', async (req, res) => {
|
|
89
|
+
const result = await store.important(req.params.id);
|
|
90
|
+
if (!result) return res.status(404).json({ error: 'Memory not found' });
|
|
91
|
+
res.json(result);
|
|
92
|
+
});
|
|
93
|
+
|
|
45
94
|
// Delete by id
|
|
46
|
-
app.delete('/mem/:id', (req, res) => {
|
|
47
|
-
store.forget(req.params.id);
|
|
95
|
+
app.delete('/mem/:id', async (req, res) => {
|
|
96
|
+
await store.forget(req.params.id);
|
|
48
97
|
res.json({ ok: true, id: req.params.id });
|
|
49
98
|
});
|
|
50
99
|
|
|
51
100
|
// Clear all for agent
|
|
52
|
-
app.delete('/mem', (req, res) => {
|
|
53
|
-
store.clear({ agent: req.agent });
|
|
101
|
+
app.delete('/mem', async (req, res) => {
|
|
102
|
+
await store.clear({ agent: req.agent });
|
|
54
103
|
res.json({ ok: true });
|
|
55
104
|
});
|
|
56
105
|
|
|
57
106
|
app.listen(PORT, () => {
|
|
58
107
|
console.log(`\n 🧠 mem.sh server running on http://localhost:${PORT}`);
|
|
59
108
|
console.log(` Auth: ${AUTH_KEY ? 'enabled' : 'disabled'}`);
|
|
109
|
+
console.log(` Embeddings: ${storeOpts.openaiKey ? 'OpenAI' : 'TF-IDF'}`);
|
|
60
110
|
console.log();
|
|
61
111
|
});
|
|
62
112
|
|