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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <h1>mem.sh</h1>
4
4
 
5
- <p><strong>Persistent memory for AI agents.</strong><br>One line to save. One line to recall.</p>
5
+ <p><strong>Persistent memory for AI agents.</strong><br>One line to save. One line to recall. Auto-ingest conversations.</p>
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/memshell.svg?style=flat-square)](https://www.npmjs.com/package/memshell)
8
8
  [![license](https://img.shields.io/npm/l/memshell.svg?style=flat-square)](https://github.com/justedv/mem.sh/blob/main/LICENSE)
@@ -10,7 +10,7 @@
10
10
 
11
11
  <br>
12
12
 
13
- [Quick Start](#quick-start) · [SDK](#sdk) · [API Server](#api-server) · [Architecture](#how-it-works) · [Contributing](CONTRIBUTING.md)
13
+ [Quick Start](#quick-start) · [Auto-Ingest](#auto-ingest) · [OpenClaw Integration](#openclaw-integration) · [SDK](#sdk) · [API Server](#api-server) · [Architecture](#how-it-works)
14
14
 
15
15
  </div>
16
16
 
@@ -29,17 +29,20 @@ Agents forget everything between sessions. **mem.sh** gives them a brain.
29
29
  | | mem.sh | LangChain Memory | Roll your own |
30
30
  |---|---|---|---|
31
31
  | **Setup** | `npx memshell set "..."` | 47 dependencies + config | Hours of boilerplate |
32
- | **External APIs** | None | OpenAI key required | Depends |
32
+ | **Auto-ingest** | Built-in | No | You build it |
33
+ | **External APIs** | None (optional) | OpenAI key required | Depends |
33
34
  | **Semantic search** | Built-in TF-IDF | Embedding models | You build it |
34
35
  | **Storage** | SQLite (local) | Varies | You choose |
35
- | **Lines of code** | ~1 | ~50+ | ~200+ |
36
36
 
37
37
  ## Features
38
38
 
39
- - **Fast** TF-IDF vectorization with cosine similarity, instant results
40
- - **Local-first** SQLite storage at `~/.mem/mem.db`, no data leaves your machine
41
- - **Semantic** Recall by meaning, not exact match
42
- - **Zero config** `npx` and go. No API keys, no setup, no dependencies
39
+ - **Fast** -- TF-IDF vectorization with cosine similarity, instant results
40
+ - **Local-first** -- SQLite storage at `~/.mem/mem.db`, no data leaves your machine
41
+ - **Semantic** -- Recall by meaning, not exact match
42
+ - **Auto-ingest** -- Feed raw conversations, auto-extract key facts via LLM
43
+ - **OpenClaw integration** -- Watch session transcripts and auto-learn
44
+ - **Zero config** -- `npx` and go. No API keys needed for core features
45
+ - **Smart recall** -- Shows source, creation time, and recall frequency
43
46
 
44
47
  ## Quick Start
45
48
 
@@ -51,7 +54,7 @@ npx memshell set "user prefers dark mode"
51
54
 
52
55
  # Recall semantically
53
56
  npx memshell recall "what theme does the user like?"
54
- # user prefers dark mode (score: 0.87)
57
+ # => user prefers dark mode (score: 0.87)
55
58
 
56
59
  # List all memories
57
60
  npx memshell list
@@ -63,7 +66,92 @@ npx memshell forget <id>
63
66
  npx memshell clear
64
67
  ```
65
68
 
66
- ### SDK
69
+ ## Auto-Ingest
70
+
71
+ Feed raw conversations and let the LLM extract key facts automatically.
72
+
73
+ Requires `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` (or configure via `memshell config set apiKey <key>`).
74
+
75
+ ### From a file
76
+
77
+ ```bash
78
+ npx memshell ingest conversation.txt
79
+ npx memshell ingest chat.jsonl
80
+ npx memshell ingest notes.md
81
+ ```
82
+
83
+ ### From stdin
84
+
85
+ ```bash
86
+ echo "User said they prefer dark mode and use vim" | npx memshell ingest --stdin
87
+ ```
88
+
89
+ ### Watch a directory
90
+
91
+ ```bash
92
+ npx memshell ingest --watch ./logs/
93
+ ```
94
+
95
+ Watches for new or changed `.txt`, `.md`, `.json`, and `.jsonl` files. Tracks what has been processed to avoid duplicates.
96
+
97
+ ### Via API
98
+
99
+ ```bash
100
+ curl -X POST http://localhost:3456/mem/ingest \
101
+ -H "Content-Type: application/json" \
102
+ -d '{"text": "User mentioned they love Rust and prefer dark themes"}'
103
+ # => {"extracted": 2, "stored": 2, "duplicates": 0}
104
+ ```
105
+
106
+ ### How it works
107
+
108
+ 1. Text is split into ~2000-token chunks
109
+ 2. Each chunk is sent to an LLM (gpt-4o-mini or claude-3-haiku) to extract standalone facts
110
+ 3. Facts are deduplicated against existing memories (Jaccard similarity > 0.85 = skip)
111
+ 4. New facts are stored with auto-generated tags and source tracking
112
+
113
+ ## OpenClaw Integration
114
+
115
+ Automatically learn from your OpenClaw agent conversations:
116
+
117
+ ```bash
118
+ # Start watching OpenClaw session transcripts
119
+ npx memshell connect openclaw
120
+
121
+ # Or specify a custom path
122
+ npx memshell connect openclaw /path/to/sessions/
123
+ ```
124
+
125
+ This watches the OpenClaw sessions directory (`~/.openclaw/agents/main/sessions/` by default), parses JSONL transcripts, and auto-ingests new conversations.
126
+
127
+ ### Daemon mode
128
+
129
+ Run continuous ingestion in the background:
130
+
131
+ ```bash
132
+ # Configure watchers first
133
+ npx memshell config set watch.openclaw ~/.openclaw/agents/main/sessions/
134
+
135
+ # Start the daemon
136
+ npx memshell daemon
137
+ ```
138
+
139
+ ### Configuration
140
+
141
+ ```bash
142
+ # Set LLM API key
143
+ npx memshell config set apiKey sk-...
144
+
145
+ # Set model
146
+ npx memshell config set model gpt-4o-mini
147
+
148
+ # View config
149
+ npx memshell config get
150
+ ```
151
+
152
+ Config is stored at `~/.mem/config.json`.
153
+
154
+ ## SDK
67
155
 
68
156
  ```js
69
157
  const mem = require('memshell');
@@ -72,9 +160,9 @@ const mem = require('memshell');
72
160
  await mem.set('user prefers dark mode');
73
161
  await mem.set('favorite language is rust', { agent: 'coder-bot' });
74
162
 
75
- // Recall (semantic search)
163
+ // Recall (semantic search) -- now includes source and recall count
76
164
  const results = await mem.recall('what does the user like?');
77
- // [{ id, text, score, created_at }]
165
+ // [{ id, text, score, created_at, source, recall_count }]
78
166
 
79
167
  // List all
80
168
  const all = await mem.list();
@@ -92,7 +180,7 @@ mem.sh uses **TF-IDF vectorization** with **cosine similarity** for semantic sea
92
180
 
93
181
  Memories are stored in `~/.mem/mem.db` (SQLite). Each memory is tokenized and vectorized on write. Queries are vectorized at recall time and ranked by cosine similarity against stored vectors.
94
182
 
95
- [Full architecture docs](docs/ARCHITECTURE.md)
183
+ Optional: Enable OpenAI embeddings with `--embeddings` flag for higher quality recall (requires `OPENAI_API_KEY`).
96
184
 
97
185
  ## API Server
98
186
 
@@ -107,29 +195,19 @@ npx memshell serve --port 3456 --key my-secret-key
107
195
  | Method | Path | Description |
108
196
  |--------|------|-------------|
109
197
  | `POST` | `/mem` | Store a memory |
198
+ | `POST` | `/mem/ingest` | Auto-ingest raw text |
110
199
  | `GET` | `/mem/recall?q=` | Semantic recall |
111
200
  | `GET` | `/mem/list` | List all memories |
201
+ | `GET` | `/mem/stats` | Memory statistics |
202
+ | `GET` | `/mem/export` | Export all memories |
203
+ | `POST` | `/mem/import` | Import memories |
112
204
  | `DELETE` | `/mem/:id` | Delete a memory |
113
205
  | `DELETE` | `/mem` | Clear all memories |
114
206
 
115
207
  ### Headers
116
208
 
117
- - `X-Mem-Key` API key (required if `--key` is set)
118
- - `X-Mem-Agent` Agent namespace (optional, isolates memories per agent)
119
-
120
- ### Example
121
-
122
- ```bash
123
- # Store
124
- curl -X POST http://localhost:3456/mem \
125
- -H "Content-Type: application/json" \
126
- -H "X-Mem-Key: my-secret-key" \
127
- -d '{"text": "user prefers dark mode"}'
128
-
129
- # Recall
130
- curl "http://localhost:3456/mem/recall?q=theme+preference" \
131
- -H "X-Mem-Key: my-secret-key"
132
- ```
209
+ - `X-Mem-Key` -- API key (required if `--key` is set)
210
+ - `X-Mem-Agent` -- Agent namespace (optional, isolates memories per agent)
133
211
 
134
212
  ### SDK with API Mode
135
213
 
@@ -146,9 +224,27 @@ await mem.set('user prefers dark mode');
146
224
  const results = await mem.recall('theme preference');
147
225
  ```
148
226
 
149
- ## Contributing
227
+ ## All CLI Commands
150
228
 
151
- We welcome contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
229
+ ```
230
+ memshell set <text> Store a memory
231
+ memshell recall <query> Semantic recall
232
+ memshell list List all memories
233
+ memshell forget <id> Delete a memory by ID
234
+ memshell clear Wipe all memories
235
+ memshell important <id> Boost memory importance
236
+ memshell ingest <file> Extract facts from a file
237
+ memshell ingest --stdin Extract facts from piped text
238
+ memshell ingest --watch <dir> Watch directory for new files
239
+ memshell connect openclaw Watch OpenClaw transcripts
240
+ memshell daemon Run continuous ingestion
241
+ memshell config set <key> <val> Set config value
242
+ memshell config get [key] Show config
243
+ memshell stats Show memory statistics
244
+ memshell export Export all memories as JSON
245
+ memshell import <file.json> Import memories from JSON
246
+ memshell serve [--port N] Start API server
247
+ ```
152
248
 
153
249
  ## License
154
250
 
package/bin/mem.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
- mem.sh — persistent memory for AI agents
13
+ \x1b[1mmem.sh\x1b[0m — persistent memory for AI agents
12
14
 
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
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
- Options:
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
- Examples:
27
- mem set "user prefers dark mode"
28
- mem recall "what theme?"
29
- mem list
30
- mem forget 3
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
- return args[i + 1] || true;
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
- if (api) mem.configure({ api, key, agent });
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 = 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})`);
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 = args.slice(1).filter(a => !a.startsWith('--')).join(' ').replace(/^["']|["']$/g, '');
60
- if (!query) return console.log('Usage: mem recall <query>');
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('No memories found.');
122
+ if (!results.length) return console.log('\x1b[33mNo memories found.\x1b[0m');
63
123
  for (const r of results) {
64
- console.log(` [${r.id}] ${r.text} (score: ${r.score})`);
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('No memories stored.');
133
+ if (!all.length) return console.log('\x1b[33mNo memories stored.\x1b[0m');
71
134
  for (const r of all) {
72
- console.log(` [${r.id}] ${r.text} (${r.created_at})`);
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: mem forget <id>');
145
+ if (!id) return console.log('Usage: memshell forget <id>');
80
146
  await mem.forget(id);
81
- console.log(`✓ Forgotten (id: ${id})`);
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(' All memories cleared');
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('Error:', e.message); process.exit(1); });
314
+ main().catch(e => { console.error('\x1b[31mError:\x1b[0m', e.message); process.exit(1); });