klyro 0.1.29 → 0.1.30
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 +31 -14
- package/package.json +1 -1
package/dist/tui/app.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Klyro TUI — opencode-
|
|
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 — opencode-
|
|
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';
|
|
@@ -71,6 +71,27 @@ function groupTools(items) {
|
|
|
71
71
|
flush();
|
|
72
72
|
return out;
|
|
73
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.ansi.dim : undefined, wrap: "wrap", children: text.slice(last, m.index) }, `t-${idx++}`));
|
|
85
|
+
parts.push(_jsx(Text, { bold: true, color: dim ? undefined : tokens.ansi.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.ansi.dim : undefined, wrap: "wrap", children: text.slice(last) }, `t-${idx++}`));
|
|
90
|
+
if (parts.length === 0)
|
|
91
|
+
return _jsx(Text, { color: dim ? tokens.ansi.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
|
+
}
|
|
74
95
|
export function App(props) {
|
|
75
96
|
const { stdout } = useStdout();
|
|
76
97
|
const [transcript, setTranscript] = useState(props.initialTranscript ?? []);
|
|
@@ -84,12 +105,11 @@ export function App(props) {
|
|
|
84
105
|
const [expandedGroups, setExpandedGroups] = useState(new Set());
|
|
85
106
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
86
107
|
const streamingIdRef = useRef(null);
|
|
87
|
-
const placeholder = 'Message Klyro…';
|
|
88
108
|
const width = stdout?.columns ?? 100;
|
|
89
109
|
const height = stdout?.rows ?? 30;
|
|
90
110
|
const isFullscreen = props.isFullscreen ?? false;
|
|
91
|
-
const viewportH = Math.max(5, height - 10);
|
|
92
111
|
const grouped = groupTools(transcript);
|
|
112
|
+
const viewportH = Math.max(5, height - 10);
|
|
93
113
|
const totalRows = grouped.length + (plan.length > 0 ? 1 : 0) + 2;
|
|
94
114
|
const maxOffset = Math.max(0, totalRows - viewportH);
|
|
95
115
|
const isAtBottom = scrollOffset >= maxOffset;
|
|
@@ -182,7 +202,7 @@ export function App(props) {
|
|
|
182
202
|
if (!v)
|
|
183
203
|
return;
|
|
184
204
|
if (queuedInputs.length >= 3)
|
|
185
|
-
return;
|
|
205
|
+
return;
|
|
186
206
|
setQueuedInputs((prev) => [...prev, v]);
|
|
187
207
|
setInput('');
|
|
188
208
|
return;
|
|
@@ -223,9 +243,7 @@ export function App(props) {
|
|
|
223
243
|
const ctxPct = totalTokens > 0 ? Math.round((totalTokens / 120_000) * 100) : 0;
|
|
224
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';
|
|
225
245
|
const hints = maxOffset > 0 && isFullscreen ? `${baseHints} · PgUp/Dn scroll` : baseHints;
|
|
226
|
-
|
|
227
|
-
const wrapW = Math.max(20, width - 6);
|
|
228
|
-
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.ansi.dim, children: placeholder })) : visibleGrouped.map((item) => {
|
|
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.ansi.dim, children: "Message Klyro\u2026" })) : visibleGrouped.map((item) => {
|
|
229
247
|
if (item.verb) {
|
|
230
248
|
const gr = item;
|
|
231
249
|
const isExpanded = expandedGroups.has(gr.id);
|
|
@@ -268,7 +286,7 @@ export function App(props) {
|
|
|
268
286
|
const p = a.path ?? a.pattern ?? a.command ?? '';
|
|
269
287
|
const short = p ? String(p).split('/').pop()?.slice(0, 40) ?? p : '';
|
|
270
288
|
if (it.name === 'read_file' && short)
|
|
271
|
-
friendly = `${short}
|
|
289
|
+
friendly = `${short}`;
|
|
272
290
|
else if (it.name === 'shell_exec' && p)
|
|
273
291
|
friendly = `$ ${String(p).slice(0, 40)}`;
|
|
274
292
|
else if (short)
|
|
@@ -279,7 +297,7 @@ export function App(props) {
|
|
|
279
297
|
catch {
|
|
280
298
|
friendly = it.args.slice(0, 40);
|
|
281
299
|
}
|
|
282
|
-
return (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [g('end'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: friendly })
|
|
300
|
+
return (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [g('end'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: friendly })] }, it.id));
|
|
283
301
|
}) : null] }, gr.id));
|
|
284
302
|
}
|
|
285
303
|
const it = item;
|
|
@@ -287,9 +305,8 @@ export function App(props) {
|
|
|
287
305
|
return _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsx(Text, { wrap: "wrap", children: it.text })] }, it.id);
|
|
288
306
|
}
|
|
289
307
|
if (it.kind === 'text') {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: tokens.ansi.accent, children: [g('agentBullet'), " Klyro"] })] }), _jsxs(Box, { paddingLeft: 2, children: [_jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " "] }), _jsx(Text, { wrap: "wrap", children: it.text })] })] }, it.id));
|
|
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.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: tokens.ansi.accent, children: [g('agentBullet'), " Klyro"] })] }), _jsx(Box, { paddingLeft: 2, flexDirection: "column", children: _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " "] }), _jsx(Box, { flexGrow: 1, children: _jsx(MarkdownText, { text: it.text }) })] }) })] }, it.id));
|
|
293
310
|
}
|
|
294
311
|
if (it.kind === 'error')
|
|
295
312
|
return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
|
|
@@ -300,5 +317,5 @@ export function App(props) {
|
|
|
300
317
|
if (it.kind === 'diff')
|
|
301
318
|
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [_jsx(Text, { bold: true, color: tokens.ansi.soft, children: it.summary ?? 'Diff' }), it.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 0, children: [_jsx(Text, { color: tokens.ansi.soft, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { wrap: "wrap", color: l.kind === 'add' ? tokens.ansi.ok : l.kind === 'remove' ? tokens.ansi.err : tokens.ansi.dim, children: [l.kind === 'add' ? '+ ' : l.kind === 'remove' ? '- ' : ' ', l.text] }, j)))] }, i)))] }, it.id));
|
|
302
319
|
return null;
|
|
303
|
-
}), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: "Thinking..." }), _jsxs(Text, { color: tokens.ansi.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.ansi.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.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: p.status === 'done' ? tokens.ansi.ok : p.status === 'in_progress' ? tokens.ansi.accent : tokens.ansi.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.ansi.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.ansi.accent : tokens.ansi.guide, children: i === thumbPos ? '●' : '│' }, i))) })) : null] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tokens.ansi.guide, children: rule }), _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsxs(Text, { wrap: "wrap", children: [input || _jsx(Text, { color: tokens.ansi.dim, children:
|
|
320
|
+
}), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: "Thinking..." }), _jsxs(Text, { color: tokens.ansi.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.ansi.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.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: p.status === 'done' ? tokens.ansi.ok : p.status === 'in_progress' ? tokens.ansi.accent : tokens.ansi.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.ansi.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.ansi.accent : tokens.ansi.guide, children: i === thumbPos ? '●' : '│' }, i))) })) : null] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tokens.ansi.guide, children: g('rule').repeat(Math.max(10, width - 2)) }), _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsxs(Text, { wrap: "wrap", children: [input || _jsx(Text, { color: tokens.ansi.dim, children: "Message Klyro\u2026" }), "\u258F"] })] }), _jsx(Text, { color: tokens.ansi.guide, children: g('rule').repeat(Math.max(10, width - 2)) })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: tokens.ansi.dim, children: [baseHints, maxOffset > 0 && isFullscreen ? ' · PgUp/Dn scroll' : ''] }), _jsxs(Text, { color: tokens.ansi.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ctx · ` : '', status.status === 'running' ? 'auto mode on ●' : ''] })] })] }));
|
|
304
321
|
}
|