clarity-ai 6.5.5 → 6.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/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ---
4
4
 
5
+ ## 6.6.0 (2026-06-06)
6
+
7
+ ### Premium UI rebuild + DeepSeek R1 reasoning models
8
+ - **Models**: Flash → DeepSeek-R1-Distill-Qwen-1.5B (reasoning), Heavy → DeepSeek-R1-Distill-Qwen-7B (reasoning)
9
+ - **Permanent center CLARITY ASCII logo** — stays as background until first message
10
+ - **Box-drawing overlays** (`┌─┐│└─┘`) on CommandPicker and ModelPicker
11
+ - **Non-bouncing 3-tier viewport** — topBar(1) + viewport(fill) + dock(4)
12
+ - **Cleaner message rendering** — `│` prefix for assistant text, no wasted rows
13
+ - **Tool log compression** — completed tools collapse to single-line chips
14
+ - **Orange selection bar** (`#FF6B35`) on all pickers
15
+ - **DeepSeek R1 reasoning** — model outputs `<think>` blocks naturally
16
+
17
+ ## 6.5.6 (2026-06-06)
18
+
19
+ ### Fix first-request timeout — model download needs >8s
20
+ - Raised `CONNECT_TIMEOUT` from 8s to 120s so the CLI waits for model download on cold Space
21
+ - Raised `READ_TIMEOUT` to 15s for per-chunk streaming
22
+ - Fixes "Processing..." hang + 404 in Space logs when model is still loading
23
+
5
24
  ## 6.5.5 (2026-06-06)
6
25
 
7
26
  ### Fix model routing — repo prefix stripped by regex
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "6.5.5",
3
+ "version": "6.6.0",
4
4
  "description": "CLARITY — terminal AI agent with local GGUF inference on HF Spaces",
5
5
  "type": "module",
