klyro 0.1.28 → 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.
Files changed (2) hide show
  1. package/dist/tui/app.js +45 -17
  2. package/package.json +1 -1
package/dist/tui/app.js CHANGED
@@ -80,7 +80,7 @@ export function App(props) {
80
80
  const [plan, setPlan] = useState([]);
81
81
  const [status, setStatus] = useState({ model: props.initialModel, step: 0, maxSteps: props.maxSteps, usageInput: 0, usageOutput: 0, repairs: 0, status: 'idle', ...props.initialStatus });
82
82
  const [elapsed, setElapsed] = useState(0);
83
- const [queued, setQueued] = useState(null);
83
+ const [queuedInputs, setQueuedInputs] = useState([]);
84
84
  const [expandedGroups, setExpandedGroups] = useState(new Set());
85
85
  const [scrollOffset, setScrollOffset] = useState(0);
86
86
  const streamingIdRef = useRef(null);
@@ -97,17 +97,19 @@ export function App(props) {
97
97
  const thumbPos = maxOffset === 0 ? 0 : Math.round((scrollOffset / maxOffset) * (trackH - 1));
98
98
  const visibleGrouped = isFullscreen ? grouped.slice(scrollOffset, scrollOffset + viewportH) : grouped;
99
99
  useEffect(() => bridge.subscribe((p) => setAwaitingApproval(p !== null)), [bridge]);
100
- useEffect(() => { if (queued && status.status !== 'running' && !awaitingApproval) {
101
- const toSend = queued;
102
- setQueued(null);
103
- setTranscript((prev) => [...prev, { id: nextId('user'), kind: 'text', text: toSend, role: 'user' }]);
104
- streamingIdRef.current = null;
105
- const cmd = parseSlash(toSend.trim());
106
- if (cmd.kind === 'prompt')
107
- void props.onPrompt(cmd.text);
108
- else
109
- void props.onSlash(cmd);
110
- } }, [queued, status.status, awaitingApproval]);
100
+ useEffect(() => {
101
+ if (queuedInputs.length > 0 && status.status !== 'running' && !awaitingApproval) {
102
+ const toSend = queuedInputs[0];
103
+ setQueuedInputs((prev) => prev.slice(1));
104
+ setTranscript((prev) => [...prev, { id: nextId('user'), kind: 'text', text: toSend, role: 'user' }]);
105
+ streamingIdRef.current = null;
106
+ const cmd = parseSlash(toSend.trim());
107
+ if (cmd.kind === 'prompt')
108
+ void props.onPrompt(cmd.text);
109
+ else
110
+ void props.onSlash(cmd);
111
+ }
112
+ }, [queuedInputs, status.status, awaitingApproval]);
111
113
  useEffect(() => { if (status.status !== 'running')
112
114
  return; const start = Date.now() - elapsed; const t = setInterval(() => setElapsed(Date.now() - start), 1000); return () => clearInterval(t); }, [status.status, elapsed]);
113
115
  useEffect(() => { if (isAtBottom)
@@ -141,6 +143,10 @@ export function App(props) {
141
143
  const scrollUp = (n = 3) => setScrollOffset((p) => Math.max(0, p - n));
142
144
  const scrollDown = (n = 3) => setScrollOffset((p) => Math.min(maxOffset, p + n));
143
145
  useInput((inputStr, key) => {
146
+ if (key.escape && queuedInputs.length > 0) {
147
+ setQueuedInputs((prev) => prev.slice(1));
148
+ return;
149
+ }
144
150
  if (key.pageUp || (key.ctrl && inputStr === 'u')) {
145
151
  scrollUp(5);
146
152
  return;
@@ -175,9 +181,10 @@ export function App(props) {
175
181
  const v = input.trim();
176
182
  if (!v)
177
183
  return;
178
- setQueued(v);
184
+ if (queuedInputs.length >= 3)
185
+ return; // max 3 queued per §6.6
186
+ setQueuedInputs((prev) => [...prev, v]);
179
187
  setInput('');
180
- setTranscript((prev) => [...prev, { id: nextId('queued'), kind: 'text', text: `queued: ${v.slice(0, 80)}`, role: 'assistant' }]);
181
188
  return;
182
189
  }
183
190
  if (key.backspace || key.delete) {
@@ -252,7 +259,28 @@ export function App(props) {
252
259
  return `${gr.verb} ${gr.items.length} items`;
253
260
  })();
254
261
  const right = gr.status === 'running' ? `${(elapsed / 1000).toFixed(1)}s` : gr.status === 'error' ? '✗' : `${gr.totalMs}ms`;
255
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { children: [isExpanded ? g('expanded') : g('collapsed'), " ", verbLine] }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", right] })] }), isExpanded ? gr.items.map((it) => (_jsxs(Box, { paddingLeft: 4, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [g('end'), " "] }), _jsx(Text, { children: it.name }), _jsxs(Text, { color: tokens.ansi.dim, children: [" ", it.args.slice(0, 50)] })] }, 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));
256
284
  }
257
285
  const it = item;
258
286
  if (it.kind === 'text' && it.role === 'user') {
@@ -266,11 +294,11 @@ export function App(props) {
266
294
  if (it.kind === 'error')
267
295
  return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
268
296
  if (it.kind === 'policy')
269
- 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;
270
298
  if (it.kind === 'file_changed')
271
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);
272
300
  if (it.kind === 'diff')
273
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));
274
302
  return null;
275
- }), 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] }), 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 ●' : ''] })] })] }));
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 ●' : ''] })] })] }));
276
304
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "0.1.28",
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",