cvc-tui 0.1.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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/app/completion.js +98 -0
- package/dist/app/historyStore.js +119 -0
- package/dist/app/inputBuffer.js +116 -0
- package/dist/app/inputStore.js +24 -0
- package/dist/app/promptStore.js +40 -0
- package/dist/app/queueStore.js +21 -0
- package/dist/app/slash/commands/core.js +292 -0
- package/dist/app/slash/commands/debug.js +11 -0
- package/dist/app/slash/commands/ops.js +163 -0
- package/dist/app/slash/commands/session.js +91 -0
- package/dist/app/slash/commands/setup.js +47 -0
- package/dist/app/slash/commands/toggles.js +36 -0
- package/dist/app/slash/registry.js +79 -0
- package/dist/app/slash/types.js +16 -0
- package/dist/app/turnStore.js +60 -0
- package/dist/app/uiStore.js +31 -0
- package/dist/app.js +219 -0
- package/dist/banner.js +20 -0
- package/dist/components/appLayout.js +22 -0
- package/dist/components/branding.js +6 -0
- package/dist/components/overlays/confirmPrompt.js +25 -0
- package/dist/components/overlays/helpOverlay.js +75 -0
- package/dist/components/overlays/historySearch.js +48 -0
- package/dist/components/overlays/modelPicker.js +59 -0
- package/dist/components/overlays/overlayUtils.js +18 -0
- package/dist/components/overlays/secretPrompt.js +35 -0
- package/dist/components/overlays/sessionPicker.js +92 -0
- package/dist/components/overlays/skillsHub.js +70 -0
- package/dist/components/streamingMarkdown.js +220 -0
- package/dist/components/textInput.js +264 -0
- package/dist/components/thinking.js +39 -0
- package/dist/components/transcript.js +22 -0
- package/dist/config/timing.js +14 -0
- package/dist/entry.js +43 -0
- package/dist/gateway/client.js +312 -0
- package/dist/types.js +7 -0
- package/package.json +77 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// Core slash commands: /help /quit /clear /redraw /status /history /save
|
|
2
|
+
// /title /compact /fortune /copy /paste /logs /redraw /details
|
|
3
|
+
import { flagFromArg } from '../types.js';
|
|
4
|
+
const FORTUNES = [
|
|
5
|
+
'Every commit is a love letter to future-you.',
|
|
6
|
+
'The mind is a Merkle DAG; tend it well.',
|
|
7
|
+
'No conversation is ever truly lost — only un-recalled.',
|
|
8
|
+
'Branch boldly, merge mindfully.',
|
|
9
|
+
'Context is the only currency that compounds.',
|
|
10
|
+
'A clean transcript is a clear conscience.',
|
|
11
|
+
];
|
|
12
|
+
export const coreCommands = [
|
|
13
|
+
{
|
|
14
|
+
name: 'help',
|
|
15
|
+
aliases: ['?'],
|
|
16
|
+
category: 'core',
|
|
17
|
+
help: 'list slash commands + hotkeys',
|
|
18
|
+
run: (_arg, ctx) => {
|
|
19
|
+
// Open the full-screen helpOverlay; also emit the textual panel so
|
|
20
|
+
// non-TUI consumers (logs, scripts) still see a readable summary.
|
|
21
|
+
ctx.emit({ kind: 'overlay', overlay: 'help' });
|
|
22
|
+
ctx.emit({
|
|
23
|
+
kind: 'panel',
|
|
24
|
+
title: 'CVC — slash commands',
|
|
25
|
+
sections: [
|
|
26
|
+
{
|
|
27
|
+
title: 'Core',
|
|
28
|
+
rows: [
|
|
29
|
+
['/help', 'this help panel'],
|
|
30
|
+
['/quit, /exit', 'leave CVC'],
|
|
31
|
+
['/clear, /new', 'start a new session'],
|
|
32
|
+
['/status', 'show live session info'],
|
|
33
|
+
['/usage', 'show token + cost usage'],
|
|
34
|
+
['/history', 'view current transcript'],
|
|
35
|
+
['/save', 'save transcript to JSON'],
|
|
36
|
+
['/title <text>', 'set session title'],
|
|
37
|
+
['/redraw', 'force a full UI repaint'],
|
|
38
|
+
['/fortune [random|daily]', 'a CVC fortune'],
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: 'Sessions',
|
|
43
|
+
rows: [
|
|
44
|
+
['/sessions', 'browse prior sessions'],
|
|
45
|
+
['/resume [id]', 'resume a prior session'],
|
|
46
|
+
['/new [title]', 'new session (alias of /clear)'],
|
|
47
|
+
['/checkpoint', 'create a CVC checkpoint'],
|
|
48
|
+
['/rollback [hash]', 'rollback to a checkpoint'],
|
|
49
|
+
['/undo', 'undo last assistant turn'],
|
|
50
|
+
['/retry', 'retry the last user message'],
|
|
51
|
+
['/branch [name]', 'create a CVC branch'],
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
title: 'Models & Tools',
|
|
56
|
+
rows: [
|
|
57
|
+
['/model [name]', 'switch model (arrow-key picker if blank)'],
|
|
58
|
+
['/provider [name]', 'switch provider'],
|
|
59
|
+
['/skills', 'browse skills hub'],
|
|
60
|
+
['/reload-skills', 'rescan skill directory'],
|
|
61
|
+
['/agents', 'list registered agents'],
|
|
62
|
+
['/memory [show|edit|clear]', 'memory store'],
|
|
63
|
+
['/mem', 'alias of /memory'],
|
|
64
|
+
['/tools', 'list tools'],
|
|
65
|
+
['/reload', 'reload config'],
|
|
66
|
+
['/reload-mcp', 'reload MCP servers'],
|
|
67
|
+
['/compact', 'toggle compact transcript'],
|
|
68
|
+
['/compress', 'compress conversation'],
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
title: 'UI & Toggles',
|
|
73
|
+
rows: [
|
|
74
|
+
['/personality [name]', 'switch personality'],
|
|
75
|
+
['/skin [name]', 'switch skin'],
|
|
76
|
+
['/statusbar [on|off|top|bottom]', 'status bar position'],
|
|
77
|
+
['/indicator [on|off]', 'busy indicator'],
|
|
78
|
+
['/details [hidden|collapsed|expanded|cycle]', 'agent detail visibility'],
|
|
79
|
+
['/mouse [on|off]', 'mouse/wheel tracking'],
|
|
80
|
+
['/verbose, /reasoning, /fast, /yolo', 'misc toggles'],
|
|
81
|
+
['/voice, /image, /browser', 'media toggles'],
|
|
82
|
+
['/copy [n], /paste', 'clipboard'],
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
title: 'Ops & Debug',
|
|
87
|
+
rows: [
|
|
88
|
+
['/stop', 'interrupt current turn'],
|
|
89
|
+
['/steer <hint>', 'steer the in-flight turn'],
|
|
90
|
+
['/logs [n]', 'tail gateway logs'],
|
|
91
|
+
['/heapdump', 'capture a heap snapshot'],
|
|
92
|
+
['/replay [id]', 'replay a session'],
|
|
93
|
+
['/replay-diff <a> <b>', 'diff two replays'],
|
|
94
|
+
['/setup', 'run setup wizard'],
|
|
95
|
+
['/terminal-setup [auto|cursor|vscode|windsurf]', 'IDE keybindings'],
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'quit',
|
|
104
|
+
aliases: ['exit', 'q'],
|
|
105
|
+
category: 'core',
|
|
106
|
+
help: 'exit CVC',
|
|
107
|
+
run: (_arg, ctx) => ctx.emit({ kind: 'die', exitCode: 0 }),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'clear',
|
|
111
|
+
aliases: ['new'],
|
|
112
|
+
category: 'core',
|
|
113
|
+
help: 'start a new session',
|
|
114
|
+
run: (arg, ctx, raw) => {
|
|
115
|
+
const isNew = raw.startsWith('/new');
|
|
116
|
+
const title = isNew ? arg.trim() : '';
|
|
117
|
+
ctx.emit({ kind: 'rpc', method: 'session.new', params: title ? { title } : {} });
|
|
118
|
+
ctx.emit({ kind: 'patchUi', ui: { status: 'forging session…' } });
|
|
119
|
+
ctx.emit({ kind: 'sys', text: isNew ? 'new session started' : 'session cleared' });
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'redraw',
|
|
124
|
+
category: 'core',
|
|
125
|
+
help: 'force a full UI repaint',
|
|
126
|
+
run: (_a, ctx) => {
|
|
127
|
+
ctx.emit({ kind: 'patchUi', ui: { _redrawTick: Date.now() } });
|
|
128
|
+
ctx.emit({ kind: 'sys', text: 'ui redrawn' });
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'status',
|
|
133
|
+
category: 'core',
|
|
134
|
+
help: 'show live session info',
|
|
135
|
+
run: (_a, ctx) => {
|
|
136
|
+
if (!ctx.sid) {
|
|
137
|
+
ctx.emit({ kind: 'sys', text: 'no active session' });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
ctx.emit({ kind: 'rpc', method: 'session.status', params: { session_id: ctx.sid } });
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'usage',
|
|
145
|
+
category: 'core',
|
|
146
|
+
help: 'show token + cost usage',
|
|
147
|
+
run: (_a, ctx) => ctx.emit({ kind: 'rpc', method: 'session.usage', params: ctx.sid ? { session_id: ctx.sid } : {} }),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'history',
|
|
151
|
+
category: 'core',
|
|
152
|
+
help: 'view current transcript',
|
|
153
|
+
run: (arg, ctx) => {
|
|
154
|
+
const items = ctx.history().filter(m => m.role === 'user' || m.role === 'assistant');
|
|
155
|
+
if (!items.length)
|
|
156
|
+
return ctx.emit({ kind: 'sys', text: 'no conversation yet' });
|
|
157
|
+
const preview = Math.max(80, parseInt(arg, 10) || 400);
|
|
158
|
+
const lines = items.map((m, i) => {
|
|
159
|
+
const tag = m.role === 'user' ? `You #${i + 1}` : `CVC #${i + 1}`;
|
|
160
|
+
const body = (m.content || '').trim() || '(empty)';
|
|
161
|
+
const clip = body.length > preview ? `${body.slice(0, preview).trimEnd()}…` : body;
|
|
162
|
+
return `[${tag}]\n${clip}`;
|
|
163
|
+
});
|
|
164
|
+
ctx.emit({ kind: 'page', title: 'History', text: lines.join('\n\n') });
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'save',
|
|
169
|
+
category: 'core',
|
|
170
|
+
help: 'save transcript to JSON',
|
|
171
|
+
run: (_a, ctx) => {
|
|
172
|
+
if (!ctx.sid)
|
|
173
|
+
return ctx.emit({ kind: 'sys', text: 'no active session — nothing to save' });
|
|
174
|
+
ctx.emit({ kind: 'rpc', method: 'session.save', params: { session_id: ctx.sid } });
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'title',
|
|
179
|
+
category: 'core',
|
|
180
|
+
help: 'set or show session title',
|
|
181
|
+
run: (arg, ctx) => {
|
|
182
|
+
if (!ctx.sid)
|
|
183
|
+
return ctx.emit({ kind: 'sys', text: 'no active session' });
|
|
184
|
+
const t = arg.trim();
|
|
185
|
+
ctx.emit({
|
|
186
|
+
kind: 'rpc',
|
|
187
|
+
method: 'session.title',
|
|
188
|
+
params: t ? { session_id: ctx.sid, title: t } : { session_id: ctx.sid },
|
|
189
|
+
});
|
|
190
|
+
if (t)
|
|
191
|
+
ctx.emit({ kind: 'sys', text: `session title set: ${t}` });
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'compact',
|
|
196
|
+
category: 'core',
|
|
197
|
+
help: 'toggle compact transcript',
|
|
198
|
+
run: (arg, ctx) => {
|
|
199
|
+
const cur = Boolean(ctx.ui.compact);
|
|
200
|
+
const next = flagFromArg(arg, cur);
|
|
201
|
+
if (next === null)
|
|
202
|
+
return ctx.emit({ kind: 'sys', text: 'usage: /compact [on|off|toggle]' });
|
|
203
|
+
ctx.emit({ kind: 'patchUi', ui: { compact: next } });
|
|
204
|
+
ctx.emit({ kind: 'rpc', method: 'config.set', params: { key: 'compact', value: next ? 'on' : 'off' } });
|
|
205
|
+
ctx.emit({ kind: 'sys', text: `compact ${next ? 'on' : 'off'}` });
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'compress',
|
|
210
|
+
category: 'core',
|
|
211
|
+
help: 'compress conversation history',
|
|
212
|
+
run: (_a, ctx) => ctx.emit({ kind: 'rpc', method: 'session.compress', params: ctx.sid ? { session_id: ctx.sid } : {} }),
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'commit',
|
|
216
|
+
category: 'core',
|
|
217
|
+
help: 'create a CVC commit from the current transcript',
|
|
218
|
+
run: (arg, ctx) => {
|
|
219
|
+
const msg = arg.trim() || 'tui commit';
|
|
220
|
+
ctx.emit({ kind: 'rpc', method: 'cvc.commit', params: { message: msg, commit_type: 'checkpoint' } });
|
|
221
|
+
ctx.emit({ kind: 'sys', text: `commit: ${msg}` });
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'fortune',
|
|
226
|
+
category: 'core',
|
|
227
|
+
help: 'a local CVC fortune',
|
|
228
|
+
run: (arg, ctx) => {
|
|
229
|
+
const k = arg.trim().toLowerCase();
|
|
230
|
+
if (['daily', 'today', 'stable'].includes(k)) {
|
|
231
|
+
const seed = (ctx.sid ?? new Date().toISOString().slice(0, 10))
|
|
232
|
+
.split('').reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 0);
|
|
233
|
+
return ctx.emit({ kind: 'sys', text: FORTUNES[seed % FORTUNES.length] });
|
|
234
|
+
}
|
|
235
|
+
ctx.emit({ kind: 'sys', text: FORTUNES[Math.floor(Math.random() * FORTUNES.length)] });
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'copy',
|
|
240
|
+
category: 'core',
|
|
241
|
+
help: 'copy last assistant message',
|
|
242
|
+
run: (arg, ctx) => {
|
|
243
|
+
const all = ctx.history().filter(m => m.role === 'assistant');
|
|
244
|
+
if (!all.length)
|
|
245
|
+
return ctx.emit({ kind: 'sys', text: 'nothing to copy' });
|
|
246
|
+
const idx = arg ? Math.min(Math.max(1, parseInt(arg, 10) || all.length), all.length) - 1 : all.length - 1;
|
|
247
|
+
const target = all[idx];
|
|
248
|
+
ctx.emit({ kind: 'rpc', method: 'clipboard.write', params: { text: target.content } });
|
|
249
|
+
ctx.emit({ kind: 'sys', text: `copied ${target.content.length} characters` });
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'paste',
|
|
254
|
+
category: 'core',
|
|
255
|
+
help: 'attach clipboard text/image',
|
|
256
|
+
run: (_a, ctx) => ctx.emit({ kind: 'rpc', method: 'clipboard.read' }),
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'logs',
|
|
260
|
+
category: 'core',
|
|
261
|
+
help: 'tail gateway logs',
|
|
262
|
+
run: (arg, ctx) => {
|
|
263
|
+
const n = Math.min(200, Math.max(1, parseInt(arg, 10) || 40));
|
|
264
|
+
ctx.emit({ kind: 'rpc', method: 'gateway.logs', params: { tail: n } });
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: 'details',
|
|
269
|
+
aliases: ['detail'],
|
|
270
|
+
category: 'core',
|
|
271
|
+
help: 'control agent detail visibility',
|
|
272
|
+
run: (arg, ctx) => {
|
|
273
|
+
const allowed = ['hidden', 'collapsed', 'expanded', 'cycle'];
|
|
274
|
+
const m = arg.trim().toLowerCase();
|
|
275
|
+
if (!m) {
|
|
276
|
+
ctx.emit({ kind: 'sys', text: `details: ${ctx.ui.detailsMode ?? 'collapsed'}` });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (!allowed.includes(m)) {
|
|
280
|
+
return ctx.emit({ kind: 'sys', text: 'usage: /details [hidden|collapsed|expanded|cycle]' });
|
|
281
|
+
}
|
|
282
|
+
let next = m;
|
|
283
|
+
if (m === 'cycle') {
|
|
284
|
+
const order = ['hidden', 'collapsed', 'expanded'];
|
|
285
|
+
const cur = ctx.ui.detailsMode ?? 'collapsed';
|
|
286
|
+
next = order[(order.indexOf(cur) + 1) % order.length];
|
|
287
|
+
}
|
|
288
|
+
ctx.emit({ kind: 'patchUi', ui: { detailsMode: next } });
|
|
289
|
+
ctx.emit({ kind: 'sys', text: `details: ${next}` });
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const debugCommands = [
|
|
2
|
+
{
|
|
3
|
+
name: 'heapdump',
|
|
4
|
+
category: 'debug',
|
|
5
|
+
help: 'capture a V8 heap snapshot',
|
|
6
|
+
run: (_a, ctx) => {
|
|
7
|
+
ctx.emit({ kind: 'rpc', method: 'debug.heapdump' });
|
|
8
|
+
ctx.emit({ kind: 'sys', text: 'heap snapshot requested' });
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
];
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
export const opsCommands = [
|
|
2
|
+
{
|
|
3
|
+
name: 'model',
|
|
4
|
+
category: 'ops',
|
|
5
|
+
help: 'switch model (arrow-key picker if blank)',
|
|
6
|
+
run: (arg, ctx) => {
|
|
7
|
+
const name = arg.trim();
|
|
8
|
+
if (!name)
|
|
9
|
+
return ctx.emit({ kind: 'overlay', overlay: 'modelPicker' });
|
|
10
|
+
ctx.emit({ kind: 'rpc', method: 'config.set', params: { key: 'model', value: name } });
|
|
11
|
+
ctx.emit({ kind: 'sys', text: `model: ${name}` });
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'provider',
|
|
16
|
+
category: 'ops',
|
|
17
|
+
help: 'switch provider',
|
|
18
|
+
run: (arg, ctx) => {
|
|
19
|
+
const name = arg.trim();
|
|
20
|
+
if (!name)
|
|
21
|
+
return ctx.emit({ kind: 'rpc', method: 'config.get', params: { key: 'provider' } });
|
|
22
|
+
ctx.emit({ kind: 'rpc', method: 'config.set', params: { key: 'provider', value: name } });
|
|
23
|
+
ctx.emit({ kind: 'sys', text: `provider: ${name}` });
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'skills',
|
|
28
|
+
category: 'ops',
|
|
29
|
+
help: 'browse the skills hub',
|
|
30
|
+
run: (_a, ctx) => ctx.emit({ kind: 'overlay', overlay: 'skillsHub' }),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'reload-skills',
|
|
34
|
+
category: 'ops',
|
|
35
|
+
help: 'rescan skill directory',
|
|
36
|
+
run: (_a, ctx) => {
|
|
37
|
+
ctx.emit({ kind: 'rpc', method: 'skills.reload' });
|
|
38
|
+
ctx.emit({ kind: 'sys', text: 'skills reloaded' });
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'agents',
|
|
43
|
+
category: 'ops',
|
|
44
|
+
help: 'list registered agents',
|
|
45
|
+
run: (_a, ctx) => ctx.emit({ kind: 'overlay', overlay: 'agentsList' }),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'memory',
|
|
49
|
+
aliases: ['mem'],
|
|
50
|
+
category: 'ops',
|
|
51
|
+
help: 'open the memory editor',
|
|
52
|
+
run: (arg, ctx) => {
|
|
53
|
+
const m = arg.trim().toLowerCase();
|
|
54
|
+
if (m === 'clear') {
|
|
55
|
+
ctx.emit({ kind: 'rpc', method: 'memory.clear' });
|
|
56
|
+
return ctx.emit({ kind: 'sys', text: 'memory cleared' });
|
|
57
|
+
}
|
|
58
|
+
if (m === 'show')
|
|
59
|
+
return ctx.emit({ kind: 'rpc', method: 'memory.show' });
|
|
60
|
+
ctx.emit({ kind: 'overlay', overlay: 'memoryEditor' });
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'tools',
|
|
65
|
+
category: 'ops',
|
|
66
|
+
help: 'list available tools',
|
|
67
|
+
run: (_a, ctx) => ctx.emit({ kind: 'rpc', method: 'tools.list' }),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'reload',
|
|
71
|
+
category: 'ops',
|
|
72
|
+
help: 'reload CVC config',
|
|
73
|
+
run: (_a, ctx) => {
|
|
74
|
+
ctx.emit({ kind: 'rpc', method: 'config.reload' });
|
|
75
|
+
ctx.emit({ kind: 'sys', text: 'config reloaded' });
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'reload-mcp',
|
|
80
|
+
category: 'ops',
|
|
81
|
+
help: 'reload MCP servers',
|
|
82
|
+
run: (_a, ctx) => {
|
|
83
|
+
ctx.emit({ kind: 'rpc', method: 'mcp.reload' });
|
|
84
|
+
ctx.emit({ kind: 'sys', text: 'mcp servers reloading' });
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'personality',
|
|
89
|
+
category: 'ops',
|
|
90
|
+
help: 'switch personality',
|
|
91
|
+
run: (arg, ctx) => {
|
|
92
|
+
const name = arg.trim();
|
|
93
|
+
if (!name)
|
|
94
|
+
return ctx.emit({ kind: 'rpc', method: 'config.get', params: { key: 'personality' } });
|
|
95
|
+
ctx.emit({ kind: 'rpc', method: 'config.set', params: { key: 'personality', value: name } });
|
|
96
|
+
ctx.emit({ kind: 'sys', text: `personality: ${name}` });
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'skin',
|
|
101
|
+
category: 'ops',
|
|
102
|
+
help: 'switch CLI skin',
|
|
103
|
+
run: (arg, ctx) => {
|
|
104
|
+
const name = arg.trim();
|
|
105
|
+
if (!name)
|
|
106
|
+
return ctx.emit({ kind: 'rpc', method: 'config.get', params: { key: 'skin' } });
|
|
107
|
+
ctx.emit({ kind: 'rpc', method: 'config.set', params: { key: 'skin', value: name } });
|
|
108
|
+
ctx.emit({ kind: 'sys', text: `skin: ${name}` });
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'statusbar',
|
|
113
|
+
aliases: ['sb'],
|
|
114
|
+
category: 'ops',
|
|
115
|
+
help: 'status bar position (on|off|top|bottom)',
|
|
116
|
+
run: (arg, ctx) => {
|
|
117
|
+
const m = arg.trim().toLowerCase();
|
|
118
|
+
const cur = ctx.ui.statusBar ?? 'top';
|
|
119
|
+
const next = !m || m === 'toggle'
|
|
120
|
+
? (cur === 'off' ? 'top' : 'off')
|
|
121
|
+
: m === 'on' || m === 'top'
|
|
122
|
+
? 'top'
|
|
123
|
+
: (m === 'off' || m === 'bottom') ? m : null;
|
|
124
|
+
if (!next)
|
|
125
|
+
return ctx.emit({ kind: 'sys', text: 'usage: /statusbar [on|off|top|bottom|toggle]' });
|
|
126
|
+
ctx.emit({ kind: 'patchUi', ui: { statusBar: next } });
|
|
127
|
+
ctx.emit({ kind: 'rpc', method: 'config.set', params: { key: 'statusbar', value: next } });
|
|
128
|
+
ctx.emit({ kind: 'sys', text: `statusbar: ${next}` });
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'indicator',
|
|
133
|
+
category: 'ops',
|
|
134
|
+
help: 'busy-indicator style [dots|braille|kawaii|off]',
|
|
135
|
+
run: (arg, ctx) => {
|
|
136
|
+
const m = arg.trim().toLowerCase();
|
|
137
|
+
const allowed = ['dots', 'braille', 'kawaii', 'off'];
|
|
138
|
+
if (!m)
|
|
139
|
+
return ctx.emit({ kind: 'sys', text: `indicator: ${ctx.ui.indicator ?? 'kawaii'}` });
|
|
140
|
+
if (!allowed.includes(m))
|
|
141
|
+
return ctx.emit({ kind: 'sys', text: `usage: /indicator [${allowed.join('|')}]` });
|
|
142
|
+
ctx.emit({ kind: 'patchUi', ui: { indicator: m } });
|
|
143
|
+
ctx.emit({ kind: 'rpc', method: 'config.set', params: { key: 'indicator', value: m } });
|
|
144
|
+
ctx.emit({ kind: 'sys', text: `indicator: ${m}` });
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'mouse',
|
|
149
|
+
aliases: ['scroll'],
|
|
150
|
+
category: 'ops',
|
|
151
|
+
help: 'toggle mouse/wheel tracking',
|
|
152
|
+
run: (arg, ctx) => {
|
|
153
|
+
const cur = Boolean(ctx.ui.mouseTracking);
|
|
154
|
+
const m = arg.trim().toLowerCase();
|
|
155
|
+
const next = !m || m === 'toggle' ? !cur : m === 'on' ? true : m === 'off' ? false : null;
|
|
156
|
+
if (next === null)
|
|
157
|
+
return ctx.emit({ kind: 'sys', text: 'usage: /mouse [on|off|toggle]' });
|
|
158
|
+
ctx.emit({ kind: 'patchUi', ui: { mouseTracking: next } });
|
|
159
|
+
ctx.emit({ kind: 'rpc', method: 'config.set', params: { key: 'mouse', value: next ? 'on' : 'off' } });
|
|
160
|
+
ctx.emit({ kind: 'sys', text: `mouse tracking ${next ? 'on' : 'off'}` });
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const sessionCommands = [
|
|
2
|
+
{
|
|
3
|
+
name: 'sessions',
|
|
4
|
+
category: 'session',
|
|
5
|
+
help: 'browse prior sessions',
|
|
6
|
+
run: (_a, ctx) => ctx.emit({ kind: 'overlay', overlay: 'sessionPicker' }),
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'resume',
|
|
10
|
+
category: 'session',
|
|
11
|
+
help: 'resume a prior session by id',
|
|
12
|
+
run: (arg, ctx) => {
|
|
13
|
+
const id = arg.trim();
|
|
14
|
+
if (!id)
|
|
15
|
+
return ctx.emit({ kind: 'overlay', overlay: 'sessionPicker' });
|
|
16
|
+
ctx.emit({ kind: 'rpc', method: 'session.resume', params: { session_id: id } });
|
|
17
|
+
ctx.emit({ kind: 'sys', text: `resuming session ${id}…` });
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'checkpoint',
|
|
22
|
+
category: 'session',
|
|
23
|
+
help: 'create a CVC checkpoint commit',
|
|
24
|
+
run: (arg, ctx) => {
|
|
25
|
+
const msg = arg.trim() || 'tui checkpoint';
|
|
26
|
+
ctx.emit({ kind: 'rpc', method: 'cvc.commit', params: { message: msg, commit_type: 'checkpoint' } });
|
|
27
|
+
ctx.emit({ kind: 'sys', text: `checkpoint: ${msg}` });
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'rollback',
|
|
32
|
+
category: 'session',
|
|
33
|
+
help: 'rollback to a checkpoint',
|
|
34
|
+
run: (arg, ctx) => {
|
|
35
|
+
const h = arg.trim();
|
|
36
|
+
if (!h)
|
|
37
|
+
return ctx.emit({ kind: 'sys', text: 'usage: /rollback <commit-hash>' });
|
|
38
|
+
ctx.emit({ kind: 'rpc', method: 'cvc.restore', params: { commit_hash: h } });
|
|
39
|
+
ctx.emit({ kind: 'sys', text: `rolling back to ${h}…` });
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'undo',
|
|
44
|
+
category: 'session',
|
|
45
|
+
help: 'undo the last turn',
|
|
46
|
+
run: (_a, ctx) => ctx.emit({ kind: 'rpc', method: 'session.undo', params: ctx.sid ? { session_id: ctx.sid } : {} }),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'retry',
|
|
50
|
+
category: 'session',
|
|
51
|
+
help: 'retry the last user message',
|
|
52
|
+
run: (_a, ctx) => ctx.emit({ kind: 'rpc', method: 'session.retry', params: ctx.sid ? { session_id: ctx.sid } : {} }),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'branch',
|
|
56
|
+
category: 'session',
|
|
57
|
+
help: 'create a CVC cognitive branch',
|
|
58
|
+
run: (arg, ctx) => {
|
|
59
|
+
const name = arg.trim();
|
|
60
|
+
if (!name)
|
|
61
|
+
return ctx.emit({ kind: 'sys', text: 'usage: /branch <name>' });
|
|
62
|
+
ctx.emit({ kind: 'rpc', method: 'cvc.branch', params: { name } });
|
|
63
|
+
ctx.emit({ kind: 'sys', text: `branch created: ${name}` });
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'stop',
|
|
68
|
+
category: 'session',
|
|
69
|
+
help: 'interrupt the current turn',
|
|
70
|
+
run: (_a, ctx) => {
|
|
71
|
+
ctx.emit({ kind: 'rpc', method: 'session.stop', params: ctx.sid ? { session_id: ctx.sid } : {} });
|
|
72
|
+
ctx.emit({ kind: 'sys', text: 'interrupt requested' });
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'steer',
|
|
77
|
+
category: 'session',
|
|
78
|
+
help: 'steer the in-flight turn',
|
|
79
|
+
run: (arg, ctx) => {
|
|
80
|
+
const hint = arg.trim();
|
|
81
|
+
if (!hint)
|
|
82
|
+
return ctx.emit({ kind: 'sys', text: 'usage: /steer <hint>' });
|
|
83
|
+
ctx.emit({
|
|
84
|
+
kind: 'rpc',
|
|
85
|
+
method: 'session.steer',
|
|
86
|
+
params: { session_id: ctx.sid, hint },
|
|
87
|
+
});
|
|
88
|
+
ctx.emit({ kind: 'sys', text: 'steer queued' });
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const setupCommands = [
|
|
2
|
+
{
|
|
3
|
+
name: 'setup',
|
|
4
|
+
category: 'setup',
|
|
5
|
+
help: 'launch the CVC setup wizard',
|
|
6
|
+
run: (_a, ctx) => {
|
|
7
|
+
ctx.emit({ kind: 'rpc', method: 'cli.setup' });
|
|
8
|
+
ctx.emit({ kind: 'sys', text: 'launching setup wizard…' });
|
|
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
|
+
},
|
|
47
|
+
];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Trivial toggle-style commands — boolean UI flags routed through patchUi
|
|
2
|
+
// + a config.set RPC so the gateway can persist them.
|
|
3
|
+
import { flagFromArg } from '../types.js';
|
|
4
|
+
const TOGGLES = [
|
|
5
|
+
{ name: 'verbose', help: 'toggle verbose logging', key: 'verbose' },
|
|
6
|
+
{ name: 'reasoning', help: 'toggle reasoning trace display', key: 'reasoning' },
|
|
7
|
+
{ name: 'fast', help: 'toggle fast mode (skip extras)', key: 'fast' },
|
|
8
|
+
{ name: 'yolo', help: 'toggle YOLO mode (auto-approve)', key: 'yolo' },
|
|
9
|
+
{ name: 'busy', help: 'toggle busy indicator', key: 'busy' },
|
|
10
|
+
{ name: 'queue', help: 'toggle queued-input mode', key: 'queue' },
|
|
11
|
+
{ name: 'background', help: 'toggle background-task display', key: 'background' },
|
|
12
|
+
{ name: 'browser', help: 'toggle browser tool', key: 'browserEnabled', aliases: ['br'] },
|
|
13
|
+
{ name: 'voice', help: 'toggle voice input/output', key: 'voice' },
|
|
14
|
+
{ name: 'image', help: 'toggle inline image rendering', key: 'image' },
|
|
15
|
+
];
|
|
16
|
+
export const toggleCommands = TOGGLES.map(t => ({
|
|
17
|
+
name: t.name,
|
|
18
|
+
aliases: t.aliases,
|
|
19
|
+
category: 'toggles',
|
|
20
|
+
help: t.help,
|
|
21
|
+
run: (arg, ctx) => {
|
|
22
|
+
const cur = Boolean(ctx.ui[t.key]);
|
|
23
|
+
const next = flagFromArg(arg, cur);
|
|
24
|
+
if (next === null) {
|
|
25
|
+
ctx.emit({ kind: 'sys', text: `usage: /${t.name} [on|off|toggle]` });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
ctx.emit({ kind: 'patchUi', ui: { [t.key]: next } });
|
|
29
|
+
ctx.emit({
|
|
30
|
+
kind: 'rpc',
|
|
31
|
+
method: 'config.set',
|
|
32
|
+
params: { key: t.configKey ?? t.name, value: next ? 'on' : 'off' },
|
|
33
|
+
});
|
|
34
|
+
ctx.emit({ kind: 'sys', text: `${t.name} ${next ? 'on' : 'off'}` });
|
|
35
|
+
},
|
|
36
|
+
}));
|