6
6
  "bin": {
@@ -76,12 +76,8 @@ export function App({ config }) {
76
76
 
77
77
  return h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
78
78
  h(Layout, {
79
- state,
80
- streamContent,
81
- model,
82
- provider,
83
- showCommands,
84
- showModels,
79
+ state, streamContent, model, provider,
80
+ showCommands, showModels,
85
81
  onCommandSelect: handleCommandSelect,
86
82
  onModelSelect: handleModelSelect,
87
83
  onCloseCommands: () => setShowCommands(false),
@@ -21,26 +21,28 @@ export function CodeBlock({ code, language }) {
21
21
  const { cols } = getLayout();
22
22
  const maxLines = 20;
23
23
  const visible = lines.slice(0, maxLines);
24
+ const codeWidth = cols - 10;
24
25
 
25
26
  return h(Box, { flexDirection: 'column', backgroundColor: hex.codeBg },
26
- h(Box, { flexDirection: 'row', backgroundColor: hex.codeBg },
27
- h(Text, { color: langColor, bold: true, backgroundColor: hex.codeBg }, ' ' + lang + ' '),
28
- h(Text, { color: hex.textMuted, backgroundColor: hex.codeBg }, String(lines.length) + ' lines '),
27
+ h(Box, { height: 1, backgroundColor: hex.codeBg },
28
+ h(Text, { color: langColor, bold: true, backgroundColor: hex.codeBg }, ' ' + lang),
29
+ h(Text, { color: hex.textMuted, backgroundColor: hex.codeBg }, ' ' + String(lines.length) + ' lines '),
29
30
  ),
30
31
  h(Box, { flexDirection: 'column', backgroundColor: hex.codeBg },
31
32
  visible.map((line, i) =>
32
- h(Box, { key: i, flexDirection: 'row', backgroundColor: hex.codeBg },
33
+ h(Box, { key: i, height: 1, backgroundColor: hex.codeBg },
33
34
  h(Text, { color: hex.textMuted, backgroundColor: hex.codeBg },
34
- ' ' + String(i + 1).padStart(lnW) + ' '
35
+ ' ' + String(i + 1).padStart(lnW) + ' '
35
36
  ),
36
37
  h(Text, { color: '#C9D1D9', backgroundColor: hex.codeBg, wrap: 'truncate-end' },
37
- (line || ' ').slice(0, cols - 8)
38
+ (line || ' ').slice(0, codeWidth)
38
39
  )
39
40
  )
40
41
  ),
41
42
  lines.length > maxLines
42
- ? h(Text, { color: hex.textMuted, backgroundColor: hex.codeBg },
43
- ' ' + sym.ellipsis + ' ' + (lines.length - maxLines) + ' more lines')
43
+ ? h(Box, { height: 1, backgroundColor: hex.codeBg },
44
+ h(Text, { color: hex.textMuted, backgroundColor: hex.codeBg }, ' ' + sym.ellipsis + ' ' + (lines.length - maxLines) + ' more lines')
45
+ )
44
46
  : null
45
47
  )
46
48
  );
@@ -5,7 +5,7 @@ import { getLayout } from '../config/layout.js';
5
5
  const { createElement: h } = React;
6
6
 
7
7
  const COMMANDS = [
8
- { name: '/keys', desc: 'Set API key for a provider' },
8
+ { name: '/keys', desc: 'Set API key' },
9
9
  { name: '/model', desc: 'Switch model' },
10
10
  { name: '/provider', desc: 'Switch provider' },
11
11
  { name: '/agent', desc: 'Toggle agent mode' },
@@ -36,28 +36,51 @@ export function CommandPicker({ query, onSelect, onClose }) {
36
36
 
37
37
  const w = Math.min(cols - 4, 48);
38
38
 
39
- return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt, width: w },
39
+ const items = filtered.map((cmd, i) => {
40
+ const isSel = i === idx;
41
+ return h(Box, {
42
+ key: cmd.name, height: 1,
43
+ backgroundColor: isSel ? hex.selectionBg : hex.bg,
44
+ },
45
+ h(Text, {
46
+ color: isSel ? hex.selectionText : hex.text,
47
+ bold: isSel,
48
+ backgroundColor: isSel ? hex.selectionBg : hex.bg,
49
+ }, ' ' + (isSel ? '\u276F ' : ' ') + cmd.name),
50
+ h(Text, {
51
+ color: isSel ? hex.selectionText : hex.textDim,
52
+ backgroundColor: isSel ? hex.selectionBg : hex.bg,
53
+ }, ' ' + cmd.desc)
54
+ );
55
+ });
56
+
57
+ const maxH = Math.min(items.length + 3, 16);
58
+
59
+ return h(Box, { flexDirection: 'column', width: w, marginLeft: 2, backgroundColor: hex.bg },
60
+ h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
61
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
62
+ sym.box.tl + sym.box.h.repeat(w - 2) + sym.box.tr)
63
+ ),
40
64
  h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
41
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.star + ' ' + (search || 'filter commands...'))
65
+ h(Text, { color: hex.gold, backgroundColor: hex.surfaceAlt }, sym.box.v + ' '),
66
+ h(Text, { color: hex.text, backgroundColor: hex.surfaceAlt }, 'Commands'),
67
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
68
+ ' type to filter...' + ' '.repeat(Math.max(0, w - 20 - 4)) + sym.box.v)
42
69
  ),
43
- filtered.map((cmd, i) =>
44
- h(Box, {
45
- key: cmd.name, height: 1,
46
- backgroundColor: i === idx ? hex.selectionBg : 'transparent',
47
- },
48
- h(Text, {
49
- color: i === idx ? hex.selectionText : hex.text,
50
- bold: i === idx,
51
- backgroundColor: i === idx ? hex.selectionBg : 'transparent',
52
- }, ' ' + cmd.name + ' '),
53
- h(Text, {
54
- color: i === idx ? hex.selectionText : hex.textDim,
55
- backgroundColor: i === idx ? hex.selectionBg : 'transparent',
56
- }, cmd.desc)
57
- )
70
+ h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
71
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
72
+ sym.box.v + ' ' + (search || sym.ellipsis) + ' '.repeat(Math.max(0, w - 6)) + sym.box.v)
73
+ ),
74
+ h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
75
+ ...items,
58
76
  ),
59
77
  h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
60
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.arrowU + sym.arrowD + ' nav ' + sym.arrowR + ' select Esc close')
61
- )
78
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
79
+ sym.box.bl + sym.box.h.repeat(w - 2) + sym.box.br)
80
+ ),
81
+ h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
82
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
83
+ ' ' + sym.arrowU + sym.arrowD + ' nav ' + sym.arrowR + ' select Esc close')
84
+ ),
62
85
  );
63
86
  }
@@ -13,7 +13,7 @@ export function Composer({ provider, model, agentMode, thinking, onSlash, onSubm
13
13
  r.current = input;
14
14
 
15
15
  const { cols } = getLayout();
16
- const w = Math.max(10, cols - 6);
16
+ const w = Math.max(10, cols - 8);
17
17
  const lineCount = Math.max(1, Math.ceil((input.length || 1) / w));
18
18
  const visible = Math.min(lineCount, MAX_ROWS);
19
19
  const mShort = model.replace(/^[^/]+\//, '').slice(0, 18);
@@ -45,11 +45,11 @@ export function Composer({ provider, model, agentMode, thinking, onSlash, onSubm
45
45
  }
46
46
  });
47
47
 
48
- const rows = [
49
- h(Box, { key: 'sep', height: 1, backgroundColor: hex.surfaceAlt },
50
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.lightH.repeat(Math.max(0, cols - 4)))
51
- ),
52
- ];
48
+ const sep = h(Box, { key: 'sep', height: 1, backgroundColor: hex.surfaceAlt },
49
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.box.tl + sym.box.h.repeat(Math.max(0, cols - 6)) + sym.box.tr)
50
+ );
51
+
52
+ const rows = [sep];
53
53
 
