vektor-slipstream 1.1.0 → 1.1.2

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
@@ -1,252 +1,52 @@
1
- \# vektor-slipstream
2
-
3
-
4
-
5
- \*\*Hardware-accelerated persistent memory for AI agents.\*\*
6
-
7
- Local-first. Zero cloud dependency. $0 embedding cost.
8
-
9
-
10
-
11
- ```bash
12
-
13
- npm install vektor-slipstream
14
-
15
- ```
16
-
17
-
18
-
19
- \---
20
-
21
-
22
-
23
- \## What it is
24
-
25
-
26
-
27
- A drop-in memory layer for any AI agent. Your agent remembers everything across sessions — preferences, decisions, research, conversations — stored in a single portable SQLite file on your machine.
28
-
29
-
30
-
31
- No OpenAI embedding bill. No cloud roundtrip. No API key for memory.
32
-
33
-
34
-
35
- ```js
36
-
37
- const { createMemory } = require('vektor-slipstream');
38
-
39
-
40
-
41
- const memory = await createMemory({ agentId: 'my-agent' });
42
-
43
-
44
-
45
- // Store a memory
46
-
47
- await memory.remember('User prefers TypeScript over JavaScript');
48
-
49
-
50
-
51
- // Recall by semantic similarity — avg 8ms, fully local
52
-
53
- const results = await memory.recall('coding preferences');
54
-
55
- // → \[{ content: 'User prefers TypeScript...', score: 0.97, id: 1 }]
56
-
57
- ```
58
-
59
-
60
-
61
- \---
62
-
63
-
64
-
65
- \## How it works
66
-
67
-
68
-
69
- \- \*\*Local ONNX embeddings\*\* — `all-MiniLM-L6-v2 INT8` runs on your hardware via `onnxruntime-node`. No API calls.
70
-
71
- \- \*\*Hyper-PRAGMA SQLite\*\* — WAL mode, 1GB mmap, 64MB cache. Recall at RAM speeds.
72
-
73
- \- \*\*MAGMA graph\*\* — 4-layer associative graph (semantic · causal · temporal · entity). Memories connect to each other.
74
-
75
- \- \*\*Hardware auto-detection\*\* — uses CUDA on NVIDIA, CoreML on Apple Silicon, CPU everywhere else.
76
-
77
-
78
-
79
- \---
80
-
81
-
82
-
83
- \## API
84
-
85
-
86
-
87
- ```js
88
-
89
- const { createMemory } = require('vektor-slipstream');
90
-
91
-
92
-
93
- const memory = await createMemory({
94
-
95
-   agentId: 'my-agent', // isolates memories per agent
96
-
97
-   dbPath: './memory.db', // default: ./slipstream-memory.db
98
-
99
-   silent: false, // suppress boot banner
100
-
101
- });
102
-
103
- ```
104
-
105
-
106
-
107
- \### `memory.remember(text, opts?)`
108
-
109
- Store a memory with its vector embedding.
110
-
111
- ```js
112
-
113
- const { id } = await memory.remember('User is based in Brisbane, AU');
114
-
115
- const { id } = await memory.remember('Closed Series A at $4M', { importance: 5 });
116
-
117
- ```
118
-
119
-
120
-
121
- \### `memory.recall(query, topK?)`
122
-
123
- Semantic recall — returns top-k most relevant memories.
124
-
125
- ```js
126
-
127
- const results = await memory.recall('user location', 5);
128
-
129
- // → \[{ id, content, score, importance }]
130
-
131
- ```
132
-
133
-
134
-
135
- \### `memory.graph(concept, opts?)`
136
-
137
- Breadth-first traversal from a concept — finds connected memories.
138
-
139
- ```js
140
-
141
- const { nodes, edges } = await memory.graph('fundraising', { hops: 2 });
142
-
143
- ```
144
-
145
-
146
-
147
- \### `memory.delta(topic, days?)`
148
-
149
- What changed on a topic in the last N days.
150
-
151
- ```js
152
-
153
- const changes = await memory.delta('project status', 7);
154
-
155
- ```
156
-
157
-
158
-
159
- \### `memory.briefing()`
160
-
161
- Summary of everything learned in the last 24 hours. Inject into system prompt.
162
-
163
- ```js
164
-
165
- const brief = await memory.briefing();
166
-
167
- // → "\[SLIPSTREAM BRIEFING — last 24h — 12 memories]\\n1. ..."
168
-
169
- ```
170
-
171
-
172
-
173
- \---
174
-
175
-
176
-
177
- \## Examples
178
-
179
-
180
-
181
- Three production-ready agent examples are included in `examples/`:
182
-
183
-
184
-
185
- | File | Description |
186
-
187
- |------|-------------|
188
-
189
- | `example-langchain-researcher.js` | LangChain agent that builds a persistent knowledge base |
190
-
191
- | `example-openai-assistant.js` | OpenAI assistant with automatic cross-session memory |
192
-
193
- | `example-claude-mcp.js` | Claude MCP server + direct chat mode |
194
-
195
-
196
-
197
- See \[`examples/README.md`](examples/README.md) for setup and usage.
198
-
199
-
200
-
201
- \---
202
-
203
-
204
-
205
- \## Performance
206
-
207
-
208
-
209
- | Metric | Value |
210
-
211
- |--------|-------|
212
-
213
- | Recall latency | \~8ms avg (local SQLite) |
214
-
215
- | Embedding cost | $0 — fully local ONNX |
216
-
217
- | Embedding latency | \~10ms GPU / \~25ms CPU (post-warmup) |
218
-
219
- | DB engine | SQLite WAL + 1GB mmap |
220
-
221
- | Vector dimensions | 384 (all-MiniLM-L6-v2 INT8) |
222
-
223
-
224
-
225
- \---
226
-
227
-
228
-
229
- \## Requirements
230
-
231
-
232
-
233
- \- Node.js 18+
234
-
235
- \- \~25MB disk for the ONNX model (bundled)
236
-
237
-
238
-
239
- \---
240
-
241
-
242
-
243
- \## License
244
-
245
-
246
-
247
- Commercial licence — see LICENSE file.
248
-
249
- One-time purchase includes all future updates.
250
-
251
- Purchase at \[vektormemory.com](https://vektormemory.com)
252
-
1
+ # vektor-slipstream
2
+
3
+ Hardware-accelerated persistent memory for AI agents. Local-first. No cloud. One-time payment.
4
+
5
+ ## Install
6
+ ```bash
7
+ npm install vektor-slipstream
8
+ npx vektor setup
9
+ ```
10
+
11
+ ## Quick Start
12
+ ```js
13
+ const { createMemory } = require('vektor-slipstream');
14
+
15
+ const memory = await createMemory({
16
+ agentId: 'my-agent',
17
+ licenceKey: process.env.VEKTOR_LICENCE_KEY,
18
+ });
19
+
20
+ await memory.remember('User prefers TypeScript over JavaScript');
21
+ const results = await memory.recall('coding preferences');
22
+ // → [{ content, score, id }]
23
+ ```
24
+
25
+ ## CLI
26
+ ```bash
27
+ npx vektor setup # First-run wizard
28
+ npx vektor activate # Activate licence key
29
+ npx vektor test # Test memory engine
30
+ npx vektor status # System health check
31
+ npx vektor mcp # Start Claude MCP server
32
+ npx vektor rem # Run REM dream cycle
33
+ npx vektor help # All commands
34
+ ```
35
+
36
+ ## What's Included
37
+
38
+ - **MAGMA graph** — 4-layer associative memory (semantic, causal, temporal, entity)
39
+ - **AUDN curation** — zero contradictions, zero duplicates
40
+ - **REM dream cycle** — 50:1 memory compression
41
+ - **Claude MCP server** persistent memory for Claude Desktop
42
+ - **Cloak** — stealth browser, AES-256 vault, layout sensor
43
+ - **Mistral bridge** — vektor_memoire tool for Le Chat
44
+ - **LangChain · OpenAI · Gemini · Groq · Ollama** — all supported
45
+ - **Local ONNX embeddings** — $0 embedding cost, no API key
46
+ - **Single SQLite file** — portable, yours forever
47
+
48
+ ## Licence
49
+
50
+ Commercial licence. One-time payment. 3-machine activation via Polar.
51
+ Purchase at: https://vektormemory.com/product#pricing
52
+ Support: hello@vektormemory.com
@@ -1,305 +1,184 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
1
4
  /**
2
- * example-claude-mcp.js
3
- * VEKTOR SLIPSTREAM Claude MCP Agent Example
4
- * ─────────────────────────────────────────────
5
- * Two modes:
6
- * 1. MCP SERVER MODE — stdio MCP server for Claude Desktop (--mcp flag)
7
- * 2. DIRECT CHAT MODE — interactive chat with memory
8
- *
9
- * MCP server mode (claude_desktop_config.json):
10
- * {
11
- * "mcpServers": {
12
- * "slipstream": {
13
- * "command": "node",
14
- * "args": ["/path/to/example-claude-mcp.js", "--mcp"],
15
- * "env": {
16
- * "VEKTOR_LICENCE_KEY": "YOUR-KEY",
17
- * "SLIPSTREAM_AGENT_ID": "claude-desktop"
18
- * }
19
- * }
20
- * }
21
- * }
22
- *
23
- * Direct chat mode:
24
- * ANTHROPIC_API_KEY=sk-ant-... node example-claude-mcp.js
5
+ * VEKTOR SLIPSTREAM — Claude Desktop MCP Server
6
+ * Responds to initialize immediately, loads memory lazily
25
7
  */
26
8
 
27
- 'use strict';
9
+ const path = require('path');
10
+ const os = require('os');
28
11
 
29
- const { createMemory } = require('vektor-slipstream');
30
- const Anthropic = require('@anthropic-ai/sdk');
31
- const readline = require('readline');
12
+ const IS_MCP = process.argv.includes('--mcp');
13
+ if (!IS_MCP) { require('./example-claude-direct'); return; }
32
14
 
33
- const IS_MCP = process.argv.includes('--mcp');
34
- const AGENT_ID = process.env.SLIPSTREAM_AGENT_ID || 'claude-mcp';
35
- const MODEL = 'claude-haiku-4-5-20251001';
15
+ // ── Tool definitions (static, no requires needed) ─────────────────────────────
36
16
 
37
- // ── MCP Tool Definitions ──────────────────────────────────────────────────────
38
-
39
- const MCP_TOOLS = [
17
+ const TOOLS = [
40
18
  {
41
19
  name: 'vektor_recall',
42
- description: 'Search Slipstream persistent memory for relevant context. Call before answering any question that might have prior context.',
43
- input_schema: {
44
- type: 'object',
45
- properties: {
46
- query: { type: 'string', description: 'What to search for.' },
47
- top_k: { type: 'integer', description: 'Number of results (default 5, max 20).', default: 5 },
48
- },
49
- required: ['query'],
50
- },
20
+ description: 'Search persistent memory. Call before answering anything that might have prior context.',
21
+ input_schema: { type: 'object', properties: { query: { type: 'string' }, top_k: { type: 'integer', default: 5 } }, required: ['query'] },
51
22
  },
52
23
  {
53
24
  name: 'vektor_store',
54
- description: 'Store a fact, preference, decision, or piece of context in persistent memory.',
55
- input_schema: {
56
- type: 'object',
57
- properties: {
58
- content: { type: 'string', description: 'The memory to store as a clear complete sentence.' },
59
- importance: { type: 'number', description: 'Importance 1-5. 5=critical, 3=useful, 1=minor.' },
60
- },
61
- required: ['content'],
62
- },
25
+ description: 'Store a fact, preference, or decision in persistent memory.',
26
+ input_schema: { type: 'object', properties: { content: { type: 'string' }, importance: { type: 'number' } }, required: ['content'] },
63
27
  },
64
28
  {
65
29
  name: 'vektor_graph',
66
- description: 'Traverse the memory graph from a concept — finds connected memories and relationships.',
67
- input_schema: {
68
- type: 'object',
69
- properties: {
70
- concept: { type: 'string', description: 'The concept to start from.' },
71
- hops: { type: 'integer', description: 'Traversal depth 1-3 (default 2).', default: 2 },
72
- },
73
- required: ['concept'],
74
- },
30
+ description: 'Traverse the memory graph from a concept.',
31
+ input_schema: { type: 'object', properties: { concept: { type: 'string' }, hops: { type: 'integer', default: 2 } }, required: ['concept'] },
75
32
  },
76
33
  {
77
34
  name: 'vektor_delta',
78
- description: 'See what changed in memory on a topic over recent days.',
79
- input_schema: {
80
- type: 'object',
81
- properties: {
82
- topic: { type: 'string', description: 'The topic to check.' },
83
- days: { type: 'integer', description: 'How many days back (default 7).', default: 7 },
84
- },
85
- required: ['topic'],
86
- },
35
+ description: 'See what changed in memory on a topic.',
36
+ input_schema: { type: 'object', properties: { topic: { type: 'string' }, days: { type: 'integer', default: 7 } }, required: ['topic'] },
37
+ },
38
+ {
39
+ name: 'cloak_fetch',
40
+ description: 'Fetch a URL using stealth headless browser. Returns clean compressed text. Saves tokens vs raw HTML.',
41
+ input_schema: { type: 'object', properties: { url: { type: 'string' }, force: { type: 'boolean' } }, required: ['url'] },
42
+ },
43
+ {
44
+ name: 'cloak_render',
45
+ description: 'Render a page and return computed CSS layout, fonts, and gap analysis.',
46
+ input_schema: { type: 'object', properties: { url: { type: 'string' }, selectors: { type: 'array', items: { type: 'string' } } }, required: ['url'] },
47
+ },
48
+ {
49
+ name: 'cloak_diff',
50
+ description: 'Return what semantically changed on a URL since last fetch.',
51
+ input_schema: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] },
52
+ },
53
+ {
54
+ name: 'cloak_passport',
55
+ description: 'Read/write to AES-256 encrypted credential vault. Omit value to read.',
56
+ input_schema: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' } }, required: ['key'] },
57
+ },
58
+ {
59
+ name: 'tokens_saved',
60
+ description: 'Calculate token savings and ROI for a session.',
61
+ input_schema: { type: 'object', properties: { raw_tokens: { type: 'number' }, actual_tokens: { type: 'number' }, agent_id: { type: 'string' } }, required: ['raw_tokens', 'actual_tokens'] },
87
62
  },
88
63
  ];
