vektor-slipstream 1.1.13 → 1.2.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.
Potentially problematic release.
This version of vektor-slipstream might be problematic. Click here for more details.
- package/examples/example-claude-mcp.js +135 -213
- package/package.json +5 -19
- package/vektor-tui.js +268 -277
package/vektor-tui.js
CHANGED
|
@@ -2,363 +2,358 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
let blessed;
|
|
5
|
-
try { blessed = require('blessed'); }
|
|
6
|
-
|
|
7
|
-
require('child_process').execSync('npm install blessed --prefix ' + __dirname, { stdio: 'inherit' });
|
|
5
|
+
try { blessed = require('blessed'); } catch(e) {
|
|
6
|
+
require('child_process').execSync('npm install blessed --prefix ' + __dirname, {stdio:'inherit'});
|
|
8
7
|
blessed = require('blessed');
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
const path = require('path');
|
|
12
|
-
|
|
13
|
-
try { PKG = require('./package.json'); } catch(_) { PKG = {
|
|
14
|
-
|
|
15
|
-
// ──
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
dim:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
const os = require('os');
|
|
12
|
+
let PKG; try { PKG = require('./package.json'); } catch(_) { PKG = {version:'1.1.13'}; }
|
|
13
|
+
|
|
14
|
+
// ── palette — xterm-256 codes, work on Windows Terminal ──────────────────────
|
|
15
|
+
// blessed accepts these as numbers 0-255
|
|
16
|
+
const C = {
|
|
17
|
+
bg: 0, // black
|
|
18
|
+
bg2: 235, // very dark grey
|
|
19
|
+
orange: 208, // orange
|
|
20
|
+
amber: 214, // gold/amber
|
|
21
|
+
cream: 223, // warm cream
|
|
22
|
+
cream2: 180, // muted tan
|
|
23
|
+
dim: 240, // mid grey
|
|
24
|
+
bord: 242, // visible grey border
|
|
25
|
+
bord2: 238, // subtle inner border
|
|
26
|
+
green: 114, // sage green
|
|
27
|
+
teal: 39, // bright cyan-blue
|
|
28
|
+
coral: 203, // coral red
|
|
29
|
+
gold: 172, // dark amber/gold
|
|
30
|
+
white: 230, // near white cream
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
content: '',
|
|
41
|
-
});
|
|
33
|
+
// type colour map
|
|
34
|
+
function typeColor(et) {
|
|
35
|
+
return { semantic: C.teal, causal: C.gold, temporal: C.green, entity: C.coral }[et] || C.dim;
|
|
36
|
+
}
|
|
37
|
+
function typeLabel(et) {
|
|
38
|
+
return { semantic:'SEM', causal:'CAU', temporal:'TMP', entity:'ENT' }[et] || 'MEM';
|
|
39
|
+
}
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
// ── screen — force 256 colours ────────────────────────────────────────────────
|
|
42
|
+
const screen = blessed.screen({
|
|
43
|
+
smartCSR: true,
|
|
44
|
+
fullUnicode: true,
|
|
45
|
+
title: 'VEKTOR SLIPSTREAM',
|
|
46
|
+
terminal: 'xterm-256color',
|
|
47
|
+
forceUnicode: true,
|
|
50
48
|
});
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
top: 0, left:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
inputOnFocus: true,
|
|
58
|
-
value: 'type: all',
|
|
50
|
+
// ── TOP BAR ───────────────────────────────────────────────────────────────────
|
|
51
|
+
const topBar = blessed.box({
|
|
52
|
+
top: 0, left: 0, width: '100%', height: 1,
|
|
53
|
+
style: { fg: C.orange, bg: C.bg2 },
|
|
54
|
+
content: ` ▶ VEKTOR SLIPSTREAM v${PKG.version} sovereign agent memory`,
|
|
59
55
|
});
|
|
56
|
+
screen.append(topBar);
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
top:
|
|
58
|
+
// ── LEFT PANEL ────────────────────────────────────────────────────────────────
|
|
59
|
+
const leftPanel = blessed.box({
|
|
60
|
+
top: 1, left: 0, width: '50%', height: '100%-4',
|
|
64
61
|
border: { type: 'line' },
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
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: 'black' },
|
|
77
|
-
tags: true,
|
|
62
|
+
style: {
|
|
63
|
+
fg: C.cream, bg: C.bg,
|
|
64
|
+
border: { fg: C.bord },
|
|
65
|
+
},
|
|
66
|
+
scrollable: true, alwaysScroll: true,
|
|
67
|
+
keys: true, mouse: true,
|
|
68
|
+
tags: false, content: '',
|
|
78
69
|
});
|
|
70
|
+
screen.append(leftPanel);
|
|
79
71
|
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
// ── RIGHT PANEL ───────────────────────────────────────────────────────────────
|
|
73
|
+
const rightPanel = blessed.box({
|
|
74
|
+
top: 1, left: '50%', width: '50%', height: '100%-4',
|
|
82
75
|
border: { type: 'line' },
|
|
83
|
-
style: {
|
|
84
|
-
|
|
76
|
+
style: {
|
|
77
|
+
fg: C.cream, bg: C.bg,
|
|
78
|
+
border: { fg: C.bord },
|
|
79
|
+
},
|
|
80
|
+
scrollable: true, alwaysScroll: true,
|
|
81
|
+
keys: true, mouse: true,
|
|
82
|
+
tags: false, content: '',
|
|
85
83
|
});
|
|
84
|
+
screen.append(rightPanel);
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// ── BOTTOM STATUS BAR ─────────────────────────────────────────────────────────
|
|
91
|
-
const statusBar = blessed.box({
|
|
86
|
+
// ── BOTTOM BAR ────────────────────────────────────────────────────────────────
|
|
87
|
+
const bottomBar = blessed.box({
|
|
92
88
|
bottom: 0, left: 0, width: '100%', height: 3,
|
|
93
89
|
border: { type: 'line' },
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
content: '',
|
|
90
|
+
style: { fg: C.dim, bg: C.bg2, border: { fg: C.bord2 } },
|
|
91
|
+
tags: false, content: '',
|
|
97
92
|
});
|
|
98
|
-
screen.append(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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();
|
|
93
|
+
screen.append(bottomBar);
|
|
94
|
+
|
|
95
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
96
|
+
function ageLine(ts) {
|
|
97
|
+
if (!ts) return '—';
|
|
98
|
+
const h = Math.round((Date.now() - (ts * 1000)) / 3600000);
|
|
99
|
+
if (h < 1) return 'just now';
|
|
100
|
+
if (h < 24) return h + 'h ago';
|
|
101
|
+
return Math.round(h/24) + 'd ago';
|
|
119
102
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
function typeColor(type) {
|
|
126
|
-
return { semantic: 'cyan', causal: 'yellow', temporal: 'green', entity: 'red' }[type] || 'grey';
|
|
103
|
+
function impDots(imp) {
|
|
104
|
+
const n = imp || 1;
|
|
105
|
+
let s = '●';
|
|
106
|
+
for (let i = 1; i < 5; i++) { s += '──'; s += (i < n) ? '●' : '○'; }
|
|
107
|
+
return s;
|
|
127
108
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return
|
|
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;
|
|
109
|
+
function impBar(imp) {
|
|
110
|
+
const n = imp || 1;
|
|
111
|
+
return '█'.repeat(n) + '░'.repeat(5 - n);
|
|
147
112
|
}
|
|
148
113
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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 ? 'yellow' : 'grey';
|
|
159
|
-
const preview = m.content.slice(0, W - 4);
|
|
160
|
-
|
|
161
|
-
content += isSelected
|
|
162
|
-
? `{${'yellow'}-fg}┌${'─'.repeat(W - 2)}┐{/${'yellow'}-fg}\n`
|
|
163
|
-
: `{${'grey'}-fg}┌${'─'.repeat(W - 2)}┐{/${'grey'}-fg}\n`;
|
|
164
|
-
|
|
165
|
-
// Title row
|
|
166
|
-
content += isSelected ? `{${'yellow'}-fg}│{/${'yellow'}-fg}` : `{${'grey'}-fg}│{/${'grey'}-fg}`;
|
|
167
|
-
content += ` {bold}{${color}-fg}${typeTag}{/${color}-fg}{/bold}`;
|
|
168
|
-
content += ` {${isSelected ? 'white' : 'white'}-fg}${preview}{/${isSelected ? 'white' : 'white'}-fg}`;
|
|
169
|
-
content += ' '.repeat(Math.max(0, W - 6 - typeTag.length - preview.length));
|
|
170
|
-
content += isSelected ? `{${'yellow'}-fg}│{/${'yellow'}-fg}\n` : `{${'grey'}-fg}│{/${'grey'}-fg}\n`;
|
|
171
|
-
|
|
172
|
-
// Timeline row
|
|
173
|
-
content += isSelected ? `{${'yellow'}-fg}│{/${'yellow'}-fg}` : `{${'grey'}-fg}│{/${'grey'}-fg}`;
|
|
174
|
-
content += ` ${renderTimeline(m.importance)}`;
|
|
175
|
-
content += ` {${'grey'}-fg}${age}{/${'grey'}-fg}`;
|
|
176
|
-
content += ' '.repeat(Math.max(0, W - 4 - (m.importance || 1) * 4 - age.length - 2));
|
|
177
|
-
content += isSelected ? `{${'yellow'}-fg}│{/${'yellow'}-fg}\n` : `{${'grey'}-fg}│{/${'grey'}-fg}\n`;
|
|
178
|
-
|
|
179
|
-
content += isSelected
|
|
180
|
-
? `{${'yellow'}-fg}└${'─'.repeat(W - 2)}┘{/${'yellow'}-fg}\n`
|
|
181
|
-
: `{${'grey'}-fg}└${'─'.repeat(W - 2)}┘{/${'grey'}-fg}\n`;
|
|
182
|
-
content += '\n';
|
|
183
|
-
});
|
|
114
|
+
// ── render left cards ─────────────────────────────────────────────────────────
|
|
115
|
+
function renderLeft() {
|
|
116
|
+
const W = Math.floor(screen.cols * 0.5) - 4;
|
|
117
|
+
const lines = [];
|
|
184
118
|
|
|
185
119
|
if (memories.length === 0) {
|
|
186
|
-
|
|
187
|
-
|
|
120
|
+
lines.push('');
|
|
121
|
+
lines.push(' no memories yet');
|
|
122
|
+
lines.push(' press A to add one');
|
|
123
|
+
leftPanel.setContent(lines.join('\n'));
|
|
124
|
+
screen.render();
|
|
125
|
+
return;
|
|
188
126
|
}
|
|
189
127
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
128
|
+
memories.forEach((m, i) => {
|
|
129
|
+
const isSel = i === selected;
|
|
130
|
+
const arrow = isSel ? '▶' : ' ';
|
|
131
|
+
const et = m.edge_type || 'semantic';
|
|
132
|
+
const tlabel = typeLabel(et);
|
|
133
|
+
const age = ageLine(m.created_at);
|
|
134
|
+
const imp = m.importance || 1;
|
|
135
|
+
const tstr = m.created_at
|
|
136
|
+
? new Date(m.created_at * 1000).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'})
|
|
137
|
+
: '--:--';
|
|
138
|
+
const preview = m.content.replace(/\n/g,' ').slice(0, W - 14);
|
|
139
|
+
|
|
140
|
+
if (i > 0) lines.push(' ' + '─'.repeat(W - 2));
|
|
141
|
+
|
|
142
|
+
lines.push(`${arrow} [${tlabel}] ${preview}`);
|
|
143
|
+
lines.push(` ${impDots(imp)} ${age.padEnd(10)} ${tstr}`);
|
|
144
|
+
lines.push('');
|
|
145
|
+
});
|
|
196
146
|
|
|
147
|
+
leftPanel.setContent(lines.join('\n'));
|
|
148
|
+
leftPanel.scrollTo(selected * 4);
|
|
197
149
|
screen.render();
|
|
198
150
|
}
|
|
199
151
|
|
|
200
|
-
// ──
|
|
201
|
-
function
|
|
152
|
+
// ── render right detail ───────────────────────────────────────────────────────
|
|
153
|
+
function renderRight() {
|
|
202
154
|
const m = memories[selected];
|
|
203
155
|
if (!m) {
|
|
204
|
-
|
|
156
|
+
rightPanel.setContent('\n\n select a memory to inspect');
|
|
205
157
|
screen.render();
|
|
206
158
|
return;
|
|
207
159
|
}
|
|
208
160
|
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
161
|
+
const W = Math.floor(screen.cols * 0.5) - 4;
|
|
162
|
+
const et = m.edge_type || 'semantic';
|
|
163
|
+
const tlabel = typeLabel(et);
|
|
164
|
+
const age = ageLine(m.created_at);
|
|
165
|
+
const created = m.created_at ? new Date(m.created_at * 1000).toLocaleString() : '—';
|
|
166
|
+
const imp = m.importance || 1;
|
|
167
|
+
const sameType = memories.filter(x => (x.edge_type||'semantic') === et).length;
|
|
168
|
+
const sep = '─'.repeat(W - 2);
|
|
169
|
+
|
|
170
|
+
const lines = [
|
|
171
|
+
'',
|
|
172
|
+
` [${tlabel}] #${m.id} ${age}`,
|
|
173
|
+
'',
|
|
174
|
+
' ' + sep,
|
|
175
|
+
'',
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
// Word-wrap content
|
|
179
|
+
const words = m.content.split(' ');
|
|
180
|
+
let line = ' ';
|
|
181
|
+
for (const w of words) {
|
|
182
|
+
if ((line + w).length > W) { lines.push(line.trimEnd()); line = ' '; }
|
|
183
|
+
line += w + ' ';
|
|
184
|
+
}
|
|
185
|
+
if (line.trim()) lines.push(line.trimEnd());
|
|
186
|
+
|
|
187
|
+
lines.push('');
|
|
188
|
+
lines.push(' ' + sep);
|
|
189
|
+
lines.push('');
|
|
190
|
+
lines.push(` IMPORTANCE ${impBar(imp)} ${imp}/5`);
|
|
191
|
+
lines.push(` TIMELINE ${impDots(imp)}`);
|
|
192
|
+
lines.push('');
|
|
193
|
+
lines.push(` CREATED ${created}`);
|
|
194
|
+
lines.push(` TYPE POOL ${sameType} of ${memories.length} memories`);
|
|
195
|
+
|
|
196
|
+
if (m.tags) lines.push(` TAGS ${m.tags}`);
|
|
197
|
+
if (m.summary) {
|
|
198
|
+
lines.push(''); lines.push(' ' + sep); lines.push('');
|
|
199
|
+
lines.push(' SUMMARY');
|
|
200
|
+
const sw = m.summary.split(' ');
|
|
201
|
+
let sl = ' ';
|
|
202
|
+
for (const w of sw) {
|
|
203
|
+
if ((sl+w).length > W) { lines.push(sl.trimEnd()); sl = ' '; }
|
|
204
|
+
sl += w + ' ';
|
|
205
|
+
}
|
|
206
|
+
if (sl.trim()) lines.push(sl.trimEnd());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
lines.push(''); lines.push(' ' + sep); lines.push('');
|
|
210
|
+
lines.push(' D delete E importance+ B briefing');
|
|
211
|
+
|
|
212
|
+
rightPanel.setContent(lines.join('\n'));
|
|
229
213
|
screen.render();
|
|
230
214
|
}
|
|
231
215
|
|
|
232
|
-
// ──
|
|
216
|
+
// ── render bottom ─────────────────────────────────────────────────────────────
|
|
217
|
+
function renderBottom(msg) {
|
|
218
|
+
const keys = [
|
|
219
|
+
['↑↓','nav'],['⏎','open'],['A','add'],['D','del'],
|
|
220
|
+
['/','search'],['B','brief'],['T','tab'],['Q','quit'],
|
|
221
|
+
];
|
|
222
|
+
const left = keys.map(([k,v]) => ` [${k}] ${v}`).join(' ');
|
|
223
|
+
const right = msg ? ` ${msg} ` : ` v${PKG.version} `;
|
|
224
|
+
const space = Math.max(1, screen.cols - left.length - right.length - 4);
|
|
225
|
+
bottomBar.setContent(left + ' '.repeat(space) + right);
|
|
226
|
+
screen.render();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── state ─────────────────────────────────────────────────────────────────────
|
|
230
|
+
let memory = null, memories = [], selected = 0;
|
|
231
|
+
|
|
232
|
+
// ── load ──────────────────────────────────────────────────────────────────────
|
|
233
|
+
async function loadMemories(query = '') {
|
|
234
|
+
try {
|
|
235
|
+
if (query) {
|
|
236
|
+
memories = await memory.recall(query, 100);
|
|
237
|
+
} else {
|
|
238
|
+
memories = memory.db.prepare(
|
|
239
|
+
'SELECT * FROM memories ORDER BY created_at DESC LIMIT 500'
|
|
240
|
+
).all();
|
|
241
|
+
}
|
|
242
|
+
if (selected >= memories.length) selected = Math.max(0, memories.length - 1);
|
|
243
|
+
renderLeft(); renderRight();
|
|
244
|
+
renderBottom(`${memories.length} memories`);
|
|
245
|
+
} catch(e) {
|
|
246
|
+
renderBottom('error: ' + e.message.slice(0,50));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── input overlay ─────────────────────────────────────────────────────────────
|
|
233
251
|
const inputOverlay = blessed.box({
|
|
234
|
-
top: 'center', left: 'center', width: '
|
|
252
|
+
top: 'center', left: 'center', width: '50%', height: 5,
|
|
235
253
|
hidden: true,
|
|
236
254
|
border: { type: 'line' },
|
|
237
|
-
style: { bg:
|
|
238
|
-
tags:
|
|
255
|
+
style: { fg: C.cream, bg: C.bg2, border: { fg: C.amber } },
|
|
256
|
+
tags: false,
|
|
239
257
|
});
|
|
240
|
-
|
|
241
258
|
const inputField = blessed.textbox({
|
|
242
259
|
parent: inputOverlay,
|
|
243
|
-
top: 1, left:
|
|
244
|
-
style: { fg:
|
|
260
|
+
top: 1, left: 1, width: '100%-4', height: 1,
|
|
261
|
+
style: { fg: C.orange, bg: C.bg2 },
|
|
245
262
|
inputOnFocus: true,
|
|
246
263
|
});
|
|
247
|
-
|
|
248
264
|
screen.append(inputOverlay);
|
|
249
265
|
|
|
250
266
|
function showInput(label, cb) {
|
|
251
|
-
inputOverlay.setLabel(`
|
|
267
|
+
inputOverlay.setLabel(` ${label} `);
|
|
252
268
|
inputField.setValue('');
|
|
253
269
|
inputOverlay.show();
|
|
254
270
|
screen.render();
|
|
255
271
|
inputField.focus();
|
|
256
|
-
|
|
272
|
+
let buf = '';
|
|
273
|
+
|
|
274
|
+
function cleanup() {
|
|
275
|
+
screen.removeListener('keypress', onKey);
|
|
257
276
|
inputOverlay.hide();
|
|
277
|
+
inputField.setValue('');
|
|
278
|
+
buf = '';
|
|
279
|
+
leftPanel.focus();
|
|
258
280
|
screen.render();
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
}
|
|
281
|
+
}
|
|
282
|
+
function onKey(ch, key) {
|
|
283
|
+
if (!key) return;
|
|
284
|
+
if (key.name === 'enter' || key.name === 'return') {
|
|
285
|
+
const val = buf.trim(); cleanup(); if (val) cb(val); return;
|
|
286
|
+
}
|
|
287
|
+
if (key.name === 'escape') { cleanup(); return; }
|
|
288
|
+
if (key.name === 'backspace') {
|
|
289
|
+
buf = buf.slice(0,-1); inputField.setValue(buf); screen.render(); return;
|
|
290
|
+
}
|
|
291
|
+
if (ch && !key.ctrl && !key.meta && ch.length === 1) {
|
|
292
|
+
buf += ch; inputField.setValue(buf); screen.render();
|
|
276
293
|
}
|
|
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
294
|
}
|
|
295
|
+
screen.on('keypress', onKey);
|
|
284
296
|
}
|
|
285
297
|
|
|
286
|
-
// ──
|
|
298
|
+
// ── actions ───────────────────────────────────────────────────────────────────
|
|
287
299
|
async function addMemory(content) {
|
|
288
300
|
try {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
} catch(e) { renderStatusBar('Error: ' + e.message.slice(0, 40)); }
|
|
301
|
+
await memory.remember(content, {importance:3});
|
|
302
|
+
renderBottom('saved'); await loadMemories();
|
|
303
|
+
} catch(e) { renderBottom('error: ' + e.message.slice(0,50)); }
|
|
293
304
|
}
|
|
294
305
|
|
|
295
306
|
async function deleteSelected() {
|
|
296
|
-
const m = memories[selected];
|
|
297
|
-
if (!m) return;
|
|
307
|
+
const m = memories[selected]; if (!m) return;
|
|
298
308
|
try {
|
|
299
309
|
memory.db.prepare('DELETE FROM memories WHERE id=?').run(m.id);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
} catch(e) { renderStatusBar('Error: ' + e.message.slice(0, 40)); }
|
|
310
|
+
renderBottom('deleted #' + m.id); await loadMemories();
|
|
311
|
+
} catch(e) { renderBottom('error: ' + e.message.slice(0,50)); }
|
|
303
312
|
}
|
|
304
313
|
|
|
305
314
|
async function runRem() {
|
|
306
|
-
|
|
307
|
-
try {
|
|
308
|
-
|
|
309
|
-
renderStatusBar('✓ REM complete');
|
|
310
|
-
await loadMemories();
|
|
311
|
-
} catch(e) { renderStatusBar('REM: ' + e.message.slice(0, 40)); }
|
|
315
|
+
renderBottom('REM running...');
|
|
316
|
+
try { throw new Error("REM not in this build — add dream() to slipstream-core"); }
|
|
317
|
+
catch(e) { renderBottom('REM error: ' + e.message.slice(0,40)); }
|
|
312
318
|
}
|
|
313
319
|
|
|
314
320
|
async function showBriefing() {
|
|
315
321
|
try {
|
|
316
322
|
const brief = await memory.briefing();
|
|
317
|
-
|
|
318
|
-
|
|
323
|
+
rightPanel.setContent('\n BRIEFING\n ' + '─'.repeat(40) + '\n\n' +
|
|
324
|
+
brief.split('\n').map(l => ' ' + l).join('\n'));
|
|
319
325
|
screen.render();
|
|
320
|
-
} catch(e) {
|
|
326
|
+
} catch(e) { renderBottom('briefing error'); }
|
|
321
327
|
}
|
|
322
328
|
|
|
323
|
-
// ──
|
|
324
|
-
screen.key(['q',
|
|
325
|
-
|
|
326
|
-
screen.key(['
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
screen.key(['
|
|
330
|
-
|
|
329
|
+
// ── keyboard ──────────────────────────────────────────────────────────────────
|
|
330
|
+
screen.key(['q','C-c'], () => { screen.destroy(); process.exit(0); });
|
|
331
|
+
screen.key(['up','k'], () => { if (selected > 0) { selected--; renderLeft(); renderRight(); }});
|
|
332
|
+
screen.key(['down','j'], () => { if (selected < memories.length-1) { selected++; renderLeft(); renderRight(); }});
|
|
333
|
+
screen.key(['a'], () => showInput('ADD MEMORY', addMemory));
|
|
334
|
+
screen.key(['d'], deleteSelected);
|
|
335
|
+
screen.key(['/'], () => showInput('SEARCH', async q => loadMemories(q)));
|
|
336
|
+
screen.key(['l'], async () => loadMemories());
|
|
337
|
+
screen.key(['r'], runRem);
|
|
338
|
+
screen.key(['b'], showBriefing);
|
|
339
|
+
screen.key(['tab','t'], () => { screen.focused === leftPanel ? rightPanel.focus() : leftPanel.focus(); screen.render(); });
|
|
340
|
+
screen.key(['e'], () => {
|
|
341
|
+
const m = memories[selected]; if (!m) return;
|
|
342
|
+
m.importance = ((m.importance||1) % 5) + 1;
|
|
343
|
+
try { memory.db.prepare('UPDATE memories SET importance=? WHERE id=?').run(m.importance, m.id); } catch(_) {}
|
|
344
|
+
renderLeft(); renderRight();
|
|
331
345
|
});
|
|
332
346
|
|
|
333
|
-
|
|
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 ──────────────────────────────────────────────────────────────────────
|
|
347
|
+
// ── boot ──────────────────────────────────────────────────────────────────────
|
|
351
348
|
async function boot() {
|
|
352
349
|
const _l = console.log; const _e = console.error;
|
|
353
350
|
console.log = console.error = () => {};
|
|
354
|
-
|
|
355
|
-
renderStatusBar('connecting...');
|
|
356
|
-
renderCards();
|
|
357
|
-
screen.render();
|
|
351
|
+
renderBottom('connecting...'); renderLeft(); screen.render();
|
|
358
352
|
|
|
359
353
|
try {
|
|
360
|
-
const {
|
|
361
|
-
const dbPath = process.env.VEKTOR_DB_PATH ||
|
|
354
|
+
const {createMemory} = require('./slipstream-core');
|
|
355
|
+
const dbPath = process.env.VEKTOR_DB_PATH ||
|
|
356
|
+
path.join(os.homedir(), 'vektor-slipstream-memory.db');
|
|
362
357
|
memory = await createMemory({
|
|
363
358
|
agentId: process.env.VEKTOR_AGENT_ID || 'tui',
|
|
364
359
|
dbPath, silent: true,
|
|
@@ -366,16 +361,12 @@ async function boot() {
|
|
|
366
361
|
});
|
|
367
362
|
await loadMemories();
|
|
368
363
|
} catch(e) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
renderCards();
|
|
372
|
-
renderDetail();
|
|
364
|
+
rightPanel.setContent('\n boot error: ' + e.message.slice(0,60));
|
|
365
|
+
renderLeft(); screen.render();
|
|
373
366
|
}
|
|
374
367
|
|
|
375
368
|
console.log = _l; console.error = _e;
|
|
376
|
-
|
|
377
|
-
screen.render();
|
|
378
|
-
|
|
369
|
+
leftPanel.focus(); screen.render();
|
|
379
370
|
setInterval(() => { if (memory) loadMemories(); }, 30000);
|
|
380
371
|
}
|
|
381
372
|
|