klyro 0.1.34 → 0.1.36

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,9 @@ 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
- // Detect alt-screen support (§19.5): Windows Terminal (WT_SESSION), VS Code, xterm, or non-Windows.
110
- // Fallback to inline (native scroll) on conhost/mintty so text isn't clipped — degrade don't break (§1.8).
111
- const supportsAlt = !!process.env.WT_SESSION || process.env.TERM_PROGRAM === 'vscode' || (process.env.TERM ?? '').includes('xterm') || process.platform !== 'win32' || process.env.KLYRO_ALT_SCREEN === '1';
112
- const noAlt = process.env.KLYRO_NO_ALT === '1' || process.env.NO_ALT === '1';
113
- const isAltScreen = useTui && !!process.stdout.isTTY && supportsAlt && !noAlt;
108
+ // ── Full-screen takeover like OpenCode always when klyro in TTY (user explicitly wants it)
109
+ // Scroll now works correctly via internal viewport, not native terminal scroll
110
+ const isAltScreen = useTui && !!process.stdout.isTTY && process.env.KLYRO_NO_ALT !== '1';
114
111
  const enterAlt = () => {
115
112
  if (!isAltScreen)
116
113
  return;
package/dist/tui/app.js CHANGED
@@ -19,7 +19,7 @@ function Header({ cwd, model, version, width }) {
19
19
  return '';
20
20
  } })();
21
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.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}` : ''] })] }));
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: "\u2502 /help /config /clear /exit" }) : null] }), _jsxs(Text, { color: tokens.colors.dim, children: [model, "[200k] \u00B7 API Usage Billing"] }), _jsxs(Text, { color: tokens.colors.dim, children: [cwd, branch ? ` · ${branch}` : ''] })] }));
23
23
  }
24
24
  function verbForTool(name) {
25
25
  if (name === 'read_file')
@@ -81,14 +81,14 @@ function MarkdownText({ text, dim, width }) {
81
81
  let idx = 0;
82
82
  while ((m = re.exec(text))) {
83
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++}`));
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
86
  last = m.index + m[0].length;
87
87
  }
88
88
  if (last < text.length)
89
- parts.push(_jsx(Text, { color: dim ? tokens.ansi.dim : undefined, wrap: "wrap", children: text.slice(last) }, `t-${idx++}`));
89
+ parts.push(_jsx(Text, { color: dim ? tokens.colors.dim : undefined, wrap: "wrap", children: text.slice(last) }, `t-${idx++}`));
90
90
  if (parts.length === 0)
91
- return _jsx(Text, { color: dim ? tokens.ansi.dim : undefined, wrap: "wrap", children: text });
91
+ return _jsx(Text, { color: dim ? tokens.colors.dim : undefined, wrap: "wrap", children: text });
92
92
  // Render as single line with bold segments — Ink will wrap the parent Box
93
93
  return _jsx(Text, { wrap: "wrap", children: parts });
94
94
  }
@@ -110,7 +110,15 @@ export function App(props) {
110
110
  const isFullscreen = props.isFullscreen ?? false;
111
111
  const grouped = groupTools(transcript);
112
112
  const viewportH = Math.max(5, height - 10);
113
- const totalRows = grouped.length + (plan.length > 0 ? 1 : 0) + 2;
113
+ const estLines = (s) => Math.max(1, Math.ceil(s.length / Math.max(20, width - 6)));
114
+ const totalRows = grouped.reduce((sum, it) => {
115
+ if (it.verb)
116
+ return sum + 1 + ((it.items.length > 1 && expandedGroups.has(it.id)) ? it.items.length : 0);
117
+ const t = it;
118
+ if (t.kind === 'text')
119
+ return sum + estLines(t.text) + 1;
120
+ return sum + 2;
121
+ }, 0) + (plan.length > 0 ? plan.length + 1 : 0) + 2;
114
122
  const maxOffset = Math.max(0, totalRows - viewportH);
115
123
  const isAtBottom = scrollOffset >= maxOffset;
116
124
  const trackH = viewportH;
@@ -243,7 +251,7 @@ export function App(props) {
243
251
  const ctxPct = totalTokens > 0 ? Math.round((totalTokens / 120_000) * 100) : 0;
244
252
  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
253
  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.ansi.dim, children: "Message Klyro\u2026" })) : visibleGrouped.map((item) => {
254
+ 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\u2026" })) : visibleGrouped.map((item) => {
247
255
  if (item.verb) {
248
256
  const gr = item;
249
257
  const isExpanded = expandedGroups.has(gr.id);
@@ -278,8 +286,8 @@ export function App(props) {
278
286
  })();
279
287
  const right = gr.status === 'running' ? `${(elapsed / 1000).toFixed(1)}s` : gr.status === 'error' ? '✗' : `${gr.totalMs}ms`;
280
288
  const marker = isExpanded ? '▼' : '✓';
281
- const markerColor = gr.status === 'error' ? tokens.ansi.err : gr.status === 'running' ? tokens.ansi.warn : tokens.ansi.ok;
282
- 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) => {
289
+ const markerColor = gr.status === 'error' ? tokens.colors.err : gr.status === 'running' ? tokens.colors.warn : tokens.colors.ok;
290
+ 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) => {
283
291
  let friendly = '';
284
292
  try {
285
293
  const a = JSON.parse(it.args);
@@ -297,25 +305,25 @@ export function App(props) {
297
305
  catch {
298
306
  friendly = it.args.slice(0, 40);
299
307
  }
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));
308
+ 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));
301
309
  }) : null] }, gr.id));
