cvc-tui 0.1.0 → 0.4.0

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 (130) hide show
  1. package/dist/app/completion.js +4 -0
  2. package/dist/app/createGatewayEventHandler.js +508 -0
  3. package/dist/app/createSlashHandler.js +101 -0
  4. package/dist/app/delegationStore.js +51 -0
  5. package/dist/app/gatewayContext.js +17 -0
  6. package/dist/app/historyStore.js +4 -0
  7. package/dist/app/inputBuffer.js +4 -0
  8. package/dist/app/inputSelectionStore.js +8 -0
  9. package/dist/app/inputStore.js +4 -0
  10. package/dist/app/interfaces.js +6 -0
  11. package/dist/app/overlayStore.js +40 -0
  12. package/dist/app/promptStore.js +4 -0
  13. package/dist/app/queueStore.js +4 -0
  14. package/dist/app/scroll.js +44 -0
  15. package/dist/app/setupHandoff.js +28 -0
  16. package/dist/app/slash/commands/core.js +421 -234
  17. package/dist/app/slash/commands/debug.js +39 -6
  18. package/dist/app/slash/commands/ops.js +471 -136
  19. package/dist/app/slash/commands/session.js +405 -65
  20. package/dist/app/slash/commands/setup.js +16 -43
  21. package/dist/app/slash/commands/toggles.js +4 -0
  22. package/dist/app/slash/registry.js +7 -68
  23. package/dist/app/slash/types.js +1 -16
  24. package/dist/app/spawnHistoryStore.js +105 -0
  25. package/dist/app/turnController.js +650 -0
  26. package/dist/app/turnStore.js +47 -59
  27. package/dist/app/uiStore.js +34 -29
  28. package/dist/app/useComposerState.js +265 -0
  29. package/dist/app/useConfigSync.js +144 -0
  30. package/dist/app/useInputHandlers.js +403 -0
  31. package/dist/app/useLongRunToolCharms.js +50 -0
  32. package/dist/app/useMainApp.js +629 -0
  33. package/dist/app/useSessionLifecycle.js +175 -0
  34. package/dist/app/useSubmission.js +287 -0
  35. package/dist/app.js +13 -217
  36. package/dist/banner.js +40 -3
  37. package/dist/components/agentsOverlay.js +474 -0
  38. package/dist/components/appChrome.js +252 -0
  39. package/dist/components/appLayout.js +121 -22
  40. package/dist/components/appOverlays.js +65 -0
  41. package/dist/components/branding.js +97 -6
  42. package/dist/components/fpsOverlay.js +22 -0
  43. package/dist/components/helpHint.js +21 -0
  44. package/dist/components/markdown.js +501 -0
  45. package/dist/components/maskedPrompt.js +12 -0
  46. package/dist/components/messageLine.js +82 -0
  47. package/dist/components/modelPicker.js +254 -0
  48. package/dist/components/overlayControls.js +30 -0
  49. package/dist/components/overlays/helpOverlay.js +1 -0
  50. package/dist/components/overlays/historySearch.js +1 -0
  51. package/dist/components/overlays/modelPicker.js +2 -1
  52. package/dist/components/overlays/overlayUtils.js +2 -1
  53. package/dist/components/overlays/secretPrompt.js +1 -0
  54. package/dist/components/overlays/sessionPicker.js +1 -0
  55. package/dist/components/overlays/skillsHub.js +1 -0
  56. package/dist/components/prompts.js +95 -0
  57. package/dist/components/queuedMessages.js +24 -0
  58. package/dist/components/sessionPicker.js +130 -0
  59. package/dist/components/skillsHub.js +165 -0
  60. package/dist/components/streamingAssistant.js +35 -0
  61. package/dist/components/streamingMarkdown.js +110 -186
  62. package/dist/components/textInput.js +748 -218
  63. package/dist/components/themed.js +12 -0
  64. package/dist/components/thinking.js +493 -36
  65. package/dist/components/todoPanel.js +40 -0
  66. package/dist/config/env.js +18 -0
  67. package/dist/config/limits.js +22 -0
  68. package/dist/config/timing.js +4 -0
  69. package/dist/content/charms.js +5 -0
  70. package/dist/content/faces.js +21 -0
  71. package/dist/content/fortunes.js +29 -0
  72. package/dist/content/hotkeys.js +38 -0
  73. package/dist/content/placeholders.js +15 -0
  74. package/dist/content/setup.js +14 -0
  75. package/dist/content/verbs.js +41 -0
  76. package/dist/domain/details.js +53 -0
  77. package/dist/domain/messages.js +63 -0
  78. package/dist/domain/paths.js +16 -0
  79. package/dist/domain/providers.js +11 -0
  80. package/dist/domain/roles.js +6 -0
  81. package/dist/domain/slash.js +11 -0
  82. package/dist/domain/usage.js +1 -0
  83. package/dist/domain/viewport.js +33 -0
  84. package/dist/entry.js +65 -40
  85. package/dist/gatewayClient.js +574 -0
  86. package/dist/gatewayTypes.js +1 -0
  87. package/dist/hooks/useCompletion.js +86 -0
  88. package/dist/hooks/useGitBranch.js +58 -0
  89. package/dist/hooks/useInputHistory.js +12 -0
  90. package/dist/hooks/useQueue.js +57 -0
  91. package/dist/hooks/useVirtualHistory.js +401 -0
  92. package/dist/lib/circularBuffer.js +43 -0
  93. package/dist/lib/clipboard.js +126 -0
  94. package/dist/lib/editor.js +41 -0
  95. package/dist/lib/editor.test.js +58 -0
  96. package/dist/lib/emoji.js +49 -0
  97. package/dist/lib/externalCli.js +11 -0
  98. package/dist/lib/forceTruecolor.js +26 -0
  99. package/dist/lib/fpsStore.js +36 -0
  100. package/dist/lib/gracefulExit.js +29 -0
  101. package/dist/lib/history.js +69 -0
  102. package/dist/lib/inputMetrics.js +143 -0
  103. package/dist/lib/liveProgress.js +51 -0
  104. package/dist/lib/liveProgress.test.js +89 -0
  105. package/dist/lib/mathUnicode.js +685 -0
  106. package/dist/lib/memory.js +123 -0
  107. package/dist/lib/memoryMonitor.js +76 -0
  108. package/dist/lib/messages.js +3 -0
  109. package/dist/lib/messages.test.js +25 -0
  110. package/dist/lib/osc52.js +53 -0
  111. package/dist/lib/perfPane.js +94 -0
  112. package/dist/lib/platform.js +312 -0
  113. package/dist/lib/precisionWheel.js +25 -0
  114. package/dist/lib/reasoning.js +39 -0
  115. package/dist/lib/rpc.js +26 -0
  116. package/dist/lib/subagentTree.js +287 -0
  117. package/dist/lib/syntax.js +89 -0
  118. package/dist/lib/terminalModes.js +46 -0
  119. package/dist/lib/terminalParity.js +48 -0
  120. package/dist/lib/terminalSetup.js +321 -0
  121. package/dist/lib/text.js +203 -0
  122. package/dist/lib/text.test.js +18 -0
  123. package/dist/lib/todo.js +2 -0
  124. package/dist/lib/todo.test.js +22 -0
  125. package/dist/lib/viewportStore.js +82 -0
  126. package/dist/lib/virtualHeights.js +61 -0
  127. package/dist/lib/wheelAccel.js +143 -0
  128. package/dist/theme.js +398 -0
  129. package/dist/types.js +1 -7
  130. package/package.json +2 -1