54
54
  for (let i = 0; i < MAX_ROWS; i++) {
55
55
  const start = i * w;
@@ -60,7 +60,7 @@ export function Composer({ provider, model, agentMode, thinking, onSlash, onSubm
60
60
  color: isPlaceholder ? hex.textMuted : hex.text,
61
61
  backgroundColor: hex.bg,
62
62
  wrap: 'truncate-end',
63
- }, ' ' + sym.triR + ' ' + (seg || (i === 0 && isPlaceholder ? 'type a message...' : ' ')))
63
+ }, ' \u2502 ' + (seg || (i === 0 && isPlaceholder ? 'type a message...' : ' ')) + ' ')
64
64
  )
65
65
  );
66
66
  }
@@ -68,7 +68,7 @@ export function Composer({ provider, model, agentMode, thinking, onSlash, onSubm
68
68
  rows.push(
69
69
  h(Box, { key: 'st', height: 1, backgroundColor: hex.surfaceAlt },
70
70
  h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
71
- ' ' + provider + ' ' + sym.midDot + ' ' + mShort + (agentMode ? ' ' + sym.midDot + ' AGENT' : '') + ' ' + sym.midDot + ' Ctrl+P')
71
+ ' ' + sym.box.bl + sym.box.h + ' ' + provider + ' ' + sym.midDot + ' ' + mShort + (agentMode ? ' ' + sym.midDot + ' AGENT' : '') + ' ' + sym.midDot + ' Ctrl+P')
72
72
  )
73
73
  );
74
74
 
@@ -9,29 +9,21 @@ import { ModelPicker } from './ModelPicker.js';
9
9
  const { createElement: h } = React;
10
10
 
11
11
  export function Layout({ state, streamContent, model, provider, showCommands, showModels, onCommandSelect, onModelSelect, onCloseCommands, onCloseModels, onSlash, onSubmit }) {
12
- return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, minHeight: '100%' },
12
+ return h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
13
13
  h(StatusBar, { model, provider, agentMode: state.agentMode, thinking: state.thinking }),
14
14
  h(Box, { flexGrow: 1, flexDirection: 'column' },
15
15
  h(MessageList, {
16
- messages: state.messages,
17
- thinking: state.thinking,
18
- streamContent,
19
- agentStatus: state.agentStatus,
16
+ messages: state.messages, thinking: state.thinking,
17
+ streamContent, agentStatus: state.agentStatus,
20
18
  toolExecutions: state.toolExecutions,
21
19
  })
22
20
  ),
23
21
  showCommands || showModels
24
- ? h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
22
+ ? h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
25
23
  showCommands ? h(CommandPicker, { query: '', onSelect: onCommandSelect, onClose: onCloseCommands }) : null,
26
24
  showModels ? h(ModelPicker, { onSelect: onModelSelect, onClose: onCloseModels }) : null
27
25
  )
28
26
  : null,
29
- h(Composer, {
30
- provider, model,
31
- agentMode: state.agentMode,
32
- thinking: state.thinking,
33
- onSlash,
34
- onSubmit,
35
- })
27
+ h(Composer, { provider, model, agentMode: state.agentMode, thinking: state.thinking, onSlash, onSubmit })
36
28
  );
37
29
  }
@@ -4,8 +4,8 @@ import { hex, sym } from '../config/theme.js';
4
4
  const { createElement: h } = React;
5
5
 
6
6
  export function LoadingIndicator({ label }) {
7
- return h(Box, { flexDirection: 'row', backgroundColor: hex.surface },
8
- h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' ' + sym.dot),
7
+ return h(Box, { height: 1, backgroundColor: hex.surface },
8
+ h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' ' + sym.dot),
9
9
  h(Text, { color: hex.textDim, backgroundColor: hex.surface }, ' ' + (label || 'processing'))
10
10
  );
11
11
  }
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { Box, Text } from 'ink';
3
- import { hex, sym } from '../config/theme.js';
3
+ import { hex, sym, LOGO } from '../config/theme.js';
4
4
  import { getLayout, sliceToViewport, buildLineArray } from '../config/layout.js';
5
5
  const { createElement: h } = React;
6
6
 
