openaxies 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openaxies",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/src/App.js CHANGED
@@ -1,25 +1,16 @@
1
1
  import React from 'react';
2
2
  import { Box, Text, useInput, useStdout } from 'ink';
3
3
  import { hex } from './config/theme.js';
4
- import { getCommands } from './config/commands.js';
4
+
5
5
  import { getModels, getModelById, DEFAULT_MODEL_ID } from './config/models.js';
6
6
  import { callModel } from './providers/index.js';
7
- import { createComposerDock, DOCK_HEIGHT } from './components/ComposerDock.js';
7
+ import { createBrandHeader, BRAND_HEIGHT } from './components/BrandHeader.js';
8
+ import TextInput from 'ink-text-input';
8
9
 
9
10
  const h = React.createElement;
10
11
 
11
12
  export default AppRoot;
12
13
 
13
- const LOGO = [
14
- ' \u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588',
15
- '\u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 ',
16
- '\u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588',
17
- '\u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588',
18
- ' \u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588 ',
19
- ];
20
-
21
- const LOGO_COLORS = ['#00F0FF', '#00DDFF', '#00CCFF', '#00BBFF', '#00AAFF'];
22
-
23
14
  function buildHistory(messages, newText) {
24
15
  const h = [];
25
16
  for (let i = 0; i < messages.length; i++) {
@@ -36,20 +27,28 @@ function buildHistory(messages, newText) {
36
27
  return h;
37
28
  }
38
29
 
39
- function formatTimer(s) {
30
+ function fmtTime(s) {
40
31
  if (typeof s !== 'number' || s < 0) return '0.0s';
41
32
  return s.toFixed(1) + 's';
42
33
  }
43
34
 
44
- function hr(cols, color) {
35
+ function hr(cols) {
45
36
  const n = typeof cols === 'number' && cols > 0 ? cols : 80;
46
- const c = typeof color === 'string' ? color : '#1A1A28';
47
37
  let s = '';
48
38
  for (let i = 0; i < n; i++) s = s + '\u2500';
49
- return h(Text, { color: c }, s);
39
+ return h(Text, { color: '#1A1A28' }, s);
50
40
  }
51
41
 
52
- const SPINNER = ['\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF', '\u25D5', '\u25D0', '\u25D4'];
42
+ const SPINNER = ['\u25CB', '\u25D4', '\u25D0', '\u25D5', '\u25CF', '\u25D5', '\u25D0', '\u25D4', '\u25D1', '\u25D2', '\u25D3'];
43
+
44
+ const COMMANDS = [
45
+ { id: 'model', trigger: '/model', desc: 'Switch model' },
46
+ { id: 'thoughts', trigger: '/thoughts', desc: 'Toggle thinking visibility' },
47
+ { id: 'resume', trigger: '/resume', desc: 'Re-run last query' },
48
+ { id: 'clear', trigger: '/clear', desc: 'Clear conversation' },
49
+ { id: 'help', trigger: '/help', desc: 'Show commands' },
50
+ { id: 'exit', trigger: '/exit', desc: 'Quit' },
51
+ ];
53
52
 
54
53
  function AppRoot() {
55
54
  const { stdout } = useStdout();
@@ -67,50 +66,31 @@ function AppRoot() {
67
66
  const [showOverlay, setShowOverlay] = React.useState(false);
68
67
  const [overlayIndex, setOverlayIndex] = React.useState(0);
69
68
  const [toolInfo, setToolInfo] = React.useState(null);
69
+ const [showThoughts, setShowThoughts] = React.useState(true);
70
70
 
71
- const commands = getCommands();
72
- const LOGO_H = 5;
73
- const overlayH = showOverlay ? 6 : 0;
74
- const fixedH = LOGO_H + overlayH + DOCK_HEIGHT;
75
- const availLines = Math.max(1, rows - fixedH);
71
+ const STATUS_H = 3;
72
+ const DOCK_H = 1;
73
+ const fixedH = BRAND_HEIGHT + STATUS_H + DOCK_H;
74
+ const availLines = Math.max(10, rows - fixedH);
76
75
 
77
76
  const abortRef = React.useRef(null);
78
77
  const connRef = React.useRef(null);
79
78
  const spinnerRef = React.useRef(null);
80
79
  const timerRef = React.useRef(null);
81
80
  const thinkStartRef = React.useRef(null);
81
+ const lastQueryRef = React.useRef('');
82
82
 
83
83
  React.useEffect(function () {
84
- if (isThinking === false) {
85
- if (spinnerRef.current !== null) {
86
- clearInterval(spinnerRef.current);
87
- spinnerRef.current = null;
88
- }
89
- if (timerRef.current !== null) {
90
- clearInterval(timerRef.current);
91
- timerRef.current = null;
92
- }
93
- return;
94
- }
84
+ if (isThinking === false) return;
95
85
  let idx = 0;
96
- spinnerRef.current = setInterval(function () {
97
- idx = (idx + 1) % SPINNER.length;
98
- setSpinnerIdx(idx);
99
- }, 100);
100
- timerRef.current = setInterval(function () {
101
- if (thinkStartRef.current !== null) {
102
- setThinkingElapsed((Date.now() - thinkStartRef.current) / 1000);
103
- }
86
+ const si = setInterval(function () { idx = (idx + 1) % SPINNER.length; setSpinnerIdx(idx); }, 120);
87
+ const ti = setInterval(function () {
88
+ if (thinkStartRef.current !== null) setThinkingElapsed((Date.now() - thinkStartRef.current) / 1000);
104
89
  }, 100);
90
+ spinnerRef.current = si;
91
+ timerRef.current = ti;
105
92
  return function () {
106
- if (spinnerRef.current !== null) {
107
- clearInterval(spinnerRef.current);
108
- spinnerRef.current = null;
109
- }
110
- if (timerRef.current !== null) {
111
- clearInterval(timerRef.current);
112
- timerRef.current = null;
113
- }
93
+ clearInterval(si); clearInterval(ti);
114
94
  };
115
95
  }, [isThinking]);
116
96
 
@@ -136,6 +116,7 @@ function AppRoot() {
136
116
  async function handleSubmit(text) {
137
117
  const safe = typeof text === 'string' ? text : '';
138
118
  if (safe.length === 0 || streamingActive === true) return;
119
+ lastQueryRef.current = safe;
139
120
  setStreamingActive(true);
140
121
  setToolInfo(null);
141
122
  setStreamText(' connecting');
@@ -148,16 +129,13 @@ function AppRoot() {
148
129
  return ' connecting';
149
130
  });
150
131
  }, 400);
151
- setMessages(function (p) {
152
- return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]);
153
- });
132
+ setMessages(function (p) { return p.concat([{ role: 'user', content: safe, id: 'u-' + Date.now() }]); });
154
133
  setInputBuffer('');
155
134
  const modelInfo = getModelById(activeModel);
156
135
  if (modelInfo === null) {
157
136
  if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
158
- setStreamingActive(false);
159
- setStreamText('');
160
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'No model selected. Use /model.', id: 'e-' + Date.now() }]); });
137
+ setStreamingActive(false); setStreamText('');
138
+ setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'No model selected.', id: 'e-' + Date.now() }]); });
161
139
  return;