89
64
 
90
- // ── Tool execution ────────────────────────────────────────────────────────────
65
+ // ── Lazy loaders ──────────────────────────────────────────────────────────────
66
+
67
+ let _memory = null;
68
+ async function getMemory() {
69
+ if (_memory) return _memory;
70
+ const { createMemory } = require('vektor-slipstream');
71
+ const dbPath = process.env.VEKTOR_DB_PATH ||
72
+ path.join(os.homedir(), 'vektor-slipstream-memory.db');
73
+ _memory = await createMemory({
74
+ agentId: 'claude-mcp',
75
+ dbPath,
76
+ silent: true,
77
+ licenceKey: process.env.VEKTOR_LICENCE_KEY,
78
+ });
79
+ return _memory;
80
+ }
81
+
82
+ let _cloak = null;
83
+ function getCloak() {
84
+ if (!_cloak) _cloak = require('vektor-slipstream/cloak');
85
+ return _cloak;
86
+ }
91
87
 
92
- async function runTool(name, input, memory) {
88
+ // ── Tool runner ───────────────────────────────────────────────────────────────
89
+
90
+ async function runTool(name, input) {
93
91
  switch (name) {
94
92
  case 'vektor_recall': {
95
- const results = await memory.recall(input.query, input.top_k || 5);
96
- if (!results.length) return { found: 0, memories: [] };
97
- return {
98
- found: results.length,
99
- memories: results.map(r => ({ content: r.content, relevance: r.score, id: r.id })),
100
- };
93
+ const mem = await getMemory();
94
+ const results = await mem.recall(input.query, input.top_k || 5);
95
+ return { found: results.length, memories: results.map(r => ({ content: r.content, score: r.score, id: r.id })) };
101
96
  }
102
97
  case 'vektor_store': {
103
- const { id } = await memory.remember(input.content, { importance: input.importance || 2 });
104
- return { stored: true, memory_id: id, content: input.content };
98
+ const mem = await getMemory();
99
+ const { id } = await mem.remember(input.content, { importance: input.importance || 2 });
100
+ return { stored: true, id, content: input.content };
105
101
  }
106
102
  case 'vektor_graph': {
107
- const { nodes, edges } = await memory.graph(input.concept, { hops: input.hops || 2 });
108
- return {
109
- nodes: nodes.slice(0, 10).map(n => ({ id: n.id, content: n.content, importance: n.importance })),
110
- edge_count: edges.length,
111
- node_count: nodes.length,
112
- };
103
+ const mem = await getMemory();
104
+ const { nodes, edges } = await mem.graph(input.concept, { hops: input.hops || 2 });
105
+ return { nodes: nodes.slice(0, 10).map(n => ({ id: n.id, content: n.content })), edge_count: edges.length };
113
106
  }
114
107
  case 'vektor_delta': {
115
- const changes = await memory.delta(input.topic, input.days || 7);
116
- return {
117
- topic: input.topic,
118
- days: input.days || 7,
119
- changes: changes.slice(0, 10).map(c => ({ content: c.content, updated_at: c.updated_at })),
120
- };
108
+ const mem = await getMemory();
109
+ const changes = await mem.delta(input.topic, input.days || 7);
110
+ return { topic: input.topic, changes: changes.slice(0, 10) };
111
+ }
112
+ case 'cloak_fetch': {
113
+ const r = await getCloak().cloak_fetch(input.url, { force: input.force });
114
+ return { text: r.text, tokens_saved: r.tokensSaved, from_cache: r.fromCache };
121
115
  }
122
- default:
123
- return { error: `Unknown tool: ${name}` };
116
+ case 'cloak_render': return await getCloak().cloak_render(input.url, input.selectors || []);
117
+ case 'cloak_diff': return await getCloak().cloak_diff(input.url);
118
+ case 'cloak_passport': return { result: getCloak().cloak_passport(input.key, input.value) || null };
119
+ case 'tokens_saved': return getCloak().tokens_saved({ raw_tokens: input.raw_tokens, actual_tokens: input.actual_tokens, agent_id: input.agent_id });
120
+ default: return { error: `Unknown tool: ${name}` };
124
121
  }
125
122
  }
126
123
 
127
- // ── MCP Server Mode ───────────────────────────────────────────────────────────
128
- // CRITICAL: Must respond to `initialize` IMMEDIATELY — before any async work.
129
- // Claude Desktop times out after ~2 seconds. We start listening on stdin first,
130
- // then load memory in the background while the handshake completes.
131
-
132
- async function runMCPServer() {
133
- // Start memory loading in background — don't await yet
134
- const memoryPromise = createMemory({
135
- agentId: AGENT_ID,
136
- dbPath: './claude-memory.db',
137
- silent: true,
138
- licenceKey: process.env.VEKTOR_LICENCE_KEY,
139
- }).catch(e => {
140
- process.stderr.write('[vektor-mcp] Memory init failed: ' + e.message + '\n');
141
- return null;
142
- });
124
+ // ── MCP stdio server ──────────────────────────────────────────────────────────
143
125
 
144
- let memory = null; // will be set once memoryPromise resolves
126
+ process.stdin.setEncoding('utf8');
127
+ let buf = '';
145
128
 
146
- // Start resolving memory immediately (non-blocking)
147
- memoryPromise.then(m => { memory = m; });
148
-
149
- process.stdin.setEncoding('utf8');
150
- let buffer = '';
151
-
152
- function send(obj) {
153
- process.stdout.write(JSON.stringify(obj) + '\n');
154
- }
155
-
156
- process.stdin.on('data', async chunk => {
157
- buffer += chunk;
158
- const lines = buffer.split('\n');
159
- buffer = lines.pop();
129
+ function send(obj) {
130
+ process.stdout.write(JSON.stringify(obj) + '\n');
131
+ }
160
132
 
161
- for (const line of lines) {
162
- if (!line.trim()) continue;
133
+ process.stdin.on('data', async chunk => {
134
+ buf += chunk;
135
+ const lines = buf.split('\n');
136
+ buf = lines.pop();
137
+
138
+ for (const line of lines) {
139
+ if (!line.trim()) continue;
140
+ let req;
141
+ try { req = JSON.parse(line); } catch (e) {
142
+ send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
143
+ continue;
144
+ }
163
145
 
164
- let req;
165
- try {
166
- req = JSON.parse(line);
167
- } catch (e) {
168
- send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
146
+ if (!req || typeof req.method !== 'string') continue;
147
+ if (req.method.startsWith('notifications/')) continue;
148
+ if (!('id' in req)) continue;
149
+
150
+ try {
151
+ if (req.method === 'initialize') {
152
+ send({ jsonrpc: '2.0', id: req.id, result: {
153
+ protocolVersion: '2025-11-25',
154
+ serverInfo: { name: 'vektor-slipstream', version: '1.1.1' },
155
+ capabilities: { tools: {} },
156
+ }});
157
+ // Warm up memory in background after responding
158
+ getMemory().catch(e => process.stderr.write('[vektor-mcp] Memory init: ' + e.message + '\n'));
169
159
  continue;
170
160
  }
171
161
 
172
- // Notifications no response needed
173
- if (req.method && req.method.startsWith('notifications/')) continue;
174
- if (!('id' in req)) continue;
175
-
176
- try {
177
- if (req.method === 'initialize') {
178
- // Respond IMMEDIATELY — do not wait for memory
179
- send({
180
- jsonrpc: '2.0',
181
- id: req.id,
182
- result: {
183
- protocolVersion: '2025-11-25',
184
- serverInfo: { name: 'vektor-slipstream', version: '1.0.6' },
185
- capabilities: { tools: {} },
186
- },
187
- });
188
- continue;
189
- }
190
-
191
- if (req.method === 'tools/list') {
192
- send({ jsonrpc: '2.0', id: req.id, result: { tools: MCP_TOOLS } });
193
- continue;
194
- }
195
-
196
- if (req.method === 'tools/call') {
197
- // Wait for memory if not ready yet
198
- if (!memory) memory = await memoryPromise;
199
- if (!memory) {
200
- send({ jsonrpc: '2.0', id: req.id, error: { code: -32603, message: 'Memory not initialised' } });
201
- continue;
202
- }
203
- const { name, arguments: args } = req.params;
204
- const result = await runTool(name, args, memory);
205
- send({
206
- jsonrpc: '2.0',
207
- id: req.id,
208
- result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] },
209
- });
210
- continue;
211
- }
212
-
213
- // Unknown method
214
- send({ jsonrpc: '2.0', id: req.id, error: { code: -32601, message: 'Method not found' } });
215
-
216
- } catch (e) {
217
- process.stderr.write('[vektor-mcp] Error: ' + e.message + '\n');
218
- send({ jsonrpc: '2.0', id: req.id, error: { code: -32603, message: e.message } });
162
+ if (req.method === 'tools/list') {
163
+ send({ jsonrpc: '2.0', id: req.id, result: { tools: TOOLS } });
164
+ continue;
219
165
  }
220
- }
221
- });
222
-
223
- // Keep process alive
224
- process.stdin.resume();
225
- }
226
-
227
- // ── Direct Chat Mode ──────────────────────────────────────────────────────────
228
-
229
- async function runDirectChat() {
230
- console.log('\n[CLAUDE+SLIPSTREAM] Booting Slipstream memory...');
231
-
232
- const memory = await createMemory({
233
- agentId: AGENT_ID,
234
- dbPath: './claude-memory.db',
235
- silent: false,
236
- licenceKey: process.env.VEKTOR_LICENCE_KEY,
237
- });
238
-
239
- const client = new Anthropic();
240
- const messages = [];
241
- const briefing = await memory.briefing();
242
-
243
- const system = `You are a persistent assistant with long-term memory via VEKTOR Slipstream.
244
-
245
- You have four memory tools: vektor_recall, vektor_store, vektor_graph, vektor_delta.
246
- - Always recall before answering questions that might have prior context
247
- - Store important facts the user shares
248
- - Be proactive about remembering preferences and decisions
249
-
250
- ${briefing}`;
251
166
 
252
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
253
- const prompt = () => new Promise(resolve => rl.question('\nYou: ', resolve));
254
-
255
- console.log('\n[CLAUDE+SLIPSTREAM] Ready. Memories persist across sessions.');
256
- console.log('[CLAUDE+SLIPSTREAM] Type "exit" to quit.\n');
257
-
258
- while (true) {
259
- const userInput = (await prompt()).trim();
260
- if (!userInput) continue;
261
- if (userInput.toLowerCase() === 'exit') { rl.close(); break; }
262
-
263
- messages.push({ role: 'user', content: userInput });
264
-
265
- while (true) {
266
- const response = await client.messages.create({
267
- model: MODEL, max_tokens: 1024, system, tools: MCP_TOOLS, messages,
268
- });
269
-
270
- messages.push({ role: 'assistant', content: response.content });
271
-
272
- if (response.stop_reason !== 'tool_use') {
273
- const text = response.content.find(b => b.type === 'text')?.text || '';
274
- console.log(`\nClaude: ${text}`);
275
- break;
167
+ if (req.method === 'tools/call') {
168
+ const { name, arguments: args } = req.params;
169
+ const result = await runTool(name, args || {});
170
+ send({ jsonrpc: '2.0', id: req.id, result: {
171
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
172
+ }});
173
+ continue;
276
174
  }
277
175
 
278
- const toolResults = [];
279
- for (const block of response.content) {
280
- if (block.type !== 'tool_use') continue;
281
- const result = await runTool(block.name, block.input, memory);
282
- console.log(` [${block.name}] ${JSON.stringify(result).slice(0, 100)}...`);
283
- toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: JSON.stringify(result) });
284
- }
285
- messages.push({ role: 'user', content: toolResults });
176
+ send({ jsonrpc: '2.0', id: req.id, error: { code: -32601, message: 'Method not found' } });
177
+ } catch (e) {
178
+ process.stderr.write('[vektor-mcp] Error: ' + e.message + '\n');
179
+ send({ jsonrpc: '2.0', id: req.id, error: { code: -32603, message: e.message } });
286
180
  }
287
181
  }
288
-
289
- console.log('\n[CLAUDE+SLIPSTREAM] Session ended. Memories saved.');
290
- }
291
-
292
- // ── Entry Point ───────────────────────────────────────────────────────────────
293
-
294
- async function main() {
295
- if (IS_MCP) {
296
- await runMCPServer();
297
- } else {
298
- await runDirectChat();
299
- }
300
- }
301
-
302
- main().catch(e => {
303
- process.stderr.write('[CLAUDE+SLIPSTREAM] Fatal: ' + e.message + '\n');
304
- process.exit(1);
305
182
  });
183
+
184
+ process.stdin.resume();
package/package.json CHANGED
@@ -1,69 +1,91 @@
1
- {
2
- "name": "vektor-slipstream",
3
- "version": "1.1.0",
4
- "description": "Hardware-accelerated persistent memory for AI agents. Local-first, zero cloud dependency, $0 embedding cost.",
5
- "main": "slipstream-core.js",
6
- "bin": {
7
- "vektor": "./vektor-cli.js"
8
- },
9
- "exports": {
10
- ".": "./slipstream-core.js",
11
- "./cloak": "./cloak.js",
12
- "./db": "./slipstream-db.js",
13
- "./embedder": "./slipstream-embedder.js"
14
- },
15
- "keywords": [
16
- "ai", "memory", "agent", "vector", "sqlite", "embeddings",
17
- "langchain", "openai", "anthropic", "claude", "mcp", "rag",
18
- "persistent-memory", "local-ai", "onnx", "mistral", "cloak"
19
- ],
20
- "author": "VEKTOR Memory <hello@vektormemory.com>",
21
- "license": "SEE LICENSE IN LICENSE",
22
- "homepage": "https://vektormemory.com",
23
- "repository": {
24
- "type": "git",
25
- "url": "https://github.com/Vektor-Memory/Vektor-memory"
26
- },
27
- "engines": {
28
- "node": ">=18.0.0"
29
- },
30
- "dependencies": {
31
- "better-sqlite3": "^12.8.0",
32
- "onnxruntime-node": "^1.17.3"
33
- },
34
- "optionalDependencies": {
35
- "@anthropic-ai/sdk": "^0.82.0",
36
- "@xenova/transformers": "^2.17.2",
37
- "playwright": "^1.44.0",
38
- "sqlite-vec-darwin-arm64": "^0.1.6",
39
- "sqlite-vec-linux-x64": "^0.1.6",
40
- "sqlite-vec-windows-x64": "^0.1.6"
41
- },
42
- "files": [
43
- "slipstream-core.js",
44
- "slipstream-db.js",
45
- "slipstream-embedder.js",
46
- "detect-hardware.js",
47
- "vektor-licence.js",
48
- "vektor-licence-prompt.js",
49
- "vektor-cli.js",
50
- "vektor-setup.js",
51
- "vektor-banner-loader.js",
52
- "boot-screen.html",
53
- "cloak.js",
54
- "sovereign.js",
55
- "visualize.js",
56
- "TENETS.md",
57
- "README.md",
58
- "LICENSE",
59
- "models/",
60
- "examples/",
61
- "mistral/"
62
- ],
63
- "publishConfig": {
64
- "access": "public"
65
- },
66
- "devDependencies": {
67
- "javascript-obfuscator": "^5.4.1"
68
- }
69
- }
1
+ {
2
+ "name": "vektor-slipstream",
3
+ "version": "1.1.2",
4
+ "description": "Hardware-accelerated persistent memory for AI agents. Local-first, zero cloud dependency, $0 embedding cost.",
5
+ "main": "slipstream-core.js",
6
+ "bin": {
7
+ "vektor": "./vektor-cli.js"
8
+ },
9
+ "exports": {
10
+ ".": "./slipstream-core.js",
11
+ "./cloak": "./cloak.js",
12
+ "./db": "./slipstream-db.js",
13
+ "./embedder": "./slipstream-embedder.js"
14
+ },
15
+ "keywords": [
16
+ "ai",
17
+ "memory",
18
+ "agent",
19
+ "vector",
20
+ "sqlite",
21
+ "embeddings",
22
+ "langchain",
23
+ "openai",
24
+ "anthropic",
25
+ "claude",
26
+ "mcp",
27
+ "rag",
28
+ "persistent-memory",
29
+ "local-ai",
30
+ "onnx",
31
+ "mistral",
32
+ "cloak"
33
+ ],
34
+ "author": "VEKTOR Memory <hello@vektormemory.com>",
35
+ "license": "SEE LICENSE IN LICENSE",
36
+ "homepage": "https://vektormemory.com",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/Vektor-Memory/Vektor-memory"
40
+ },
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ },
44
+ "dependencies": {
45
+ "better-sqlite3": "^12.8.0",
46
+ "blessed": "^0.1.81",
47
+ "blessed-contrib": "^4.11.0",
48
+ "onnxruntime-node": "^1.17.3"
49
+ },
50
+ "optionalDependencies": {
51
+ "@anthropic-ai/sdk": "^0.82.0",
52
+ "@xenova/transformers": "^2.17.2",
53
+ "playwright": "^1.44.0",
54
+ "sqlite-vec-darwin-arm64": "^0.1.6",
55
+ "sqlite-vec-linux-x64": "^0.1.6",
56
+ "sqlite-vec-windows-x64": "^0.1.6",
57
+ "blessed": "^0.1.81",
58
+ "blessed-contrib": "^4.11.0"
59
+ },
60
+ "files": [
61
+ "slipstream-core.js",
62
+ "slipstream-db.js",
63
+ "slipstream-embedder.js",
64
+ "detect-hardware.js",
65
+ "vektor-licence.js",
66
+ "vektor-licence-prompt.js",
67
+ "vektor-cli.js",
68
+ "vektor-setup.js",
69
+ "vektor-banner-loader.js",
70
+ "vektor-tui.js",
71
+ "graph-viewer.html",
72
+ "boot-screen.html",
73
+ "cloak.js",
74
+ "sovereign.js",
75
+ "visualize.js",
76
+ "TENETS.md",
77
+ "README.md",
78
+ "LICENSE",
79
+ "models/",
80
+ "examples/",
81
+ "mistral/",
82
+ "vektor-tui.js",
83
+ "graph-viewer.html"
84
+ ],
85
+ "publishConfig": {
86
+ "access": "public"
87
+ },
88
+ "devDependencies": {
89
+ "javascript-obfuscator": "^5.4.1"
90
+ }
91
+ }
package/vektor-cli.js CHANGED
@@ -199,10 +199,12 @@ async function cmdSetup() {
199
199
  require('./vektor-setup');
200
200
  }
201
201
 
202
+ async function cmdTui() { require('./vektor-tui'); }
203
+
202
204
  const commands = {
203
205
  help: cmdHelp, status: cmdStatus, activate: cmdActivate,
204
206
  deactivate: cmdDeactivate, test: cmdTest, mcp: cmdMcp,
205
- rem: cmdRem, setup: cmdSetup,
207
+ rem: cmdRem, setup: cmdSetup, tui: async function(){ require('./vektor-tui'); },
206
208
  };
207
209
 
208
210
  const fn = commands[command];
package/vektor-tui.js ADDED
@@ -0,0 +1,382 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ let blessed;
5
+ try { blessed = require('blessed'); }
6
+ catch(e) {
7
+ require('child_process').execSync('npm install blessed --prefix ' + __dirname, { stdio: 'inherit' });
8
+ blessed = require('blessed');
9
+ }
10
+
11
+ const path = require('path');
12
+ let PKG;
13
+ try { PKG = require('./package.json'); } catch(_) { PKG = { version: '1.1.1' }; }
14
+
15
+ // ── PALETTE (SBB-inspired warm dark) ─────────────────────────────────────────
16
+ const P = {
17
+ bg: '#151510',
18
+ bg2: '#1e1e18',
19
+ card: '#1a1a14',
20
+ border: '#2a2a20',
21
+ orange: '#e8742a',
22
+ amber: '#d4a017',
23
+ cream: '#c8b896',
24
+ dim: '#5a5040',
25
+ white: '#e8e0d0',
26
+ green: '#7ab87a',
27
+ red: '#c45050',
28
+ cyan: '#7ab8c8',
29
+ sel: '#2a2418',
30
+ selbrd: '#e8742a',
31
+ };
32
+
33
+ // ── SCREEN ────────────────────────────────────────────────────────────────────
34
+ const screen = blessed.screen({ smartCSR: true, fullUnicode: true, title: 'VEKTOR TUI' });
35
+
36
+ // ── TOP SEARCH BAR ────────────────────────────────────────────────────────────
37
+ const searchBar = blessed.box({
38
+ top: 0, left: 0, width: '100%', height: 3,
39
+ style: { bg: P.bg2, fg: P.cream },
40
+ content: '',
41
+ });
42
+
43
+ const searchLeft = blessed.textbox({
44
+ parent: searchBar,
45
+ top: 0, left: 0, width: '45%', height: 3,
46
+ border: { type: 'line' },
47
+ style: { fg: P.cream, bg: P.bg2, border: { fg: P.border }, focus: { border: { fg: P.orange } } },
48
+ inputOnFocus: true,
49
+ value: 'search memories...',
50
+ });
51
+
52
+ const searchRight = blessed.textbox({
53
+ parent: searchBar,
54
+ top: 0, left: '45%', width: '35%', height: 3,
55
+ border: { type: 'line' },
56
+ style: { fg: P.cream, bg: P.bg2, border: { fg: P.border }, focus: { border: { fg: P.orange } } },
57
+ inputOnFocus: true,
58
+ value: 'type: all',
59
+ });
60
+
61
+ const searchBtn = blessed.box({
62
+ parent: searchBar,
63
+ top: 0, right: 0, width: '20%', height: 3,
64
+ border: { type: 'line' },
65
+ tags: true,
66
+ style: { fg: P.cream, bg: P.bg2, border: { fg: P.border } },
67
+ content: ` {bold}{#e8742a-fg}▶{/#e8742a-fg}{/bold} VEKTOR SLIPSTREAM v${PKG.version}`,
68
+ });
69
+
70
+ screen.append(searchBar);
71
+
72
+ // ── MAIN AREA ─────────────────────────────────────────────────────────────────
73
+ const mainLeft = blessed.box({
74
+ top: 3, left: 0, width: '48%', height: '100%-6',
75
+ scrollable: true, alwaysScroll: true, keys: true, mouse: true,
76
+ style: { bg: P.bg },
77
+ tags: true,
78
+ });
79
+
80
+ const mainRight = blessed.box({
81
+ top: 3, left: '48%', width: '52%', height: '100%-6',
82
+ border: { type: 'line' },
83
+ style: { bg: P.bg, border: { fg: P.border } },
84
+ tags: true, scrollable: true, keys: true, mouse: true,
85
+ });
86
+
87
+ screen.append(mainLeft);
88
+ screen.append(mainRight);
89
+
90
+ // ── BOTTOM STATUS BAR ─────────────────────────────────────────────────────────
91
+ const statusBar = blessed.box({
92
+ bottom: 0, left: 0, width: '100%', height: 3,
93
+ border: { type: 'line' },
94
+ tags: true,
95
+ style: { bg: P.bg2, border: { fg: P.border } },
96
+ content: '',
97
+ });
98
+ screen.append(statusBar);
99
+
100
+ function renderStatusBar(msg='') {
101
+ const keys = [
102
+ ['↑↓', 'navigate'],
103
+ ['↵', 'inspect'],
104
+ ['A', 'add'],
105
+ ['D', 'delete'],
106
+ ['/', 'search'],
107
+ ['R', 'REM'],
108
+ ['B', 'briefing'],
109
+ ['Q', 'quit'],
110
+ ];
111
+ const left = keys.map(([k,v]) =>
112
+ ` {#e8742a-fg}{bold}${k}{/bold}{/#e8742a-fg} {#5a5040-fg}${v}{/#5a5040-fg}`
113
+ ).join(' ');
114
+ const right = msg
115
+ ? `{#7ab87a-fg}${msg}{/#7ab87a-fg} `
116
+ : `{#5a5040-fg}VEKTOR-TUI v${PKG.version}{/#5a5040-fg} `;
117
+ statusBar.setContent(left + ' ' + right);
118
+ screen.render();
119
+ }
120
+
121
+ // ── STATE ─────────────────────────────────────────────────────────────────────
122
+ let memory = null, memories = [], selected = 0, filterType = 'all';
123
+
124
+ // ── CARD RENDERER ─────────────────────────────────────────────────────────────
125
+ function typeColor(type) {
126
+ return { semantic: P.cyan, causal: P.amber, temporal: P.green, entity: P.red }[type] || P.dim;
127
+ }
128
+
129
+ function typeBg(type) {
130
+ return { semantic: '#1a2a30', causal: '#2a2010', temporal: '#1a2a1a', entity: '#2a1a1a' }[type] || P.card;
131
+ }
132
+
133
+ function renderTimeline(importance) {
134
+ // Draw ●──○──○──● style based on importance
135
+ const steps = 5;
136
+ const filled = importance || 1;
137
+ let line = '{#e8742a-fg}●{/#e8742a-fg}';
138
+ for (let i = 1; i < steps; i++) {
139
+ line += '{#2a2a20-fg}──{/#2a2a20-fg}';
140
+ if (i < filled) {
141
+ line += '{#e8742a-fg}●{/#e8742a-fg}';
142
+ } else {
143
+ line += '{#2a2a20-fg}○{/#2a2a20-fg}';
144
+ }
145
+ }
146
+ return line;
147
+ }
148
+
149
+ function renderCards() {
150
+ const W = Math.floor(screen.cols * 0.48) - 4;
151
+ let content = '\n';
152
+
153
+ memories.forEach((m, i) => {
154
+ const isSelected = i === selected;
155
+ const color = typeColor(m.type);
156
+ const age = m.created_at ? Math.round((Date.now() - m.created_at) / 3600000) + 'h ago' : '—';
157
+ const typeTag = (m.type || 'mem').slice(0, 3).toUpperCase();
158
+ const bColor = isSelected ? P.selbrd : P.border;
159
+ const preview = m.content.slice(0, W - 4);
160
+
161
+ content += isSelected
162
+ ? `{${P.orange}-fg}┌${'─'.repeat(W - 2)}┐{/${P.orange}-fg}\n`
163
+ : `{${P.border}-fg}┌${'─'.repeat(W - 2)}┐{/${P.border}-fg}\n`;
164
+
165
+ // Title row
166
+ content += isSelected ? `{${P.orange}-fg}│{/${P.orange}-fg}` : `{${P.border}-fg}│{/${P.border}-fg}`;
167
+ content += ` {bold}{${color}-fg}${typeTag}{/${color}-fg}{/bold}`;
168
+ content += ` {${isSelected ? P.white : P.cream}-fg}${preview}{/${isSelected ? P.white : P.cream}-fg}`;
169
+ content += ' '.repeat(Math.max(0, W - 6 - typeTag.length - preview.length));
170
+ content += isSelected ? `{${P.orange}-fg}│{/${P.orange}-fg}\n` : `{${P.border}-fg}│{/${P.border}-fg}\n`;
171
+
172
+ // Timeline row
173
+ content += isSelected ? `{${P.orange}-fg}│{/${P.orange}-fg}` : `{${P.border}-fg}│{/${P.border}-fg}`;
174
+ content += ` ${renderTimeline(m.importance)}`;
175
+ content += ` {${P.dim}-fg}${age}{/${P.dim}-fg}`;
176
+ content += ' '.repeat(Math.max(0, W - 4 - (m.importance || 1) * 4 - age.length - 2));
177
+ content += isSelected ? `{${P.orange}-fg}│{/${P.orange}-fg}\n` : `{${P.border}-fg}│{/${P.border}-fg}\n`;
178
+
179
+ content += isSelected
180
+ ? `{${P.orange}-fg}└${'─'.repeat(W - 2)}┘{/${P.orange}-fg}\n`
181
+ : `{${P.border}-fg}└${'─'.repeat(W - 2)}┘{/${P.border}-fg}\n`;
182
+ content += '\n';
183
+ });
184
+
185
+ if (memories.length === 0) {
186
+ content += `\n {${P.dim}-fg}No memories found.{/${P.dim}-fg}\n\n`;
187
+ content += ` {${P.amber}-fg}Press A to add your first memory.{/${P.amber}-fg}\n`;
188
+ }
189
+
190
+ mainLeft.setContent(content);
191
+
192
+ // Scroll to selected
193
+ const cardHeight = 4;
194
+ const scrollTo = selected * (cardHeight + 1);
195
+ mainLeft.scrollTo(scrollTo);
196
+
197
+ screen.render();
198
+ }
199
+
200
+ // ── DETAIL PANEL ─────────────────────────────────────────────────────────────
201
+ function renderDetail() {
202
+ const m = memories[selected];
203
+ if (!m) {
204
+ mainRight.setContent(`\n\n {${P.dim}-fg}Select a memory to inspect{/${P.dim}-fg}`);
205
+ screen.render();
206
+ return;
207
+ }
208
+
209
+ const color = typeColor(m.type);
210
+ const age = m.created_at ? new Date(m.created_at).toLocaleString() : '—';
211
+ const typeTag = (m.type || 'memory').toUpperCase();
212
+
213
+ let content = '\n';
214
+ content += ` {bold}{${color}-fg}${typeTag}{/${color}-fg}{/bold} {${P.dim}-fg}#${m.id}{/${P.dim}-fg}\n\n`;
215
+ content += ` {bold}{${P.white}-fg}${m.content}{/${P.white}-fg}{/bold}\n\n`;
216
+ content += ` {${P.border}-fg}${'─'.repeat(40)}{/${P.border}-fg}\n\n`;
217
+ content += ` {${P.dim}-fg}IMPORTANCE {/${P.dim}-fg} ${renderTimeline(m.importance)}\n\n`;
218
+ content += ` {${P.dim}-fg}CREATED {/${P.dim}-fg} {${P.cream}-fg}${age}{/${P.cream}-fg}\n\n`;
219
+ content += ` {${P.border}-fg}${'─'.repeat(40)}{/${P.border}-fg}\n\n`;
220
+ content += ` {${P.dim}-fg}[D]{/${P.dim}-fg} delete {${P.dim}-fg}[E]{/${P.dim}-fg} edit importance\n\n`;
221
+
222
+ // Show connected memories count if available
223
+ const total = memories.length;
224
+ const sameType = memories.filter(x => x.type === m.type).length;
225
+ content += ` {${P.border}-fg}${'─'.repeat(40)}{/${P.border}-fg}\n\n`;
226
+ content += ` {${P.dim}-fg}TYPE POOL {/${P.dim}-fg} {${color}-fg}${sameType}{/${color}-fg} {${P.dim}-fg}of ${total} memories{/${P.dim}-fg}\n`;
227
+
228
+ mainRight.setContent(content);
229
+ screen.render();
230
+ }
231
+
232
+ // ── INPUT OVERLAY ─────────────────────────────────────────────────────────────
233
+ const inputOverlay = blessed.box({
234
+ top: 'center', left: 'center', width: '60%', height: 5,
235
+ hidden: true,
236
+ border: { type: 'line' },
237
+ style: { bg: P.bg2, border: { fg: P.orange } },
238
+ tags: true,
239
+ });
240
+
241
+ const inputField = blessed.textbox({
242
+ parent: inputOverlay,
243
+ top: 1, left: 2, width: '100%-6', height: 1,
244
+ style: { fg: P.orange, bg: P.bg2 },
245
+ inputOnFocus: true,
246
+ });
247
+
248
+ screen.append(inputOverlay);
249
+
250
+ function showInput(label, cb) {
251
+ inputOverlay.setLabel(` {#e8742a-fg}${label}{/#e8742a-fg} `);
252
+ inputField.setValue('');
253
+ inputOverlay.show();
254
+ screen.render();
255
+ inputField.focus();
256
+ inputField.readInput((err, val) => {
257
+ inputOverlay.hide();
258
+ screen.render();
259
+ if (!err && val && val.trim()) cb(val.trim());
260
+ else mainLeft.focus();
261
+ });
262
+ }
263
+
264
+ // ── LOAD ──────────────────────────────────────────────────────────────────────
265
+ async function loadMemories(query = '') {
266
+ try {
267
+ if (query) {
268
+ memories = await memory.recall(query, 100);
269
+ } else {
270
+ memories = memory.db.prepare(
271
+ 'SELECT * FROM memories ORDER BY created_at DESC LIMIT 500'
272
+ ).all();
273
+ if (filterType !== 'all') {
274
+ memories = memories.filter(m => m.type === filterType);
275
+ }
276
+ }
277
+ if (selected >= memories.length) selected = Math.max(0, memories.length - 1);
278
+ renderCards();
279
+ renderDetail();
280
+ renderStatusBar(`${memories.length} memories`);
281
+ } catch(e) {
282
+ renderStatusBar('Error: ' + e.message.slice(0, 40));
283
+ }
284
+ }
285
+
286
+ // ── ACTIONS ───────────────────────────────────────────────────────────────────
287
+ async function addMemory(content) {
288
+ try {
289
+ const { id } = await memory.remember(content, { importance: 3 });
290
+ renderStatusBar('✓ Saved #' + id);
291
+ await loadMemories();
292
+ } catch(e) { renderStatusBar('Error: ' + e.message.slice(0, 40)); }
293
+ }
294
+
295
+ async function deleteSelected() {
296
+ const m = memories[selected];
297
+ if (!m) return;
298
+ try {
299
+ memory.db.prepare('DELETE FROM memories WHERE id=?').run(m.id);
300
+ renderStatusBar('Deleted #' + m.id);
301
+ await loadMemories();
302
+ } catch(e) { renderStatusBar('Error: ' + e.message.slice(0, 40)); }
303
+ }
304
+
305
+ async function runRem() {
306
+ renderStatusBar('REM cycle running...');
307
+ try {
308
+ if (memory) await memory.dream();
309
+ renderStatusBar('✓ REM complete');
310
+ await loadMemories();
311
+ } catch(e) { renderStatusBar('REM: ' + e.message.slice(0, 40)); }
312
+ }
313
+
314
+ async function showBriefing() {
315
+ try {
316
+ const brief = await memory.briefing();
317
+ mainRight.setLabel(' {bold}BRIEFING{/bold} ');
318
+ mainRight.setContent('\n' + brief.split('\n').map(l => ' ' + l).join('\n'));
319
+ screen.render();
320
+ } catch(e) { renderStatusBar('Briefing error'); }
321
+ }
322
+
323
+ // ── KEYBOARD ──────────────────────────────────────────────────────────────────
324
+ screen.key(['q', 'C-c'], () => { screen.destroy(); process.exit(0); });
325
+
326
+ screen.key(['up', 'k'], () => {
327
+ if (selected > 0) { selected--; renderCards(); renderDetail(); }
328
+ });
329
+ screen.key(['down', 'j'], () => {
330
+ if (selected < memories.length - 1) { selected++; renderCards(); renderDetail(); }
331
+ });
332
+
333
+ screen.key(['a'], () => showInput('▶ ADD MEMORY', addMemory));
334
+ screen.key(['d'], deleteSelected);
335
+ screen.key(['/'], () => showInput('▶ SEARCH', async q => { await loadMemories(q); }));
336
+ screen.key(['l'], async () => { await loadMemories(); });
337
+ screen.key(['r'], runRem);
338
+ screen.key(['b'], showBriefing);
339
+ screen.key(['enter'], () => { renderDetail(); });
340
+
341
+ // Tab through filter types
342
+ screen.key(['t'], async () => {
343
+ const types = ['all', 'semantic', 'causal', 'temporal', 'entity'];
344
+ const idx = types.indexOf(filterType);
345
+ filterType = types[(idx + 1) % types.length];
346
+ searchRight.setValue('type: ' + filterType);
347
+ await loadMemories();
348
+ });
349
+
350
+ // ── BOOT ──────────────────────────────────────────────────────────────────────
351
+ async function boot() {
352
+ const _l = console.log; const _e = console.error;
353
+ console.log = console.error = () => {};
354
+
355
+ renderStatusBar('connecting...');
356
+ renderCards();
357
+ screen.render();
358
+
359
+ try {
360
+ const { createMemory } = require('./slipstream-core');
361
+ const dbPath = process.env.VEKTOR_DB_PATH || path.join(process.cwd(), 'slipstream-memory.db');
362
+ memory = await createMemory({
363
+ agentId: process.env.VEKTOR_AGENT_ID || 'tui',
364
+ dbPath, silent: true,
365
+ licenceKey: process.env.VEKTOR_LICENCE_KEY,
366
+ });
367
+ await loadMemories();
368
+ } catch(e) {
369
+ renderStatusBar('Error: ' + e.message.slice(0, 50));
370
+ memories = [];
371
+ renderCards();
372
+ renderDetail();
373
+ }
374
+
375
+ console.log = _l; console.error = _e;
376
+ mainLeft.focus();
377
+ screen.render();
378
+
379
+ setInterval(() => { if (memory) loadMemories(); }, 30000);
380
+ }
381
+
382
+ boot();