@@ -8,11 +8,11 @@ function Line({ type, text, data }) {
8
8
  switch (type) {
9
9
  case 'user_head':
10
10
  return h(Box, { height: 1, backgroundColor: hex.userBg },
11
- h(Text, { color: hex.accent, bold: true, backgroundColor: hex.userBg }, ' ' + sym.bullet + ' YOU')
11
+ h(Text, { color: hex.accent, bold: true, backgroundColor: hex.userBg }, ' \u276F ' + sym.bullet + ' YOU')
12
12
  );
13
13
  case 'user_line':
14
14
  return h(Box, { height: 1, backgroundColor: hex.userBg },
15
- h(Text, { color: hex.text, backgroundColor: hex.userBg, wrap: 'wrap' }, ' ' + (text || ' '))
15
+ h(Text, { color: hex.text, backgroundColor: hex.userBg, wrap: 'wrap' }, ' ' + (text || ' '))
16
16
  );
17
17
  case 'asst_head':
18
18
  return h(Box, { height: 1, backgroundColor: hex.surface },
@@ -20,7 +20,7 @@ function Line({ type, text, data }) {
20
20
  );
21
21
  case 'asst_line':
22
22
  return h(Box, { height: 1, backgroundColor: hex.surface },
23
- h(Text, { color: hex.text, backgroundColor: hex.surface, wrap: 'wrap' }, ' ' + (text || ' '))
23
+ h(Text, { color: hex.text, backgroundColor: hex.surface, wrap: 'wrap' }, ' \u2502 ' + (text || ' '))
24
24
  );
25
25
  case 'asst_foot':
26
26
  return h(Box, { height: 1, backgroundColor: hex.surface },
@@ -28,15 +28,15 @@ function Line({ type, text, data }) {
28
28
  );
29
29
  case 'tool_line':
30
30
  return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
31
- h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' ' + sym.bullet + ' ' + (text || ''))
31
+ h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' ' + sym.bullet + ' ' + (text || ''))
32
32
  );
33
33
  case 'sys_line':
34
34
  return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
35
- h(Text, { color: hex.green, backgroundColor: hex.surfaceAlt }, ' ' + sym.bullet + ' ' + (text || ''))
35
+ h(Text, { color: hex.green, backgroundColor: hex.surfaceAlt }, ' ' + sym.bullet + ' ' + (text || ''))
36
36
  );
37
37
  case 'err_line':
38
38
  return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
39
- h(Text, { color: hex.red, backgroundColor: hex.surfaceAlt }, ' ' + sym.cross + ' ' + (text || ''))
39
+ h(Text, { color: hex.red, backgroundColor: hex.surfaceAlt }, ' ' + sym.cross + ' ' + (text || ''))
40
40
  );
41
41
  case 'stream_head':
42
42
  return h(Box, { height: 1, backgroundColor: hex.surface },
@@ -44,11 +44,11 @@ function Line({ type, text, data }) {
44
44
  );
45
45
  case 'stream_status':
46
46
  return h(Box, { height: 1, backgroundColor: hex.surface },
47
- h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' ' + sym.dot + ' ' + (text || ''))
47
+ h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' ' + sym.dot + ' ' + (text || ''))
48
48
  );
49
49
  case 'stream_line':
50
50
  return h(Box, { height: 1, backgroundColor: hex.surface },
51
- h(Text, { color: hex.text, backgroundColor: hex.surface, wrap: 'wrap' }, ' ' + (text || ' '))
51
+ h(Text, { color: hex.text, backgroundColor: hex.surface, wrap: 'wrap' }, ' \u2502 ' + (text || ' '))
52
52
  );
53
53
  default:
54
54
  return null;