302
310
  }
303
311
  const it = item;
304
312
  if (it.kind === 'text' && it.role === 'user') {
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);
313
+ 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);
306
314
  }
307
315
  if (it.kind === 'text') {
308
316
  // 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));
317
+ 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));
310
318
  }
311
319
  if (it.kind === 'error')
312
- return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
320
+ return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.colors.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
313
321
  if (it.kind === 'policy')
314
322
  return null;
315
323
  if (it.kind === 'file_changed')
316
- 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);
324
+ 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);
317
325
  if (it.kind === 'diff')
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));
326
+ 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));
319
327
  return null;
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 ●' : ''] })] })] }));
328
+ }), 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\u2026" }), "\u258F"] })] }), _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 ●' : ''] })] })] }));
321
329
  }
@@ -18,21 +18,21 @@ export declare const tokens: {
18
18
  readonly diffDelBg: "#2A1212";
19
19
  };
20
20
  readonly ansi: {
21
- readonly accent: "yellow";
21
+ readonly accent: "yellowBright";
22
22
  readonly accentBold: "yellowBright";
23
- readonly fg: "white";
24
- readonly soft: "whiteBright";
23
+ readonly fg: "whiteBright";
24
+ readonly soft: "white";
25
25
  readonly dim: "gray";
26
26
  readonly guide: "gray";
27
- readonly ok: "yellow";
27
+ readonly ok: "yellowBright";
28
28
  readonly err: "red";
29
- readonly warn: "yellow";
29
+ readonly warn: "yellowBright";
30
30
  readonly info: "white";
31
31
  readonly border: "gray";
32
32
  readonly muted: "gray";
33
- readonly success: "yellow";
33
+ readonly success: "yellowBright";
34
34
  readonly error: "red";
35
- readonly warning: "yellow";
35
+ readonly warning: "yellowBright";
36
36
  };
37
37
  };
38
38
  export declare const glyphs: {
@@ -18,21 +18,21 @@ export const tokens = {
18
18
  diffDelBg: '#2A1212',
19
19
  },
20
20
  ansi: {
21
- accent: 'yellow', // #E8843C → 209 → yellow
21
+ accent: 'yellowBright', // #E8843C → vivid orange
22
22
  accentBold: 'yellowBright',
23
- fg: 'white', // #FFFFFF
24
- soft: 'whiteBright', // #F5F5F5
25
- dim: 'gray', // #9A9A9A
23
+ fg: 'whiteBright', // #FFFFFF — pure white
24
+ soft: 'white',
25
+ dim: 'gray',
26
26
  guide: 'gray',
27
- ok: 'yellow', // orange for in white/orange theme
27
+ ok: 'yellowBright', // ✓ orange vivid
28
28
  err: 'red',
29
- warn: 'yellow', // orange
29
+ warn: 'yellowBright', // spinner orange vivid
30
30
  info: 'white',
31
31
  border: 'gray',
32
32
  muted: 'gray',
33
- success: 'yellow',
33
+ success: 'yellowBright',
34
34
  error: 'red',
35
- warning: 'yellow',
35
+ warning: 'yellowBright',
36
36
  },
37
37
  };
38
38
  export const glyphs = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
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",