cvc-tui 0.4.4 → 0.4.7
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 +512 -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 +638 -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 +122 -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 -70236
- 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/localSessionInfo.js +116 -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,287 @@
|
|
|
1
|
+
const ROOT_KEY = '__root__';
|
|
2
|
+
/**
|
|
3
|
+
* Reconstruct the subagent spawn tree from a flat event-ordered list.
|
|
4
|
+
*
|
|
5
|
+
* Grouping is by `parentId`; a missing `parentId` (or one pointing at an
|
|
6
|
+
* unknown subagent) is treated as a top-level spawn of the current turn.
|
|
7
|
+
* Children within a parent are sorted by `depth` then `index` — same key
|
|
8
|
+
* used in `turnController.upsertSubagent`, so render order matches spawn
|
|
9
|
+
* order regardless of network reordering of gateway events.
|
|
10
|
+
*
|
|
11
|
+
* Older gateways omit `parentId`; every subagent is then a top-level node
|
|
12
|
+
* and the tree renders flat — matching pre-observability behaviour.
|
|
13
|
+
*/
|
|
14
|
+
export function buildSubagentTree(items) {
|
|
15
|
+
if (!items.length) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const byParent = new Map();
|
|
19
|
+
const known = new Set();
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
known.add(item.id);
|
|
22
|
+
}
|
|
23
|
+
for (const item of items) {
|
|
24
|
+
const parentKey = item.parentId && known.has(item.parentId) ? item.parentId : ROOT_KEY;
|
|
25
|
+
const bucket = byParent.get(parentKey) ?? [];
|
|
26
|
+
bucket.push(item);
|
|
27
|
+
byParent.set(parentKey, bucket);
|
|
28
|
+
}
|
|
29
|
+
for (const bucket of byParent.values()) {
|
|
30
|
+
bucket.sort((a, b) => a.depth - b.depth || a.index - b.index);
|
|
31
|
+
}
|
|
32
|
+
const build = (item) => {
|
|
33
|
+
const kids = byParent.get(item.id) ?? [];
|
|
34
|
+
const children = kids.map(build);
|
|
35
|
+
return { aggregate: aggregate(item, children), children, item };
|
|
36
|
+
};
|
|
37
|
+
return (byParent.get(ROOT_KEY) ?? []).map(build);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Roll up counts for a node's whole subtree. Kept pure so the live view
|
|
41
|
+
* and the post-hoc replay can share the same renderer unchanged.
|
|
42
|
+
*
|
|
43
|
+
* `hotness` = tools per second across the subtree — a crude proxy for
|
|
44
|
+
* "how much work is happening in this branch". Used to colour tree rails
|
|
45
|
+
* in the overlay / inline view so the eye spots the expensive branch.
|
|
46
|
+
*/
|
|
47
|
+
export function aggregate(item, children) {
|
|
48
|
+
let totalTools = item.toolCount ?? 0;
|
|
49
|
+
let totalDuration = item.durationSeconds ?? 0;
|
|
50
|
+
let descendantCount = 0;
|
|
51
|
+
let activeCount = isRunning(item) ? 1 : 0;
|
|
52
|
+
let maxDepthFromHere = 0;
|
|
53
|
+
let inputTokens = item.inputTokens ?? 0;
|
|
54
|
+
let outputTokens = item.outputTokens ?? 0;
|
|
55
|
+
let costUsd = item.costUsd ?? 0;
|
|
56
|
+
let filesTouched = (item.filesRead?.length ?? 0) + (item.filesWritten?.length ?? 0);
|
|
57
|
+
for (const child of children) {
|
|
58
|
+
totalTools += child.aggregate.totalTools;
|
|
59
|
+
totalDuration += child.aggregate.totalDuration;
|
|
60
|
+
descendantCount += child.aggregate.descendantCount + 1;
|
|
61
|
+
activeCount += child.aggregate.activeCount;
|
|
62
|
+
maxDepthFromHere = Math.max(maxDepthFromHere, child.aggregate.maxDepthFromHere + 1);
|
|
63
|
+
inputTokens += child.aggregate.inputTokens;
|
|
64
|
+
outputTokens += child.aggregate.outputTokens;
|
|
65
|
+
costUsd += child.aggregate.costUsd;
|
|
66
|
+
filesTouched += child.aggregate.filesTouched;
|
|
67
|
+
}
|
|
68
|
+
const hotness = totalDuration > 0 ? totalTools / totalDuration : 0;
|
|
69
|
+
return {
|
|
70
|
+
activeCount,
|
|
71
|
+
costUsd,
|
|
72
|
+
descendantCount,
|
|
73
|
+
filesTouched,
|
|
74
|
+
hotness,
|
|
75
|
+
inputTokens,
|
|
76
|
+
maxDepthFromHere,
|
|
77
|
+
outputTokens,
|
|
78
|
+
totalDuration,
|
|
79
|
+
totalTools
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Count of subagents at each depth level, indexed by depth (0 = top level).
|
|
84
|
+
* Drives the inline sparkline (`▁▃▇▅`) and the status-bar HUD.
|
|
85
|
+
*/
|
|
86
|
+
export function widthByDepth(tree) {
|
|
87
|
+
const widths = [];
|
|
88
|
+
const walk = (nodes, depth) => {
|
|
89
|
+
if (!nodes.length) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
widths[depth] = (widths[depth] ?? 0) + nodes.length;
|
|
93
|
+
for (const node of nodes) {
|
|
94
|
+
walk(node.children, depth + 1);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
walk(tree, 0);
|
|
98
|
+
return widths;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Flat totals across the full tree — feeds the summary chip header.
|
|
102
|
+
*/
|
|
103
|
+
export function treeTotals(tree) {
|
|
104
|
+
let totalTools = 0;
|
|
105
|
+
let totalDuration = 0;
|
|
106
|
+
let descendantCount = 0;
|
|
107
|
+
let activeCount = 0;
|
|
108
|
+
let maxDepthFromHere = 0;
|
|
109
|
+
let inputTokens = 0;
|
|
110
|
+
let outputTokens = 0;
|
|
111
|
+
let costUsd = 0;
|
|
112
|
+
let filesTouched = 0;
|
|
113
|
+
for (const node of tree) {
|
|
114
|
+
totalTools += node.aggregate.totalTools;
|
|
115
|
+
totalDuration += node.aggregate.totalDuration;
|
|
116
|
+
descendantCount += node.aggregate.descendantCount + 1;
|
|
117
|
+
activeCount += node.aggregate.activeCount;
|
|
118
|
+
maxDepthFromHere = Math.max(maxDepthFromHere, node.aggregate.maxDepthFromHere + 1);
|
|
119
|
+
inputTokens += node.aggregate.inputTokens;
|
|
120
|
+
outputTokens += node.aggregate.outputTokens;
|
|
121
|
+
costUsd += node.aggregate.costUsd;
|
|
122
|
+
filesTouched += node.aggregate.filesTouched;
|
|
123
|
+
}
|
|
124
|
+
const hotness = totalDuration > 0 ? totalTools / totalDuration : 0;
|
|
125
|
+
return {
|
|
126
|
+
activeCount,
|
|
127
|
+
costUsd,
|
|
128
|
+
descendantCount,
|
|
129
|
+
filesTouched,
|
|
130
|
+
hotness,
|
|
131
|
+
inputTokens,
|
|
132
|
+
maxDepthFromHere,
|
|
133
|
+
outputTokens,
|
|
134
|
+
totalDuration,
|
|
135
|
+
totalTools
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Flatten the tree into visit order — useful for keyboard navigation and
|
|
140
|
+
* for "kill subtree" walks that fire one RPC per descendant.
|
|
141
|
+
*/
|
|
142
|
+
export function flattenTree(tree) {
|
|
143
|
+
const out = [];
|
|
144
|
+
const walk = (nodes) => {
|
|
145
|
+
for (const node of nodes) {
|
|
146
|
+
out.push(node);
|
|
147
|
+
walk(node.children);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
walk(tree);
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Collect every descendant's id for a given node (excluding the node itself).
|
|
155
|
+
*/
|
|
156
|
+
export function descendantIds(node) {
|
|
157
|
+
const ids = [];
|
|
158
|
+
const walk = (children) => {
|
|
159
|
+
for (const child of children) {
|
|
160
|
+
ids.push(child.item.id);
|
|
161
|
+
walk(child.children);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
walk(node.children);
|
|
165
|
+
return ids;
|
|
166
|
+
}
|
|
167
|
+
export function isRunning(item) {
|
|
168
|
+
return item.status === 'running' || item.status === 'queued';
|
|
169
|
+
}
|
|
170
|
+
const SPARK_RAMP = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
|
171
|
+
/**
|
|
172
|
+
* 8-step unicode bar sparkline from a positive-integer array. Zeroes render
|
|
173
|
+
* as spaces so a sparse tree doesn't read as equal activity at every depth.
|
|
174
|
+
*/
|
|
175
|
+
export function sparkline(values) {
|
|
176
|
+
if (!values.length) {
|
|
177
|
+
return '';
|
|
178
|
+
}
|
|
179
|
+
const max = Math.max(...values);
|
|
180
|
+
if (max <= 0) {
|
|
181
|
+
return ' '.repeat(values.length);
|
|
182
|
+
}
|
|
183
|
+
return values
|
|
184
|
+
.map(v => {
|
|
185
|
+
if (v <= 0) {
|
|
186
|
+
return ' ';
|
|
187
|
+
}
|
|
188
|
+
const idx = Math.min(SPARK_RAMP.length - 1, Math.max(0, Math.ceil((v / max) * (SPARK_RAMP.length - 1))));
|
|
189
|
+
return SPARK_RAMP[idx];
|
|
190
|
+
})
|
|
191
|
+
.join('');
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Format totals into a compact one-line summary: `d2 · 7 agents · 124 tools · 2m 14s`
|
|
195
|
+
*/
|
|
196
|
+
export function formatSummary(totals) {
|
|
197
|
+
const pieces = [`d${Math.max(0, totals.maxDepthFromHere)}`];
|
|
198
|
+
pieces.push(`${totals.descendantCount} agent${totals.descendantCount === 1 ? '' : 's'}`);
|
|
199
|
+
if (totals.totalTools > 0) {
|
|
200
|
+
pieces.push(`${totals.totalTools} tool${totals.totalTools === 1 ? '' : 's'}`);
|
|
201
|
+
}
|
|
202
|
+
if (totals.totalDuration > 0) {
|
|
203
|
+
pieces.push(fmtDuration(totals.totalDuration));
|
|
204
|
+
}
|
|
205
|
+
const tokens = totals.inputTokens + totals.outputTokens;
|
|
206
|
+
if (tokens > 0) {
|
|
207
|
+
pieces.push(`${fmtTokens(tokens)} tok`);
|
|
208
|
+
}
|
|
209
|
+
if (totals.costUsd > 0) {
|
|
210
|
+
pieces.push(fmtCost(totals.costUsd));
|
|
211
|
+
}
|
|
212
|
+
if (totals.activeCount > 0) {
|
|
213
|
+
pieces.push(`⚡${totals.activeCount}`);
|
|
214
|
+
}
|
|
215
|
+
return pieces.join(' · ');
|
|
216
|
+
}
|
|
217
|
+
/** Compact dollar amount: `$0.02`, `$1.34`, `$12.4` — never > 5 chars beyond the `$`. */
|
|
218
|
+
export function fmtCost(usd) {
|
|
219
|
+
if (!Number.isFinite(usd) || usd <= 0) {
|
|
220
|
+
return '';
|
|
221
|
+
}
|
|
222
|
+
if (usd < 0.01) {
|
|
223
|
+
return '<$0.01';
|
|
224
|
+
}
|
|
225
|
+
if (usd < 10) {
|
|
226
|
+
return `$${usd.toFixed(2)}`;
|
|
227
|
+
}
|
|
228
|
+
return `$${usd.toFixed(1)}`;
|
|
229
|
+
}
|
|
230
|
+
/** Compact token count: `12k`, `1.2k`, `542`. */
|
|
231
|
+
export function fmtTokens(n) {
|
|
232
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
233
|
+
return '0';
|
|
234
|
+
}
|
|
235
|
+
if (n < 1000) {
|
|
236
|
+
return String(Math.round(n));
|
|
237
|
+
}
|
|
238
|
+
if (n < 10_000) {
|
|
239
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
240
|
+
}
|
|
241
|
+
return `${Math.round(n / 1000)}k`;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* `Ns` / `Nm` / `Nm Ss` formatter for seconds. Shared with the agents
|
|
245
|
+
* overlay so the timeline + list + summary all speak the same dialect.
|
|
246
|
+
*/
|
|
247
|
+
export function fmtDuration(seconds) {
|
|
248
|
+
if (seconds < 60) {
|
|
249
|
+
return `${Math.max(0, Math.round(seconds))}s`;
|
|
250
|
+
}
|
|
251
|
+
const m = Math.floor(seconds / 60);
|
|
252
|
+
const s = Math.round(seconds - m * 60);
|
|
253
|
+
return s === 0 ? `${m}m` : `${m}m ${s}s`;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* A subagent is top-level if it has no `parentId`, or its parent isn't in
|
|
257
|
+
* the same snapshot (orphaned by a pruned mid-flight root). Same rule
|
|
258
|
+
* `buildSubagentTree` uses — keep call sites consistent across the live
|
|
259
|
+
* view, disk label, and diff pane.
|
|
260
|
+
*/
|
|
261
|
+
export function topLevelSubagents(items) {
|
|
262
|
+
const ids = new Set(items.map(s => s.id));
|
|
263
|
+
return items.filter(s => !s.parentId || !ids.has(s.parentId));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Normalize a node's hotness into a palette index 0..N-1 where N = buckets.
|
|
267
|
+
* Higher hotness = "hotter" colour. Normalized against the tree's peak hotness
|
|
268
|
+
* so a uniformly slow tree still shows gradient across its busiest branches.
|
|
269
|
+
*/
|
|
270
|
+
export function hotnessBucket(hotness, peakHotness, buckets) {
|
|
271
|
+
if (!Number.isFinite(hotness) || hotness <= 0 || peakHotness <= 0 || buckets <= 1) {
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
const ratio = Math.min(1, hotness / peakHotness);
|
|
275
|
+
return Math.min(buckets - 1, Math.max(0, Math.round(ratio * (buckets - 1))));
|
|
276
|
+
}
|
|
277
|
+
export function peakHotness(tree) {
|
|
278
|
+
let peak = 0;
|
|
279
|
+
const walk = (nodes) => {
|
|
280
|
+
for (const node of nodes) {
|
|
281
|
+
peak = Math.max(peak, node.aggregate.hotness);
|
|
282
|
+
walk(node.children);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
walk(tree);
|
|
286
|
+
return peak;
|
|
287
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const KW = (s) => new Set(s.split(/\s+/).filter(Boolean));
|
|
2
|
+
const TS = KW(`
|
|
3
|
+
abstract as async await break case catch class const continue debugger default delete do else enum export extends
|
|
4
|
+
false finally for from function get if implements import in instanceof interface is let new null of package private
|
|
5
|
+
protected public readonly return set static super switch this throw true try type typeof undefined var void while
|
|
6
|
+
with yield
|
|
7
|
+
`);
|
|
8
|
+
const PY = KW(`
|
|
9
|
+
False None True and as assert async await break class continue def del elif else except finally for from global if
|
|
10
|
+
import in is lambda nonlocal not or pass raise return try while with yield
|
|
11
|
+
`);
|
|
12
|
+
const SH = KW(`
|
|
13
|
+
if then else elif fi for in do done while until case esac function return break continue local export readonly
|
|
14
|
+
declare typeset
|
|
15
|
+
`);
|
|
16
|
+
const GO = KW(`
|
|
17
|
+
break case chan const continue default defer else fallthrough for func go goto if import interface map package range
|
|
18
|
+
return select struct switch type var nil true false
|
|
19
|
+
`);
|
|
20
|
+
const RUST = KW(`
|
|
21
|
+
as async await break const continue crate dyn else enum extern false fn for if impl in let loop match mod move mut
|
|
22
|
+
pub ref return self Self static struct super trait true type unsafe use where while yield
|
|
23
|
+
`);
|
|
24
|
+
const SQL = KW(`
|
|
25
|
+
select from where and or not in is null as by group order limit offset insert into values update set delete create
|
|
26
|
+
table drop alter add column primary key foreign references join left right inner outer on
|
|
27
|
+
`);
|
|
28
|
+
const LANGS = {
|
|
29
|
+
go: { comment: '//', keywords: GO },
|
|
30
|
+
json: { comment: null, keywords: KW('true false null') },
|
|
31
|
+
py: { comment: '#', keywords: PY },
|
|
32
|
+
rust: { comment: '//', keywords: RUST },
|
|
33
|
+
sh: { comment: '#', keywords: SH },
|
|
34
|
+
sql: { comment: '--', keywords: SQL },
|
|
35
|
+
ts: { comment: '//', keywords: TS },
|
|
36
|
+
yaml: { comment: '#', keywords: KW('true false null yes no on off') }
|
|
37
|
+
};
|
|
38
|
+
const ALIAS = {
|
|
39
|
+
bash: 'sh',
|
|
40
|
+
javascript: 'ts',
|
|
41
|
+
js: 'ts',
|
|
42
|
+
jsx: 'ts',
|
|
43
|
+
python: 'py',
|
|
44
|
+
rs: 'rust',
|
|
45
|
+
shell: 'sh',
|
|
46
|
+
tsx: 'ts',
|
|
47
|
+
typescript: 'ts',
|
|
48
|
+
yml: 'yaml',
|
|
49
|
+
zsh: 'sh'
|
|
50
|
+
};
|
|
51
|
+
const resolve = (lang) => LANGS[ALIAS[lang] ?? lang] ?? null;
|
|
52
|
+
export const isHighlightable = (lang) => resolve(lang) !== null;
|
|
53
|
+
const TOKEN_RE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`|\b\d+(?:\.\d+)?\b|[A-Za-z_$][\w$]*/g;
|
|
54
|
+
export function highlightLine(line, lang, t) {
|
|
55
|
+
const spec = resolve(lang);
|
|
56
|
+
if (!spec) {
|
|
57
|
+
return [['', line]];
|
|
58
|
+
}
|
|
59
|
+
if (spec.comment && line.trimStart().startsWith(spec.comment)) {
|
|
60
|
+
return [[t.color.muted, line]];
|
|
61
|
+
}
|
|
62
|
+
const tokens = [];
|
|
63
|
+
let last = 0;
|
|
64
|
+
for (const m of line.matchAll(TOKEN_RE)) {
|
|
65
|
+
const start = m.index ?? 0;
|
|
66
|
+
if (start > last) {
|
|
67
|
+
tokens.push(['', line.slice(last, start)]);
|
|
68
|
+
}
|
|
69
|
+
const tok = m[0];
|
|
70
|
+
const ch = tok[0];
|
|
71
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
72
|
+
tokens.push([t.color.accent, tok]);
|
|
73
|
+
}
|
|
74
|
+
else if (ch >= '0' && ch <= '9') {
|
|
75
|
+
tokens.push([t.color.text, tok]);
|
|
76
|
+
}
|
|
77
|
+
else if (spec.keywords.has(tok)) {
|
|
78
|
+
tokens.push([t.color.border, tok]);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
tokens.push(['', tok]);
|
|
82
|
+
}
|
|
83
|
+
last = start + tok.length;
|
|
84
|
+
}
|
|
85
|
+
if (last < line.length) {
|
|
86
|
+
tokens.push(['', line.slice(last)]);
|
|
87
|
+
}
|
|
88
|
+
return tokens;
|
|
89
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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 { writeSync } from 'node:fs';
|
|
6
|
+
export const TERMINAL_MODE_RESET = '\x1b[0\'z' + // DEC locator reporting
|
|
7
|
+
'\x1b[0\'{' + // selectable locator events
|
|
8
|
+
'\x1b[?2029l' + // passive mouse
|
|
9
|
+
'\x1b[?1016l' + // SGR-pixels mouse
|
|
10
|
+
'\x1b[?1015l' + // urxvt decimal mouse
|
|
11
|
+
'\x1b[?1006l' + // SGR mouse
|
|
12
|
+
'\x1b[?1005l' + // UTF-8 extended mouse
|
|
13
|
+
'\x1b[?1003l' + // any-motion mouse
|
|
14
|
+
'\x1b[?1002l' + // button-motion mouse
|
|
15
|
+
'\x1b[?1001l' + // highlight mouse
|
|
16
|
+
'\x1b[?1000l' + // click mouse
|
|
17
|
+
'\x1b[?9l' + // X10 mouse
|
|
18
|
+
'\x1b[?1004l' + // focus events
|
|
19
|
+
'\x1b[?2004l' + // bracketed paste
|
|
20
|
+
'\x1b[?1049l' + // alternate screen
|
|
21
|
+
'\x1b[<u' + // kitty keyboard
|
|
22
|
+
'\x1b[>4m' + // modifyOtherKeys
|
|
23
|
+
'\x1b[0m' + // attributes
|
|
24
|
+
'\x1b[?25h'; // cursor visible
|
|
25
|
+
export function resetTerminalModes(stream = process.stdout) {
|
|
26
|
+
if (!stream.isTTY) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const fd = typeof stream.fd === 'number' ? stream.fd : stream === process.stdout ? 1 : undefined;
|
|
30
|
+
if (fd !== undefined) {
|
|
31
|
+
try {
|
|
32
|
+
writeSync(fd, TERMINAL_MODE_RESET);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Fall through to stream.write for mocked or unusual TTY streams.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
stream.write(TERMINAL_MODE_RESET);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 { detectVSCodeLikeTerminal, isRemoteShellSession, shouldPromptForTerminalSetup } from './terminalSetup.js';
|
|
6
|
+
export function detectMacTerminalContext(env = process.env) {
|
|
7
|
+
const termProgram = env['TERM_PROGRAM'] ?? '';
|
|
8
|
+
return {
|
|
9
|
+
isAppleTerminal: termProgram === 'Apple_Terminal' || !!env['TERM_SESSION_ID'],
|
|
10
|
+
isRemote: isRemoteShellSession(env),
|
|
11
|
+
isTmux: !!env['TMUX'],
|
|
12
|
+
vscodeLike: detectVSCodeLikeTerminal(env)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export async function terminalParityHints(env = process.env, options) {
|
|
16
|
+
const ctx = detectMacTerminalContext(env);
|
|
17
|
+
const hints = [];
|
|
18
|
+
if (ctx.vscodeLike &&
|
|
19
|
+
(await shouldPromptForTerminalSetup({ env, fileOps: options?.fileOps, homeDir: options?.homeDir }))) {
|
|
20
|
+
hints.push({
|
|
21
|
+
key: 'ide-setup',
|
|
22
|
+
tone: 'info',
|
|
23
|
+
message: `Detected ${ctx.vscodeLike} terminal · run /terminal-setup for best Cmd+Enter / undo parity`
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (ctx.isAppleTerminal) {
|
|
27
|
+
hints.push({
|
|
28
|
+
key: 'apple-terminal',
|
|
29
|
+
tone: 'warn',
|
|
30
|
+
message: 'Apple Terminal detected · use /paste for image-only clipboard fallback, and try Ctrl+A / Ctrl+E / Ctrl+U if Cmd+←/→/⌫ gets rewritten'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (ctx.isTmux) {
|
|
34
|
+
hints.push({
|
|
35
|
+
key: 'tmux',
|
|
36
|
+
tone: 'warn',
|
|
37
|
+
message: 'tmux detected · clipboard copy/paste uses passthrough when available; allow-passthrough improves OSC52 reliability'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (ctx.isRemote) {
|
|
41
|
+
hints.push({
|
|
42
|
+
key: 'remote',
|
|
43
|
+
tone: 'warn',
|
|
44
|
+
message: 'SSH session detected · text clipboard can bridge via OSC52, but image clipboard and local screenshot paths still depend on the machine running Hermes'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return hints;
|
|
48
|
+
}
|