@@ -59,24 +59,17 @@ export function MessageList({ messages, thinking, streamContent, agentStatus, to
59
59
  const { viewport, contentWidth } = getLayout();
60
60
 
61
61
  const entries = useMemo(() => {
62
- const e = messages.map(m => ({
62
+ return messages.map(m => ({
63
63
  id: m.id, role: m.role, content: m.content,
64
64
  duration: m.duration, toolName: m.toolName, error: m.error, completed: true,
65
65
  }));
66
- if (thinking || streamContent) {
67
- e.push({
68
- id: 'stream', role: 'streaming',
69
- content: streamContent || '',
70
- status: agentStatus || (thinking ? 'processing...' : ''),
71
- completed: false,
72
- });
73
- }
74
- return e;
75
- }, [messages, thinking, streamContent, agentStatus]);
66
+ }, [messages]);
67
+
68
+ const showLogo = entries.length <= 1 && !thinking && !streamContent;
76
69
 
77
70
  const { slice, clipIndex, clipLines } = useMemo(
78
- () => sliceToViewport(entries, viewport, contentWidth),
79
- [entries, viewport, contentWidth]
71
+ () => sliceToViewport(entries, Math.max(viewport - (showLogo ? 14 : 0), viewport), contentWidth),
72
+ [entries, viewport, contentWidth, showLogo]
80
73
  );
81
74
 
82
75
  const lines = useMemo(
@@ -85,17 +78,36 @@ export function MessageList({ messages, thinking, streamContent, agentStatus, to
85
78
  );
86
79
 
87
80
  const padded = [...lines];
88
- for (let i = padded.length; i < viewport; i++) {
89
- padded.unshift({ type: 'empty' });
81
+ if (showLogo) {
82
+ while (padded.length < Math.max(viewport - 14, 0)) {
83
+ padded.unshift({ type: 'empty' });
84
+ }
85
+ } else {
86
+ for (let i = padded.length; i < viewport; i++) {
87
+ padded.unshift({ type: 'empty' });
88
+ }
90
89
  }
91
90
 
92
- return h(Box, { height: viewport, flexDirection: 'column', overflow: 'hidden' },
93
- padded.map((ln, i) =>
94
- ln.type === 'empty'
95
- ? h(Box, { key: 'e' + i, height: 1, backgroundColor: hex.bg })
96
- : h(Box, { key: (ln.data?.id || 'l') + '-' + i, height: 1 },
97
- h(Line, { type: ln.type, text: ln.text, data: ln.data })
98
- )
99
- )
91
+ const logoVisible = showLogo;
92
+ const totalHeight = viewport;
93
+
94
+ return h(Box, { height: totalHeight, flexDirection: 'column', overflow: 'hidden' },
95
+ padded.map((ln, i) => {
96
+ if (ln.type === 'empty') {
97
+ if (logoVisible && i < 14) {
98
+ const logoLine = LOGO[i] || '';
99
+ return h(Box, { key: 'e' + i, height: 1, backgroundColor: hex.bg },
100
+ h(Text, {
101
+ color: (i === 1 || i === 9 || i === 10 || i === 11) ? hex.accent : hex.textMuted,
102
+ backgroundColor: hex.bg,
103
+ }, ' ' + logoLine)
104
+ );
105
+ }
106
+ return h(Box, { key: 'e' + i, height: 1, backgroundColor: hex.bg });
107
+ }
108
+ return h(Box, { key: (ln.data?.id || 'l') + '-' + i, height: 1 },
109
+ h(Line, { type: ln.type, text: ln.text, data: ln.data })
110
+ );
111
+ })
100
112
  );
101
113
  }
@@ -39,30 +39,53 @@ export function ModelPicker({ onSelect, onClose }) {
39
39
 
40
40
  const w = Math.min(cols - 4, 52);
41
41
 
42
- return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt, width: w },
42
+ const items = flat.map((m, i) => {
43
+ if (m._header) {
44
+ return h(Box, { key: 'h-' + m._provider, height: 1, backgroundColor: hex.surfaceAlt },
45
+ h(Text, { color: hex.blue, bold: true, backgroundColor: hex.surfaceAlt }, sym.box.v + ' ' + m._provider.toUpperCase()),
46
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' '.repeat(Math.max(0, w - m._provider.length - 8)) + sym.box.v)
47
+ );
48
+ }
49
+ const isSel = i === idx;
50
+ return h(Box, {
51
+ key: m.id, height: 1,
52
+ backgroundColor: isSel ? hex.selectionBg : hex.bg,
53
+ },
54
+ h(Text, {
55
+ color: isSel ? hex.selectionText : hex.text,
56
+ bold: isSel,
57
+ backgroundColor: isSel ? hex.selectionBg : hex.bg,
58
+ }, (isSel ? '\u276F ' : ' ') + m.label + (m.badge ? ' [' + m.badge + ']' : '') + ' '.repeat(Math.max(0, w - m.label.length - (m.badge ? m.badge.length + 4 : 0) - 4)) + (isSel ? '\u276F' : ' '))
59
+ );
60
+ });
61
+
62
+ const count = flat.filter(m => !m._header).length;
63
+
64
+ return h(Box, { flexDirection: 'column', width: w, marginLeft: 2, backgroundColor: hex.bg },
43
65
  h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
44
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.star + ' ' + (search || 'search models...'))
66
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
67
+ sym.box.tl + sym.box.h.repeat(w - 2) + sym.box.tr)
45
68
  ),
46
- flat.map((m, i) => {
47
- if (m._header) {
48
- return h(Box, { key: 'h-' + m._provider, height: 1, backgroundColor: hex.surfaceAlt },
49
- h(Text, { color: hex.blue, bold: true, backgroundColor: hex.surfaceAlt }, ' ' + sym.triD + ' ' + m._provider.toUpperCase())
50
- );
51
- }
52
- const isSel = i === idx;
53
- return h(Box, {
54
- key: m.id, height: 1,
55
- backgroundColor: isSel ? hex.selectionBg : 'transparent',
56
- },
57
- h(Text, {
58
- color: isSel ? hex.selectionText : hex.text,
59
- bold: isSel,
60
- backgroundColor: isSel ? hex.selectionBg : 'transparent',
61
- }, ' ' + m.label + (m.badge ? ' [' + m.badge + ']' : ''))
62
- );
63
- }),
64
69
  h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
65
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' ' + sym.arrowU + sym.arrowD + ' nav ' + sym.arrowR + ' select Esc close')
66
- )
70
+ h(Text, { color: hex.gold, backgroundColor: hex.surfaceAlt }, sym.box.v + ' '),
71
+ h(Text, { color: hex.text, backgroundColor: hex.surfaceAlt }, 'Models'),
72
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
73
+ ' ' + count + ' available' + ' '.repeat(Math.max(0, w - 14 - 8)) + sym.box.v)
74
+ ),
75
+ h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
76
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
77
+ sym.box.v + ' ' + (search || sym.ellipsis) + ' '.repeat(Math.max(0, w - 6)) + sym.box.v)
78
+ ),
79
+ h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
80
+ ...items,
81
+ ),
82
+ h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
83
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
84
+ sym.box.bl + sym.box.h.repeat(w - 2) + sym.box.br)
85
+ ),
86
+ h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
87
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
88
+ ' ' + sym.arrowU + sym.arrowD + ' nav ' + sym.arrowR + ' select Esc close')
89
+ ),
67
90
  );
