clarity-ai 6.5.6 → 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,18 @@
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
+
5
17
  ## 6.5.6 (2026-06-06)
6
18
 
7
19
  ### Fix first-request timeout — model download needs >8s
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "6.5.6",
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
+ ];