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,163 +1,498 @@
|
|
|
1
|
+
import { applyDelegationStatus, getDelegationState } from '../../delegationStore.js';
|
|
2
|
+
import { patchOverlayState } from '../../overlayStore.js';
|
|
3
|
+
import { getSpawnHistory, pushDiskSnapshot, setDiffPair } from '../../spawnHistoryStore.js';
|
|
1
4
|
export const opsCommands = [
|
|
2
5
|
{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
help: 'stop background processes',
|
|
7
|
+
name: 'stop',
|
|
8
|
+
run: (_arg, ctx) => {
|
|
9
|
+
ctx.gateway
|
|
10
|
+
.rpc('process.stop', {})
|
|
11
|
+
.then(ctx.guarded(r => {
|
|
12
|
+
const killed = Number(r.killed ?? 0);
|
|
13
|
+
const noun = killed === 1 ? 'process' : 'processes';
|
|
14
|
+
ctx.transcript.sys(`stopped ${killed} background ${noun}`);
|
|
15
|
+
}))
|
|
16
|
+
.catch(ctx.guardedErr);
|
|
17
|
+
}
|
|
13
18
|
},
|
|
14
19
|
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
aliases: ['reload_mcp'],
|
|
21
|
+
help: 'reload MCP servers in the live session (warns about prompt cache invalidation)',
|
|
22
|
+
name: 'reload-mcp',
|
|
18
23
|
run: (arg, ctx) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
// Parse arg: `now` / `always` skip the confirmation gate.
|
|
25
|
+
// `always` additionally persists approvals.mcp_reload_confirm=false.
|
|
26
|
+
const a = (arg || '').trim().toLowerCase();
|
|
27
|
+
const params = {
|
|
28
|
+
session_id: ctx.sid
|
|
29
|
+
};
|
|
30
|
+
if (a === 'now' || a === 'approve' || a === 'once' || a === 'yes') {
|
|
31
|
+
params.confirm = true;
|
|
32
|
+
}
|
|
33
|
+
else if (a === 'always') {
|
|
34
|
+
params.confirm = true;
|
|
35
|
+
params.always = true;
|
|
36
|
+
}
|
|
37
|
+
ctx.gateway
|
|
38
|
+
.rpc('reload.mcp', params)
|
|
39
|
+
.then(ctx.guarded(r => {
|
|
40
|
+
if (r.status === 'confirm_required') {
|
|
41
|
+
ctx.transcript.sys(r.message || '/reload-mcp requires confirmation');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (r.status === 'reloaded') {
|
|
45
|
+
ctx.transcript.sys(params.always
|
|
46
|
+
? 'MCP servers reloaded · future /reload-mcp will run without confirmation'
|
|
47
|
+
: 'MCP servers reloaded');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
ctx.transcript.sys('reload complete');
|
|
51
|
+
}))
|
|
52
|
+
.catch(ctx.guardedErr);
|
|
53
|
+
}
|
|
40
54
|
},
|
|
41
55
|
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
help: 're-read ~/.hermes/.env into the running gateway (CLI parity)',
|
|
57
|
+
name: 'reload',
|
|
58
|
+
run: (_arg, ctx) => {
|
|
59
|
+
ctx.gateway
|
|
60
|
+
.rpc('reload.env', {})
|
|
61
|
+
.then(ctx.guarded(r => {
|
|
62
|
+
const n = Number(r.updated ?? 0);
|
|
63
|
+
const noun = n === 1 ? 'var' : 'vars';
|
|
64
|
+
ctx.transcript.sys(`reloaded .env (${n} ${noun} updated)`);
|
|
65
|
+
}))
|
|
66
|
+
.catch(ctx.guardedErr);
|
|
67
|
+
}
|
|
46
68
|
},
|
|
47
69
|
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
category: 'ops',
|
|
51
|
-
help: 'open the memory editor',
|
|
70
|
+
help: 'manage browser CDP connection [connect|disconnect|status]',
|
|
71
|
+
name: 'browser',
|
|
52
72
|
run: (arg, ctx) => {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return ctx.
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
const [rawAction = 'status', ...rest] = arg.trim().split(/\s+/).filter(Boolean);
|
|
74
|
+
const action = rawAction.toLowerCase();
|
|
75
|
+
if (!['connect', 'disconnect', 'status'].includes(action)) {
|
|
76
|
+
return ctx.transcript.sys('usage: /browser [connect|disconnect|status] [url] · persistent: set browser.cdp_url in config.yaml');
|
|
77
|
+
}
|
|
78
|
+
const sid = ctx.sid ?? null;
|
|
79
|
+
const url = action === 'connect' ? rest.join(' ').trim() || 'http://127.0.0.1:9222' : undefined;
|
|
80
|
+
if (url) {
|
|
81
|
+
ctx.transcript.sys(`checking Chrome remote debugging at ${url}...`);
|
|
82
|
+
}
|
|
83
|
+
ctx.gateway
|
|
84
|
+
.rpc('browser.manage', { action, session_id: sid, ...(url && { url }) })
|
|
85
|
+
.then(ctx.guarded(r => {
|
|
86
|
+
// Without a session we can't subscribe to streamed
|
|
87
|
+
// browser.progress events, so flush the bundled list.
|
|
88
|
+
if (!sid) {
|
|
89
|
+
r.messages?.forEach(message => ctx.transcript.sys(message));
|
|
90
|
+
}
|
|
91
|
+
if (action === 'status') {
|
|
92
|
+
return ctx.transcript.sys(r.connected
|
|
93
|
+
? `browser connected: ${r.url || '(url unavailable)'}`
|
|
94
|
+
: 'browser not connected (try /browser connect <url> or set browser.cdp_url in config.yaml)');
|
|
95
|
+
}
|
|
96
|
+
if (action === 'disconnect') {
|
|
97
|
+
return ctx.transcript.sys('browser disconnected');
|
|
98
|
+
}
|
|
99
|
+
if (r.connected) {
|
|
100
|
+
ctx.transcript.sys('Browser connected to live Chrome via CDP');
|
|
101
|
+
ctx.transcript.sys(`Endpoint: ${r.url || '(url unavailable)'}`);
|
|
102
|
+
ctx.transcript.sys('next browser tool call will use this CDP endpoint');
|
|
103
|
+
}
|
|
104
|
+
}))
|
|
105
|
+
.catch(ctx.guardedErr);
|
|
106
|
+
}
|
|
77
107
|
},
|
|
78
108
|
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
help: 'list, diff, or restore checkpoints',
|
|
110
|
+
name: 'rollback',
|
|
111
|
+
run: (arg, ctx) => {
|
|
112
|
+
if (!ctx.sid) {
|
|
113
|
+
return ctx.transcript.sys('no active session — nothing to rollback');
|
|
114
|
+
}
|
|
115
|
+
const trimmed = arg.trim();
|
|
116
|
+
const [first = '', ...rest] = trimmed.split(/\s+/).filter(Boolean);
|
|
117
|
+
const lower = first.toLowerCase();
|
|
118
|
+
if (!trimmed || lower === 'list' || lower === 'ls') {
|
|
119
|
+
return ctx.gateway
|
|
120
|
+
.rpc('rollback.list', { session_id: ctx.sid })
|
|
121
|
+
.then(ctx.guarded(r => {
|
|
122
|
+
if (!r.enabled) {
|
|
123
|
+
return ctx.transcript.sys('checkpoints are not enabled');
|
|
124
|
+
}
|
|
125
|
+
const checkpoints = r.checkpoints ?? [];
|
|
126
|
+
if (!checkpoints.length) {
|
|
127
|
+
return ctx.transcript.sys('no checkpoints found');
|
|
128
|
+
}
|
|
129
|
+
ctx.transcript.panel('Rollback checkpoints', [
|
|
130
|
+
{
|
|
131
|
+
rows: checkpoints.map((c, idx) => [
|
|
132
|
+
`${idx + 1}. ${c.hash.slice(0, 10)}`,
|
|
133
|
+
[c.timestamp, c.message].filter(Boolean).join(' · ') || '(no metadata)'
|
|
134
|
+
])
|
|
135
|
+
}
|
|
136
|
+
]);
|
|
137
|
+
}))
|
|
138
|
+
.catch(ctx.guardedErr);
|
|
139
|
+
}
|
|
140
|
+
if (lower === 'diff') {
|
|
141
|
+
const hash = rest[0];
|
|
142
|
+
if (!hash) {
|
|
143
|
+
return ctx.transcript.sys('usage: /rollback diff <checkpoint>');
|
|
144
|
+
}
|
|
145
|
+
return ctx.gateway
|
|
146
|
+
.rpc('rollback.diff', { hash, session_id: ctx.sid })
|
|
147
|
+
.then(ctx.guarded(r => {
|
|
148
|
+
const body = (r.rendered || r.diff || '').trim();
|
|
149
|
+
if (!body && !r.stat) {
|
|
150
|
+
return ctx.transcript.sys('no changes since this checkpoint');
|
|
151
|
+
}
|
|
152
|
+
const text = [r.stat || '', body].filter(Boolean).join('\n\n');
|
|
153
|
+
ctx.transcript.page(text, 'Rollback diff');
|
|
154
|
+
}))
|
|
155
|
+
.catch(ctx.guardedErr);
|
|
156
|
+
}
|
|
157
|
+
const hash = first;
|
|
158
|
+
const filePath = rest.join(' ').trim();
|
|
159
|
+
return ctx.gateway
|
|
160
|
+
.rpc('rollback.restore', {
|
|
161
|
+
...(filePath ? { file_path: filePath } : {}),
|
|
162
|
+
hash,
|
|
163
|
+
session_id: ctx.sid
|
|
164
|
+
})
|
|
165
|
+
.then(ctx.guarded(r => {
|
|
166
|
+
if (!r.success) {
|
|
167
|
+
return ctx.transcript.sys(`rollback failed: ${r.error || r.message || 'unknown error'}`);
|
|
168
|
+
}
|
|
169
|
+
const target = filePath || 'workspace';
|
|
170
|
+
const detail = r.reason || r.message || r.restored_to || 'restored';
|
|
171
|
+
ctx.transcript.sys(`rollback restored ${target}: ${detail}`);
|
|
172
|
+
if ((r.history_removed ?? 0) > 0) {
|
|
173
|
+
ctx.transcript.setHistoryItems(prev => ctx.transcript.trimLastExchange(prev));
|
|
174
|
+
}
|
|
175
|
+
}))
|
|
176
|
+
.catch(ctx.guardedErr);
|
|
177
|
+
}
|
|
86
178
|
},
|
|
87
179
|
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
180
|
+
aliases: ['tasks'],
|
|
181
|
+
help: 'open the spawn-tree dashboard (live audit + kill/pause controls)',
|
|
182
|
+
name: 'agents',
|
|
91
183
|
run: (arg, ctx) => {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
184
|
+
const sub = arg.trim().toLowerCase();
|
|
185
|
+
// Stay compatible with the gateway `/agents [pause|resume|status]` CLI —
|
|
186
|
+
// explicit subcommands skip the overlay and act directly so scripts and
|
|
187
|
+
// multi-step flows can drive it without entering interactive mode.
|
|
188
|
+
if (sub === 'pause' || sub === 'resume' || sub === 'unpause') {
|
|
189
|
+
const paused = sub === 'pause';
|
|
190
|
+
ctx.gateway.gw
|
|
191
|
+
.request('delegation.pause', { paused })
|
|
192
|
+
.then(r => {
|
|
193
|
+
applyDelegationStatus({ paused: r?.paused });
|
|
194
|
+
ctx.transcript.sys(`delegation · ${r?.paused ? 'paused' : 'resumed'}`);
|
|
195
|
+
})
|
|
196
|
+
.catch(ctx.guardedErr);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (sub === 'status') {
|
|
200
|
+
const d = getDelegationState();
|
|
201
|
+
ctx.transcript.sys(`delegation · ${d.paused ? 'paused' : 'active'} · caps d${d.maxSpawnDepth ?? '?'}/${d.maxConcurrentChildren ?? '?'}`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
patchOverlayState({ agents: true, agentsInitialHistoryIndex: 0 });
|
|
205
|
+
}
|
|
98
206
|
},
|
|
99
207
|
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
help: 'switch CLI skin',
|
|
208
|
+
help: 'replay a completed spawn tree · `/replay [N|last|list|load <path>]`',
|
|
209
|
+
name: 'replay',
|
|
103
210
|
run: (arg, ctx) => {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
211
|
+
const history = getSpawnHistory();
|
|
212
|
+
const raw = arg.trim();
|
|
213
|
+
const lower = raw.toLowerCase();
|
|
214
|
+
// ── Disk-backed listing ─────────────────────────────────────
|
|
215
|
+
if (lower === 'list' || lower === 'ls') {
|
|
216
|
+
ctx.gateway
|
|
217
|
+
.rpc('spawn_tree.list', {
|
|
218
|
+
limit: 30,
|
|
219
|
+
session_id: ctx.sid ?? 'default'
|
|
220
|
+
})
|
|
221
|
+
.then(ctx.guarded(r => {
|
|
222
|
+
const entries = r.entries ?? [];
|
|
223
|
+
if (!entries.length) {
|
|
224
|
+
return ctx.transcript.sys('no archived spawn trees on disk for this session');
|
|
225
|
+
}
|
|
226
|
+
const rows = entries.map(e => {
|
|
227
|
+
const ts = e.finished_at ? new Date(e.finished_at * 1000).toLocaleString() : '?';
|
|
228
|
+
const label = e.label || `${e.count} subagents`;
|
|
229
|
+
return [`${ts} · ${e.count}×`, `${label}\n ${e.path}`];
|
|
230
|
+
});
|
|
231
|
+
ctx.transcript.panel('Archived spawn trees', [{ rows }]);
|
|
232
|
+
}))
|
|
233
|
+
.catch(ctx.guardedErr);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// ── Disk-backed load by path ─────────────────────────────────
|
|
237
|
+
if (lower.startsWith('load ')) {
|
|
238
|
+
const path = raw.slice(5).trim();
|
|
239
|
+
if (!path) {
|
|
240
|
+
return ctx.transcript.sys('usage: /replay load <path>');
|
|
241
|
+
}
|
|
242
|
+
ctx.gateway
|
|
243
|
+
.rpc('spawn_tree.load', { path })
|
|
244
|
+
.then(ctx.guarded(r => {
|
|
245
|
+
if (!r.subagents?.length) {
|
|
246
|
+
return ctx.transcript.sys('snapshot empty or unreadable');
|
|
247
|
+
}
|
|
248
|
+
// Push onto the in-memory history so the overlay picks it up
|
|
249
|
+
// by index 1 just like any other snapshot.
|
|
250
|
+
pushDiskSnapshot(r, path);
|
|
251
|
+
patchOverlayState({ agents: true, agentsInitialHistoryIndex: 1 });
|
|
252
|
+
}))
|
|
253
|
+
.catch(ctx.guardedErr);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// ── In-memory nav (same-session) ─────────────────────────────
|
|
257
|
+
if (!history.length) {
|
|
258
|
+
return ctx.transcript.sys('no completed spawn trees this session · try /replay list');
|
|
259
|
+
}
|
|
260
|
+
let index = 1;
|
|
261
|
+
if (raw && lower !== 'last') {
|
|
262
|
+
const parsed = parseInt(raw, 10);
|
|
263
|
+
if (Number.isNaN(parsed) || parsed < 1 || parsed > history.length) {
|
|
264
|
+
return ctx.transcript.sys(`replay: index out of range 1..${history.length} · use /replay list for disk`);
|
|
265
|
+
}
|
|
266
|
+
index = parsed;
|
|
267
|
+
}
|
|
268
|
+
patchOverlayState({ agents: true, agentsInitialHistoryIndex: index });
|
|
269
|
+
}
|
|
110
270
|
},
|
|
111
271
|
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
category: 'ops',
|
|
115
|
-
help: 'status bar position (on|off|top|bottom)',
|
|
272
|
+
help: 'diff two completed spawn trees · `/replay-diff <baseline> <candidate>` (indexes from /replay list or history N)',
|
|
273
|
+
name: 'replay-diff',
|
|
116
274
|
run: (arg, ctx) => {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
275
|
+
const parts = arg.trim().split(/\s+/).filter(Boolean);
|
|
276
|
+
if (parts.length !== 2) {
|
|
277
|
+
return ctx.transcript.sys('usage: /replay-diff <a> <b> (e.g. /replay-diff 1 2 for last two)');
|
|
278
|
+
}
|
|
279
|
+
const [a, b] = parts;
|
|
280
|
+
const history = getSpawnHistory();
|
|
281
|
+
const resolve = (token) => {
|
|
282
|
+
const n = parseInt(token, 10);
|
|
283
|
+
if (Number.isFinite(n) && n >= 1 && n <= history.length) {
|
|
284
|
+
return history[n - 1] ?? null;
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
};
|
|
288
|
+
const baseline = resolve(a);
|
|
289
|
+
const candidate = resolve(b);
|
|
290
|
+
if (!baseline || !candidate) {
|
|
291
|
+
return ctx.transcript.sys(`replay-diff: could not resolve indices · history has ${history.length} entries`);
|
|
292
|
+
}
|
|
293
|
+
setDiffPair({ baseline, candidate });
|
|
294
|
+
patchOverlayState({ agents: true, agentsInitialHistoryIndex: 0 });
|
|
295
|
+
}
|
|
130
296
|
},
|
|
131
297
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
run: (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
298
|
+
aliases: ['reload_skills'],
|
|
299
|
+
help: 're-scan installed skills in the live TUI gateway',
|
|
300
|
+
name: 'reload-skills',
|
|
301
|
+
run: (_arg, ctx) => {
|
|
302
|
+
ctx.gateway
|
|
303
|
+
.rpc('skills.reload', {})
|
|
304
|
+
.then(ctx.guarded(r => {
|
|
305
|
+
ctx.transcript.page(r.output || 'skills reloaded', 'Reload Skills');
|
|
306
|
+
ctx.gateway
|
|
307
|
+
.rpc('commands.catalog', {})
|
|
308
|
+
.then(ctx.guarded(catalog => {
|
|
309
|
+
if (!catalog?.pairs) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
ctx.local.setCatalog({
|
|
313
|
+
canon: (catalog.canon ?? {}),
|
|
314
|
+
categories: catalog.categories ?? [],
|
|
315
|
+
pairs: catalog.pairs,
|
|
316
|
+
skillCount: (catalog.skill_count ?? 0),
|
|
317
|
+
sub: (catalog.sub ?? {})
|
|
318
|
+
});
|
|
319
|
+
}))
|
|
320
|
+
.catch(() => { });
|
|
321
|
+
}))
|
|
322
|
+
.catch(ctx.guardedErr);
|
|
323
|
+
}
|
|
146
324
|
},
|
|
147
325
|
{
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
326
|
+
help: 'browse, inspect, install skills',
|
|
327
|
+
name: 'skills',
|
|
328
|
+
run: (arg, ctx, cmd) => {
|
|
329
|
+
const text = arg.trim();
|
|
330
|
+
if (!text) {
|
|
331
|
+
return patchOverlayState({ skillsHub: true });
|
|
332
|
+
}
|
|
333
|
+
const [sub, ...rest] = text.split(/\s+/);
|
|
334
|
+
const query = rest.join(' ').trim();
|
|
335
|
+
const { rpc } = ctx.gateway;
|
|
336
|
+
const { panel, sys } = ctx.transcript;
|
|
337
|
+
const runViaSlashWorker = () => {
|
|
338
|
+
ctx.gateway.gw
|
|
339
|
+
.request('slash.exec', { command: cmd.slice(1), session_id: ctx.sid })
|
|
340
|
+
.then(r => {
|
|
341
|
+
if (ctx.stale()) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const body = r?.output || '/skills: no output';
|
|
345
|
+
const formatted = r?.warning ? `warning: ${r.warning}\n${body}` : body;
|
|
346
|
+
const long = formatted.length > 180 || formatted.split('\n').filter(Boolean).length > 2;
|
|
347
|
+
long ? ctx.transcript.page(formatted, 'Skills') : ctx.transcript.sys(formatted);
|
|
348
|
+
})
|
|
349
|
+
.catch(ctx.guardedErr);
|
|
350
|
+
};
|
|
351
|
+
if (sub === 'list') {
|
|
352
|
+
rpc('skills.manage', { action: 'list' })
|
|
353
|
+
.then(ctx.guarded(r => {
|
|
354
|
+
const cats = Object.entries(r.skills ?? {}).sort();
|
|
355
|
+
if (!cats.length) {
|
|
356
|
+
return sys('no skills available');
|
|
357
|
+
}
|
|
358
|
+
panel('Skills', cats.map(([title, items]) => ({ items, title })));
|
|
359
|
+
}))
|
|
360
|
+
.catch(ctx.guardedErr);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (sub === 'inspect') {
|
|
364
|
+
if (!query) {
|
|
365
|
+
return sys('usage: /skills inspect <name>');
|
|
366
|
+
}
|
|
367
|
+
rpc('skills.manage', { action: 'inspect', query })
|
|
368
|
+
.then(ctx.guarded(r => {
|
|
369
|
+
const info = r.info ?? {};
|
|
370
|
+
if (!info.name) {
|
|
371
|
+
return sys(`unknown skill: ${query}`);
|
|
372
|
+
}
|
|
373
|
+
const rows = [
|
|
374
|
+
['Name', String(info.name)],
|
|
375
|
+
['Category', String(info.category ?? '')],
|
|
376
|
+
['Path', String(info.path ?? '')]
|
|
377
|
+
];
|
|
378
|
+
const sections = [{ rows }];
|
|
379
|
+
if (info.description) {
|
|
380
|
+
sections.push({ text: String(info.description) });
|
|
381
|
+
}
|
|
382
|
+
panel('Skill', sections);
|
|
383
|
+
}))
|
|
384
|
+
.catch(ctx.guardedErr);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (sub === 'search') {
|
|
388
|
+
if (!query) {
|
|
389
|
+
return sys('usage: /skills search <query>');
|
|
390
|
+
}
|
|
391
|
+
rpc('skills.manage', { action: 'search', query })
|
|
392
|
+
.then(ctx.guarded(r => {
|
|
393
|
+
const results = r.results ?? [];
|
|
394
|
+
if (!results.length) {
|
|
395
|
+
return sys(`no results for: ${query}`);
|
|
396
|
+
}
|
|
397
|
+
panel(`Search: ${query}`, [{ rows: results.map(s => [s.name, s.description ?? '']) }]);
|
|
398
|
+
}))
|
|
399
|
+
.catch(ctx.guardedErr);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (sub === 'install') {
|
|
403
|
+
if (!query) {
|
|
404
|
+
return sys('usage: /skills install <name or url>');
|
|
405
|
+
}
|
|
406
|
+
sys(`installing ${query}…`);
|
|
407
|
+
rpc('skills.manage', { action: 'install', query })
|
|
408
|
+
.then(ctx.guarded(r => sys(r.installed ? `installed ${r.name ?? query}` : 'install failed')))
|
|
409
|
+
.catch(ctx.guardedErr);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (sub === 'browse') {
|
|
413
|
+
const pageNum = query ? parseInt(query, 10) : 1;
|
|
414
|
+
if (Number.isNaN(pageNum) || pageNum < 1) {
|
|
415
|
+
return sys('usage: /skills browse [page] (page must be a positive number)');
|
|
416
|
+
}
|
|
417
|
+
sys('fetching community skills (scans 6 sources, may take ~15s)…');
|
|
418
|
+
rpc('skills.manage', { action: 'browse', page: pageNum })
|
|
419
|
+
.then(ctx.guarded(r => {
|
|
420
|
+
const items = r.items ?? [];
|
|
421
|
+
if (!items.length) {
|
|
422
|
+
return sys(`no skills on page ${pageNum}${r.total ? ` (total ${r.total})` : ''}`);
|
|
423
|
+
}
|
|
424
|
+
const rows = items.map(s => [
|
|
425
|
+
s.trust ? `${s.name} · ${s.trust}` : s.name,
|
|
426
|
+
String(s.description ?? '').slice(0, 160)
|
|
427
|
+
]);
|
|
428
|
+
const footer = [];
|
|
429
|
+
if (r.page && r.total_pages) {
|
|
430
|
+
footer.push(`page ${r.page} of ${r.total_pages}`);
|
|
431
|
+
}
|
|
432
|
+
if (r.total) {
|
|
433
|
+
footer.push(`${r.total} skills total`);
|
|
434
|
+
}
|
|
435
|
+
if (r.page && r.total_pages && r.page < r.total_pages) {
|
|
436
|
+
footer.push(`/skills browse ${r.page + 1} for more`);
|
|
437
|
+
}
|
|
438
|
+
panel(`Browse Skills${pageNum > 1 ? ` — p${pageNum}` : ''}`, [
|
|
439
|
+
{ rows },
|
|
440
|
+
...(footer.length ? [{ text: footer.join(' · ') }] : [])
|
|
441
|
+
]);
|
|
442
|
+
}))
|
|
443
|
+
.catch(ctx.guardedErr);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
runViaSlashWorker();
|
|
447
|
+
}
|
|
162
448
|
},
|
|
449
|
+
{
|
|
450
|
+
help: 'enable or disable tools (client-side history reset on change)',
|
|
451
|
+
name: 'tools',
|
|
452
|
+
run: (arg, ctx, cmd) => {
|
|
453
|
+
const [subcommand, ...names] = arg.trim().split(/\s+/).filter(Boolean);
|
|
454
|
+
if (subcommand !== 'disable' && subcommand !== 'enable') {
|
|
455
|
+
ctx.gateway.gw
|
|
456
|
+
.request('slash.exec', { command: cmd.slice(1), session_id: ctx.sid })
|
|
457
|
+
.then(r => {
|
|
458
|
+
if (ctx.stale()) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const body = r?.output || '/tools: no output';
|
|
462
|
+
const text = r?.warning ? `warning: ${r.warning}\n${body}` : body;
|
|
463
|
+
const long = text.length > 180 || text.split('\n').filter(Boolean).length > 2;
|
|
464
|
+
long ? ctx.transcript.page(text, 'Tools') : ctx.transcript.sys(text);
|
|
465
|
+
})
|
|
466
|
+
.catch(ctx.guardedErr);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (!names.length) {
|
|
470
|
+
ctx.transcript.sys(`usage: /tools ${subcommand} <name> [name ...]`);
|
|
471
|
+
ctx.transcript.sys(`built-in toolset: /tools ${subcommand} web`);
|
|
472
|
+
ctx.transcript.sys(`MCP tool: /tools ${subcommand} github:create_issue`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
ctx.gateway
|
|
476
|
+
.rpc('tools.configure', { action: subcommand, names, session_id: ctx.sid })
|
|
477
|
+
.then(ctx.guarded(r => {
|
|
478
|
+
if (r.info) {
|
|
479
|
+
ctx.session.setSessionStartedAt(Date.now());
|
|
480
|
+
ctx.session.resetVisibleHistory(r.info);
|
|
481
|
+
}
|
|
482
|
+
if (r.changed?.length) {
|
|
483
|
+
ctx.transcript.sys(`${subcommand === 'disable' ? 'disabled' : 'enabled'}: ${r.changed.join(', ')}`);
|
|
484
|
+
}
|
|
485
|
+
if (r.unknown?.length) {
|
|
486
|
+
ctx.transcript.sys(`unknown toolsets: ${r.unknown.join(', ')}`);
|
|
487
|
+
}
|
|
488
|
+
if (r.missing_servers?.length) {
|
|
489
|
+
ctx.transcript.sys(`missing MCP servers: ${r.missing_servers.join(', ')}`);
|
|
490
|
+
}
|
|
491
|
+
if (r.reset) {
|
|
492
|
+
ctx.transcript.sys('session reset. new tool configuration is active.');
|
|
493
|
+
}
|
|
494
|
+
}))
|
|
495
|
+
.catch(ctx.guardedErr);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
163
498
|
];
|