cvc-tui 0.4.0 → 0.4.2

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.
Files changed (133) hide show
  1. package/dist/entry.js +71148 -61
  2. package/package.json +2 -2
  3. package/dist/app/completion.js +0 -102
  4. package/dist/app/createGatewayEventHandler.js +0 -508
  5. package/dist/app/createSlashHandler.js +0 -101
  6. package/dist/app/delegationStore.js +0 -51
  7. package/dist/app/gatewayContext.js +0 -17
  8. package/dist/app/historyStore.js +0 -123
  9. package/dist/app/inputBuffer.js +0 -120
  10. package/dist/app/inputSelectionStore.js +0 -8
  11. package/dist/app/inputStore.js +0 -28
  12. package/dist/app/interfaces.js +0 -6
  13. package/dist/app/overlayStore.js +0 -40
  14. package/dist/app/promptStore.js +0 -44
  15. package/dist/app/queueStore.js +0 -25
  16. package/dist/app/scroll.js +0 -44
  17. package/dist/app/setupHandoff.js +0 -28
  18. package/dist/app/slash/commands/core.js +0 -479
  19. package/dist/app/slash/commands/debug.js +0 -44
  20. package/dist/app/slash/commands/ops.js +0 -498
  21. package/dist/app/slash/commands/session.js +0 -431
  22. package/dist/app/slash/commands/setup.js +0 -20
  23. package/dist/app/slash/commands/toggles.js +0 -40
  24. package/dist/app/slash/registry.js +0 -18
  25. package/dist/app/slash/types.js +0 -1
  26. package/dist/app/spawnHistoryStore.js +0 -105
  27. package/dist/app/turnController.js +0 -650
  28. package/dist/app/turnStore.js +0 -48
  29. package/dist/app/uiStore.js +0 -36
  30. package/dist/app/useComposerState.js +0 -265
  31. package/dist/app/useConfigSync.js +0 -144
  32. package/dist/app/useInputHandlers.js +0 -403
  33. package/dist/app/useLongRunToolCharms.js +0 -50
  34. package/dist/app/useMainApp.js +0 -629
  35. package/dist/app/useSessionLifecycle.js +0 -175
  36. package/dist/app/useSubmission.js +0 -287
  37. package/dist/app.js +0 -15
  38. package/dist/banner.js +0 -57
  39. package/dist/components/agentsOverlay.js +0 -474
  40. package/dist/components/appChrome.js +0 -252
  41. package/dist/components/appLayout.js +0 -121
  42. package/dist/components/appOverlays.js +0 -65
  43. package/dist/components/branding.js +0 -97
  44. package/dist/components/fpsOverlay.js +0 -22
  45. package/dist/components/helpHint.js +0 -21
  46. package/dist/components/markdown.js +0 -501
  47. package/dist/components/maskedPrompt.js +0 -12
  48. package/dist/components/messageLine.js +0 -82
  49. package/dist/components/modelPicker.js +0 -254
  50. package/dist/components/overlayControls.js +0 -30
  51. package/dist/components/overlays/confirmPrompt.js +0 -25
  52. package/dist/components/overlays/helpOverlay.js +0 -76
  53. package/dist/components/overlays/historySearch.js +0 -49
  54. package/dist/components/overlays/modelPicker.js +0 -60
  55. package/dist/components/overlays/overlayUtils.js +0 -19
  56. package/dist/components/overlays/secretPrompt.js +0 -36
  57. package/dist/components/overlays/sessionPicker.js +0 -93
  58. package/dist/components/overlays/skillsHub.js +0 -71
  59. package/dist/components/prompts.js +0 -95
  60. package/dist/components/queuedMessages.js +0 -24
  61. package/dist/components/sessionPicker.js +0 -130
  62. package/dist/components/skillsHub.js +0 -165
  63. package/dist/components/streamingAssistant.js +0 -35
  64. package/dist/components/streamingMarkdown.js +0 -144
  65. package/dist/components/textInput.js +0 -794
  66. package/dist/components/themed.js +0 -12
  67. package/dist/components/thinking.js +0 -496
  68. package/dist/components/todoPanel.js +0 -40
  69. package/dist/components/transcript.js +0 -22
  70. package/dist/config/env.js +0 -18
  71. package/dist/config/limits.js +0 -22
  72. package/dist/config/timing.js +0 -18
  73. package/dist/content/charms.js +0 -5
  74. package/dist/content/faces.js +0 -21
  75. package/dist/content/fortunes.js +0 -29
  76. package/dist/content/hotkeys.js +0 -38
  77. package/dist/content/placeholders.js +0 -15
  78. package/dist/content/setup.js +0 -14
  79. package/dist/content/verbs.js +0 -41
  80. package/dist/domain/details.js +0 -53
  81. package/dist/domain/messages.js +0 -63
  82. package/dist/domain/paths.js +0 -16
  83. package/dist/domain/providers.js +0 -11
  84. package/dist/domain/roles.js +0 -6
  85. package/dist/domain/slash.js +0 -11
  86. package/dist/domain/usage.js +0 -1
  87. package/dist/domain/viewport.js +0 -33
  88. package/dist/gateway/client.js +0 -312
  89. package/dist/gatewayClient.js +0 -574
  90. package/dist/gatewayTypes.js +0 -1
  91. package/dist/hooks/useCompletion.js +0 -86
  92. package/dist/hooks/useGitBranch.js +0 -58
  93. package/dist/hooks/useInputHistory.js +0 -12
  94. package/dist/hooks/useQueue.js +0 -57
  95. package/dist/hooks/useVirtualHistory.js +0 -401
  96. package/dist/lib/circularBuffer.js +0 -43
  97. package/dist/lib/clipboard.js +0 -126
  98. package/dist/lib/editor.js +0 -41
  99. package/dist/lib/editor.test.js +0 -58
  100. package/dist/lib/emoji.js +0 -49
  101. package/dist/lib/externalCli.js +0 -11
  102. package/dist/lib/forceTruecolor.js +0 -26
  103. package/dist/lib/fpsStore.js +0 -36
  104. package/dist/lib/gracefulExit.js +0 -29
  105. package/dist/lib/history.js +0 -69
  106. package/dist/lib/inputMetrics.js +0 -143
  107. package/dist/lib/liveProgress.js +0 -51
  108. package/dist/lib/liveProgress.test.js +0 -89
  109. package/dist/lib/mathUnicode.js +0 -685
  110. package/dist/lib/memory.js +0 -123
  111. package/dist/lib/memoryMonitor.js +0 -76
  112. package/dist/lib/messages.js +0 -3
  113. package/dist/lib/messages.test.js +0 -25
  114. package/dist/lib/osc52.js +0 -53
  115. package/dist/lib/perfPane.js +0 -94
  116. package/dist/lib/platform.js +0 -312
  117. package/dist/lib/precisionWheel.js +0 -25
  118. package/dist/lib/reasoning.js +0 -39
  119. package/dist/lib/rpc.js +0 -26
  120. package/dist/lib/subagentTree.js +0 -287
  121. package/dist/lib/syntax.js +0 -89
  122. package/dist/lib/terminalModes.js +0 -46
  123. package/dist/lib/terminalParity.js +0 -48
  124. package/dist/lib/terminalSetup.js +0 -321
  125. package/dist/lib/text.js +0 -203
  126. package/dist/lib/text.test.js +0 -18
  127. package/dist/lib/todo.js +0 -2
  128. package/dist/lib/todo.test.js +0 -22
  129. package/dist/lib/viewportStore.js +0 -82
  130. package/dist/lib/virtualHeights.js +0 -61
  131. package/dist/lib/wheelAccel.js +0 -143
  132. package/dist/theme.js +0 -398
  133. package/dist/types.js +0 -1
