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.
- package/dist/entry.js +71148 -61
- package/package.json +2 -2
- package/dist/app/completion.js +0 -102
- package/dist/app/createGatewayEventHandler.js +0 -508
- package/dist/app/createSlashHandler.js +0 -101
- package/dist/app/delegationStore.js +0 -51
- package/dist/app/gatewayContext.js +0 -17
- package/dist/app/historyStore.js +0 -123
- package/dist/app/inputBuffer.js +0 -120
- package/dist/app/inputSelectionStore.js +0 -8
- package/dist/app/inputStore.js +0 -28
- package/dist/app/interfaces.js +0 -6
- package/dist/app/overlayStore.js +0 -40
- package/dist/app/promptStore.js +0 -44
- package/dist/app/queueStore.js +0 -25
- package/dist/app/scroll.js +0 -44
- package/dist/app/setupHandoff.js +0 -28
- package/dist/app/slash/commands/core.js +0 -479
- package/dist/app/slash/commands/debug.js +0 -44
- package/dist/app/slash/commands/ops.js +0 -498
- package/dist/app/slash/commands/session.js +0 -431
- package/dist/app/slash/commands/setup.js +0 -20
- package/dist/app/slash/commands/toggles.js +0 -40
- package/dist/app/slash/registry.js +0 -18
- package/dist/app/slash/types.js +0 -1
- package/dist/app/spawnHistoryStore.js +0 -105
- package/dist/app/turnController.js +0 -650
- package/dist/app/turnStore.js +0 -48
- package/dist/app/uiStore.js +0 -36
- package/dist/app/useComposerState.js +0 -265
- package/dist/app/useConfigSync.js +0 -144
- package/dist/app/useInputHandlers.js +0 -403
- package/dist/app/useLongRunToolCharms.js +0 -50
- package/dist/app/useMainApp.js +0 -629
- package/dist/app/useSessionLifecycle.js +0 -175
- package/dist/app/useSubmission.js +0 -287
- package/dist/app.js +0 -15
- package/dist/banner.js +0 -57
- package/dist/components/agentsOverlay.js +0 -474
- package/dist/components/appChrome.js +0 -252
- package/dist/components/appLayout.js +0 -121
- package/dist/components/appOverlays.js +0 -65
- package/dist/components/branding.js +0 -97
- package/dist/components/fpsOverlay.js +0 -22
- package/dist/components/helpHint.js +0 -21
- package/dist/components/markdown.js +0 -501
- package/dist/components/maskedPrompt.js +0 -12
- package/dist/components/messageLine.js +0 -82
- package/dist/components/modelPicker.js +0 -254
- package/dist/components/overlayControls.js +0 -30
- package/dist/components/overlays/confirmPrompt.js +0 -25
- package/dist/components/overlays/helpOverlay.js +0 -76
- package/dist/components/overlays/historySearch.js +0 -49
- package/dist/components/overlays/modelPicker.js +0 -60
- package/dist/components/overlays/overlayUtils.js +0 -19
- package/dist/components/overlays/secretPrompt.js +0 -36
- package/dist/components/overlays/sessionPicker.js +0 -93
- package/dist/components/overlays/skillsHub.js +0 -71
- package/dist/components/prompts.js +0 -95
- package/dist/components/queuedMessages.js +0 -24
- package/dist/components/sessionPicker.js +0 -130
- package/dist/components/skillsHub.js +0 -165
- package/dist/components/streamingAssistant.js +0 -35
- package/dist/components/streamingMarkdown.js +0 -144
- package/dist/components/textInput.js +0 -794
- package/dist/components/themed.js +0 -12
- package/dist/components/thinking.js +0 -496
- package/dist/components/todoPanel.js +0 -40
- package/dist/components/transcript.js +0 -22
- package/dist/config/env.js +0 -18
- package/dist/config/limits.js +0 -22
- package/dist/config/timing.js +0 -18
- package/dist/content/charms.js +0 -5
- package/dist/content/faces.js +0 -21
- package/dist/content/fortunes.js +0 -29
- package/dist/content/hotkeys.js +0 -38
- package/dist/content/placeholders.js +0 -15
- package/dist/content/setup.js +0 -14
- package/dist/content/verbs.js +0 -41
- package/dist/domain/details.js +0 -53
- package/dist/domain/messages.js +0 -63
- package/dist/domain/paths.js +0 -16
- package/dist/domain/providers.js +0 -11
- package/dist/domain/roles.js +0 -6
- package/dist/domain/slash.js +0 -11
- package/dist/domain/usage.js +0 -1
- package/dist/domain/viewport.js +0 -33
- package/dist/gateway/client.js +0 -312
- package/dist/gatewayClient.js +0 -574
- package/dist/gatewayTypes.js +0 -1
- package/dist/hooks/useCompletion.js +0 -86
- package/dist/hooks/useGitBranch.js +0 -58
- package/dist/hooks/useInputHistory.js +0 -12
- package/dist/hooks/useQueue.js +0 -57
- package/dist/hooks/useVirtualHistory.js +0 -401
- package/dist/lib/circularBuffer.js +0 -43
- package/dist/lib/clipboard.js +0 -126
- package/dist/lib/editor.js +0 -41
- package/dist/lib/editor.test.js +0 -58
- package/dist/lib/emoji.js +0 -49
- package/dist/lib/externalCli.js +0 -11
- package/dist/lib/forceTruecolor.js +0 -26
- package/dist/lib/fpsStore.js +0 -36
- package/dist/lib/gracefulExit.js +0 -29
- package/dist/lib/history.js +0 -69
- package/dist/lib/inputMetrics.js +0 -143
- package/dist/lib/liveProgress.js +0 -51
- package/dist/lib/liveProgress.test.js +0 -89
- package/dist/lib/mathUnicode.js +0 -685
- package/dist/lib/memory.js +0 -123
- package/dist/lib/memoryMonitor.js +0 -76
- package/dist/lib/messages.js +0 -3
- package/dist/lib/messages.test.js +0 -25
- package/dist/lib/osc52.js +0 -53
- package/dist/lib/perfPane.js +0 -94
- package/dist/lib/platform.js +0 -312
- package/dist/lib/precisionWheel.js +0 -25
- package/dist/lib/reasoning.js +0 -39
- package/dist/lib/rpc.js +0 -26
- package/dist/lib/subagentTree.js +0 -287
- package/dist/lib/syntax.js +0 -89
- package/dist/lib/terminalModes.js +0 -46
- package/dist/lib/terminalParity.js +0 -48
- package/dist/lib/terminalSetup.js +0 -321
- package/dist/lib/text.js +0 -203
- package/dist/lib/text.test.js +0 -18
- package/dist/lib/todo.js +0 -2
- package/dist/lib/todo.test.js +0 -22
- package/dist/lib/viewportStore.js +0 -82
- package/dist/lib/virtualHeights.js +0 -61
- package/dist/lib/wheelAccel.js +0 -143
- package/dist/theme.js +0 -398
- 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
|
-
}
|