clarity-ai 6.8.0 → 7.0.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
+ ## 7.0.0 (2026-06-06)
6
+
7
+ ### Ground-up architectural rebuild — centered minimalist terminal platform
8
+ - **Complete UI obliteration**: all legacy components replaced with fresh centered layout
9
+ - **Ink-text-input**: controlled form text entry via `ink-text-input` (v6) with dim placeholder
10
+ - **Dynamic viewport hardening**: `process.stdout.on('resize')` listener tracks rows/columns live; auto-downgrades padding below 50 cols
11
+ - **Centered gradient CLARITY logo**: permanent eye-level anchor using gradient-string (orange→blue); never unmounts or wraps
12
+ - **Bordered PromptCard**: clean `┌─┐` container width-capped at 55 cols; status bar with model/provider/agent indicators
13
+ - **Stream concurrency protection**: wrap-ansi + cli-truncate gates all text output; keyboard locked during thinking/streaming
14
+ - **Keybind metadata footer**: right-aligned "tab agents ctrl+p commands" in muted text
15
+ - **System callout banner**: `▀ Tip: Use /help ...` at bottom edge with keyword highlighting
16
+
5
17
  ## 6.8.0 (2026-06-06)
6
18
 
7
19
  ### Clean-slate TUI: dynamic dimension defense, sandboxed streams, @inkjs/ui
package/bin/clarity.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import React from 'react';
3
3
  import { render } from 'ink';
4
- import { App } from '../src/components/AppRoot.js';
4
+ import { App } from '../src/app.js';
5
5
  import { hasKey } from '../src/config/keys.js';
6
6
  import { createInterface } from 'readline';
7
7
 
