memshell 0.1.2 → 0.2.1
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/memshell.js +102 -0
- package/package.json +4 -6
- package/src/index.js +46 -31
package/bin/memshell.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const mem = require('../src/index');
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const cmd = args[0];
|
|
8
|
+
const rest = args.slice(1).join(' ');
|
|
9
|
+
|
|
10
|
+
const HELP = `
|
|
11
|
+
mem.sh — persistent memory for AI agents
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
mem set <text> Store a memory
|
|
15
|
+
mem recall <query> Semantic recall
|
|
16
|
+
mem list List all memories
|
|
17
|
+
mem forget <id> Delete a memory by ID
|
|
18
|
+
mem clear Wipe all memories
|
|
19
|
+
mem serve [--port N] Start API server
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--agent <name> Agent namespace
|
|
23
|
+
--api <url> Use remote API instead of local
|
|
24
|
+
--key <key> API key for remote server
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
mem set "user prefers dark mode"
|
|
28
|
+
mem recall "what theme?"
|
|
29
|
+
mem list
|
|
30
|
+
mem forget 3
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
// Parse flags
|
|
34
|
+
function flag(name) {
|
|
35
|
+
const i = args.indexOf('--' + name);
|
|
36
|
+
if (i === -1) return null;
|
|
37
|
+
return args[i + 1] || true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
const agent = flag('agent') || 'default';
|
|
42
|
+
const api = flag('api');
|
|
43
|
+
const key = flag('key');
|
|
44
|
+
|
|
45
|
+
if (api) mem.configure({ api, key, agent });
|
|
46
|
+
else mem.configure({ agent });
|
|
47
|
+
|
|
48
|
+
const opts = { agent };
|
|
49
|
+
|
|
50
|
+
switch (cmd) {
|
|
51
|
+
case 'set': case 's': case 'save': case 'remember': {
|
|
52
|
+
const text = args.slice(1).filter(a => !a.startsWith('--')).join(' ').replace(/^["']|["']$/g, '');
|
|
53
|
+
if (!text) return console.log('Usage: mem set <text>');
|
|
54
|
+
const r = await mem.set(text, opts);
|
|
55
|
+
console.log(`✓ Stored (id: ${r.id})`);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case 'recall': case 'r': case 'search': case 'q': {
|
|
59
|
+
const query = args.slice(1).filter(a => !a.startsWith('--')).join(' ').replace(/^["']|["']$/g, '');
|
|
60
|
+
if (!query) return console.log('Usage: mem recall <query>');
|
|
61
|
+
const results = await mem.recall(query, opts);
|
|
62
|
+
if (!results.length) return console.log('No memories found.');
|
|
63
|
+
for (const r of results) {
|
|
64
|
+
console.log(` [${r.id}] ${r.text} (score: ${r.score})`);
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case 'list': case 'ls': case 'l': {
|
|
69
|
+
const all = await mem.list(opts);
|
|
70
|
+
if (!all.length) return console.log('No memories stored.');
|
|
71
|
+
for (const r of all) {
|
|
72
|
+
console.log(` [${r.id}] ${r.text} (${r.created_at})`);
|
|
73
|
+
}
|
|
74
|
+
console.log(`\n ${all.length} memor${all.length === 1 ? 'y' : 'ies'}`);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case 'forget': case 'delete': case 'rm': {
|
|
78
|
+
const id = args[1];
|
|
79
|
+
if (!id) return console.log('Usage: mem forget <id>');
|
|
80
|
+
await mem.forget(id);
|
|
81
|
+
console.log(`✓ Forgotten (id: ${id})`);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case 'clear': case 'wipe': case 'reset': {
|
|
85
|
+
await mem.clear(opts);
|
|
86
|
+
console.log('✓ All memories cleared');
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case 'serve': case 'server': {
|
|
90
|
+
const port = flag('port') || 3456;
|
|
91
|
+
const authKey = flag('key') || process.env.MEM_KEY || '';
|
|
92
|
+
process.env.MEM_PORT = port;
|
|
93
|
+
if (authKey) process.env.MEM_KEY = authKey;
|
|
94
|
+
require('../server');
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
console.log(HELP);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
main().catch(e => { console.error('Error:', e.message); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memshell",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Persistent memory for AI agents. Like localStorage but for LLMs.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
8
|
-
"mem.sh": "./bin/mem.js"
|
|
7
|
+
"memshell": "bin/memshell.js"
|
|
9
8
|
},
|
|
10
9
|
"scripts": {
|
|
11
10
|
"start": "node server.js",
|
|
@@ -17,7 +16,7 @@
|
|
|
17
16
|
"memory",
|
|
18
17
|
"llm",
|
|
19
18
|
"semantic-search",
|
|
20
|
-
"
|
|
19
|
+
"tfidf",
|
|
21
20
|
"chatgpt",
|
|
22
21
|
"langchain",
|
|
23
22
|
"vector",
|
|
@@ -26,7 +25,6 @@
|
|
|
26
25
|
"author": "justedv",
|
|
27
26
|
"license": "MIT",
|
|
28
27
|
"dependencies": {
|
|
29
|
-
"better-sqlite3": "^11.0.0",
|
|
30
28
|
"express": "^4.18.2"
|
|
31
29
|
},
|
|
32
30
|
"repository": {
|
|
@@ -34,4 +32,4 @@
|
|
|
34
32
|
"url": "https://github.com/justedv/mem.sh"
|
|
35
33
|
},
|
|
36
34
|
"homepage": "https://justedv.github.io/mem.sh"
|
|
37
|
-
}
|
|
35
|
+
}
|
package/src/index.js
CHANGED
|
@@ -64,60 +64,75 @@ class TfIdf {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// ──
|
|
67
|
+
// ── JSON File Store ────────────────────────────────────────────
|
|
68
68
|
class LocalStore {
|
|
69
69
|
constructor(dir) {
|
|
70
70
|
this.dir = dir || path.join(os.homedir(), '.mem');
|
|
71
|
-
this.dbPath = path.join(this.dir, 'mem.
|
|
71
|
+
this.dbPath = path.join(this.dir, 'mem.json');
|
|
72
72
|
this.tfidf = new TfIdf();
|
|
73
|
-
this.
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.
|
|
81
|
-
|
|
82
|
-
this.
|
|
83
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
84
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
85
|
-
text TEXT NOT NULL,
|
|
86
|
-
agent TEXT DEFAULT 'default',
|
|
87
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
88
|
-
metadata TEXT DEFAULT '{}'
|
|
89
|
-
)
|
|
90
|
-
`);
|
|
73
|
+
this._data = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_load() {
|
|
77
|
+
if (this._data) return this._data;
|
|
78
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
79
|
+
try {
|
|
80
|
+
this._data = JSON.parse(fs.readFileSync(this.dbPath, 'utf8'));
|
|
81
|
+
} catch {
|
|
82
|
+
this._data = { nextId: 1, memories: [] };
|
|
91
83
|
}
|
|
92
|
-
return this.
|
|
84
|
+
return this._data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_save() {
|
|
88
|
+
fs.writeFileSync(this.dbPath, JSON.stringify(this._data, null, 2));
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
set(text, opts = {}) {
|
|
92
|
+
const data = this._load();
|
|
96
93
|
const agent = opts.agent || 'default';
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
const entry = {
|
|
95
|
+
id: data.nextId++,
|
|
96
|
+
text,
|
|
97
|
+
agent,
|
|
98
|
+
created_at: new Date().toISOString(),
|
|
99
|
+
metadata: opts.metadata || {}
|
|
100
|
+
};
|
|
101
|
+
data.memories.push(entry);
|
|
102
|
+
this._save();
|
|
103
|
+
return { id: entry.id, text, agent };
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
recall(query, opts = {}) {
|
|
107
|
+
const data = this._load();
|
|
103
108
|
const agent = opts.agent || 'default';
|
|
104
109
|
const limit = opts.limit || 10;
|
|
105
|
-
const rows =
|
|
110
|
+
const rows = data.memories.filter(m => m.agent === agent);
|
|
106
111
|
return this.tfidf.rank(query, rows).slice(0, limit);
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
list(opts = {}) {
|
|
115
|
+
const data = this._load();
|
|
110
116
|
const agent = opts.agent || 'default';
|
|
111
|
-
return
|
|
117
|
+
return data.memories.filter(m => m.agent === agent).sort((a, b) => b.id - a.id);
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
forget(id) {
|
|
115
|
-
|
|
121
|
+
const data = this._load();
|
|
122
|
+
const numId = Number(id);
|
|
123
|
+
const before = data.memories.length;
|
|
124
|
+
data.memories = data.memories.filter(m => m.id !== numId);
|
|
125
|
+
this._save();
|
|
126
|
+
return { changes: before - data.memories.length };
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
clear(opts = {}) {
|
|
130
|
+
const data = this._load();
|
|
119
131
|
const agent = opts.agent || 'default';
|
|
120
|
-
|
|
132
|
+
const before = data.memories.length;
|
|
133
|
+
data.memories = data.memories.filter(m => m.agent !== agent);
|
|
134
|
+
this._save();
|
|
135
|
+
return { changes: before - data.memories.length };
|
|
121
136
|
}
|
|
122
137
|
}
|
|
123
138
|
|
|
@@ -129,9 +144,9 @@ class ApiClient {
|
|
|
129
144
|
this.agent = opts.agent || 'default';
|
|
130
145
|
}
|
|
131
146
|
|
|
132
|
-
_req(method,
|
|
147
|
+
_req(method, urlPath, body) {
|
|
133
148
|
return new Promise((resolve, reject) => {
|
|
134
|
-
const url = new URL(this.base +
|
|
149
|
+
const url = new URL(this.base + urlPath);
|
|
135
150
|
const mod = url.protocol === 'https:' ? https : http;
|
|
136
151
|
const headers = { 'Content-Type': 'application/json' };
|
|
137
152
|
if (this.key) headers['X-Mem-Key'] = this.key;
|
|
@@ -153,7 +168,7 @@ class ApiClient {
|
|
|
153
168
|
recall(query, opts = {}) { return this._req('GET', `/mem/recall?q=${encodeURIComponent(query)}&limit=${opts.limit || 10}`); }
|
|
154
169
|
list() { return this._req('GET', '/mem/list'); }
|
|
155
170
|
forget(id) { return this._req('DELETE', `/mem/${id}`); }
|
|
156
|
-
clear() { return this._req('DELETE', '/mem'); }
|
|
171
|
+
clear() { return this._req('DELETE', '/mem?confirm=true'); }
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
// ── Exports ────────────────────────────────────────────────────
|