@@ -0,0 +1,254 @@
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, useInput, useStdout } from '@cvc/ink';
7
+ import { useEffect, useMemo, useState } from 'react';
8
+ import { providerDisplayNames } from '../domain/providers.js';
9
+ import { TUI_SESSION_MODEL_FLAG } from '../domain/slash.js';
10
+ import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js';
11
+ import { OverlayHint, useOverlayKeys, windowItems } from './overlayControls.js';
12
+ const VISIBLE = 12;
13
+ const MIN_WIDTH = 40;
14
+ const MAX_WIDTH = 90;
15
+ export function ModelPicker({ gw, onCancel, onSelect, sessionId, t }) {
16
+ const [providers, setProviders] = useState([]);
17
+ const [currentModel, setCurrentModel] = useState('');
18
+ const [err, setErr] = useState('');
19
+ const [loading, setLoading] = useState(true);
20
+ const [persistGlobal, setPersistGlobal] = useState(false);
21
+ const [providerIdx, setProviderIdx] = useState(0);
22
+ const [modelIdx, setModelIdx] = useState(0);
23
+ const [stage, setStage] = useState('provider');
24
+ const [keyInput, setKeyInput] = useState('');
25
+ const [keySaving, setKeySaving] = useState(false);
26
+ const [keyError, setKeyError] = useState('');
27
+ const { stdout } = useStdout();
28
+ // Pin the picker to a stable width so the FloatBox parent (which shrinks-
29
+ // to-fit with alignSelf="flex-start") doesn't resize as long provider /
30
+ // model names scroll into view, and so `wrap="truncate-end"` on each row
31
+ // has an actual constraint to truncate against.
32
+ const width = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, (stdout?.columns ?? 80) - 6));
33
+ useEffect(() => {
34
+ gw.request('model.options', sessionId ? { session_id: sessionId } : {})
35
+ .then(raw => {
36
+ const r = asRpcResult(raw);
37
+ if (!r) {
38
+ setErr('invalid response: model.options');
39
+ setLoading(false);
40
+ return;
41
+ }
42
+ const next = r.providers ?? [];
43
+ setProviders(next);
44
+ setCurrentModel(String(r.model ?? ''));
45
+ setProviderIdx(Math.max(0, next.findIndex(p => p.is_current)));
46
+ setModelIdx(0);
47
+ setStage('provider');
48
+ setErr('');
49
+ setLoading(false);
50
+ })
51
+ .catch((e) => {
52
+ setErr(rpcErrorMessage(e));
53
+ setLoading(false);
54
+ });
55
+ }, [gw, sessionId]);
56
+ const provider = providers[providerIdx];
57
+ const models = provider?.models ?? [];
58
+ const names = useMemo(() => providerDisplayNames(providers), [providers]);
59
+ const back = () => {
60
+ if (stage === 'model' || stage === 'key' || stage === 'disconnect') {
61
+ setStage('provider');
62
+ setModelIdx(0);
63
+ setKeyInput('');
64
+ setKeyError('');
65
+ setKeySaving(false);
66
+ return;
67
+ }
68
+ onCancel();
69
+ };
70
+ useOverlayKeys({ onBack: back, onClose: onCancel });
71
+ useInput((ch, key) => {
72
+ // Key entry stage handles its own input
73
+ if (stage === 'key') {
74
+ if (keySaving) {
75
+ return;
76
+ }
77
+ if (key.return) {
78
+ if (!keyInput.trim()) {
79
+ return;
80
+ }
81
+ setKeySaving(true);
82
+ setKeyError('');
83
+ gw.request('model.save_key', {
84
+ slug: provider?.slug,
85
+ api_key: keyInput.trim(),
86
+ ...(sessionId ? { session_id: sessionId } : {}),
87
+ })
88
+ .then(raw => {
89
+ const r = asRpcResult(raw);
90
+ if (!r?.provider) {
91
+ setKeyError('failed to save key');
92
+ setKeySaving(false);
93
+ return;
94
+ }
95
+ // Update the provider in our list with fresh data
96
+ setProviders(prev => prev.map(p => p.slug === r.provider.slug ? r.provider : p));
97
+ setKeyInput('');
98
+ setKeySaving(false);
99
+ setStage('model');
100
+ setModelIdx(0);
101
+ })
102
+ .catch((e) => {
103
+ setKeyError(rpcErrorMessage(e));
104
+ setKeySaving(false);
105
+ });
106
+ return;
107
+ }
108
+ if (key.backspace || key.delete) {
109
+ setKeyInput(v => v.slice(0, -1));
110
+ return;
111
+ }
112
+ // ctrl+u clears input
113
+ if (ch === '\u0015') {
114
+ setKeyInput('');
115
+ return;
116
+ }
117
+ if (ch && !key.ctrl && !key.meta) {
118
+ setKeyInput(v => v + ch);
119
+ }
120
+ return;
121
+ }
122
+ // Disconnect confirmation stage
123
+ if (stage === 'disconnect') {
124
+ if (ch.toLowerCase() === 'y' || key.return) {
125
+ if (!provider) {
126
+ setStage('provider');
127
+ return;
128
+ }
129
+ setKeySaving(true);
130
+ gw.request('model.disconnect', {
131
+ slug: provider.slug,
132
+ ...(sessionId ? { session_id: sessionId } : {}),
133
+ })
134
+ .then(raw => {
135
+ const r = asRpcResult(raw);
136
+ if (r?.disconnected) {
137
+ // Mark provider as unauthenticated in local state
138
+ setProviders(prev => prev.map(p => p.slug === provider.slug
139
+ ? { ...p, authenticated: false, models: [], total_models: 0, warning: p.key_env ? `paste ${p.key_env} to activate` : 'run `hermes model` to configure' }
140
+ : p));
141
+ }
142
+ setKeySaving(false);
143
+ setStage('provider');
144
+ })
145
+ .catch(() => {
146
+ setKeySaving(false);
147
+ setStage('provider');
148
+ });
149
+ return;
150
+ }
151
+ if (ch.toLowerCase() === 'n' || key.escape) {
152
+ setStage('provider');
153
+ return;
154
+ }
155
+ return;
156
+ }
157
+ const count = stage === 'provider' ? providers.length : models.length;
158
+ const sel = stage === 'provider' ? providerIdx : modelIdx;
159
+ const setSel = stage === 'provider' ? setProviderIdx : setModelIdx;
160
+ if (key.upArrow && sel > 0) {
161
+ setSel(v => v - 1);
162
+ return;
163
+ }
164
+ if (key.downArrow && sel < count - 1) {
165
+ setSel(v => v + 1);
166
+ return;
167
+ }
168
+ if (key.return) {
169
+ if (stage === 'provider') {
170
+ if (!provider) {
171
+ return;
172
+ }
173
+ if (provider.authenticated === false) {
174
+ // api_key providers: prompt for key inline
175
+ if (provider.auth_type === 'api_key' && provider.key_env) {
176
+ setStage('key');
177
+ setKeyInput('');
178
+ setKeyError('');
179
+ }
180
+ // Other auth types: no-op (warning shown tells them to run hermes model)
181
+ return;
182
+ }
183
+ setStage('model');
184
+ setModelIdx(0);
185
+ return;
186
+ }
187
+ const model = models[modelIdx];
188
+ if (provider && model) {
189
+ onSelect(`${model} --provider ${provider.slug}${persistGlobal ? ' --global' : ` ${TUI_SESSION_MODEL_FLAG}`}`);
190
+ }
191
+ else {
192
+ setStage('provider');
193
+ }
194
+ return;
195
+ }
196
+ if (ch.toLowerCase() === 'g') {
197
+ setPersistGlobal(v => !v);
198
+ return;
199
+ }
200
+ // Disconnect: only in provider stage, only for authenticated providers
201
+ if (ch.toLowerCase() === 'd' && stage === 'provider' && provider?.authenticated !== false) {
202
+ setStage('disconnect');
203
+ return;
204
+ }
205
+ });
206
+ if (loading) {
207
+ return _jsx(Text, { color: t.color.muted, children: "loading models\u2026" });
208
+ }
209
+ if (err) {
210
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: t.color.label, children: ["error: ", err] }), _jsx(OverlayHint, { t: t, children: "Esc/q cancel" })] }));
211
+ }
212
+ if (!providers.length) {
213
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: t.color.muted, children: "no providers available" }), _jsx(OverlayHint, { t: t, children: "Esc/q cancel" })] }));
214
+ }
215
+ // ── Key entry stage ──────────────────────────────────────────────────
216
+ if (stage === 'key' && provider) {
217
+ const masked = keyInput ? '•'.repeat(Math.min(keyInput.length, 40)) : '';
218
+ return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Text, { bold: true, color: t.color.accent, wrap: "truncate-end", children: ["Configure ", provider.name] }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: "Paste your API key below (saved to ~/.hermes/.env)" }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: " " }), _jsxs(Text, { color: t.color.muted, wrap: "truncate-end", children: [provider.key_env, ":"] }), _jsxs(Text, { color: t.color.accent, wrap: "truncate-end", children: [' ', masked || '(empty)', keySaving ? '' : '▎'] }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: " " }), keyError ? (_jsxs(Text, { color: t.color.label, wrap: "truncate-end", children: ["error: ", keyError] })) : keySaving ? (_jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: "saving\u2026" })) : (_jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: " " })), _jsx(OverlayHint, { t: t, children: "Enter save \u00B7 Ctrl+U clear \u00B7 Esc back" })] }));
219
+ }
220
+ // ── Disconnect confirmation stage ─────────────────────────────────────
221
+ if (stage === 'disconnect' && provider) {
222
+ return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Text, { bold: true, color: t.color.accent, wrap: "truncate-end", children: ["Disconnect ", provider.name, "?"] }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: " " }), _jsxs(Text, { color: t.color.muted, wrap: "truncate-end", children: ["This removes saved credentials for ", provider.name, "."] }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: "You can re-authenticate later by selecting it again." }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: " " }), keySaving ? (_jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: "disconnecting\u2026" })) : (_jsx(OverlayHint, { t: t, children: "y/Enter confirm \u00B7 n/Esc cancel" }))] }));
223
+ }
224
+ // ── Provider selection stage ─────────────────────────────────────────
225
+ if (stage === 'provider') {
226
+ const rows = providers.map((p, i) => {
227
+ const authMark = p.authenticated === false ? '○' : p.is_current ? '*' : '●';
228
+ const modelCount = p.total_models ?? p.models?.length ?? 0;
229
+ const suffix = p.authenticated === false
230
+ ? (p.auth_type === 'api_key' ? '(no key)' : '(needs setup)')
231
+ : `${modelCount} models`;
232
+ return `${authMark} ${names[i]} · ${suffix}`;
233
+ });
234
+ const { items, offset } = windowItems(rows, providerIdx, VISIBLE);
235
+ return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsx(Text, { bold: true, color: t.color.accent, wrap: "truncate-end", children: "Select provider (step 1/2)" }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: "Full model IDs on the next step \u00B7 Enter to continue" }), _jsxs(Text, { color: t.color.muted, wrap: "truncate-end", children: ["Current: ", currentModel || '(unknown)'] }), _jsx(Text, { color: t.color.label, wrap: "truncate-end", children: provider?.warning ? `warning: ${provider.warning}` : ' ' }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: offset > 0 ? ` ↑ ${offset} more` : ' ' }), Array.from({ length: VISIBLE }, (_, i) => {
236
+ const row = items[i];
237
+ const idx = offset + i;
238
+ const p = providers[idx];
239
+ const dimmed = p?.authenticated === false;
240
+ return row ? (_jsxs(Text, { bold: providerIdx === idx, color: providerIdx === idx ? t.color.accent : dimmed ? t.color.label : t.color.muted, inverse: providerIdx === idx, wrap: "truncate-end", children: [providerIdx === idx ? '▸ ' : ' ', idx + 1, ". ", row] }, providers[idx]?.slug ?? `row-${idx}`)) : (_jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: ' ' }, `pad-${i}`));
241
+ }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: offset + VISIBLE < rows.length ? ` ↓ ${rows.length - offset - VISIBLE} more` : ' ' }), _jsxs(Text, { color: t.color.muted, wrap: "truncate-end", children: ["persist: ", persistGlobal ? 'global' : 'session', " \u00B7 g toggle"] }), _jsx(OverlayHint, { t: t, children: "\u2191/\u2193 select \u00B7 Enter choose \u00B7 d disconnect \u00B7 Esc/q cancel" })] }));
242
+ }
243
+ // ── Model selection stage ────────────────────────────────────────────
244
+ const { items, offset } = windowItems(models, modelIdx, VISIBLE);
245
+ return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsx(Text, { bold: true, color: t.color.accent, wrap: "truncate-end", children: "Select model (step 2/2)" }), _jsxs(Text, { color: t.color.muted, wrap: "truncate-end", children: [names[providerIdx] || '(unknown provider)', " \u00B7 Esc back"] }), _jsx(Text, { color: t.color.label, wrap: "truncate-end", children: provider?.warning ? `warning: ${provider.warning}` : ' ' }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: offset > 0 ? ` ↑ ${offset} more` : ' ' }), Array.from({ length: VISIBLE }, (_, i) => {
246
+ const row = items[i];
247
+ const idx = offset + i;
248
+ if (!row) {
249
+ return !models.length && i === 0 ? (_jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: "no models listed for this provider" }, "empty")) : (_jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: ' ' }, `pad-${i}`));
250
+ }
251
+ const prefix = modelIdx === idx ? '▸ ' : row === currentModel ? '* ' : ' ';
252
+ return (_jsxs(Text, { bold: modelIdx === idx, color: modelIdx === idx ? t.color.accent : t.color.muted, inverse: modelIdx === idx, wrap: "truncate-end", children: [prefix, idx + 1, ". ", row] }, `${provider?.slug ?? 'prov'}:${idx}:${row}`));
253
+ }), _jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: offset + VISIBLE < models.length ? ` ↓ ${models.length - offset - VISIBLE} more` : ' ' }), _jsxs(Text, { color: t.color.muted, wrap: "truncate-end", children: ["persist: ", persistGlobal ? 'global' : 'session', " \u00B7 g toggle"] }), _jsx(OverlayHint, { t: t, children: models.length ? '↑/↓ select · Enter switch · Esc back · q close' : 'Enter/Esc back · q close' })] }));
254
+ }
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } 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 { Text, useInput } from '@cvc/ink';
7
+ export function useOverlayKeys({ disabled = false, onBack, onClose }) {
8
+ useInput((ch, key) => {
9
+ if (disabled) {
10
+ return;
11
+ }
12
+ if (ch === 'q') {
13
+ return onClose();
14
+ }
15
+ if (key.escape) {
16
+ return onBack ? onBack() : onClose();
17
+ }
18
+ });
19
+ }
20
+ export function OverlayHint({ children, t }) {
21
+ return (_jsx(Text, { color: t.color.muted, wrap: "truncate-end", children: children }));
22
+ }
23
+ export const windowOffset = (count, selected, visible) => Math.max(0, Math.min(selected - Math.floor(visible / 2), count - visible));
24
+ export function windowItems(items, selected, visible) {
25
+ const offset = windowOffset(items.length, selected, visible);
26
+ return {
27
+ items: items.slice(offset, offset + visible),
28
+ offset
29
+ };
30
+ }
@@ -1,4 +1,5 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ // @ts-nocheck
2
3
  // CVC helpOverlay — full-screen /help overlay listing every slash command
