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
|
@@ -1,91 +1,431 @@
|
|
|
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 { attachedImageNotice, introMsg, toTranscriptMessages } from '../../../domain/messages.js';
|
|
6
|
+
import { TUI_SESSION_MODEL_FLAG } from '../../../domain/slash.js';
|
|
7
|
+
import { formatVoiceRecordKey, parseVoiceRecordKey } from '../../../lib/platform.js';
|
|
8
|
+
import { fmtK } from '../../../lib/text.js';
|
|
9
|
+
import { DEFAULT_INDICATOR_STYLE, INDICATOR_STYLES } from '../../interfaces.js';
|
|
10
|
+
import { patchOverlayState } from '../../overlayStore.js';
|
|
11
|
+
import { patchUiState } from '../../uiStore.js';
|
|
12
|
+
const TUI_SESSION_MODEL_RE = new RegExp(`(?:^|\\s)${TUI_SESSION_MODEL_FLAG}(?:\\s|$)`);
|
|
13
|
+
const TUI_SESSION_STRIP_RE = new RegExp(`\\s*${TUI_SESSION_MODEL_FLAG}\\b\\s*`, 'g');
|
|
14
|
+
const stripTuiSessionFlag = (trimmed) => trimmed.replace(TUI_SESSION_STRIP_RE, ' ').replace(/\s+/g, ' ').trim();
|
|
15
|
+
const modelValueForConfigSet = (arg) => {
|
|
16
|
+
const trimmed = arg.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
return trimmed;
|
|
19
|
+
}
|
|
20
|
+
if (TUI_SESSION_MODEL_RE.test(trimmed)) {
|
|
21
|
+
return stripTuiSessionFlag(trimmed);
|
|
22
|
+
}
|
|
23
|
+
return trimmed;
|
|
24
|
+
};
|
|
1
25
|
export const sessionCommands = [
|
|
2
26
|
{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
run: (
|
|
27
|
+
aliases: ['bg', 'btw'],
|
|
28
|
+
help: 'launch a background prompt',
|
|
29
|
+
name: 'background',
|
|
30
|
+
run: (arg, ctx) => {
|
|
31
|
+
if (!arg) {
|
|
32
|
+
return ctx.transcript.sys('/background <prompt>');
|
|
33
|
+
}
|
|
34
|
+
ctx.gateway.rpc('prompt.background', { session_id: ctx.sid, text: arg }).then(ctx.guarded(r => {
|
|
35
|
+
if (!r.task_id) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
patchUiState(state => ({ ...state, bgTasks: new Set(state.bgTasks).add(r.task_id) }));
|
|
39
|
+
ctx.transcript.sys(`bg ${r.task_id} started`);
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
7
42
|
},
|
|
8
43
|
{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
help: 'resume a prior session by id',
|
|
44
|
+
help: 'change or show model',
|
|
45
|
+
name: 'model',
|
|
12
46
|
run: (arg, ctx) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
47
|
+
if (ctx.session.guardBusySessionSwitch('change models')) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!arg.trim()) {
|
|
51
|
+
return patchOverlayState({ modelPicker: true });
|
|
52
|
+
}
|
|
53
|
+
ctx.gateway
|
|
54
|
+
.rpc('config.set', { key: 'model', session_id: ctx.sid, value: modelValueForConfigSet(arg) })
|
|
55
|
+
.then(ctx.guarded(r => {
|
|
56
|
+
if (!r.value) {
|
|
57
|
+
return ctx.transcript.sys('error: invalid response: model switch');
|
|
58
|
+
}
|
|
59
|
+
ctx.transcript.sys(`model → ${r.value}`);
|
|
60
|
+
ctx.local.maybeWarn(r);
|
|
61
|
+
patchUiState(state => ({
|
|
62
|
+
...state,
|
|
63
|
+
info: state.info ? { ...state.info, model: r.value } : { model: r.value, skills: {}, tools: {} }
|
|
64
|
+
}));
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
19
67
|
},
|
|
20
68
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
help: 'create a CVC checkpoint commit',
|
|
69
|
+
help: 'browse and resume previous sessions',
|
|
70
|
+
name: 'sessions',
|
|
24
71
|
run: (arg, ctx) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
72
|
+
if (ctx.session.guardBusySessionSwitch('switch sessions')) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!arg.trim()) {
|
|
76
|
+
return patchOverlayState({ picker: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
29
79
|
},
|
|
30
80
|
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
help: 'rollback to a checkpoint',
|
|
81
|
+
help: 'attach an image',
|
|
82
|
+
name: 'image',
|
|
34
83
|
run: (arg, ctx) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
84
|
+
ctx.gateway.rpc('image.attach', { path: arg, session_id: ctx.sid }).then(ctx.guarded(r => {
|
|
85
|
+
ctx.transcript.sys(attachedImageNotice(r));
|
|
86
|
+
if (r.remainder) {
|
|
87
|
+
ctx.composer.setInput(r.remainder);
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
41
91
|
},
|
|
42
92
|
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
93
|
+
help: 'switch personality for this session',
|
|
94
|
+
name: 'personality',
|
|
95
|
+
run: (arg, ctx) => {
|
|
96
|
+
if (!arg) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
ctx.gateway.rpc('config.set', { key: 'personality', session_id: ctx.sid, value: arg }).then(ctx.guarded(r => {
|
|
100
|
+
if (r.history_reset) {
|
|
101
|
+
ctx.session.resetVisibleHistory(r.info ?? null);
|
|
102
|
+
}
|
|
103
|
+
ctx.transcript.sys(`personality: ${r.value || 'default'}${r.history_reset ? ' · transcript cleared' : ''}`);
|
|
104
|
+
ctx.local.maybeWarn(r);
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
47
107
|
},
|
|
48
108
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
109
|
+
help: 'compress transcript',
|
|
110
|
+
name: 'compress',
|
|
111
|
+
run: (arg, ctx) => {
|
|
112
|
+
ctx.gateway
|
|
113
|
+
.rpc('session.compress', {
|
|
114
|
+
session_id: ctx.sid,
|
|
115
|
+
...(arg ? { focus_topic: arg } : {})
|
|
116
|
+
})
|
|
117
|
+
.then(ctx.guarded(r => {
|
|
118
|
+
if (Array.isArray(r.messages)) {
|
|
119
|
+
const rows = toTranscriptMessages(r.messages);
|
|
120
|
+
ctx.transcript.setHistoryItems(r.info ? [introMsg(r.info), ...rows] : rows);
|
|
121
|
+
}
|
|
122
|
+
if (r.info) {
|
|
123
|
+
patchUiState({ info: r.info });
|
|
124
|
+
}
|
|
125
|
+
if (r.usage) {
|
|
126
|
+
patchUiState(state => ({ ...state, usage: { ...state.usage, ...r.usage } }));
|
|
127
|
+
}
|
|
128
|
+
if (r.summary?.headline) {
|
|
129
|
+
const prefix = r.summary.noop ? '' : '✓ ';
|
|
130
|
+
ctx.transcript.sys(`${prefix}${r.summary.headline}`);
|
|
131
|
+
if (r.summary.token_line) {
|
|
132
|
+
ctx.transcript.sys(` ${r.summary.token_line}`);
|
|
133
|
+
}
|
|
134
|
+
if (r.summary.note) {
|
|
135
|
+
ctx.transcript.sys(` ${r.summary.note}`);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if ((r.removed ?? 0) <= 0) {
|
|
140
|
+
return ctx.transcript.sys('nothing to compress');
|
|
141
|
+
}
|
|
142
|
+
ctx.transcript.sys(`compressed ${r.removed} messages${r.usage?.total ? ` · ${fmtK(r.usage.total)} tok` : ''}`);
|
|
143
|
+
}))
|
|
144
|
+
.catch(ctx.guardedErr);
|
|
145
|
+
}
|
|
53
146
|
},
|
|
54
147
|
{
|
|
148
|
+
aliases: ['fork'],
|
|
149
|
+
help: 'branch the session',
|
|
55
150
|
name: 'branch',
|
|
56
|
-
category: 'session',
|
|
57
|
-
help: 'create a CVC cognitive branch',
|
|
58
151
|
run: (arg, ctx) => {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
152
|
+
const prevSid = ctx.sid;
|
|
153
|
+
ctx.gateway.rpc('session.branch', { name: arg, session_id: ctx.sid }).then(ctx.guarded(r => {
|
|
154
|
+
if (!r.session_id) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
void ctx.session.closeSession(prevSid);
|
|
158
|
+
patchUiState({ sid: r.session_id });
|
|
159
|
+
ctx.session.setSessionStartedAt(Date.now());
|
|
160
|
+
ctx.transcript.setHistoryItems([]);
|
|
161
|
+
ctx.transcript.sys(`branched → ${r.title ?? ''}`);
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
65
164
|
},
|
|
66
165
|
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
166
|
+
help: 'voice mode: [on|off|tts|status]',
|
|
167
|
+
name: 'voice',
|
|
168
|
+
run: (arg, ctx) => {
|
|
169
|
+
const normalized = (arg ?? '').trim().toLowerCase();
|
|
170
|
+
const action = normalized === 'on' || normalized === 'off' || normalized === 'tts' || normalized === 'status'
|
|
171
|
+
? normalized
|
|
172
|
+
: 'status';
|
|
173
|
+
ctx.gateway.rpc('voice.toggle', { action }).then(ctx.guarded(r => {
|
|
174
|
+
ctx.voice.setVoiceEnabled(!!r.enabled);
|
|
175
|
+
// Render the configured record key (config.yaml ``voice.record_key``)
|
|
176
|
+
// instead of hardcoded "Ctrl+B" — the gateway response carries the
|
|
177
|
+
// current value so /voice status and /voice on stay in sync with
|
|
178
|
+
// both the CLI and the TUI's actual binding (#18994).
|
|
179
|
+
//
|
|
180
|
+
// Copilot review on #19835 caught that rendering from the fresh
|
|
181
|
+
// backend response WITHOUT updating the frontend ``voice.recordKey``
|
|
182
|
+
// state would skew display and binding between config-edit and
|
|
183
|
+
// the next ``mtime`` poll (~5s). Parse once, push into state so
|
|
184
|
+
// ``useInputHandlers()`` picks up the new binding immediately.
|
|
185
|
+
//
|
|
186
|
+
// Round-2 follow-up: only push state when the response actually
|
|
187
|
+
// carries ``record_key`` — otherwise an older gateway (or a future
|
|
188
|
+
// branch that forgets to include it) would clobber a custom user
|
|
189
|
+
// binding back to the default on every /voice invocation. The
|
|
190
|
+
// label still falls back to the documented default for display.
|
|
191
|
+
const parsed = r.record_key ? parseVoiceRecordKey(r.record_key) : undefined;
|
|
192
|
+
if (parsed) {
|
|
193
|
+
ctx.voice.setVoiceRecordKey(parsed);
|
|
194
|
+
}
|
|
195
|
+
const recordKeyLabel = formatVoiceRecordKey(parsed ?? parseVoiceRecordKey('ctrl+b'));
|
|
196
|
+
// Match CLI's _show_voice_status / _enable_voice_mode /
|
|
197
|
+
// _toggle_voice_tts output shape so users don't have to learn
|
|
198
|
+
// two vocabularies.
|
|
199
|
+
if (action === 'status') {
|
|
200
|
+
const mode = r.enabled ? 'ON' : 'OFF';
|
|
201
|
+
const tts = r.tts ? 'ON' : 'OFF';
|
|
202
|
+
ctx.transcript.sys('Voice Mode Status');
|
|
203
|
+
ctx.transcript.sys(` Mode: ${mode}`);
|
|
204
|
+
ctx.transcript.sys(` TTS: ${tts}`);
|
|
205
|
+
ctx.transcript.sys(` Record key: ${recordKeyLabel}`);
|
|
206
|
+
// CLI's "Requirements:" block — surfaces STT/audio setup issues
|
|
207
|
+
// so the user sees "STT provider: MISSING ..." instead of
|
|
208
|
+
// silently failing on every record-key press.
|
|
209
|
+
if (r.details) {
|
|
210
|
+
ctx.transcript.sys('');
|
|
211
|
+
ctx.transcript.sys(' Requirements:');
|
|
212
|
+
for (const line of r.details.split('\n')) {
|
|
213
|
+
if (line.trim()) {
|
|
214
|
+
ctx.transcript.sys(` ${line}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (action === 'tts') {
|
|
221
|
+
ctx.transcript.sys(`Voice TTS ${r.tts ? 'enabled' : 'disabled'}.`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// on/off — mirror cli.py:_enable_voice_mode's 3-line output
|
|
225
|
+
if (r.enabled) {
|
|
226
|
+
const tts = r.tts ? ' (TTS enabled)' : '';
|
|
227
|
+
ctx.transcript.sys(`Voice mode enabled${tts}`);
|
|
228
|
+
ctx.transcript.sys(` ${recordKeyLabel} to start/stop recording`);
|
|
229
|
+
ctx.transcript.sys(' /voice tts to toggle speech output');
|
|
230
|
+
ctx.transcript.sys(' /voice off to disable voice mode');
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
ctx.transcript.sys('Voice mode disabled.');
|
|
234
|
+
}
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
74
237
|
},
|
|
75
238
|
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
help: 'steer the in-flight turn',
|
|
239
|
+
help: 'switch theme skin (fires skin.changed)',
|
|
240
|
+
name: 'skin',
|
|
79
241
|
run: (arg, ctx) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
242
|
+
if (!arg) {
|
|
243
|
+
return ctx.gateway
|
|
244
|
+
.rpc('config.get', { key: 'skin' })
|
|
245
|
+
.then(ctx.guarded(r => ctx.transcript.sys(`skin: ${r.value || 'default'}`)));
|
|
246
|
+
}
|
|
247
|
+
ctx.gateway
|
|
248
|
+
.rpc('config.set', { key: 'skin', value: arg })
|
|
249
|
+
.then(ctx.guarded(r => r.value && ctx.transcript.sys(`skin → ${r.value}`)));
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
help: 'pick the busy indicator: kaomoji (default), emoji, unicode (braille), or ascii',
|
|
254
|
+
name: 'indicator',
|
|
255
|
+
usage: `/indicator [${INDICATOR_STYLES.join('|')}]`,
|
|
256
|
+
run: (arg, ctx) => {
|
|
257
|
+
const value = arg.trim().toLowerCase();
|
|
258
|
+
if (!value) {
|
|
259
|
+
return ctx.gateway
|
|
260
|
+
.rpc('config.get', { key: 'indicator' })
|
|
261
|
+
.then(ctx.guarded(r => ctx.transcript.sys(`indicator: ${r.value || DEFAULT_INDICATOR_STYLE}`)));
|
|
262
|
+
}
|
|
263
|
+
if (!INDICATOR_STYLES.includes(value)) {
|
|
264
|
+
return ctx.transcript.sys(`usage: /indicator [${INDICATOR_STYLES.join('|')}]`);
|
|
265
|
+
}
|
|
266
|
+
ctx.gateway.rpc('config.set', { key: 'indicator', value }).then(ctx.guarded(r => {
|
|
267
|
+
if (!r.value) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// Hot-swap the running TUI immediately so the next render
|
|
271
|
+
// uses the new style without waiting for the 5s mtime poll
|
|
272
|
+
// to re-apply config.full.
|
|
273
|
+
patchUiState({ indicatorStyle: value });
|
|
274
|
+
ctx.transcript.sys(`indicator → ${r.value}`);
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
help: 'toggle yolo mode (per-session approvals)',
|
|
280
|
+
name: 'yolo',
|
|
281
|
+
run: (_arg, ctx) => {
|
|
282
|
+
ctx.gateway
|
|
283
|
+
.rpc('config.set', { key: 'yolo', session_id: ctx.sid })
|
|
284
|
+
.then(ctx.guarded(r => ctx.transcript.sys(`yolo ${r.value === '1' ? 'on' : 'off'}`)));
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
help: 'inspect or set reasoning effort (updates live agent)',
|
|
289
|
+
name: 'reasoning',
|
|
290
|
+
run: (arg, ctx) => {
|
|
291
|
+
if (!arg) {
|
|
292
|
+
return ctx.gateway
|
|
293
|
+
.rpc('config.get', { key: 'reasoning' })
|
|
294
|
+
.then(ctx.guarded(r => r.value && ctx.transcript.sys(`reasoning: ${r.value} · display ${r.display || 'hide'}`)));
|
|
295
|
+
}
|
|
296
|
+
ctx.gateway
|
|
297
|
+
.rpc('config.set', { key: 'reasoning', session_id: ctx.sid, value: arg })
|
|
298
|
+
.then(ctx.guarded(r => {
|
|
299
|
+
if (!r.value) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (r.value === 'hide') {
|
|
303
|
+
patchUiState(state => ({
|
|
304
|
+
...state,
|
|
305
|
+
sections: { ...state.sections, thinking: 'hidden' },
|
|
306
|
+
showReasoning: false
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
else if (r.value === 'show') {
|
|
310
|
+
patchUiState(state => ({
|
|
311
|
+
...state,
|
|
312
|
+
sections: { ...state.sections, thinking: 'expanded' },
|
|
313
|
+
showReasoning: true
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
ctx.transcript.sys(`reasoning: ${r.value}`);
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
90
319
|
},
|
|
320
|
+
{
|
|
321
|
+
help: 'toggle fast mode [normal|fast|status|on|off|toggle]',
|
|
322
|
+
name: 'fast',
|
|
323
|
+
run: (arg, ctx) => {
|
|
324
|
+
const mode = arg.trim().toLowerCase();
|
|
325
|
+
const valid = new Set(['', 'status', 'normal', 'fast', 'on', 'off', 'toggle']);
|
|
326
|
+
if (!valid.has(mode)) {
|
|
327
|
+
return ctx.transcript.sys('usage: /fast [normal|fast|status|on|off|toggle]');
|
|
328
|
+
}
|
|
329
|
+
if (!mode || mode === 'status') {
|
|
330
|
+
return ctx.gateway
|
|
331
|
+
.rpc('config.get', { key: 'fast', session_id: ctx.sid })
|
|
332
|
+
.then(ctx.guarded(r => ctx.transcript.sys(`fast mode: ${r.value === 'fast' ? 'fast' : 'normal'}`)))
|
|
333
|
+
.catch(ctx.guardedErr);
|
|
334
|
+
}
|
|
335
|
+
ctx.gateway
|
|
336
|
+
.rpc('config.set', { key: 'fast', session_id: ctx.sid, value: mode })
|
|
337
|
+
.then(ctx.guarded(r => {
|
|
338
|
+
const next = r.value === 'fast' ? 'fast' : 'normal';
|
|
339
|
+
ctx.transcript.sys(`fast mode: ${next}`);
|
|
340
|
+
patchUiState(state => ({
|
|
341
|
+
...state,
|
|
342
|
+
info: state.info
|
|
343
|
+
? {
|
|
344
|
+
...state.info,
|
|
345
|
+
fast: next === 'fast',
|
|
346
|
+
service_tier: next === 'fast' ? 'priority' : ''
|
|
347
|
+
}
|
|
348
|
+
: state.info
|
|
349
|
+
}));
|
|
350
|
+
}))
|
|
351
|
+
.catch(ctx.guardedErr);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
help: 'control busy enter mode [queue|steer|interrupt|status]',
|
|
356
|
+
name: 'busy',
|
|
357
|
+
run: (arg, ctx) => {
|
|
358
|
+
const mode = arg.trim().toLowerCase();
|
|
359
|
+
const valid = new Set(['', 'status', 'queue', 'steer', 'interrupt']);
|
|
360
|
+
if (!valid.has(mode)) {
|
|
361
|
+
return ctx.transcript.sys('usage: /busy [queue|steer|interrupt|status]');
|
|
362
|
+
}
|
|
363
|
+
if (!mode || mode === 'status') {
|
|
364
|
+
return ctx.gateway
|
|
365
|
+
.rpc('config.get', { key: 'busy' })
|
|
366
|
+
.then(ctx.guarded(r => {
|
|
367
|
+
const current = r.value || 'interrupt';
|
|
368
|
+
ctx.transcript.sys(`busy input mode: ${current}`);
|
|
369
|
+
}))
|
|
370
|
+
.catch(ctx.guardedErr);
|
|
371
|
+
}
|
|
372
|
+
ctx.gateway
|
|
373
|
+
.rpc('config.set', { key: 'busy', value: mode })
|
|
374
|
+
.then(ctx.guarded(r => {
|
|
375
|
+
const next = r.value || mode;
|
|
376
|
+
ctx.transcript.sys(`busy input mode: ${next}`);
|
|
377
|
+
}))
|
|
378
|
+
.catch(ctx.guardedErr);
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
help: 'cycle verbose tool-output mode (updates live agent)',
|
|
383
|
+
name: 'verbose',
|
|
384
|
+
run: (arg, ctx) => {
|
|
385
|
+
ctx.gateway
|
|
386
|
+
.rpc('config.set', { key: 'verbose', session_id: ctx.sid, value: arg || 'cycle' })
|
|
387
|
+
.then(ctx.guarded(r => r.value && ctx.transcript.sys(`verbose: ${r.value}`)));
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
help: 'session usage (live counts — worker sees zeros)',
|
|
392
|
+
name: 'usage',
|
|
393
|
+
run: (_arg, ctx) => {
|
|
394
|
+
ctx.gateway.rpc('session.usage', { session_id: ctx.sid }).then(r => {
|
|
395
|
+
if (ctx.stale()) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (r) {
|
|
399
|
+
patchUiState({
|
|
400
|
+
usage: { calls: r.calls ?? 0, input: r.input ?? 0, output: r.output ?? 0, total: r.total ?? 0 }
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (!r?.calls) {
|
|
404
|
+
return ctx.transcript.sys('no API calls yet');
|
|
405
|
+
}
|
|
406
|
+
const f = (v) => (v ?? 0).toLocaleString();
|
|
407
|
+
const cost = r.cost_usd != null ? `${r.cost_status === 'estimated' ? '~' : ''}$${r.cost_usd.toFixed(4)}` : null;
|
|
408
|
+
const rows = [
|
|
409
|
+
['Model', r.model ?? ''],
|
|
410
|
+
['Input tokens', f(r.input)],
|
|
411
|
+
['Cache read tokens', f(r.cache_read)],
|
|
412
|
+
['Cache write tokens', f(r.cache_write)],
|
|
413
|
+
['Output tokens', f(r.output)],
|
|
414
|
+
['Total tokens', f(r.total)],
|
|
415
|
+
['API calls', f(r.calls)]
|
|
416
|
+
];
|
|
417
|
+
if (cost) {
|
|
418
|
+
rows.push(['Cost', cost]);
|
|
419
|
+
}
|
|
420
|
+
const sections = [{ rows }];
|
|
421
|
+
if (r.context_max) {
|
|
422
|
+
sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${r.context_percent}%)` });
|
|
423
|
+
}
|
|
424
|
+
if (r.compressions) {
|
|
425
|
+
sections.push({ text: `Compressions: ${r.compressions}` });
|
|
426
|
+
}
|
|
427
|
+
ctx.transcript.panel('Usage', sections);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
91
431
|
];
|
|
@@ -1,47 +1,20 @@
|
|
|
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 { withInkSuspended } from '@cvc/ink';
|
|
6
|
+
import { launchHermesCommand } from '../../../lib/externalCli.js';
|
|
7
|
+
import { runExternalSetup } from '../../setupHandoff.js';
|
|
1
8
|
export const setupCommands = [
|
|
2
9
|
{
|
|
10
|
+
help: 'run full setup wizard (launches `hermes setup`)',
|
|
3
11
|
name: 'setup',
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
name: 'terminal-setup',
|
|
13
|
-
category: 'setup',
|
|
14
|
-
help: 'configure IDE terminal keybindings (auto|cursor|vscode|windsurf)',
|
|
15
|
-
run: (arg, ctx) => {
|
|
16
|
-
const target = arg.trim().toLowerCase() || 'auto';
|
|
17
|
-
const ok = ['auto', 'cursor', 'vscode', 'windsurf'];
|
|
18
|
-
if (!ok.includes(target)) {
|
|
19
|
-
return ctx.emit({ kind: 'sys', text: 'usage: /terminal-setup [auto|cursor|vscode|windsurf]' });
|
|
20
|
-
}
|
|
21
|
-
ctx.emit({ kind: 'rpc', method: 'cli.terminalSetup', params: { target } });
|
|
22
|
-
ctx.emit({ kind: 'sys', text: `terminal-setup (${target})…` });
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
name: 'replay',
|
|
27
|
-
category: 'setup',
|
|
28
|
-
help: 'replay a prior session by id',
|
|
29
|
-
run: (arg, ctx) => {
|
|
30
|
-
const id = arg.trim();
|
|
31
|
-
if (!id)
|
|
32
|
-
return ctx.emit({ kind: 'sys', text: 'usage: /replay <session-id>' });
|
|
33
|
-
ctx.emit({ kind: 'rpc', method: 'session.replay', params: { session_id: id } });
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
name: 'replay-diff',
|
|
38
|
-
category: 'setup',
|
|
39
|
-
help: 'diff two replays /replay-diff <a> <b>',
|
|
40
|
-
run: (arg, ctx) => {
|
|
41
|
-
const [a, b] = arg.trim().split(/\s+/);
|
|
42
|
-
if (!a || !b)
|
|
43
|
-
return ctx.emit({ kind: 'sys', text: 'usage: /replay-diff <a> <b>' });
|
|
44
|
-
ctx.emit({ kind: 'rpc', method: 'session.replayDiff', params: { a, b } });
|
|
45
|
-
},
|
|
46
|
-
},
|
|
12
|
+
run: (arg, ctx) => void runExternalSetup({
|
|
13
|
+
args: ['setup', ...arg.split(/\s+/).filter(Boolean)],
|
|
14
|
+
ctx,
|
|
15
|
+
done: 'setup complete — starting session…',
|
|
16
|
+
launcher: launchHermesCommand,
|
|
17
|
+
suspend: withInkSuspended
|
|
18
|
+
})
|
|
19
|
+
}
|
|
47
20
|
];
|
|
@@ -1,3 +1,7 @@
|
|
|
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.
|
|
1
5
|
// Trivial toggle-style commands — boolean UI flags routed through patchUi
|
|
2
6
|
// + a config.set RPC so the gateway can persist them.
|
|
3
7
|
import { flagFromArg } from '../types.js';
|
|
@@ -1,79 +1,18 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
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.
|
|
3
5
|
import { coreCommands } from './commands/core.js';
|
|
4
6
|
import { debugCommands } from './commands/debug.js';
|
|
5
7
|
import { opsCommands } from './commands/ops.js';
|
|
6
8
|
import { sessionCommands } from './commands/session.js';
|
|
7
9
|
import { setupCommands } from './commands/setup.js';
|
|
8
|
-
import { toggleCommands } from './commands/toggles.js';
|
|
9
10
|
export const SLASH_COMMANDS = [
|
|
10
11
|
...coreCommands,
|
|
11
12
|
...sessionCommands,
|
|
12
13
|
...opsCommands,
|
|
13
14
|
...setupCommands,
|
|
14
|
-
...
|
|
15
|
-
...debugCommands,
|
|
15
|
+
...debugCommands
|
|
16
16
|
];
|
|
17
|
-
const byName = new Map(SLASH_COMMANDS.flatMap(cmd => [cmd.name, ...(cmd.aliases ?? [])].map(
|
|
18
|
-
export
|
|
19
|
-
return byName.get(name.replace(/^\//, '').toLowerCase());
|
|
20
|
-
}
|
|
21
|
-
export function listSlashNames() {
|
|
22
|
-
return SLASH_COMMANDS.map(c => `/${c.name}`).sort();
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Returns "/"-prefixed canonical names + aliases. Used by completion.ts to
|
|
26
|
-
* populate slash candidates. Keeps both names ("listSlashNames" and
|
|
27
|
-
* "listCommandNames") exported so older slice modules don't drift.
|
|
28
|
-
*/
|
|
29
|
-
export function listCommandNames() {
|
|
30
|
-
const all = new Set();
|
|
31
|
-
for (const c of SLASH_COMMANDS) {
|
|
32
|
-
all.add(`/${c.name}`);
|
|
33
|
-
for (const a of c.aliases ?? [])
|
|
34
|
-
all.add(`/${a}`);
|
|
35
|
-
}
|
|
36
|
-
return Array.from(all).sort();
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Parse a raw input line and dispatch it. Returns true if it WAS a slash
|
|
40
|
-
* command (handled or not), false if the line should be treated as a normal
|
|
41
|
-
* user message.
|
|
42
|
-
*
|
|
43
|
-
* Strip a single matched layer of surrounding quotes from `arg`. This is the
|
|
44
|
-
* same affordance Hermes ui-tui applies to inputs like `/title 'foo bar'` or
|
|
45
|
-
* `/title "foo bar"` — users coming from a shell expect quoting to "just
|
|
46
|
-
* work" for arguments containing spaces.
|
|
47
|
-
*/
|
|
48
|
-
export function unquoteArg(arg) {
|
|
49
|
-
const t = arg.trim();
|
|
50
|
-
if (t.length < 2)
|
|
51
|
-
return t;
|
|
52
|
-
const first = t.charCodeAt(0);
|
|
53
|
-
const last = t.charCodeAt(t.length - 1);
|
|
54
|
-
if (first === last && (first === 34 /* " */ || first === 39 /* ' */)) {
|
|
55
|
-
return t.slice(1, -1);
|
|
56
|
-
}
|
|
57
|
-
return t;
|
|
58
|
-
}
|
|
59
|
-
export async function dispatchSlash(line, ctx) {
|
|
60
|
-
if (!line.startsWith('/'))
|
|
61
|
-
return false;
|
|
62
|
-
const trimmed = line.trim();
|
|
63
|
-
const sp = trimmed.indexOf(' ');
|
|
64
|
-
const head = sp === -1 ? trimmed.slice(1) : trimmed.slice(1, sp);
|
|
65
|
-
const rawArg = sp === -1 ? '' : trimmed.slice(sp + 1);
|
|
66
|
-
const arg = unquoteArg(rawArg);
|
|
67
|
-
const cmd = findSlashCommand(head);
|
|
68
|
-
if (!cmd) {
|
|
69
|
-
ctx.emit({ kind: 'sys', text: `unknown command: /${head} — try /help` });
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
await cmd.run(arg, ctx, trimmed);
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
ctx.emit({ kind: 'sys', text: `command failed: ${String(err)}` });
|
|
77
|
-
}
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
17
|
+
const byName = new Map(SLASH_COMMANDS.flatMap(cmd => [cmd.name, ...(cmd.aliases ?? [])].map(name => [name, cmd])));
|
|
18
|
+
export const findSlashCommand = (name) => byName.get(name.toLowerCase());
|