@@ -36,7 +36,7 @@ async function main() {
36
36
 
37
37
  const config = { provider, model: process.env.CLARITY_MODEL || 'groq/llama-3.3-70b-versatile' };
38
38
 
39
- const { clear, waitUntilExit, rerender } = render(React.createElement(App, { config }), {
39
+ const { clear, waitUntilExit } = render(React.createElement(App, { config }), {
40
40
  fullscreen: true,
41
41
  patchConsole: false,
42
42
  exitOnCtrlC: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-ai",
3
- "version": "6.8.0",
3
+ "version": "7.0.0",
4
4
  "description": "CLARITY — terminal AI agent with local GGUF inference on HF Spaces",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,16 +19,18 @@
19
19
  "access": "public"
20
20
  },
21
21
  "dependencies": {
22
- "ink": "^5",
23
- "react": "^18",
24
- "ink-spinner": "^5",
22
+ "@inkjs/ui": "^2",
23
+ "ansi-escapes": "^7",
25
24
  "chalk": "^5",
26
- "wrap-ansi": "^9",
27
- "gradient-string": "^3",
25
+ "cli-truncate": "^6",
26
+ "ink-text-input": "^6",
28
27
  "figures": "^6",
29
- "ansi-escapes": "^7",
28
+ "gradient-string": "^3",
29
+ "ink": "^5",
30
+ "ink-spinner": "^5",
31
+ "ink-text-input": "^6.0.0",
32
+ "react": "^18",
30
33
  "string-width": "^7",
31
- "@inkjs/ui": "^2",
32
- "cli-truncate": "^6"
34
+ "wrap-ansi": "^9"
33
35
  }
34
36
  }
package/src/app.js ADDED
@@ -0,0 +1,105 @@
1
+ import React, { useState, useCallback, useRef, useEffect } from 'react';
2
+ import { Box, Text, useStdout } from 'ink';
3
+ import { createChatState, handleSend, handleCommand } from './chat.js';
4
+ import { hex } from './config/theme.js';
5
+ import { getLayout } from './config/layout.js';
6
+ import { Header } from './components/Header.js';
7
+ import { StreamView } from './components/StreamView.js';
8
+ import { PromptCard } from './components/PromptCard.js';
9
+ import { Footer } from './components/Footer.js';
10
+ const { createElement: h } = React;
11
+
12
+ let abortController = null;
13
+
14
+ export function getAbortController() {
15
+ return abortController;
16
+ }
17
+
18
+ export function setAbortController(ac) {
19
+ abortController = ac;
20
+ }
21
+
22
+ export function cancelStream() {
23
+ if (abortController) {
24
+ try { abortController.abort(); } catch {}
25
+ abortController = null;
26
+ }
27
+ }
28
+
29
+ export function App({ config }) {
30
+ const { stdout } = useStdout();
31
+ const [dims, setDims] = useState({ rows: stdout.rows || 30, cols: stdout.columns || 80 });
32
+ const [state, setState] = useState(() => createChatState());
33
+ const [streamContent, setStreamContent] = useState('');
34
+ const defaultModel = (config.model || 'groq/llama-3.3-70b-versatile').replace(/^[^/]+\//, '');
35
+ const [model, setModel] = useState(defaultModel);
36
+ const [provider, setProvider] = useState(config.provider || 'groq');
37
+
38
+ const stateRef = useRef(state);
39
+ const modelRef = useRef(model);
40
+ const providerRef = useRef(provider);
41
+ stateRef.current = state;
42
+ modelRef.current = model;
43
+ providerRef.current = provider;
44
+
45
+ useEffect(() => {
46
+ function onResize() {
47
+ setDims({ rows: process.stdout.rows || 30, cols: process.stdout.columns || 80 });
48
+ }
49
+ process.stdout.on('resize', onResize);
50
+ return () => process.stdout.removeListener('resize', onResize);
51
+ }, []);
52
+
53
+ const cols = dims.cols;
54
+ const rows = dims.rows;
55
+ const isCompact = cols < 50;
56
+ const cardWidth = Math.min(cols - 4, 55);
57
+ const contentPad = isCompact ? 1 : 2;
58
+
59
+ const onSubmit = useCallback(async (input) => {
60
+ if (input === '/exit') { process.exit(0); return; }
61
+ cancelStream();
62
+ const ac = new AbortController();
63
+ setAbortController(ac);
64
+ ac.signal.addEventListener('abort', () => {
65
+ setState(s => ({ ...s, thinking: false, streamContent: '', agentStatus: '', toolExecutions: [], thoughtTimer: null }));
66
+ setStreamContent('');
67
+ });
68
+ await handleSend(stateRef.current, setState, input, modelRef.current, providerRef.current, setStreamContent, ac.signal);
69
+ }, []);
70
+
71
+ const onCommand = useCallback(async (input) => {
72
+ if (input.startsWith('/stop')) { cancelStream(); return; }
73
+ if (input.startsWith('/exit')) { process.exit(0); return; }
74
+ await handleCommand(input, stateRef.current, setState, setModel, setProvider, modelRef.current, providerRef.current);
75
+ }, []);
76
+
77
+ function handleInputSubmit(value) {
78
+ const trimmed = value.trim();
79
+ if (!trimmed) return;
80
+ if (trimmed.startsWith('/')) {
81
+ onCommand(trimmed);
82
+ } else {
83
+ onSubmit(trimmed);
84
+ }
85
+ }
86
+
87
+ return h(Box, { width: '100%', height: '100%', flexDirection: 'column', alignItems: 'center', backgroundColor: hex.bg },
88
+ h(Box, { flexGrow: 1, minHeight: 1 }),
89
+ h(Header, { cols }),
90
+ h(Box, { flexGrow: 2, width: cardWidth, minHeight: 2 },
91
+ h(StreamView, { messages: state.messages, streamContent, thinking: state.thinking, width: cardWidth - 2 })
92
+ ),
93
+ h(PromptCard, {
94
+ width: cardWidth,
95
+ compact: isCompact,
96
+ provider,
97
+ model,
98
+ agentMode: state.agentMode,
99
+ thinking: state.thinking,
100
+ onSubmit: handleInputSubmit,
101
+ }),
102
+ h(Box, { flexGrow: 1, minHeight: 1 }),
103
+ h(Footer, { cols })
104
+ );
105
+ }
package/src/chat.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { callAI } from './providers/index.js';
2
2
  import { setKey } from './config/keys.js';
3
3
  import { TOOLS, executeTool } from './tools.js';
4
- import { cancelStream } from './components/AppRoot.js';
4
+ import { cancelStream } from './app.js';
5
5
 
6
6
  const sleep = ms => new Promise(r => setTimeout(r, ms));
7
7
 
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { hex } from '../config/theme.js';
4
+ const { createElement: h } = React;
5
+
6
+ export function Footer({ cols }) {
7
+ const showFull = cols >= 60;
8
+ const keybindText = 'tab agents ctrl+p commands';
9
+ const tipText = 'Tip: Use /help to view system commands and switch active models';
10
+
11
+ return h(Box, { width: '100%', flexDirection: 'column', backgroundColor: hex.bg },
12
+ h(Box, { height: 1, justifyContent: 'flex-end', paddingRight: 2 },
13
+ h(Text, { color: hex.textMuted }, keybindText)
14
+ ),
15
+ h(Box, { height: 1, paddingLeft: 2, paddingRight: 2 },
16
+ h(Text, { color: hex.textDim },
17
+ '\u2580 ',
18
+ h(Text, { color: hex.blue }, 'Tip'),
19
+ h(Text, { color: hex.textDim }, ': ' + (showFull ? tipText : 'Use /help for commands'))
20
+ )
21
+ )
22
+ );
23
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { appGradient } from '../config/theme.js';
4
+ const { createElement: h } = React;
5
+
6
+ export function Header({ cols }) {
7
+ const label = appGradient('CLARITY');
8
+ return h(Box, { height: 3, width: '100%', alignItems: 'center', justifyContent: 'center' },
9
+ h(Text, { bold: true }, label)
10
+ );
11
+ }
@@ -0,0 +1,50 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import { hex } from '../config/theme.js';
5
+ const { createElement: h } = React;
6
+
7
+ const DIVIDER = '\u2500';
8
+
9
+ export function PromptCard({ width, compact, provider, model, agentMode, thinking, onSubmit }) {
10
+ const [input, setInput] = useState('');
11
+ const mShort = model.replace(/^[^/]+\//, '').slice(0, compact ? 10 : 20);
12
+ const pShort = provider.slice(0, compact ? 6 : 10);
13
+ const statusLine = '[ ' + pShort + ' \u00B7 ' + mShort + (agentMode ? ' \u00B7 AGENT' : '') + ' ]';
14
+ const innerW = width - 2;
15
+
16
+ function handleSubmit(value) {
17
+ onSubmit(value);
18
+ setInput('');
19
+ }
20
+
21
+ return h(Box, { flexDirection: 'column', width, backgroundColor: hex.surface },
22
+ h(Box, { height: 1 },
23
+ h(Text, { color: hex.textMuted },
24
+ '\u250C' + '\u2500'.repeat(Math.max(0, innerW)) + '\u2510')
25
+ ),
26
+ h(Box, { height: 1, paddingLeft: 1, paddingRight: 1 },
27
+ h(TextInput, {
28
+ value: input,
29
+ onChange: setInput,
30
+ onSubmit: handleSubmit,
31
+ placeholder: 'Ask anything...',
32
+ focus: !thinking,
33
+ })
34
+ ),
35
+ h(Box, { height: 1 },
36
+ h(Text, { color: hex.textMuted },
37
+ '\u2502' + '\u2500'.repeat(Math.max(0, innerW)) + '\u2502')
38
+ ),
39
+ h(Box, { height: 1, paddingLeft: 1, paddingRight: 1, alignItems: 'center' },
40
+ h(Text, { color: hex.textDim },
41
+ statusLine + ' '.repeat(Math.max(0, innerW - statusLine.length - 1))
42
+ ),
43
+ h(Text, { color: hex.textMuted }, '\u2502')
44
+ ),
45
+ h(Box, { height: 1 },
46
+ h(Text, { color: hex.textMuted },
47
+ '\u2514' + '\u2500'.repeat(Math.max(0, innerW)) + '\u2518')
48
+ ),
49
+ );
50
+ }
@@ -0,0 +1,78 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { hex } from '../config/theme.js';
4
+ import wrapAnsi from 'wrap-ansi';
5
+ import cliTruncate from 'cli-truncate';
6
+ const { createElement: h } = React;
7
+
8
+ function wrap(text, width) {
9
+ if (!text) return '';
10
+ return wrapAnsi(String(text), Math.max(1, width), { trim: false, hard: true });
11
+ }
12
+
13
+ function trunc(text, width) {
14
+ return cliTruncate(text || '', Math.max(1, width), { position: 'end' });
15
+ }
16
+
17
+ function MessageLine({ line, width }) {
18
+ switch (line.role) {
19
+ case 'system':
20
+ return h(Box, { height: 1 },
21
+ h(Text, { color: hex.blue }, ' \u25C9 ' + trunc(line.content, width - 4))
22
+ );
23
+ case 'user':
24
+ return h(Box, { height: 1 },
25
+ h(Text, { color: hex.accent, bold: true }, ' \u276F '),
26
+ h(Text, { color: hex.text }, trunc(line.content, width - 4))
27
+ );
28
+ case 'assistant':
29
+ return h(Box, { height: 1 },
30
+ h(Text, { color: hex.purple, bold: true }, ' \u25C6 '),
31
+ h(Text, { color: hex.text }, trunc(line.content, width - 4))
32
+ );
33
+ case 'tool':
34
+ return h(Box, { height: 1 },
35
+ h(Text, { color: hex.green }, ' \u25C9 ' + trunc((line.toolName || 'tool') + (line.duration ? ' ' + line.duration + 'ms' : ''), width - 6))
36
+ );
37
+ case 'error':
38
+ return h(Box, { height: 1 },
39
+ h(Text, { color: hex.red }, ' \u2716 ' + trunc(line.content, width - 4))
40
+ );
41
+ default:
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function StreamingLine({ content, width }) {
47
+ const wrapped = wrap(content || '', width - 4);
48
+ const first = wrapped.split('\n')[0] || '';
49
+ return h(Box, { height: 1 },
50
+ h(Text, { color: hex.purple }, ' \u25CF '),
51
+ h(Text, { color: hex.text }, trunc(first, width - 4))
52
+ );
53
+ }
54
+
55
+ export function StreamView({ messages, streamContent, thinking, width }) {
56
+ const contentW = Math.max(1, width - 2);
57
+
58
+ const visibleMessages = useMemo(() => {
59
+ if (messages.length === 0) {
60
+ return [{ id: 'empty', role: 'system', content: 'CLARITY AI ready \u00B7 Ctrl+P commands \u00B7 /help' }];
61
+ }
62
+ return messages.slice(-20);
63
+ }, [messages]);
64
+
65
+ return h(Box, { flexDirection: 'column', width: '100%', paddingLeft: 1, paddingRight: 1 },
66
+ visibleMessages.map(m =>
67
+ h(MessageLine, { key: m.id, line: m, width: contentW })
68
+ ),
69
+ thinking && streamContent
70
+ ? h(StreamingLine, { content: streamContent, width: contentW })
71
+ : null,
72
+ thinking && !streamContent
73
+ ? h(Box, { height: 1 },
74
+ h(Text, { color: hex.blue }, ' \u25CF processing...')
75
+ )
76
+ : null,
77
+ );
78
+ }
@@ -1,97 +0,0 @@
1
- import React, { useState, useCallback, useRef, useEffect } from 'react';
2
- import { Box, Text } from 'ink';
3
- import { createChatState, handleSend, handleCommand } from '../chat.js';
4
- import { hex } from '../config/theme.js';
5
- import { getLayout } from '../config/layout.js';
6
- import { Layout } from './Layout.js';
7
- const { createElement: h } = React;
8
-
9
- let abortController = null;
10
-
11
- export function getAbortController() {
12
- return abortController;
13
- }
14
-
15
- export function setAbortController(ac) {
16
- abortController = ac;
17
- }
18
-
19
- export function cancelStream() {
20
- if (abortController) {
21
- try { abortController.abort(); } catch {}
22
- abortController = null;
23
- }
24
- }
25
-
26
- export function App({ config }) {
27
- const [state, setState] = useState(() => createChatState());
28
- const [streamContent, setStreamContent] = useState('');
29
- const defaultModel = (config.model || 'groq/llama-3.3-70b-versatile').replace(/^[^/]+\//, '');
30
- const [model, setModel] = useState(defaultModel);
31
- const [provider, setProvider] = useState(config.provider || 'groq');
32
- const [showCommands, setShowCommands] = useState(false);
33
- const [showModels, setShowModels] = useState(false);
34
-
35
- const stateRef = useRef(state);
36
- const modelRef = useRef(model);
37
- const providerRef = useRef(provider);
38
- stateRef.current = state;
39
- modelRef.current = model;
40
- providerRef.current = provider;
41
-
42
- const onSubmit = useCallback(async (input) => {
43
- if (input === '/exit') { process.exit(0); return; }
44
- if (input.startsWith('/')) {
45
- if (input === '/model' || input === '/models') { setShowModels(true); return; }
46
- if (input === '/help') { setShowCommands(true); return; }
47
- if (input === '/stop') { cancelStream(); return; }
48
- await handleCommand(input, stateRef.current, setState, setModel, setProvider, modelRef.current, providerRef.current);
49
- return;
50
- }
51
- cancelStream();
52
- const ac = new AbortController();
53
- setAbortController(ac);
54
- ac.signal.addEventListener('abort', () => {
55
- setState(s => ({ ...s, thinking: false, streamContent: '', agentStatus: '', toolExecutions: [], thoughtTimer: null }));
56
- setStreamContent('');
57
- });
58
- await handleSend(stateRef.current, setState, input, modelRef.current, providerRef.current, setStreamContent, ac.signal);
59
- }, []);
60
-
61
- function handleCmdSelect(cmdName) {
62
- setShowCommands(false);
63
- onSubmit(cmdName);
64
- }
65
-
66
- function handleModelSelect(modelId) {
67
- setProvider(modelId.split('/')[0]);
68
- setModel(modelId.replace(/^[^/]+\//, ''));
69
- setShowModels(false);
70
- setState(s => ({
71
- ...s,
72
- messages: [...s.messages, { id: 'sys-' + Date.now(), role: 'system', content: 'Switched to ' + modelId }],
73
- }));
74
- }
75
-
76
- const layout = getLayout();
77
-
78
- if (!layout.isLargeEnough) {
79
- return h(Box, { width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', backgroundColor: hex.bg },
80
- h(Text, { color: hex.textDim },
81
- 'Terminal size too small. Please expand. (' + layout.cols + 'x' + layout.rows + ' needed: 60x20)')
82
- );
83
- }
84
-
85
- return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, width: '100%', height: '100%' },
86
- h(Layout, {
87
- state, streamContent, model, provider,
88
- showCommands, showModels,
89
- onCommandSelect: handleCmdSelect,
90
- onModelSelect: handleModelSelect,
91
- onCloseCommands: () => setShowCommands(false),
92
- onCloseModels: () => setShowModels(false),
93
- onSlash: () => setShowCommands(true),
94
- onSubmit,
95
- })
96
- );
97
- }
@@ -1,44 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import { Box, Text } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- import { getLayout } from '../config/layout.js';
5
- const { createElement: h } = React;
6
-
7
- const LANG_COLORS = {
8
- js: '#F0DB4F', jsx: '#F0DB4F', ts: '#3178C6', tsx: '#3178C6',
9
- py: '#3572A5', rb: '#CC342D', go: '#00ADD8', rs: '#DEA584',
10
- java: '#B07219', kt: '#7F52FF', swift: '#FFAC45',
11
- html: '#E34F26', css: '#1572B6', scss: '#CC6699',
12
- sh: '#89E051', bash: '#89E051',
13
- };
14
-
15
- export function CodeBlock({ code, language }) {
16
- const lang = language || 'code';
17
- const lines = useMemo(() => String(code).split('\n'), [code]);
18
- const langColor = LANG_COLORS[lang] || '#555';
19
- const { cols } = getLayout();
20
- const maxLines = 15;
21
- const visible = lines.slice(0, maxLines);
22
- const codeWidth = cols - 10;
23
- const lnW = String(lines.length).length;
24
-
25
- return h(Box, { flexDirection: 'column', backgroundColor: hex.codeBg },
26
- h(Box, { height: 1, 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'),
29
- ),
30
- h(Box, { flexDirection: 'column', backgroundColor: hex.codeBg },
31
- visible.map((line, i) =>
32
- h(Box, { key: i, height: 1, backgroundColor: hex.codeBg },
33
- h(Text, { color: hex.textMuted, backgroundColor: hex.codeBg }, ' ' + String(i + 1).padStart(lnW) + ' '),
34
- h(Text, { color: '#C9D1D9', backgroundColor: hex.codeBg, wrap: 'truncate-end' }, (line || ' ').slice(0, codeWidth))
35
- )
36
- ),
37
- lines.length > maxLines
38
- ? h(Box, { height: 1, backgroundColor: hex.codeBg },
39
- h(Text, { color: hex.textMuted, backgroundColor: hex.codeBg }, ' \u2026 ' + (lines.length - maxLines) + ' more lines')
40
- )
41
- : null
42
- )
43
- );
44
- }
@@ -1,73 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Box, Text, useInput } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- import { getLayout } from '../config/layout.js';
5
- const { createElement: h } = React;
6
-
7
- const COMMANDS = [
8
- { name: '/keys', desc: 'Set API key' },
9
- { name: '/model', desc: 'Switch model' },
10
- { name: '/provider', desc: 'Switch provider' },
11
- { name: '/agent', desc: 'Toggle agent mode' },
12
- { name: '/stop', desc: 'Cancel running stream' },
13
- { name: '/clear', desc: 'Clear conversation' },
14
- { name: '/export', desc: 'Export conversation' },
15
- { name: '/help', desc: 'Show all commands' },
16
- { name: '/exit', desc: 'Exit CLARITY' },
17
- ];
18
-
19
- export function CommandPicker({ onSelect, onClose }) {
20
- const [search, setSearch] = useState('');
21
- const [idx, setIdx] = useState(0);
22
- const { cols } = getLayout();
23
-
24
- const filtered = COMMANDS.filter(c =>
25
- c.name.includes(search) || c.desc.toLowerCase().includes(search.toLowerCase())
26
- );
27
-
28
- useInput((input, key) => {
29
- if (key.upArrow) setIdx(i => Math.max(0, i - 1));
30
- if (key.downArrow) setIdx(i => Math.min(filtered.length - 1, i + 1));
31
- if (key.return && filtered[idx]) onSelect(filtered[idx].name);
32
- if (key.escape) onClose();
33
- if (key.backspace) setSearch(s => s.slice(0, -1));
34
- else if (input && !key.ctrl && !key.meta) setSearch(s => s + input);
35
- });
36
-
37
- const w = Math.min(cols - 4, 48);
38
-
39
- return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, width: w },
40
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
41
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
42
- '\u250C' + '\u2500'.repeat(w - 2) + '\u2510')
43
- ),
44
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
45
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
46
- '\u2502 Commands' + ' '.repeat(Math.max(0, w - 12)) + '\u2502')
47
- ),
48
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
49
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
50
- '\u2502 ' + (search || '\u2026') + ' '.repeat(Math.max(0, w - 6)) + '\u2502')
51
- ),
52
- filtered.map((cmd, i) =>
53
- h(Box, {
54
- key: cmd.name, height: 1,
55
- backgroundColor: i === idx ? hex.selectionBg : hex.bg,
56
- },
57
- h(Text, {
58
- color: i === idx ? hex.selectionText : hex.text,
59
- bold: i === idx,
60
- backgroundColor: i === idx ? hex.selectionBg : hex.bg,
61
- }, (i === idx ? '\u276F ' : ' ') + cmd.name + ' ' + cmd.desc)
62
- )
63
- ),
64
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
65
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
66
- '\u2514' + '\u2500'.repeat(w - 2) + '\u2518')
67
- ),
68
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
69
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
70
- ' \u2191\u2193 nav \u2192 select Esc close')
71
- ),
72
- );
73
- }
@@ -1,74 +0,0 @@
1
- import React, { useState, useRef } from 'react';
2
- import { Box, Text, useInput } from 'ink';
3
- import { hex, sym } from '../config/theme.js';
4
- import { getLayout } from '../config/layout.js';
5
- const { createElement: h } = React;
6
-
7
- const MAX_ROWS = 3;
8
-
9
- export function Composer({ provider, model, agentMode, thinking, onSlash, onSubmit }) {
10
- const [input, setInput] = useState('');
11
- const [cursor, setCursor] = useState(0);
12
- const r = useRef('');
13
- r.current = input;
14
-
15
- const { cols } = getLayout();
16
- const w = Math.max(10, cols - 6);
17
- const lineCount = Math.max(1, Math.ceil((input.length || 1) / w));
18
- const visible = Math.min(lineCount, MAX_ROWS);
19
- const mShort = model.replace(/^[^/]+\//, '').slice(0, 18);
20
- const isPlaceholder = !input && !thinking;
21
-
22
- useInput((ch, key) => {
23
- if (key.ctrl && key.p) { onSlash(); return; }
24
- if (key.escape) { onSubmit('/exit'); return; }
25
- if (key.return && !key.shift) {
26
- if (input.trim()) { const t = input; setInput(''); setCursor(0); onSubmit(t); }
27
- return;
28
- }
29
- if (key.return && key.shift) {
30
- setInput(p => p.slice(0, cursor) + '\n' + p.slice(cursor));
31
- setCursor(c => c + 1);
32
- return;
33
- }
34
- if (key.backspace || key.delete) {
35
- if (cursor > 0) { setInput(p => p.slice(0, cursor - 1) + p.slice(cursor)); setCursor(c => c - 1); }
36
- return;
37
- }
38
- if (key.leftArrow && cursor > 0) { setCursor(c => c - 1); return; }
39
- if (key.rightArrow && cursor < input.length) { setCursor(c => c + 1); return; }
40
- if (key.home) { setCursor(0); return; }
41
- if (key.end) { setCursor(input.length); return; }
42
- if (ch && ch.length === 1 && ch.charCodeAt(0) >= 32) {
43
- setInput(p => p.slice(0, cursor) + ch + p.slice(cursor));
44
- setCursor(c => c + 1);
45
- }
46
- });
47
-
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
-
54
- for (let i = 0; i < MAX_ROWS; i++) {
55
- const start = i * w;
56
- const seg = input.slice(start, start + w);
57
- rows.push(
58
- h(Box, { key: 'r' + i, height: 1, backgroundColor: hex.bg },
59
- h(Text, {
60
- color: isPlaceholder ? hex.textMuted : hex.text,
61
- backgroundColor: hex.bg,
62
- wrap: 'truncate-end',
63
- }, ' \u2502 ' + (seg || (i === 0 && isPlaceholder ? 'type a message...' : ' ')))
64
- )
65
- );
66
- }
67
-
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
- ));
72
-
73
- return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt }, ...rows);
74
- }
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { hex, appGradient } from '../config/theme.js';
4
- import { getLayout } from '../config/layout.js';
5
- const { createElement: h } = React;
6
-
7
- export function HeaderBar({ model, provider, agentMode, thinking }) {
8
- const { cols } = getLayout();
9
- const m = model.replace(/^[^/]+\//, '').slice(0, Math.floor((cols - 20) / 2));
10
- const status = thinking ? ' \u25CF' : ' \u25CB';
11
- const mode = agentMode ? '\u25C8 AGENT' : '\u25CB USER';
12
- const label = '[' + provider + '] ' + m + ' ' + status + ' ' + mode;
13
- const labelLen = label.length + 14;
14
- const gap = Math.max(1, cols - labelLen);
15
-
16
- return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
17
- h(Text, { backgroundColor: hex.surfaceAlt }, appGradient(' CLARITY AI ')),
18
- h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, ' '.repeat(gap) + label + ' ')
19
- );
20
- }
@@ -1,59 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Box, Text, useInput } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- import { getLayout } from '../config/layout.js';
5
- const { createElement: h } = React;
6
-
7
- export function InputBar({ provider, model, agentMode, thinking, onSlash, onSubmit }) {
8
- const [input, setInput] = useState('');
9
- const [cursor, setCursor] = useState(0);
10
- const { cols } = getLayout();
11
- const w = Math.max(10, cols - 6);
12
- const mShort = model.replace(/^[^/]+\//, '').slice(0, 16);
13
-
14
- useInput((ch, key) => {
15
- if (key.ctrl && key.p) { onSlash(); return; }
16
- if (key.escape) { onSubmit('/exit'); return; }
17
- if (key.return && !key.shift) {
18
- if (input.trim()) { const t = input; setInput(''); setCursor(0); onSubmit(t); }
19
- return;
20
- }
21
- if (key.return && key.shift) {
22
- setInput(p => p.slice(0, cursor) + '\n' + p.slice(cursor));
23
- setCursor(c => c + 1);
24
- return;
25
- }
26
- if (key.backspace || key.delete) {
27
- if (cursor > 0) { setInput(p => p.slice(0, cursor - 1) + p.slice(cursor)); setCursor(c => c - 1); }
28
- return;
29
- }
30
- if (key.leftArrow && cursor > 0) { setCursor(c => c - 1); return; }
31
- if (key.rightArrow && cursor < input.length) { setCursor(c => c + 1); return; }
32
- if (key.home) { setCursor(0); return; }
33
- if (key.end) { setCursor(input.length); return; }
34
- if (ch && ch.length === 1 && ch.charCodeAt(0) >= 32) {
35
- setInput(p => p.slice(0, cursor) + ch + p.slice(cursor));
36
- setCursor(c => c + 1);
37
- }
38
- });
39
-
40
- const isPlaceholder = !input && !thinking;
41
-
42
- return h(Box, { flexDirection: 'column' },
43
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
44
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
45
- ' \u250C' + '\u2500'.repeat(Math.max(0, cols - 6)) + '\u2510')
46
- ),
47
- h(Box, { height: 1, backgroundColor: hex.bg },
48
- h(Text, {
49
- color: isPlaceholder ? hex.textMuted : hex.text,
50
- backgroundColor: hex.bg,
51
- wrap: 'truncate-end',
52
- }, ' \u2502 ' + (input || (thinking ? '\u25CF processing...' : 'type a message...')) + ' ')
53
- ),
54
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
55
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
56
- ' \u2514' + '\u2500' + ' ' + provider + ' \u00B7 ' + mShort + (agentMode ? ' \u00B7 AGENT' : '') + ' \u00B7 Ctrl+P')
57
- )
58
- );
59
- }
@@ -1,30 +0,0 @@
1
- import React from 'react';
2
- import { Box } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- import { HeaderBar } from './HeaderBar.js';
5
- import { MessageList } from './MessageList.js';
6
- import { InputBar } from './InputBar.js';
7
- import { CommandPicker } from './CommandPicker.js';
8
- import { ModelPicker } from './ModelPicker.js';
9
- const { createElement: h } = React;
10
-
11
- export function Layout({ state, streamContent, model, provider, showCommands, showModels, onCommandSelect, onModelSelect, onCloseCommands, onCloseModels, onSlash, onSubmit }) {
12
- const picker = showCommands || showModels
13
- ? h(Box, { position: 'absolute', top: 1, left: 2, backgroundColor: hex.bg, flexDirection: 'column' },
14
- showCommands ? h(CommandPicker, { onSelect: onCommandSelect, onClose: onCloseCommands }) : null,
15
- showModels ? h(ModelPicker, { onSelect: onModelSelect, onClose: onCloseModels }) : null
16
- )
17
- : null;
18
-
19
- return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, width: '100%', height: '100%' },
20
- h(HeaderBar, { model, provider, agentMode: state.agentMode, thinking: state.thinking }),
21
- h(Box, { flexGrow: 1, flexDirection: 'column', position: 'relative' },
22
- h(MessageList, {
23
- messages: state.messages, thinking: state.thinking,
24
- streamContent, agentStatus: state.agentStatus,
25
- }),
26
- picker
27
- ),
28
- h(InputBar, { provider, model, agentMode: state.agentMode, thinking: state.thinking, onSlash, onSubmit })
29
- );
30
- }
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- const { createElement: h } = React;
5
-
6
- export function LoadingIndicator({ label }) {
7
- return h(Box, { height: 1, backgroundColor: hex.surface },
8
- h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' \u25CF'),
9
- h(Text, { color: hex.textDim, backgroundColor: hex.surface }, ' ' + (label || 'processing'))
10
- );
11
- }
@@ -1,92 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import { Box, Text } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- import { getLayout, sliceToViewport, buildLineArray } from '../config/layout.js';
5
- const { createElement: h } = React;
6
-
7
- function Line({ type, text, data }) {
8
- switch (type) {
9
- case 'user_head':
10
- return h(Box, { height: 1, backgroundColor: hex.userBg },
11
- h(Text, { color: hex.accent, bold: true, backgroundColor: hex.userBg }, ' \u276F YOU')
12
- );
13
- case 'user_line':
14
- return h(Box, { height: 1, backgroundColor: hex.userBg },
15
- h(Text, { color: hex.text, backgroundColor: hex.userBg, wrap: 'wrap' }, ' ' + (text || ' '))
16
- );
17
- case 'asst_head':
18
- return h(Box, { height: 1, backgroundColor: hex.surface },
19
- h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, ' \u25C6 CLARITY')
20
- );
21
- case 'asst_line':
22
- return h(Box, { height: 1, backgroundColor: hex.surface },
23
- h(Text, { color: hex.text, backgroundColor: hex.surface, wrap: 'wrap' }, ' \u2502 ' + (text || ' '))
24
- );
25
- case 'asst_foot':
26
- return h(Box, { height: 1, backgroundColor: hex.surface },
27
- h(Text, { color: hex.textDim, backgroundColor: hex.surface },
28
- ' \u25B8 ' + (parseInt(text) < 1000 ? text + 'ms' : (parseInt(text) / 1000).toFixed(1) + 's'))
29
- );
30
- case 'tool_line':
31
- return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
32
- h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' \u25C9 ' + (text || ''))
33
- );
34
- case 'sys_line':
35
- return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
36
- h(Text, { color: hex.green, backgroundColor: hex.surfaceAlt }, ' \u25C9 ' + (text || ''))
37
- );
38
- case 'err_line':
39
- return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
40
- h(Text, { color: hex.red, backgroundColor: hex.surfaceAlt }, ' \u2716 ' + (text || ''))
41
- );
42
- case 'stream_head':
43
- return h(Box, { height: 1, backgroundColor: hex.surface },
44
- h(Text, { color: hex.purple, bold: true, backgroundColor: hex.surface }, ' \u25C6 CLARITY')
45
- );
46
- case 'stream_status':
47
- return h(Box, { height: 1, backgroundColor: hex.surface },
48
- h(Text, { color: hex.blue, backgroundColor: hex.surface }, ' \u25CF ' + (text || ''))
49
- );
50
- case 'stream_line':
51
- return h(Box, { height: 1, backgroundColor: hex.surface },
52
- h(Text, { color: hex.text, backgroundColor: hex.surface, wrap: 'wrap' }, ' \u2502 ' + (text || ' '))
53
- );
54
- default:
55
- return null;
56
- }
57
- }
58
-
59
- export function MessageList({ messages, thinking, streamContent, agentStatus }) {
60
- const { viewport, contentWidth } = getLayout();
61
-
62
- const entries = useMemo(() => {
63
- return messages.map(m => ({
64
- id: m.id, role: m.role, content: m.content,
65
- duration: m.duration, toolName: m.toolName, error: m.error, completed: true,
66
- }));
67
- }, [messages]);
68
-
69
- const { slice, clipIndex, clipLines } = useMemo(
70
- () => sliceToViewport(entries, viewport, contentWidth),
71
- [entries, viewport, contentWidth]
72
- );
73
-
74
- const rawLines = useMemo(
75
- () => buildLineArray(slice, clipIndex, clipLines, contentWidth),
76
- [slice, clipIndex, clipLines, contentWidth]
77
- );
78
-
79
- const fillCount = Math.max(0, viewport - rawLines.length);
80
- const padded = [];
81
- for (let i = 0; i < fillCount; i++) padded.push({ type: 'empty' });
82
- for (const ln of rawLines) padded.push(ln);
83
-
84
- return h(Box, { height: viewport, flexDirection: 'column', overflow: 'hidden' },
85
- padded.map((ln, i) => {
86
- if (ln.type === 'empty') return h(Box, { key: 'e' + i, height: 1, backgroundColor: hex.bg });
87
- return h(Box, { key: (ln.data?.id || 'r') + '-' + i, height: 1 },
88
- h(Line, { type: ln.type, text: ln.text, data: ln.data })
89
- );
90
- })
91
- );
92
- }
@@ -1,83 +0,0 @@
1
- import React, { useState, useMemo } from 'react';
2
- import { Box, Text, useInput } from 'ink';
3
- import { ALL_MODELS } from '../config/models.js';
4
- import { hex } from '../config/theme.js';
5
- import { getLayout } from '../config/layout.js';
6
- const { createElement: h } = React;
7
-
8
- export function ModelPicker({ onSelect, onClose }) {
9
- const [search, setSearch] = useState('');
10
- const [idx, setIdx] = useState(0);
11
- const { cols } = getLayout();
12
-
13
- const flat = useMemo(() => {
14
- const q = search.toLowerCase();
15
- const filtered = ALL_MODELS.filter(m =>
16
- m.id.toLowerCase().includes(q) || m.label.toLowerCase().includes(q)
17
- );
18
- const groups = {};
19
- for (const m of filtered) {
20
- if (!groups[m.provider]) groups[m.provider] = [];
21
- groups[m.provider].push(m);
22
- }
23
- const list = [];
24
- for (const [provider, models] of Object.entries(groups)) {
25
- list.push({ _header: provider, _provider: provider });
26
- for (const m of models) list.push(m);
27
- }
28
- return list;
29
- }, [search]);
30
-
31
- useInput((input, key) => {
32
- if (key.upArrow) setIdx(i => Math.max(0, i - 1));
33
- if (key.downArrow) setIdx(i => Math.min(flat.length - 1, i + 1));
34
- if (key.return) { const m = flat[idx]; if (m && !m._header) onSelect(m.id); return; }
35
- if (key.escape) onClose();
36
- if (key.backspace) setSearch(s => s.slice(0, -1));
37
- else if (input && !key.ctrl && !key.meta) setSearch(s => s + input);
38
- });
39
-
40
- const w = Math.min(cols - 4, 52);
41
-
42
- return h(Box, { flexDirection: 'column', backgroundColor: hex.bg, width: w },
43
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
44
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
45
- '\u250C' + '\u2500'.repeat(w - 2) + '\u2510')
46
- ),
47
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
48
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
49
- '\u2502 Models ' + flat.filter(m => !m._header).length + ' available' + ' '.repeat(Math.max(0, w - 18)) + '\u2502')
50
- ),
51
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
52
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
53
- '\u2502 ' + (search || '\u2026') + ' '.repeat(Math.max(0, w - 6)) + '\u2502')
54
- ),
55
- flat.map((m, i) => {
56
- if (m._header) {
57
- return h(Box, { key: 'h-' + m._provider, height: 1, backgroundColor: hex.surfaceAlt },
58
- h(Text, { color: hex.blue, bold: true, backgroundColor: hex.surfaceAlt },
59
- '\u2502 ' + m._provider.toUpperCase() + ' '.repeat(Math.max(0, w - m._provider.length - 5)) + '\u2502')
60
- );
61
- }
62
- const isSel = i === idx;
63
- return h(Box, {
64
- key: m.id, height: 1,
65
- backgroundColor: isSel ? hex.selectionBg : hex.bg,
66
- },
67
- h(Text, {
68
- color: isSel ? hex.selectionText : hex.text,
69
- bold: isSel,
70
- backgroundColor: isSel ? hex.selectionBg : hex.bg,
71
- }, (isSel ? '\u276F ' : ' ') + m.label + (m.badge ? ' [' + m.badge + ']' : ''))
72
- );
73
- }),
74
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
75
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
76
- '\u2514' + '\u2500'.repeat(w - 2) + '\u2518')
77
- ),
78
- h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
79
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
80
- ' \u2191\u2193 nav \u2192 select Esc close')
81
- ),
82
- );
83
- }
@@ -1,19 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- import { getLayout } from '../config/layout.js';
5
- const { createElement: h } = React;
6
-
7
- export function StatusBar({ model, provider, agentMode, thinking }) {
8
- const { cols } = getLayout();
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
-
14
- return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
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 + ' ')
18
- );
19
- }
@@ -1,27 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Box, Text } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- const { createElement: h } = React;
5
-
6
- export function ThinkingBlock({ toolResults, duration, collapsed: initialCollapsed }) {
7
- const [collapsed, setCollapsed] = useState(initialCollapsed !== undefined ? initialCollapsed : true);
8
- const items = toolResults || [];
9
- const durStr = duration
10
- ? (duration < 1000 ? duration + 'ms' : (duration / 1000).toFixed(1) + 's')
11
- : '';
12
-
13
- return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
14
- h(Box, { height: 1 },
15
- h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt },
16
- ' ' + (collapsed ? '\u25B8' : '\u25BE') + ' Thought' + (durStr ? ' (' + durStr + ')' : ''))
17
- ),
18
- collapsed ? null : h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
19
- items.map((tr, i) =>
20
- h(Box, { key: tr.execId || i, height: 1 },
21
- h(Text, { color: hex.textMuted, backgroundColor: hex.surfaceAlt },
22
- ' ' + (i === items.length - 1 ? '\u2570\u2500' : '\u256D\u2500') + ' ' + (tr.status === 'failed' ? '\u2716' : '\u25C9') + ' ' + tr.name + (tr.duration ? ' ' + tr.duration + 'ms' : ''))
23
- )
24
- )
25
- )
26
- );
27
- }
@@ -1,36 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { hex } from '../config/theme.js';
4
- import { truncate } from '../config/layout.js';
5
- const { createElement: h } = React;
6
-
7
- export function ToolCard({ exec, isActive }) {
8
- const name = exec.name || 'tool';
9
- const status = exec.status || 'running';
10
- const dur = exec.duration ? (exec.duration < 1000 ? exec.duration + 'ms' : (exec.duration / 1000).toFixed(1) + 's') : '';
11
- const args = typeof exec.args === 'string' ? exec.args : JSON.stringify(exec.args || {});
12
- const isDone = !isActive && (status === 'completed' || status === 'failed');
13
-
14
- if (isDone) {
15
- const c = status === 'failed' ? hex.red : hex.green;
16
- return h(Box, { height: 1, backgroundColor: hex.surfaceAlt },
17
- h(Text, { color: c, backgroundColor: hex.surfaceAlt }, ' ' + (status === 'failed' ? '\u2716' : '\u25C9') + ' ' + name + (dur ? ' ' + dur : ''))
18
- );
19
- }
20
-
21
- return h(Box, { flexDirection: 'column', backgroundColor: hex.surfaceAlt },
22
- h(Box, { height: 1 },
23
- h(Text, { color: hex.purple, backgroundColor: hex.surfaceAlt }, ' \u25C9 ' + name + (dur ? ' ' + dur : ''))
24
- ),
25
- args.length < 80
26
- ? h(Box, { height: 1 },
27
- h(Text, { color: hex.textDim, backgroundColor: hex.surfaceAlt }, ' \u25B8 ' + truncate(args, 56))
28
- )
29
- : null,
30
- status === 'running'
31
- ? h(Box, { height: 1 },
32
- h(Text, { color: hex.blue, backgroundColor: hex.surfaceAlt }, ' \u25CF running')
33
- )
34
- : null,
35
- );
36
- }