cvc-tui 0.4.3 → 0.4.5
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/NOTICES.md +13 -0
- package/dist/app/completion.js +102 -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 +123 -0
- package/dist/app/inputBuffer.js +120 -0
- package/dist/app/inputSelectionStore.js +8 -0
- package/dist/app/inputStore.js +28 -0
- package/dist/app/interfaces.js +6 -0
- package/dist/app/overlayStore.js +40 -0
- package/dist/app/promptStore.js +44 -0
- package/dist/app/queueStore.js +25 -0
- package/dist/app/scroll.js +44 -0
- package/dist/app/setupHandoff.js +28 -0
- package/dist/app/slash/commands/core.js +479 -0
- package/dist/app/slash/commands/debug.js +44 -0
- package/dist/app/slash/commands/ops.js +498 -0
- package/dist/app/slash/commands/session.js +431 -0
- package/dist/app/slash/commands/setup.js +20 -0
- package/dist/app/slash/commands/toggles.js +40 -0
- package/dist/app/slash/registry.js +18 -0
- package/dist/app/slash/types.js +1 -0
- package/dist/app/spawnHistoryStore.js +105 -0
- package/dist/app/turnController.js +650 -0
- package/dist/app/turnStore.js +48 -0
- package/dist/app/uiStore.js +36 -0
- 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 +15 -0
- package/dist/banner.js +63 -0
- package/dist/components/agentsOverlay.js +474 -0
- package/dist/components/appChrome.js +252 -0
- package/dist/components/appLayout.js +121 -0
- package/dist/components/appOverlays.js +65 -0
- package/dist/components/branding.js +97 -0
- 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/confirmPrompt.js +25 -0
- package/dist/components/overlays/helpOverlay.js +76 -0
- package/dist/components/overlays/historySearch.js +49 -0
- package/dist/components/overlays/modelPicker.js +60 -0
- package/dist/components/overlays/overlayUtils.js +19 -0
- package/dist/components/overlays/secretPrompt.js +36 -0
- package/dist/components/overlays/sessionPicker.js +93 -0
- package/dist/components/overlays/skillsHub.js +71 -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 +144 -0
- package/dist/components/textInput.js +794 -0
- package/dist/components/themed.js +12 -0
- package/dist/components/thinking.js +496 -0
- package/dist/components/todoPanel.js +40 -0
- package/dist/components/transcript.js +22 -0
- package/dist/config/env.js +18 -0
- package/dist/config/limits.js +22 -0
- package/dist/config/timing.js +25 -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 +64 -71149
- package/dist/gateway/client.js +312 -0
- 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/react-devtools-stub.js +12 -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/protocol/interpolation.js +4 -0
- package/dist/protocol/paste.js +3 -0
- package/dist/theme.js +398 -0
- package/dist/types.js +1 -0
- package/dist/vendor/cvc-ink/dist/entry-exports.js +52737 -0
- package/dist/vendor/cvc-ink/index.js +1 -0
- package/dist/vendor/cvc-ink/package.json +9 -0
- package/dist/vendor/cvc-ink/text-input.js +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Ported from CVC Agent (https://github.com/NousResearch/cvc)
|
|
4
|
+
// Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
|
|
5
|
+
import { forceRedraw } from '../../../vendor/cvc-ink/index.js';
|
|
6
|
+
import { NO_CONFIRM_DESTRUCTIVE } from '../../../config/env.js';
|
|
7
|
+
import { dailyFortune, randomFortune } from '../../../content/fortunes.js';
|
|
8
|
+
import { HOTKEYS } from '../../../content/hotkeys.js';
|
|
9
|
+
import { SECTION_NAMES, isSectionName, nextDetailsMode, parseDetailsMode } from '../../../domain/details.js';
|
|
10
|
+
import { writeClipboardText } from '../../../lib/clipboard.js';
|
|
11
|
+
import { writeOsc52Clipboard } from '../../../lib/osc52.js';
|
|
12
|
+
import { configureDetectedTerminalKeybindings, configureTerminalKeybindings } from '../../../lib/terminalSetup.js';
|
|
13
|
+
import { patchOverlayState } from '../../overlayStore.js';
|
|
14
|
+
import { patchUiState } from '../../uiStore.js';
|
|
15
|
+
const flagFromArg = (arg, current) => {
|
|
16
|
+
if (!arg) {
|
|
17
|
+
return !current;
|
|
18
|
+
}
|
|
19
|
+
const mode = arg.trim().toLowerCase();
|
|
20
|
+
if (mode === 'on') {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (mode === 'off') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (mode === 'toggle') {
|
|
27
|
+
return !current;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
const RESET_WORDS = new Set(['reset', 'clear', 'default']);
|
|
32
|
+
const CYCLE_WORDS = new Set(['cycle', 'toggle']);
|
|
33
|
+
const DETAILS_USAGE = 'usage: /details [hidden|collapsed|expanded|cycle] or /details <section> [hidden|collapsed|expanded|reset]';
|
|
34
|
+
const DETAILS_SECTION_USAGE = 'usage: /details <section> [hidden|collapsed|expanded|reset]';
|
|
35
|
+
export const coreCommands = [
|
|
36
|
+
{
|
|
37
|
+
help: 'list commands + hotkeys',
|
|
38
|
+
name: 'help',
|
|
39
|
+
run: (_arg, ctx) => {
|
|
40
|
+
const sections = (ctx.local.catalog?.categories ?? []).map(cat => ({
|
|
41
|
+
rows: cat.pairs,
|
|
42
|
+
title: cat.name
|
|
43
|
+
}));
|
|
44
|
+
if (ctx.local.catalog?.skillCount) {
|
|
45
|
+
sections.push({ text: `${ctx.local.catalog.skillCount} skill commands available — /skills to browse` });
|
|
46
|
+
}
|
|
47
|
+
sections.push({
|
|
48
|
+
rows: [
|
|
49
|
+
['/details [hidden|collapsed|expanded|cycle]', 'set global agent detail visibility mode'],
|
|
50
|
+
[
|
|
51
|
+
'/details <section> [hidden|collapsed|expanded|reset]',
|
|
52
|
+
'override one section (thinking/tools/subagents/activity)'
|
|
53
|
+
],
|
|
54
|
+
['/fortune [random|daily]', 'show a random or daily local fortune']
|
|
55
|
+
],
|
|
56
|
+
title: 'TUI'
|
|
57
|
+
}, { rows: HOTKEYS, title: 'Hotkeys' });
|
|
58
|
+
ctx.transcript.panel(ctx.ui.theme.brand.helpHeader, sections);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
aliases: ['exit', 'q'],
|
|
63
|
+
help: 'exit hermes',
|
|
64
|
+
name: 'quit',
|
|
65
|
+
run: (_arg, ctx) => ctx.session.die()
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
aliases: ['scroll'],
|
|
69
|
+
help: 'toggle mouse/wheel tracking [on|off|toggle]',
|
|
70
|
+
name: 'mouse',
|
|
71
|
+
run: (arg, ctx) => {
|
|
72
|
+
const current = ctx.ui.mouseTracking;
|
|
73
|
+
const next = flagFromArg(arg, current);
|
|
74
|
+
if (next === null) {
|
|
75
|
+
return ctx.transcript.sys('usage: /mouse [on|off|toggle]');
|
|
76
|
+
}
|
|
77
|
+
patchUiState({ mouseTracking: next });
|
|
78
|
+
ctx.gateway.rpc('config.set', { key: 'mouse', value: next ? 'on' : 'off' }).catch(() => { });
|
|
79
|
+
queueMicrotask(() => ctx.transcript.sys(`mouse tracking ${next ? 'on' : 'off'}`));
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
aliases: ['new'],
|
|
84
|
+
help: 'start a new session',
|
|
85
|
+
name: 'clear',
|
|
86
|
+
run: (arg, ctx, cmd) => {
|
|
87
|
+
if (ctx.session.guardBusySessionSwitch('switch sessions')) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const isNew = cmd.startsWith('/new');
|
|
91
|
+
const requestedTitle = isNew ? arg.trim() : '';
|
|
92
|
+
const commit = () => {
|
|
93
|
+
patchUiState({ status: 'forging session…' });
|
|
94
|
+
ctx.session.newSession(isNew ? 'new session started' : undefined, requestedTitle || undefined);
|
|
95
|
+
};
|
|
96
|
+
if (NO_CONFIRM_DESTRUCTIVE) {
|
|
97
|
+
return commit();
|
|
98
|
+
}
|
|
99
|
+
patchOverlayState({
|
|
100
|
+
confirm: {
|
|
101
|
+
cancelLabel: 'No, keep going',
|
|
102
|
+
confirmLabel: isNew ? 'Yes, start a new session' : 'Yes, clear the session',
|
|
103
|
+
danger: true,
|
|
104
|
+
detail: 'This ends the current conversation and clears the transcript.',
|
|
105
|
+
onConfirm: commit,
|
|
106
|
+
title: isNew ? 'Start a new session?' : 'Clear the current session?'
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
help: 'force a full UI repaint',
|
|
113
|
+
name: 'redraw',
|
|
114
|
+
run: (_arg, ctx) => {
|
|
115
|
+
forceRedraw(process.stdout);
|
|
116
|
+
ctx.transcript.sys('ui redrawn');
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
help: 'show live session info',
|
|
121
|
+
name: 'status',
|
|
122
|
+
run: (_arg, ctx) => {
|
|
123
|
+
if (!ctx.sid) {
|
|
124
|
+
return ctx.transcript.sys('no active session');
|
|
125
|
+
}
|
|
126
|
+
ctx.gateway
|
|
127
|
+
.rpc('session.status', { session_id: ctx.sid })
|
|
128
|
+
.then(ctx.guarded(r => ctx.transcript.page(r.output || '(no status)', 'Status')))
|
|
129
|
+
.catch(ctx.guardedErr);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
help: 'resume a prior session',
|
|
134
|
+
name: 'resume',
|
|
135
|
+
run: (arg, ctx) => {
|
|
136
|
+
if (ctx.session.guardBusySessionSwitch('switch sessions')) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
arg ? ctx.session.resumeById(arg) : patchOverlayState({ picker: true });
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
help: 'set or show current session title',
|
|
144
|
+
name: 'title',
|
|
145
|
+
run: (arg, ctx) => {
|
|
146
|
+
if (!ctx.sid) {
|
|
147
|
+
return ctx.transcript.sys('no active session');
|
|
148
|
+
}
|
|
149
|
+
const title = arg.trim();
|
|
150
|
+
if (!arg) {
|
|
151
|
+
ctx.gateway
|
|
152
|
+
.rpc('session.title', { session_id: ctx.sid })
|
|
153
|
+
.then(ctx.guarded(r => {
|
|
154
|
+
const current = (r?.title ?? '').trim();
|
|
155
|
+
ctx.transcript.sys(current ? `title: ${current}` : 'no title set');
|
|
156
|
+
}))
|
|
157
|
+
.catch(ctx.guardedErr);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (!title) {
|
|
161
|
+
return ctx.transcript.sys('usage: /title <your session title>');
|
|
162
|
+
}
|
|
163
|
+
ctx.gateway
|
|
164
|
+
.rpc('session.title', { session_id: ctx.sid, title })
|
|
165
|
+
.then(ctx.guarded(r => {
|
|
166
|
+
const next = (r?.title ?? title).trim();
|
|
167
|
+
const suffix = r?.pending ? ' (queued while session initializes)' : '';
|
|
168
|
+
ctx.transcript.sys(`session title set: ${next}${suffix}`);
|
|
169
|
+
}))
|
|
170
|
+
.catch(ctx.guardedErr);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
help: 'toggle compact transcript',
|
|
175
|
+
name: 'compact',
|
|
176
|
+
run: (arg, ctx) => {
|
|
177
|
+
const next = flagFromArg(arg, ctx.ui.compact);
|
|
178
|
+
if (next === null) {
|
|
179
|
+
return ctx.transcript.sys('usage: /compact [on|off|toggle]');
|
|
180
|
+
}
|
|
181
|
+
patchUiState({ compact: next });
|
|
182
|
+
ctx.gateway.rpc('config.set', { key: 'compact', value: next ? 'on' : 'off' }).catch(() => { });
|
|
183
|
+
queueMicrotask(() => ctx.transcript.sys(`compact ${next ? 'on' : 'off'}`));
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
aliases: ['detail'],
|
|
188
|
+
help: 'control agent detail visibility (global or per-section)',
|
|
189
|
+
name: 'details',
|
|
190
|
+
run: (arg, ctx) => {
|
|
191
|
+
const { gateway, transcript, ui } = ctx;
|
|
192
|
+
if (!arg) {
|
|
193
|
+
gateway
|
|
194
|
+
.rpc('config.get', { key: 'details_mode' })
|
|
195
|
+
.then(r => {
|
|
196
|
+
if (ctx.stale()) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const mode = parseDetailsMode(r?.value) ?? ui.detailsMode;
|
|
200
|
+
patchUiState({ detailsMode: mode, detailsModeCommandOverride: false });
|
|
201
|
+
const overrides = SECTION_NAMES.filter(s => ui.sections[s])
|
|
202
|
+
.map(s => `${s}=${ui.sections[s]}`)
|
|
203
|
+
.join(' ');
|
|
204
|
+
transcript.sys(`details: ${mode}${overrides ? ` (${overrides})` : ''}`);
|
|
205
|
+
})
|
|
206
|
+
.catch(() => !ctx.stale() && transcript.sys(`details: ${ui.detailsMode}`));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const [first, second] = arg.trim().toLowerCase().split(/\s+/);
|
|
210
|
+
if (second && isSectionName(first)) {
|
|
211
|
+
const reset = RESET_WORDS.has(second);
|
|
212
|
+
const mode = reset ? null : parseDetailsMode(second);
|
|
213
|
+
if (!reset && !mode) {
|
|
214
|
+
return transcript.sys(DETAILS_SECTION_USAGE);
|
|
215
|
+
}
|
|
216
|
+
const { [first]: _drop, ...rest } = ui.sections;
|
|
217
|
+
patchUiState({ sections: mode ? { ...rest, [first]: mode } : rest });
|
|
218
|
+
gateway
|
|
219
|
+
.rpc('config.set', { key: `details_mode.${first}`, value: mode ?? '' })
|
|
220
|
+
.catch(() => { });
|
|
221
|
+
transcript.sys(`details ${first}: ${mode ?? 'reset'}`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const next = CYCLE_WORDS.has(first ?? '') ? nextDetailsMode(ui.detailsMode) : parseDetailsMode(first);
|
|
225
|
+
if (!next) {
|
|
226
|
+
return transcript.sys(DETAILS_USAGE);
|
|
227
|
+
}
|
|
228
|
+
const sections = Object.fromEntries(SECTION_NAMES.map(section => [section, next]));
|
|
229
|
+
patchUiState({ detailsMode: next, detailsModeCommandOverride: true, sections });
|
|
230
|
+
gateway.rpc('config.set', { key: 'details_mode', value: next }).catch(() => { });
|
|
231
|
+
transcript.sys(`details: ${next}`);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
help: 'local fortune',
|
|
236
|
+
name: 'fortune',
|
|
237
|
+
run: (arg, ctx) => {
|
|
238
|
+
const key = arg.trim().toLowerCase();
|
|
239
|
+
if (!arg || key === 'random') {
|
|
240
|
+
return ctx.transcript.sys(randomFortune());
|
|
241
|
+
}
|
|
242
|
+
if (['daily', 'stable', 'today'].includes(key)) {
|
|
243
|
+
return ctx.transcript.sys(dailyFortune(ctx.sid));
|
|
244
|
+
}
|
|
245
|
+
ctx.transcript.sys('usage: /fortune [random|daily]');
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
help: 'copy selection or assistant message',
|
|
250
|
+
name: 'copy',
|
|
251
|
+
run: async (arg, ctx) => {
|
|
252
|
+
const { sys } = ctx.transcript;
|
|
253
|
+
if (!arg && ctx.composer.hasSelection) {
|
|
254
|
+
const text = await ctx.composer.selection.copySelection();
|
|
255
|
+
if (text) {
|
|
256
|
+
return sys(`copied ${text.length} characters`);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
return sys('clipboard copy failed — try CVC_TUI_FORCE_OSC52=1 to force the escape sequence; CVC_TUI_DEBUG_CLIPBOARD=1 for details');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (arg && Number.isNaN(parseInt(arg, 10))) {
|
|
263
|
+
return sys('usage: /copy [number]');
|
|
264
|
+
}
|
|
265
|
+
const all = ctx.local.getHistoryItems().filter(m => m.role === 'assistant');
|
|
266
|
+
const target = all[arg ? Math.min(parseInt(arg, 10), all.length) - 1 : all.length - 1];
|
|
267
|
+
if (!target) {
|
|
268
|
+
return sys('nothing to copy — start a conversation first');
|
|
269
|
+
}
|
|
270
|
+
void writeClipboardText(target.text)
|
|
271
|
+
.then(nativeOk => {
|
|
272
|
+
if (ctx.stale()) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (nativeOk) {
|
|
276
|
+
sys('copied to clipboard');
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
writeOsc52Clipboard(target.text);
|
|
280
|
+
sys('sent OSC52 copy sequence (terminal support required)');
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
.catch(error => {
|
|
284
|
+
if (!ctx.stale()) {
|
|
285
|
+
sys(`copy failed: ${String(error)}`);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
help: 'attach clipboard image',
|
|
292
|
+
name: 'paste',
|
|
293
|
+
run: (arg, ctx) => (arg ? ctx.transcript.sys('usage: /paste') : ctx.composer.paste())
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
help: 'configure IDE terminal keybindings for multiline + undo/redo',
|
|
297
|
+
name: 'terminal-setup',
|
|
298
|
+
run: (arg, ctx) => {
|
|
299
|
+
const target = arg.trim().toLowerCase();
|
|
300
|
+
if (target && !['auto', 'cursor', 'vscode', 'windsurf'].includes(target)) {
|
|
301
|
+
return ctx.transcript.sys('usage: /terminal-setup [auto|vscode|cursor|windsurf]');
|
|
302
|
+
}
|
|
303
|
+
const runner = !target || target === 'auto'
|
|
304
|
+
? configureDetectedTerminalKeybindings()
|
|
305
|
+
: configureTerminalKeybindings(target);
|
|
306
|
+
void runner
|
|
307
|
+
.then(result => {
|
|
308
|
+
if (ctx.stale()) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
ctx.transcript.sys(result.message);
|
|
312
|
+
if (result.success && result.requiresRestart) {
|
|
313
|
+
ctx.transcript.sys('restart the IDE terminal for the new keybindings to take effect');
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
.catch(error => {
|
|
317
|
+
if (!ctx.stale()) {
|
|
318
|
+
ctx.transcript.sys(`terminal setup failed: ${String(error)}`);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
help: 'view gateway logs',
|
|
325
|
+
name: 'logs',
|
|
326
|
+
run: (arg, ctx) => {
|
|
327
|
+
const text = ctx.gateway.gw.getLogTail(Math.min(80, Math.max(1, parseInt(arg, 10) || 20)));
|
|
328
|
+
text ? ctx.transcript.page(text, 'Logs') : ctx.transcript.sys('no gateway logs');
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
help: 'view current transcript (user + assistant messages)',
|
|
333
|
+
name: 'history',
|
|
334
|
+
run: (arg, ctx) => {
|
|
335
|
+
// The CLI-side `/history` runs in a detached slash-worker subprocess
|
|
336
|
+
// that never sees the TUI's turns — it only surfaces whatever was
|
|
337
|
+
// persisted before this process started. Render the TUI's own
|
|
338
|
+
// transcript so `/history` actually reflects what the user just did.
|
|
339
|
+
const items = ctx.local.getHistoryItems().filter(m => m.role === 'user' || m.role === 'assistant');
|
|
340
|
+
if (!items.length) {
|
|
341
|
+
return ctx.transcript.sys('no conversation yet');
|
|
342
|
+
}
|
|
343
|
+
const preview = Math.max(80, parseInt(arg, 10) || 400);
|
|
344
|
+
const lines = items.map((m, i) => {
|
|
345
|
+
const tag = m.role === 'user' ? `You #${i + 1}` : `Hermes #${i + 1}`;
|
|
346
|
+
const body = m.text.trim() || (m.tools?.length ? `(${m.tools.length} tool calls)` : '(empty)');
|
|
347
|
+
const clipped = body.length > preview ? `${body.slice(0, preview).trimEnd()}…` : body;
|
|
348
|
+
return `[${tag}]\n${clipped}`;
|
|
349
|
+
});
|
|
350
|
+
ctx.transcript.page(lines.join('\n\n'), 'History');
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
help: 'save the current transcript to JSON',
|
|
355
|
+
name: 'save',
|
|
356
|
+
run: (_arg, ctx) => {
|
|
357
|
+
const hasConversation = ctx.local
|
|
358
|
+
.getHistoryItems()
|
|
359
|
+
.some(m => m.role === 'user' || m.role === 'assistant' || m.role === 'tool');
|
|
360
|
+
if (!hasConversation) {
|
|
361
|
+
return ctx.transcript.sys('no conversation yet');
|
|
362
|
+
}
|
|
363
|
+
if (!ctx.sid) {
|
|
364
|
+
return ctx.transcript.sys('no active session — nothing to save');
|
|
365
|
+
}
|
|
366
|
+
ctx.gateway
|
|
367
|
+
.rpc('session.save', { session_id: ctx.sid })
|
|
368
|
+
.then(ctx.guarded(r => {
|
|
369
|
+
const file = r?.file;
|
|
370
|
+
if (file) {
|
|
371
|
+
ctx.transcript.sys(`conversation saved to: ${file}`);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
ctx.transcript.sys('failed to save');
|
|
375
|
+
}
|
|
376
|
+
}))
|
|
377
|
+
.catch(ctx.guardedErr);
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
aliases: ['sb'],
|
|
382
|
+
help: 'status bar position (on|off|top|bottom)',
|
|
383
|
+
name: 'statusbar',
|
|
384
|
+
run: (arg, ctx) => {
|
|
385
|
+
const mode = arg.trim().toLowerCase();
|
|
386
|
+
const toggle = ctx.ui.statusBar === 'off' ? 'top' : 'off';
|
|
387
|
+
const next = !mode || mode === 'toggle'
|
|
388
|
+
? toggle
|
|
389
|
+
: mode === 'on' || mode === 'top'
|
|
390
|
+
? 'top'
|
|
391
|
+
: mode === 'off' || mode === 'bottom'
|
|
392
|
+
? mode
|
|
393
|
+
: null;
|
|
394
|
+
if (!next) {
|
|
395
|
+
return ctx.transcript.sys('usage: /statusbar [on|off|top|bottom|toggle]');
|
|
396
|
+
}
|
|
397
|
+
patchUiState({ statusBar: next });
|
|
398
|
+
ctx.gateway.rpc('config.set', { key: 'statusbar', value: next }).catch(() => { });
|
|
399
|
+
queueMicrotask(() => ctx.transcript.sys(`status bar ${next}`));
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
help: 'inspect or enqueue a message',
|
|
404
|
+
name: 'queue',
|
|
405
|
+
run: (arg, ctx) => {
|
|
406
|
+
if (!arg) {
|
|
407
|
+
return ctx.transcript.sys(`${ctx.composer.queueRef.current.length} queued message(s)`);
|
|
408
|
+
}
|
|
409
|
+
ctx.composer.enqueue(arg);
|
|
410
|
+
ctx.transcript.sys(`queued: "${arg.slice(0, 50)}${arg.length > 50 ? '…' : ''}"`);
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
help: 'inject a message after the next tool call (no interrupt)',
|
|
415
|
+
name: 'steer',
|
|
416
|
+
run: (arg, ctx) => {
|
|
417
|
+
const payload = arg?.trim() ?? '';
|
|
418
|
+
if (!payload) {
|
|
419
|
+
return ctx.transcript.sys('usage: /steer <prompt>');
|
|
420
|
+
}
|
|
421
|
+
// If the agent isn't running, fall back to the queue so the user's
|
|
422
|
+
// message isn't lost — identical semantics to the gateway handler.
|
|
423
|
+
if (!ctx.ui.busy || !ctx.sid) {
|
|
424
|
+
ctx.composer.enqueue(payload);
|
|
425
|
+
ctx.transcript.sys(`no active turn — queued for next: "${payload.slice(0, 50)}${payload.length > 50 ? '…' : ''}"`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
ctx.gateway
|
|
429
|
+
.rpc('session.steer', { session_id: ctx.sid, text: payload })
|
|
430
|
+
.then(ctx.guarded(r => {
|
|
431
|
+
if (r?.status === 'queued') {
|
|
432
|
+
ctx.transcript.sys(`steer queued — arrives after next tool call: "${payload.slice(0, 50)}${payload.length > 50 ? '…' : ''}"`);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
ctx.transcript.sys('steer rejected');
|
|
436
|
+
}
|
|
437
|
+
}))
|
|
438
|
+
.catch(ctx.guardedErr);
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
help: 'undo last exchange',
|
|
443
|
+
name: 'undo',
|
|
444
|
+
run: (_arg, ctx) => {
|
|
445
|
+
if (!ctx.sid) {
|
|
446
|
+
return ctx.transcript.sys('nothing to undo');
|
|
447
|
+
}
|
|
448
|
+
ctx.gateway.rpc('session.undo', { session_id: ctx.sid }).then(ctx.guarded(r => {
|
|
449
|
+
if ((r.removed ?? 0) > 0) {
|
|
450
|
+
ctx.transcript.setHistoryItems((prev) => ctx.transcript.trimLastExchange(prev));
|
|
451
|
+
ctx.transcript.sys(`undid ${r.removed} messages`);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
ctx.transcript.sys('nothing to undo');
|
|
455
|
+
}
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
help: 'retry last user message',
|
|
461
|
+
name: 'retry',
|
|
462
|
+
run: (_arg, ctx) => {
|
|
463
|
+
const last = ctx.local.getLastUserMsg();
|
|
464
|
+
if (!last) {
|
|
465
|
+
return ctx.transcript.sys('nothing to retry');
|
|
466
|
+
}
|
|
467
|
+
if (!ctx.sid) {
|
|
468
|
+
return ctx.transcript.send(last);
|
|
469
|
+
}
|
|
470
|
+
ctx.gateway.rpc('session.undo', { session_id: ctx.sid }).then(ctx.guarded(r => {
|
|
471
|
+
if ((r.removed ?? 0) <= 0) {
|
|
472
|
+
return ctx.transcript.sys('nothing to retry');
|
|
473
|
+
}
|
|
474
|
+
ctx.transcript.setHistoryItems((prev) => ctx.transcript.trimLastExchange(prev));
|
|
475
|
+
ctx.transcript.send(last);
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Ported from CVC Agent (https://github.com/NousResearch/cvc)
|
|
4
|
+
// Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
|
|
5
|
+
import { formatBytes, performHeapDump } from '../../../lib/memory.js';
|
|
6
|
+
export const debugCommands = [
|
|
7
|
+
{
|
|
8
|
+
help: 'write a V8 heap snapshot + memory diagnostics (see HERMES_HEAPDUMP_DIR)',
|
|
9
|
+
name: 'heapdump',
|
|
10
|
+
run: (_arg, ctx) => {
|
|
11
|
+
const { heapUsed, rss } = process.memoryUsage();
|
|
12
|
+
ctx.transcript.sys(`writing heap dump (heap ${formatBytes(heapUsed)} · rss ${formatBytes(rss)})…`);
|
|
13
|
+
void performHeapDump('manual').then(r => {
|
|
14
|
+
if (ctx.stale()) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (!r.success) {
|
|
18
|
+
return ctx.transcript.sys(`heapdump failed: ${r.error ?? 'unknown error'}`);
|
|
19
|
+
}
|
|
20
|
+
ctx.transcript.sys(`heapdump: ${r.heapPath}`);
|
|
21
|
+
ctx.transcript.sys(`diagnostics: ${r.diagPath}`);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
help: 'print live V8 heap + rss numbers',
|
|
27
|
+
name: 'mem',
|
|
28
|
+
run: (_arg, ctx) => {
|
|
29
|
+
const { arrayBuffers, external, heapTotal, heapUsed, rss } = process.memoryUsage();
|
|
30
|
+
ctx.transcript.panel('Memory', [
|
|
31
|
+
{
|
|
32
|
+
rows: [
|
|
33
|
+
['heap used', formatBytes(heapUsed)],
|
|
34
|
+
['heap total', formatBytes(heapTotal)],
|
|
35
|
+
['external', formatBytes(external)],
|
|
36
|
+
['array buffers', formatBytes(arrayBuffers)],
|
|
37
|
+
['rss', formatBytes(rss)],
|
|
38
|
+
['uptime', `${process.uptime().toFixed(0)}s`]
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
];
|