@volter/supercode-terminal 0.2.30

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/package.json ADDED
@@ -0,0 +1,156 @@
1
+ {
2
+ "name": "@volter/supercode-terminal",
3
+ "version": "0.2.30",
4
+ "type": "module",
5
+ "description": "Optional leak-safe terminal and tmux host adapter for Volter Harness frontends",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./index.d.ts",
9
+ "import": "./index.mjs"
10
+ },
11
+ "./client": {
12
+ "types": "./client.d.ts",
13
+ "import": "./client.mjs"
14
+ },
15
+ "./ui": {
16
+ "types": "./ui.d.ts",
17
+ "import": "./ui.mjs"
18
+ },
19
+ "./react": {
20
+ "types": "./react/ui.d.ts",
21
+ "import": "./react/ui.mjs"
22
+ },
23
+ "./ui/styles.css": "./styles.css",
24
+ "./stream": {
25
+ "types": "./stream.d.ts",
26
+ "import": "./stream.mjs"
27
+ },
28
+ "./react/stream": {
29
+ "types": "./react/stream.d.ts",
30
+ "import": "./react/stream.mjs"
31
+ },
32
+ "./docker-exec": {
33
+ "types": "./substrate/docker-exec.d.ts",
34
+ "import": "./substrate/docker-exec.js"
35
+ },
36
+ "./substrate/attach.js": {
37
+ "types": "./substrate/attach.d.ts",
38
+ "import": "./substrate/attach.js"
39
+ },
40
+ "./substrate/client.js": {
41
+ "types": "./substrate/client.d.ts",
42
+ "import": "./substrate/client.js"
43
+ },
44
+ "./substrate/local-terminal.js": {
45
+ "types": "./substrate/local-terminal.d.ts",
46
+ "import": "./substrate/local-terminal.js"
47
+ },
48
+ "./substrate/native-host.js": {
49
+ "types": "./substrate/native-host.d.ts",
50
+ "import": "./substrate/native-host.js"
51
+ },
52
+ "./substrate/session-lifecycle.js": {
53
+ "types": "./substrate/session-lifecycle.d.ts",
54
+ "import": "./substrate/session-lifecycle.js"
55
+ },
56
+ "./substrate/tmux-stream.js": {
57
+ "types": "./substrate/tmux-stream.d.ts",
58
+ "import": "./substrate/tmux-stream.js"
59
+ },
60
+ "./substrate/tmux.js": {
61
+ "types": "./substrate/tmux.d.ts",
62
+ "import": "./substrate/tmux.js"
63
+ },
64
+ "./substrate/internal/exec.js": {
65
+ "types": "./substrate/internal/exec.d.ts",
66
+ "import": "./substrate/internal/exec.js"
67
+ },
68
+ "./substrate/internal/process-tree.js": {
69
+ "types": "./substrate/internal/process-tree.d.ts",
70
+ "import": "./substrate/internal/process-tree.js"
71
+ }
72
+ },
73
+ "types": "./index.d.ts",
74
+ "files": [
75
+ "client.d.ts",
76
+ "client.mjs",
77
+ "index.d.ts",
78
+ "index.mjs",
79
+ "react",
80
+ "ui.d.ts",
81
+ "ui.mjs",
82
+ "styles.css",
83
+ "README.md",
84
+ "CAPABILITIES.md",
85
+ "stream.mjs",
86
+ "stream.d.ts",
87
+ "substrate",
88
+ "LICENSE-SUBSTRATE",
89
+ "NOTICE-SUBSTRATE"
90
+ ],
91
+ "scripts": {
92
+ "build": "npm run build:substrate && node scripts/build.mjs",
93
+ "test": "npm run build && node --test test/*.test.mjs",
94
+ "prepack": "npm test",
95
+ "build:substrate": "tsc -p tsconfig.substrate.json"
96
+ },
97
+ "peerDependencies": {
98
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1 || ^0.14.1",
99
+ "@xterm/addon-fit": "^0.11.0",
100
+ "@xterm/addon-search": "^0.16.0",
101
+ "@xterm/addon-web-links": "^0.12.0",
102
+ "@xterm/xterm": "^6.0.0",
103
+ "preact": ">=10.19.0 <11",
104
+ "react": ">=18 <20",
105
+ "ws": "^8.21.0",
106
+ "@volter/supercode-harness-sdk": "^0.3.18"
107
+ },
108
+ "peerDependenciesMeta": {
109
+ "@homebridge/node-pty-prebuilt-multiarch": {
110
+ "optional": true
111
+ },
112
+ "@xterm/addon-fit": {
113
+ "optional": true
114
+ },
115
+ "@xterm/addon-search": {
116
+ "optional": true
117
+ },
118
+ "@xterm/addon-web-links": {
119
+ "optional": true
120
+ },
121
+ "@xterm/xterm": {
122
+ "optional": true
123
+ },
124
+ "preact": {
125
+ "optional": true
126
+ },
127
+ "react": {
128
+ "optional": true
129
+ },
130
+ "ws": {
131
+ "optional": true
132
+ },
133
+ "@volter/supercode-harness-sdk": {
134
+ "optional": true
135
+ }
136
+ },
137
+ "devDependencies": {
138
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
139
+ "@types/node": "^22.20.1",
140
+ "@types/ws": "^8.18.1",
141
+ "@volter/supercode-harness-sdk": "npm:@volter-ai-dev/supercode-harness-sdk@^0.3.18",
142
+ "@xterm/addon-fit": "^0.11.0",
143
+ "@xterm/addon-search": "^0.16.0",
144
+ "@xterm/addon-web-links": "^0.12.0",
145
+ "@xterm/xterm": "^6.0.0",
146
+ "esbuild": "^0.25.0",
147
+ "preact": "^10.19.0",
148
+ "react": "^19.2.3",
149
+ "typescript": "^5.9.3",
150
+ "ws": "^8.21.0"
151
+ },
152
+ "engines": {
153
+ "node": ">=20"
154
+ },
155
+ "license": "MIT OR Apache-2.0"
156
+ }
@@ -0,0 +1,3 @@
1
+ import { TerminalViewer, type TerminalConnector } from './ui.js';
2
+ export type { TerminalConnector, TerminalStreamHandlers } from './ui.js';
3
+ export const TerminalStreamViewer: (props: Parameters<typeof TerminalViewer>[0] & { connect: TerminalConnector }) => ReturnType<typeof TerminalViewer>;
@@ -0,0 +1,446 @@
1
+ // src/ui.jsx
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ var DEFAULT_LABELS = Object.freeze({
5
+ back: "Back to terminals",
6
+ control: "Control",
7
+ empty: "No terminal sessions are running.",
8
+ open: "Open",
9
+ refresh: "Refresh",
10
+ // what a session with no observed cwd or command is
11
+ session: "tmux session",
12
+ stop: "Stop",
13
+ subtitle: "Local tmux sessions",
14
+ title: "Terminals",
15
+ view: "View"
16
+ });
17
+ var WORKSPACE_LABELS = Object.freeze({
18
+ ...DEFAULT_LABELS,
19
+ attach: "Open session",
20
+ back: "Back to chats",
21
+ closeTab: "Detach terminal",
22
+ newTerminal: "New terminal",
23
+ noTabs: "Open a local session or start a new coding agent.",
24
+ open: "Open locally",
25
+ sessions: "Sessions",
26
+ fitViewport: "Fit terminal to window",
27
+ scaleViewport: "Keep terminal grid scaled",
28
+ stop: "Stop session",
29
+ terminal: "Terminal"
30
+ });
31
+ var TERMINAL_THEME_SLOTS = ["foreground", "cursor", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "brightBlack", "brightRed", "brightGreen", "brightYellow", "brightBlue", "brightMagenta", "brightCyan", "brightWhite"];
32
+ function terminalTheme(element) {
33
+ const probe = element.ownerDocument.createElement("span");
34
+ probe.hidden = true;
35
+ element.append(probe);
36
+ const theme = { background: "rgba(0, 0, 0, 0)" };
37
+ for (const slot of TERMINAL_THEME_SLOTS) {
38
+ const name = `--sctui-terminal-${slot.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`;
39
+ probe.style.color = `var(${name}, var(${name}-default))`;
40
+ theme[slot] = getComputedStyle(probe).color;
41
+ }
42
+ probe.remove();
43
+ return theme;
44
+ }
45
+ function TerminalStreamViewer({ active = true, attachment, connect, convertEol = false, onReady, onActivity, onBell, onOpenLink, onRetry, onStatus, onTitle }) {
46
+ const root = useRef(null);
47
+ const runtime = useRef(null);
48
+ const searchInput = useRef(null);
49
+ const pasteDialog = useRef(null);
50
+ const bellTimer = useRef(null);
51
+ const activeRef = useRef(active);
52
+ const callbacks = useRef({});
53
+ const [status, setStatus] = useState("Loading terminal\u2026");
54
+ const [bellVisible, setBellVisible] = useState(false);
55
+ const [findOpen, setFindOpen] = useState(false);
56
+ const [findQuery, setFindQuery] = useState("");
57
+ const [findFound, setFindFound] = useState(null);
58
+ const [helpOpen, setHelpOpen] = useState(false);
59
+ const [historyOpen, setHistoryOpen] = useState(false);
60
+ const [pasteHistory, setPasteHistory] = useState([]);
61
+ const [pendingPaste, setPendingPaste] = useState(null);
62
+ activeRef.current = active;
63
+ callbacks.current = { onActivity, onBell, onOpenLink, onRetry, onStatus, onTitle };
64
+ const publishStatus = (next) => {
65
+ setStatus(next);
66
+ callbacks.current.onStatus?.(next);
67
+ };
68
+ const focusTerminal = () => runtime.current?.terminal.focus();
69
+ const closeFind = () => {
70
+ runtime.current?.search.clearDecorations();
71
+ setFindOpen(false);
72
+ queueMicrotask(focusTerminal);
73
+ };
74
+ const find = (direction = "next", query = findQuery) => {
75
+ if (!query || !runtime.current) return;
76
+ const found = runtime.current.search[direction === "previous" ? "findPrevious" : "findNext"](query, {
77
+ incremental: direction === "next"
78
+ });
79
+ setFindFound(found);
80
+ };
81
+ const openFind = () => {
82
+ setHelpOpen(false);
83
+ setHistoryOpen(false);
84
+ setFindOpen(true);
85
+ queueMicrotask(() => searchInput.current?.focus());
86
+ };
87
+ const sendPaste = (value, remember = true) => {
88
+ runtime.current?.terminal.paste(value);
89
+ if (!remember || value.length > 5e4) return;
90
+ setPasteHistory((current) => [value, ...current.filter((item) => item !== value)].slice(0, 20));
91
+ };
92
+ const proposePaste = (value) => {
93
+ setHistoryOpen(false);
94
+ if (value.length > 1e3 || /[\r\n]/.test(value)) setPendingPaste(value);
95
+ else sendPaste(value);
96
+ };
97
+ const saveContents = () => {
98
+ const terminal = runtime.current?.terminal;
99
+ if (!terminal) return;
100
+ const buffer = terminal.buffer.normal;
101
+ const lines = [];
102
+ for (let index = 0; index < buffer.length; index += 1) {
103
+ lines.push(buffer.getLine(index)?.translateToString(true) ?? "");
104
+ }
105
+ const blob = new Blob([`${lines.join("\n")}
106
+ `], { type: "text/plain;charset=utf-8" });
107
+ const url = URL.createObjectURL(blob);
108
+ const link = document.createElement("a");
109
+ const name = (attachment.sessionId ?? "session").replaceAll(/[^a-z0-9_-]/gi, "-").slice(0, 80);
110
+ link.download = `terminal-${name}.txt`;
111
+ link.href = url;
112
+ link.click();
113
+ queueMicrotask(() => URL.revokeObjectURL(url));
114
+ };
115
+ useEffect(() => {
116
+ if (!root.current) return;
117
+ let disposed = false;
118
+ let disposeRuntime = () => void 0;
119
+ void Promise.all([
120
+ import("@xterm/addon-fit"),
121
+ import("@xterm/addon-search"),
122
+ import("@xterm/addon-web-links"),
123
+ import("@xterm/xterm")
124
+ ]).then(([{ FitAddon }, { SearchAddon }, { WebLinksAddon }, { Terminal }]) => {
125
+ if (disposed || !root.current) return;
126
+ const terminal = new Terminal({
127
+ allowTransparency: true,
128
+ convertEol,
129
+ cursorBlink: attachment.mode === "control",
130
+ disableStdin: attachment.mode !== "control",
131
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
132
+ fontSize: 12,
133
+ scrollback: 5e3,
134
+ theme: terminalTheme(root.current)
135
+ });
136
+ const fit = new FitAddon();
137
+ const search = new SearchAddon();
138
+ terminal.loadAddon(fit);
139
+ terminal.loadAddon(search);
140
+ terminal.loadAddon(new WebLinksAddon((_event, uri) => {
141
+ if (callbacks.current.onOpenLink) callbacks.current.onOpenLink(uri);
142
+ else globalThis.open(uri, "_blank", "noopener,noreferrer");
143
+ }));
144
+ terminal.open(root.current);
145
+ fit.fit();
146
+ runtime.current = { search, terminal };
147
+ const title = terminal.onTitleChange((value) => {
148
+ const normalized = value.replaceAll(/[\u0000-\u001f\u007f]/g, " ").trim().slice(0, 160);
149
+ if (normalized) callbacks.current.onTitle?.(normalized);
150
+ });
151
+ const bell = terminal.onBell(() => {
152
+ clearTimeout(bellTimer.current);
153
+ setBellVisible(true);
154
+ bellTimer.current = setTimeout(() => setBellVisible(false), 350);
155
+ callbacks.current.onBell?.();
156
+ });
157
+ let connected = false;
158
+ const handlers = {
159
+ onOpen() {
160
+ connected = true;
161
+ publishStatus(attachment.mode === "control" ? "Live \xB7 interactive" : "Live \xB7 read-only");
162
+ },
163
+ onOutput(data) {
164
+ if (disposed) return;
165
+ terminal.write(data);
166
+ if (!activeRef.current) callbacks.current.onActivity?.();
167
+ },
168
+ onResize(cols, rows) {
169
+ if (!disposed) terminal.resize(cols, rows);
170
+ },
171
+ onError(message) {
172
+ if (!disposed) {
173
+ publishStatus(message);
174
+ terminal.writeln(`\r
175
+ ${message}`);
176
+ }
177
+ },
178
+ onExit() {
179
+ connected = false;
180
+ if (!disposed) publishStatus("Terminal exited");
181
+ },
182
+ onClose() {
183
+ connected = false;
184
+ if (!disposed) setStatus((current) => {
185
+ const next = current === "Terminal exited" ? current : "Terminal disconnected";
186
+ callbacks.current.onStatus?.(next);
187
+ return next;
188
+ });
189
+ }
190
+ };
191
+ const size = { cols: terminal.cols, rows: terminal.rows };
192
+ let connection;
193
+ try {
194
+ connection = connect(attachment, handlers, size);
195
+ } catch (error) {
196
+ bell.dispose();
197
+ title.dispose();
198
+ terminal.dispose();
199
+ runtime.current = null;
200
+ throw error;
201
+ }
202
+ let resizeFrame = 0;
203
+ let sentSize = size;
204
+ const refit = () => {
205
+ if (disposed || !connected || attachment.mode !== "control") return;
206
+ fit.fit();
207
+ if (terminal.cols === sentSize.cols && terminal.rows === sentSize.rows) return;
208
+ sentSize = { cols: terminal.cols, rows: terminal.rows };
209
+ connection.resize(terminal.cols, terminal.rows);
210
+ };
211
+ const resizeObserver = attachment.mode === "control" && globalThis.ResizeObserver ? new ResizeObserver(() => {
212
+ cancelAnimationFrame(resizeFrame);
213
+ resizeFrame = requestAnimationFrame(refit);
214
+ }) : null;
215
+ resizeObserver?.observe(root.current);
216
+ const input = attachment.mode === "control" ? terminal.onData((data) => connection.write(data)) : null;
217
+ onReady?.({ focus() {
218
+ refit();
219
+ terminal.focus();
220
+ } });
221
+ const focus = () => {
222
+ if (activeRef.current && attachment.mode === "control") terminal.focus();
223
+ };
224
+ root.current.addEventListener("pointerdown", focus);
225
+ const paste = (event) => {
226
+ if (attachment.mode !== "control") return;
227
+ const value = event.clipboardData?.getData("text/plain");
228
+ if (!value) return;
229
+ event.preventDefault();
230
+ proposePaste(value);
231
+ };
232
+ root.current.addEventListener("paste", paste, true);
233
+ if (active) requestAnimationFrame(focus);
234
+ disposeRuntime = () => {
235
+ root.current?.removeEventListener("pointerdown", focus);
236
+ root.current?.removeEventListener("paste", paste, true);
237
+ input?.dispose();
238
+ resizeObserver?.disconnect();
239
+ cancelAnimationFrame(resizeFrame);
240
+ bell.dispose();
241
+ title.dispose();
242
+ connection.close();
243
+ terminal.dispose();
244
+ runtime.current = null;
245
+ clearTimeout(bellTimer.current);
246
+ };
247
+ }).catch((error) => {
248
+ if (!disposed) publishStatus(error instanceof Error ? error.message : "Could not load the terminal viewer");
249
+ });
250
+ return () => {
251
+ disposed = true;
252
+ disposeRuntime();
253
+ };
254
+ }, [attachment.id, attachment.mode, connect, convertEol]);
255
+ useEffect(() => {
256
+ if (!active || attachment.mode !== "control") return;
257
+ const focus = () => root.current?.querySelector(".xterm-helper-textarea")?.focus({ preventScroll: true });
258
+ focus();
259
+ requestAnimationFrame(focus);
260
+ }, [active, attachment.mode]);
261
+ useEffect(() => {
262
+ if (!pendingPaste) return;
263
+ queueMicrotask(() => pasteDialog.current?.querySelector("button")?.focus({ preventScroll: true }));
264
+ }, [pendingPaste]);
265
+ const retryable = status === "Terminal disconnected" || status === "Terminal connection failed";
266
+ return /* @__PURE__ */ jsxs(
267
+ "div",
268
+ {
269
+ class: "sctui-viewer",
270
+ "data-bell": String(bellVisible),
271
+ onKeyDownCapture: (event) => {
272
+ if (pendingPaste) {
273
+ if (event.key === "Escape") {
274
+ event.preventDefault();
275
+ setPendingPaste(null);
276
+ queueMicrotask(focusTerminal);
277
+ } else if (event.key === "Tab") {
278
+ const buttons = [...pasteDialog.current?.querySelectorAll("button") ?? []];
279
+ const first = buttons.at(0);
280
+ const last = buttons.at(-1);
281
+ if (event.shiftKey && document.activeElement === first) {
282
+ event.preventDefault();
283
+ last?.focus();
284
+ } else if (!event.shiftKey && document.activeElement === last) {
285
+ event.preventDefault();
286
+ first?.focus();
287
+ }
288
+ }
289
+ return;
290
+ }
291
+ if (event.key === "Escape" && findOpen) {
292
+ event.preventDefault();
293
+ closeFind();
294
+ } else if (event.key === "Escape" && (helpOpen || historyOpen)) {
295
+ event.preventDefault();
296
+ setHelpOpen(false);
297
+ setHistoryOpen(false);
298
+ queueMicrotask(focusTerminal);
299
+ } else if (event.altKey && event.shiftKey && !event.ctrlKey && !event.metaKey && event.code === "KeyF") {
300
+ event.preventDefault();
301
+ openFind();
302
+ }
303
+ },
304
+ children: [
305
+ /* @__PURE__ */ jsxs("div", { class: "sctui-viewerbar", children: [
306
+ /* @__PURE__ */ jsxs("div", { class: "sctui-status", "data-live": String(status.startsWith("Live")), role: "status", children: [
307
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
308
+ status,
309
+ retryable && onRetry ? /* @__PURE__ */ jsx("button", { type: "button", onClick: onRetry, children: "Reconnect" }) : null
310
+ ] }),
311
+ /* @__PURE__ */ jsxs("div", { class: "sctui-tools", "aria-label": "Terminal tools", children: [
312
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-keyshortcuts": "Alt+Shift+F", "aria-label": "Find in terminal", title: `Find (${terminalShortcutLabel("F")})`, onClick: openFind, children: /* @__PURE__ */ jsx(SearchGlyph, {}) }),
313
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Paste history", "aria-expanded": historyOpen, disabled: !pasteHistory.length, title: "Paste history (memory only)", onClick: () => {
314
+ setHelpOpen(false);
315
+ setHistoryOpen((open) => !open);
316
+ }, children: /* @__PURE__ */ jsx(HistoryGlyph, {}) }),
317
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Save terminal contents", title: "Save terminal contents", onClick: saveContents, children: /* @__PURE__ */ jsx(DownloadGlyph, {}) }),
318
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Terminal shortcut help", "aria-expanded": helpOpen, title: "Keyboard shortcuts", onClick: () => {
319
+ setHistoryOpen(false);
320
+ setHelpOpen((open) => !open);
321
+ }, children: /* @__PURE__ */ jsx(HelpGlyph, {}) })
322
+ ] })
323
+ ] }),
324
+ findOpen ? /* @__PURE__ */ jsxs("form", { class: "sctui-find", onSubmit: (event) => {
325
+ event.preventDefault();
326
+ find(event.shiftKey ? "previous" : "next");
327
+ }, children: [
328
+ /* @__PURE__ */ jsx(SearchGlyph, {}),
329
+ /* @__PURE__ */ jsx("input", { ref: searchInput, value: findQuery, "aria-label": "Find", placeholder: "Find", onInput: (event) => {
330
+ const query = event.currentTarget.value;
331
+ setFindQuery(query);
332
+ if (query) find("next", query);
333
+ else {
334
+ runtime.current?.search.clearDecorations();
335
+ setFindFound(null);
336
+ }
337
+ } }),
338
+ /* @__PURE__ */ jsx("small", { children: findFound === null ? "" : findFound ? "Match" : "No results" }),
339
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Previous match", onClick: () => find("previous"), children: /* @__PURE__ */ jsx(UpGlyph, {}) }),
340
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Next match", onClick: () => find("next"), children: /* @__PURE__ */ jsx(DownGlyph, {}) }),
341
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Close find", onClick: closeFind, children: /* @__PURE__ */ jsx(CloseGlyph, {}) })
342
+ ] }) : null,
343
+ historyOpen ? /* @__PURE__ */ jsxs("div", { class: "sctui-popover sctui-paste-history", role: "menu", "aria-label": "Paste history", children: [
344
+ /* @__PURE__ */ jsxs("strong", { children: [
345
+ "Paste history ",
346
+ /* @__PURE__ */ jsxs("span", { children: [
347
+ /* @__PURE__ */ jsx("small", { children: "memory only" }),
348
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
349
+ setPasteHistory([]);
350
+ setHistoryOpen(false);
351
+ }, children: "Clear" })
352
+ ] })
353
+ ] }),
354
+ pasteHistory.map((value, index) => /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", onClick: () => proposePaste(value), children: [
355
+ /* @__PURE__ */ jsx("span", { children: value.replaceAll(/\s+/g, " ").slice(0, 90) }),
356
+ /* @__PURE__ */ jsxs("small", { children: [
357
+ value.length,
358
+ " chars"
359
+ ] })
360
+ ] }, `${index}:${value.length}`))
361
+ ] }) : null,
362
+ helpOpen ? /* @__PURE__ */ jsxs("div", { class: "sctui-popover sctui-shortcuts", role: "dialog", "aria-label": "Terminal keyboard shortcuts", children: [
363
+ /* @__PURE__ */ jsx("strong", { children: "Terminal shortcuts" }),
364
+ /* @__PURE__ */ jsxs("dl", { children: [
365
+ /* @__PURE__ */ jsxs("div", { children: [
366
+ /* @__PURE__ */ jsx("dt", { children: "New or attach" }),
367
+ /* @__PURE__ */ jsx("dd", { children: terminalShortcutLabel("T") })
368
+ ] }),
369
+ /* @__PURE__ */ jsxs("div", { children: [
370
+ /* @__PURE__ */ jsx("dt", { children: "Find" }),
371
+ /* @__PURE__ */ jsx("dd", { children: terminalShortcutLabel("F") })
372
+ ] }),
373
+ /* @__PURE__ */ jsxs("div", { children: [
374
+ /* @__PURE__ */ jsx("dt", { children: "Detach tab" }),
375
+ /* @__PURE__ */ jsx("dd", { children: terminalShortcutLabel("D") })
376
+ ] }),
377
+ /* @__PURE__ */ jsxs("div", { children: [
378
+ /* @__PURE__ */ jsx("dt", { children: "Previous / next tab" }),
379
+ /* @__PURE__ */ jsx("dd", { children: terminalShortcutLabel("[ / ]") })
380
+ ] }),
381
+ /* @__PURE__ */ jsxs("div", { children: [
382
+ /* @__PURE__ */ jsx("dt", { children: "Select tab" }),
383
+ /* @__PURE__ */ jsx("dd", { children: terminalShortcutLabel("1\u20139") })
384
+ ] })
385
+ ] }),
386
+ /* @__PURE__ */ jsx("small", { children: "Command and Control shortcuts stay with the browser and terminal." })
387
+ ] }) : null,
388
+ pendingPaste ? /* @__PURE__ */ jsxs("div", { class: "sctui-paste-confirm", ref: pasteDialog, role: "alertdialog", "aria-modal": "true", "aria-label": "Confirm terminal paste", children: [
389
+ /* @__PURE__ */ jsx("strong", { children: "Paste into this terminal?" }),
390
+ /* @__PURE__ */ jsxs("span", { children: [
391
+ pendingPaste.split(/\r?\n/).length,
392
+ " lines \xB7 ",
393
+ pendingPaste.length.toLocaleString(),
394
+ " characters"
395
+ ] }),
396
+ /* @__PURE__ */ jsx("pre", { children: pendingPaste.replaceAll(/\s+/g, " ").slice(0, 180) }),
397
+ /* @__PURE__ */ jsxs("div", { children: [
398
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
399
+ setPendingPaste(null);
400
+ queueMicrotask(focusTerminal);
401
+ }, children: "Cancel" }),
402
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
403
+ sendPaste(pendingPaste);
404
+ setPendingPaste(null);
405
+ queueMicrotask(focusTerminal);
406
+ }, children: "Paste" })
407
+ ] })
408
+ ] }) : null,
409
+ /* @__PURE__ */ jsx("div", { class: "sctui-screen", "data-active": String(active), ref: root })
410
+ ]
411
+ }
412
+ );
413
+ }
414
+ function CloseGlyph() {
415
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", children: /* @__PURE__ */ jsx("path", { d: "m4.5 4.5 7 7m0-7-7 7" }) });
416
+ }
417
+ function SearchGlyph() {
418
+ return /* @__PURE__ */ jsxs("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", children: [
419
+ /* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "3.5" }),
420
+ /* @__PURE__ */ jsx("path", { d: "m9.7 9.7 3 3" })
421
+ ] });
422
+ }
423
+ function HistoryGlyph() {
424
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", children: /* @__PURE__ */ jsx("path", { d: "M3.2 5.3A5 5 0 1 1 3 10M3.2 5.3V2.7m0 2.6h2.6M8 4.8V8l2 1.3" }) });
425
+ }
426
+ function DownloadGlyph() {
427
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", children: /* @__PURE__ */ jsx("path", { d: "M8 2.5v7m-2.5-2L8 10l2.5-2.5M3 12.5h10" }) });
428
+ }
429
+ function HelpGlyph() {
430
+ return /* @__PURE__ */ jsxs("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", children: [
431
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "5.5" }),
432
+ /* @__PURE__ */ jsx("path", { d: "M6.7 6.2A1.5 1.5 0 0 1 8.2 5c1 0 1.7.6 1.7 1.5 0 1.4-1.8 1.4-1.8 2.6M8.1 11.4h.01" })
433
+ ] });
434
+ }
435
+ function UpGlyph() {
436
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", children: /* @__PURE__ */ jsx("path", { d: "m4 10 4-4 4 4" }) });
437
+ }
438
+ function DownGlyph() {
439
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", viewBox: "0 0 16 16", children: /* @__PURE__ */ jsx("path", { d: "m4 6 4 4 4-4" }) });
440
+ }
441
+ function terminalShortcutLabel(key) {
442
+ return /Mac|iPhone|iPad|iPod/i.test(globalThis.navigator?.platform ?? "") ? `\u2325\u21E7${key}` : `Alt+Shift+${key}`;
443
+ }
444
+ export {
445
+ TerminalStreamViewer
446
+ };