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/vektor-tui.js CHANGED
@@ -2,363 +2,358 @@
2
2
  'use strict';
3
3
 
4
4
  let blessed;
5
- try { blessed = require('blessed'); }
6
- catch(e) {
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
- 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: 'black',
18
- bg2: 'black',
19
- card: 'black',
20
- border: '#2a2a20',
21
- orange: 'yellow',
22
- amber: 'yellow',
23
- cream: 'white',
24
- dim: 'grey',
25
- white: '#e8e0d0',
26
- green: 'green',
27
- red: 'red',
28
- cyan: 'cyan',
29
- sel: 'black',
30
- selbrd: 'yellow',
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
- // ── 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: 'black', fg: 'white' },
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
- const searchLeft = blessed.textbox({
44
- parent: searchBar,
45
- top: 0, left: 0, width: '45%', height: 3,
46
- border: { type: 'line' },
47
- style: { fg: 'white', bg: 'black', border: { fg: 'grey' }, focus: { border: { fg: 'yellow' } } },
48
- inputOnFocus: true,
49
- value: 'search memories...',
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
- const searchRight = blessed.textbox({
53
- parent: searchBar,
54
- top: 0, left: '45%', width: '35%', height: 3,
55
- border: { type: 'line' },
56
- style: { fg: 'white', bg: 'black', border: { fg: 'grey' }, focus: { border: { fg: 'yellow' } } },
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
- const searchBtn = blessed.box({
62
- parent: searchBar,
63
- top: 0, right: 0, width: '20%', height: 3,
58
+ // ── LEFT PANEL ────────────────────────────────────────────────────────────────
59
+ const leftPanel = blessed.box({
60
+ top: 1, left: 0, width: '50%', height: '100%-4',
64
61
  border: { type: 'line' },
65
- tags: true,
66
- style: { fg: 'white', bg: 'black', border: { fg: 'grey' } },
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: '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
- const mainRight = blessed.box({
81
- top: 3, left: '48%', width: '52%', height: '100%-6',
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: { bg: 'black', border: { fg: 'grey' } },
84
- tags: true, scrollable: true, keys: true, mouse: true,
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
- screen.append(mainLeft);
88
- screen.append(mainRight);
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
- tags: true,
95
- style: { bg: 'black', border: { fg: 'grey' } },
96
- content: '',
90
+ style: { fg: C.dim, bg: C.bg2, border: { fg: C.bord2 } },
91
+ tags: false, content: '',
97
92
  });
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();
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
- // ── STATE ─────────────────────────────────────────────────────────────────────
122
- let memory = null, memories = [], selected = 0, filterType = 'all';
123
-
124
- // ── CARD RENDERER ─────────────────────────────────────────────────────────────
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
- 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;
109
+ function impBar(imp) {
110
+ const n = imp || 1;
111
+ return ''.repeat(n) + ''.repeat(5 - n);
147
112
  }
148
113
 
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 ? '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
- content += `\n {${'grey'}-fg}No memories found.{/${'grey'}-fg}\n\n`;
187
- content += ` {${'yellow'}-fg}Press A to add your first memory.{/${'yellow'}-fg}\n`;
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
- mainLeft.setContent(content);
191
-
192
- // Scroll to selected
193
- const cardHeight = 4;
194
- const scrollTo = selected * (cardHeight + 1);
195
- mainLeft.scrollTo(scrollTo);
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
- // ── DETAIL PANEL ─────────────────────────────────────────────────────────────
201
- function renderDetail() {
152
+ // ── render right detail ───────────────────────────────────────────────────────
153
+ function renderRight() {
202
154
  const m = memories[selected];
203
155
  if (!m) {
204
- mainRight.setContent(`\n\n {${'grey'}-fg}Select a memory to inspect{/${'grey'}-fg}`);
156
+ rightPanel.setContent('\n\n select a memory to inspect');
205
157
  screen.render();
206
158
  return;
207
159
  }
208
160
 
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} {${'grey'}-fg}#${m.id}{/${'grey'}-fg}\n\n`;
215
- content += ` {bold}{${'white'}-fg}${m.content}{/${'white'}-fg}{/bold}\n\n`;
216
- content += ` {${'grey'}-fg}${'─'.repeat(40)}{/${'grey'}-fg}\n\n`;
217
- content += ` {${'grey'}-fg}IMPORTANCE {/${'grey'}-fg} ${renderTimeline(m.importance)}\n\n`;
218
- content += ` {${'grey'}-fg}CREATED {/${'grey'}-fg} {${'white'}-fg}${age}{/${'white'}-fg}\n\n`;
219
- content += ` {${'grey'}-fg}${'─'.repeat(40)}{/${'grey'}-fg}\n\n`;
220
- content += ` {${'grey'}-fg}[D]{/${'grey'}-fg} delete {${'grey'}-fg}[E]{/${'grey'}-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 += ` {${'grey'}-fg}${'─'.repeat(40)}{/${'grey'}-fg}\n\n`;
226
- content += ` {${'grey'}-fg}TYPE POOL {/${'grey'}-fg} {${color}-fg}${sameType}{/${color}-fg} {${'grey'}-fg}of ${total} memories{/${'grey'}-fg}\n`;
227
-
228
- mainRight.setContent(content);
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
- // ── INPUT OVERLAY ─────────────────────────────────────────────────────────────
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: '60%', height: 5,
252
+ top: 'center', left: 'center', width: '50%', height: 5,
235
253
  hidden: true,
236
254
  border: { type: 'line' },
237
- style: { bg: 'black', border: { fg: 'yellow' } },
238
- tags: true,
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: 2, width: '100%-6', height: 1,
244
- style: { fg: 'yellow', bg: 'black' },
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(` {#e8742a-fg}${label}{/#e8742a-fg} `);
267
+ inputOverlay.setLabel(` ${label} `);
252
268
  inputField.setValue('');
253
269
  inputOverlay.show();
254
270
  screen.render();
255
271
  inputField.focus();
256
- inputField.readInput((err, val) => {
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
- 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
- }
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
- // ── ACTIONS ───────────────────────────────────────────────────────────────────
298
+ // ── actions ───────────────────────────────────────────────────────────────────
287
299
  async function addMemory(content) {
288
300
  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)); }
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
- renderStatusBar('Deleted #' + m.id);
301
- await loadMemories();
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
- 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)); }
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
- mainRight.setLabel(' {bold}BRIEFING{/bold} ');
318
- mainRight.setContent('\n' + brief.split('\n').map(l => ' ' + l).join('\n'));
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) { renderStatusBar('Briefing error'); }
326
+ } catch(e) { renderBottom('briefing error'); }
321
327
  }
322
328
 
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(); }
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
- 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 ──────────────────────────────────────────────────────────────────────
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 { createMemory } = require('./slipstream-core');
361
- const dbPath = process.env.VEKTOR_DB_PATH || path.join(process.cwd(), 'slipstream-memory.db');
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
- renderStatusBar('Error: ' + e.message.slice(0, 50));
370
- memories = [];
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
- mainLeft.focus();
377
- screen.render();
378
-
369
+ leftPanel.focus(); screen.render();
379
370
  setInterval(() => { if (memory) loadMemories(); }, 30000);
380
371
  }
381
372