klyro 0.1.27 → 0.1.29

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 CHANGED
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Klyro TUI — TUI_DESIGN.md §3-7,10-12 Professional monochrome + one accent orange #E8843C
3
- * No boxes, no borders (§24). Guide at col2, accent ● at col4 for agent.
4
- * Regions: scrollback (header+turns) / live window (streaming tail+groups) / pinned (input+status)
2
+ * Klyro TUI — opencode-perfect — TUI_DESIGN.md §2-7 + user request: no clumsy words, smooth scroll like opencode
3
+ * Full-screen alt takeover when supported, inline degrade otherwise. Clean wrap at word boundaries, slider │●.
5
4
  */
6
5
  import React from 'react';
7
6
  import type { StatusSnapshot } from './status.js';
package/dist/tui/app.js CHANGED
@@ -1,8 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
- * Klyro TUI — TUI_DESIGN.md §3-7,10-12 Professional monochrome + one accent orange #E8843C
4
- * No boxes, no borders (§24). Guide at col2, accent ● at col4 for agent.
5
- * Regions: scrollback (header+turns) / live window (streaming tail+groups) / pinned (input+status)
3
+ * Klyro TUI — opencode-perfect — TUI_DESIGN.md §2-7 + user request: no clumsy words, smooth scroll like opencode
4
+ * Full-screen alt takeover when supported, inline degrade otherwise. Clean wrap at word boundaries, slider │●.
6
5
  */
7
6
  import { useState, useEffect, useRef, useCallback } from 'react';
8
7
  import { Box, Text, useInput, useStdout } from 'ink';
@@ -11,7 +10,6 @@ import { parse as parseSlash } from '../cli/slash/parser.js';
11
10
  import { tokens, g } from './tokens.js';
12
11
  let _id = 0;
13
12
  function nextId(p) { _id++; return `${p}-${_id}`; }
14
- // ── Header §4 ───────────────────────────────────────────────────────────────
15
13
  function Header({ cwd, model, version, width }) {
16
14
  const branch = (() => { try {
17
15
  const { execSync } = require('node:child_process');
@@ -21,29 +19,26 @@ function Header({ cwd, model, version, width }) {
21
19
  return '';
22
20
  } })();
23
21
  const showLinks = width >= 120;
24
- const links = '│ /help /config /clear /exit';
25
- const ctxShort = '200k'; // TODO wire real context window
26
- const row1Left = `KLYRO v${version}`;
27
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: tokens.ansi.accent, children: row1Left }), showLinks ? _jsx(Text, { color: tokens.ansi.dim, children: links }) : null] }), _jsxs(Text, { color: tokens.ansi.dim, children: [model, "[", ctxShort, "] \u00B7 API Usage Billing"] }), _jsxs(Text, { color: tokens.ansi.dim, children: [cwd, branch ? ` · ${branch}` : ''] })] }));
22
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { bold: true, color: tokens.ansi.accent, children: ["KLYRO v", version] }), showLinks ? _jsx(Text, { color: tokens.ansi.dim, children: "\u2502 /help /config /clear /exit" }) : null] }), _jsxs(Text, { color: tokens.ansi.dim, children: [model, "[200k] \u00B7 API Usage Billing"] }), _jsxs(Text, { color: tokens.ansi.dim, children: [cwd, branch ? ` · ${branch}` : ''] })] }));
28
23
  }
29
24
  function verbForTool(name) {
30
25
  if (name === 'read_file')
31
- return { verb: 'Read', plural: 'Read' };
26
+ return 'Read';
32
27
  if (name === 'list_directory')
33
- return { verb: 'Listed', plural: 'Listed' };
28
+ return 'Listed';
34
29
  if (name === 'grep' || name === 'glob' || name === 'find_files' || name === 'search_files' || name === 'recent_files')
35
- return { verb: 'Searched', plural: 'Searched' };
30
+ return 'Searched';
36
31
  if (name === 'shell_exec')
37
- return { verb: 'Ran', plural: 'Ran' };
32
+ return 'Ran';
38
33
  if (name.startsWith('git_'))
39
- return { verb: 'Checked git', plural: 'Checked git' };
34
+ return 'Checked git';
40
35
  if (name === 'web_fetch')
41
- return { verb: 'Fetched', plural: 'Fetched' };
36
+ return 'Fetched';
42
37
  if (name === 'web_search')
43
- return { verb: 'Searched web', plural: 'Searched web' };
38
+ return 'Searched web';
44
39
  if (name === 'edit_file' || name === 'multi_edit' || name === 'apply_patch' || name === 'write_file')
45
- return { verb: 'Edited', plural: 'Edited' };
46
- return { verb: 'Called', plural: 'Called' };
40
+ return 'Edited';
41
+ return 'Called';
47
42
  }
