klyro 0.1.23 → 0.1.25

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/cli/repl.js CHANGED
@@ -105,12 +105,35 @@ export async function startRepl(opts = {}) {
105
105
  else
106
106
  pendingQueue.push({ kind: 'plan', plan: p });
107
107
  }
108
+ // ── Full-screen takeover like OpenCode/Claude Code (§3) ──
109
+ // When useTui, enter alternate screen so `klyro` owns the whole terminal.
110
+ // Ctrl+C (SIGINT) + /quit both leave the alt screen and restore the shell like opencode.
111
+ const isAltScreen = useTui && !!process.stdout.isTTY;
112
+ const enterAlt = () => {
113
+ if (!isAltScreen)
114
+ return;
115
+ try {
116
+ process.stdout.write('\x1b[?1049h\x1b[?25l'); // alt screen + hide cursor
117
+ process.stdout.write('\x1b[H\x1b[2J'); // home + clear
118
+ }
119
+ catch { /* ignore */ }
120
+ };
121
+ const leaveAlt = () => {
122
+ if (!isAltScreen)
123
+ return;
124
+ try {
125
+ process.stdout.write('\x1b[?25h\x1b[?1049l'); // show cursor + leave alt
126
+ }
127
+ catch { /* ignore */ }
128
+ };
108
129
  // Declare app before handler to avoid TDZ; handler added after render
109
130
  let app;
110
131
  let sigintHandler;
111
132
  // Level 9 — session store for TUI (one store per REPL)
112
133
  const tuiStore = getDefaultSessionStore();
113
134
  let tuiSessionId;
135
+ if (isAltScreen)
136
+ enterAlt();
114
137
  app = render(React.createElement(App, {
115
138
  initialModel: model,
116
139
  maxSteps: opts.maxSteps ?? 30,
@@ -140,8 +163,9 @@ export async function startRepl(opts = {}) {
140
163
  }
141
164
  pendingQueue.length = 0;
142
165
  },
143
- }));
166
+ }), { patchConsole: false });
144
167
  // Install SIGINT handler only after app exists (avoids TDZ) and use once
168
+ // On Ctrl+C in alt-screen, leave alt before exit so shell is restored
145
169
  sigintHandler = () => {
146
170
  ac.abort();
147
171
  queuedStatus({ status: 'aborted' });
@@ -149,6 +173,7 @@ export async function startRepl(opts = {}) {
149
173
  app?.unmount();
150
174
  }
151
175
  catch { /* ignore */ }
176
+ leaveAlt();
152
177
  };
153
178
  process.once('SIGINT', sigintHandler);
