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.
@@ -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.2",
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
- "mem": "./bin/mem.js",
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
- "embeddings",
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
- // ── Local Store ────────────────────────────────────────────────
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.db');
71
+ this.dbPath = path.join(this.dir, 'mem.json');
72
72
  this.tfidf = new TfIdf();
73
- this._db = null;
74
- }
75
-
76
- get db() {
77
- if (!this._db) {
78
- fs.mkdirSync(this.dir, { recursive: true });
79
- const Database = require('better-sqlite3');
80
- this._db = new Database(this.dbPath);
81
- this._db.pragma('journal_mode = WAL');
82
- this._db.exec(`
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._db;
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 meta = JSON.stringify(opts.metadata || {});
98
- const info = this.db.prepare('INSERT INTO memories (text, agent, metadata) VALUES (?, ?, ?)').run(text, agent, meta);
99
- return { id: info.lastInsertRowid, text, agent };
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 = this.db.prepare('SELECT * FROM memories WHERE agent = ?').all(agent);
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 this.db.prepare('SELECT * FROM memories WHERE agent = ? ORDER BY created_at DESC').all(agent);
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
- return this.db.prepare('DELETE FROM memories WHERE id = ?').run(id);
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
- return this.db.prepare('DELETE FROM memories WHERE agent = ?').run(agent);
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, path, body) {
147
+ _req(method, urlPath, body) {
133
148
  return new Promise((resolve, reject) => {
134
- const url = new URL(this.base + path);
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 ────────────────────────────────────────────────────