68
91
  }
@@ -8,7 +8,7 @@ export function StatusBar({ model, provider, agentMode, thinking }) {
8
8
  const { cols } = getLayout();
9
9
  const m = model.replace(/^[^/]+\//, '').slice(0, 22);
10
10
  const left = sym.circle + ' CLARITY ' + sym.midDot + ' ' + m + ' ' + sym.midDot + ' ' + provider;
11
- const right = (agentMode ? 'AGENT' : 'USER') + (thinking ? ' ' + sym.dot : '');
11
+ const right = (agentMode ? '\u25C8 AGENT' : '\u25CB USER') + (thinking ? ' ' + sym.dot : '');
12
12
  const gap = Math.max(1, cols - left.length - right.length - 4);
13
13
  return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
14
14
  h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, ' ' + left + ' '.repeat(gap) + right + ' ')
@@ -12,40 +12,34 @@ export function ThinkingBlock({ toolResults, duration }) {
12
12
  ? (duration < 1000 ? duration + 'ms' : (duration / 1000).toFixed(1) + 's')
13
13
  : '';
14
14
 
15
- const headerText = sym.triR + ' ' + (collapsed ? sym.triR : sym.triD) + ' Thought' + (durStr ? ' (' + durStr + ')' : '');
16
-
17
- const rows = 1;
18
- const totalRows = collapsed ? 1 : 1 + items.length;
15
+ const headerText = sym.triR + ' Thought' + (durStr ? ' (' + durStr + ')' : '');
16
+ const icon = collapsed ? sym.triR : sym.triD;
19
17
 
20
18
  return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
21
19
  h(Box, { height: 1 },
22
- h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' ' + headerText)
20
+ h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' ' + icon + ' ' + headerText)
23
21
  ),
24
- collapsed
25
- ? null
26
- : h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
27
- items.map((tr, i) => {
28
- const isLast = i === items.length - 1;
29
- const prefix = isLast ? sym.treeTip + sym.u.h : sym.treeFork + sym.u.h;
30
- const conn = isLast ? ' ' : sym.treeCon;
31
- const icon = tr.status === 'failed' ? sym.cross : sym.circle;
32
- const col = tr.status === 'failed' ? hex.red : hex.green;
33
- const td = tr.duration ? ' ' + tr.duration + 'ms' : '';
34
- const line = ' ' + prefix + ' ' + icon + ' ' + tr.name + td;
35
- const contentLine = tr.content && tr.content.length < 200
36
- ? ' ' + conn + ' ' + String(tr.content).slice(0, cols - 10)
37
- : null;
38
- return h(Box, { key: tr.execId || i, flexDirection: 'column' },
39
- h(Box, { height: 1 },
40
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, line)
41
- ),
42
- contentLine
43
- ? h(Box, { height: 1 },
44
- h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, contentLine)
45
- )
46
- : null
47
- );
48
- })
49
- )
22
+ collapsed ? null : h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
23
+ items.map((tr, i) => {
24
+ const isLast = i === items.length - 1;
25
+ const prefix = isLast ? sym.treeTip + sym.u.h : sym.treeFork + sym.u.h;
26
+ const conn = isLast ? ' ' : sym.treeCon;
27
+ const ico = tr.status === 'failed' ? sym.cross : sym.circle;
28
+ const col = tr.status === 'failed' ? hex.red : hex.green;
29
+ const td = tr.duration ? ' ' + tr.duration + 'ms' : '';
30
+ const line = ' ' + prefix + ' ' + ico + ' ' + tr.name + td;
31
+ const contentLine = tr.content && tr.content.length < 200
32
+ ? ' ' + conn + ' ' + String(tr.content).slice(0, cols - 14)
33
+ : null;
34
+ return h(Box, { key: tr.execId || i, flexDirection: 'column' },
35
+ h(Box, { height: 1 },
36
+ h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, line)
37
+ ),
38
+ contentLine ? h(Box, { height: 1 },
39
+ h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, contentLine)
40
+ ) : null
41
+ );
42
+ })
43
+ )
50
44
  );