@@ -1,252 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- // @ts-nocheck
3
- // SPDX-License-Identifier: MIT
4
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
5
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
6
- import { Box, Text } from '@cvc/ink';
7
- import { useStore } from '@nanostores/react';
8
- import { useEffect, useMemo, useRef, useState } from 'react';
9
- import unicodeSpinners from 'unicode-animations';
10
- import { $delegationState } from '../app/delegationStore.js';
11
- import { useTurnSelector } from '../app/turnStore.js';
12
- import { $uiState } from '../app/uiStore.js';
13
- import { FACES } from '../content/faces.js';
14
- import { VERBS } from '../content/verbs.js';
15
- import { fmtDuration } from '../domain/messages.js';
16
- import { stickyPromptFromViewport } from '../domain/viewport.js';
17
- import { buildSubagentTree, treeTotals, widthByDepth } from '../lib/subagentTree.js';
18
- import { fmtK } from '../lib/text.js';
19
- import { useScrollbarSnapshot, useViewportSnapshot } from '../lib/viewportStore.js';
20
- const FACE_TICK_MS = 2500;
21
- const HEART_COLORS = ['#ff5fa2', '#ff4d6d'];
22
- // Keep verb segment width stable so status-bar content to the right doesn't
23
- // jitter when the ticker rotates between short/long verbs.
24
- export const VERB_PAD_LEN = VERBS.reduce((max, v) => Math.max(max, v.length), 0) + 1; // + ellipsis
25
- export const padVerb = (verb) => `${verb}…`.padEnd(VERB_PAD_LEN, ' ');
26
- // Compact alternates for the `emoji` and `ascii` indicator styles.
27
- // Each entry is a fixed-width (display-width) glyph.
28
- const EMOJI_FRAMES = ['⚕ ', '🌀', '🤔', '✨', '🍵', '🔮'];
29
- const ASCII_FRAMES = ['|', '/', '-', '\\'];
30
- // Faster tick for spinner-style indicators — they read as motion only
31
- // at frame rates closer to their authored interval.
32
- const SPINNER_TICK_MS = 100;
33
- const renderIndicator = (style, tick) => {
34
- if (style === 'kaomoji') {
35
- return { frame: FACES[tick % FACES.length] ?? '', intervalMs: FACE_TICK_MS, showVerb: true };
36
- }
37
- if (style === 'emoji') {
38
- return {
39
- frame: EMOJI_FRAMES[tick % EMOJI_FRAMES.length] ?? '⚕ ',
40
- intervalMs: SPINNER_TICK_MS * 6,
41
- showVerb: true
42
- };
43
- }
44
- if (style === 'ascii') {
45
- return {
46
- frame: ASCII_FRAMES[tick % ASCII_FRAMES.length] ?? '|',
47
- intervalMs: SPINNER_TICK_MS,
48
- showVerb: true
49
- };
50
- }
51
- // 'unicode' — braille spinner (fixed 1-col). Authored interval is
52
- // ~80ms; honour it but bound below at a safe minimum so React
53
- // re-renders stay reasonable. This style is for users who want
54
- // the cleanest possible status, so no verb rotation either.
55
- const spinner = unicodeSpinners.braille;
56
- const frame = spinner.frames[tick % spinner.frames.length] ?? '⠋';
57
- return { frame, intervalMs: Math.max(SPINNER_TICK_MS, spinner.interval), showVerb: false };
58
- };
59
- function FaceTicker({ color, startedAt }) {
60
- const ui = useStore($uiState);
61
- const style = ui.indicatorStyle;
62
- const [tick, setTick] = useState(() => Math.floor(Math.random() * 1000));
63
- const [verbTick, setVerbTick] = useState(() => Math.floor(Math.random() * VERBS.length));
64
- const [now, setNow] = useState(() => Date.now());
65
- // Pre-compute cadence + verb-visibility for the active style so an
66
- // `/indicator` switch re-arms the interval (and skips the verb timer
67
- // for verb-less styles like `unicode`) without leaving the previous
68
- // timer dangling.
69
- const { intervalMs, showVerb } = renderIndicator(style, 0);
70
- useEffect(() => {
71
- const glyph = setInterval(() => setTick(n => n + 1), intervalMs);
72
- const clock = setInterval(() => setNow(Date.now()), 1000);
73
- // Verb timer is gated on `showVerb` — `unicode` style hides the verb
74
- // entirely, so cycling `verbTick` would be an avoidable re-render.
75
- const verb = showVerb ? setInterval(() => setVerbTick(n => n + 1), FACE_TICK_MS) : null;
76
- return () => {
77
- clearInterval(glyph);
78
- clearInterval(clock);
79
- if (verb !== null) {
80
- clearInterval(verb);
81
- }
82
- };
83
- }, [intervalMs, showVerb]);
84
- const { frame } = renderIndicator(style, tick);
85
- const verb = VERBS[verbTick % VERBS.length] ?? '';
86
- const verbSegment = showVerb ? ` ${padVerb(verb)}` : '';
87
- // Leading space keeps a gap between the frame and the duration when the
88
- // verb segment is hidden (e.g. `unicode` spinner style). When the verb
89
- // IS shown, its trailing padding already provides the gap, so the extra
90
- // space is harmless.
91
- const durationSegment = startedAt ? ` · ${fmtDuration(now - startedAt)}` : '';
92
- return (_jsxs(Text, { color: color, children: [frame, verbSegment, durationSegment] }));
93
- }
94
- function ctxBarColor(pct, t) {
95
- if (pct == null) {
96
- return t.color.muted;
97
- }
98
- if (pct >= 95) {
99
- return t.color.statusCritical;
100
- }
101
- if (pct > 80) {
102
- return t.color.statusBad;
103
- }
104
- if (pct >= 50) {
105
- return t.color.statusWarn;
106
- }
107
- return t.color.statusGood;
108
- }
109
- function ctxBar(pct, w = 10) {
110
- const p = Math.max(0, Math.min(100, pct ?? 0));
111
- const filled = Math.round((p / 100) * w);
112
- return '█'.repeat(filled) + '░'.repeat(w - filled);
113
- }
114
- function SpawnHud({ t }) {
115
- // Tight HUD that only appears when the session is actually fanning out.
116
- // Colour escalates to warn/error as depth or concurrency approaches the cap.
117
- const delegation = useStore($delegationState);
118
- const subagents = useTurnSelector(state => state.subagents);
119
- const tree = useMemo(() => buildSubagentTree(subagents), [subagents]);
120
- const totals = useMemo(() => treeTotals(tree), [tree]);
121
- if (!totals.descendantCount && !delegation.paused) {
122
- return null;
123
- }
124
- const maxDepth = delegation.maxSpawnDepth;
125
- const maxConc = delegation.maxConcurrentChildren;
126
- const depth = Math.max(0, totals.maxDepthFromHere);
127
- const active = totals.activeCount;
128
- // `max_concurrent_children` is a per-parent cap, not a global one.
129
- // `activeCount` sums every running agent across the tree and would
130
- // over-warn for multi-orchestrator runs. The widest level of the tree
131
- // is a closer proxy to "most concurrent spawns that could be hitting a
132
- // single parent's slot budget".
133
- const widestLevel = widthByDepth(tree).reduce((a, b) => Math.max(a, b), 0);
134
- const depthRatio = maxDepth ? depth / maxDepth : 0;
135
- const concRatio = maxConc ? widestLevel / maxConc : 0;
136
- const ratio = Math.max(depthRatio, concRatio);
137
- const color = delegation.paused || ratio >= 1 ? t.color.error : ratio >= 0.66 ? t.color.warn : t.color.muted;
138
- const pieces = [];
139
- if (delegation.paused) {
140
- pieces.push('⏸ paused');
141
- }
142
- if (totals.descendantCount > 0) {
143
- const depthLabel = maxDepth ? `${depth}/${maxDepth}` : `${depth}`;
144
- pieces.push(`d${depthLabel}`);
145
- if (active > 0) {
146
- // Label pairs the widest-level count (drives concRatio above) with
147
- // the total active count for context. `W/cap` triggers the warn,
148
- // `+N` is everything else currently running across the tree.
149
- const extra = Math.max(0, active - widestLevel);
150
- const widthLabel = maxConc ? `${widestLevel}/${maxConc}` : `${widestLevel}`;
151
- const suffix = extra > 0 ? `+${extra}` : '';
152
- pieces.push(`⚡${widthLabel}${suffix}`);
153
- }
154
- }
155
- const atCap = depthRatio >= 1 || concRatio >= 1;
156
- return (_jsxs(Text, { color: color, children: [atCap ? ' │ ⚠ ' : ' │ ', pieces.join(' ')] }));
157
- }
158
- function SessionDuration({ startedAt }) {
159
- const [now, setNow] = useState(() => Date.now());
160
- useEffect(() => {
161
- setNow(Date.now());
162
- const id = setInterval(() => setNow(Date.now()), 1000);
163
- return () => clearInterval(id);
164
- }, [startedAt]);
165
- return fmtDuration(now - startedAt);
166
- }
167
- const effortLabel = (effort) => {
168
- const value = String(effort ?? '')
169
- .trim()
170
- .toLowerCase();
171
- return value && value !== 'medium' && value !== 'normal' && value !== 'default' ? value : '';
172
- };
173
- const shortModelLabel = (model) => model
174
- .split('/')
175
- .pop()
176
- .replace(/^claude[-_]/, '')
177
- .replace(/^anthropic[-_]/, '')
178
- .replace(/[-_]/g, ' ')
179
- .replace(/\b(\d+)\s+(\d+)\b/g, '$1.$2')
180
- .trim();
181
- const modelLabel = (model, effort, fast) => [shortModelLabel(model), effortLabel(effort), fast ? 'fast' : ''].filter(Boolean).join(' ');
182
- export function GoodVibesHeart({ tick, t }) {
183
- const [active, setActive] = useState(false);
184
- const [color, setColor] = useState(t.color.accent);
185
- useEffect(() => {
186
- if (tick <= 0) {
187
- return;
188
- }
189
- const palette = [t.color.error, t.color.warn, t.color.accent];
190
- setColor(palette[Math.floor(Math.random() * palette.length)]);
191
- setActive(true);
192
- const id = setTimeout(() => setActive(false), 650);
193
- return () => clearTimeout(id);
194
- }, [t.color.accent, tick]);
195
- if (!active) {
196
- return null;
197
- }
198
- return _jsx(Text, { color: color, children: "\u2665" });
199
- }
200
- export function StatusRule({ cwdLabel, cols, busy, status, statusColor, model, modelFast, modelReasoningEffort, usage, bgCount, sessionStartedAt, showCost, turnStartedAt, voiceLabel, t }) {
201
- const pct = usage.context_percent;
202
- const barColor = ctxBarColor(pct, t);
203
- const ctxLabel = usage.context_max
204
- ? `${fmtK(usage.context_used ?? 0)}/${fmtK(usage.context_max)}`
205
- : usage.total > 0
206
- ? `${fmtK(usage.total)} tok`
207
- : '';
208
- const bar = usage.context_max ? ctxBar(pct) : '';
209
- const leftWidth = Math.max(12, cols - cwdLabel.length - 3);
210
- return (_jsxs(Box, { height: 1, children: [_jsx(Box, { flexShrink: 1, width: leftWidth, children: _jsxs(Text, { color: t.color.border, wrap: "truncate-end", children: ['─ ', busy ? (_jsx(FaceTicker, { color: statusColor, startedAt: turnStartedAt })) : (_jsx(Text, { color: statusColor, children: status })), _jsxs(Text, { color: t.color.muted, children: [" \u2502 ", modelLabel(model, modelReasoningEffort, modelFast)] }), ctxLabel ? _jsxs(Text, { color: t.color.muted, children: [" \u2502 ", ctxLabel] }) : null, bar ? (_jsxs(Text, { color: t.color.muted, children: [' │ ', _jsxs(Text, { color: barColor, children: ["[", bar, "]"] }), " ", _jsx(Text, { color: barColor, children: pct != null ? `${pct}%` : '' })] })) : null, sessionStartedAt ? (_jsxs(Text, { color: t.color.muted, children: [' │ ', _jsx(SessionDuration, { startedAt: sessionStartedAt })] })) : null, typeof usage.compressions === 'number' && usage.compressions > 0 ? (_jsxs(Text, { color: t.color.muted, children: [' │ ', _jsxs(Text, { color: usage.compressions >= 10 ? t.color.error : usage.compressions >= 5 ? t.color.warn : t.color.muted, children: ["cmp ", usage.compressions] })] })) : null, _jsx(SpawnHud, { t: t }), voiceLabel ? (_jsxs(Text, { color: voiceLabel.startsWith('●') ? t.color.error : voiceLabel.startsWith('◉') ? t.color.warn : t.color.muted, children: [' │ ', voiceLabel] })) : null, bgCount > 0 ? _jsxs(Text, { color: t.color.muted, children: [" \u2502 ", bgCount, " bg"] }) : null, showCost && typeof usage.cost_usd === 'number' ? (_jsxs(Text, { color: t.color.muted, children: [" \u2502 $", usage.cost_usd.toFixed(4)] })) : null] }) }), _jsx(Text, { color: t.color.border, children: " \u2500 " }), _jsx(Text, { color: t.color.label, children: cwdLabel })] }));
211
- }
212
- export function FloatBox({ children, color }) {
213
- return (_jsx(Box, { alignSelf: "flex-start", borderColor: color, borderStyle: "double", flexDirection: "column", marginTop: 1, opaque: true, paddingX: 1, children: children }));
214
- }
215
- export function StickyPromptTracker({ messages, offsets, scrollRef, onChange }) {
216
- const { atBottom, bottom, top } = useViewportSnapshot(scrollRef);
217
- const text = stickyPromptFromViewport(messages, offsets, top, bottom, atBottom);
218
- useEffect(() => onChange(text), [onChange, text]);
219
- return null;
220
- }
221
- export function TranscriptScrollbar({ scrollRef, t }) {
222
- const [hover, setHover] = useState(false);
223
- const [grab, setGrab] = useState(null);
224
- const grabRef = useRef(null);
225
- const { scrollHeight: total, top: pos, viewportHeight: vp } = useScrollbarSnapshot(scrollRef);
226
- if (!vp) {
227
- return _jsx(Box, { width: 1 });
228
- }
229
- const s = scrollRef.current;
230
- const scrollable = total > vp;
231
- const thumb = scrollable ? Math.max(1, Math.round((vp * vp) / total)) : vp;
232
- const travel = Math.max(1, vp - thumb);
233
- const thumbTop = scrollable ? Math.round((pos / Math.max(1, total - vp)) * travel) : 0;
234
- const thumbColor = grab !== null ? t.color.primary : hover ? t.color.accent : t.color.border;
235
- const trackColor = hover ? t.color.border : t.color.muted;
236
- const jump = (row, offset) => {
237
- if (!s || !scrollable) {
238
- return;
239
- }
240
- s.scrollTo(Math.round((Math.max(0, Math.min(travel, row - offset)) / travel) * Math.max(0, total - vp)));
241
- };
242
- return (_jsx(Box, { flexDirection: "column", onMouseDown: (e) => {
243
- const row = Math.max(0, Math.min(vp - 1, e.localRow ?? 0));
244
- const off = row >= thumbTop && row < thumbTop + thumb ? row - thumbTop : Math.floor(thumb / 2);
245
- grabRef.current = off;
246
- setGrab(off);
247
- jump(row, off);
248
- }, onMouseDrag: (e) => jump(Math.max(0, Math.min(vp - 1, e.localRow ?? 0)), grabRef.current ?? Math.floor(thumb / 2)), onMouseEnter: () => setHover(true), onMouseLeave: () => setHover(false), onMouseUp: () => {
249
- grabRef.current = null;
250
- setGrab(null);
251
- }, width: 1, children: !scrollable ? (_jsxs(Text, { color: trackColor, dim: true, children: [' \n'.repeat(Math.max(0, vp - 1)), ' '] })) : (_jsxs(_Fragment, { children: [thumbTop > 0 ? (_jsx(Text, { color: trackColor, dim: !hover, children: `${'│\n'.repeat(Math.max(0, thumbTop - 1))}${thumbTop > 0 ? '│' : ''}` })) : null, thumb > 0 ? (_jsx(Text, { color: thumbColor, children: `${'┃\n'.repeat(Math.max(0, thumb - 1))}${thumb > 0 ? '┃' : ''}` })) : null, vp - thumbTop - thumb > 0 ? (_jsx(Text, { color: trackColor, dim: !hover, children: `${'│\n'.repeat(Math.max(0, vp - thumbTop - thumb - 1))}${vp - thumbTop - thumb > 0 ? '│' : ''}` })) : null] })) }));
252
- }
@@ -1,121 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- // @ts-nocheck
3
- // SPDX-License-Identifier: MIT
4
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
5
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
6
- import { AlternateScreen, Box, NoSelect, ScrollBox, Text } from '@cvc/ink';
7
- import { useStore } from '@nanostores/react';
8
- import { Fragment, memo, useMemo, useRef } from 'react';
9
- import { useGateway } from '../app/gatewayContext.js';
10
- import { $isBlocked, $overlayState, patchOverlayState } from '../app/overlayStore.js';
11
- import { $uiState } from '../app/uiStore.js';
12
- import { INLINE_MODE, SHOW_FPS } from '../config/env.js';
13
- import { FULL_RENDER_TAIL_ITEMS } from '../config/limits.js';
14
- import { PLACEHOLDER } from '../content/placeholders.js';
15
- import { COMPOSER_PROMPT_GAP_WIDTH, composerPromptWidth, inputVisualHeight, stableComposerColumns } from '../lib/inputMetrics.js';
16
- import { PerfPane } from '../lib/perfPane.js';
17
- import { AgentsOverlay } from './agentsOverlay.js';
18
- import { GoodVibesHeart, StatusRule, StickyPromptTracker, TranscriptScrollbar } from './appChrome.js';
19
- import { FloatingOverlays, PromptZone } from './appOverlays.js';
20
- import { Banner, Panel, SessionPanel } from './branding.js';
21
- import { FpsOverlay } from './fpsOverlay.js';
22
- import { HelpHint } from './helpHint.js';
23
- import { MessageLine } from './messageLine.js';
24
- import { QueuedMessages } from './queuedMessages.js';
25
- import { LiveTodoPanel, StreamingAssistant } from './streamingAssistant.js';
26
- import { TextInput } from './textInput.js';
27
- const PromptPrefix = memo(function PromptPrefix({ bold = false, color, promptText, width }) {
28
- const glyphWidth = Math.max(1, width - COMPOSER_PROMPT_GAP_WIDTH);
29
- return (_jsxs(Box, { width: width, children: [_jsx(Box, { width: glyphWidth, children: _jsx(Text, { bold: bold, color: color, children: promptText }) }), _jsx(Box, { width: COMPOSER_PROMPT_GAP_WIDTH })] }));
30
- });
31
- const TranscriptPane = memo(function TranscriptPane({ actions, composer, progress, transcript }) {
32
- const ui = useStore($uiState);
33
- // LiveTodoPanel rides as a child of the latest user-message row so it
34
- // visually belongs to the prompt and follows it during scroll. -1 when
35
- // empty → row.index === -1 is always false → no render.
36
- const lastUserIdx = useMemo(() => {
37
- const items = transcript.historyItems;
38
- for (let i = items.length - 1; i >= 0; i--) {
39
- if (items[i].role === 'user') {
40
- return i;
41
- }
42
- }
43
- return -1;
44
- }, [transcript.historyItems]);
45
- // Index of the first user-role message; every later user message gets a
46
- // small dash above it so multi-turn transcripts visually segment by
47
- // turn. -1 when no user message has been sent yet → no separator ever
48
- // renders.
49
- const firstUserIdx = useMemo(() => transcript.historyItems.findIndex(m => m.role === 'user'), [transcript.historyItems]);
50
- return (_jsxs(_Fragment, { children: [_jsx(ScrollBox, { flexDirection: "column", flexGrow: 1, flexShrink: 1, onClick: (e) => {
51
- if (e.cellIsBlank) {
52
- actions.clearSelection();
53
- }
54
- }, ref: transcript.scrollRef, stickyScroll: true, children: _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [transcript.virtualHistory.topSpacer > 0 ? _jsx(Box, { height: transcript.virtualHistory.topSpacer }) : null, transcript.virtualRows.slice(transcript.virtualHistory.start, transcript.virtualHistory.end).map(row => (_jsxs(Box, { flexDirection: "column", ref: transcript.virtualHistory.measureRef(row.key), children: [row.msg.role === 'user' && firstUserIdx >= 0 && row.index > firstUserIdx && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: ui.theme.color.border, children: "\u2500\u2500\u2500" }) })), row.msg.kind === 'intro' ? (_jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [_jsx(Banner, { t: ui.theme }), row.msg.info && _jsx(SessionPanel, { info: row.msg.info, sid: ui.sid, t: ui.theme })] })) : row.msg.kind === 'panel' && row.msg.panelData ? (_jsx(Panel, { sections: row.msg.panelData.sections, t: ui.theme, title: row.msg.panelData.title })) : (_jsx(MessageLine, { cols: composer.cols, compact: ui.compact, detailsMode: ui.detailsMode, detailsModeCommandOverride: ui.detailsModeCommandOverride, limitHistoryRender: row.index < transcript.historyItems.length - FULL_RENDER_TAIL_ITEMS, msg: row.msg, sections: ui.sections, t: ui.theme })), row.index === lastUserIdx && _jsx(LiveTodoPanel, {})] }, row.key))), transcript.virtualHistory.bottomSpacer > 0 ? _jsx(Box, { height: transcript.virtualHistory.bottomSpacer }) : null, _jsx(StreamingAssistant, { cols: composer.cols, compact: ui.compact, detailsMode: ui.detailsMode, detailsModeCommandOverride: ui.detailsModeCommandOverride, progress: progress, sections: ui.sections })] }) }), _jsx(NoSelect, { flexShrink: 0, marginLeft: 1, children: _jsx(TranscriptScrollbar, { scrollRef: transcript.scrollRef, t: ui.theme }) }), _jsx(StickyPromptTracker, { messages: transcript.historyItems, offsets: transcript.virtualHistory.offsets, onChange: actions.setStickyPrompt, scrollRef: transcript.scrollRef })] }));
55
- });
56
- const ComposerPane = memo(function ComposerPane({ actions, composer, status }) {
57
- const ui = useStore($uiState);
58
- const isBlocked = useStore($isBlocked);
59
- const sh = (composer.inputBuf[0] ?? composer.input).startsWith('!');
60
- const promptText = sh ? '$' : ui.theme.brand.prompt;
61
- const promptWidth = composerPromptWidth(promptText);
62
- const promptBlank = ' '.repeat(promptWidth);
63
- const inputColumns = stableComposerColumns(composer.cols, promptWidth);
64
- const inputHeight = inputVisualHeight(composer.input, inputColumns);
65
- const inputMouseRef = useRef(null);
66
- const captureInputDrag = (e) => {
67
- if (e.button !== 0) {
68
- return;
69
- }
70
- e.stopImmediatePropagation?.();
71
- inputMouseRef.current?.startAtBeginning();
72
- };
73
- // Drag origin matches the input box's top-left, so localRow / localCol
74
- // map directly into TextInput coords (after backing out the prompt cell).
75
- const dragFromPromptRow = (e) => {
76
- if (e.button !== 0) {
77
- return;
78
- }
79
- e.stopImmediatePropagation?.();
80
- inputMouseRef.current?.dragAt(e.localRow ?? 0, (e.localCol ?? 0) - promptWidth);
81
- };
82
- // Spacer rows live on a different vertical origin; only the column is
83
- // parent-aligned with the input. Force row=0 so vertical drags can't
84
- // jump the cursor to the wrong wrapped line.
85
- const dragFromSpacer = (e) => {
86
- if (e.button !== 0) {
87
- return;
88
- }
89
- e.stopImmediatePropagation?.();
90
- inputMouseRef.current?.dragAt(0, (e.localCol ?? 0) - promptWidth);
91
- };
92
- const endInputDrag = () => inputMouseRef.current?.end();
93
- return (_jsxs(NoSelect, { flexDirection: "column", flexShrink: 0, fromLeftEdge: true, onClick: (e) => {
94
- if (e.cellIsBlank) {
95
- actions.clearSelection();
96
- }
97
- }, paddingX: 1, children: [_jsx(QueuedMessages, { cols: composer.cols, queued: composer.queuedDisplay, queueEditIdx: composer.queueEditIdx, t: ui.theme }), ui.bgTasks.size > 0 && (_jsxs(Text, { color: ui.theme.color.muted, children: [ui.bgTasks.size, " background ", ui.bgTasks.size === 1 ? 'task' : 'tasks', " running"] })), status.showStickyPrompt ? (_jsxs(Text, { color: ui.theme.color.muted, wrap: "truncate-end", children: [_jsx(Text, { color: ui.theme.color.label, children: "\u21B3 " }), status.stickyPrompt] })) : (_jsx(Box, { height: 1, onMouseDown: captureInputDrag, onMouseDrag: dragFromSpacer, onMouseUp: endInputDrag })), _jsx(StatusRulePane, { at: "top", composer: composer, status: status }), _jsxs(Box, { flexDirection: "column", marginTop: ui.statusBar === 'top' ? 0 : 1, position: "relative", children: [_jsx(FloatingOverlays, { cols: composer.cols, compIdx: composer.compIdx, completions: composer.completions, onModelSelect: actions.onModelSelect, onPickerSelect: actions.resumeById, pagerPageSize: composer.pagerPageSize }), composer.input === '?' && !composer.inputBuf.length && _jsx(HelpHint, { t: ui.theme }), !isBlocked && (_jsxs(_Fragment, { children: [composer.inputBuf.map((line, i) => (_jsxs(Box, { children: [_jsx(Box, { width: promptWidth, children: i === 0 ? (_jsx(PromptPrefix, { color: ui.theme.color.muted, promptText: promptText, width: promptWidth })) : (_jsx(Text, { color: ui.theme.color.muted, children: promptBlank })) }), _jsx(Text, { color: ui.theme.color.text, children: line || ' ' })] }, i))), _jsxs(Box, { onMouseDown: captureInputDrag, onMouseDrag: dragFromPromptRow, onMouseUp: endInputDrag, position: "relative", width: Math.max(1, composer.cols - 2), children: [_jsx(Box, { width: promptWidth, children: sh ? (_jsx(PromptPrefix, { color: ui.theme.color.shellDollar, promptText: promptText, width: promptWidth })) : composer.inputBuf.length ? (_jsx(Text, { color: ui.theme.color.prompt, children: promptBlank })) : (_jsx(PromptPrefix, { bold: true, color: ui.theme.color.prompt, promptText: promptText, width: promptWidth })) }), _jsx(Box, { flexGrow: 0, flexShrink: 0, height: inputHeight, width: inputColumns, children: _jsx(TextInput, { columns: inputColumns, mouseApiRef: inputMouseRef, onChange: composer.updateInput, onPaste: composer.handleTextPaste, onSubmit: composer.submit, placeholder: composer.empty ? PLACEHOLDER : ui.busy ? 'Ctrl+C to interrupt…' : '', value: composer.input, voiceRecordKey: composer.voiceRecordKey }) }), _jsx(Box, { position: "absolute", right: 0, children: _jsx(GoodVibesHeart, { t: ui.theme, tick: status.goodVibesTick }) })] })] }))] }), !composer.empty && !ui.sid && _jsxs(Text, { color: ui.theme.color.muted, children: ["\u2695 ", ui.status] }), _jsx(StatusRulePane, { at: "bottom", composer: composer, status: status })] }));
98
- });
99
- const AgentsOverlayPane = memo(function AgentsOverlayPane() {
100
- const { gw } = useGateway();
101
- const ui = useStore($uiState);
102
- const overlay = useStore($overlayState);
103
- return (_jsx(AgentsOverlay, { gw: gw, initialHistoryIndex: overlay.agentsInitialHistoryIndex, onClose: () => patchOverlayState({ agents: false, agentsInitialHistoryIndex: 0 }), t: ui.theme }));
104
- });
105
- const StatusRulePane = memo(function StatusRulePane({ at, composer, status }) {
106
- const ui = useStore($uiState);
107
- if (ui.statusBar !== at) {
108
- return null;
109
- }
110
- return (_jsx(Box, { marginTop: at === 'top' ? 1 : 0, children: _jsx(StatusRule, { bgCount: ui.bgTasks.size, busy: ui.busy, cols: composer.cols, cwdLabel: status.cwdLabel, model: ui.info?.model ?? '', modelFast: ui.info?.fast || ui.info?.service_tier === 'priority', modelReasoningEffort: ui.info?.reasoning_effort, sessionStartedAt: status.sessionStartedAt, showCost: ui.showCost, status: ui.status, statusColor: status.statusColor, t: ui.theme, turnStartedAt: status.turnStartedAt, usage: ui.usage, voiceLabel: status.voiceLabel }) }));
111
- });
112
- export const AppLayout = memo(function AppLayout({ actions, composer, mouseTracking, progress, status, transcript }) {
113
- const overlay = useStore($overlayState);
114
- const ui = useStore($uiState);
115
- // Inline mode skips AlternateScreen so the host terminal's native
116
- // scrollback captures rows scrolled off the top; composer + progress
117
- // stay anchored via normal flex-column flow.
118
- const Shell = INLINE_MODE ? Fragment : AlternateScreen;
119
- const shellProps = INLINE_MODE ? {} : { mouseTracking };
120
- return (_jsx(Shell, { ...shellProps, children: _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Box, { flexDirection: "row", flexGrow: 1, children: overlay.agents ? (_jsx(PerfPane, { id: "agents", children: _jsx(AgentsOverlayPane, {}) })) : (_jsx(PerfPane, { id: "transcript", children: _jsx(TranscriptPane, { actions: actions, composer: composer, progress: progress, transcript: transcript }) })) }), !overlay.agents && (_jsxs(_Fragment, { children: [_jsx(PerfPane, { id: "prompt", children: _jsx(PromptZone, { cols: composer.cols, onApprovalChoice: actions.answerApproval, onClarifyAnswer: actions.answerClarify, onSecretSubmit: actions.answerSecret, onSudoSubmit: actions.answerSudo }) }), _jsx(PerfPane, { id: "composer", children: _jsx(ComposerPane, { actions: actions, composer: composer, status: status }) }), SHOW_FPS && (_jsx(Box, { flexShrink: 0, justifyContent: "flex-end", paddingRight: 1, children: _jsx(FpsOverlay, { t: ui.theme }) }))] }))] }) }));
121
- });
@@ -1,65 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @ts-nocheck
3
- // SPDX-License-Identifier: MIT
4
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
5
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
6
- import { Box, Text } from '@cvc/ink';
7
- import { useStore } from '@nanostores/react';
8
- import { useGateway } from '../app/gatewayContext.js';
9
- import { $overlayState, patchOverlayState } from '../app/overlayStore.js';
10
- import { $uiSessionId, $uiTheme } from '../app/uiStore.js';
11
- import { FloatBox } from './appChrome.js';
12
- import { MaskedPrompt } from './maskedPrompt.js';
13
- import { ModelPicker } from './modelPicker.js';
14
- import { OverlayHint } from './overlayControls.js';
15
- import { ApprovalPrompt, ClarifyPrompt, ConfirmPrompt } from './prompts.js';
16
- import { SessionPicker } from './sessionPicker.js';
17
- import { SkillsHub } from './skillsHub.js';
18
- const COMPLETION_WINDOW = 16;
19
- export function PromptZone({ cols, onApprovalChoice, onClarifyAnswer, onSecretSubmit, onSudoSubmit }) {
20
- const overlay = useStore($overlayState);
21
- const theme = useStore($uiTheme);
22
- if (overlay.approval) {
23
- return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingX: 1, paddingY: 1, children: _jsx(ApprovalPrompt, { onChoice: onApprovalChoice, req: overlay.approval, t: theme }) }));
24
- }
25
- if (overlay.confirm) {
26
- const req = overlay.confirm;
27
- const onConfirm = () => {
28
- patchOverlayState({ confirm: null });
29
- req.onConfirm();
30
- };
31
- const onCancel = () => patchOverlayState({ confirm: null });
32
- return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingX: 1, paddingY: 1, children: _jsx(ConfirmPrompt, { onCancel: onCancel, onConfirm: onConfirm, req: req, t: theme }) }));
33
- }
34
- if (overlay.clarify) {
35
- return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingX: 1, paddingY: 1, children: _jsx(ClarifyPrompt, { cols: cols, onAnswer: onClarifyAnswer, onCancel: () => onClarifyAnswer(''), req: overlay.clarify, t: theme }) }));
36
- }
37
- if (overlay.sudo) {
38
- return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingX: 1, paddingY: 1, children: _jsx(MaskedPrompt, { cols: cols, icon: "\uD83D\uDD10", label: "sudo password required", onSubmit: onSudoSubmit, t: theme }) }));
39
- }
40
- if (overlay.secret) {
41
- return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingX: 1, paddingY: 1, children: _jsx(MaskedPrompt, { cols: cols, icon: "\uD83D\uDD11", label: overlay.secret.prompt, onSubmit: onSecretSubmit, sub: `for ${overlay.secret.envVar}`, t: theme }) }));
42
- }
43
- return null;
44
- }
45
- export function FloatingOverlays({ cols, compIdx, completions, onModelSelect, onPickerSelect, pagerPageSize }) {
46
- const { gw } = useGateway();
47
- const overlay = useStore($overlayState);
48
- const sid = useStore($uiSessionId);
49
- const theme = useStore($uiTheme);
50
- const hasAny = overlay.modelPicker || overlay.pager || overlay.picker || overlay.skillsHub || completions.length;
51
- if (!hasAny) {
52
- return null;
53
- }
54
- // Fixed viewport centered on compIdx — previously the slice end was
55
- // compIdx + 8 so the dropdown grew from 8 rows to 16 as the user scrolled
56
- // down, bouncing the height on every keystroke.
57
- const viewportSize = Math.min(COMPLETION_WINDOW, completions.length);
58
- const start = Math.max(0, Math.min(compIdx - Math.floor(COMPLETION_WINDOW / 2), completions.length - viewportSize));
59
- return (_jsxs(Box, { alignItems: "flex-start", bottom: "100%", flexDirection: "column", left: 0, position: "absolute", right: 0, children: [overlay.picker && (_jsx(FloatBox, { color: theme.color.border, children: _jsx(SessionPicker, { gw: gw, onCancel: () => patchOverlayState({ picker: false }), onSelect: onPickerSelect, t: theme }) })), overlay.modelPicker && (_jsx(FloatBox, { color: theme.color.border, children: _jsx(ModelPicker, { gw: gw, onCancel: () => patchOverlayState({ modelPicker: false }), onSelect: onModelSelect, sessionId: sid, t: theme }) })), overlay.skillsHub && (_jsx(FloatBox, { color: theme.color.border, children: _jsx(SkillsHub, { gw: gw, onClose: () => patchOverlayState({ skillsHub: false }), t: theme }) })), overlay.pager && (_jsx(FloatBox, { color: theme.color.border, children: _jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [overlay.pager.title && (_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.color.primary, children: overlay.pager.title }) })), overlay.pager.lines.slice(overlay.pager.offset, overlay.pager.offset + pagerPageSize).map((line, i) => (_jsx(Text, { children: line }, i))), _jsx(Box, { marginTop: 1, children: _jsx(OverlayHint, { t: theme, children: overlay.pager.offset + pagerPageSize < overlay.pager.lines.length
60
- ? `↑↓/jk line · Enter/Space/PgDn page · b/PgUp back · g/G top/bottom · Esc/q close (${Math.min(overlay.pager.offset + pagerPageSize, overlay.pager.lines.length)}/${overlay.pager.lines.length})`
61
- : `end · ↑↓/jk · b/PgUp back · g top · Esc/q close (${overlay.pager.lines.length} lines)` }) })] }) })), !!completions.length && (_jsx(FloatBox, { color: theme.color.primary, children: _jsx(Box, { flexDirection: "column", width: Math.max(28, cols - 6), children: completions.slice(start, start + viewportSize).map((item, i) => {
62
- const active = start + i === compIdx;
63
- return (_jsxs(Box, { backgroundColor: active ? theme.color.completionCurrentBg : theme.color.completionBg, flexDirection: "row", width: "100%", children: [_jsxs(Text, { bold: true, color: theme.color.label, children: [' ', item.display] }), item.meta ? (_jsxs(Text, { backgroundColor: active ? theme.color.completionMetaCurrentBg : theme.color.completionMetaBg, color: theme.color.muted, children: [' ', item.meta] })) : null] }, `${start + i}:${item.text}:${item.display}:${item.meta ?? ''}`));
64
- }) }) }))] }));
65
- }
@@ -1,97 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- // @ts-nocheck
3
- // SPDX-License-Identifier: MIT
4
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
5
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
6
- import { Box, Text, useStdout } from '@cvc/ink';
7
- import { useEffect, useState } from 'react';
8
- import unicodeSpinners from 'unicode-animations';
9
- import { artWidth, caduceus, CADUCEUS_WIDTH, logo, LOGO_WIDTH } from '../banner.js';
10
- import { flat } from '../lib/text.js';
11
- const LOADER_TICK_MS = 120;
12
- function InlineLoader({ label, t }) {
13
- const [tick, setTick] = useState(0);
14
- const spinner = unicodeSpinners.braille;
15
- const frame = spinner.frames[tick % spinner.frames.length] ?? '⠋';
16
- useEffect(() => {
17
- const id = setInterval(() => setTick(n => n + 1), Math.max(LOADER_TICK_MS, spinner.interval));
18
- return () => clearInterval(id);
19
- }, [spinner.interval]);
20
- return (_jsxs(Text, { color: t.color.muted, wrap: "truncate", children: [_jsx(Text, { color: t.color.accent, children: frame }), " ", label] }));
21
- }
22
- export function ArtLines({ lines }) {
23
- return (_jsx(_Fragment, { children: lines.map(([c, text], i) => (_jsx(Text, { color: c, children: text }, i))) }));
24
- }
25
- export function Banner({ t }) {
26
- const cols = useStdout().stdout?.columns ?? 80;
27
- const logoLines = logo(t.color, t.bannerLogo || undefined);
28
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [cols >= (t.bannerLogo ? artWidth(logoLines) : LOGO_WIDTH) ? (_jsx(ArtLines, { lines: logoLines })) : (_jsxs(Text, { bold: true, color: t.color.primary, children: [t.brand.icon, " NOUS HERMES"] })), _jsxs(Text, { color: t.color.muted, children: [t.brand.icon, " Nous Research \u00B7 Messenger of the Digital Gods"] })] }));
29
- }
30
- // ── Collapsible helpers ──────────────────────────────────────────────
31
- function CollapseToggle({ count, open, suffix, t, title, onToggle }) {
32
- return (_jsxs(Box, { onClick: onToggle, children: [_jsx(Text, { color: t.color.accent, children: open ? '▾ ' : '▸ ' }), _jsx(Text, { bold: true, color: t.color.accent, children: title }), typeof count === 'number' ? (_jsxs(Text, { color: t.color.muted, children: [" (", count, ")"] })) : null, suffix ? (_jsxs(Text, { color: t.color.muted, children: [" ", suffix] })) : null] }));
33
- }
34
- // ── SessionPanel ─────────────────────────────────────────────────────
35
- const SKILLS_MAX = 8;
36
- const TOOLSETS_MAX = 8;
37
- export function SessionPanel({ info, sid, t }) {
38
- const cols = useStdout().stdout?.columns ?? 100;
39
- const heroLines = caduceus(t.color, t.bannerHero || undefined);
40
- const leftW = Math.min((artWidth(heroLines) || CADUCEUS_WIDTH) + 4, Math.floor(cols * 0.4));
41
- const wide = cols >= 90 && leftW + 40 < cols;
42
- const w = Math.max(20, wide ? cols - leftW - 14 : cols - 12);
43
- const lineBudget = Math.max(12, w - 2);
44
- const strip = (s) => (s.endsWith('_tools') ? s.slice(0, -6) : s);
45
- // ── Local collapse state for each section ──
46
- const [toolsOpen, setToolsOpen] = useState(true);
47
- const [skillsOpen, setSkillsOpen] = useState(false);
48
- const [systemOpen, setSystemOpen] = useState(false);
49
- const [mcpOpen, setMcpOpen] = useState(false);
50
- const truncLine = (pfx, items) => {
51
- let line = '';
52
- let shown = 0;
53
- for (const item of [...items].sort()) {
54
- const next = line ? `${line}, ${item}` : item;
55
- if (pfx.length + next.length > lineBudget) {
56
- return line ? `${line}, …+${items.length - shown}` : `${item}, …`;
57
- }
58
- line = next;
59
- shown++;
60
- }
61
- return line;
62
- };
63
- // ── Collapsible skills section ──
64
- const skillEntries = Object.entries(info.skills).sort();
65
- const skillsTotal = flat(info.skills).length;
66
- const skillsCatCount = skillEntries.length;
67
- const skillsBody = () => {
68
- if (info.lazy && skillEntries.length === 0) {
69
- return _jsx(InlineLoader, { label: "scanning skills", t: t });
70
- }
71
- const shown = skillEntries.slice(0, SKILLS_MAX);
72
- const overflow = skillEntries.length - SKILLS_MAX;
73
- return (_jsxs(_Fragment, { children: [shown.map(([k, vs]) => (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: t.color.muted, children: [strip(k), ": "] }), _jsx(Text, { color: t.color.text, children: truncLine(strip(k) + ': ', vs) })] }, k))), overflow > 0 && (_jsxs(Text, { color: t.color.muted, children: ["(and ", overflow, " more categories\u2026)"] }))] }));
74
- };
75
- // ── Collapsible tools section ──
76
- const toolEntries = Object.entries(info.tools).sort();
77
- const toolsTotal = flat(info.tools).length;
78
- const toolsBody = () => {
79
- const shown = toolEntries.slice(0, TOOLSETS_MAX);
80
- const overflow = toolEntries.length - TOOLSETS_MAX;
81
- return (_jsxs(_Fragment, { children: [shown.map(([k, vs]) => (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: t.color.muted, children: [strip(k), ": "] }), _jsx(Text, { color: t.color.text, children: truncLine(strip(k) + ': ', vs) })] }, k))), overflow > 0 && (_jsxs(Text, { color: t.color.muted, children: ["(and ", overflow, " more toolsets\u2026)"] }))] }));
82
- };
83
- // ── Collapsible MCP section ──
84
- const mcpBody = () => (_jsx(_Fragment, { children: (info.mcp_servers ?? []).map(s => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: t.color.muted, children: ` ${s.name} ` }), _jsx(Text, { color: t.color.muted, children: `[${s.transport}]` }), _jsx(Text, { color: t.color.muted, children: ": " }), s.connected ? (_jsxs(Text, { color: t.color.text, children: [s.tools, " tool", s.tools === 1 ? '' : 's'] })) : (_jsx(Text, { color: t.color.error, children: "failed" }))] }, s.name))) }));
85
- // ── System prompt body ──
86
- const sysPromptLen = (info.system_prompt ?? '').length;
87
- const systemBody = () => {
88
- if (sysPromptLen === 0) {
89
- return _jsx(Text, { color: t.color.muted, children: "No system prompt loaded." });
90
- }
91
- return (_jsx(Text, { color: t.color.muted, children: info.system_prompt }));
92
- };
93
- return (_jsxs(Box, { borderColor: t.color.border, borderStyle: "round", marginBottom: 1, paddingX: 2, paddingY: 1, children: [wide && (_jsxs(Box, { flexDirection: "column", marginRight: 2, width: leftW, children: [_jsx(ArtLines, { lines: heroLines }), _jsx(Text, {}), _jsxs(Text, { color: t.color.accent, children: [info.model.split('/').pop(), _jsx(Text, { color: t.color.muted, children: " \u00B7 Nous Research" })] }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: info.cwd || process.cwd() }), sid && (_jsxs(Text, { children: [_jsx(Text, { color: t.color.sessionLabel, children: "Session: " }), _jsx(Text, { color: t.color.sessionBorder, children: sid })] }))] })), _jsxs(Box, { flexDirection: "column", width: w, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsxs(Text, { bold: true, color: t.color.primary, children: [t.brand.name, info.version ? ` v${info.version}` : '', info.release_date ? ` (${info.release_date})` : ''] }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(CollapseToggle, { onToggle: () => setToolsOpen(v => !v), open: toolsOpen, t: t, title: "Available Tools" }), toolsOpen && toolsBody()] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(CollapseToggle, { count: skillsTotal, onToggle: () => setSkillsOpen(v => !v), open: skillsOpen, suffix: skillsCatCount > 0 ? `in ${skillsCatCount} categor${skillsCatCount === 1 ? 'y' : 'ies'}` : undefined, t: t, title: "Available Skills" }), skillsOpen && skillsBody()] }), sysPromptLen > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(CollapseToggle, { onToggle: () => setSystemOpen(v => !v), open: systemOpen, suffix: `— ${sysPromptLen.toLocaleString()} chars`, t: t, title: "System Prompt" }), systemOpen && systemBody()] })), info.mcp_servers && info.mcp_servers.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(CollapseToggle, { count: info.mcp_servers.length, onToggle: () => setMcpOpen(v => !v), open: mcpOpen, suffix: "connected", t: t, title: "MCP Servers" }), mcpOpen && mcpBody()] })), _jsx(Text, {}), _jsxs(Text, { color: t.color.text, children: [toolsTotal, " tools", ' · ', skillsTotal, " skills", info.mcp_servers?.length ? ` · ${info.mcp_servers.length} MCP` : '', ' · ', _jsx(Text, { color: t.color.muted, children: "/help for commands" })] }), typeof info.update_behind === 'number' && info.update_behind > 0 && (_jsxs(Text, { bold: true, color: t.color.warn, children: ["! ", info.update_behind, " ", info.update_behind === 1 ? 'commit' : 'commits', " behind", _jsxs(Text, { bold: false, color: t.color.warn, dimColor: true, children: [' ', "- run", ' '] }), _jsx(Text, { bold: true, color: t.color.warn, children: info.update_command || 'hermes update' }), _jsxs(Text, { bold: false, color: t.color.warn, dimColor: true, children: [' ', "to update"] })] }))] })] }));
94
- }
95
- export function Panel({ sections, t, title }) {
96
- return (_jsxs(Box, { borderColor: t.color.border, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsx(Text, { bold: true, color: t.color.primary, children: title }) }), sections.map((sec, si) => (_jsxs(Box, { flexDirection: "column", marginTop: si > 0 ? 1 : 0, children: [sec.title && (_jsx(Text, { bold: true, color: t.color.accent, children: sec.title })), sec.rows?.map(([k, v], ri) => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: t.color.muted, children: k.padEnd(20) }), _jsx(Text, { color: t.color.text, children: v })] }, ri))), sec.items?.map((item, ii) => (_jsx(Text, { color: t.color.text, wrap: "truncate", children: item }, ii))), sec.text && _jsx(Text, { color: t.color.muted, children: sec.text })] }, si)))] }));
97
- }
@@ -1,22 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @ts-nocheck
3
- // SPDX-License-Identifier: MIT
4
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
5
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
6
- // FPS counter overlay (CVC_TUI_FPS=1). Zero-cost when disabled.
7
- import { Text } from '@cvc/ink';
8
- import { useStore } from '@nanostores/react';
9
- import { SHOW_FPS } from '../config/env.js';
10
- import { $fpsState } from '../lib/fpsStore.js';
11
- const fpsColor = (fps, t) => fps >= 50 ? t.color.statusGood : fps >= 30 ? t.color.statusWarn : t.color.error;
12
- export function FpsOverlay({ t }) {
13
- if (!SHOW_FPS) {
14
- return null;
15
- }
16
- return _jsx(FpsOverlayInner, { t: t });
17
- }
18
- function FpsOverlayInner({ t }) {
19
- const { fps, lastDurationMs, totalFrames } = useStore($fpsState);
20
- // Zero-pad widths so digit churn doesn't jitter the corner.
21
- return (_jsxs(Text, { color: fpsColor(fps, t), children: [fps.toFixed(1).padStart(5), "fps \u00B7 ", lastDurationMs.toFixed(1).padStart(5), "ms \u00B7 #", totalFrames] }));
22
- }
@@ -1,21 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @ts-nocheck
3
- // SPDX-License-Identifier: MIT
4
- // Ported from CVC Agent (https://github.com/NousResearch/cvc)
5
- // Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
6
- import { Box, Text } from '@cvc/ink';
7
- import { HOTKEYS } from '../content/hotkeys.js';
8
- const COMMON_COMMANDS = [
9
- ['/help', 'full list of commands + hotkeys'],
10
- ['/clear', 'start a new session'],
11
- ['/resume', 'resume a prior session'],
12
- ['/details', 'control transcript detail level'],
13
- ['/copy', 'copy selection or last assistant message'],
14
- ['/quit', 'exit hermes']
15
- ];
16
- const HOTKEY_PREVIEW = HOTKEYS.slice(0, 8);
17
- export function HelpHint({ t }) {
18
- const labelW = Math.max(...COMMON_COMMANDS.map(([k]) => k.length), ...HOTKEY_PREVIEW.map(([k]) => k.length));
19
- const pad = (s) => s + ' '.repeat(Math.max(0, labelW - s.length + 2));
20
- return (_jsx(Box, { alignItems: "flex-start", bottom: "100%", flexDirection: "column", left: 0, position: "absolute", right: 0, children: _jsxs(Box, { alignSelf: "flex-start", borderColor: t.color.primary, borderStyle: "round", flexDirection: "column", marginBottom: 1, opaque: true, paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: t.color.primary, children: "? quick help" }), _jsx(Text, { color: t.color.muted, children: ' · type /help for the full panel · backspace to dismiss' })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, color: t.color.accent, children: "Common commands" }) }), COMMON_COMMANDS.map(([k, v]) => (_jsxs(Text, { children: [_jsx(Text, { color: t.color.label, children: pad(k) }), _jsx(Text, { color: t.color.muted, children: v })] }, k))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, color: t.color.accent, children: "Hotkeys" }) }), HOTKEY_PREVIEW.map(([k, v]) => (_jsxs(Text, { children: [_jsx(Text, { color: t.color.label, children: pad(k) }), _jsx(Text, { color: t.color.muted, children: v })] }, k)))] }) }));
21
- }