154
179
  async function runWithBridge(text) {
@@ -524,18 +549,9 @@ export async function startRepl(opts = {}) {
524
549
  }
525
550
  case 'compact': {
526
551
  queuedAppend({ id: `compact-${Date.now()}`, kind: 'text', text: 'Compacting context…', role: 'assistant' });
527
- // 8.3 compaction would summarize oldest 60% — stub emits compacted event
528
552
  queuedStatus({ status: 'running' });
529
553
  return;
530
554
  }
531
- case 'compact':
532
- queuedAppend({
533
- id: `stub-${Date.now()}`,
534
- kind: 'text',
535
- text: `/compact is a stub in this build. (persistence integration pending)`,
536
- role: 'assistant',
537
- });
538
- return;
539
555
  case 'model': {
540
556
  const next = cmd.model?.trim();
541
557
  if (!next) {
@@ -563,9 +579,11 @@ export async function startRepl(opts = {}) {
563
579
  const onExit = () => {
564
580
  if (sigintHandler)
565
581
  process.removeListener('SIGINT', sigintHandler);
582
+ leaveAlt();
566
583
  resolve(ac.signal.aborted ? 130 : 0);
567
584
  };
568
585
  if (!app) {
586
+ leaveAlt();
569
587
  resolve(1);
570
588
  return;
571
589
  }
package/dist/tui/app.js CHANGED
@@ -89,6 +89,7 @@ export function App(props) {
89
89
  const [elapsed, setElapsed] = useState(0);
90
90
  const [queued, setQueued] = useState(null);
91
91
  const [expandedGroups, setExpandedGroups] = useState(new Set());
92
+ const [scrollOffset, setScrollOffset] = useState(0);
92
93
  const streamingIdRef = useRef(null);
93
94
  const placeholders = ['Message Klyro…', 'Message @file to attach…', 'Type / for commands…', '! runs a shell command…'];
94
95
  const placeholder = placeholders[0] ?? 'Message Klyro…';
@@ -158,11 +159,38 @@ export function App(props) {
158
159
  n.delete(id);
159
160
  else
160
161
  n.add(id); return n; });
162
+ const width = stdout?.columns ?? 100;
163
+ const height = stdout?.rows ?? 30;
164
+ const grouped = groupTools(transcript);
165
+ const viewportH = Math.max(5, height - 10);
166
+ const totalRows = grouped.length + (plan.length > 0 ? 1 : 0) + 2;
167
+ const maxOffset = Math.max(0, totalRows - viewportH);
168
+ const isAtBottom = scrollOffset >= maxOffset;
169
+ useEffect(() => { if (isAtBottom)
170
+ setScrollOffset(maxOffset); }, [transcript.length, plan.length, maxOffset, isAtBottom]);
171
+ const scrollUp = (n = 3) => setScrollOffset((p) => Math.max(0, p - n));
172
+ const scrollDown = (n = 3) => setScrollOffset((p) => Math.min(maxOffset, p + n));
161
173
  useInput((inputStr, key) => {
174
+ // slider scroll: PageUp/PageDown, Ctrl+U/D, Shift+↑/↓, j/k in vim-like
175
+ if (key.pageUp || (key.ctrl && inputStr === 'u')) {
176
+ scrollUp(5);
177
+ return;
178
+ }
179
+ if (key.pageDown || (key.ctrl && inputStr === 'd')) {
180
+ scrollDown(5);
181
+ return;
182
+ }
183
+ if (key.upArrow && (key.shift || key.ctrl)) {
184
+ scrollUp(1);
185
+ return;
186
+ }
187
+ if (key.downArrow && (key.shift || key.ctrl)) {
188
+ scrollDown(1);
189
+ return;
190
+ }
162
191
  if (awaitingApproval)
163
192
  return;
164
193
  if (key.ctrl && inputStr === 'o') {
165
- // Ctrl+O toggle most recent group in live window
166
194
  const groups = groupTools(transcript).filter((x) => typeof x.verb === 'string');
167
195
  const last = groups[groups.length - 1];
168
196
  if (last)
@@ -212,69 +240,69 @@ export function App(props) {
212
240
  if (!key.ctrl && !key.meta)
213
241
  setInput((v) => v + inputStr);
214
242
  });
215
- const width = stdout?.columns ?? 100;
216
- const height = stdout?.rows ?? 30;
217
243
  const isSmall = width < 80;
218
- const ver = props.version ?? '0.1.19';
244
+ const ver = props.version ?? '0.1.24';
219
245
  const rule = g('rule').repeat(Math.max(10, width - 2));
220
246
  // Derived status right
221
247
  const cost = (status.usageInput / 1000 * 0.003 + status.usageOutput / 1000 * 0.015);
222
248
  const totalTokens = status.usageInput + status.usageOutput;
223
249
  const ctxPct = totalTokens > 0 ? Math.round((totalTokens / 120_000) * 100) : 0;
224
- const hints = 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';
225
- // Grouped transcript
226
- const grouped = groupTools(transcript);
227
- return (_jsxs(Box, { flexDirection: "column", width: width, height: height - 1, children: [_jsx(Header, { cwd: props.cwd, model: status.model, version: ver, width: width }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingX: 0, children: [grouped.length === 0 ? (_jsx(Text, { color: tokens.ansi.dim, children: placeholder })) : grouped.map((item) => {
228
- if (item.verb) {
229
- const gr = item;
230
- const isExpanded = expandedGroups.has(gr.id);
231
- const marker = isExpanded ? g('expanded') : g('collapsed');
232
- const verbLine = (() => {
233
- if (gr.items.length === 1) {
234
- const it = gr.items[0];
235
- const primary = (() => {
236
- try {
237
- const a = JSON.parse(it.args);
238
- return a.path ?? a.pattern ?? a.command?.slice(0, 48) ?? '';
239
- }
240
- catch {
241
- return '';
250
+ 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';
251
+ const hints = maxOffset > 0 ? `${baseHints} · PgUp/Dn scroll` : baseHints;
252
+ const visibleGrouped = grouped.slice(scrollOffset, scrollOffset + viewportH);
253
+ const trackH = viewportH;
254
+ const thumbPos = maxOffset === 0 ? 0 : Math.round((scrollOffset / maxOffset) * (trackH - 1));
255
+ return (_jsxs(Box, { flexDirection: "column", width: width, height: height - 1, children: [_jsx(Header, { cwd: props.cwd, model: status.model, version: ver, width: width }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingX: 0, children: [grouped.length === 0 ? (_jsx(Text, { color: tokens.ansi.dim, children: placeholder })) : visibleGrouped.map((item) => {
256
+ if (item.verb) {
257
+ const gr = item;
258
+ const isExpanded = expandedGroups.has(gr.id);
259
+ const marker = isExpanded ? g('expanded') : g('collapsed');
260
+ const verbLine = (() => {
261
+ if (gr.items.length === 1) {
262
+ const it = gr.items[0];
263
+ const primary = (() => {
264
+ try {
265
+ const a = JSON.parse(it.args);
266
+ return a.path ?? a.pattern ?? a.command?.slice(0, 48) ?? '';
267
+ }
268
+ catch {
269
+ return '';
270
+ }
271
+ })();
272
+ 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}`;
273
+ return name;
242
274
  }
275
+ if (gr.verb === 'Read')
276
+ return `Read ${gr.items.length} files`;
277
+ if (gr.verb === 'Searched')
278
+ return `Searched ${gr.items.length} patterns`;
279
+ if (gr.verb === 'Ran')
280
+ return `Ran ${gr.items.length} commands`;
281
+ if (gr.verb === 'Edited' || gr.verb === 'Created')
282
+ return `${gr.verb} ${gr.items.length} files`;
283
+ return `${gr.verb} ${gr.items.length} items`;
243
284
  })();
244
- 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}`;
245
- return name;
285
+ const right = gr.status === 'running' ? `${(elapsed / 1000).toFixed(1)}s` : gr.status === 'error' ? '' : `${gr.totalMs}ms`;
286
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, 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: 2, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('end'), " "] }), _jsxs(Text, { color: it.isError ? tokens.ansi.err : tokens.ansi.dim, children: [it.name, " ", it.args.slice(0, 80)] }), it.result ? _jsxs(Text, { color: tokens.ansi.dim, children: [" \u00B7 ", String(it.result).slice(0, 80)] }) : null] }, it.id))) : null] }, gr.id));
287
+ }
288
+ const it = item;
289
+ if (it.kind === 'text' && it.role === 'user') {
290
+ return _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsx(Text, { children: it.text })] }, it.id);
291
+ }
292
+ if (it.kind === 'text') {
293
+ // Check if it's queued indicator
294
+ if (it.text.startsWith('queued:'))
295
+ return _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", it.text, " esc to drop"] }) }, it.id);
296
+ return (_jsxs(Box, { flexDirection: "column", 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, { children: it.text })] })] }, it.id));
246
297
  }
247
- if (gr.verb === 'Read')
248
- return `Read ${gr.items.length} files`;
249
- if (gr.verb === 'Searched')
250
- return `Searched ${gr.items.length} patterns`;
251
- if (gr.verb === 'Ran')
252
- return `Ran ${gr.items.length} commands`;
253
- if (gr.verb === 'Edited' || gr.verb === 'Created')
254
- return `${gr.verb} ${gr.items.length} files`;
255
- return `${gr.verb} ${gr.items.length} items`;
256
- })();
257
- const right = gr.status === 'running' ? `${(elapsed / 1000).toFixed(1)}s` : gr.status === 'error' ? '✗' : `${gr.totalMs}ms`;
258
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, 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: 2, children: [_jsxs(Text, { color: tokens.ansi.guide, children: [" ", g('end'), " "] }), _jsxs(Text, { color: it.isError ? tokens.ansi.err : tokens.ansi.dim, children: [it.name, " ", it.args.slice(0, 80)] }), it.result ? _jsxs(Text, { color: tokens.ansi.dim, children: [" \u00B7 ", String(it.result).slice(0, 80)] }) : null] }, it.id))) : null] }, gr.id));
259
- }
260
- const it = item;
261
- if (it.kind === 'text' && it.role === 'user') {
262
- return _jsxs(Box, { children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsx(Text, { children: it.text })] }, it.id);
263
- }
264
- if (it.kind === 'text') {
265
- // Check if it's queued indicator
266
- if (it.text.startsWith('queued:'))
267
- return _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", it.text, " esc to drop"] }) }, it.id);
268
- return (_jsxs(Box, { flexDirection: "column", 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, { children: it.text })] })] }, it.id));
269
- }
270
- if (it.kind === 'error')
271
- return _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
272
- if (it.kind === 'policy')
273
- return _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " [policy] ", it.action, " ", it.name] }) }, it.id);
274
- if (it.kind === 'file_changed')
275
- return _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", g('editsBadge'), " ", it.path, " ", it.op] }) }, it.id);
276
- if (it.kind === 'diff')
277
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsx(Text, { bold: true, children: it.summary ?? 'Diff' }), it.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: tokens.ansi.soft, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { 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));
278
- return null;
279
- }), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, 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: 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, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", g('editsBadge'), " ", transcript.filter((x) => x.kind === 'file_changed').length, " files \u00B7 /diff"] }) })) : 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 ●' : ''] })] })] }));
298
+ if (it.kind === 'error')
299
+ return _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
300
+ if (it.kind === 'policy')
301
+ return _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " [policy] ", it.action, " ", it.name] }) }, it.id);
302
+ if (it.kind === 'file_changed')
303
+ return _jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", g('editsBadge'), " ", it.path, " ", it.op] }) }, it.id);
304
+ if (it.kind === 'diff')
305
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsx(Text, { bold: true, children: it.summary ?? 'Diff' }), it.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: tokens.ansi.soft, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { 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
+ return null;
307
+ }), status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, 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: 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, children: _jsxs(Text, { color: tokens.ansi.dim, children: [" ", g('guide'), " ", g('editsBadge'), " ", transcript.filter((x) => x.kind === 'file_changed').length, " files \u00B7 /diff"] }) })) : null] }), _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))) })] }), _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 ●' : ''] })] })] }));
280
308
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
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",