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