3
4
  // from the central registry, grouped by category. Arrow-key scrollable.
4
5
  import { useMemo, useState } from 'react';
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @ts-nocheck
2
3
  // Reverse-incremental history search overlay (Ctrl+R).
3
4
  // Keystrokes refine the query; Up/Down moves through matches; Enter accepts;
4
5
  // Esc cancels.
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @ts-nocheck
2
3
  // CVC modelPicker overlay — arrow-key navigable, two-stage (provider → model).
3
- // Behavior parity with Hermes ui-tui modelPicker, but consumes preloaded data
4
+ // Behavior parity with CVC ui-tui modelPicker, but consumes preloaded data
4
5
  // via props so it stays trivial to test and wire.
5
6
  import { useMemo, useState } from 'react';
6
7
  import { Box, Text, useInput } from 'ink';
@@ -1,6 +1,7 @@
1
+ // @ts-nocheck
1
2
  // Tiny shared helpers for CVC arrow-key navigable overlays.
2
3
  // Keeps modelPicker / sessionPicker / skillsHub / helpOverlay consistent
3
- // without dragging in the heavier Hermes overlayControls surface.
4
+ // without dragging in the heavier CVC overlayControls surface.
4
5
  /** Window a list around the selected index so a cursor at any depth stays visible. */