48
43
  function groupTools(items) {
49
44
  const out = [];
@@ -51,10 +46,9 @@ function groupTools(items) {
51
46
  const flush = () => {
52
47
  if (cur.length === 0)
53
48
  return;
54
- // bucket by verb
55
49
  const byVerb = new Map();
56
50
  for (const it of cur) {
57
- const v = verbForTool(it.name).verb;
51
+ const v = verbForTool(it.name);
58
52
  if (!byVerb.has(v))
59
53
  byVerb.set(v, []);
60
54
  byVerb.get(v).push(it);
@@ -77,7 +71,6 @@ function groupTools(items) {
77
71
  flush();
78
72
  return out;
79
73
  }
80
- // ── Main App §3 ────────────────────────────────────────────────────────────
81
74
  export function App(props) {
82
75
  const { stdout } = useStdout();
83
76
  const [transcript, setTranscript] = useState(props.initialTranscript ?? []);
@@ -87,17 +80,27 @@ export function App(props) {
87
80
  const [plan, setPlan] = useState([]);
88
81
  const [status, setStatus] = useState({ model: props.initialModel, step: 0, maxSteps: props.maxSteps, usageInput: 0, usageOutput: 0, repairs: 0, status: 'idle', ...props.initialStatus });
89
82
  const [elapsed, setElapsed] = useState(0);
90
- const [queued, setQueued] = useState(null);
83
+ const [queuedInputs, setQueuedInputs] = useState([]);
91
84
  const [expandedGroups, setExpandedGroups] = useState(new Set());
92
85
  const [scrollOffset, setScrollOffset] = useState(0);
93
86
  const streamingIdRef = useRef(null);
94
- const placeholders = ['Message Klyro…', 'Message @file to attach…', 'Type / for commands…', '! runs a shell command…'];
95
- const placeholder = placeholders[0] ?? 'Message Klyro…';
87
+ const placeholder = 'Message Klyro…';
88
+ const width = stdout?.columns ?? 100;
89
+ const height = stdout?.rows ?? 30;
90
+ const isFullscreen = props.isFullscreen ?? false;
91
+ const viewportH = Math.max(5, height - 10);
92
+ const grouped = groupTools(transcript);
93
+ const totalRows = grouped.length + (plan.length > 0 ? 1 : 0) + 2;
94
+ const maxOffset = Math.max(0, totalRows - viewportH);
95
+ const isAtBottom = scrollOffset >= maxOffset;
96
+ const trackH = viewportH;
97
+ const thumbPos = maxOffset === 0 ? 0 : Math.round((scrollOffset / maxOffset) * (trackH - 1));
98
+ const visibleGrouped = isFullscreen ? grouped.slice(scrollOffset, scrollOffset + viewportH) : grouped;
96
99
  useEffect(() => bridge.subscribe((p) => setAwaitingApproval(p !== null)), [bridge]);
97
100
  useEffect(() => {
98
- if (queued && status.status !== 'running' && !awaitingApproval) {
99
- const toSend = queued;
100
- setQueued(null);
101
+ if (queuedInputs.length > 0 && status.status !== 'running' && !awaitingApproval) {
102
+ const toSend = queuedInputs[0];
103
+ setQueuedInputs((prev) => prev.slice(1));
101
104
  setTranscript((prev) => [...prev, { id: nextId('user'), kind: 'text', text: toSend, role: 'user' }]);
102
105
  streamingIdRef.current = null;
103
106
  const cmd = parseSlash(toSend.trim());
@@ -106,35 +109,20 @@ export function App(props) {
106
109
  else
107
110
  void props.onSlash(cmd);
108
111
  }
109
- }, [queued, status.status, awaitingApproval]);
110
- useEffect(() => {
111
- if (status.status !== 'running')
112
- return;
113
- const start = Date.now() - elapsed;
114
- const t = setInterval(() => setElapsed(Date.now() - start), 1000);
115
- return () => clearInterval(t);
116
- }, [status.status, elapsed]);
117
- const append = useCallback((item) => {
118
- if (item.kind !== 'text' || item.role !== 'assistant')
119
- streamingIdRef.current = null;
120
- setTranscript((prev) => [...prev, item]);
121
- }, []);
112
+ }, [queuedInputs, status.status, awaitingApproval]);
113
+ useEffect(() => { if (status.status !== 'running')
114
+ return; const start = Date.now() - elapsed; const t = setInterval(() => setElapsed(Date.now() - start), 1000); return () => clearInterval(t); }, [status.status, elapsed]);
115
+ useEffect(() => { if (isAtBottom)
116
+ setScrollOffset(maxOffset); }, [transcript.length, plan.length, maxOffset, isAtBottom]);
117
+ const append = useCallback((item) => { if (item.kind !== 'text' || item.role !== 'assistant')
118
+ streamingIdRef.current = null; setTranscript((prev) => [...prev, item]); }, []);
122
119
  const appendDelta = useCallback((text) => {
123
120
  if (!text)
124
121
  return;
125
122
  const sid = streamingIdRef.current;
126
- if (sid) {
127
- setTranscript((prev) => {
128
- const idx = prev.findIndex((x) => x.id === sid);
129
- if (idx === -1)
130
- return [...prev, { id: sid, kind: 'text', text, role: 'assistant' }];
131
- const cur = prev[idx];
132
- const next = { ...cur, text: cur.text + text };
133
- const copy = [...prev];
134
- copy[idx] = next;
135
- return copy;
136
- });
137
- }
123
+ if (sid)
124
+ setTranscript((prev) => { const idx = prev.findIndex((x) => x.id === sid); if (idx === -1)
125
+ 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; });
138
126
  else {
139
127
  const id = nextId('stream');
140
128
  streamingIdRef.current = id;
@@ -147,32 +135,18 @@ export function App(props) {
147
135
  const updatePlan = useCallback((p) => setPlan(p), []);
148
136
  const onMountedRef = useRef(props.onMounted);
149
137
  useEffect(() => { onMountedRef.current = props.onMounted; }, [props.onMounted]);
150
- useEffect(() => {
151
- onMountedRef.current?.({ append, appendDelta, updateStatus, updatePlan });
152
- globalThis.__klyroAppAppend = append;
153
- globalThis.__klyroAppendDelta = appendDelta;
154
- globalThis.__klyroAppStatus = updateStatus;
155
- globalThis.__klyroAppPlan = updatePlan;
156
- return () => { delete globalThis.__klyroAppAppend; delete globalThis.__klyroAppendDelta; delete globalThis.__klyroAppStatus; delete globalThis.__klyroAppPlan; };
157
- }, [append, appendDelta, updateStatus, updatePlan]);
138
+ 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]);
158
139
  const toggleGroup = (id) => setExpandedGroups((prev) => { const n = new Set(prev); if (n.has(id))
159
140
  n.delete(id);
160
141
  else
161
142
  n.add(id); return n; });
162
- const width = stdout?.columns ?? 100;
163
- const height = stdout?.rows ?? 30;
164
- const isFullscreen = props.isFullscreen ?? false;
165
- const grouped = groupTools(transcript);
166
- const viewportH = Math.max(5, height - 10);
167
- const totalRows = grouped.length + (plan.length > 0 ? 1 : 0) + 2;
168
- const maxOffset = Math.max(0, totalRows - viewportH);
169
- const isAtBottom = scrollOffset >= maxOffset;
170
- useEffect(() => { if (isAtBottom)
171
- setScrollOffset(maxOffset); }, [transcript.length, plan.length, maxOffset, isAtBottom]);
172
143
  const scrollUp = (n = 3) => setScrollOffset((p) => Math.max(0, p - n));
173
144
  const scrollDown = (n = 3) => setScrollOffset((p) => Math.min(maxOffset, p + n));
174
145
  useInput((inputStr, key) => {
175
- // slider scroll: PageUp/PageDown, Ctrl+U/D, Shift+↑/↓, j/k in vim-like
146
+ if (key.escape && queuedInputs.length > 0) {
147
+ setQueuedInputs((prev) => prev.slice(1));
148
+ return;
149
+ }
176
150
  if (key.pageUp || (key.ctrl && inputStr === 'u')) {
177
151
  scrollUp(5);
178
152
  return;
@@ -192,7 +166,7 @@ export function App(props) {
192
166
  if (awaitingApproval)
193
167
  return;
194
168
  if (key.ctrl && inputStr === 'o') {
195
- const groups = groupTools(transcript).filter((x) => typeof x.verb === 'string');
169
+ const groups = grouped.filter((x) => typeof x.verb === 'string');
196
170
  const last = groups[groups.length - 1];
197
171
  if (last)
198
172
  toggleGroup(last.id);
@@ -207,9 +181,10 @@ export function App(props) {
207
181
  const v = input.trim();
208
182
  if (!v)
209
183
  return;
210
- setQueued(v);
184
+ if (queuedInputs.length >= 3)
185
+ return; // max 3 queued per §6.6
186
+ setQueuedInputs((prev) => [...prev, v]);
211
187
  setInput('');
212
- setTranscript((prev) => [...prev, { id: nextId('queued'), kind: 'text', text: `queued: ${v.slice(0, 80)}`, role: 'assistant' }]);
213
188
  return;
214
189
  }
215
190
  if (key.backspace || key.delete) {
@@ -241,37 +216,37 @@ export function App(props) {
241
216
  if (!key.ctrl && !key.meta)
242
217
  setInput((v) => v + inputStr);
243
218
  });
244
- const isSmall = width < 80;
245
- const ver = props.version ?? '0.1.24';
219
+ const ver = props.version ?? '0.1.27';
246
220
  const rule = g('rule').repeat(Math.max(10, width - 2));
247
- // Derived status right
248
221
  const cost = (status.usageInput / 1000 * 0.003 + status.usageOutput / 1000 * 0.015);
249
222
  const totalTokens = status.usageInput + status.usageOutput;
250
223
  const ctxPct = totalTokens > 0 ? Math.round((totalTokens / 120_000) * 100) : 0;
251
- const baseHints = status.status === 'running' ? 'ctrl+c to stop · enter to queue · ctrl+o expand' : status.status === 'idle' && transcript.length === 0 ? 'shift+tab to cycle · ↑↓ for history · / for commands' : 'enter to send · shift+enter newline · @ to attach';
224
+ 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';
252
225
  const hints = maxOffset > 0 && isFullscreen ? `${baseHints} · PgUp/Dn scroll` : baseHints;
253
- const visibleGrouped = isFullscreen ? grouped.slice(scrollOffset, scrollOffset + viewportH) : grouped;
254
- const trackH = viewportH;
255
- const thumbPos = maxOffset === 0 ? 0 : Math.round((scrollOffset / maxOffset) * (trackH - 1));
226
+ // wrap width: leave 6 cells for guide+indent+scrollbar so words never clump
227
+ const wrapW = Math.max(20, width - 6);
256
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) => {
257
229
  if (item.verb) {
258
230
  const gr = item;
259
231
  const isExpanded = expandedGroups.has(gr.id);
260
- const marker = isExpanded ? g('expanded') : g('collapsed');
261
232
  const verbLine = (() => {
262
233
  if (gr.items.length === 1) {
263
234
  const it = gr.items[0];
264
- const primary = (() => {
265
- try {
266
- const a = JSON.parse(it.args);
267
- return a.path ?? a.pattern ?? a.command?.slice(0, 48) ?? '';
268
- }
269
- catch {
270
- return '';
271
- }
272
- })();
273
- const name = gr.verb === 'Read' && primary ? `Read ${primary.split('/').pop()}` : gr.verb === 'Searched' && primary ? `Searched "${primary}"` : gr.verb === 'Ran' && primary ? `Ran ${primary.split(' ')[0]}` : `${gr.verb} ${primary}`;
274
- return name;
235
+ let p = '';
236
+ try {
237
+ const a = JSON.parse(it.args);
238
+ p = a.path ?? a.pattern ?? a.command?.slice(0, 48) ?? '';
239
+ }
240
+ catch {
241
+ p = '';
242
+ }
243
+ if (gr.verb === 'Read' && p)
244
+ return `Read ${p.split('/').pop()}`;
245
+ if (gr.verb === 'Searched' && p)
246
+ return `Searched "${p}"`;
247
+ if (gr.verb === 'Ran' && p)
248
+ return `Ran ${p.split(' ')[0]}`;
249
+ return `${gr.verb} ${p}`;
275
250
  }
276
251
  if (gr.verb === 'Read')
277
252
  return `Read ${gr.items.length} files`;
@@ -279,30 +254,51 @@ export function App(props) {
279
254
  return `Searched ${gr.items.length} patterns`;
280
255
  if (gr.verb === 'Ran')
281
256
  return `Ran ${gr.items.length} commands`;
282
- if (gr.verb === 'Edited' || gr.verb === 'Created')
283
- return `${gr.verb} ${gr.items.length} files`;
257
+ if (gr.verb === 'Edited')
258
+ return `Edited ${gr.items.length} files`;
284
259
  return `${gr.verb} ${gr.items.length} items`;
285
260
  })();
286
261
  const right = gr.status === 'running' ? `${(elapsed / 1000).toFixed(1)}s` : gr.status === 'error' ? '✗' : `${gr.totalMs}ms`;
287
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: gr.status === 'running' ? tokens.ansi.warn : undefined, children: [isExpanded ? g('expanded') : g('collapsed'), " ", verbLine] }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", right] })] }), isExpanded ? gr.items.map((it) => (_jsxs(Box, { paddingLeft: 4, marginTop: 0, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [g('end'), " "] }), _jsx(Text, { color: it.isError ? tokens.ansi.err : undefined, children: it.name }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", it.args.slice(0, 60)] })] }, it.id))) : null] }, gr.id));
262
+ const marker = isExpanded ? '' : '';
263
+ const markerColor = gr.status === 'error' ? tokens.ansi.err : gr.status === 'running' ? tokens.ansi.warn : tokens.ansi.ok;
264
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: markerColor, children: [marker, " ", verbLine] }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", right] })] }), isExpanded ? gr.items.map((it) => {
265
+ let friendly = '';
266
+ try {
267
+ const a = JSON.parse(it.args);
268
+ const p = a.path ?? a.pattern ?? a.command ?? '';
269
+ const short = p ? String(p).split('/').pop()?.slice(0, 40) ?? p : '';
270
+ if (it.name === 'read_file' && short)
271
+ friendly = `${short} ${it.result ? String(it.result).split('\n').length + ' lines' : ''}`.trim();
272
+ else if (it.name === 'shell_exec' && p)
273
+ friendly = `$ ${String(p).slice(0, 40)}`;
274
+ else if (short)
275
+ friendly = short;
276
+ else
277
+ friendly = it.args.slice(0, 40);
278
+ }
279
+ catch {
280
+ friendly = it.args.slice(0, 40);
281
+ }
282
+ return (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [g('end'), " "] }), _jsx(Text, { color: tokens.ansi.dim, children: friendly }), it.isError ? _jsx(Text, { color: tokens.ansi.err, children: " \u2717" }) : null] }, it.id));
283
+ }) : null] }, gr.id));
288
284
  }
