klyro 0.1.38 → 0.1.40
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 +142 -123
- package/dist/tui/tokens.d.ts +26 -45
- package/dist/tui/tokens.js +27 -44
- 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 — opencode-clean — no clumsy words, correct wrap, markdown, scroll
|
|
3
|
+
* Header 3 rows, guide │ at col2, ◠Klyro accent, prose wrapped at word boundaries
|
|
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 — opencode-clean — no clumsy words, correct wrap, markdown, scroll
|
|
4
|
+
* Header 3 rows, guide │ at col2, ◠Klyro accent, prose wrapped at word boundaries
|
|
5
5
|
*/
|
|
6
6
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
7
7
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
@@ -10,62 +10,33 @@ 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
|
-
|
|
14
|
-
function TopBar({ width, cwd, model }) {
|
|
13
|
+
function Header({ cwd, model, version, width }) {
|
|
15
14
|
const branch = (() => { try {
|
|
16
15
|
const { execSync } = require('node:child_process');
|
|
17
16
|
return execSync('git rev-parse --abbrev-ref HEAD', { cwd, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
18
17
|
}
|
|
19
18
|
catch {
|
|
20
|
-
return '
|
|
19
|
+
return '';
|
|
21
20
|
} })();
|
|
22
|
-
const
|
|
23
|
-
return (_jsxs(Box, {
|
|
21
|
+
const showLinks = width >= 120;
|
|
22
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { bold: true, color: tokens.colors.accent, children: ["KLYRO v", version] }), showLinks ? _jsx(Text, { color: tokens.colors.dim, children: "\u00E2\u201D\u201A /help /config /clear /exit" }) : null] }), _jsxs(Text, { color: tokens.colors.dim, children: [model, "[200k] \u00C2\u00B7 API Usage Billing"] }), _jsxs(Text, { color: tokens.colors.dim, children: [cwd, branch ? ` · ${branch}` : ''] })] }));
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
|
|
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')
|
|
24
|
+
function verbForTool(name) {
|
|
25
|
+
if (name === 'read_file')
|
|
59
26
|
return 'Read';
|
|
60
|
-
if (
|
|
27
|
+
if (name === 'list_directory')
|
|
61
28
|
return 'Listed';
|
|
62
|
-
if (
|
|
29
|
+
if (name === 'grep' || name === 'glob' || name === 'find_files' || name === 'search_files' || name === 'recent_files')
|
|
63
30
|
return 'Searched';
|
|
64
|
-
if (
|
|
31
|
+
if (name === 'shell_exec')
|
|
65
32
|
return 'Ran';
|
|
66
|
-
if (
|
|
33
|
+
if (name.startsWith('git_'))
|
|
67
34
|
return 'Checked git';
|
|
68
|
-
if (
|
|
35
|
+
if (name === 'web_fetch')
|
|
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')
|
|
69
40
|
return 'Edited';
|
|
70
41
|
return 'Called';
|
|
71
42
|
}
|
|
@@ -73,7 +44,7 @@ function groupTools(items) {
|
|
|
73
44
|
const out = [];
|
|
74
45
|
let cur = [];
|
|
75
46
|
const flush = () => {
|
|
76
|
-
if (
|
|
47
|
+
if (cur.length === 0)
|
|
77
48
|
return;
|
|
78
49
|
const byVerb = new Map();
|
|
79
50
|
for (const it of cur) {
|
|
@@ -84,7 +55,7 @@ function groupTools(items) {
|
|
|
84
55
|
}
|
|
85
56
|
for (const [verb, list] of byVerb) {
|
|
86
57
|
const totalMs = list.reduce((s, x) => s + (x.latencyMs ?? 0), 0);
|
|
87
|
-
const status = list.some(x => x.isError || x.status === 'error') ? 'error' : list.some(x => x.status === 'running') ? 'running' : 'done';
|
|
58
|
+
const status = list.some((x) => x.isError || x.status === 'error') ? 'error' : list.some((x) => x.status === 'running') ? 'running' : 'done';
|
|
88
59
|
out.push({ id: nextId('g'), verb, items: list, totalMs, status });
|
|
89
60
|
}
|
|
90
61
|
cur = [];
|
|
@@ -100,6 +71,27 @@ function groupTools(items) {
|
|
|
100
71
|
flush();
|
|
101
72
|
return out;
|
|
102
73
|
}
|
|
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
|
+
}
|
|
103
95
|
export function App(props) {
|
|
104
96
|
const { stdout } = useStdout();
|
|
105
97
|
const [transcript, setTranscript] = useState(props.initialTranscript ?? []);
|
|
@@ -113,60 +105,66 @@ export function App(props) {
|
|
|
113
105
|
const [expandedGroups, setExpandedGroups] = useState(new Set());
|
|
114
106
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
115
107
|
const streamingIdRef = useRef(null);
|
|
116
|
-
const width = stdout?.columns ??
|
|
108
|
+
const width = stdout?.columns ?? 100;
|
|
117
109
|
const height = stdout?.rows ?? 30;
|
|
118
110
|
const isFullscreen = props.isFullscreen ?? false;
|
|
119
111
|
const grouped = groupTools(transcript);
|
|
120
112
|
const viewportH = Math.max(5, height - 10);
|
|
121
|
-
const totalRows = grouped.
|
|
113
|
+
const totalRows = grouped.length + (plan.length > 0 ? 1 : 0) + 2;
|
|
122
114
|
const maxOffset = Math.max(0, totalRows - viewportH);
|
|
115
|
+
const isAtBottom = scrollOffset >= maxOffset;
|
|
123
116
|
const trackH = viewportH;
|
|
124
|
-
const thumbPos = maxOffset === 0 ? 0 : Math.round(scrollOffset / maxOffset * (trackH - 1));
|
|
117
|
+
const thumbPos = maxOffset === 0 ? 0 : Math.round((scrollOffset / maxOffset) * (trackH - 1));
|
|
125
118
|
const visibleGrouped = isFullscreen ? grouped.slice(scrollOffset, scrollOffset + viewportH) : grouped;
|
|
126
|
-
|
|
127
|
-
useEffect(() =>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
119
|
+
useEffect(() => bridge.subscribe((p) => setAwaitingApproval(p !== null)), [bridge]);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (queuedInputs.length > 0 && status.status !== 'running' && !awaitingApproval) {
|
|
122
|
+
const toSend = queuedInputs[0];
|
|
123
|
+
setQueuedInputs((prev) => prev.slice(1));
|
|
124
|
+
setTranscript((prev) => [...prev, { id: nextId('user'), kind: 'text', text: toSend, role: 'user' }]);
|
|
125
|
+
streamingIdRef.current = null;
|
|
126
|
+
const cmd = parseSlash(toSend.trim());
|
|
127
|
+
if (cmd.kind === 'prompt')
|
|
128
|
+
void props.onPrompt(cmd.text);
|
|
129
|
+
else
|
|
130
|
+
void props.onSlash(cmd);
|
|
131
|
+
}
|
|
132
|
+
}, [queuedInputs, status.status, awaitingApproval]);
|
|
139
133
|
useEffect(() => { if (status.status !== 'running')
|
|
140
134
|
return; const start = Date.now() - elapsed; const t = setInterval(() => setElapsed(Date.now() - start), 1000); return () => clearInterval(t); }, [status.status, elapsed]);
|
|
141
|
-
useEffect(() => { if (
|
|
142
|
-
setScrollOffset(maxOffset); }, [transcript.length, plan.length, maxOffset]);
|
|
135
|
+
useEffect(() => { if (isAtBottom)
|
|
136
|
+
setScrollOffset(maxOffset); }, [transcript.length, plan.length, maxOffset, isAtBottom]);
|
|
143
137
|
const append = useCallback((item) => { if (item.kind !== 'text' || item.role !== 'assistant')
|
|
144
|
-
streamingIdRef.current = null; setTranscript(
|
|
145
|
-
const appendDelta = useCallback((text) => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
138
|
+
streamingIdRef.current = null; setTranscript((prev) => [...prev, item]); }, []);
|
|
139
|
+
const appendDelta = useCallback((text) => {
|
|
140
|
+
if (!text)
|
|
141
|
+
return;
|
|
142
|
+
const sid = streamingIdRef.current;
|
|
143
|
+
if (sid)
|
|
144
|
+
setTranscript((prev) => { const idx = prev.findIndex((x) => x.id === sid); if (idx === -1)
|
|
145
|
+
return [...prev, { id: sid, kind: 'text', text, role: 'assistant' }]; const cur = prev[idx]; const copy = [...prev]; copy[idx] = { ...cur, text: cur.text + text }; return copy; });
|
|
146
|
+
else {
|
|
147
|
+
const id = nextId('stream');
|
|
148
|
+
streamingIdRef.current = id;
|
|
149
|
+
setTranscript((prev) => [...prev, { id, kind: 'text', text, role: 'assistant' }]);
|
|
150
|
+
}
|
|
151
|
+
}, []);
|
|
154
152
|
useEffect(() => { if (status.status !== 'running')
|
|
155
153
|
streamingIdRef.current = null; }, [status.status]);
|
|
156
|
-
const updateStatus = useCallback((s) => setStatus(p => ({ ...p, ...s })), []);
|
|
154
|
+
const updateStatus = useCallback((s) => setStatus((p) => ({ ...p, ...s })), []);
|
|
157
155
|
const updatePlan = useCallback((p) => setPlan(p), []);
|
|
158
156
|
const onMountedRef = useRef(props.onMounted);
|
|
159
157
|
useEffect(() => { onMountedRef.current = props.onMounted; }, [props.onMounted]);
|
|
160
158
|
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]);
|
|
161
|
-
const toggleGroup = (id) => setExpandedGroups(
|
|
159
|
+
const toggleGroup = (id) => setExpandedGroups((prev) => { const n = new Set(prev); if (n.has(id))
|
|
162
160
|
n.delete(id);
|
|
163
161
|
else
|
|
164
162
|
n.add(id); return n; });
|
|
165
|
-
const scrollUp = (n = 3) => setScrollOffset(p => Math.max(0, p - n));
|
|
166
|
-
const scrollDown = (n = 3) => setScrollOffset(p => Math.min(maxOffset, p + n));
|
|
163
|
+
const scrollUp = (n = 3) => setScrollOffset((p) => Math.max(0, p - n));
|
|
164
|
+
const scrollDown = (n = 3) => setScrollOffset((p) => Math.min(maxOffset, p + n));
|
|
167
165
|
useInput((inputStr, key) => {
|
|
168
166
|
if (key.escape && queuedInputs.length > 0) {
|
|
169
|
-
setQueuedInputs(
|
|
167
|
+
setQueuedInputs((prev) => prev.slice(1));
|
|
170
168
|
return;
|
|
171
169
|
}
|
|
172
170
|
if (key.pageUp || (key.ctrl && inputStr === 'u')) {
|
|
@@ -205,16 +203,16 @@ export function App(props) {
|
|
|
205
203
|
return;
|
|
206
204
|
if (queuedInputs.length >= 3)
|
|
207
205
|
return;
|
|
208
|
-
setQueuedInputs(
|
|
206
|
+
setQueuedInputs((prev) => [...prev, v]);
|
|
209
207
|
setInput('');
|
|
210
208
|
return;
|
|
211
209
|
}
|
|
212
210
|
if (key.backspace || key.delete) {
|
|
213
|
-
setInput(v => v.slice(0, -1));
|
|
211
|
+
setInput((v) => v.slice(0, -1));
|
|
214
212
|
return;
|
|
215
213
|
}
|
|
216
214
|
if (!key.ctrl && !key.meta)
|
|
217
|
-
setInput(v => v + inputStr.replace(/\r\n/g, '\n').replace(/\r/g, '\n'));
|
|
215
|
+
setInput((v) => v + inputStr.replace(/\r\n/g, '\n').replace(/\r/g, '\n'));
|
|
218
216
|
return;
|
|
219
217
|
}
|
|
220
218
|
if (key.return) {
|
|
@@ -222,7 +220,7 @@ export function App(props) {
|
|
|
222
220
|
if (!v)
|
|
223
221
|
return;
|
|
224
222
|
setInput('');
|
|
225
|
-
setTranscript(
|
|
223
|
+
setTranscript((prev) => [...prev, { id: nextId('user'), kind: 'text', text: v, role: 'user' }]);
|
|
226
224
|
streamingIdRef.current = null;
|
|
227
225
|
const cmd = parseSlash(v);
|
|
228
226
|
if (cmd.kind === 'prompt')
|
|
@@ -232,71 +230,92 @@ export function App(props) {
|
|
|
232
230
|
return;
|
|
233
231
|
}
|
|
234
232
|
if (key.backspace || key.delete) {
|
|
235
|
-
setInput(v => v.slice(0, -1));
|
|
233
|
+
setInput((v) => v.slice(0, -1));
|
|
236
234
|
return;
|
|
237
235
|
}
|
|
238
236
|
if (!key.ctrl && !key.meta)
|
|
239
|
-
setInput(v => v + inputStr);
|
|
237
|
+
setInput((v) => v + inputStr);
|
|
240
238
|
});
|
|
241
|
-
const ver = props.version ?? '0.1.
|
|
242
|
-
const rule = '
|
|
239
|
+
const ver = props.version ?? '0.1.27';
|
|
240
|
+
const rule = g('rule').repeat(Math.max(10, width - 2));
|
|
243
241
|
const cost = (status.usageInput / 1000 * 0.003 + status.usageOutput / 1000 * 0.015);
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
|
|
242
|
+
const totalTokens = status.usageInput + status.usageOutput;
|
|
243
|
+
const ctxPct = totalTokens > 0 ? Math.round((totalTokens / 120_000) * 100) : 0;
|
|
244
|
+
const baseHints = 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';
|
|
245
|
+
const hints = maxOffset > 0 && isFullscreen ? `${baseHints} · PgUp/Dn scroll` : baseHints;
|
|
246
|
+
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\u00E2\u20AC\u00A6" })) : visibleGrouped.map((item) => {
|
|
247
247
|
if (item.verb) {
|
|
248
248
|
const gr = item;
|
|
249
249
|
const isExpanded = expandedGroups.has(gr.id);
|
|
250
|
-
const verbLine = (() => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
const verbLine = (() => {
|
|
251
|
+
if (gr.items.length === 1) {
|
|
252
|
+
const it = gr.items[0];
|
|
253
|
+
let p = '';
|
|
254
|
+
try {
|
|
255
|
+
const a = JSON.parse(it.args);
|
|
256
|
+
p = a.path ?? a.pattern ?? a.command?.slice(0, 48) ?? '';
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
p = '';
|
|
260
|
+
}
|
|
261
|
+
if (gr.verb === 'Read' && p)
|
|
262
|
+
return `Read ${p.split('/').pop()}`;
|
|
263
|
+
if (gr.verb === 'Searched' && p)
|
|
264
|
+
return `Searched "${p}"`;
|
|
265
|
+
if (gr.verb === 'Ran' && p)
|
|
266
|
+
return `Ran ${p.split(' ')[0]}`;
|
|
267
|
+
return `${gr.verb} ${p}`;
|
|
256
268
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
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 => {
|
|
269
|
+
if (gr.verb === 'Read')
|
|
270
|
+
return `Read ${gr.items.length} files`;
|
|
271
|
+
if (gr.verb === 'Searched')
|
|
272
|
+
return `Searched ${gr.items.length} patterns`;
|
|
273
|
+
if (gr.verb === 'Ran')
|
|
274
|
+
return `Ran ${gr.items.length} commands`;
|
|
275
|
+
if (gr.verb === 'Edited')
|
|
276
|
+
return `Edited ${gr.items.length} files`;
|
|
277
|
+
return `${gr.verb} ${gr.items.length} items`;
|
|
278
|
+
})();
|
|
279
|
+
const right = gr.status === 'running' ? `${(elapsed / 1000).toFixed(1)}s` : gr.status === 'error' ? '✗' : `${gr.totalMs}ms`;
|
|
280
|
+
const marker = isExpanded ? '▼' : '✓';
|
|
281
|
+
const markerColor = gr.status === 'error' ? tokens.colors.err : gr.status === 'running' ? tokens.colors.warn : tokens.colors.ok;
|
|
282
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.colors.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: markerColor, children: [marker, " ", verbLine] }), _jsxs(Text, { color: tokens.colors.dim, children: [" ", right] })] }), isExpanded ? gr.items.map((it) => {
|
|
273
283
|
let friendly = '';
|
|
274
284
|
try {
|
|
275
285
|
const a = JSON.parse(it.args);
|
|
276
|
-
const p = a.path ?? '';
|
|
277
|
-
|
|
286
|
+
const p = a.path ?? a.pattern ?? a.command ?? '';
|
|
287
|
+
const short = p ? String(p).split('/').pop()?.slice(0, 40) ?? p : '';
|
|
288
|
+
if (it.name === 'read_file' && short)
|
|
289
|
+
friendly = `${short}`;
|
|
290
|
+
else if (it.name === 'shell_exec' && p)
|
|
291
|
+
friendly = `$ ${String(p).slice(0, 40)}`;
|
|
292
|
+
else if (short)
|
|
293
|
+
friendly = short;
|
|
294
|
+
else
|
|
295
|
+
friendly = it.args.slice(0, 40);
|
|
278
296
|
}
|
|
279
297
|
catch {
|
|
280
298
|
friendly = it.args.slice(0, 40);
|
|
281
299
|
}
|
|
282
|
-
return
|
|
300
|
+
return (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: tokens.colors.guide, children: [g('end'), " "] }), _jsx(Text, { color: tokens.colors.dim, children: friendly })] }, it.id));
|
|
283
301
|
}) : null] }, gr.id));
|
|
284
302
|
}
|
|
285
303
|
const it = item;
|
|
286
304
|
if (it.kind === 'text' && it.role === 'user') {
|
|
287
|
-
return _jsxs(Box, {
|
|
305
|
+
return _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: tokens.colors.accent, bold: true, children: [g('prompt'), " "] }), _jsx(Text, { wrap: "wrap", children: it.text })] }, it.id);
|
|
288
306
|
}
|
|
289
307
|
if (it.kind === 'text') {
|
|
290
|
-
|
|
308
|
+
// prose — render markdown, not raw **, with proper wrap and guide
|
|
309
|
+
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));
|
|
291
310
|
}
|
|
292
311
|
if (it.kind === 'error')
|
|
293
|
-
return _jsx(Box, {
|
|
312
|
+
return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.colors.err, children: [" ", g('guide'), " \u00E2\u0153\u2014 ", it.message] }) }, it.id);
|
|
294
313
|
if (it.kind === 'policy')
|
|
295
314
|
return null;
|
|
296
315
|
if (it.kind === 'file_changed')
|
|
297
|
-
return _jsx(Box, {
|
|
316
|
+
return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.colors.dim, children: [" ", g('guide'), " ", g('editsBadge'), " ", it.path, " ", it.op] }) }, it.id);
|
|
298
317
|
if (it.kind === 'diff')
|
|
299
|
-
return _jsxs(Box, {
|
|
318
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, color: tokens.colors.soft, children: it.summary ?? 'Diff' }), it.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 0, children: [_jsx(Text, { color: tokens.colors.soft, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { wrap: "wrap", color: l.kind === 'add' ? tokens.colors.ok : l.kind === 'remove' ? tokens.colors.err : tokens.colors.dim, children: [l.kind === 'add' ? '+ ' : l.kind === 'remove' ? '- ' : ' ', l.text] }, j)))] }, i)))] }, it.id));
|
|
300
319
|
return null;
|
|
301
|
-
}), status.status === 'running' && !streamingIdRef.current ?
|
|
320
|
+
}), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.colors.guide, children: [" ", g('guide'), " "] }), _jsx(Text, { color: tokens.colors.dim, children: "Thinking..." }), _jsxs(Text, { color: tokens.colors.dim, children: [" ", (elapsed / 1000).toFixed(1), "s"] })] })) : null, plan.length > 0 ? (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginTop: 0, marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.colors.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { bold: true, children: [g('todoPlan'), " Plan ", plan.filter((p) => p.status === 'done').length, "/", plan.length] })] }), plan.slice(0, 8).map((p) => (_jsxs(Box, { children: [_jsxs(Text, { color: tokens.colors.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: p.status === 'done' ? tokens.colors.ok : p.status === 'in_progress' ? tokens.colors.accent : tokens.colors.dim, children: [p.status === 'done' ? g('todoDone') : p.status === 'in_progress' ? g('todoActive') : g('todoPending'), " ", p.title] })] }, p.id)))] })) : null, queuedInputs.length > 0 ? (_jsx(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: queuedInputs.map((q, i) => (_jsxs(Text, { color: tokens.colors.dim, children: ["queued: ", q.slice(0, 60), i === 0 ? ' esc to drop' : ''] }, i))) })) : null] }), isFullscreen ? (_jsx(Box, { flexDirection: "column", width: 1, marginLeft: 1, children: Array.from({ length: trackH }).map((_, i) => (_jsx(Text, { color: i === thumbPos ? tokens.colors.accent : tokens.colors.guide, children: i === thumbPos ? 'â—' : '│' }, i))) })) : null] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tokens.colors.guide, children: g('rule').repeat(Math.max(10, width - 2)) }), _jsxs(Box, { children: [_jsxs(Text, { color: tokens.colors.accent, bold: true, children: [g('prompt'), " "] }), _jsxs(Text, { wrap: "wrap", children: [input || _jsx(Text, { color: tokens.colors.dim, children: "Message Klyro\u00E2\u20AC\u00A6" }), "\u00E2\u2013\u008F"] })] }), _jsx(Text, { color: tokens.colors.guide, children: g('rule').repeat(Math.max(10, width - 2)) })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: tokens.colors.dim, children: [baseHints, maxOffset > 0 && isFullscreen ? ' · PgUp/Dn scroll' : ''] }), _jsxs(Text, { color: tokens.colors.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ctx · ` : '', status.status === 'running' ? 'auto mode on â—' : ''] })] })] }));
|
|
302
321
|
}
|
package/dist/tui/tokens.d.ts
CHANGED
|
@@ -1,53 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Only orange is saturated in chrome; content uses dim borders.
|
|
2
|
+
* Klyro tokens — White & Orange #FF6B1A light + Dark #E8843C — per design.md + TUI_DESIGN.md
|
|
3
|
+
* True 24-bit hex, Ink will render via 256-color fallback. No backgrounds except diff.
|
|
5
4
|
*/
|
|
6
5
|
export declare const tokens: {
|
|
7
6
|
readonly colors: {
|
|
8
7
|
readonly bg: "#FFFFFF";
|
|
9
8
|
readonly bgElevated: "#FAF7F2";
|
|
10
|
-
readonly bgSubtle: "#F5F0E8";
|
|
11
9
|
readonly fg: "#1A1A1A";
|
|
12
|
-
readonly
|
|
13
|
-
readonly
|
|
14
|
-
readonly
|
|
15
|
-
readonly
|
|
16
|
-
readonly
|
|
17
|
-
readonly
|
|
18
|
-
readonly
|
|
19
|
-
readonly
|
|
20
|
-
readonly
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
23
|
-
readonly codeBg: "#FAF7F2";
|
|
24
|
-
readonly codeBorder: "#E8E0D2";
|
|
25
|
-
readonly guide: "#E8E0D2";
|
|
26
|
-
readonly darkBg: "#1A1A1A";
|
|
27
|
-
readonly darkOrange: "#FF8A3D";
|
|
28
|
-
readonly darkBorder: "#333333";
|
|
10
|
+
readonly soft: "#F5F5F5";
|
|
11
|
+
readonly dim: "#9A9A9A";
|
|
12
|
+
readonly guide: "#3A3A3A";
|
|
13
|
+
readonly accent: "#FF6B1A";
|
|
14
|
+
readonly accentSoft: "#FFF1E6";
|
|
15
|
+
readonly ok: "#FF6B1A";
|
|
16
|
+
readonly err: "#E06C6C";
|
|
17
|
+
readonly warn: "#FF6B1A";
|
|
18
|
+
readonly info: "#6FA8DC";
|
|
19
|
+
readonly diffAddBg: "#12250F";
|
|
20
|
+
readonly diffDelBg: "#2A1212";
|
|
29
21
|
};
|
|
30
22
|
readonly ansi: {
|
|
31
|
-
readonly
|
|
32
|
-
readonly
|
|
33
|
-
readonly
|
|
23
|
+
readonly accent: "yellowBright";
|
|
24
|
+
readonly accentBold: "yellowBright";
|
|
25
|
+
readonly fg: "white";
|
|
26
|
+
readonly soft: "whiteBright";
|
|
34
27
|
readonly dim: "gray";
|
|
35
|
-
readonly orange: "yellow";
|
|
36
|
-
readonly orangeBright: "red";
|
|
37
|
-
readonly border: "gray";
|
|
38
28
|
readonly guide: "gray";
|
|
39
|
-
readonly
|
|
40
|
-
readonly
|
|
41
|
-
readonly
|
|
42
|
-
readonly
|
|
43
|
-
readonly
|
|
44
|
-
readonly soft: "white";
|
|
29
|
+
readonly ok: "yellowBright";
|
|
30
|
+
readonly err: "red";
|
|
31
|
+
readonly warn: "yellowBright";
|
|
32
|
+
readonly info: "blue";
|
|
33
|
+
readonly border: "gray";
|
|
45
34
|
readonly muted: "gray";
|
|
35
|
+
readonly success: "yellowBright";
|
|
46
36
|
readonly error: "red";
|
|
47
|
-
readonly
|
|
48
|
-
readonly warn: "yellow";
|
|
49
|
-
readonly info: "blue";
|
|
50
|
-
readonly successAlt: "green";
|
|
37
|
+
readonly warning: "yellowBright";
|
|
51
38
|
};
|
|
52
39
|
};
|
|
53
40
|
export declare const glyphs: {
|
|
@@ -72,12 +59,10 @@ export declare const glyphs: {
|
|
|
72
59
|
readonly logoBar: "▌";
|
|
73
60
|
readonly dotFilled: "●";
|
|
74
61
|
readonly dotEmpty: "○";
|
|
75
|
-
readonly dotHalf: "◐";
|
|
76
|
-
readonly sidebar: "⊞";
|
|
77
|
-
readonly inspector: "▢";
|
|
78
|
-
readonly copy: "⧉";
|
|
79
62
|
readonly brand: "◆";
|
|
80
63
|
readonly compaction: "⟲";
|
|
64
|
+
readonly editsBadge: "✎";
|
|
65
|
+
readonly dot: "·";
|
|
81
66
|
};
|
|
82
67
|
export declare const glyphAscii: {
|
|
83
68
|
readonly prompt: ">";
|
|
@@ -101,14 +86,10 @@ export declare const glyphAscii: {
|
|
|
101
86
|
readonly logoBar: "|";
|
|
102
87
|
readonly dotFilled: "*";
|
|
103
88
|
readonly dotEmpty: "o";
|
|
104
|
-
readonly dotHalf: "o";
|
|
105
89
|
};
|
|
106
90
|
export declare function isAsciiMode(): boolean;
|
|
107
91
|
export declare function g(name: keyof typeof glyphs): string;
|
|
108
92
|
export declare const spacing: {
|
|
109
|
-
readonly maxWidth: 120;
|
|
110
|
-
readonly indent: 2;
|
|
111
|
-
readonly gap: 1;
|
|
112
93
|
readonly sidebar: 28;
|
|
113
94
|
readonly inspector: 36;
|
|
114
95
|
};
|
package/dist/tui/tokens.js
CHANGED
|
@@ -1,54 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Only orange is saturated in chrome; content uses dim borders.
|
|
2
|
+
* Klyro tokens — White & Orange #FF6B1A light + Dark #E8843C — per design.md + TUI_DESIGN.md
|
|
3
|
+
* True 24-bit hex, Ink will render via 256-color fallback. No backgrounds except diff.
|
|
5
4
|
*/
|
|
6
5
|
export const tokens = {
|
|
7
6
|
colors: {
|
|
8
7
|
bg: '#FFFFFF',
|
|
9
8
|
bgElevated: '#FAF7F2',
|
|
10
|
-
bgSubtle: '#F5F0E8',
|
|
11
9
|
fg: '#1A1A1A',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
codeBg: '#FAF7F2',
|
|
24
|
-
codeBorder: '#E8E0D2',
|
|
25
|
-
guide: '#E8E0D2',
|
|
26
|
-
// dark mirror
|
|
27
|
-
darkBg: '#1A1A1A',
|
|
28
|
-
darkOrange: '#FF8A3D',
|
|
29
|
-
darkBorder: '#333333',
|
|
10
|
+
soft: '#F5F5F5',
|
|
11
|
+
dim: '#9A9A9A',
|
|
12
|
+
guide: '#3A3A3A',
|
|
13
|
+
accent: '#FF6B1A',
|
|
14
|
+
accentSoft: '#FFF1E6',
|
|
15
|
+
ok: '#FF6B1A',
|
|
16
|
+
err: '#E06C6C',
|
|
17
|
+
warn: '#FF6B1A',
|
|
18
|
+
info: '#6FA8DC',
|
|
19
|
+
diffAddBg: '#12250F',
|
|
20
|
+
diffDelBg: '#2A1212',
|
|
30
21
|
},
|
|
31
22
|
ansi: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
accent: 'yellowBright',
|
|
24
|
+
accentBold: 'yellowBright',
|
|
25
|
+
fg: 'white',
|
|
26
|
+
soft: 'whiteBright',
|
|
35
27
|
dim: 'gray',
|
|
36
|
-
orange: 'yellow', // #FF6B1A → 202
|
|
37
|
-
orangeBright: 'red', // fallback vivid
|
|
38
|
-
border: 'gray',
|
|
39
28
|
guide: 'gray',
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
soft: 'white',
|
|
29
|
+
ok: 'yellowBright',
|
|
30
|
+
err: 'red',
|
|
31
|
+
warn: 'yellowBright',
|
|
32
|
+
info: 'blue',
|
|
33
|
+
border: 'gray',
|
|
46
34
|
muted: 'gray',
|
|
35
|
+
success: 'yellowBright',
|
|
47
36
|
error: 'red',
|
|
48
|
-
|
|
49
|
-
warn: 'yellow',
|
|
50
|
-
info: 'blue',
|
|
51
|
-
successAlt: 'green',
|
|
37
|
+
warning: 'yellowBright',
|
|
52
38
|
},
|
|
53
39
|
};
|
|
54
40
|
export const glyphs = {
|
|
@@ -73,12 +59,10 @@ export const glyphs = {
|
|
|
73
59
|
logoBar: '▌',
|
|
74
60
|
dotFilled: '●',
|
|
75
61
|
dotEmpty: '○',
|
|
76
|
-
dotHalf: '◐',
|
|
77
|
-
sidebar: '⊞',
|
|
78
|
-
inspector: '▢',
|
|
79
|
-
copy: '⧉',
|
|
80
62
|
brand: '◆',
|
|
81
63
|
compaction: '⟲',
|
|
64
|
+
editsBadge: '✎',
|
|
65
|
+
dot: '·',
|
|
82
66
|
};
|
|
83
67
|
export const glyphAscii = {
|
|
84
68
|
prompt: '>',
|
|
@@ -102,7 +86,6 @@ export const glyphAscii = {
|
|
|
102
86
|
logoBar: '|',
|
|
103
87
|
dotFilled: '*',
|
|
104
88
|
dotEmpty: 'o',
|
|
105
|
-
dotHalf: 'o',
|
|
106
89
|
};
|
|
107
90
|
export function isAsciiMode() {
|
|
108
91
|
return process.env.TERM === 'dumb' || process.env.KLYRO_ASCII === '1' || false;
|
|
@@ -112,4 +95,4 @@ export function g(name) {
|
|
|
112
95
|
return glyphAscii[name] ?? glyphs[name];
|
|
113
96
|
return glyphs[name];
|
|
114
97
|
}
|
|
115
|
-
export const spacing = {
|
|
98
|
+
export const spacing = { sidebar: 28, inspector: 36 };
|