51
45
  }
@@ -16,21 +16,30 @@ export function ToolCard({ exec, isActive }) {
16
16
  const c = status === 'failed' ? hex.red : hex.green;
17
17
  const icon = status === 'failed' ? sym.cross : sym.bullet;
18
18
  return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
19
- h(Text, { color: c, backgroundColor: hex.surfaceAlt }, ' ' + icon + ' ' + name + (dur ? ' ' + dur : ''))
19
+ h(Text, { color: c, backgroundColor: hex.surfaceAlt }, ' ' + icon + ' ' + name + (dur ? ' ' + dur : ''))
20
20
  );
21
21
  }
22
22
 
23
- const w = Math.min(cols - 6, 56);
24
- const lines = [sym.bullet + ' ' + name + (dur ? ' ' + dur : '')];
25
- if (args.length < 80) lines.push(' ' + sym.triR + ' ' + args.slice(0, w));
26
- if (status === 'running') lines.push(' ' + sym.dot + ' running');
27
- if (status === 'failed' && exec.error) lines.push(' ' + sym.cross + ' ' + String(exec.error).slice(0, w - 4));
23
+ const w = Math.min(cols - 8, 56);
28
24
 
29
25
  return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
30
- lines.slice(0, 4).map((line, i) =>
31
- h(Box, { key: i, height: 1 },
32
- h(Text, { color: i === 0 ? hex.purple : hex.textDim, backgroundColor: hex.surfaceAlt }, ' ' + line)
33
- )
34
- )
26
+ h(Box, { height: 1 },
27
+ h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' ' + sym.bullet + ' ' + name + (dur ? ' ' + dur : ''))
28
+ ),
29
+ args.length < 80
30
+ ? h(Box, { height: 1 },
31
+ h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, ' ' + sym.triR + ' ' + args.slice(0, w))
32
+ )
33
+ : null,
34
+ status === 'running'
35
+ ? h(Box, { height: 1 },
36
+ h(Text, { color: hex.blue, backgroundColor: hex.surfaceAlt }, ' ' + sym.dot + ' running')
37
+ )
38
+ : null,
39
+ status === 'failed' && exec.error
40
+ ? h(Box, { height: 1 },
41
+ h(Text, { color: hex.red, backgroundColor: hex.surfaceAlt }, ' ' + sym.cross + ' ' + String(exec.error).slice(0, w - 4))
42
+ )
43
+ : null,
35
44
  );
36
45
  }
@@ -10,7 +10,8 @@ export function getLayout() {
10
10
  dock: 4,
11
11
  viewport: Math.max(8, rows - 5),
12
12
  contentWidth: Math.max(20, cols - 4),
13
- padLeft: ' '.repeat(2),
13
+ padLeft: ' ',
14
+ padRight: ' ',
14
15
  };
15
16
  }
16
17
 
@@ -78,7 +79,6 @@ export function buildLineArray(slice, clipIndex, clipLines, w) {
78
79
  }