162
140
  }
163
141
  const history = buildHistory(messages, safe);
@@ -170,90 +148,72 @@ function AppRoot() {
170
148
  let thinkStarted = false;
171
149
  for await (const event of gen) {
172
150
  if (abortController.signal.aborted === true) break;
173
- if (firstEvent === true) {
174
- firstEvent = false;
175
- if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
176
- }
177
- if (event.type === 'tool') {
178
- setToolInfo({ tool: event.tool, used: event.used, sites: event.sites, query: event.query });
179
- continue;
180
- }
151
+ if (firstEvent === true) { firstEvent = false; if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null; }
152
+ if (event.type === 'tool') { setToolInfo({ tool: event.tool, used: event.used, sites: event.sites, query: event.query }); continue; }
181
153
  if (event.type === 'thinking' || event.type === 'token') {
182
- const content = typeof event.content === 'string' ? event.content : '';
183
- accumulated = accumulated + content;
154
+ const c = typeof event.content === 'string' ? event.content : '';
155
+ accumulated = accumulated + c;
184
156
  const inside = checkThink(accumulated);
185
157
  setStreamText(accumulated);
186
158
  setIsThinking(inside);
187
- if (inside === true && thinkStarted === false) {
188
- thinkStarted = true;
189
- thinkStartRef.current = Date.now();
190
- setThinkingElapsed(0);
191
- }
192
- if (inside === false && thinkStarted === true) {
193
- thinkStarted = false;
194
- thinkStartRef.current = null;
195
- setThinkingElapsed(0);
196
- }
197
- await new Promise(function (r) { setTimeout(r, 0); });
159
+ if (inside === true && thinkStarted === false) { thinkStarted = true; thinkStartRef.current = Date.now(); setThinkingElapsed(0); }
160
+ if (inside === false && thinkStarted === true) { thinkStarted = false; thinkStartRef.current = null; setThinkingElapsed(0); }
198
161
  }
199
162
  if (event.type === 'done') break;
200
163
  if (event.type === 'timeout') {
201
164
  if (accumulated.length > 0) accumulated = accumulated + '\n\n\u2014 stream timed out \u2014';
202
- setStreamText(accumulated);
203
- break;
165
+ setStreamText(accumulated); break;
204
166
  }
205
167
  }
206
168
  if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
207
- setIsThinking(false);
208
- thinkStartRef.current = null;
209
- setThinkingElapsed(0);
210
- setStreamingActive(false);
169
+ setIsThinking(false); thinkStartRef.current = null; setThinkingElapsed(0); setStreamingActive(false);
211
170
  setStreamText('');
212
171
  if (accumulated.length > 0) {
213
172
  setMessages(function (p) { return p.concat([{ role: 'assistant', content: accumulated, id: 'a-' + Date.now() }]); });
214
173
  }
215
174
  } catch (err) {
216
175
  if (connRef.current !== null) clearInterval(connRef.current), connRef.current = null;
217
- setIsThinking(false);
218
- thinkStartRef.current = null;
219
- setThinkingElapsed(0);
220
- setStreamingActive(false);
221
- setStreamText('');
176
+ setIsThinking(false); thinkStartRef.current = null; setThinkingElapsed(0); setStreamingActive(false); setStreamText('');
222
177
  const errMsg = err !== null && err !== undefined ? (err.message || String(err)) : 'Unknown error';
223
- setMessages(function (p) {
224
- return p.concat([{ role: 'assistant', content: 'Error: ' + errMsg + '\n\nSpace cold-starting? Try again.', id: 'er-' + Date.now() }]);
225
- });
178
+ setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Error: ' + errMsg, id: 'er-' + Date.now() }]); });
226
179
  }
