klyro 0.1.56 → 0.1.57
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/tui/app.js +2 -2
- package/dist/tui/app.test.js +54 -0
- package/package.json +1 -1
package/dist/tui/app.js
CHANGED
|
@@ -27,7 +27,7 @@ function Header({ cwd, model, version, width }) {
|
|
|
27
27
|
}
|
|
28
28
|
}, [cwd]);
|
|
29
29
|
const showLinks = width >= 120;
|
|
30
|
-
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, " \u00B7 ", cwd] }), branch ? _jsxs(Text, { color: tokens.colors.dim, children: ["\u2387 ", branch] }) : null] }));
|
|
30
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, flexShrink: 0, 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, " \u00B7 ", cwd] }), branch ? _jsxs(Text, { color: tokens.colors.dim, children: ["\u2387 ", branch] }) : null] }));
|
|
31
31
|
}
|
|
32
32
|
function verbForTool(name) {
|
|
33
33
|
if (name === 'read_file')
|
|
@@ -751,5 +751,5 @@ export function App(props) {
|
|
|
751
751
|
if (it.kind === 'diff')
|
|
752
752
|
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));
|
|
753
753
|
return null;
|
|
754
|
-
}) : null, showThinking && status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.colors.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: tokens.colors.accent, children: [_jsx(Spinner, { type: "dots" }), " "] }), _jsx(Text, { color: tokens.colors.dim, children: "Thinking... (esc to cancel)" }), _jsxs(Text, { color: tokens.colors.dim, children: [" ", (elapsed / 1000).toFixed(1), "s"] })] })) : null, showPlan && 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, i) => (_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'), " ", i + 1, ". ", p.title] })] }, p.id)))] })) : null, showQueued && 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] }), pinned && pendingNew > 0 ? (_jsx(Box, { justifyContent: "flex-end", paddingX: 1, marginTop: -1, children: _jsxs(Text, { backgroundColor: tokens.colors.accentSoft, color: tokens.colors.accent, bold: true, children: [' ↓ ', pendingNew >= 1000 ? '999+ new' : `${pendingNew} new`, ' '] }) })) : null, slashSuggest.length > 0 ? (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [slashSuggest.map((s, i) => (_jsxs(Text, { color: i === 0 ? tokens.colors.accent : tokens.colors.dim, children: [i === 0 ? '▸' : ' ', " /", s.name, " \u2014 ", s.hint] }, s.name))), _jsx(Text, { color: tokens.colors.dim, children: " tab to complete" })] })) : null, _jsx(ApprovalModal, { bridge: bridge }), _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..." }), "|"] })] }), _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: [status.status === 'running' ? (_jsxs(Text, { color: tokens.colors.accent, children: [_jsx(Spinner, { type: "dots" }), " working \u00B7 "] })) : null, baseHints, maxTop > 0 && isFullscreen ? ' · PgUp/Dn scroll' : ''] }), _jsxs(Text, { color: tokens.colors.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct, "% ctx \u00B7 ", status.model, status.status === 'running' ? ' ●' : ''] })] })] }));
|
|
754
|
+
}) : null, showThinking && status.status === 'running' && !streamingIdRef.current ? (_jsxs(Box, { paddingLeft: 2, marginBottom: 1, children: [_jsxs(Text, { color: tokens.colors.guide, children: [" ", g('guide'), " "] }), _jsxs(Text, { color: tokens.colors.accent, children: [_jsx(Spinner, { type: "dots" }), " "] }), _jsx(Text, { color: tokens.colors.dim, children: "Thinking... (esc to cancel)" }), _jsxs(Text, { color: tokens.colors.dim, children: [" ", (elapsed / 1000).toFixed(1), "s"] })] })) : null, showPlan && 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, i) => (_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'), " ", i + 1, ". ", p.title] })] }, p.id)))] })) : null, showQueued && 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] }), pinned && pendingNew > 0 ? (_jsx(Box, { justifyContent: "flex-end", paddingX: 1, marginTop: -1, flexShrink: 0, children: _jsxs(Text, { backgroundColor: tokens.colors.accentSoft, color: tokens.colors.accent, bold: true, children: [' ↓ ', pendingNew >= 1000 ? '999+ new' : `${pendingNew} new`, ' '] }) })) : null, slashSuggest.length > 0 ? (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, flexShrink: 0, children: [slashSuggest.map((s, i) => (_jsxs(Text, { color: i === 0 ? tokens.colors.accent : tokens.colors.dim, children: [i === 0 ? '▸' : ' ', " /", s.name, " \u2014 ", s.hint] }, s.name))), _jsx(Text, { color: tokens.colors.dim, children: " tab to complete" })] })) : null, _jsx(ApprovalModal, { bridge: bridge }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, 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..." }), "|"] })] }), _jsx(Text, { color: tokens.colors.guide, children: g('rule').repeat(Math.max(10, width - 2)) })] }), _jsxs(Box, { justifyContent: "space-between", flexShrink: 0, children: [_jsxs(Text, { color: tokens.colors.dim, children: [status.status === 'running' ? (_jsxs(Text, { color: tokens.colors.accent, children: [_jsx(Spinner, { type: "dots" }), " working \u00B7 "] })) : null, baseHints, maxTop > 0 && isFullscreen ? ' · PgUp/Dn scroll' : ''] }), _jsxs(Text, { color: tokens.colors.dim, children: [cost > 0 ? `$${cost.toFixed(2)} · ` : '', ctxPct, "% ctx \u00B7 ", status.model, status.status === 'running' ? ' ●' : ''] })] })] }));
|
|
755
755
|
}
|
package/dist/tui/app.test.js
CHANGED
|
@@ -464,6 +464,60 @@ describe('App', () => {
|
|
|
464
464
|
hooks.clearThinking();
|
|
465
465
|
await waitForAbsent(lastFrame, /weighing two approaches/);
|
|
466
466
|
});
|
|
467
|
+
it('empty state: composer pinned to bottom, header first (TEST 1)', async () => {
|
|
468
|
+
const { lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { }, isFullscreen: true }));
|
|
469
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
470
|
+
const rows = (lastFrame() ?? '').split('\n');
|
|
471
|
+
expect(rows[0]).toMatch(/KLYRO/);
|
|
472
|
+
const nonEmpty = rows.map((r, i) => ({ r, i })).filter((x) => x.r.trim().length > 0);
|
|
473
|
+
const last = nonEmpty[nonEmpty.length - 1];
|
|
474
|
+
expect(last.r).toMatch(/enter to send|for history|to attach/);
|
|
475
|
+
const placeholder = rows.findIndex((r) => r.includes('Message Klyro'));
|
|
476
|
+
expect(placeholder).toBeGreaterThanOrEqual(0);
|
|
477
|
+
expect(placeholder).toBeLessThan(last.i);
|
|
478
|
+
});
|
|
479
|
+
it('multiline input grows upward, status stays last (TEST 5)', async () => {
|
|
480
|
+
const { stdin, lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { }, isFullscreen: true }));
|
|
481
|
+
stdin.write('build authentication');
|
|
482
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
483
|
+
stdin.write('\x1b[13;2u');
|
|
484
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
485
|
+
stdin.write('add tests');
|
|
486
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
487
|
+
const rows = (lastFrame() ?? '').split('\n');
|
|
488
|
+
expect(rows.join('\n')).toContain('build authentication');
|
|
489
|
+
expect(rows.join('\n')).toContain('add tests');
|
|
490
|
+
const nonEmpty = rows.map((r, i) => ({ r, i })).filter((x) => x.r.trim().length > 0);
|
|
491
|
+
const last = nonEmpty[nonEmpty.length - 1];
|
|
492
|
+
expect(last.r).toMatch(/enter to send|for history|to attach/);
|
|
493
|
+
expect(rows.length).toBeLessThanOrEqual(32);
|
|
494
|
+
});
|
|
495
|
+
it('tool events aggregate, no raw internals leak (TEST 13)', async () => {
|
|
496
|
+
let hooks = null;
|
|
497
|
+
const { lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { }, isFullscreen: true, onMounted: (h) => {
|
|
498
|
+
hooks = { append: h.append, updateTool: h.updateTool };
|
|
499
|
+
} }));
|
|
500
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
501
|
+
const tools = [
|
|
502
|
+
['read_file', 'c1', '{"path":"a.ts"}'],
|
|
503
|
+
['read_file', 'c2', '{"path":"b.ts"}'],
|
|
504
|
+
['read_file', 'c3', '{"path":"c.ts"}'],
|
|
505
|
+
['shell_exec', 'c4', '{"command":"npm test"}'],
|
|
506
|
+
['shell_exec', 'c5', '{"command":"npm run build"}'],
|
|
507
|
+
];
|
|
508
|
+
for (const [name, id, args] of tools) {
|
|
509
|
+
hooks.append({ id: `t-${id}`, kind: 'tool', name, id_call: id, args, status: 'running' });
|
|
510
|
+
}
|
|
511
|
+
await waitForMatch(lastFrame, /Read 3 files/);
|
|
512
|
+
for (const [, id] of tools) {
|
|
513
|
+
hooks.updateTool(id, { result: 'ok', isError: false, latencyMs: 5, status: 'done' });
|
|
514
|
+
}
|
|
515
|
+
const frame = await waitForMatch(lastFrame, /Ran 2 commands/);
|
|
516
|
+
expect(frame).toContain('Read 3 files');
|
|
517
|
+
expect(frame).not.toContain('tool_call');
|
|
518
|
+
expect(frame).not.toContain('{"path"');
|
|
519
|
+
expect(frame).not.toContain('queued:');
|
|
520
|
+
});
|
|
467
521
|
it('Shift+Up / Shift+Down scroll by one line', async () => {
|
|
468
522
|
const { stdin, lastFrame } = render(_jsx(App, { initialModel: "m", maxSteps: 10, cwd: "/test", onPrompt: async () => { }, onSlash: async () => { }, isFullscreen: true, initialTranscript: makeInitialTranscript(25) }));
|
|
469
523
|
await new Promise((r) => setTimeout(r, 50));
|
package/package.json
CHANGED