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 +52 -252
- package/examples/example-claude-mcp.js +137 -258
- package/package.json +91 -69
- package/vektor-cli.js +3 -1
- package/vektor-tui.js +382 -0
package/README.md
CHANGED
|
@@ -1,252 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
*
|
|
3
|
-
*
|
|
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
|
-
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
28
11
|
|
|
29
|
-
const
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
const MCP_TOOLS = [
|
|
17
|
+
const TOOLS = [
|
|
40
18
|
{
|
|
41
19
|
name: 'vektor_recall',
|
|
42
|
-
description: 'Search
|
|
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,
|
|
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
|
|
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
|
|
79
|
-
input_schema: {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
// ──
|
|
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
|
-
|
|
88
|
+
// ── Tool runner ───────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
async function runTool(name, input) {
|
|
93
91
|
switch (name) {
|
|
94
92
|
case 'vektor_recall': {
|
|
95
|
-
const
|
|
96
|
-
|
|
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
|
|
104
|
-
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
|
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
|
-
|
|
126
|
+
process.stdin.setEncoding('utf8');
|
|
127
|
+
let buf = '';
|
|
145
128
|
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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();
|