klyro 0.1.36 → 0.1.37
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/dist/tui/app.d.ts +2 -2
- package/dist/tui/app.js +121 -148
- package/dist/tui/app.test.js +4 -3
- package/dist/tui/snapshot.test.js +5 -5
- package/dist/tui/tokens.d.ts +53 -34
- package/dist/tui/tokens.js +54 -40
- package/package.json +1 -1
package/dist/tui/app.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Klyro TUI —
|
|
3
|
-
*
|
|
2
|
+
* Klyro TUI — design.md Claude Code-like — White & Orange #FF6B1A — 3 columns
|
|
3
|
+
* Top bar + Sidebar 28 + Chat flex + Inspector 36 + Prompt — per plan 1-10
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import type { StatusSnapshot } from './status.js';
|
package/dist/tui/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* Klyro TUI —
|
|
4
|
-
*
|
|
3
|
+
* Klyro TUI — design.md Claude Code-like — White & Orange #FF6B1A — 3 columns
|
|
4
|
+
* Top bar + Sidebar 28 + Chat flex + Inspector 36 + Prompt — per plan 1-10
|
|
5
5
|
*/
|
|
6
6
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
7
7
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
@@ -10,33 +10,62 @@ import { parse as parseSlash } from '../cli/slash/parser.js';
|
|
|
10
10
|
import { tokens, g } from './tokens.js';
|
|
11
11
|
let _id = 0;
|
|
12
12
|
function nextId(p) { _id++; return `${p}-${_id}`; }
|
|
13
|
-
|
|
13
|
+
// Top bar §3
|
|
14
|
+
function TopBar({ width, cwd, model }) {
|
|
14
15
|
const branch = (() => { try {
|
|
15
16
|
const { execSync } = require('node:child_process');
|
|
16
17
|
return execSync('git rev-parse --abbrev-ref HEAD', { cwd, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
17
18
|
}
|
|
18
19
|
catch {
|
|
19
|
-
return '';
|
|
20
|
+
return 'main';
|
|
20
21
|
} })();
|
|
21
|
-
const
|
|
22
|
-
return (_jsxs(Box, {
|
|
22
|
+
const showFull = width >= 120;
|
|
23
|
+
return (_jsxs(Box, { width: width, justifyContent: "space-between", borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.colors.orange, bold: true, children: [g('logoBar'), " claude-code"] }), _jsxs(Text, { color: tokens.colors.fgDim, children: [" \u2502 ", branch, " \u2502"] }), _jsx(Text, { color: tokens.colors.orange, children: " \u25C9 3 sessions " }), _jsx(Text, { color: tokens.colors.fgDim, children: "\u2502 \u2318 palette \u2502 ? help" })] }), _jsxs(Box, { children: [showFull ? _jsx(Text, { color: tokens.colors.orange, children: "\u25CF\u25CF\u25CF \u25CF\u25CF " }) : null, _jsx(Text, { color: tokens.colors.fgDim, children: "\u229E \u25A2" })] })] }));
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// Sidebar §4
|
|
26
|
+
function Sidebar({ width }) {
|
|
27
|
+
if (width < 60)
|
|
28
|
+
return null;
|
|
29
|
+
const w = Math.min(28, Math.floor(width * 0.22));
|
|
30
|
+
return (_jsxs(Box, { flexDirection: "column", width: w, borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, marginRight: 1, children: [_jsx(Text, { bold: true, color: tokens.colors.fg, children: "\u25BE Sessions" }), _jsxs(Text, { color: tokens.colors.orange, children: ["\u25C9 Build auth flow ", _jsx(Text, { color: tokens.colors.fgDim, children: "2m" })] }), _jsx(Text, { color: tokens.colors.fgDim, children: "\u25CB Refactor parser 12m" }), _jsx(Text, { color: tokens.colors.fgDim, children: "\u25CB Fix bug #42 1h" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, color: tokens.colors.fg, children: "\u25B8 Files" }) }), _jsx(Text, { color: tokens.colors.fgDim, children: "\u25B8 Agents" }), _jsx(Text, { color: tokens.colors.fgDim, children: "\u25B8 MCP" }), _jsx(Text, { color: tokens.colors.fgDim, children: "\u25B8 Hooks" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: tokens.colors.orange, children: "[+ new]" }) })] }));
|
|
31
|
+
}
|
|
32
|
+
// Inspector §6
|
|
33
|
+
function Inspector({ width }) {
|
|
34
|
+
if (width < 80)
|
|
35
|
+
return null;
|
|
36
|
+
const w = 36;
|
|
37
|
+
return (_jsxs(Box, { flexDirection: "column", width: w, borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, marginLeft: 1, children: [_jsx(Text, { bold: true, color: tokens.colors.fg, children: "Inspector \u00B7 Tools" }), _jsx(Text, { color: tokens.colors.success, children: "\u2713 edit_file src/routes.rs" }), _jsx(Text, { color: tokens.colors.fgDim, children: " +12 / -3 0.3s" }), _jsx(Text, { children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Text, { color: tokens.colors.success, children: "\u2713 read_file Cargo.toml" }), _jsx(Text, { color: tokens.colors.fgDim, children: " 890 bytes 0.1s" }), _jsx(Text, { children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Text, { color: tokens.colors.warning, children: "\u23F3 bash cargo test" }), _jsx(Text, { color: tokens.colors.fgDim, children: " running... 2.1s" })] }));
|
|
38
|
+
}
|
|
39
|
+
// Chat bubbles §5.1-5.4
|
|
40
|
+
function MarkdownText({ text }) {
|
|
41
|
+
// minimal: **bold** → bold, keep rest
|
|
42
|
+
const parts = [];
|
|
43
|
+
let last = 0;
|
|
44
|
+
const re = /\*\*(.+?)\*\*/g;
|
|
45
|
+
let m;
|
|
46
|
+
let i = 0;
|
|
47
|
+
while ((m = re.exec(text))) {
|
|
48
|
+
if (m.index > last)
|
|
49
|
+
parts.push(_jsx(Text, { wrap: "wrap", children: text.slice(last, m.index) }, `t-${i++}`));
|
|
50
|
+
parts.push(_jsx(Text, { bold: true, wrap: "wrap", children: m[1] }, `b-${i++}`));
|
|
51
|
+
last = m.index + m[0].length;
|
|
52
|
+
}
|
|
53
|
+
if (last < text.length)
|
|
54
|
+
parts.push(_jsx(Text, { wrap: "wrap", children: text.slice(last) }, `t-${i++}`));
|
|
55
|
+
return _jsx(Text, { wrap: "wrap", children: parts.length ? parts : text });
|
|
56
|
+
}
|
|
57
|
+
function verbForTool(n) {
|
|
58
|
+
if (n === 'read_file')
|
|
26
59
|
return 'Read';
|
|
27
|
-
if (
|
|
60
|
+
if (n === 'list_directory')
|
|
28
61
|
return 'Listed';
|
|
29
|
-
if (
|
|
62
|
+
if (n === 'grep' || n === 'glob' || n.includes('search'))
|
|
30
63
|
return 'Searched';
|
|
31
|
-
if (
|
|
64
|
+
if (n === 'shell_exec')
|
|
32
65
|
return 'Ran';
|
|
33
|
-
if (
|
|
66
|
+
if (n.startsWith('git_'))
|
|
34
67
|
return 'Checked git';
|
|
35
|
-
if (
|
|
36
|
-
return 'Fetched';
|
|
37
|
-
if (name === 'web_search')
|
|
38
|
-
return 'Searched web';
|
|
39
|
-
if (name === 'edit_file' || name === 'multi_edit' || name === 'apply_patch' || name === 'write_file')
|
|
68
|
+
if (n === 'edit_file' || n.includes('edit') || n.includes('write'))
|
|
40
69
|
return 'Edited';
|
|
41
70
|
return 'Called';
|
|
42
71
|
}
|
|
@@ -44,7 +73,7 @@ function groupTools(items) {
|
|
|
44
73
|
const out = [];
|
|
45
74
|
let cur = [];
|
|
46
75
|
const flush = () => {
|
|
47
|
-
if (cur.length
|
|
76
|
+
if (!cur.length)
|
|
48
77
|
return;
|
|
49
78
|
const byVerb = new Map();
|
|
50
79
|
for (const it of cur) {
|
|
@@ -55,7 +84,7 @@ function groupTools(items) {
|
|
|
55
84
|
}
|
|
56
85
|
for (const [verb, list] of byVerb) {
|
|
57
86
|
const totalMs = list.reduce((s, x) => s + (x.latencyMs ?? 0), 0);
|
|
58
|
-
const status = list.some(
|
|
87
|
+
const status = list.some(x => x.isError || x.status === 'error') ? 'error' : list.some(x => x.status === 'running') ? 'running' : 'done';
|
|
59
88
|
out.push({ id: nextId('g'), verb, items: list, totalMs, status });
|
|
60
89
|
}
|
|
61
90
|
cur = [];
|
|
@@ -71,27 +100,6 @@ function groupTools(items) {
|
|
|
71
100
|
flush();
|
|
72
101
|
return out;
|
|
73
102
|
}
|
|
74
|
-
// Simple markdown: **bold** → bold, keep lists/tables, wrap at word boundaries
|
|
75
|
-
function MarkdownText({ text, dim, width }) {
|
|
76
|
-
// Split by **bold** segments
|
|
77
|
-
const parts = [];
|
|
78
|
-
let last = 0;
|
|
79
|
-
const re = /\*\*(.+?)\*\*/g;
|
|
80
|
-
let m;
|
|
81
|
-
let idx = 0;
|
|
82
|
-
while ((m = re.exec(text))) {
|
|
83
|
-
if (m.index > last)
|
|
84
|
-
parts.push(_jsx(Text, { color: dim ? tokens.colors.dim : undefined, wrap: "wrap", children: text.slice(last, m.index) }, `t-${idx++}`));
|
|
85
|
-
parts.push(_jsx(Text, { bold: true, color: dim ? undefined : tokens.colors.soft, wrap: "wrap", children: m[1] }, `b-${idx++}`));
|
|
86
|
-
last = m.index + m[0].length;
|
|
87
|
-
}
|
|
88
|
-
if (last < text.length)
|
|
89
|
-
parts.push(_jsx(Text, { color: dim ? tokens.colors.dim : undefined, wrap: "wrap", children: text.slice(last) }, `t-${idx++}`));
|
|
90
|
-
if (parts.length === 0)
|
|
91
|
-
return _jsx(Text, { color: dim ? tokens.colors.dim : undefined, wrap: "wrap", children: text });
|
|
92
|
-
// Render as single line with bold segments — Ink will wrap the parent Box
|
|
93
|
-
return _jsx(Text, { wrap: "wrap", children: parts });
|
|
94
|
-
}
|
|
95
103
|
export function App(props) {
|
|
96
104
|
const { stdout } = useStdout();
|
|
97
105
|
const [transcript, setTranscript] = useState(props.initialTranscript ?? []);
|
|
@@ -105,74 +113,60 @@ export function App(props) {
|
|
|
105
113
|
const [expandedGroups, setExpandedGroups] = useState(new Set());
|
|
106
114
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
107
115
|
const streamingIdRef = useRef(null);
|
|
108
|
-
const width = stdout?.columns ??
|
|
116
|
+
const width = stdout?.columns ?? 120;
|
|
109
117
|
const height = stdout?.rows ?? 30;
|
|
110
118
|
const isFullscreen = props.isFullscreen ?? false;
|
|
111
119
|
const grouped = groupTools(transcript);
|
|
112
120
|
const viewportH = Math.max(5, height - 10);
|
|
113
|
-
const
|
|
114
|
-
const totalRows = grouped.reduce((sum, it) => {
|
|
115
|
-
if (it.verb)
|
|
116
|
-
return sum + 1 + ((it.items.length > 1 && expandedGroups.has(it.id)) ? it.items.length : 0);
|
|
117
|
-
const t = it;
|
|
118
|
-
if (t.kind === 'text')
|
|
119
|
-
return sum + estLines(t.text) + 1;
|
|
120
|
-
return sum + 2;
|
|
121
|
-
}, 0) + (plan.length > 0 ? plan.length + 1 : 0) + 2;
|
|
121
|
+
const totalRows = grouped.reduce((s, it) => s + (it.verb ? 1 + (it.items.length > 1 && expandedGroups.has(it.id) ? it.items.length : 0) : 2), 0) + (plan.length ? 1 : 0) + 2;
|
|
122
122
|
const maxOffset = Math.max(0, totalRows - viewportH);
|
|
123
|
-
const isAtBottom = scrollOffset >= maxOffset;
|
|
124
123
|
const trackH = viewportH;
|
|
125
|
-
const thumbPos = maxOffset === 0 ? 0 : Math.round(
|
|
124
|
+
const thumbPos = maxOffset === 0 ? 0 : Math.round(scrollOffset / maxOffset * (trackH - 1));
|
|
126
125
|
const visibleGrouped = isFullscreen ? grouped.slice(scrollOffset, scrollOffset + viewportH) : grouped;
|
|
127
|
-
|
|
128
|
-
useEffect(() =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}, [queuedInputs, status.status, awaitingApproval]);
|
|
126
|
+
const placeholder = 'Message Klyro…';
|
|
127
|
+
useEffect(() => bridge.subscribe(p => setAwaitingApproval(p !== null)), [bridge]);
|
|
128
|
+
useEffect(() => { if (queuedInputs.length > 0 && status.status !== 'running' && !awaitingApproval) {
|
|
129
|
+
const toSend = queuedInputs[0];
|
|
130
|
+
setQueuedInputs(p => p.slice(1));
|
|
131
|
+
setTranscript(p => [...p, { id: nextId('user'), kind: 'text', text: toSend, role: 'user' }]);
|
|
132
|
+
streamingIdRef.current = null;
|
|
133
|
+
const cmd = parseSlash(toSend.trim());
|
|
134
|
+
if (cmd.kind === 'prompt')
|
|
135
|
+
void props.onPrompt(cmd.text);
|
|
136
|
+
else
|
|
137
|
+
void props.onSlash(cmd);
|
|
138
|
+
} }, [queuedInputs, status.status, awaitingApproval]);
|
|
141
139
|
useEffect(() => { if (status.status !== 'running')
|
|
142
140
|
return; const start = Date.now() - elapsed; const t = setInterval(() => setElapsed(Date.now() - start), 1000); return () => clearInterval(t); }, [status.status, elapsed]);
|
|
143
|
-
useEffect(() => { if (
|
|
144
|
-
setScrollOffset(maxOffset); }, [transcript.length, plan.length, maxOffset
|
|
141
|
+
useEffect(() => { if (scrollOffset >= maxOffset)
|
|
142
|
+
setScrollOffset(maxOffset); }, [transcript.length, plan.length, maxOffset]);
|
|
145
143
|
const append = useCallback((item) => { if (item.kind !== 'text' || item.role !== 'assistant')
|
|
146
|
-
streamingIdRef.current = null; setTranscript(
|
|
147
|
-
const appendDelta = useCallback((text) => {
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
streamingIdRef.current = id;
|
|
157
|
-
setTranscript((prev) => [...prev, { id, kind: 'text', text, role: 'assistant' }]);
|
|
158
|
-
}
|
|
159
|
-
}, []);
|
|
144
|
+
streamingIdRef.current = null; setTranscript(p => [...p, item]); }, []);
|
|
145
|
+
const appendDelta = useCallback((text) => { if (!text)
|
|
146
|
+
return; const sid = streamingIdRef.current; if (sid)
|
|
147
|
+
setTranscript(p => { const idx = p.findIndex(x => x.id === sid); if (idx === -1)
|
|
148
|
+
return [...p, { id: sid, kind: 'text', text, role: 'assistant' }]; const cur = p[idx]; const copy = [...p]; copy[idx] = { ...cur, text: cur.text + text }; return copy; });
|
|
149
|
+
else {
|
|
150
|
+
const id = nextId('stream');
|
|
151
|
+
streamingIdRef.current = id;
|
|
152
|
+
setTranscript(p => [...p, { id, kind: 'text', text, role: 'assistant' }]);
|
|
153
|
+
} }, []);
|
|
160
154
|
useEffect(() => { if (status.status !== 'running')
|
|
161
155
|
streamingIdRef.current = null; }, [status.status]);
|
|
162
|
-
const updateStatus = useCallback((s) => setStatus(
|
|
156
|
+
const updateStatus = useCallback((s) => setStatus(p => ({ ...p, ...s })), []);
|
|
163
157
|
const updatePlan = useCallback((p) => setPlan(p), []);
|
|
164
158
|
const onMountedRef = useRef(props.onMounted);
|
|
165
159
|
useEffect(() => { onMountedRef.current = props.onMounted; }, [props.onMounted]);
|
|
166
160
|
useEffect(() => { onMountedRef.current?.({ append, appendDelta, updateStatus, updatePlan }); globalThis.__klyroAppAppend = append; globalThis.__klyroAppendDelta = appendDelta; globalThis.__klyroAppStatus = updateStatus; globalThis.__klyroAppPlan = updatePlan; return () => { delete globalThis.__klyroAppAppend; delete globalThis.__klyroAppendDelta; delete globalThis.__klyroAppStatus; delete globalThis.__klyroAppPlan; }; }, [append, appendDelta, updateStatus, updatePlan]);
|
|
167
|
-
const toggleGroup = (id) => setExpandedGroups(
|
|
161
|
+
const toggleGroup = (id) => setExpandedGroups(p => { const n = new Set(p); if (n.has(id))
|
|
168
162
|
n.delete(id);
|
|
169
163
|
else
|
|
170
164
|
n.add(id); return n; });
|
|
171
|
-
const scrollUp = (n = 3) => setScrollOffset(
|
|
172
|
-
const scrollDown = (n = 3) => setScrollOffset(
|
|
165
|
+
const scrollUp = (n = 3) => setScrollOffset(p => Math.max(0, p - n));
|
|
166
|
+
const scrollDown = (n = 3) => setScrollOffset(p => Math.min(maxOffset, p + n));
|
|
173
167
|
useInput((inputStr, key) => {
|
|
174
168
|
if (key.escape && queuedInputs.length > 0) {
|
|
175
|
-
setQueuedInputs(
|
|
169
|
+
setQueuedInputs(p => p.slice(1));
|
|
176
170
|
return;
|
|
177
171
|
}
|
|
178
172
|
if (key.pageUp || (key.ctrl && inputStr === 'u')) {
|
|
@@ -211,16 +205,16 @@ export function App(props) {
|
|
|
211
205
|
return;
|
|
212
206
|
if (queuedInputs.length >= 3)
|
|
213
207
|
return;
|
|
214
|
-
setQueuedInputs(
|
|
208
|
+
setQueuedInputs(p => [...p, v]);
|
|
215
209
|
setInput('');
|
|
216
210
|
return;
|
|
217
211
|
}
|
|
218
212
|
if (key.backspace || key.delete) {
|
|
219
|
-
setInput(
|
|
213
|
+
setInput(v => v.slice(0, -1));
|
|
220
214
|
return;
|
|
221
215
|
}
|
|
222
216
|
if (!key.ctrl && !key.meta)
|
|
223
|
-
setInput(
|
|
217
|
+
setInput(v => v + inputStr.replace(/\r\n/g, '\n').replace(/\r/g, '\n'));
|
|
224
218
|
return;
|
|
225
219
|
}
|
|
226
220
|
if (key.return) {
|
|
@@ -228,7 +222,7 @@ export function App(props) {
|
|
|
228
222
|
if (!v)
|
|
229
223
|
return;
|
|
230
224
|
setInput('');
|
|
231
|
-
setTranscript(
|
|
225
|
+
setTranscript(p => [...p, { id: nextId('user'), kind: 'text', text: v, role: 'user' }]);
|
|
232
226
|
streamingIdRef.current = null;
|
|
233
227
|
const cmd = parseSlash(v);
|
|
234
228
|
if (cmd.kind === 'prompt')
|
|
@@ -238,92 +232,71 @@ export function App(props) {
|
|
|
238
232
|
return;
|
|
239
233
|
}
|
|
240
234
|
if (key.backspace || key.delete) {
|
|
241
|
-
setInput(
|
|
235
|
+
setInput(v => v.slice(0, -1));
|
|
242
236
|
return;
|
|
243
237
|
}
|
|
244
238
|
if (!key.ctrl && !key.meta)
|
|
245
|
-
setInput(
|
|
239
|
+
setInput(v => v + inputStr);
|
|
246
240
|
});
|
|
247
|
-
const ver = props.version ?? '0.1.
|
|
248
|
-
const rule =
|
|
241
|
+
const ver = props.version ?? '0.1.36';
|
|
242
|
+
const rule = '─'.repeat(Math.max(10, width - 2));
|
|
249
243
|
const cost = (status.usageInput / 1000 * 0.003 + status.usageOutput / 1000 * 0.015);
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
const hints = maxOffset > 0 && isFullscreen ? `${baseHints} · PgUp/Dn scroll` : baseHints;
|
|
254
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, height: isFullscreen ? height - 1 : undefined, children: [_jsx(Header, { cwd: props.cwd, model: status.model, version: ver, width: width }), _jsxs(Box, { flexDirection: "row", flexGrow: isFullscreen ? 1 : 0, overflow: isFullscreen ? 'hidden' : undefined, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: isFullscreen ? 'hidden' : undefined, paddingX: 0, children: [grouped.length === 0 ? (_jsx(Text, { color: tokens.colors.dim, children: "Message Klyro\u2026" })) : visibleGrouped.map((item) => {
|
|
244
|
+
const ctxPct = status.usageInput + status.usageOutput > 0 ? Math.round((status.usageInput + status.usageOutput) / 120_000 * 100) : 0;
|
|
245
|
+
const hints = maxOffset > 0 && isFullscreen ? 'PgUp/Dn scroll · ' : '' + (status.status === 'running' ? 'ctrl+c to stop · enter to queue · ctrl+o expand' : transcript.length === 0 ? 'shift+tab to cycle · ↑↓ for history · / for commands' : 'enter to send · shift+enter newline · @ to attach');
|
|
246
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: isFullscreen ? height - 1 : undefined, children: [_jsx(TopBar, { width: width, cwd: props.cwd, model: status.model }), _jsxs(Box, { flexDirection: "row", flexGrow: isFullscreen ? 1 : 0, overflow: isFullscreen ? 'hidden' : undefined, children: [_jsx(Sidebar, { width: width }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, overflow: isFullscreen ? 'hidden' : undefined, children: [grouped.length === 0 ? _jsx(Text, { color: tokens.colors.fgDim, children: "Message Klyro\u2026" }) : visibleGrouped.map(item => {
|
|
255
247
|
if (item.verb) {
|
|
256
248
|
const gr = item;
|
|
257
249
|
const isExpanded = expandedGroups.has(gr.id);
|
|
258
|
-
const verbLine = (() => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
p = a.path ?? a.pattern ?? a.command?.slice(0, 48) ?? '';
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
p = '';
|
|
268
|
-
}
|
|
269
|
-
if (gr.verb === 'Read' && p)
|
|
270
|
-
return `Read ${p.split('/').pop()}`;
|
|
271
|
-
if (gr.verb === 'Searched' && p)
|
|
272
|
-
return `Searched "${p}"`;
|
|
273
|
-
if (gr.verb === 'Ran' && p)
|
|
274
|
-
return `Ran ${p.split(' ')[0]}`;
|
|
275
|
-
return `${gr.verb} ${p}`;
|
|
250
|
+
const verbLine = (() => { if (gr.items.length === 1) {
|
|
251
|
+
const it = gr.items[0];
|
|
252
|
+
let p = '';
|
|
253
|
+
try {
|
|
254
|
+
const a = JSON.parse(it.args);
|
|
255
|
+
p = a.path ?? a.pattern ?? a.command?.slice(0, 48) ?? '';
|
|
276
256
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
257
|
+
catch { }
|
|
258
|
+
if (gr.verb === 'Read' && p)
|
|
259
|
+
return `Read ${p.split('/').pop()}`;
|
|
260
|
+
if (gr.verb === 'Searched' && p)
|
|
261
|
+
return `Searched "${p}"`;
|
|
262
|
+
if (gr.verb === 'Ran' && p)
|
|
263
|
+
return `Ran ${p.split(' ')[0]}`;
|
|
264
|
+
return `${gr.verb} ${p}`;
|
|
265
|
+
} if (gr.verb === 'Read')
|
|
266
|
+
return `Read ${gr.items.length} files`; if (gr.verb === 'Searched')
|
|
267
|
+
return `Searched ${gr.items.length} patterns`; if (gr.verb === 'Ran')
|
|
268
|
+
return `Ran ${gr.items.length} commands`; return `${gr.verb} ${gr.items.length} items`; })();
|
|
287
269
|
const right = gr.status === 'running' ? `${(elapsed / 1000).toFixed(1)}s` : gr.status === 'error' ? '✗' : `${gr.totalMs}ms`;
|
|
288
270
|
const marker = isExpanded ? '▼' : '✓';
|
|
289
|
-
const markerColor = gr.status === 'error' ? tokens.colors.
|
|
290
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1,
|
|
271
|
+
const markerColor = gr.status === 'error' ? tokens.colors.danger : gr.status === 'running' ? tokens.colors.orange : tokens.colors.success;
|
|
272
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: markerColor, children: [marker, " ", verbLine] }), _jsx(Text, { color: tokens.colors.fgDim, children: right })] }), isExpanded ? gr.items.map(it => {
|
|
291
273
|
let friendly = '';
|
|
292
274
|
try {
|
|
293
275
|
const a = JSON.parse(it.args);
|
|
294
|
-
const p = a.path ??
|
|
295
|
-
|
|
296
|
-
if (it.name === 'read_file' && short)
|
|
297
|
-
friendly = `${short}`;
|
|
298
|
-
else if (it.name === 'shell_exec' && p)
|
|
299
|
-
friendly = `$ ${String(p).slice(0, 40)}`;
|
|
300
|
-
else if (short)
|
|
301
|
-
friendly = short;
|
|
302
|
-
else
|
|
303
|
-
friendly = it.args.slice(0, 40);
|
|
276
|
+
const p = a.path ?? '';
|
|
277
|
+
friendly = p ? p.split('/').pop() ?? p : it.args.slice(0, 40);
|
|
304
278
|
}
|
|
305
279
|
catch {
|
|
306
280
|
friendly = it.args.slice(0, 40);
|
|
307
281
|
}
|
|
308
|
-
return (
|
|
282
|
+
return _jsx(Box, { paddingLeft: 1, children: _jsxs(Text, { color: tokens.colors.fgDim, children: ["\u2514 ", friendly] }) }, it.id);
|
|
309
283
|
}) : null] }, gr.id));
|
|
310
284
|
}
|
|
311
285
|
const it = item;
|
|
312
286
|
if (it.kind === 'text' && it.role === 'user') {
|
|
313
|
-
return _jsxs(Box, { marginBottom: 1,
|
|
287
|
+
return _jsxs(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, children: [_jsx(Text, { color: tokens.colors.fgMuted, children: "You" }), _jsx(Text, { wrap: "wrap", children: it.text })] }, it.id);
|
|
314
288
|
}
|
|
315
289
|
if (it.kind === 'text') {
|
|
316
|
-
|
|
317
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.colors.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: tokens.colors.accent, children: [g('agentBullet'), " Klyro"] })] }), _jsx(Box, { paddingLeft: 2, flexDirection: "column", children: _jsxs(Box, { children: [_jsxs(Text, { color: tokens.colors.dim, children: [" ", g('guide'), " "] }), _jsx(Box, { flexGrow: 1, children: _jsx(MarkdownText, { text: it.text }) })] }) })] }, it.id));
|
|
290
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: tokens.colors.orangeSoft, paddingX: 1, borderLeftColor: tokens.colors.orange, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: tokens.colors.orange, children: "Claude" }), _jsxs(Text, { color: tokens.colors.fgDim, children: [" \u00B7 ", status.model, " ", status.status === 'running' && !streamingIdRef.current ? '◯ thinking' : ''] })] }), _jsx(Box, { marginTop: 1, children: _jsx(MarkdownText, { text: it.text }) })] }, it.id));
|
|
318
291
|
}
|
|
319
292
|
if (it.kind === 'error')
|
|
320
|
-
return _jsx(Box, {
|
|
293
|
+
return _jsx(Box, { borderStyle: "single", borderColor: tokens.colors.danger, paddingX: 1, marginBottom: 1, children: _jsxs(Text, { color: tokens.colors.danger, children: ["\u2717 ", it.message] }) }, it.id);
|
|
321
294
|
if (it.kind === 'policy')
|
|
322
295
|
return null;
|
|
323
296
|
if (it.kind === 'file_changed')
|
|
324
|
-
return _jsx(Box, {
|
|
297
|
+
return _jsx(Box, { borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, marginBottom: 1, children: _jsxs(Text, { color: tokens.colors.fgDim, children: ["\u270E ", it.path, " ", it.op] }) }, it.id);
|
|
325
298
|
if (it.kind === 'diff')
|
|
326
|
-
return
|
|
299
|
+
return _jsxs(Box, { borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, children: it.summary }), it.hunks.map((h, i) => _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tokens.colors.fg, children: h.path }), h.lines.map((l, j) => _jsxs(Text, { wrap: "wrap", color: l.kind === 'add' ? tokens.colors.success : l.kind === 'remove' ? tokens.colors.danger : tokens.colors.fgDim, children: [l.kind === 'add' ? "+ " : l.kind === 'remove' ? "- " : " ", l.text] }, j))] }, i))] }, it.id);
|
|
327
300
|
return null;
|
|
328
|
-
}), status.status === 'running' && !streamingIdRef.current ? (
|
|
301
|
+
}), status.status === 'running' && !streamingIdRef.current ? _jsx(Box, { borderStyle: "single", borderColor: tokens.colors.orangeSoft, paddingX: 1, marginBottom: 1, children: _jsxs(Text, { color: tokens.colors.orange, children: ["\u25EF thinking ", (elapsed / 1000).toFixed(1), "s"] }) }) : null, plan.length > 0 ? _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, marginBottom: 1, children: [_jsxs(Text, { bold: true, children: ["\u25C7 Plan ", plan.filter(p => p.status === 'done').length, "/", plan.length] }), plan.slice(0, 8).map(p => _jsxs(Text, { color: p.status === 'done' ? tokens.colors.success : p.status === 'in_progress' ? tokens.colors.orange : tokens.colors.fgDim, children: [p.status === 'done' ? '✓' : p.status === 'in_progress' ? '●' : '○', " ", p.title] }, p.id))] }) : null, queuedInputs.length > 0 ? _jsx(Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: queuedInputs.map((q, i) => _jsxs(Text, { color: tokens.colors.fgDim, children: ["queued: ", q.slice(0, 60), i === 0 ? ' esc to drop' : ''] }, i)) }) : null] }), _jsx(Inspector, { width: width }), isFullscreen ? _jsx(Box, { flexDirection: "column", width: 1, marginLeft: 1, children: Array.from({ length: trackH }).map((_, i) => _jsx(Text, { color: i === thumbPos ? tokens.colors.orange : tokens.colors.border, children: i === thumbPos ? '●' : '│' }, i)) }) : null] }), _jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: tokens.colors.border, paddingX: 1, children: _jsxs(Box, { children: [_jsx(Text, { color: tokens.colors.orange, children: "\u258C " }), _jsxs(Text, { wrap: "wrap", children: [input || _jsx(Text, { color: tokens.colors.fgDim, children: "Type a message\u2026 \u23CE send \u21E7\u23CE newline /" }), "\u258F"] })] }) }), _jsxs(Box, { justifyContent: "space-between", paddingX: 1, children: [_jsx(Text, { color: tokens.colors.fgDim, children: hints }), _jsxs(Text, { color: tokens.colors.fgDim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ` : ' ', _jsx(Text, { color: tokens.colors.orange, children: "\u25CF\u25CF\u25CF \u25CF\u25CF" })] })] })] }));
|
|
329
302
|
}
|
package/dist/tui/app.test.js
CHANGED
|
@@ -6,8 +6,9 @@ describe('App', () => {
|
|
|
6
6
|
it('shows the empty-state hint and the status line', () => {
|
|
7
7
|
const { lastFrame } = render(_jsx(App, { initialModel: "mock", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { } }));
|
|
8
8
|
const out = lastFrame();
|
|
9
|
-
|
|
10
|
-
expect(out).toMatch(/Message Klyro
|
|
9
|
+
// design.md: top bar shows claude-code + sessions, center shows Message Klyro placeholder
|
|
10
|
+
expect(out).toMatch(/claude-code|Sessions|Message Klyro/i);
|
|
11
|
+
expect(out).toMatch(/Message Klyro|Type a message/i);
|
|
11
12
|
});
|
|
12
13
|
it('renders initial transcript items', () => {
|
|
13
14
|
const items = [
|
|
@@ -16,7 +17,7 @@ describe('App', () => {
|
|
|
16
17
|
const { lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { }, initialTranscript: items }));
|
|
17
18
|
const frame = lastFrame();
|
|
18
19
|
expect(frame).toContain('seed');
|
|
19
|
-
expect(frame).toMatch(
|
|
20
|
+
expect(frame).toMatch(/You|seed|›|>/);
|
|
20
21
|
});
|
|
21
22
|
it('honors initialStatus overrides', () => {
|
|
22
23
|
const overrides = { step: 5, repairs: 3, status: 'running' };
|
|
@@ -16,11 +16,11 @@ describe('App visual snapshot', () => {
|
|
|
16
16
|
it('renders header + statusline + transcript + input at idle', () => {
|
|
17
17
|
const { lastFrame } = render(_jsx(App, { ...DEFAULT_PROPS, initialModel: "gpt-4o-mini", maxSteps: 30, initialStatus: { status: 'idle', model: 'gpt-4o-mini', step: 0, maxSteps: 30, usageInput: 0, usageOutput: 0, repairs: 0 } }));
|
|
18
18
|
const frame = lastFrame();
|
|
19
|
-
|
|
20
|
-
expect(frame).
|
|
21
|
-
expect(frame).
|
|
22
|
-
expect(frame).toMatch(/shift\+tab|for history|Message Klyro/);
|
|
23
|
-
expect(frame).toMatch(/
|
|
19
|
+
// design.md: top bar shows ▌ claude-code, not KLYRO, and sessions/files
|
|
20
|
+
expect(frame).toMatch(/claude-code|KLYRO/i);
|
|
21
|
+
expect(frame).toMatch(/demo|Sessions|Files/i);
|
|
22
|
+
expect(frame).toMatch(/shift\+tab|for history|Message Klyro|Type a message/i);
|
|
23
|
+
expect(frame).toMatch(/Message Klyro|claude-code|>/i);
|
|
24
24
|
});
|
|
25
25
|
it('renders a transcript with assistant text', () => {
|
|
26
26
|
const { lastFrame } = render(_jsx(App, { ...DEFAULT_PROPS, initialModel: "gpt-4o-mini", maxSteps: 30, initialStatus: { status: 'idle', model: 'gpt-4o-mini', step: 0, maxSteps: 30, usageInput: 0, usageOutput: 0, repairs: 0 }, initialTranscript: [
|
package/dist/tui/tokens.d.ts
CHANGED
|
@@ -1,38 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* §2
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* design.md §2 — White & Orange (light default) + Dark mirror
|
|
3
|
+
* Light: bg #FFFFFF orange #FF6B1A — Dark: bg #1A1A1A orange #FF8A3D
|
|
4
|
+
* Only orange is saturated in chrome; content uses dim borders.
|
|
5
5
|
*/
|
|
6
6
|
export declare const tokens: {
|
|
7
7
|
readonly colors: {
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
13
|
-
readonly
|
|
14
|
-
readonly
|
|
15
|
-
readonly
|
|
16
|
-
readonly
|
|
17
|
-
readonly
|
|
18
|
-
readonly
|
|
8
|
+
readonly bg: "#FFFFFF";
|
|
9
|
+
readonly bgElevated: "#FAF7F2";
|
|
10
|
+
readonly bgSubtle: "#F5F0E8";
|
|
11
|
+
readonly fg: "#1A1A1A";
|
|
12
|
+
readonly fgMuted: "#6B6B6B";
|
|
13
|
+
readonly fgDim: "#9A9A9A";
|
|
14
|
+
readonly orange: "#FF6B1A";
|
|
15
|
+
readonly orangeHot: "#FF8A3D";
|
|
16
|
+
readonly orangeDeep: "#CC4F0A";
|
|
17
|
+
readonly orangeSoft: "#FFF1E6";
|
|
18
|
+
readonly border: "#E8E0D2";
|
|
19
|
+
readonly borderStrong: "#1A1A1A";
|
|
20
|
+
readonly success: "#2E7D32";
|
|
21
|
+
readonly warning: "#C77700";
|
|
22
|
+
readonly danger: "#C62828";
|
|
23
|
+
readonly codeBg: "#FAF7F2";
|
|
24
|
+
readonly codeBorder: "#E8E0D2";
|
|
25
|
+
readonly guide: "#E8E0D2";
|
|
26
|
+
readonly darkBg: "#1A1A1A";
|
|
27
|
+
readonly darkOrange: "#FF8A3D";
|
|
28
|
+
readonly darkBorder: "#333333";
|
|
19
29
|
};
|
|
20
30
|
readonly ansi: {
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
23
|
-
readonly
|
|
24
|
-
readonly soft: "white";
|
|
31
|
+
readonly bg: "white";
|
|
32
|
+
readonly fg: "black";
|
|
33
|
+
readonly fgMuted: "gray";
|
|
25
34
|
readonly dim: "gray";
|
|
26
|
-
readonly
|
|
27
|
-
readonly
|
|
28
|
-
readonly err: "red";
|
|
29
|
-
readonly warn: "yellowBright";
|
|
30
|
-
readonly info: "white";
|
|
35
|
+
readonly orange: "yellow";
|
|
36
|
+
readonly orangeBright: "red";
|
|
31
37
|
readonly border: "gray";
|
|
38
|
+
readonly guide: "gray";
|
|
39
|
+
readonly success: "green";
|
|
40
|
+
readonly warning: "yellow";
|
|
41
|
+
readonly danger: "red";
|
|
42
|
+
readonly accent: "yellow";
|
|
43
|
+
readonly accentBold: "yellowBright";
|
|
44
|
+
readonly soft: "white";
|
|
32
45
|
readonly muted: "gray";
|
|
33
|
-
readonly success: "yellowBright";
|
|
34
46
|
readonly error: "red";
|
|
35
|
-
readonly
|
|
47
|
+
readonly ok: "green";
|
|
48
|
+
readonly warn: "yellow";
|
|
49
|
+
readonly info: "blue";
|
|
50
|
+
readonly successAlt: "green";
|
|
36
51
|
};
|
|
37
52
|
};
|
|
38
53
|
export declare const glyphs: {
|
|
@@ -54,15 +69,13 @@ export declare const glyphs: {
|
|
|
54
69
|
readonly todoActive: "●";
|
|
55
70
|
readonly todoDone: "✓";
|
|
56
71
|
readonly todoPlan: "◇";
|
|
57
|
-
readonly
|
|
58
|
-
readonly
|
|
59
|
-
readonly
|
|
60
|
-
readonly
|
|
61
|
-
readonly
|
|
62
|
-
readonly
|
|
63
|
-
readonly
|
|
64
|
-
readonly meterEmpty: "▱";
|
|
65
|
-
readonly continuation: "↪";
|
|
72
|
+
readonly logoBar: "▌";
|
|
73
|
+
readonly dotFilled: "●";
|
|
74
|
+
readonly dotEmpty: "○";
|
|
75
|
+
readonly dotHalf: "◐";
|
|
76
|
+
readonly sidebar: "⊞";
|
|
77
|
+
readonly inspector: "▢";
|
|
78
|
+
readonly copy: "⧉";
|
|
66
79
|
readonly brand: "◆";
|
|
67
80
|
readonly compaction: "⟲";
|
|
68
81
|
};
|
|
@@ -85,6 +98,10 @@ export declare const glyphAscii: {
|
|
|
85
98
|
readonly todoActive: "[>]";
|
|
86
99
|
readonly todoDone: "[x]";
|
|
87
100
|
readonly todoPlan: "#";
|
|
101
|
+
readonly logoBar: "|";
|
|
102
|
+
readonly dotFilled: "*";
|
|
103
|
+
readonly dotEmpty: "o";
|
|
104
|
+
readonly dotHalf: "o";
|
|
88
105
|
};
|
|
89
106
|
export declare function isAsciiMode(): boolean;
|
|
90
107
|
export declare function g(name: keyof typeof glyphs): string;
|
|
@@ -92,4 +109,6 @@ export declare const spacing: {
|
|
|
92
109
|
readonly maxWidth: 120;
|
|
93
110
|
readonly indent: 2;
|
|
94
111
|
readonly gap: 1;
|
|
112
|
+
readonly sidebar: 28;
|
|
113
|
+
readonly inspector: 36;
|
|
95
114
|
};
|
package/dist/tui/tokens.js
CHANGED
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* §2
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* design.md §2 — White & Orange (light default) + Dark mirror
|
|
3
|
+
* Light: bg #FFFFFF orange #FF6B1A — Dark: bg #1A1A1A orange #FF8A3D
|
|
4
|
+
* Only orange is saturated in chrome; content uses dim borders.
|
|
5
5
|
*/
|
|
6
6
|
export const tokens = {
|
|
7
7
|
colors: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
bg: '#FFFFFF',
|
|
9
|
+
bgElevated: '#FAF7F2',
|
|
10
|
+
bgSubtle: '#F5F0E8',
|
|
11
|
+
fg: '#1A1A1A',
|
|
12
|
+
fgMuted: '#6B6B6B',
|
|
13
|
+
fgDim: '#9A9A9A',
|
|
14
|
+
orange: '#FF6B1A',
|
|
15
|
+
orangeHot: '#FF8A3D',
|
|
16
|
+
orangeDeep: '#CC4F0A',
|
|
17
|
+
orangeSoft: '#FFF1E6',
|
|
18
|
+
border: '#E8E0D2',
|
|
19
|
+
borderStrong: '#1A1A1A',
|
|
20
|
+
success: '#2E7D32',
|
|
21
|
+
warning: '#C77700',
|
|
22
|
+
danger: '#C62828',
|
|
23
|
+
codeBg: '#FAF7F2',
|
|
24
|
+
codeBorder: '#E8E0D2',
|
|
25
|
+
guide: '#E8E0D2',
|
|
26
|
+
// dark mirror
|
|
27
|
+
darkBg: '#1A1A1A',
|
|
28
|
+
darkOrange: '#FF8A3D',
|
|
29
|
+
darkBorder: '#333333',
|
|
19
30
|
},
|
|
20
31
|
ansi: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
soft: 'white',
|
|
32
|
+
bg: 'white',
|
|
33
|
+
fg: 'black',
|
|
34
|
+
fgMuted: 'gray',
|
|
25
35
|
dim: 'gray',
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
err: 'red',
|
|
29
|
-
warn: 'yellowBright', // spinner orange vivid
|
|
30
|
-
info: 'white',
|
|
36
|
+
orange: 'yellow', // #FF6B1A → 202
|
|
37
|
+
orangeBright: 'red', // fallback vivid
|
|
31
38
|
border: 'gray',
|
|
39
|
+
guide: 'gray',
|
|
40
|
+
success: 'green',
|
|
41
|
+
warning: 'yellow',
|
|
42
|
+
danger: 'red',
|
|
43
|
+
accent: 'yellow',
|
|
44
|
+
accentBold: 'yellowBright',
|
|
45
|
+
soft: 'white',
|
|
32
46
|
muted: 'gray',
|
|
33
|
-
success: 'yellowBright',
|
|
34
47
|
error: 'red',
|
|
35
|
-
|
|
48
|
+
ok: 'green',
|
|
49
|
+
warn: 'yellow',
|
|
50
|
+
info: 'blue',
|
|
51
|
+
successAlt: 'green',
|
|
36
52
|
},
|
|
37
53
|
};
|
|
38
54
|
export const glyphs = {
|
|
@@ -54,16 +70,13 @@ export const glyphs = {
|
|
|
54
70
|
todoActive: '●',
|
|
55
71
|
todoDone: '✓',
|
|
56
72
|
todoPlan: '◇',
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
meterEmpty: '▱',
|
|
65
|
-
continuation: '↪',
|
|
66
|
-
// compat
|
|
73
|
+
logoBar: '▌',
|
|
74
|
+
dotFilled: '●',
|
|
75
|
+
dotEmpty: '○',
|
|
76
|
+
dotHalf: '◐',
|
|
77
|
+
sidebar: '⊞',
|
|
78
|
+
inspector: '▢',
|
|
79
|
+
copy: '⧉',
|
|
67
80
|
brand: '◆',
|
|
68
81
|
compaction: '⟲',
|
|
69
82
|
};
|
|
@@ -86,16 +99,17 @@ export const glyphAscii = {
|
|
|
86
99
|
todoActive: '[>]',
|
|
87
100
|
todoDone: '[x]',
|
|
88
101
|
todoPlan: '#',
|
|
102
|
+
logoBar: '|',
|
|
103
|
+
dotFilled: '*',
|
|
104
|
+
dotEmpty: 'o',
|
|
105
|
+
dotHalf: 'o',
|
|
89
106
|
};
|
|
90
107
|
export function isAsciiMode() {
|
|
91
|
-
return
|
|
92
|
-
process.env.KLYRO_ASCII === '1' ||
|
|
93
|
-
(process.env.LANG !== undefined && !process.env.LANG.toLowerCase().includes('utf-8')) ||
|
|
94
|
-
false);
|
|
108
|
+
return process.env.TERM === 'dumb' || process.env.KLYRO_ASCII === '1' || false;
|
|
95
109
|
}
|
|
96
110
|
export function g(name) {
|
|
97
111
|
if (isAsciiMode())
|
|
98
112
|
return glyphAscii[name] ?? glyphs[name];
|
|
99
113
|
return glyphs[name];
|
|
100
114
|
}
|
|
101
|
-
export const spacing = { maxWidth: 120, indent: 2, gap: 1 };
|
|
115
|
+
export const spacing = { maxWidth: 120, indent: 2, gap: 1, sidebar: 28, inspector: 36 };
|