5
6
  export function windowItems(all, selected, visible) {
6
7
  if (all.length <= visible)
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @ts-nocheck
2
3
  // Secret prompt overlay — masked input. Resolves with the entered string,
3
4
  // or null on Esc.
4
5
  import { useState } from 'react';
@@ -1,4 +1,5 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ // @ts-nocheck
2
3
  // CVC sessionPicker overlay — lists ~/.cvc/sessions/*.json with timestamp +
3
4
  // first-message preview, arrow-key navigable, filter input.
4
5
  import { useEffect, useMemo, useState } from 'react';
@@ -1,4 +1,5 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // @ts-nocheck
2
3
  // CVC skillsHub overlay — list skills (via gateway or supplied prop), search/filter,
3
4
  // arrow-key navigable, Enter triggers an onActivate callback (e.g. to inject the
4
5
  // skill into the next user message).
@@ -0,0 +1,95 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } 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, useInput } from '@cvc/ink';
7
+ import { useState } from 'react';
8
+ import { isMac } from '../lib/platform.js';
9
+ import { TextInput } from './textInput.js';
10
+ const OPTS = ['once', 'session', 'always', 'deny'];
11
+ const LABELS = { always: 'Always allow', deny: 'Deny', once: 'Allow once', session: 'Allow this session' };
12
+ const CMD_PREVIEW_LINES = 10;
13
+ export function ApprovalPrompt({ onChoice, req, t }) {
14
+ const [sel, setSel] = useState(0);
15
+ useInput((ch, key) => {
16
+ if (key.upArrow && sel > 0) {
17
+ setSel(s => s - 1);
18
+ }
19
+ if (key.downArrow && sel < OPTS.length - 1) {
20
+ setSel(s => s + 1);
21
+ }
22
+ const n = parseInt(ch, 10);
23
+ if (n >= 1 && n <= OPTS.length) {
24
+ onChoice(OPTS[n - 1]);
25
+ return;
26
+ }
27
+ if (key.return) {
28
+ onChoice(OPTS[sel]);
29
+ }
30
+ });
31
+ const rawLines = req.command.split('\n');
32
+ const shown = rawLines.slice(0, CMD_PREVIEW_LINES);
33
+ const overflow = rawLines.length - shown.length;
34
+ return (_jsxs(Box, { borderColor: t.color.warn, borderStyle: "double", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { bold: true, color: t.color.warn, children: ["\u26A0 approval required \u00B7 ", req.description] }), _jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [shown.map((line, i) => (_jsx(Text, { color: t.color.text, wrap: "truncate-end", children: line || ' ' }, i))), overflow > 0 ? (_jsxs(Text, { color: t.color.muted, children: ["\u2026 +", overflow, " more line", overflow === 1 ? '' : 's', " (full text above)"] })) : null] }), _jsx(Text, {}), OPTS.map((o, i) => (_jsx(Text, { children: _jsxs(Text, { bold: sel === i, color: sel === i ? t.color.warn : t.color.muted, inverse: sel === i, children: [sel === i ? '▸ ' : ' ', i + 1, ". ", LABELS[o]] }) }, o))), _jsx(Text, { color: t.color.muted, children: "\u2191/\u2193 select \u00B7 Enter confirm \u00B7 1-4 quick pick \u00B7 Ctrl+C deny" })] }));
35
+ }
36
+ export function ClarifyPrompt({ cols = 80, onAnswer, onCancel, req, t }) {
37
+ const [sel, setSel] = useState(0);
38
+ const [custom, setCustom] = useState('');
39
+ const [typing, setTyping] = useState(false);
40
+ const choices = req.choices ?? [];
41
+ const heading = (_jsxs(Text, { bold: true, children: [_jsx(Text, { color: t.color.accent, children: "ask" }), _jsxs(Text, { color: t.color.text, children: [" ", req.question] })] }));
42
+ useInput((ch, key) => {
43
+ if (key.escape) {
44
+ typing && choices.length ? setTyping(false) : onCancel();
45
+ return;
46
+ }
47
+ if (typing || !choices.length) {
48
+ return;
49
+ }
50
+ if (key.upArrow && sel > 0) {
51
+ setSel(s => s - 1);
52
+ }
53
+ if (key.downArrow && sel < choices.length) {
54
+ setSel(s => s + 1);
55
+ }
56
+ if (key.return) {
57
+ sel === choices.length ? setTyping(true) : choices[sel] && onAnswer(choices[sel]);
58
+ }
59
+ const n = parseInt(ch);
60
+ if (n >= 1 && n <= choices.length) {
61
+ onAnswer(choices[n - 1]);
62
+ }
63
+ });
64
+ if (typing || !choices.length) {
65
+ return (_jsxs(Box, { flexDirection: "column", children: [heading, _jsxs(Box, { children: [_jsx(Text, { color: t.color.label, children: '> ' }), _jsx(TextInput, { columns: Math.max(20, cols - 6), onChange: setCustom, onSubmit: onAnswer, value: custom })] }), _jsxs(Text, { color: t.color.muted, children: ["Enter send \u00B7 Esc ", choices.length ? 'back' : 'cancel', " \u00B7", ' ', isMac ? 'Cmd+C copy · Cmd+V paste · Ctrl+C cancel' : 'Ctrl+C cancel'] })] }));
66
+ }
67
+ return (_jsxs(Box, { flexDirection: "column", children: [heading, [...choices, 'Other (type your answer)'].map((c, i) => (_jsx(Text, { children: _jsxs(Text, { bold: sel === i, color: sel === i ? t.color.label : t.color.muted, inverse: sel === i, children: [sel === i ? '▸ ' : ' ', i + 1, ". ", c] }) }, i))), _jsxs(Text, { color: t.color.muted, children: ["\u2191/\u2193 select \u00B7 Enter confirm \u00B7 1-", choices.length, " quick pick \u00B7 Esc/Ctrl+C cancel"] })] }));
68
+ }
69
+ export function ConfirmPrompt({ onCancel, onConfirm, req, t }) {
70
+ const [sel, setSel] = useState(0);
71
+ useInput((ch, key) => {
72
+ const lower = ch.toLowerCase();
73
+ if (key.escape || (key.ctrl && lower === 'c') || lower === 'n') {
74
+ return onCancel();
75
+ }
76
+ if (lower === 'y') {
77
+ return onConfirm();
78
+ }
79
+ if (key.upArrow) {
80
+ setSel(0);
81
+ }
82
+ if (key.downArrow) {
83
+ setSel(1);
84
+ }
85
+ if (key.return) {
86
+ sel === 0 ? onCancel() : onConfirm();
87
+ }
88
+ });
89
+ const accent = req.danger ? t.color.error : t.color.warn;
90
+ const rows = [
91
+ { color: t.color.text, label: req.cancelLabel ?? 'No' },
92
+ { color: req.danger ? t.color.error : t.color.text, label: req.confirmLabel ?? 'Yes' }
93
+ ];
94
+ return (_jsxs(Box, { borderColor: accent, borderStyle: "double", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { bold: true, color: accent, children: [req.danger ? '⚠' : '?', " ", req.title] }), req.detail ? (_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: t.color.text, wrap: "truncate-end", children: req.detail }) })) : null, _jsx(Text, {}), rows.map((row, i) => (_jsxs(Text, { children: [_jsx(Text, { color: sel === i ? accent : t.color.muted, children: sel === i ? '▸ ' : ' ' }), _jsx(Text, { color: sel === i ? row.color : t.color.muted, children: row.label })] }, row.label))), _jsx(Text, { color: t.color.muted, children: "\u2191/\u2193 select \u00B7 Enter confirm \u00B7 Y/N quick \u00B7 Esc cancel" })] }));
95
+ }
@@ -0,0 +1,24 @@
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 { compactPreview } from '../lib/text.js';
8
+ export const QUEUE_WINDOW = 3;
9
+ export function getQueueWindow(queueLen, queueEditIdx) {
10
+ const start = queueEditIdx === null ? 0 : Math.max(0, Math.min(queueEditIdx - 1, Math.max(0, queueLen - QUEUE_WINDOW)));
11
+ const end = Math.min(queueLen, start + QUEUE_WINDOW);
12
+ return { end, showLead: start > 0, showTail: end < queueLen, start };
13
+ }
14
+ export function QueuedMessages({ cols, queueEditIdx, queued, t }) {
15
+ if (!queued.length) {
16
+ return null;
17
+ }
18
+ const q = getQueueWindow(queued.length, queueEditIdx);
19
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: t.color.muted, dimColor: true, children: `queued (${queued.length})${queueEditIdx !== null ? ` · editing ${queueEditIdx + 1} · Ctrl+X delete · Esc cancel` : ''}` }), q.showLead && (_jsxs(Text, { color: t.color.muted, dimColor: true, children: [' ', "\u2026"] })), queued.slice(q.start, q.end).map((item, i) => {
20
+ const idx = q.start + i;
21
+ const active = queueEditIdx === idx;
22
+ return (_jsxs(Text, { color: active ? t.color.accent : t.color.muted, dimColor: true, children: [active ? '▸' : ' ', " ", idx + 1, ". ", compactPreview(item, Math.max(16, cols - 10))] }, `${idx}-${item.slice(0, 16)}`));
23
+ }), q.showTail && (_jsxs(Text, { color: t.color.muted, dimColor: true, children: [' ', "\u2026and ", queued.length - q.end, " more"] }))] }));
24
+ }
@@ -0,0 +1,130 @@
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, useInput, useStdout } from '@cvc/ink';
7
+ import { useEffect, useState } from 'react';
8
+ import { asRpcResult, rpcErrorMessage } from '../lib/rpc.js';
9
+ import { OverlayHint, useOverlayKeys, windowOffset } from './overlayControls.js';
10
+ const VISIBLE = 15;
11
+ const MIN_WIDTH = 60;
12
+ const MAX_WIDTH = 120;
13
+ const age = (ts) => {
14
+ const d = (Date.now() / 1000 - ts) / 86400;
15
+ if (d < 1) {
16
+ return 'today';
17
+ }
18
+ if (d < 2) {
19
+ return 'yesterday';
20
+ }
21
+ return `${Math.floor(d)}d ago`;
22
+ };
23
+ export function SessionPicker({ gw, onCancel, onSelect, t }) {
24
+ const [items, setItems] = useState([]);
25
+ const [err, setErr] = useState('');
26
+ const [sel, setSel] = useState(0);
27
+ const [loading, setLoading] = useState(true);
28
+ // When non-null, the user pressed `d` on this index and we're waiting for
29
+ // a second `d`/`D` to confirm deletion. Any other key cancels the prompt.
30
+ const [confirmDelete, setConfirmDelete] = useState(null);
31
+ const [deleting, setDeleting] = useState(false);
32
+ const { stdout } = useStdout();
33
+ const width = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, (stdout?.columns ?? 80) - 6));
34
+ useOverlayKeys({ onClose: onCancel });
35
+ useEffect(() => {
36
+ gw.request('session.list', { limit: 200 })
37
+ .then(raw => {
38
+ const r = asRpcResult(raw);
39
+ if (!r) {
40
+ setErr('invalid response: session.list');
41
+ setLoading(false);
42
+ return;
43
+ }
44
+ setItems(r.sessions ?? []);
45
+ setErr('');
46
+ setLoading(false);
47
+ })
48
+ .catch((e) => {
49
+ setErr(rpcErrorMessage(e));
50
+ setLoading(false);
51
+ });
52
+ }, [gw]);
53
+ const performDelete = (index) => {
54
+ const target = items[index];
55
+ if (!target || deleting) {
56
+ return;
57
+ }
58
+ setDeleting(true);
59
+ gw.request('session.delete', { session_id: target.id })
60
+ .then(raw => {
61
+ const r = asRpcResult(raw);
62
+ if (!r || r.deleted !== target.id) {
63
+ setErr('invalid response: session.delete');
64
+ setDeleting(false);
65
+ return;
66
+ }
67
+ setItems(prev => {
68
+ const next = prev.filter((_, i) => i !== index);
69
+ setSel(s => Math.max(0, Math.min(s, next.length - 1)));
70
+ return next;
71
+ });
72
+ setErr('');
73
+ setDeleting(false);
74
+ })
75
+ .catch((e) => {
76
+ setErr(rpcErrorMessage(e));
77
+ setDeleting(false);
78
+ });
79
+ };
80
+ useInput((ch, key) => {
81
+ if (deleting) {
82
+ return;
83
+ }
84
+ if (confirmDelete !== null) {
85
+ if (ch?.toLowerCase() === 'd') {
86
+ const idx = confirmDelete;
87
+ setConfirmDelete(null);
88
+ performDelete(idx);
89
+ }
90
+ else {
91
+ setConfirmDelete(null);
92
+ }
93
+ return;
94
+ }
95
+ if (key.upArrow && sel > 0) {
96
+ setSel(s => s - 1);
97
+ }
98
+ if (key.downArrow && sel < items.length - 1) {
99
+ setSel(s => s + 1);
100
+ }
101
+ if (key.return && items[sel]) {
102
+ onSelect(items[sel].id);
103
+ return;
104
+ }
105
+ if (ch?.toLowerCase() === 'd' && items[sel]) {
106
+ setConfirmDelete(sel);
107
+ return;
108
+ }
109
+ const n = parseInt(ch);
110
+ if (n >= 1 && n <= Math.min(9, items.length)) {
111
+ onSelect(items[n - 1].id);
112
+ }
113
+ });
114
+ if (loading) {
115
+ return _jsx(Text, { color: t.color.muted, children: "loading sessions\u2026" });
116
+ }
117
+ if (err && !items.length) {
118
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: t.color.label, children: ["error: ", err] }), _jsx(OverlayHint, { t: t, children: "Esc/q cancel" })] }));
119
+ }
120
+ if (!items.length) {
121
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: t.color.muted, children: "no previous sessions" }), _jsx(OverlayHint, { t: t, children: "Esc/q cancel" })] }));
122
+ }
123
+ const offset = windowOffset(items.length, sel, VISIBLE);
124
+ return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsx(Text, { bold: true, color: t.color.accent, children: "Resume Session" }), offset > 0 && _jsxs(Text, { color: t.color.muted, children: [" \u2191 ", offset, " more"] }), items.slice(offset, offset + VISIBLE).map((s, vi) => {
125
+ const i = offset + vi;
126
+ const selected = sel === i;
127
+ const pendingDelete = confirmDelete === i;
128
+ return (_jsxs(Box, { children: [_jsx(Text, { bold: selected, color: selected ? t.color.accent : t.color.muted, inverse: selected, children: selected ? '▸ ' : ' ' }), _jsx(Box, { width: 30, children: _jsxs(Text, { bold: selected, color: selected ? t.color.accent : t.color.muted, inverse: selected, children: [String(i + 1).padStart(2), ". [", s.id, "]"] }) }), _jsx(Box, { width: 30, children: _jsxs(Text, { bold: selected, color: selected ? t.color.accent : t.color.muted, inverse: selected, children: ["(", s.message_count, " msgs, ", age(s.started_at), ", ", s.source || 'tui', ")"] }) }), _jsx(Text, { bold: selected, color: pendingDelete ? t.color.label : selected ? t.color.accent : t.color.muted, inverse: selected, wrap: "truncate-end", children: pendingDelete ? 'press d again to delete' : s.title || s.preview || '(untitled)' })] }, s.id));
129
+ }), offset + VISIBLE < items.length && _jsxs(Text, { color: t.color.muted, children: [" \u2193 ", items.length - offset - VISIBLE, " more"] }), err && _jsxs(Text, { color: t.color.label, children: ["error: ", err] }), deleting ? (_jsx(OverlayHint, { t: t, children: "deleting\u2026" })) : (_jsx(OverlayHint, { t: t, children: "\u2191/\u2193 select \u00B7 Enter resume \u00B7 1-9 quick \u00B7 d delete \u00B7 Esc/q cancel" }))] }));
130
+ }