289
285
  const it = item;
290
286
  if (it.kind === 'text' && it.role === 'user') {
291
- return _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsx(Text, { children: it.text })] }, it.id);
287
+ 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);
292
288
  }
293
289
  if (it.kind === 'text') {
294
290
  if (it.text.startsWith('queued:'))
295
291
  return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", it.text, " esc to drop"] }) }, it.id);
296
- 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, marginTop: 0, children: [_jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " "] }), _jsx(Text, { wrap: "wrap", children: it.text })] })] }, it.id));
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));
297
293
  }
298
294
  if (it.kind === 'error')
299
295
  return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
300
296
  if (it.kind === 'policy')
301
- return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " [policy] ", it.action, " ", it.name] }) }, it.id);
297
+ return null;
302
298
  if (it.kind === 'file_changed')
303
299
  return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", g('editsBadge'), " ", it.path, " ", it.op] }) }, it.id);
304
300
  if (it.kind === 'diff')
305
301
  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));
306
302
  return null;
307
- }), 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))), plan.length > 8 ? _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " \u2026 +", plan.length - 8, " more (/todos)"] }) : null] })) : null, status.status === 'done' && transcript.some((x) => x.kind === 'file_changed') ? (_jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", g('editsBadge'), " ", transcript.filter((x) => x.kind === 'file_changed').length, " files \u00B7 /diff"] }) })) : 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, { children: [input || _jsx(Text, { color: tokens.ansi.dim, children: placeholder }), "\u258F"] })] }), _jsx(Text, { color: tokens.ansi.guide, children: rule })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: tokens.ansi.dim, children: hints }), _jsxs(Text, { color: tokens.ansi.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ctx · ` : '', status.status === 'running' ? 'auto mode on ●' : ''] })] })] }));
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: placeholder }), "\u258F"] })] }), _jsx(Text, { color: tokens.ansi.guide, children: rule })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: tokens.ansi.dim, children: hints }), _jsxs(Text, { color: tokens.ansi.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct > 0 ? `${ctxPct}% ctx · ` : '', status.status === 'running' ? 'auto mode on ●' : ''] })] })] }));
308
304
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Klyro — autonomous coding harness CLI that streams from any OpenAI-compatible or Anthropic LLM endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",