omniharness-cli 0.1.81 → 0.1.82
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/ui/sidebar.js +105 -0
- package/dist/ui/terminalInterface.js +69 -27
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data shaping for the sidebar. The layout lives in the Ink component; every
|
|
3
|
+
* decision it has to make is here so it can be tested without a terminal.
|
|
4
|
+
*
|
|
5
|
+
* The shape follows OpenCode's session sidebar (MIT, sst/opencode): a fixed
|
|
6
|
+
* narrow panel carrying what the conversation column should not have to —
|
|
7
|
+
* session identity, what is running, and what is queued. The implementation is
|
|
8
|
+
* ours; Ink has no scrollbox and no absolute positioning, so the behaviour
|
|
9
|
+
* differs where it has to.
|
|
10
|
+
*/
|
|
11
|
+
/** Fixed width of the panel, matching the measure OpenCode settled on. */
|
|
12
|
+
export const SIDEBAR_WIDTH = 34;
|
|
13
|
+
/**
|
|
14
|
+
* Below this the terminal cannot hold a sidebar and a readable conversation
|
|
15
|
+
* at once, so the toggle shows the sidebar *instead of* the conversation
|
|
16
|
+
* rather than beside it. Ink cannot overlay one panel on another, which is
|
|
17
|
+
* what OpenCode does at this size.
|
|
18
|
+
*/
|
|
19
|
+
export const SIDEBAR_MIN_SPLIT = 92;
|
|
20
|
+
/**
|
|
21
|
+
* How the sidebar should be shown, given the terminal width and whether the
|
|
22
|
+
* user has it toggled on.
|
|
23
|
+
*/
|
|
24
|
+
export function sidebarMode(width, wanted) {
|
|
25
|
+
if (!wanted)
|
|
26
|
+
return 'hidden';
|
|
27
|
+
return width >= SIDEBAR_MIN_SPLIT ? 'split' : 'replace';
|
|
28
|
+
}
|
|
29
|
+
/** Width left for the conversation once the sidebar has taken its share. */
|
|
30
|
+
export function conversationWidth(width, mode) {
|
|
31
|
+
if (mode !== 'split')
|
|
32
|
+
return width;
|
|
33
|
+
return Math.max(20, width - SIDEBAR_WIDTH - 1);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Render a token count the way it is read: exact while small, then thousands.
|
|
37
|
+
* A six-digit number in a 34-column panel is noise, not information.
|
|
38
|
+
*/
|
|
39
|
+
export function compactTokens(n) {
|
|
40
|
+
if (!Number.isFinite(n) || n < 0)
|
|
41
|
+
return '0';
|
|
42
|
+
if (n < 1000)
|
|
43
|
+
return String(Math.round(n));
|
|
44
|
+
if (n < 1_000_000) {
|
|
45
|
+
const k = n / 1000;
|
|
46
|
+
return `${k < 10 ? k.toFixed(1) : Math.round(k)}k`;
|
|
47
|
+
}
|
|
48
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Cost to four places under a cent, two above. Sub-cent spend is the normal
|
|
52
|
+
* case for a single turn and "$0.00" would report every one of them as free.
|
|
53
|
+
*/
|
|
54
|
+
export function formatCost(usd) {
|
|
55
|
+
if (!Number.isFinite(usd) || usd <= 0)
|
|
56
|
+
return '$0';
|
|
57
|
+
return usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The usage rows, or none at all when nothing has been spent yet. An empty
|
|
61
|
+
* panel section reads as broken; a missing one reads as "not yet".
|
|
62
|
+
*/
|
|
63
|
+
export function usageRows(usage) {
|
|
64
|
+
const rows = [];
|
|
65
|
+
const inTokens = usage.tokensIn ?? 0;
|
|
66
|
+
const outTokens = usage.tokensOut ?? 0;
|
|
67
|
+
if (inTokens > 0 || outTokens > 0) {
|
|
68
|
+
// Only name a direction that was measured: "1.2k in · 0 out" claims zero
|
|
69
|
+
// output rather than admitting it is not counted here.
|
|
70
|
+
const parts = [];
|
|
71
|
+
if (inTokens > 0)
|
|
72
|
+
parts.push(`${compactTokens(inTokens)} in`);
|
|
73
|
+
if (outTokens > 0)
|
|
74
|
+
parts.push(`${compactTokens(outTokens)} out`);
|
|
75
|
+
rows.push({ label: 'tokens', value: parts.join(' · ') });
|
|
76
|
+
}
|
|
77
|
+
if ((usage.costUSD ?? 0) > 0)
|
|
78
|
+
rows.push({ label: 'cost', value: formatCost(usage.costUSD ?? 0) });
|
|
79
|
+
if ((usage.requests ?? 0) > 0)
|
|
80
|
+
rows.push({ label: 'calls', value: String(usage.requests) });
|
|
81
|
+
return rows;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Task queue rows. Plain-word markers rather than glyphs, matching the rest of
|
|
85
|
+
* the interface. The active item is marked so the eye finds it without colour,
|
|
86
|
+
* which matters under NO_COLOR.
|
|
87
|
+
*/
|
|
88
|
+
export function todoRows(todos, limit, width) {
|
|
89
|
+
return todos.slice(0, Math.max(0, limit)).map((todo) => ({
|
|
90
|
+
marker: todo.status === 'done' ? 'x' : todo.status === 'active' ? '>' : '-',
|
|
91
|
+
title: clip(todo.title, Math.max(4, width)),
|
|
92
|
+
active: todo.status === 'active',
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
/** How many queued items were not shown, for a "+N more" line. */
|
|
96
|
+
export function overflowCount(total, shown) {
|
|
97
|
+
return Math.max(0, total - shown);
|
|
98
|
+
}
|
|
99
|
+
export function clip(text, width) {
|
|
100
|
+
const runes = [...text.replace(/\s+/g, ' ')];
|
|
101
|
+
if (runes.length <= width)
|
|
102
|
+
return runes.join('');
|
|
103
|
+
return `${runes.slice(0, Math.max(0, width - 1)).join('')}…`;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=sidebar.js.map
|
|
@@ -9,6 +9,7 @@ import { renderMarkdown } from './markdown.js';
|
|
|
9
9
|
import { looksLikeDiff, diffSegments } from './diff.js';
|
|
10
10
|
import { palette } from './palette.js';
|
|
11
11
|
import { capabilityLine, recentRows, shortenPath, twoColumn } from './home.js';
|
|
12
|
+
import { conversationWidth, overflowCount, sidebarMode, SIDEBAR_WIDTH, todoRows, usageRows, clip as clipRow } from './sidebar.js';
|
|
12
13
|
import { contextMeter, meterBar } from './modelWindows.js';
|
|
13
14
|
import { BEL, SYNC_QUERY, isSyncOutputReply, osc9Notify, osc52Copy, shouldNudgeOnFinish, wrapSynchronizedOutput } from './termcaps.js';
|
|
14
15
|
import { KITTY_POP, KITTY_PUSH, KITTY_QUERY, isEncodedKey, isKittyQueryResponse, parseRawKey } from './keys.js';
|
|
@@ -180,6 +181,30 @@ function TranscriptEntry({ line, width, fallbackModel }) {
|
|
|
180
181
|
const bullet = line.role === 'user' ? '>' : line.role === 'error' ? '!' : '-';
|
|
181
182
|
return _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { bold: true, color: colorFor(line.role), children: [bullet, " ", label] }), rows.map((segments, index) => _jsx(SegmentText, { segments: segments, role: line.role }, index)), line.saved ? _jsxs(Text, { dimColor: true, children: [" ", line.saved] }) : null] });
|
|
182
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* The session panel, shown beside the live region on Ctrl+B.
|
|
186
|
+
*
|
|
187
|
+
* It carries what the conversation column should not have to: what is
|
|
188
|
+
* running, what is queued, and what the turn has cost. The shape follows
|
|
189
|
+
* OpenCode's session sidebar (MIT); the implementation is ours, because Ink
|
|
190
|
+
* has neither a scrollbox nor absolute positioning, so it cannot scroll
|
|
191
|
+
* independently or overlay the conversation.
|
|
192
|
+
*
|
|
193
|
+
* Sections are absent rather than empty. A heading with nothing under it
|
|
194
|
+
* reads as something failing to load.
|
|
195
|
+
*/
|
|
196
|
+
function SidebarPanel(props) {
|
|
197
|
+
const { width, model, mode, perm, workspace, usage, agents, todos, skills, plugins } = props;
|
|
198
|
+
const inner = Math.max(10, width - 4);
|
|
199
|
+
// Two columns of slack, not one. At an exact fit Ink still wraps, and a
|
|
200
|
+
// wrapped value in a 34-column panel shows as a stray blank line under a
|
|
201
|
+
// bare label — measured, not reasoned about.
|
|
202
|
+
const value = Math.max(6, inner - LABEL_WIDTH - 2);
|
|
203
|
+
const usageLines = usageRows(usage);
|
|
204
|
+
const shownTodos = todoRows(todos, 6, inner - 2);
|
|
205
|
+
const moreTodos = overflowCount(todos.length, shownTodos.length);
|
|
206
|
+
return _jsxs(Box, { flexDirection: "column", width: width, borderStyle: "round", borderColor: PALETTE.muted, paddingX: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "session" }), _jsx(Field, { label: "model", children: clip(model, value) }), _jsx(Field, { label: "mode", children: _jsx(Text, { color: MODE_ACCENT[mode], children: mode }) }), _jsx(Field, { label: "perms", children: _jsx(Text, { color: mode === 'crazy' ? PALETTE.error : PERM_COLOR(perm), children: mode === 'crazy' ? 'bypass' : PERM_LABEL[perm] }) }), _jsx(Field, { label: "cwd", children: shortenPath(workspace, value) }), skills > 0 && _jsx(Field, { label: "skills", children: clip(plugins > 0 ? `${skills} · ${plugins} plugins` : String(skills), value) }), usageLines.length > 0 && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "usage" }), usageLines.map((row) => _jsx(Field, { label: row.label, children: clip(row.value, value) }, row.label))] }), agents.length > 0 && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "agents" }), agents.slice(-6).map((lane, index) => _jsxs(Text, { children: [_jsx(Text, { color: AGENT_COLORS[index % AGENT_COLORS.length], children: lane.status === 'error' ? 'FAIL' : lane.status === 'done' ? 'ok ' : '.. ' }), _jsx(Text, { dimColor: true, children: clipRow(lane.label, inner - 5) })] }, lane.id))] }), shownTodos.length > 0 && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "plan" }), shownTodos.map((row) => _jsxs(Text, { children: [_jsxs(Text, { color: row.active ? PALETTE.accent : PALETTE.muted, children: [row.marker, " "] }), _jsx(Text, { dimColor: !row.active, children: row.title })] }, row.title)), moreTodos > 0 && _jsxs(Text, { dimColor: true, children: ["+", moreTodos, " more"] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Ctrl+B hides this" }) })] });
|
|
207
|
+
}
|
|
183
208
|
/**
|
|
184
209
|
* Width of the dim label column on the home screen. The value gets whatever is
|
|
185
210
|
* left, and every caller has to subtract this: a value sized to the whole box
|
|
@@ -220,7 +245,7 @@ export function TerminalInterface({ engine }) {
|
|
|
220
245
|
const { stdin } = useStdin();
|
|
221
246
|
const [width, setWidth] = useState(() => widthOf(stdout));
|
|
222
247
|
const [edit, setEdit] = useState({ value: '', cursor: 0 });
|
|
223
|
-
const inputWidth = Math.max(16, Math.min(width, MAX_MEASURE) - 12);
|
|
248
|
+
const inputWidth = Math.max(16, Math.min(width, MAX_MEASURE) - 12); // recomputed below once the split is known
|
|
224
249
|
// Settled transcript. Rendered once each into <Static> — the terminal's own
|
|
225
250
|
// scrollback is the history; there is no in-app viewport to scroll.
|
|
226
251
|
const [lines, setLines] = useState(() => engine.state.messages.map(lineFromMessage));
|
|
@@ -256,6 +281,9 @@ export function TerminalInterface({ engine }) {
|
|
|
256
281
|
// Recent snapshots for the home screen. Read once on mount and left
|
|
257
282
|
// alone: the home screen is only on screen before the first turn.
|
|
258
283
|
const [recentSessions, setRecentSessions] = useState([]);
|
|
284
|
+
// Off by default: it is a second thing to read, and the conversation is
|
|
285
|
+
// the first. Ctrl+B brings it in.
|
|
286
|
+
const [sidebarWanted, setSidebarWanted] = useState(false);
|
|
259
287
|
const [layoutDebug, setLayoutDebug] = useState(false);
|
|
260
288
|
const syncRestoreRef = useRef(null);
|
|
261
289
|
const pushLine = (line) => setLines((current) => [...current, line]);
|
|
@@ -696,6 +724,9 @@ export function TerminalInterface({ engine }) {
|
|
|
696
724
|
case 'ctrlE':
|
|
697
725
|
cycleMode();
|
|
698
726
|
return;
|
|
727
|
+
case 'ctrlB':
|
|
728
|
+
setSidebarWanted((on) => !on);
|
|
729
|
+
return;
|
|
699
730
|
case 'up':
|
|
700
731
|
if (sessionsOpen) {
|
|
701
732
|
setSessionsIndex((current) => clamp(current - 1, 0, Math.max(0, sessionsList.length - 1)));
|
|
@@ -789,6 +820,10 @@ export function TerminalInterface({ engine }) {
|
|
|
789
820
|
applyAction({ kind: 'ctrlE' });
|
|
790
821
|
return;
|
|
791
822
|
}
|
|
823
|
+
if (key.ctrl && value.toLowerCase() === 'b') {
|
|
824
|
+
applyAction({ kind: 'ctrlB' });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
792
827
|
if (pickerOpen) {
|
|
793
828
|
if (key.escape) {
|
|
794
829
|
applyAction({ kind: 'escape' });
|
|
@@ -922,9 +957,13 @@ export function TerminalInterface({ engine }) {
|
|
|
922
957
|
// input box and the text you are reading at opposite ends of the screen.
|
|
923
958
|
// Hold the content to a readable measure and centre it instead; below that
|
|
924
959
|
// measure the gutter collapses and nothing changes.
|
|
925
|
-
const
|
|
960
|
+
const sideMode = sidebarMode(width, sidebarWanted);
|
|
961
|
+
// With the panel beside it the conversation gets what is left, so the
|
|
962
|
+
// centred measure applies to the pair rather than to the text alone.
|
|
963
|
+
const measure = Math.min(width, sideMode === 'split' ? MAX_MEASURE + SIDEBAR_WIDTH + 1 : MAX_MEASURE);
|
|
926
964
|
const gutter = Math.max(2, Math.floor((width - measure) / 2));
|
|
927
|
-
const
|
|
965
|
+
const convoWidth = conversationWidth(measure, sideMode);
|
|
966
|
+
const contentWidth = Math.max(20, convoWidth - 6);
|
|
928
967
|
const terminalRows = stdout.rows ?? 24;
|
|
929
968
|
const metrics = engine.client.snapshotMetrics();
|
|
930
969
|
const meter = contextMeter(metrics.compression.inputTokens, engine.state.activeModel, metrics.fallback.activeProvider);
|
|
@@ -945,29 +984,32 @@ export function TerminalInterface({ engine }) {
|
|
|
945
984
|
const liveThinkView = liveThinkLines.slice(-Math.max(2, Math.floor(liveBudget / 2)));
|
|
946
985
|
const liveAnswerView = liveAnswerLines.slice(-liveBudget);
|
|
947
986
|
const doneAgents = agents.filter((lane) => lane.status === 'done').length;
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
987
|
+
const panel = _jsx(SidebarPanel, { width: SIDEBAR_WIDTH, model: engine.state.activeModel, mode: mode, perm: permMode, workspace: engine.state.workspace.root, usage: { tokensIn: metrics.compression.inputTokens, requests: metrics.requestCount }, agents: agents, todos: taskQueue, skills: engine.skills.length, plugins: new Set(engine.skills.map((skill) => skill.source).filter((source) => source !== undefined)).size });
|
|
988
|
+
return _jsxs(Box, { flexDirection: "column", width: width, paddingLeft: gutter, paddingRight: gutter, children: [_jsx(Static, { items: lines, children: (line, index) => _jsx(TranscriptEntry, { line: line, width: contentWidth, fallbackModel: engine.state.activeModel }, index) }, staticKey), sideMode === 'replace' && _jsx(Box, { marginBottom: 1, children: panel }), _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", children: [_jsxs(Box, { flexDirection: "column", width: sideMode === 'split' ? convoWidth : undefined, flexGrow: sideMode === 'split' ? 0 : 1, children: [lines.length === 0 && !busy && _jsx(Hero, { width: contentWidth + 2, endpoint: engine.client.endpoint ?? 'omniroute', model: engine.state.activeModel, mode: mode, perm: permMode, workspace: engine.state.workspace.root, sessions: recentSessions, skills: engine.skills.length, plugins: new Set(engine.skills.map((skill) => skill.source).filter((source) => source !== undefined)).size, mcpTools: engine.mcpTools.length }), liveThink !== '' && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.warn, children: "\u00B7 thinking" }), liveThinkView.map((segments, index) => _jsx(SegmentText, { segments: segments, role: "thinking" }, index))] }), liveAnswer !== '' && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.accent, children: engine.state.activeModel }), liveAnswerView.map((segments, index) => _jsx(SegmentText, { segments: segments, role: "assistant" }, index))] }), toolCards.slice(-5).map((card) => {
|
|
989
|
+
const expanded = expandedTool === card.id;
|
|
990
|
+
const dot = card.status === 'running' ? _jsx(Text, { color: PALETTE.warn, children: ".." }) : card.status === 'error' ? _jsx(Text, { color: PALETTE.error, children: "FAIL" }) : _jsx(Text, { color: PALETTE.success, children: "ok" });
|
|
991
|
+
const head = card.name === 'run_command'
|
|
992
|
+
? `$ ${clip(card.target || '…', Math.max(10, contentWidth - 20))}`
|
|
993
|
+
: `${toolVerb(card.name)}${card.target ? ` ${clip(card.target, Math.max(10, contentWidth - 24))}` : ''}`;
|
|
994
|
+
return _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [dot, " ", head, expanded ? ' Ctrl+T collapse' : ''] }), expanded && renderToolBody(card, contentWidth, PALETTE)] }, card.id);
|
|
995
|
+
}), sideMode === 'hidden' && agents.length > 0 && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.error, paddingX: 2, marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: PALETTE.error, children: "swarm" }), _jsxs(Text, { dimColor: true, children: [doneAgents, "/", agents.length, " lanes done"] })] }), agents.map((lane, index) => {
|
|
996
|
+
const color = AGENT_COLORS[index % AGENT_COLORS.length];
|
|
997
|
+
const glyph = lane.status === 'done' ? 'ok' : lane.status === 'error' ? 'FAIL' : lane.status === 'working' ? '..' : '--';
|
|
998
|
+
const detail = lane.note ?? (lane.label !== lane.id ? lane.label : lane.status);
|
|
999
|
+
return _jsxs(Text, { color: color, children: [glyph, " ", lane.id, " ", _jsx(Text, { dimColor: true, children: clip(detail, Math.max(12, contentWidth - 8)) })] }, lane.id);
|
|
1000
|
+
})] }), sideMode === 'hidden' && taskQueue.length > 0 && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.accent, paddingX: 2, marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: PALETTE.accent, children: "plan" }), _jsxs(Text, { dimColor: true, children: [taskQueue.filter((item) => item.status === 'done').length, "/", taskQueue.length, " done"] })] }), taskQueue.slice(-6).map((item) => {
|
|
1001
|
+
const marker = item.status === 'done' ? 'ok' : item.status === 'active' ? '>' : '-';
|
|
1002
|
+
const color = item.status === 'done' ? PALETTE.success : item.status === 'active' ? PALETTE.accent : undefined;
|
|
1003
|
+
return _jsxs(Text, { color: color, dimColor: item.status === 'done', children: [marker, " ", clip(item.title, contentWidth - 4)] }, item.id);
|
|
1004
|
+
})] }), engine.state.preview && _jsxs(Text, { color: PALETTE.success, children: ["preview live \u00B7 ", engine.state.preview.url] }), sessionsOpen && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.info, paddingX: 2, marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.info, children: "saved sessions" }), _jsx(Text, { dimColor: true, children: "up/down navigate \u00B7 enter resume \u00B7 esc close" }), sessionsList.map((session, index) => (_jsxs(Text, { color: index === sessionsIndex ? PALETTE.info : undefined, children: [index === sessionsIndex ? '> ' : ' ', session.name, session.savedAt ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", session.savedAt] }) : null] }, session.name)))] }), pickerOpen && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.accent, paddingX: 2, marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.accent, children: "choose an OmniRoute model" }), _jsx(Text, { dimColor: true, children: "up/down / j k navigate \u00B7 enter select \u00B7 esc close" }), pickerError && _jsx(Text, { color: PALETTE.error, children: clip(pickerError, contentWidth) }), pickerItems.length === 0 && !pickerError && _jsx(Text, { dimColor: true, children: "no models returned by OmniRoute." }), pickerItems.map((item, index) => {
|
|
1005
|
+
const header = index === 0 || pickerItems[index - 1].group !== item.group
|
|
1006
|
+
? _jsx(Text, { dimColor: true, bold: true, children: item.group === 'combos' ? 'your combos' : 'auto engine' }, `h-${item.group}`)
|
|
1007
|
+
: null;
|
|
1008
|
+
return _jsxs(Box, { flexDirection: "column", children: [header, _jsxs(Text, { color: index === pickerIndex ? PALETTE.accent : undefined, children: [index === pickerIndex ? '> ' : ' ', item.id, item.strategy ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", item.strategy] }) : null, item.id === engine.state.activeModel ? ' *' : ''] })] }, item.id);
|
|
1009
|
+
})] }), approval && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.warn, paddingX: 2, marginTop: 1, children: [_jsxs(Text, { bold: true, color: PALETTE.warn, children: ["approve ", approval.tool, "?"] }), _jsxs(Text, { dimColor: true, children: ["args: ", clip(JSON.stringify(approval.input), contentWidth)] }), approval.scopes.map((scope, index) => _jsxs(Text, { dimColor: true, children: [" ", index + 1, " \u00B7 ", clip(scope.label, Math.max(12, contentWidth - 6))] }, scope.id)), _jsxs(Text, { dimColor: true, children: ["y allow once \u00B7 n deny \u00B7 t always allow \u00B7 1\u2013", approval.scopes.length, " pick a trust scope"] })] }), layoutDebug && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.muted, paddingX: 2, marginTop: 1, children: [_jsxs(Text, { bold: true, dimColor: true, children: ["layout \u00B7 ", width, "\u00D7", terminalRows, " \u00B7 Ctrl+L to hide"] }), _jsxs(Text, { dimColor: true, children: ["static entries ", lines.length, " \u00B7 live budget ", liveBudget, " \u00B7 think ", liveThinkLines.length, " \u00B7 answer ", liveAnswerLines.length] }), _jsxs(Text, { dimColor: true, children: ["plan ", taskQueue.length, " \u00B7 swarm ", agents.length, " \u00B7 tool cards ", toolCards.length, " \u00B7 editor rows ", editorLayout.lines.length] })] }), queued && _jsxs(Text, { color: PALETTE.warn, children: ["queued \u00B7 ", clip(queued, Math.max(12, contentWidth - 12))] }), _jsx(Box, { borderStyle: "round", borderColor: error ? PALETTE.error : modeAccent, paddingX: 1, marginTop: 1, flexDirection: "column", children: edit.value === ''
|
|
1010
|
+
? _jsxs(Text, { color: modeAccent, children: ['>', " ", _jsx(Text, { dimColor: true, children: busy ? 'type to queue the next task' : 'describe the work and press enter' })] })
|
|
1011
|
+
: editorLayout.lines.map((text, index) => _jsxs(Text, { color: modeAccent, children: [index === 0 ? '> ' : ' ', text] }, index)) }), kitty !== null && _jsx(Text, { dimColor: true, children: kitty ? 'kitty protocol active — Shift+Enter makes a new line' : 'this terminal can\'t distinguish Shift+Enter from Enter — use Ctrl+J for a new line' }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: busy ? modeAccent : PALETTE.muted, children: [busy && _jsx(Spinner, { type: "line" }), busy ? ' ' : '', phase, elapsed ? ` · ${elapsed}` : '', agents.length > 0 ? ` · swarm ${doneAgents}/${agents.length}` : ''] }), _jsxs(Text, { color: PALETTE.muted, children: [_jsx(Text, { color: modeAccent, children: mode }), _jsx(Text, { children: " \u00B7 " }), _jsx(Text, { color: mode === 'crazy' ? PALETTE.error : PERM_COLOR(permMode), children: mode === 'crazy' ? 'bypass' : PERM_LABEL[permMode] }), _jsxs(Text, { children: [" \u00B7 ", engine.state.activeModel] }), metrics.fallback.activeProvider ? _jsxs(Text, { children: [" \u00B7 via ", metrics.fallback.activeProvider] }) : null, contextLabel ? _jsxs(Text, { color: meterColor, children: [" \u00B7 ", contextLabel] }) : null] })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: sideMode === 'split'
|
|
1012
|
+
? `Ctrl+B panel · Ctrl+${modeKey} mode · Ctrl+C ${busy ? 'cancel' : 'quit'} · /help`
|
|
1013
|
+
: `Enter send · Ctrl+J newline · Ctrl+O models · Ctrl+${modeKey} mode · Shift+Tab perms · Ctrl+B panel · Ctrl+T tool · Ctrl+Y copy · Ctrl+C ${busy ? 'cancel' : 'quit'} · /help` }), compression ? _jsxs(Text, { dimColor: true, children: ["saved ", compression, metrics.remainingQuota !== undefined ? ` · quota ${metrics.remainingQuota}` : ''] }) : null] })] })] }), sideMode === 'split' && _jsx(Box, { marginLeft: 1, children: panel })] })] });
|
|
972
1014
|
}
|
|
973
1015
|
//# sourceMappingURL=terminalInterface.js.map
|