klyro 0.1.25 → 0.1.27

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
@@ -106,9 +106,11 @@ export async function startRepl(opts = {}) {
106
106
  pendingQueue.push({ kind: 'plan', plan: p });
107
107
  }
108
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;
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;
112
114
  const enterAlt = () => {
113
115
  if (!isAltScreen)
114
116
  return;
@@ -140,6 +142,7 @@ export async function startRepl(opts = {}) {
140
142
  cwd,
141
143
  initialStatus: { status: 'idle' },
142
144
  approvalBridge: tuiBridge,
145
+ isFullscreen: isAltScreen,
143
146
  onPrompt: async (text) => {
144
147
  inflight = runWithBridge(text);
145
148
  await inflight;
package/dist/tui/app.d.ts CHANGED
@@ -24,5 +24,6 @@ export interface AppProps {
24
24
  updatePlan: (p: PlanStep[]) => void;
25
25
  }) => void;
26
26
  version?: string;
27
+ isFullscreen?: boolean;
27
28
  }
28
29
  export declare function App(props: AppProps): React.JSX.Element;
package/dist/tui/app.js CHANGED
@@ -161,6 +161,7 @@ export function App(props) {
161
161
  n.add(id); return n; });
162
162
  const width = stdout?.columns ?? 100;
163
163
  const height = stdout?.rows ?? 30;
164
+ const isFullscreen = props.isFullscreen ?? false;
164
165
  const grouped = groupTools(transcript);
165
166
  const viewportH = Math.max(5, height - 10);
166
167
  const totalRows = grouped.length + (plan.length > 0 ? 1 : 0) + 2;
@@ -248,11 +249,11 @@ export function App(props) {
248
249
  const totalTokens = status.usageInput + status.usageOutput;
249
250
  const ctxPct = totalTokens > 0 ? Math.round((totalTokens / 120_000) * 100) : 0;
250
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';
251
- const hints = maxOffset > 0 ? `${baseHints} · PgUp/Dn scroll` : baseHints;
252
- const visibleGrouped = grouped.slice(scrollOffset, scrollOffset + viewportH);
252
+ const hints = maxOffset > 0 && isFullscreen ? `${baseHints} · PgUp/Dn scroll` : baseHints;
253
+ const visibleGrouped = isFullscreen ? grouped.slice(scrollOffset, scrollOffset + viewportH) : grouped;
253
254
  const trackH = viewportH;
254
255
  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
+ 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) => {
256
257
  if (item.verb) {
257
258
  const gr = item;
258
259
  const isExpanded = expandedGroups.has(gr.id);
@@ -283,26 +284,25 @@ export function App(props) {
283
284
  return `${gr.verb} ${gr.items.length} items`;
284
285
  })();
285
286
  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
+ 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));
287
288
  }
288
289
  const it = item;
289
290
  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
+ return _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: tokens.ansi.accent, bold: true, children: [g('prompt'), " "] }), _jsx(Text, { children: it.text })] }, it.id);
291
292
  }
292
293
  if (it.kind === 'text') {
293
- // Check if it's queued indicator
294
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));
295
+ 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));
297
297
  }
298
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);
299
+ return _jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { color: tokens.ansi.err, children: [" ", g('guide'), " \u2717 ", it.message] }) }, it.id);
300
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);
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);
302
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);
303
+ 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
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));
305
+ 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
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 ●' : ''] })] })] }));
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 ●' : ''] })] })] }));
308
308
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
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",