clarity-ai 6.6.0 → 6.7.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 +13 -0
- package/bin/clarity.js +15 -4
- package/package.json +6 -2
- package/src/components/AppRoot.js +1 -3
- package/src/components/CommandPicker.js +16 -27
- package/src/components/Composer.js +11 -13
- package/src/components/Layout.js +14 -9
- package/src/components/MessageList.js +26 -32
- package/src/components/ModelPicker.js +9 -13
- package/src/components/StatusBar.js +9 -6
- package/src/components/ThinkingBlock.js +6 -17
- package/src/components/ToolCard.js +0 -5
- package/src/config/layout.js +14 -20
- package/src/config/theme.js +7 -21
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## 6.7.0 (2026-06-06)
|
|
6
|
+
|
|
7
|
+
### Premium Ink+React TUI Engine — zero-bleed grid
|
|
8
|
+
- **Hard-locked 3-tier grid**: Header(1) + Viewport(fill) + Dock(4) — absolute layout isolation
|
|
9
|
+
- **Floating picker overlays** via `position: absolute` — CommandPicker/ModelPicker never shift the message stream
|
|
10
|
+
- **Box-drawing borders** (`┌─┐│└─┘`) on all overlay panels
|
|
11
|
+
- **Orange `#FF6B35` selection bars** — full-width active item highlight
|
|
12
|
+
- **Gradient CLARITY header** via gradient-string (orange→blue)
|
|
13
|
+
- **Alternate screen buffer** (`fullscreen: true`) + SIGINT/SIGTERM cleanup
|
|
14
|
+
- **`string-width` render protection** — correct visual width for Unicode/emoji
|
|
15
|
+
- **Permanent ASCII logo** centered in empty viewport
|
|
16
|
+
- **ansi-escapes** for cursor hide/show, screen clear on boot
|
|
17
|
+
|
|
5
18
|
## 6.6.0 (2026-06-06)
|
|
6
19
|
|
|
7
20
|
### Premium UI rebuild + DeepSeek R1 reasoning models
|
package/bin/clarity.js
CHANGED
|
@@ -4,6 +4,7 @@ import { render } from 'ink';
|
|
|
4
4
|
import { App } from '../src/components/AppRoot.js';
|
|
5
5
|
import { hasKey } from '../src/config/keys.js';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
7
|
+
import ansiEscapes from 'ansi-escapes';
|
|
7
8
|
|
|
8
9
|
process.stdin.resume();
|
|
9
10
|
process.stdin.setEncoding('utf8');
|
|
@@ -24,29 +25,39 @@ async function main() {
|
|
|
24
25
|
process.stdin.resume();
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
process.stdout.write(ansiEscapes.clearScreen);
|
|
29
|
+
process.stdout.write(ansiEscapes.cursorHide);
|
|
30
|
+
|
|
27
31
|
const config = { provider, model: process.env.CLARITY_MODEL || 'groq/llama-3.3-70b-versatile' };
|
|
28
32
|
|
|
29
|
-
const { clear } = render(React.createElement(App, { config }), {
|
|
33
|
+
const { clear, waitUntilExit } = render(React.createElement(App, { config }), {
|
|
30
34
|
fullscreen: true,
|
|
31
35
|
patchConsole: false,
|
|
36
|
+
exitOnCtrlC: false,
|
|
32
37
|
});
|
|
33
38
|
|
|
34
|
-
setInterval(() => {}, 2 ** 31 - 1);
|
|
39
|
+
const keepAlive = setInterval(() => {}, 2 ** 31 - 1);
|
|
35
40
|
|
|
36
41
|
function cleanup() {
|
|
42
|
+
clearInterval(keepAlive);
|
|
43
|
+
process.stdout.write(ansiEscapes.cursorShow);
|
|
44
|
+
process.stdout.write('\x1b[0m');
|
|
37
45
|
try { clear(); } catch {}
|
|
38
|
-
process.stdout.write('\x1b[?25h\x1b[0m');
|
|
39
46
|
process.exit(0);
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
process.on('SIGINT', () => cleanup());
|
|
43
50
|
process.on('SIGTERM', () => cleanup());
|
|
51
|
+
process.on('exit', () => {
|
|
52
|
+
process.stdout.write(ansiEscapes.cursorShow);
|
|
53
|
+
});
|
|
44
54
|
|
|
45
55
|
await new Promise(() => {});
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
main().catch(err => {
|
|
49
|
-
process.stdout.write(
|
|
59
|
+
process.stdout.write(ansiEscapes.cursorShow);
|
|
60
|
+
process.stdout.write('\x1b[0m');
|
|
50
61
|
console.error('\n\x1b[31mFatal error:\x1b[0m', err.message);
|
|
51
62
|
process.exit(1);
|
|
52
63
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarity-ai",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.7.0",
|
|
4
4
|
"description": "CLARITY — terminal AI agent with local GGUF inference on HF Spaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"react": "^18",
|
|
24
24
|
"ink-spinner": "^5",
|
|
25
25
|
"chalk": "^5",
|
|
26
|
-
"wrap-ansi": "^9"
|
|
26
|
+
"wrap-ansi": "^9",
|
|
27
|
+
"gradient-string": "^3",
|
|
28
|
+
"figures": "^6",
|
|
29
|
+
"ansi-escapes": "^7",
|
|
30
|
+
"string-width": "^7"
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -34,11 +34,9 @@ export function App({ config }) {
|
|
|
34
34
|
const stateRef = useRef(state);
|
|
35
35
|
const modelRef = useRef(model);
|
|
36
36
|
const providerRef = useRef(provider);
|
|
37
|
-
const streamRef = useRef(streamContent);
|
|
38
37
|
stateRef.current = state;
|
|
39
38
|
modelRef.current = model;
|
|
40
39
|
providerRef.current = provider;
|
|
41
|
-
streamRef.current = streamContent;
|
|
42
40
|
|
|
43
41
|
const onSubmit = useCallback(async (input) => {
|
|
44
42
|
if (input === '/exit') { process.exit(0); return; }
|
|
@@ -74,7 +72,7 @@ export function App({ config }) {
|
|
|
74
72
|
}));
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
return h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
|
|
75
|
+
return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, height: '100%' },
|
|
78
76
|
h(Layout, {
|
|
79
77
|
state, streamContent, model, provider,
|
|
80
78
|
showCommands, showModels,
|
|
@@ -29,58 +29,47 @@ export function CommandPicker({ query, onSelect, onClose }) {
|
|
|
29
29
|
if (key.upArrow) setIdx(i => Math.max(0, i - 1));
|
|
30
30
|
if (key.downArrow) setIdx(i => Math.min(filtered.length - 1, i + 1));
|
|
31
31
|
if (key.return && filtered[idx]) onSelect(filtered[idx].name);
|
|
32
|
-
if (key.escape) onClose();
|
|
32
|
+
if (key.escape || key.ctrl && key.c) onClose();
|
|
33
33
|
if (key.backspace) setSearch(s => s.slice(0, -1));
|
|
34
34
|
else if (input && !key.ctrl && !key.meta) setSearch(s => s + input);
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
const w = Math.min(cols - 4, 48);
|
|
38
38
|
|
|
39
|
-
const items = filtered.map((cmd, i) =>
|
|
40
|
-
|
|
41
|
-
return h(Box, {
|
|
39
|
+
const items = filtered.map((cmd, i) =>
|
|
40
|
+
h(Box, {
|
|
42
41
|
key: cmd.name, height: 1,
|
|
43
|
-
backgroundColor:
|
|
42
|
+
backgroundColor: i === idx ? hex.selectionBg : hex.bg,
|
|
44
43
|
},
|
|
45
44
|
h(Text, {
|
|
46
|
-
color:
|
|
47
|
-
bold:
|
|
48
|
-
backgroundColor:
|
|
49
|
-
}, ' ' + (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
backgroundColor: isSel ? hex.selectionBg : hex.bg,
|
|
53
|
-
}, ' ' + cmd.desc)
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const maxH = Math.min(items.length + 3, 16);
|
|
45
|
+
color: i === idx ? hex.selectionText : hex.text,
|
|
46
|
+
bold: i === idx,
|
|
47
|
+
backgroundColor: i === idx ? hex.selectionBg : hex.bg,
|
|
48
|
+
}, ' ' + (i === idx ? '\u276F ' : ' ') + cmd.name + ' ' + cmd.desc)
|
|
49
|
+
)
|
|
50
|
+
);
|
|
58
51
|
|
|
59
|
-
return h(Box, { flexDirection: 'column',
|
|
52
|
+
return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, width: w },
|
|
60
53
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
61
54
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
62
55
|
sym.box.tl + sym.box.h.repeat(w - 2) + sym.box.tr)
|
|
63
56
|
),
|
|
64
57
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
65
|
-
h(Text, { color: hex.gold, backgroundColor: hex.surfaceAlt }, sym.box.v + ' '),
|
|
66
|
-
h(Text, { color: hex.
|
|
67
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
68
|
-
' type to filter...' + ' '.repeat(Math.max(0, w - 20 - 4)) + sym.box.v)
|
|
58
|
+
h(Text, { color: hex.gold, backgroundColor: hex.surfaceAlt }, sym.box.v + ' Commands'),
|
|
59
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' '.repeat(Math.max(0, w - 14)) + sym.box.v)
|
|
69
60
|
),
|
|
70
61
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
71
62
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
72
|
-
sym.box.v + ' ' + (search ||
|
|
73
|
-
),
|
|
74
|
-
h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
|
|
75
|
-
...items,
|
|
63
|
+
sym.box.v + ' ' + (search || '\u2026') + ' '.repeat(Math.max(0, w - 6)) + sym.box.v)
|
|
76
64
|
),
|
|
65
|
+
h(Box, { flexDirection: 'column', backgroundColor: hex.bg }, ...items),
|
|
77
66
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
78
67
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
79
68
|
sym.box.bl + sym.box.h.repeat(w - 2) + sym.box.br)
|
|
80
69
|
),
|
|
81
70
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
82
71
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
83
|
-
'
|
|
72
|
+
' \u2191\u2193 nav \u2192 select Esc close')
|
|
84
73
|
),
|
|
85
74
|
);
|
|
86
75
|
}
|
|
@@ -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 -
|
|
16
|
+
const w = Math.max(10, cols - 6);
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
const rows = [];
|
|
49
|
+
rows.push(h(Box, { key: 'sep', height: 1, backgroundColor: hex.surfaceAlt },
|
|
50
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
51
|
+
' \u250C' + sym.box.h.repeat(Math.max(0, cols - 6)) + '\u2510')
|
|
52
|
+
));
|
|
53
53
|
|
|
54
54
|
for (let i = 0; i < MAX_ROWS; i++) {
|
|
55
55
|
const start = i * w;
|
|
@@ -60,17 +60,15 @@ 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
|
-
}, ' \u2502 ' + (seg || (i === 0 && isPlaceholder ? 'type a message...' : ' '))
|
|
63
|
+
}, ' \u2502 ' + (seg || (i === 0 && isPlaceholder ? 'type a message...' : ' ')))
|
|
64
64
|
)
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
rows.push(
|
|
69
|
-
h(
|
|
70
|
-
h(
|
|
71
|
-
|
|
72
|
-
)
|
|
73
|
-
);
|
|
68
|
+
rows.push(h(Box, { key: 'st', height: 1, backgroundColor: hex.surfaceAlt },
|
|
69
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
70
|
+
' \u2514' + sym.box.h + ' ' + provider + ' \u00B7 ' + mShort + (agentMode ? ' \u00B7 AGENT' : '') + ' \u00B7 Ctrl+P')
|
|
71
|
+
));
|
|
74
72
|
|
|
75
73
|
return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt }, ...rows);
|
|
76
74
|
}
|
package/src/components/Layout.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box } from 'ink';
|
|
3
3
|
import { hex } from '../config/theme.js';
|
|
4
|
+
import { getLayout } from '../config/layout.js';
|
|
4
5
|
import { StatusBar } from './StatusBar.js';
|
|
5
6
|
import { MessageList } from './MessageList.js';
|
|
6
7
|
import { Composer } from './Composer.js';
|
|
@@ -9,21 +10,25 @@ import { ModelPicker } from './ModelPicker.js';
|
|
|
9
10
|
const { createElement: h } = React;
|
|
10
11
|
|
|
11
12
|
export function Layout({ state, streamContent, model, provider, showCommands, showModels, onCommandSelect, onModelSelect, onCloseCommands, onCloseModels, onSlash, onSubmit }) {
|
|
12
|
-
|
|
13
|
+
const { headerHeight, dockHeight, cols } = getLayout();
|
|
14
|
+
|
|
15
|
+
const picker = showCommands || showModels
|
|
16
|
+
? h(Box, { position: 'absolute', top: headerHeight + 1, left: 2, width: Math.min(cols - 4, 52), backgroundColor: hex.bg, flexDirection: 'column' },
|
|
17
|
+
showCommands ? h(CommandPicker, { query: '', onSelect: onCommandSelect, onClose: onCloseCommands }) : null,
|
|
18
|
+
showModels ? h(ModelPicker, { onSelect: onModelSelect, onClose: onCloseModels }) : null
|
|
19
|
+
)
|
|
20
|
+
: null;
|
|
21
|
+
|
|
22
|
+
return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, height: '100%' },
|
|
13
23
|
h(StatusBar, { model, provider, agentMode: state.agentMode, thinking: state.thinking }),
|
|
14
|
-
h(Box, { flexGrow: 1, flexDirection: 'column' },
|
|
24
|
+
h(Box, { flexGrow: 1, flexDirection: 'column', position: 'relative' },
|
|
15
25
|
h(MessageList, {
|
|
16
26
|
messages: state.messages, thinking: state.thinking,
|
|
17
27
|
streamContent, agentStatus: state.agentStatus,
|
|
18
28
|
toolExecutions: state.toolExecutions,
|
|
19
|
-
})
|
|
29
|
+
}),
|
|
30
|
+
picker
|
|
20
31
|
),
|
|
21
|
-
showCommands || showModels
|
|
22
|
-
? h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
|
|
23
|
-
showCommands ? h(CommandPicker, { query: '', onSelect: onCommandSelect, onClose: onCloseCommands }) : null,
|
|
24
|
-
showModels ? h(ModelPicker, { onSelect: onModelSelect, onClose: onCloseModels }) : null
|
|
25
|
-
)
|
|
26
|
-
: null,
|
|
27
32
|
h(Composer, { provider, model, agentMode: state.agentMode, thinking: state.thinking, onSlash, onSubmit })
|
|
28
33
|
);
|
|
29
34
|
}
|
|
@@ -8,7 +8,7 @@ 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 }, ' \u276F
|
|
11
|
+
h(Text, { color: hex.accent, bold: true, backgroundColor: hex.userBg }, ' \u276F \u25C9 YOU')
|
|
12
12
|
);
|
|
13
13
|
case 'user_line':
|
|
14
14
|
return h(Box, { height: 1, backgroundColor: hex.userBg },
|
|
@@ -16,7 +16,7 @@ function Line({ type, text, data }) {
|
|
|
16
16
|
);
|
|
17
17
|
case 'asst_head':
|
|
18
18
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
19
|
-
h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, '
|
|
19
|
+
h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, ' \u25C6 CLARITY')
|
|
20
20
|
);
|
|
21
21
|
case 'asst_line':
|
|
22
22
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
@@ -24,7 +24,7 @@ function Line({ type, text, data }) {
|
|
|
24
24
|
);
|
|
25
25
|
case 'asst_foot':
|
|
26
26
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
27
|
-
h(Text, { color: hex.textDim, backgroundColor: hex.surface }, '
|
|
27
|
+
h(Text, { color: hex.textDim, backgroundColor: hex.surface }, ' \u25B8 ' + (parseInt(text) < 1000 ? text + 'ms' : (parseInt(text) / 1000).toFixed(1) + 's'))
|
|
28
28
|
);
|
|
29
29
|
case 'tool_line':
|
|
30
30
|
return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
@@ -40,11 +40,11 @@ function Line({ type, text, data }) {
|
|
|
40
40
|
);
|
|
41
41
|
case 'stream_head':
|
|
42
42
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
43
|
-
h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, '
|
|
43
|
+
h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, ' \u25C6 CLARITY')
|
|
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 }, '
|
|
47
|
+
h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' \u25CF ' + (text || ''))
|
|
48
48
|
);
|
|
49
49
|
case 'stream_line':
|
|
50
50
|
return h(Box, { height: 1, backgroundColor: hex.surface },
|
|
@@ -66,46 +66,40 @@ export function MessageList({ messages, thinking, streamContent, agentStatus, to
|
|
|
66
66
|
}, [messages]);
|
|
67
67
|
|
|
68
68
|
const showLogo = entries.length <= 1 && !thinking && !streamContent;
|
|
69
|
+
const effectiveViewport = showLogo ? Math.max(viewport - 14, 4) : viewport;
|
|
69
70
|
|
|
70
71
|
const { slice, clipIndex, clipLines } = useMemo(
|
|
71
|
-
() => sliceToViewport(entries,
|
|
72
|
-
[entries,
|
|
72
|
+
() => sliceToViewport(entries, effectiveViewport, contentWidth),
|
|
73
|
+
[entries, effectiveViewport, contentWidth]
|
|
73
74
|
);
|
|
74
75
|
|
|
75
|
-
const
|
|
76
|
+
const rawLines = useMemo(
|
|
76
77
|
() => buildLineArray(slice, clipIndex, clipLines, contentWidth),
|
|
77
78
|
[slice, clipIndex, clipLines, contentWidth]
|
|
78
79
|
);
|
|
79
80
|
|
|
80
|
-
const padded = [
|
|
81
|
+
const padded = [];
|
|
82
|
+
const logoRows = showLogo ? 14 : 0;
|
|
83
|
+
const fillRows = Math.max(0, effectiveViewport - rawLines.length - logoRows);
|
|
84
|
+
for (let i = 0; i < fillRows; i++) padded.push({ type: 'empty' });
|
|
81
85
|
if (showLogo) {
|
|
82
|
-
|
|
83
|
-
padded.unshift({ type: 'empty' });
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
for (let i = padded.length; i < viewport; i++) {
|
|
87
|
-
padded.unshift({ type: 'empty' });
|
|
88
|
-
}
|
|
86
|
+
for (let i = 0; i < 14; i++) padded.push({ type: 'logo', line: i });
|
|
89
87
|
}
|
|
88
|
+
for (const ln of rawLines) padded.push(ln);
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
const totalHeight = viewport;
|
|
93
|
-
|
|
94
|
-
return h(Box, { height: totalHeight, flexDirection: 'column', overflow: 'hidden' },
|
|
90
|
+
return h(Box, { height: viewport, flexDirection: 'column', overflow: 'hidden' },
|
|
95
91
|
padded.map((ln, i) => {
|
|
96
|
-
if (ln.type === 'empty') {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
return h(Box, { key: 'e' + i, height: 1, backgroundColor: hex.bg });
|
|
92
|
+
if (ln.type === 'empty') return h(Box, { key: 'e' + i, height: 1, backgroundColor: hex.bg });
|
|
93
|
+
if (ln.type === 'logo') {
|
|
94
|
+
const logoLine = LOGO[ln.line] || '';
|
|
95
|
+
return h(Box, { key: 'l' + i, height: 1, backgroundColor: hex.bg },
|
|
96
|
+
h(Text, {
|
|
97
|
+
color: (ln.line >= 1 && ln.line <= 5) || ln.line === 9 ? hex.accent : hex.textMuted,
|
|
98
|
+
backgroundColor: hex.bg,
|
|
99
|
+
}, ' ' + logoLine)
|
|
100
|
+
);
|
|
107
101
|
}
|
|
108
|
-
return h(Box, { key: (ln.data?.id || '
|
|
102
|
+
return h(Box, { key: (ln.data?.id || 'r') + '-' + i, height: 1 },
|
|
109
103
|
h(Line, { type: ln.type, text: ln.text, data: ln.data })
|
|
110
104
|
);
|
|
111
105
|
})
|
|
@@ -32,7 +32,7 @@ export function ModelPicker({ onSelect, onClose }) {
|
|
|
32
32
|
if (key.upArrow) setIdx(i => Math.max(0, i - 1));
|
|
33
33
|
if (key.downArrow) setIdx(i => Math.min(flat.length - 1, i + 1));
|
|
34
34
|
if (key.return) { const m = flat[idx]; if (m && !m._header) onSelect(m.id); return; }
|
|
35
|
-
if (key.escape) onClose();
|
|
35
|
+
if (key.escape || key.ctrl && key.c) onClose();
|
|
36
36
|
if (key.backspace) setSearch(s => s.slice(0, -1));
|
|
37
37
|
else if (input && !key.ctrl && !key.meta) setSearch(s => s + input);
|
|
38
38
|
});
|
|
@@ -43,7 +43,7 @@ export function ModelPicker({ onSelect, onClose }) {
|
|
|
43
43
|
if (m._header) {
|
|
44
44
|
return h(Box, { key: 'h-' + m._provider, height: 1, backgroundColor: hex.surfaceAlt },
|
|
45
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 -
|
|
46
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' '.repeat(Math.max(0, w - m._provider.length - 6)) + sym.box.v)
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
const isSel = i === idx;
|
|
@@ -55,37 +55,33 @@ export function ModelPicker({ onSelect, onClose }) {
|
|
|
55
55
|
color: isSel ? hex.selectionText : hex.text,
|
|
56
56
|
bold: isSel,
|
|
57
57
|
backgroundColor: isSel ? hex.selectionBg : hex.bg,
|
|
58
|
-
}, (isSel ? '\u276F ' : ' ') + m.label + (m.badge ? ' [' + m.badge + ']' : '')
|
|
58
|
+
}, (isSel ? '\u276F ' : ' ') + m.label + (m.badge ? ' [' + m.badge + ']' : ''))
|
|
59
59
|
);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
const count = flat.filter(m => !m._header).length;
|
|
63
63
|
|
|
64
|
-
return h(Box, { flexDirection: 'column',
|
|
64
|
+
return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, width: w },
|
|
65
65
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
66
66
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
67
67
|
sym.box.tl + sym.box.h.repeat(w - 2) + sym.box.tr)
|
|
68
68
|
),
|
|
69
69
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
70
|
-
h(Text, { color: hex.gold, backgroundColor: hex.surfaceAlt }, sym.box.v + ' '),
|
|
71
|
-
h(Text, { color: hex.
|
|
72
|
-
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
73
|
-
' ' + count + ' available' + ' '.repeat(Math.max(0, w - 14 - 8)) + sym.box.v)
|
|
70
|
+
h(Text, { color: hex.gold, backgroundColor: hex.surfaceAlt }, sym.box.v + ' Models ' + count + ' available'),
|
|
71
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt }, ' '.repeat(Math.max(0, w - 19)) + sym.box.v)
|
|
74
72
|
),
|
|
75
73
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
76
74
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
77
|
-
sym.box.v + ' ' + (search ||
|
|
78
|
-
),
|
|
79
|
-
h(Box, { flexDirection: 'column', backgroundColor: hex.bg },
|
|
80
|
-
...items,
|
|
75
|
+
sym.box.v + ' ' + (search || '\u2026') + ' '.repeat(Math.max(0, w - 6)) + sym.box.v)
|
|
81
76
|
),
|
|
77
|
+
h(Box, { flexDirection: 'column', backgroundColor: hex.bg }, ...items),
|
|
82
78
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
83
79
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
84
80
|
sym.box.bl + sym.box.h.repeat(w - 2) + sym.box.br)
|
|
85
81
|
),
|
|
86
82
|
h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
87
83
|
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
88
|
-
'
|
|
84
|
+
' \u2191\u2193 nav \u2192 select Esc close')
|
|
89
85
|
),
|
|
90
86
|
);
|
|
91
87
|
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import { hex
|
|
3
|
+
import { hex } from '../config/theme.js';
|
|
4
4
|
import { getLayout } from '../config/layout.js';
|
|
5
5
|
const { createElement: h } = React;
|
|
6
6
|
|
|
7
7
|
export function StatusBar({ model, provider, agentMode, thinking }) {
|
|
8
8
|
const { cols } = getLayout();
|
|
9
|
-
const m = model.replace(/^[^/]+\//, '').slice(0,
|
|
10
|
-
const left =
|
|
11
|
-
const right = (agentMode ? '\u25C8 AGENT' : '\u25CB USER') + (thinking ? ' '
|
|
12
|
-
const gap = Math.max(1, cols - left.length - right.length -
|
|
9
|
+
const m = model.replace(/^[^/]+\//, '').slice(0, 20);
|
|
10
|
+
const left = '\u25C9 CLARITY \u00B7 ' + m + ' \u00B7 ' + provider;
|
|
11
|
+
const right = (agentMode ? '\u25C8 AGENT' : '\u25CB USER') + (thinking ? ' \u25CF' : '');
|
|
12
|
+
const gap = Math.max(1, cols - left.length - right.length - 2);
|
|
13
|
+
|
|
13
14
|
return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
|
|
14
|
-
h(Text, { color: hex.
|
|
15
|
+
h(Text, { color: hex.accent, bold: true, backgroundColor: hex.surfaceAlt }, ' ' + left),
|
|
16
|
+
h(Text, { backgroundColor: hex.surfaceAlt }, ' '.repeat(gap)),
|
|
17
|
+
h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, right + ' ')
|
|
15
18
|
);
|
|
16
19
|
}
|
|
@@ -12,32 +12,21 @@ 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 + ' Thought' + (durStr ? ' (' + durStr + ')' : '');
|
|
16
|
-
const icon = collapsed ? sym.triR : sym.triD;
|
|
17
|
-
|
|
18
15
|
return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
|
|
19
16
|
h(Box, { height: 1 },
|
|
20
|
-
h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt },
|
|
17
|
+
h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt },
|
|
18
|
+
' ' + (collapsed ? sym.triR : sym.triD) + ' Thought' + (durStr ? ' (' + durStr + ')' : ''))
|
|
21
19
|
),
|
|
22
20
|
collapsed ? null : h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
|
|
23
21
|
items.map((tr, i) => {
|
|
24
22
|
const isLast = i === items.length - 1;
|
|
25
|
-
const prefix = isLast ? sym.treeTip +
|
|
23
|
+
const prefix = isLast ? sym.treeTip + '\u2500' : sym.treeFork + '\u2500';
|
|
26
24
|
const conn = isLast ? ' ' : sym.treeCon;
|
|
27
25
|
const ico = tr.status === 'failed' ? sym.cross : sym.circle;
|
|
28
|
-
const col = tr.status === 'failed' ? hex.red : hex.green;
|
|
29
26
|
const td = tr.duration ? ' ' + tr.duration + 'ms' : '';
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
27
|
+
return h(Box, { key: tr.execId || i, height: 1 },
|
|
28
|
+
h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
|
|
29
|
+
' ' + prefix + ' ' + ico + ' ' + tr.name + td)
|
|
41
30
|
);
|
|
42
31
|
})
|
|
43
32
|
)
|
|
@@ -36,10 +36,5 @@ export function ToolCard({ exec, isActive }) {
|
|
|
36
36
|
h(Text, { color: hex.blue, backgroundColor: hex.surfaceAlt }, ' ' + sym.dot + ' running')
|
|
37
37
|
)
|
|
38
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,
|
|
44
39
|
);
|
|
45
40
|
}
|
package/src/config/layout.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import wrapAnsi from 'wrap-ansi';
|
|
2
|
+
import stringWidth from 'string-width';
|
|
2
3
|
|
|
3
4
|
export function getLayout() {
|
|
4
5
|
const rows = process.stdout.rows || 30;
|
|
5
6
|
const cols = process.stdout.columns || 80;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
7
|
+
const headerHeight = 1;
|
|
8
|
+
const dockHeight = 4;
|
|
9
|
+
const viewport = Math.max(8, rows - headerHeight - dockHeight);
|
|
10
|
+
const contentWidth = Math.max(20, cols - 4);
|
|
11
|
+
return { rows, cols, headerHeight, dockHeight, viewport, contentWidth, pad: ' ' };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function sw(text) {
|
|
15
|
+
return stringWidth(text || '');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function wrapText(text, width) {
|
|
@@ -34,9 +34,9 @@ export function truncateText(text, maxLines, width) {
|
|
|
34
34
|
|
|
35
35
|
export function measureEntry(entry, w) {
|
|
36
36
|
const nr = entry.role;
|
|
37
|
-
if (nr === 'user') return 1 + countLines(entry.content, w)
|
|
37
|
+
if (nr === 'user') return 1 + countLines(entry.content, w);
|
|
38
38
|
if (nr === 'assistant') return 2 + countLines(entry.content, w) + (entry.duration ? 1 : 0);
|
|
39
|
-
if (nr === 'tool') return
|
|
39
|
+
if (nr === 'tool') return 1;
|
|
40
40
|
if (nr === 'system' || nr === 'error') return 1;
|
|
41
41
|
if (nr === 'streaming') return 2 + Math.min(40, countLines(entry.content, w));
|
|
42
42
|
return 1;
|
|
@@ -94,12 +94,7 @@ export function buildLineArray(slice, clipIndex, clipLines, w) {
|
|
|
94
94
|
}
|
|
95
95
|
if (e.duration) lines.push({ type: 'asst_foot', text: String(e.duration), data: e });
|
|
96
96
|
} else if (nr === 'tool') {
|
|
97
|
-
|
|
98
|
-
if (e.completed) {
|
|
99
|
-
if (skip <= 0) lines.push({ type: 'tool_line', text: label + ' ' + (e.duration ? e.duration + 'ms' : ''), data: e });
|
|
100
|
-
} else {
|
|
101
|
-
lines.push({ type: 'tool_line', text: label, data: e });
|
|
102
|
-
}
|
|
97
|
+
lines.push({ type: 'tool_line', text: (e.error ? '\u2716 ' : '\u25C9 ') + (e.toolName || 'tool') + (e.duration ? ' ' + e.duration + 'ms' : ''), data: e });
|
|
103
98
|
} else if (nr === 'system') {
|
|
104
99
|
if (skip <= 0) lines.push({ type: 'sys_line', text: e.content, data: e });
|
|
105
100
|
} else if (nr === 'error') {
|
|
@@ -109,9 +104,8 @@ export function buildLineArray(slice, clipIndex, clipLines, w) {
|
|
|
109
104
|
if (e.status) lines.push({ type: 'stream_status', text: e.status, data: e });
|
|
110
105
|
const wrapped = wrapText(e.content || '', w);
|
|
111
106
|
const contentLines = wrapped.split('\n');
|
|
112
|
-
const maxLines = 40;
|
|
113
107
|
const startLine = Math.min(skip, contentLines.length);
|
|
114
|
-
const endLine = Math.min(contentLines.length, startLine +
|
|
108
|
+
const endLine = Math.min(contentLines.length, startLine + 40);
|
|
115
109
|
for (let ci = startLine; ci < endLine; ci++) {
|
|
116
110
|
lines.push({ type: 'stream_line', text: contentLines[ci], data: e });
|
|
117
111
|
}
|
package/src/config/theme.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import gradient from 'gradient-string';
|
|
3
|
+
import figures from 'figures';
|
|
2
4
|
|
|
3
5
|
export const hex = {
|
|
4
6
|
bg: '#0A0A14',
|
|
@@ -21,9 +23,6 @@ export const hex = {
|
|
|
21
23
|
textMuted: '#555577',
|
|
22
24
|
white: '#FFFFFF',
|
|
23
25
|
black: '#000000',
|
|
24
|
-
modalOverlay: 'rgba(0,0,0,0.85)',
|
|
25
|
-
logoOrange: '#FF6B35',
|
|
26
|
-
logoBlue: '#3B82F6',
|
|
27
26
|
};
|
|
28
27
|
|
|
29
28
|
export const color = {
|
|
@@ -48,55 +47,42 @@ export const color = {
|
|
|
48
47
|
};
|
|
49
48
|
|
|
50
49
|
export const sym = {
|
|
51
|
-
diamond: '\u25C6',
|
|
52
50
|
circle: '\u25C9',
|
|
53
51
|
dot: '\u25CF',
|
|
54
|
-
smallDot: '\u25CB',
|
|
55
52
|
triR: '\u25B8',
|
|
56
53
|
triD: '\u25BE',
|
|
57
54
|
bullet: '\u25C9',
|
|
58
55
|
cross: '\u2716',
|
|
59
56
|
ellipsis: '\u2026',
|
|
60
57
|
mdash: '\u2014',
|
|
61
|
-
ndash: '\u2013',
|
|
62
58
|
midDot: '\u00B7',
|
|
63
59
|
arrowR: '\u2192',
|
|
64
|
-
arrowL: '\u2190',
|
|
65
60
|
arrowU: '\u2191',
|
|
66
61
|
arrowD: '\u2193',
|
|
67
|
-
lightV: '\u2502',
|
|
68
|
-
lightH: '\u2500',
|
|
69
62
|
box: {
|
|
70
63
|
tl: '\u250C', tr: '\u2510', bl: '\u2514', br: '\u2518',
|
|
71
64
|
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
65
|
},
|
|
79
66
|
treeJ: '\u2514',
|
|
80
67
|
treeT: '\u251C',
|
|
81
68
|
treeCon: '\u2502',
|
|
82
|
-
triR2: '\u25B8',
|
|
83
|
-
triD2: '\u25BE',
|
|
84
|
-
u: { h: '\u2500' },
|
|
85
69
|
treeTip: '\u2570',
|
|
86
70
|
treeFork: '\u256D',
|
|
87
71
|
star: '\u2726',
|
|
88
|
-
asterisk: '\u2731',
|
|
89
72
|
gear: '\u2699',
|
|
90
|
-
|
|
73
|
+
pointer: '\u276F',
|
|
91
74
|
};
|
|
92
75
|
|
|
76
|
+
export const gradientText = gradient(['#FF6B35', '#3B82F6']);
|
|
77
|
+
export const gradientLine = gradient(['#FF6B35', '#3B82F6']);
|
|
78
|
+
|
|
93
79
|
export const LOGO = [
|
|
94
80
|
' ',
|
|
95
81
|
' ██████ ██ █████ ██████ ██ ████████ ██ ██ ',
|
|
96
82
|
' ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ',
|
|
97
83
|
' ██████ ██ ███████ ██████ ██ ██ ██ ██ ',
|
|
98
84
|
' ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ',
|
|
99
|
-
' ██ ███████ ██ ██ ██ ██ ██ ██
|
|
85
|
+
' ██ ███████ ██ ██ ██ ██ ██ ██ ██ ██████▄ ',
|
|
100
86
|
' ',
|
|
101
87
|
' █████ ██ ',
|
|
102
88
|
' ██ ██ ██ ',
|