227
180
  }
228
181
 
229
182
  function handleChange(v) {
230
- const s = typeof v === 'string' ? v : '';
231
- setInputBuffer(s);
232
- if (s.length === 1 && s[0] === '/' && showOverlay === false) { setShowOverlay(true); setOverlayIndex(0); }
233
- if (showOverlay === true && (s.length === 0 || s[0] !== '/')) { setShowOverlay(false); }
183
+ setInputBuffer(v);
184
+ if (v.length === 1 && v[0] === '/') { setShowOverlay(true); setOverlayIndex(0); }
185
+ if (showOverlay === true && (v.length === 0 || v[0] !== '/')) { setShowOverlay(false); }
234
186
  }
235
187
 
236
188
  function handleCmd(item) {
237
189
  if (item === null || item === undefined) return;
238
- const t = item.value;
190
+ const t = item.trigger;
239
191
  if (typeof t !== 'string') return;
240
192
  if (t === '/exit') process.exit(0);
241
- if (t === '/clear') setMessages([]);
193
+ if (t === '/clear') { setMessages([]); }
242
194
  if (t === '/model') cycleModel();
243
- if (t === '/help') {
244
- setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /exit', id: 'h-' + Date.now() }]); });
195
+ if (t === '/thoughts') { setShowThoughts(function (p) { return !p; }); }
196
+ if (t === '/resume') {
197
+ const q = lastQueryRef.current;
198
+ if (q.length > 0 && streamingActive === false) handleSubmit(q);
245
199
  }
246
- setInputBuffer('');
247
- setShowOverlay(false);
248
- setOverlayIndex(0);
200
+ if (t === '/help') { setMessages(function (p) { return p.concat([{ role: 'assistant', content: 'Commands: /help /clear /model /thoughts /resume /exit', id: 'h-' + Date.now() }]); }); }
201
+ setInputBuffer(''); setShowOverlay(false); setOverlayIndex(0);
249
202
  }
250
203
 
251
204
  useInput(function handleInput(input, key) {
252
205
  if (showOverlay === true) {
253
206
  if (key.escape === true) { setShowOverlay(false); setInputBuffer(''); setOverlayIndex(0); return; }
254
- if (key.upArrow === true) { setOverlayIndex(function (p) { return p > 0 ? p - 1 : commands.length - 1; }); return; }
255
- if (key.downArrow === true) { setOverlayIndex(function (p) { return p < commands.length - 1 ? p + 1 : 0; }); return; }
256
- if (key.return === true) { const cmd = commands[overlayIndex]; if (cmd !== null && cmd !== undefined) handleCmd({ value: cmd.trigger }); return; }
207
+ if (key.upArrow === true) { setOverlayIndex(function (p) { return p > 0 ? p - 1 : COMMANDS.length - 1; }); return; }
208
+ if (key.downArrow === true) { setOverlayIndex(function (p) { return p < COMMANDS.length - 1 ? p + 1 : 0; }); return; }
209
+ if (key.return === true) { const cmd = COMMANDS[overlayIndex]; if (cmd !== null && cmd !== undefined) handleCmd(cmd); return; }
210
+ return;
211
+ }
212
+ if (key.escape === true) {
213
+ if (streamingActive === true) {
214
+ if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; }
215
+ setStreamingActive(false); setStreamText(''); setIsThinking(false);
216
+ }
257
217
  return;
258
218
  }
259
219
  if (key.return === true) {
@@ -261,144 +221,214 @@ function AppRoot() {
261
221
  if (s.length > 0 && streamingActive === false) handleSubmit(s);
262
222
  return;
263
223
  }
264
- if (key.ctrl === true && (input === 'c' || input === 'C')) {
265
- if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; setStreamingActive(false); setStreamText(''); }
266
- process.exit(0);
224
+ if (key.ctrl === true) {
225
+ if (input === 'c' || input === 'C') {
226
+ if (abortRef.current !== null) { try { abortRef.current.abort(); } catch (_) {} abortRef.current = null; }
227
+ setStreamingActive(false); setStreamText(''); setIsThinking(false);
228
+ process.exit(0);
229
+ }
230
+ if (input === 't' || input === 'T') { setShowThoughts(function (p) { return !p; }); return; }
231
+ if (input === 'p' || input === 'P') { cycleModel(); return; }
232
+ if (input === 'r' || input === 'R') {
233
+ const q = lastQueryRef.current;
234
+ if (q.length > 0 && streamingActive === false) handleSubmit(q);
235
+ return;
236
+ }
237
+ if (input === 'l' || input === 'L') { setMessages([]); return; }
238
+ if (input === 'k' || input === 'K') { setInputBuffer('/'); setShowOverlay(true); setOverlayIndex(0); return; }
267
239
  }
268
240
  });
269
241
 
242
+ function filterText(t) {
243
+ if (showThoughts === true) return t;
244
+ return t.split('<think>').join('').split('</think>').join('');
245
+ }
246
+
270
247
  const activeInfo = getModelById(activeModel);
271
248
  const activeLabel = activeInfo !== null ? activeInfo.label : 'OpenAxies';
272
249
 
273
- const logoEls = [];
274
- for (let r = 0; r < LOGO.length; r++) {
275
- logoEls.push(
276
- h(Text, { key: 'l' + r, color: LOGO_COLORS[r % LOGO_COLORS.length], bold: true }, LOGO[r])
277
- );
250
+ // Build sections
251
+ const sections = [];
252
+
253
+ // Show overlay items
254
+ if (showOverlay === true) {
255
+ sections.push({ t: 'sep' });
256
+ sections.push({ t: 'header_bright', text: 'Commands' });
257
+ for (let i = 0; i < COMMANDS.length; i++) {
258
+ const c = COMMANDS[i];
259
+ const sel = i === overlayIndex;
260
+ sections.push({ t: 'cmd', trigger: c.trigger, desc: c.desc, selected: sel });
261
+ }
262
+ sections.push({ t: 'sep' });
278
263
  }
279
- const logo = h(Box, { flexDirection: 'column', width: '100%', flexShrink: 0, paddingLeft: 0 }, ...logoEls);
280
264
 
281
- const viewLines = [];
265
+ // Conversation history
282
266
  for (let i = 0; i < messages.length; i++) {
283
267
  const msg = messages[i];
284
268
  if (msg === null || typeof msg !== 'object') continue;
285
269
  const content = typeof msg.content === 'string' ? msg.content : '';
286
270
  if (content.length === 0) continue;
271
+
287
272
  if (msg.role === 'user') {
288
- viewLines.push({ t: 'user', text: '\u25B7 ' + content });
273
+ sections.push({ t: 'sep' });
274
+ sections.push({ t: 'user', text: content });
289
275
  } else {
290
- viewLines.push({ t: 'text', text: content });
276
+ if (sections.length > 0 && sections[sections.length - 1].t !== 'sep') sections.push({ t: 'sep' });
277
+ const display = filterText(content);
278
+ sections.push({ t: 'text', text: display });
291
279
  }
292
280
  }
293
281
 
282
+ // Streaming section
294
283
  const hasStream = typeof streamText === 'string' && streamText.length > 0;
295
284
  if (hasStream === true) {
285
+ const clean = filterText(streamText);
286
+
296
287
  if (toolInfo !== null && toolInfo.used === true && toolInfo.query.length > 0) {
297
- viewLines.push({ t: 'tool', text: '\u2500\u2500 ' + toolInfo.tool + ': "' + toolInfo.query + '" (' + toolInfo.sites + ' sites)' });
288
+ sections.push({ t: 'tool', tool: toolInfo.tool, query: toolInfo.query, sites: toolInfo.sites });
298
289
  }
290
+
299
291
  if (isThinking === true) {
300
- viewLines.push({ t: 'think', spin: SPINNER[spinnerIdx], elapsed: formatTimer(thinkingElapsed) });
301
- const clean = streamText.split('<think>').join('').split('</think>').join('');
302
- viewLines.push({ t: 'think-text', text: clean });
292
+ sections.push({ t: 'thinking', spin: SPINNER[spinnerIdx], elapsed: fmtTime(thinkingElapsed), content: clean });
303
293
  } else {
304
- const clean = streamText.split('<think>').join('').split('</think>').join('');
305
- viewLines.push({ t: 'text', text: clean });
294
+ sections.push({ t: 'stream', text: clean });
306
295
  }
307
296
  }
308
297
 
309
- if (viewLines.length === 0) {
310
- viewLines.push({ t: 'idle', text: ' \u25B8 type a message to begin | /model to switch models | /help for commands' });
311
- viewLines.push({ t: 'idle', text: '' });
312
- viewLines.push({ t: 'idle-sm', text: ' Models: OpenAxies Llama | OpenAxies GPT | OpenAxies DeepSeek' });
313
- viewLines.push({ t: 'idle-sm', text: ' Current: ' + activeLabel });
298
+ // Idle ready state
299
+ if (sections.length === 0) {
300
+ sections.push({ t: 'sep' });
301
+ sections.push({ t: 'ready', text: 'Ready' });
302
+ sections.push({ t: 'sep' });
303
+ }
304
+
305
+ // Estimate line count per section
306
+ function estLines(s) {
307
+ if (s.t === 'sep' || s.t === 'ready' || s.t === 'stream' || s.t === 'text' || s.t === 'tool') return 1;
308
+ if (s.t === 'cmd') return 1;
309
+ if (s.t === 'header_bright') return 1;
310
+ if (s.t === 'user') return 2;
311
+ if (s.t === 'thinking') return 2;
312
+ return 1;
314
313
  }
315
314
 
316
- const visible = Math.max(1, availLines - 1);
317
- const display = viewLines.slice(-visible);
318
- const viewEls = [];
315
+ // Limit to available lines - show the LAST (newest) sections
316
+ let totalEst = 0;
317
+ let lastN = 0;
318
+ for (let i = sections.length - 1; i >= 0; i--) {
319
+ const l = estLines(sections[i]);
320
+ if (totalEst + l > availLines) break;
321
+ totalEst += l;
322
+ lastN = sections.length - i;
323
+ }
324
+ const visible = sections.slice(sections.length - lastN);
325
+
326
+ // Render
327
+ const sectionEls = [];
328
+ for (let i = 0; i < visible.length; i++) {
329
+ const s = visible[i];
330
+ const ki = 's-' + i;
319
331
 
320
- for (let i = 0; i < display.length; i++) {
321
- const l = display[i];
322
- if (l.t === 'think') {
323
- viewEls.push(
324
- h(Box, { key: 'k-' + i, height: 1, paddingLeft: 2 },
325
- h(Text, { color: '#FF9F43', bold: true }, l.spin + ' Thinking \u2022 ' + l.elapsed)
332
+ if (s.t === 'sep') {
333
+ sectionEls.push(h(Box, { key: ki, height: 1 }, hr(cols)));
334
+ } else if (s.t === 'user') {
335
+ sectionEls.push(
336
+ h(Box, { key: ki + '-h', height: 1, paddingLeft: 1 },
337
+ h(Text, { color: '#888899', bold: true }, 'You')
338
+ )
339
+ );
340
+ sectionEls.push(
341
+ h(Box, { key: ki + '-t', height: 1, paddingLeft: 2 },
342
+ h(Text, { color: hex.neonBlue, wrap: 'wrap' }, s.text)
343
+ )
344
+ );
345
+ } else if (s.t === 'text' || s.t === 'stream') {
346
+ sectionEls.push(
347
+ h(Box, { key: ki, height: 1, paddingLeft: 1 },
348
+ h(Text, { color: hex.primary, wrap: 'wrap' }, s.text)
326
349
  )
327
350
  );
328
- } else if (l.t === 'think-text') {
329
- viewEls.push(
330
- h(Box, { key: 'kt-' + i, height: 1, paddingLeft: 3 },
331
- h(Text, { color: '#FF9F43' }, l.text)
351
+ } else if (s.t === 'thinking') {
352
+ sectionEls.push(
353
+ h(Box, { key: ki + '-h', height: 1, paddingLeft: 1 },
354
+ h(Text, { color: '#FF9F43', bold: true }, ' ' + s.spin + ' Thinking \u2022 ' + s.elapsed)
332
355
  )
333
356
  );
334
- } else if (l.t === 'tool') {
335
- viewEls.push(
336
- h(Box, { key: 'tl-' + i, height: 1, paddingLeft: 2 },
337
- h(Text, { color: '#5B5B8A' }, l.text)
357
+ sectionEls.push(
358
+ h(Box, { key: ki + '-c', height: 1, paddingLeft: 2 },
359
+ h(Text, { color: '#FF9F43', wrap: 'wrap' }, s.content)
338
360
  )
339
361
  );
340
- } else if (l.t === 'user') {
341
- viewEls.push(
342
- h(Box, { key: 'u-' + i, height: 1, paddingLeft: 1 },
343
- h(Text, { color: hex.neonBlue }, l.text)
362
+ } else if (s.t === 'tool') {
363
+ sectionEls.push(
364
+ h(Box, { key: ki, height: 1, paddingLeft: 1 },
365
+ h(Text, { color: '#5B5B8A' }, '\u2500\u2500 ' + s.tool + ' \u2014 ' + (s.query || '') + ' (' + (s.sites || 0) + ' sites)')
344
366
  )
345
367
  );
346
- } else if (l.t === 'text') {
347
- viewEls.push(
348
- h(Box, { key: 'a-' + i, height: 1, paddingLeft: 2 },
349
- h(Text, { color: hex.primary }, l.text)
368
+ } else if (s.t === 'header_bright') {
369
+ sectionEls.push(
370
+ h(Box, { key: ki, height: 1, paddingLeft: 1 },
371
+ h(Text, { color: '#888899', bold: true }, s.text || '')
350
372
  )
351
373
  );
352
- } else if (l.t === 'idle') {
353
- viewEls.push(
354
- h(Box, { key: 'i-' + i, height: 1, paddingLeft: 1 },
355
- h(Text, { color: '#555577' }, l.text)
374
+ } else if (s.t === 'cmd') {
375
+ const prefix = s.selected === true ? '\u276F ' : ' ';
376
+ const col = s.selected === true ? hex.neonBlue : '#666688';
377
+ sectionEls.push(
378
+ h(Box, { key: ki, height: 1, paddingLeft: 2 },
379
+ h(Text, { color: col }, prefix + s.trigger),
380
+ h(Text, { color: '#444466' }, ' ' + (s.desc || ''))
356
381
  )
357
382
  );
358
- } else if (l.t === 'idle-sm') {
359
- viewEls.push(
360
- h(Box, { key: 'is-' + i, height: 1, paddingLeft: 1 },
361
- h(Text, { color: '#333355' }, l.text)
383
+ } else if (s.t === 'ready') {
384
+ sectionEls.push(
385
+ h(Box, { key: ki, height: 1, paddingLeft: 1 },
386
+ h(Text, { color: '#00FF88', bold: true }, s.text)
362
387
  )
363
388
  );
364
389
  }
365
390
  }
366
391
 
392
+ // Fill remaining lines
393
+ while (sectionEls.length < availLines) {
394
+ sectionEls.push(h(Box, { key: 'fl-' + sectionEls.length, height: 1 }, h(Text, {}, '')));
395
+ }
396
+
367
397
  const viewport = h(Box, {
368
398
  flexGrow: 1,
369
399
  height: availLines,
370
400
  flexDirection: 'column',
371
401
  overflow: 'hidden',
372
- paddingTop: 0,
373
- paddingBottom: 0,
374
- }, ...viewEls);
375
-
376
- const slashOverlay = showOverlay === true ? createSlashOverlay(commands, overlayIndex) : null;
377
-
378
- const dock = createComposerDock({
379
- value: inputBuffer,
380
- onChange: handleChange,
381
- onSubmit: function () {},
382
- inputActive: showOverlay === false,
383
- isStreaming: streamingActive,
384
- terminalWidth: cols,
385
- modelLabel: activeLabel + ' \u25CF',
386
- modelStatus: true,
387
- });
402
+ }, ...sectionEls);
388
403
 
389
- return h(Box, {
390
- flexDirection: 'column',
391
- width: '100%',
392
- height: rows,
393
- backgroundColor: hex.black,
394
- overflow: 'hidden',
395
- },
396
- logo,
397
- h(Box, { height: 1 }, hr(cols, '#1A1A2E')),
404
+ // Status bar
405
+ const statusBar = h(Box, { flexDirection: 'column', height: STATUS_H, flexShrink: 0 },
406
+ h(Box, { height: 1 }, hr(cols)),
407
+ h(Box, { height: 1, paddingLeft: 2 },
408
+ h(Text, { color: '#444466' }, 'esc interrupt ctrl+t thoughts ctrl+p models'),
409
+ ),
410
+ h(Box, { height: 1, paddingLeft: 2 },
411
+ h(Text, { color: '#444466' }, 'ctrl+l clear ctrl+r resume ctrl+k commands'),
412
+ )
413
+ );
414
+
415
+ // Prompt
416
+ const promptLine = h(Box, { height: DOCK_H, flexShrink: 0, paddingLeft: 2, paddingRight: 2 },
417
+ h(Text, { color: hex.greenOnline }, '\u258C'),
418
+ h(TextInput, {
419
+ value: inputBuffer,
420
+ onChange: handleChange,
421
+ onSubmit: function () {},
422
+ placeholder: '',
423
+ focus: showOverlay === false,
424
+ showCursor: true,
425
+ })
426
+ );
427
+
428
+ return h(Box, { flexDirection: 'column', width: '100%', height: rows, overflow: 'hidden' },
429
+ createBrandHeader(),
398
430
  viewport,
399
- slashOverlay,
400
- dock
431
+ statusBar,
432
+ promptLine
401
433
  );
402
434
  }
403
-
404
- import { createSlashOverlay } from './components/SlashOverlay.js';
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { hex } from '../config/theme.js';
4
+
5
+ const h = React.createElement;
6
+
7
+ const LOGO = [
8
+ '██████╗ ██████╗ ███████╗███╗ ██╗ █████╗ ██╗ ██╗██╗███████╗███████╗',
9
+ '██╔═══██╗██╔══██╗██╔════╝████╗ ██║██╔══██╗╚██╗██╔╝██║██╔════╝██╔════╝',
10
+ '██║ ██║██████╔╝█████╗ ██╔██╗ ██║███████║ ╚███╔╝ ██║█████╗ ███████╗',
11
+ '██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║ ██╔██╗ ██║██╔══╝ ╚════██║',
12
+ '╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██╔╝ ██╗██║███████╗███████║',
13
+ ' ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝',
14
+ ];
15
+
16
+ export function createBrandHeader() {
17
+ const els = [];
18
+ for (let r = 0; r < LOGO.length; r++) {
19
+ els.push(
20
+ h(Text, { key: 'l' + r, color: '#00BBFF', bold: true }, LOGO[r])
21
+ );
22
+ }
23
+ els.push(
24
+ h(Text, { key: 'sub', color: '#666688' }, 'OpenAxies Agent Runtime')
25
+ );
26
+ return h(Box, {
27
+ flexDirection: 'column',
28
+ width: '100%',
29
+ flexShrink: 0,
30
+ paddingLeft: 0,
31
+ paddingTop: 0,
32
+ paddingBottom: 0,
33
+ }, ...els);
34
+ }
35
+
36
+ export const BRAND_HEIGHT = 7;
@@ -44,6 +44,9 @@ export async function* streamResponse(endpoint, body, signal) {
44
44
  headers: {
45
45
  'Content-Type': 'application/json',
46
46
  'Accept': 'text/event-stream',
47
+ 'Authorization': typeof process !== 'undefined' && process.env && process.env.OPENAXIES_HF_TOKEN
48
+ ? 'Bearer ' + process.env.OPENAXIES_HF_TOKEN
49
+ : undefined,
47
50
  },
48
51
  body: JSON.stringify(body),
49
52
  signal: combinedSignal,