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 +28 -10
- package/dist/tui/app.js +85 -57
- package/package.json +1 -1
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.
|
|
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
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
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 (
|
|
248
|
-
return
|
|
249
|
-
if (
|
|
250
|
-
return
|
|
251
|
-
if (
|
|
252
|
-
return
|
|
253
|
-
if (
|
|
254
|
-
return
|
|
255
|
-
return
|
|
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
|
}
|