79
80
  } else if (nr === 'assistant') {
80
81
  lines.push({ type: 'asst_head', data: e });
81
- lines.push({ type: 'asst_bar', data: e });
82
82
  if (skip <= 0) {
83
83
  const wrapped = wrapText(e.content, w);
84
84
  const contentLines = wrapped.split('\n');
@@ -98,14 +98,7 @@ export function buildLineArray(slice, clipIndex, clipLines, w) {
98
98
  if (e.completed) {
99
99
  if (skip <= 0) lines.push({ type: 'tool_line', text: label + ' ' + (e.duration ? e.duration + 'ms' : ''), data: e });
100
100
  } else {
101
- lines.push({ type: 'tool_head', text: label, data: e });
102
- if (e.content) {
103
- const wrapped = wrapText(String(e.content).slice(0, w * 3), w);
104
- const cls = wrapped.split('\n');
105
- for (let ci = Math.max(0, skip - 1); ci < Math.min(cls.length, 4); ci++) {
106
- lines.push({ type: 'tool_line', text: cls[ci], data: e });
107
- }
108
- }
101
+ lines.push({ type: 'tool_line', text: label, data: e });
109
102
  }
110
103
  } else if (nr === 'system') {
111
104
  if (skip <= 0) lines.push({ type: 'sys_line', text: e.content, data: e });
@@ -113,7 +106,6 @@ export function buildLineArray(slice, clipIndex, clipLines, w) {
113
106
  if (skip <= 0) lines.push({ type: 'err_line', text: e.content, data: e });
114
107
  } else if (nr === 'streaming') {
115
108
  lines.push({ type: 'stream_head', data: e });
116
- lines.push({ type: 'stream_bar', data: e });
117
109
  if (e.status) lines.push({ type: 'stream_status', text: e.status, data: e });
118
110
  const wrapped = wrapText(e.content || '', w);
119
111
  const contentLines = wrapped.split('\n');
@@ -1,6 +1,6 @@
1
1
  export const ALL_MODELS = [
2
- { id: 'huggingface/Universal-618/Clarity-flash-weights', provider: 'huggingface', label: 'Clarity Flash (Qwen 2.5 1.5B)', badge: 'Free' },
3
- { id: 'huggingface/Universal-618/Clarity-heavy-weights', provider: 'huggingface', label: 'Clarity Heavy (Qwen 2.5 1.5B)', badge: 'Free' },
2
+ { id: 'huggingface/Universal-618/Clarity-flash-weights', provider: 'huggingface', label: 'DeepSeek R1 1.5B', badge: 'Reasoning' },
3
+ { id: 'huggingface/Universal-618/Clarity-heavy-weights', provider: 'huggingface', label: 'DeepSeek R1 7B', badge: 'Reasoning' },
4
4
  { id: 'groq/llama-3.3-70b-versatile', provider: 'groq', label: 'Llama 3.3 70B Versatile', badge: null },
5
5
  { id: 'groq/llama-3.1-8b-instant', provider: 'groq', label: 'Llama 3.1 8B Instant', badge: 'Fast' },
6
6
  { id: 'groq/llama-4-scout-17b-16e-instruct', provider: 'groq', label: 'Llama 4 Scout 17B', badge: null },
@@ -22,6 +22,8 @@ export const hex = {
22
22
  white: '#FFFFFF',
23
23
  black: '#000000',
24
24
  modalOverlay: 'rgba(0,0,0,0.85)',
25
+ logoOrange: '#FF6B35',
26
+ logoBlue: '#3B82F6',
25
27
  };
26
28
 
27
29
  export const color = {
@@ -64,6 +66,16 @@ export const sym = {
64
66
  arrowD: '\u2193',
65
67
  lightV: '\u2502',
66
68
  lightH: '\u2500',
69
+ box: {
70
+ tl: '\u250C', tr: '\u2510', bl: '\u2514', br: '\u2518',
71
+ h: '\u2500', v: '\u2502',
72
+ tm: '\u252C', bm: '\u2534', lm: '\u251C', rm: '\u2524',
73
+ cross: '\u253C',
74
+ },
75
+ powerline: {
76
+ padlock: '\uE0B0',
77
+ rpadlock: '\uE0B2',
78
+ },
67
79
  treeJ: '\u2514',
68
80
  treeT: '\u251C',
69
81
  treeCon: '\u2502',
@@ -74,4 +86,22 @@ export const sym = {
74
86
  treeFork: '\u256D',
75
87
  star: '\u2726',
76
88
  asterisk: '\u2731',
89
+ gear: '\u2699',
90
+ thought: '\u25CB',
77
91
  };
92
+
93
+ export const LOGO = [
94
+ ' ',
95
+ ' ██████ ██ █████ ██████ ██ ████████ ██ ██ ',
96
+ ' ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ',
97
+ ' ██████ ██ ███████ ██████ ██ ██ ██ ██ ',
98
+ ' ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ',
99
+ ' ██ ███████ ██ ██ ██ ██ ██ ██ ██████ ',
100
+ ' ',
101
+ ' █████ ██ ',
102
+ ' ██ ██ ██ ',
103
+ ' ██████ ██ ',
104
+ ' ██ ██ ██ ',
105
+ ' ██ ██ ███████ ',
106
+ ' ',
107
+ ];
@@ -1,4 +1,5 @@
1
- const READ_TIMEOUT = 8000;
1
+ const CONNECT_TIMEOUT = 120000;
2
+ const READ_TIMEOUT = 15000;
2
3
 
3
4
  function readWithTimeout(reader, timeoutMs, signal) {
4
5
  return Promise.race([
@@ -19,7 +20,7 @@ export async function* streamResponse(endpoint, body, apiKey, extraHeaders = {},
19
20
  }
20
21
  signal?.addEventListener('abort', onAbort, { once: true });
21
22
 
22
- const fetchTimeout = setTimeout(() => controller.abort(), READ_TIMEOUT);
23
+ const fetchTimeout = setTimeout(() => controller.abort(), CONNECT_TIMEOUT);
23
24
 
24
25
  try {
25
26
  const res = await fetch(endpoint, {