@silvery/examples 0.17.3 → 0.17.5
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/UPNG-ShUlaTDh.mjs +5074 -0
- package/dist/__vite-browser-external-2447137e-Bopa5BFR.mjs +4 -0
- package/dist/_banner-A70_y2Vi.mjs +43 -0
- package/dist/ansi-0VXlUmNn.mjs +16397 -0
- package/dist/apng-B0gRaDVT.mjs +3 -0
- package/dist/apng-BTRDTfDW.mjs +68 -0
- package/dist/apps/aichat/index.mjs +1298 -0
- package/dist/apps/app-todo.mjs +138 -0
- package/dist/apps/async-data.mjs +203 -0
- package/dist/apps/cli-wizard.mjs +338 -0
- package/dist/apps/clipboard.mjs +197 -0
- package/dist/apps/components.mjs +863 -0
- package/dist/apps/data-explorer.mjs +482 -0
- package/dist/apps/dev-tools.mjs +396 -0
- package/dist/apps/explorer.mjs +697 -0
- package/dist/apps/gallery.mjs +765 -0
- package/dist/apps/inline-bench.mjs +115 -0
- package/dist/apps/kanban.mjs +279 -0
- package/dist/apps/layout-ref.mjs +186 -0
- package/dist/apps/outline.mjs +202 -0
- package/dist/apps/paste-demo.mjs +188 -0
- package/dist/apps/scroll.mjs +85 -0
- package/dist/apps/search-filter.mjs +286 -0
- package/dist/apps/selection.mjs +354 -0
- package/dist/apps/spatial-focus-demo.mjs +387 -0
- package/dist/apps/task-list.mjs +257 -0
- package/dist/apps/terminal-caps-demo.mjs +314 -0
- package/dist/apps/terminal.mjs +871 -0
- package/dist/apps/text-selection-demo.mjs +253 -0
- package/dist/apps/textarea.mjs +177 -0
- package/dist/apps/theme.mjs +660 -0
- package/dist/apps/transform.mjs +214 -0
- package/dist/apps/virtual-10k.mjs +421 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backends-Dj-11kZF.mjs +1179 -0
- package/dist/backends-U3QwStfO.mjs +3 -0
- package/dist/{cli.mjs → bin/cli.mjs} +15 -19
- package/dist/chunk-BSw8zbkd.mjs +37 -0
- package/dist/components/counter.mjs +47 -0
- package/dist/components/hello.mjs +30 -0
- package/dist/components/progress-bar.mjs +58 -0
- package/dist/components/select-list.mjs +84 -0
- package/dist/components/spinner.mjs +56 -0
- package/dist/components/text-input.mjs +61 -0
- package/dist/components/virtual-list.mjs +50 -0
- package/dist/flexily-zero-adapter-ByVzLTFP.mjs +3374 -0
- package/dist/gif-B6NGH5gs.mjs +3 -0
- package/dist/gif-CfkOF-iG.mjs +71 -0
- package/dist/gifenc-BI4ihP_T.mjs +728 -0
- package/dist/key-mapping-5oYQdAQE.mjs +3 -0
- package/dist/key-mapping-D4LR1go6.mjs +130 -0
- package/dist/layout/dashboard.mjs +1203 -0
- package/dist/layout/live-resize.mjs +302 -0
- package/dist/layout/overflow.mjs +69 -0
- package/dist/layout/text-layout.mjs +334 -0
- package/dist/node-nsrAOjH4.mjs +1083 -0
- package/dist/plugins-CT0DdV_E.mjs +3056 -0
- package/dist/resvg-js-Cnk2o49d.mjs +201 -0
- package/dist/src-9ZhfQyzD.mjs +814 -0
- package/dist/src-CUUOuRH6.mjs +5322 -0
- package/dist/src-jO3Zuzjj.mjs +23538 -0
- package/dist/usingCtx-CsEf0xO3.mjs +57 -0
- package/dist/yoga-adapter-BSQHuMV9.mjs +237 -0
- package/package.json +21 -14
- package/_banner.tsx +0 -60
- package/apps/aichat/components.tsx +0 -469
- package/apps/aichat/index.tsx +0 -220
- package/apps/aichat/script.ts +0 -460
- package/apps/aichat/state.ts +0 -325
- package/apps/aichat/types.ts +0 -19
- package/apps/app-todo.tsx +0 -201
- package/apps/async-data.tsx +0 -196
- package/apps/cli-wizard.tsx +0 -332
- package/apps/clipboard.tsx +0 -183
- package/apps/components.tsx +0 -658
- package/apps/data-explorer.tsx +0 -490
- package/apps/dev-tools.tsx +0 -395
- package/apps/explorer.tsx +0 -731
- package/apps/gallery.tsx +0 -653
- package/apps/inline-bench.tsx +0 -138
- package/apps/kanban.tsx +0 -265
- package/apps/layout-ref.tsx +0 -173
- package/apps/outline.tsx +0 -160
- package/apps/panes/index.tsx +0 -203
- package/apps/paste-demo.tsx +0 -185
- package/apps/scroll.tsx +0 -80
- package/apps/search-filter.tsx +0 -240
- package/apps/selection.tsx +0 -346
- package/apps/spatial-focus-demo.tsx +0 -372
- package/apps/task-list.tsx +0 -271
- package/apps/terminal-caps-demo.tsx +0 -317
- package/apps/terminal.tsx +0 -784
- package/apps/text-selection-demo.tsx +0 -193
- package/apps/textarea.tsx +0 -155
- package/apps/theme.tsx +0 -515
- package/apps/transform.tsx +0 -229
- package/apps/virtual-10k.tsx +0 -405
- package/apps/vterm-demo/index.tsx +0 -216
- package/components/counter.tsx +0 -49
- package/components/hello.tsx +0 -38
- package/components/progress-bar.tsx +0 -52
- package/components/select-list.tsx +0 -54
- package/components/spinner.tsx +0 -44
- package/components/text-input.tsx +0 -61
- package/components/virtual-list.tsx +0 -56
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs.map +0 -1
- package/layout/dashboard.tsx +0 -953
- package/layout/live-resize.tsx +0 -282
- package/layout/overflow.tsx +0 -51
- package/layout/text-layout.tsx +0 -283
|
@@ -0,0 +1,1179 @@
|
|
|
1
|
+
import { n as __esmMin, o as __toESM } from "./chunk-BSw8zbkd.mjs";
|
|
2
|
+
import { n as keyToAnsi, r as parseKey, t as init_key_mapping } from "./key-mapping-D4LR1go6.mjs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
//#region ../../termless/src/spawn.ts
|
|
10
|
+
/**
|
|
11
|
+
* Portable PTY spawn abstraction.
|
|
12
|
+
*
|
|
13
|
+
* Detects the runtime (Bun vs Node.js) and uses the appropriate PTY backend:
|
|
14
|
+
* - Bun: native `Bun.spawn()` with `terminal` option
|
|
15
|
+
* - Node.js: `node-pty` (optional peer dependency — throws if not installed)
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Spawn a process with a PTY, using the appropriate runtime backend.
|
|
19
|
+
*
|
|
20
|
+
* - On Bun: uses `Bun.spawn()` with the `terminal` option (built-in PTY support).
|
|
21
|
+
* - On Node.js: uses `node-pty` (loaded via createRequire). Throws a clear error if not installed.
|
|
22
|
+
*/
|
|
23
|
+
function spawnPortablePty(options) {
|
|
24
|
+
if (isBun) return spawnBunPty(options);
|
|
25
|
+
return spawnNodePty(options);
|
|
26
|
+
}
|
|
27
|
+
function spawnBunPty(options) {
|
|
28
|
+
const { argv, cols, rows, cwd, env, onData } = options;
|
|
29
|
+
const proc = Bun.spawn(argv, {
|
|
30
|
+
cwd,
|
|
31
|
+
env: {
|
|
32
|
+
...process.env,
|
|
33
|
+
...env
|
|
34
|
+
},
|
|
35
|
+
terminal: {
|
|
36
|
+
cols,
|
|
37
|
+
rows,
|
|
38
|
+
data: (_terminal, data) => {
|
|
39
|
+
try {
|
|
40
|
+
onData(data);
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const pty = proc.terminal;
|
|
46
|
+
return {
|
|
47
|
+
write(data) {
|
|
48
|
+
pty.write(data);
|
|
49
|
+
},
|
|
50
|
+
resize(newCols, newRows) {
|
|
51
|
+
pty.resize(newCols, newRows);
|
|
52
|
+
},
|
|
53
|
+
closePty() {
|
|
54
|
+
try {
|
|
55
|
+
pty.close();
|
|
56
|
+
} catch {}
|
|
57
|
+
},
|
|
58
|
+
kill(signal) {
|
|
59
|
+
proc.kill(signal);
|
|
60
|
+
},
|
|
61
|
+
get exitCode() {
|
|
62
|
+
return proc.exitCode;
|
|
63
|
+
},
|
|
64
|
+
get exited() {
|
|
65
|
+
return proc.exited;
|
|
66
|
+
},
|
|
67
|
+
get pid() {
|
|
68
|
+
return proc.pid;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Load node-pty synchronously using createRequire.
|
|
74
|
+
*
|
|
75
|
+
* node-pty is a CommonJS native addon, so createRequire is the correct way
|
|
76
|
+
* to load it from ESM. This keeps spawnPortablePty() synchronous.
|
|
77
|
+
*/
|
|
78
|
+
function loadNodePty() {
|
|
79
|
+
try {
|
|
80
|
+
return createRequire(import.meta.url)("node-pty");
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error("node-pty is required for PTY support on Node.js but was not found.\nInstall it with: npm install node-pty\nNote: node-pty requires native compilation tools (Python, C++ compiler).");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function spawnNodePty(options) {
|
|
86
|
+
const { argv, cols, rows, cwd, env, onData } = options;
|
|
87
|
+
const ptyProcess = loadNodePty().spawn(argv[0], argv.slice(1), {
|
|
88
|
+
name: "xterm-256color",
|
|
89
|
+
cols,
|
|
90
|
+
rows,
|
|
91
|
+
cwd,
|
|
92
|
+
env: {
|
|
93
|
+
...process.env,
|
|
94
|
+
...env
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const encoder = new TextEncoder();
|
|
98
|
+
ptyProcess.onData((data) => {
|
|
99
|
+
try {
|
|
100
|
+
onData(encoder.encode(data));
|
|
101
|
+
} catch {}
|
|
102
|
+
});
|
|
103
|
+
let _exitCode = null;
|
|
104
|
+
let _exitResolve = null;
|
|
105
|
+
const exitedPromise = new Promise((resolve) => {
|
|
106
|
+
_exitResolve = resolve;
|
|
107
|
+
});
|
|
108
|
+
ptyProcess.onExit((e) => {
|
|
109
|
+
_exitCode = e.exitCode;
|
|
110
|
+
_exitResolve?.(e.exitCode);
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
write(data) {
|
|
114
|
+
ptyProcess.write(data);
|
|
115
|
+
},
|
|
116
|
+
resize(newCols, newRows) {
|
|
117
|
+
ptyProcess.resize(newCols, newRows);
|
|
118
|
+
},
|
|
119
|
+
closePty() {
|
|
120
|
+
try {
|
|
121
|
+
ptyProcess.destroy();
|
|
122
|
+
} catch {}
|
|
123
|
+
},
|
|
124
|
+
kill(signal) {
|
|
125
|
+
const sig = signal === 9 ? "SIGKILL" : "SIGTERM";
|
|
126
|
+
try {
|
|
127
|
+
ptyProcess.kill(sig);
|
|
128
|
+
} catch {}
|
|
129
|
+
},
|
|
130
|
+
get exitCode() {
|
|
131
|
+
return _exitCode;
|
|
132
|
+
},
|
|
133
|
+
get exited() {
|
|
134
|
+
return exitedPromise;
|
|
135
|
+
},
|
|
136
|
+
get pid() {
|
|
137
|
+
return ptyProcess.pid;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
var isBun;
|
|
142
|
+
var init_spawn = __esmMin((() => {
|
|
143
|
+
isBun = typeof globalThis.Bun !== "undefined";
|
|
144
|
+
}));
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region ../../termless/src/pty.ts
|
|
147
|
+
/**
|
|
148
|
+
* Spawn a child process with a PTY and return a handle for interacting with it.
|
|
149
|
+
*
|
|
150
|
+
* The command is spawned directly (no shell wrapper) to avoid shell injection.
|
|
151
|
+
* Sets FORCE_COLOR=1 and TERM=xterm-256color to ensure proper color output.
|
|
152
|
+
*
|
|
153
|
+
* Runtime support:
|
|
154
|
+
* - Bun: uses native `Bun.spawn()` with `terminal` option (built-in PTY)
|
|
155
|
+
* - Node.js: uses `node-pty` (must be installed as a peer dependency)
|
|
156
|
+
*/
|
|
157
|
+
function spawnPty(options) {
|
|
158
|
+
const { env, cwd, cols, rows, onData } = options;
|
|
159
|
+
const proc = spawnPortablePty({
|
|
160
|
+
argv: "shellCommand" in options ? [
|
|
161
|
+
"bash",
|
|
162
|
+
"-c",
|
|
163
|
+
options.shellCommand
|
|
164
|
+
] : options.command,
|
|
165
|
+
cols,
|
|
166
|
+
rows,
|
|
167
|
+
cwd,
|
|
168
|
+
env: {
|
|
169
|
+
FORCE_COLOR: "1",
|
|
170
|
+
TERM: "xterm-256color",
|
|
171
|
+
...env
|
|
172
|
+
},
|
|
173
|
+
onData
|
|
174
|
+
});
|
|
175
|
+
let closed = false;
|
|
176
|
+
let exitCode = null;
|
|
177
|
+
(async () => {
|
|
178
|
+
try {
|
|
179
|
+
exitCode = await proc.exited;
|
|
180
|
+
} catch {}
|
|
181
|
+
})();
|
|
182
|
+
async function close() {
|
|
183
|
+
if (closed) return;
|
|
184
|
+
closed = true;
|
|
185
|
+
proc.closePty();
|
|
186
|
+
try {
|
|
187
|
+
proc.kill();
|
|
188
|
+
if (!await Promise.race([proc.exited.then(() => true), new Promise((resolve) => setTimeout(() => resolve(false), 2e3))])) proc.kill(9);
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
write(data) {
|
|
193
|
+
if (closed) throw new Error("PTY is closed");
|
|
194
|
+
proc.write(data);
|
|
195
|
+
},
|
|
196
|
+
resize(newCols, newRows) {
|
|
197
|
+
if (closed) throw new Error("PTY is closed");
|
|
198
|
+
proc.resize(newCols, newRows);
|
|
199
|
+
},
|
|
200
|
+
get alive() {
|
|
201
|
+
return !closed && proc.exitCode === null;
|
|
202
|
+
},
|
|
203
|
+
get exitInfo() {
|
|
204
|
+
if (exitCode !== null) return `exit=${exitCode}`;
|
|
205
|
+
return null;
|
|
206
|
+
},
|
|
207
|
+
close
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
var init_pty = __esmMin((() => {
|
|
211
|
+
init_spawn();
|
|
212
|
+
}));
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region ../../termless/src/svg.ts
|
|
215
|
+
function rgbToHex(color) {
|
|
216
|
+
return `#${color.r.toString(16).padStart(2, "0")}${color.g.toString(16).padStart(2, "0")}${color.b.toString(16).padStart(2, "0")}`;
|
|
217
|
+
}
|
|
218
|
+
function rgbToString(color, fallback) {
|
|
219
|
+
return color ? rgbToHex(color) : fallback;
|
|
220
|
+
}
|
|
221
|
+
function escapeXml(text) {
|
|
222
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
223
|
+
}
|
|
224
|
+
function cellFgBg(cell, themeFg, themeBg) {
|
|
225
|
+
let fg = rgbToString(cell.fg, themeFg);
|
|
226
|
+
let bg = rgbToString(cell.bg, themeBg);
|
|
227
|
+
if (cell.inverse) [fg, bg] = [bg, fg];
|
|
228
|
+
return {
|
|
229
|
+
fg,
|
|
230
|
+
bg
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function spansMatch(a, fg, bold, italic, dim, underline, strikethrough) {
|
|
234
|
+
return a.fill === fg && a.bold === bold && a.italic === italic && a.dim === dim && a.underline === underline && a.strikethrough === strikethrough;
|
|
235
|
+
}
|
|
236
|
+
function buildBgRects(lines, cellWidth, cellHeight, themeFg, themeBg) {
|
|
237
|
+
const rects = [];
|
|
238
|
+
for (let row = 0; row < lines.length; row++) {
|
|
239
|
+
const cells = lines[row];
|
|
240
|
+
let runStart = -1;
|
|
241
|
+
let runColor = "";
|
|
242
|
+
for (let col = 0; col < cells.length; col++) {
|
|
243
|
+
const cell = cells[col];
|
|
244
|
+
const { bg } = cellFgBg(cell, themeFg, themeBg);
|
|
245
|
+
if (bg !== themeBg) if (runStart >= 0 && runColor === bg) {} else {
|
|
246
|
+
if (runStart >= 0) rects.push({
|
|
247
|
+
x: runStart * cellWidth,
|
|
248
|
+
y: row * cellHeight,
|
|
249
|
+
width: (col - runStart) * cellWidth,
|
|
250
|
+
height: cellHeight,
|
|
251
|
+
fill: runColor
|
|
252
|
+
});
|
|
253
|
+
runStart = col;
|
|
254
|
+
runColor = bg;
|
|
255
|
+
}
|
|
256
|
+
else if (runStart >= 0) {
|
|
257
|
+
rects.push({
|
|
258
|
+
x: runStart * cellWidth,
|
|
259
|
+
y: row * cellHeight,
|
|
260
|
+
width: (col - runStart) * cellWidth,
|
|
261
|
+
height: cellHeight,
|
|
262
|
+
fill: runColor
|
|
263
|
+
});
|
|
264
|
+
runStart = -1;
|
|
265
|
+
runColor = "";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (runStart >= 0) rects.push({
|
|
269
|
+
x: runStart * cellWidth,
|
|
270
|
+
y: row * cellHeight,
|
|
271
|
+
width: (cells.length - runStart) * cellWidth,
|
|
272
|
+
height: cellHeight,
|
|
273
|
+
fill: runColor
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return rects;
|
|
277
|
+
}
|
|
278
|
+
function buildTextSpans(cells, themeFg, themeBg) {
|
|
279
|
+
const spans = [];
|
|
280
|
+
for (let col = 0; col < cells.length; col++) {
|
|
281
|
+
const cell = cells[col];
|
|
282
|
+
if (cell.char === "" && col > 0 && cells[col - 1]?.wide) continue;
|
|
283
|
+
const { fg } = cellFgBg(cell, themeFg, themeBg);
|
|
284
|
+
const char = cell.char || " ";
|
|
285
|
+
const underline = cell.underline !== false;
|
|
286
|
+
const current = spans.length > 0 ? spans[spans.length - 1] : null;
|
|
287
|
+
if (current && spansMatch(current, fg, cell.bold, cell.italic, cell.dim, underline, cell.strikethrough)) current.text += char;
|
|
288
|
+
else spans.push({
|
|
289
|
+
text: char,
|
|
290
|
+
fill: fg,
|
|
291
|
+
bold: cell.bold,
|
|
292
|
+
italic: cell.italic,
|
|
293
|
+
dim: cell.dim,
|
|
294
|
+
underline,
|
|
295
|
+
strikethrough: cell.strikethrough,
|
|
296
|
+
startCol: col
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return spans;
|
|
300
|
+
}
|
|
301
|
+
function resolveOptions(options) {
|
|
302
|
+
return {
|
|
303
|
+
fontFamily: options?.fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
304
|
+
fontSize: options?.fontSize ?? DEFAULT_FONT_SIZE,
|
|
305
|
+
cellWidth: options?.cellWidth ?? DEFAULT_CELL_WIDTH,
|
|
306
|
+
cellHeight: options?.cellHeight ?? DEFAULT_CELL_HEIGHT,
|
|
307
|
+
themeFg: options?.theme?.foreground ?? DEFAULT_THEME.foreground,
|
|
308
|
+
themeBg: options?.theme?.background ?? DEFAULT_THEME.background,
|
|
309
|
+
themeCursor: options?.theme?.cursor ?? DEFAULT_THEME.cursor,
|
|
310
|
+
padding: options?.padding ?? 0,
|
|
311
|
+
borderRadius: options?.borderRadius ?? 0,
|
|
312
|
+
windowBar: options?.windowBar ?? "none",
|
|
313
|
+
windowBarSize: options?.windowBarSize ?? 40,
|
|
314
|
+
margin: options?.margin ?? 0,
|
|
315
|
+
marginFill: options?.marginFill ?? null
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function spanToTspan(span, cellWidth, themeFg) {
|
|
319
|
+
const attrs = [`x="${span.startCol * cellWidth}"`];
|
|
320
|
+
if (span.fill !== themeFg) attrs.push(`fill="${span.fill}"`);
|
|
321
|
+
if (span.bold) attrs.push(`font-weight="bold"`);
|
|
322
|
+
if (span.italic) attrs.push(`font-style="italic"`);
|
|
323
|
+
if (span.dim) attrs.push(`opacity="0.5"`);
|
|
324
|
+
const decorations = [];
|
|
325
|
+
if (span.underline) decorations.push("underline");
|
|
326
|
+
if (span.strikethrough) decorations.push("line-through");
|
|
327
|
+
if (decorations.length > 0) attrs.push(`text-decoration="${decorations.join(" ")}"`);
|
|
328
|
+
return `<tspan ${attrs.join(" ")}>${escapeXml(span.text)}</tspan>`;
|
|
329
|
+
}
|
|
330
|
+
function renderTextRows(lines, opts) {
|
|
331
|
+
const parts = [];
|
|
332
|
+
const { cellHeight, cellWidth, fontSize, fontFamily, themeFg, themeBg } = opts;
|
|
333
|
+
for (let row = 0; row < lines.length; row++) {
|
|
334
|
+
const cells = lines[row];
|
|
335
|
+
if (!cells || cells.length === 0) continue;
|
|
336
|
+
const spans = buildTextSpans(cells, themeFg, themeBg);
|
|
337
|
+
if (spans.length === 0) continue;
|
|
338
|
+
const y = row * cellHeight + fontSize;
|
|
339
|
+
parts.push(`<text x="0" y="${y}" font-family="${escapeXml(fontFamily)}" font-size="${fontSize}" fill="${themeFg}">`);
|
|
340
|
+
for (const span of spans) parts.push(spanToTspan(span, cellWidth, themeFg));
|
|
341
|
+
parts.push(`</text>`);
|
|
342
|
+
}
|
|
343
|
+
return parts;
|
|
344
|
+
}
|
|
345
|
+
function renderCursor(cursor, opts) {
|
|
346
|
+
if (!cursor.visible) return null;
|
|
347
|
+
const cx = cursor.x * opts.cellWidth;
|
|
348
|
+
const cy = cursor.y * opts.cellHeight;
|
|
349
|
+
switch (cursor.style ?? "block") {
|
|
350
|
+
case "block": return `<rect x="${cx}" y="${cy}" width="${opts.cellWidth}" height="${opts.cellHeight}" fill="${opts.themeCursor}" opacity="0.5"/>`;
|
|
351
|
+
case "underline": return `<rect x="${cx}" y="${cy + opts.cellHeight - 2}" width="${opts.cellWidth}" height="2" fill="${opts.themeCursor}"/>`;
|
|
352
|
+
case "beam": return `<rect x="${cx}" y="${cy}" width="2" height="${opts.cellHeight}" fill="${opts.themeCursor}"/>`;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function renderWindowBar(barWidth, barHeight, style, borderRadius) {
|
|
356
|
+
if (style === "none") return [];
|
|
357
|
+
const parts = [];
|
|
358
|
+
parts.push(`<rect width="${barWidth}" height="${barHeight}" rx="${borderRadius}" ry="${borderRadius}" fill="#333333"/>`);
|
|
359
|
+
if (borderRadius > 0) parts.push(`<rect y="${barHeight - borderRadius}" width="${barWidth}" height="${borderRadius}" fill="#333333"/>`);
|
|
360
|
+
const dotRadius = 6;
|
|
361
|
+
const dotY = barHeight / 2;
|
|
362
|
+
const dotStartX = 20;
|
|
363
|
+
if (style === "rings") {
|
|
364
|
+
parts.push(`<circle cx="${dotStartX}" cy="${dotY}" r="${dotRadius}" fill="none" stroke="#ff5f57" stroke-width="1.5"/>`);
|
|
365
|
+
parts.push(`<circle cx="${dotStartX + 20}" cy="${dotY}" r="${dotRadius}" fill="none" stroke="#febc2e" stroke-width="1.5"/>`);
|
|
366
|
+
parts.push(`<circle cx="${dotStartX + 40}" cy="${dotY}" r="${dotRadius}" fill="none" stroke="#28c840" stroke-width="1.5"/>`);
|
|
367
|
+
} else {
|
|
368
|
+
parts.push(`<circle cx="${dotStartX}" cy="${dotY}" r="${dotRadius}" fill="#ff5f57"/>`);
|
|
369
|
+
parts.push(`<circle cx="${dotStartX + 20}" cy="${dotY}" r="${dotRadius}" fill="#febc2e"/>`);
|
|
370
|
+
parts.push(`<circle cx="${dotStartX + 40}" cy="${dotY}" r="${dotRadius}" fill="#28c840"/>`);
|
|
371
|
+
}
|
|
372
|
+
return parts;
|
|
373
|
+
}
|
|
374
|
+
function screenshotSvg(terminal, options) {
|
|
375
|
+
const opts = resolveOptions(options);
|
|
376
|
+
const { cellWidth, cellHeight, themeFg, themeBg, padding, borderRadius, windowBar, windowBarSize, margin, marginFill } = opts;
|
|
377
|
+
const lines = terminal.getLines();
|
|
378
|
+
const rows = lines.length;
|
|
379
|
+
const contentWidth = (rows > 0 ? Math.max(...lines.map((l) => l.length)) : 0) * cellWidth;
|
|
380
|
+
const contentHeight = rows * cellHeight;
|
|
381
|
+
if (!(padding > 0 || borderRadius > 0 || windowBar !== "none" || margin > 0)) {
|
|
382
|
+
const totalWidth = contentWidth;
|
|
383
|
+
const totalHeight = contentHeight;
|
|
384
|
+
const parts = [];
|
|
385
|
+
parts.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="${totalHeight}">`);
|
|
386
|
+
parts.push(`<rect width="100%" height="100%" fill="${themeBg}"/>`);
|
|
387
|
+
for (const rect of buildBgRects(lines, cellWidth, cellHeight, themeFg, themeBg)) parts.push(`<rect x="${rect.x}" y="${rect.y}" width="${rect.width}" height="${rect.height}" fill="${rect.fill}"/>`);
|
|
388
|
+
parts.push(...renderTextRows(lines, opts));
|
|
389
|
+
const cursorSvg = renderCursor(terminal.getCursor(), opts);
|
|
390
|
+
if (cursorSvg) parts.push(cursorSvg);
|
|
391
|
+
parts.push(`</svg>`);
|
|
392
|
+
return parts.join("\n");
|
|
393
|
+
}
|
|
394
|
+
const barHeight = windowBar !== "none" ? windowBarSize : 0;
|
|
395
|
+
const innerWidth = contentWidth + padding * 2;
|
|
396
|
+
const innerHeight = contentHeight + padding * 2 + barHeight;
|
|
397
|
+
const totalWidth = innerWidth + margin * 2;
|
|
398
|
+
const totalHeight = innerHeight + margin * 2;
|
|
399
|
+
const parts = [];
|
|
400
|
+
parts.push(`<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="${totalHeight}">`);
|
|
401
|
+
if (margin > 0 && marginFill) parts.push(`<rect width="100%" height="100%" fill="${marginFill}"/>`);
|
|
402
|
+
if (borderRadius > 0) {
|
|
403
|
+
parts.push(`<defs>`);
|
|
404
|
+
parts.push(`<clipPath id="terminal-clip">`);
|
|
405
|
+
parts.push(`<rect x="${margin}" y="${margin}" width="${innerWidth}" height="${innerHeight}" rx="${borderRadius}" ry="${borderRadius}"/>`);
|
|
406
|
+
parts.push(`</clipPath>`);
|
|
407
|
+
parts.push(`</defs>`);
|
|
408
|
+
parts.push(`<g clip-path="url(#terminal-clip)">`);
|
|
409
|
+
}
|
|
410
|
+
const bgAttrs = borderRadius > 0 ? `x="${margin}" y="${margin}" width="${innerWidth}" height="${innerHeight}" rx="${borderRadius}" ry="${borderRadius}"` : `x="${margin}" y="${margin}" width="${innerWidth}" height="${innerHeight}"`;
|
|
411
|
+
parts.push(`<rect ${bgAttrs} fill="${themeBg}"/>`);
|
|
412
|
+
if (windowBar !== "none") {
|
|
413
|
+
parts.push(`<g transform="translate(${margin}, ${margin})">`);
|
|
414
|
+
parts.push(...renderWindowBar(innerWidth, barHeight, windowBar, borderRadius));
|
|
415
|
+
parts.push(`</g>`);
|
|
416
|
+
}
|
|
417
|
+
const contentOffsetX = margin + padding;
|
|
418
|
+
const contentOffsetY = margin + padding + barHeight;
|
|
419
|
+
parts.push(`<g transform="translate(${contentOffsetX}, ${contentOffsetY})">`);
|
|
420
|
+
for (const rect of buildBgRects(lines, cellWidth, cellHeight, themeFg, themeBg)) parts.push(`<rect x="${rect.x}" y="${rect.y}" width="${rect.width}" height="${rect.height}" fill="${rect.fill}"/>`);
|
|
421
|
+
parts.push(...renderTextRows(lines, opts));
|
|
422
|
+
const cursorSvg = renderCursor(terminal.getCursor(), opts);
|
|
423
|
+
if (cursorSvg) parts.push(cursorSvg);
|
|
424
|
+
parts.push(`</g>`);
|
|
425
|
+
if (borderRadius > 0) parts.push(`</g>`);
|
|
426
|
+
parts.push(`</svg>`);
|
|
427
|
+
return parts.join("\n");
|
|
428
|
+
}
|
|
429
|
+
var DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DEFAULT_CELL_WIDTH, DEFAULT_CELL_HEIGHT, DEFAULT_THEME;
|
|
430
|
+
var init_svg = __esmMin((() => {
|
|
431
|
+
DEFAULT_FONT_FAMILY = "'Menlo', 'Monaco', 'Courier New', monospace";
|
|
432
|
+
DEFAULT_FONT_SIZE = 16;
|
|
433
|
+
DEFAULT_CELL_WIDTH = 9.6;
|
|
434
|
+
DEFAULT_CELL_HEIGHT = 20;
|
|
435
|
+
DEFAULT_THEME = {
|
|
436
|
+
foreground: "#d4d4d4",
|
|
437
|
+
background: "#1e1e1e",
|
|
438
|
+
cursor: "#aeafad"
|
|
439
|
+
};
|
|
440
|
+
}));
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region ../../termless/src/png.ts
|
|
443
|
+
async function loadResvg() {
|
|
444
|
+
if (resvgModule) return resvgModule;
|
|
445
|
+
try {
|
|
446
|
+
resvgModule = await import("./resvg-js-Cnk2o49d.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
447
|
+
return resvgModule;
|
|
448
|
+
} catch {
|
|
449
|
+
throw new Error("screenshotPng() requires @resvg/resvg-js. Install it:\n bun add -d @resvg/resvg-js");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Render a terminal screenshot as a PNG buffer.
|
|
454
|
+
*
|
|
455
|
+
* Requires `@resvg/resvg-js` as an optional dependency:
|
|
456
|
+
* bun add -d @resvg/resvg-js
|
|
457
|
+
*/
|
|
458
|
+
async function screenshotPng(terminal, options) {
|
|
459
|
+
const svg = screenshotSvg(terminal, options);
|
|
460
|
+
const scale = options?.scale ?? 2;
|
|
461
|
+
const { Resvg } = await loadResvg();
|
|
462
|
+
return new Resvg(svg, {
|
|
463
|
+
fitTo: {
|
|
464
|
+
mode: "zoom",
|
|
465
|
+
value: scale
|
|
466
|
+
},
|
|
467
|
+
font: {
|
|
468
|
+
loadSystemFonts: true,
|
|
469
|
+
defaultFontFamily: "Menlo"
|
|
470
|
+
}
|
|
471
|
+
}).render().asPng();
|
|
472
|
+
}
|
|
473
|
+
var resvgModule;
|
|
474
|
+
var init_png = __esmMin((() => {
|
|
475
|
+
init_svg();
|
|
476
|
+
resvgModule = null;
|
|
477
|
+
}));
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region ../../termless/src/views.ts
|
|
480
|
+
/** Convert a Cell[] to trimmed text. */
|
|
481
|
+
function cellsToText(cells) {
|
|
482
|
+
return cells.map((c) => c.char || " ").join("").trimEnd();
|
|
483
|
+
}
|
|
484
|
+
/** Get rows of text from a TerminalReadable for an absolute row range. */
|
|
485
|
+
function getRowTexts(readable, startRow, endRow) {
|
|
486
|
+
const lines = [];
|
|
487
|
+
for (let i = startRow; i < endRow; i++) lines.push(cellsToText(readable.getLine(i)));
|
|
488
|
+
return lines;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Create a lazy RegionView from a row-range resolver.
|
|
492
|
+
* The resolver is called on every getText()/getLines() access,
|
|
493
|
+
* so the view always reflects current terminal state.
|
|
494
|
+
*/
|
|
495
|
+
function createLazyRegionView(readable, resolveRange) {
|
|
496
|
+
return {
|
|
497
|
+
getText() {
|
|
498
|
+
const [start, end] = resolveRange();
|
|
499
|
+
return getRowTexts(readable, start, end).join("\n");
|
|
500
|
+
},
|
|
501
|
+
getLines() {
|
|
502
|
+
const [start, end] = resolveRange();
|
|
503
|
+
return getRowTexts(readable, start, end);
|
|
504
|
+
},
|
|
505
|
+
containsText(text) {
|
|
506
|
+
return this.getText().includes(text);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/** Create a CellView from a Cell with positional context. */
|
|
511
|
+
function createCellView(cell, row, col) {
|
|
512
|
+
return {
|
|
513
|
+
char: cell.char,
|
|
514
|
+
row,
|
|
515
|
+
col,
|
|
516
|
+
fg: cell.fg,
|
|
517
|
+
bg: cell.bg,
|
|
518
|
+
bold: cell.bold,
|
|
519
|
+
dim: cell.dim,
|
|
520
|
+
italic: cell.italic,
|
|
521
|
+
underline: cell.underline,
|
|
522
|
+
underlineColor: cell.underlineColor,
|
|
523
|
+
strikethrough: cell.strikethrough,
|
|
524
|
+
inverse: cell.inverse,
|
|
525
|
+
blink: cell.blink,
|
|
526
|
+
hidden: cell.hidden,
|
|
527
|
+
wide: cell.wide,
|
|
528
|
+
continuation: cell.continuation,
|
|
529
|
+
hyperlink: cell.hyperlink
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
/** Create a RegionView for a fixed absolute row range [startRow, endRow). */
|
|
533
|
+
function createRegionView(readable, startRow, endRow) {
|
|
534
|
+
return createLazyRegionView(readable, () => [startRow, endRow]);
|
|
535
|
+
}
|
|
536
|
+
/** Create a RowView for an absolute row position. screenRow is the display row number. */
|
|
537
|
+
function createRowView(readable, absRow, screenRow) {
|
|
538
|
+
return {
|
|
539
|
+
get row() {
|
|
540
|
+
return screenRow;
|
|
541
|
+
},
|
|
542
|
+
get cells() {
|
|
543
|
+
return readable.getLine(absRow);
|
|
544
|
+
},
|
|
545
|
+
getText() {
|
|
546
|
+
return cellsToText(readable.getLine(absRow));
|
|
547
|
+
},
|
|
548
|
+
getLines() {
|
|
549
|
+
return [this.getText()];
|
|
550
|
+
},
|
|
551
|
+
containsText(text) {
|
|
552
|
+
return this.getText().includes(text);
|
|
553
|
+
},
|
|
554
|
+
cellAt(col) {
|
|
555
|
+
return createCellView(readable.getCell(absRow, col), screenRow, col);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Screen view: the fixed rows × cols grid at the bottom of the buffer.
|
|
561
|
+
* In alt mode, this is the entire alt buffer.
|
|
562
|
+
*/
|
|
563
|
+
function createScreenView(readable) {
|
|
564
|
+
return createLazyRegionView(readable, () => {
|
|
565
|
+
const { totalLines, screenLines } = readable.getScrollback();
|
|
566
|
+
const base = totalLines - screenLines;
|
|
567
|
+
return [base, base + screenLines];
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Scrollback view: history lines above the screen.
|
|
572
|
+
* Empty in alt screen mode.
|
|
573
|
+
* @param n - If provided, only the last N scrollback lines.
|
|
574
|
+
*/
|
|
575
|
+
function createScrollbackView(readable, n) {
|
|
576
|
+
return createLazyRegionView(readable, () => {
|
|
577
|
+
const { totalLines, screenLines } = readable.getScrollback();
|
|
578
|
+
const base = totalLines - screenLines;
|
|
579
|
+
if (base <= 0) return [0, 0];
|
|
580
|
+
return [n != null ? Math.max(0, base - n) : 0, base];
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Buffer view: everything (scrollback + screen).
|
|
585
|
+
* Uses readable.getText() directly — not row-based.
|
|
586
|
+
*/
|
|
587
|
+
function createBufferView(readable) {
|
|
588
|
+
return {
|
|
589
|
+
getText() {
|
|
590
|
+
return readable.getText();
|
|
591
|
+
},
|
|
592
|
+
getLines() {
|
|
593
|
+
return readable.getText().split("\n");
|
|
594
|
+
},
|
|
595
|
+
containsText(text) {
|
|
596
|
+
return readable.getText().includes(text);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Viewport view: what's visible at the current scroll position.
|
|
602
|
+
* At bottom (viewportOffset = totalLines - screenLines): same as screen.
|
|
603
|
+
* Scrolled up: shows older scrollback lines.
|
|
604
|
+
*/
|
|
605
|
+
function createViewportView(readable) {
|
|
606
|
+
return createLazyRegionView(readable, () => {
|
|
607
|
+
const { viewportOffset, screenLines } = readable.getScrollback();
|
|
608
|
+
return [viewportOffset, viewportOffset + screenLines];
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Range view: a rectangular region of the screen.
|
|
613
|
+
* Coordinates are screen-relative. Uses getTextRange() — not row-based.
|
|
614
|
+
*/
|
|
615
|
+
function createRangeView(readable, r1, c1, r2, c2) {
|
|
616
|
+
return {
|
|
617
|
+
getText() {
|
|
618
|
+
const { totalLines, screenLines } = readable.getScrollback();
|
|
619
|
+
const base = totalLines - screenLines;
|
|
620
|
+
return readable.getTextRange(base + r1, c1, base + r2, c2);
|
|
621
|
+
},
|
|
622
|
+
getLines() {
|
|
623
|
+
return this.getText().split("\n");
|
|
624
|
+
},
|
|
625
|
+
containsText(text) {
|
|
626
|
+
return this.getText().includes(text);
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
var init_views = __esmMin((() => {}));
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region ../../termless/src/terminal.ts
|
|
633
|
+
/**
|
|
634
|
+
* Create a Terminal instance wrapping a backend with optional PTY support.
|
|
635
|
+
*
|
|
636
|
+
* The terminal initializes the backend immediately and provides methods for:
|
|
637
|
+
* - Feeding data directly (no PTY)
|
|
638
|
+
* - Spawning a child process with a PTY
|
|
639
|
+
* - Sending key presses and typed text to the PTY
|
|
640
|
+
* - Waiting for terminal content to appear or stabilize
|
|
641
|
+
* - Searching terminal text
|
|
642
|
+
* - Taking SVG and PNG screenshots
|
|
643
|
+
*/
|
|
644
|
+
function createTerminal(options) {
|
|
645
|
+
const { backend, scrollbackLimit } = options;
|
|
646
|
+
let cols = options.cols ?? DEFAULT_COLS;
|
|
647
|
+
let rows = options.rows ?? DEFAULT_ROWS;
|
|
648
|
+
backend.init({
|
|
649
|
+
cols,
|
|
650
|
+
rows,
|
|
651
|
+
scrollbackLimit
|
|
652
|
+
});
|
|
653
|
+
let ptyHandle = null;
|
|
654
|
+
let closed = false;
|
|
655
|
+
/** OSC 52 regex: \x1b]52;c;<base64>\x07 or \x1b]52;c;<base64>\x1b\\ */
|
|
656
|
+
const osc52Re = /\x1b\]52;[a-z]*;([A-Za-z0-9+/=]+)(?:\x07|\x1b\\)/g;
|
|
657
|
+
const clipboardWrites = [];
|
|
658
|
+
function scanOsc52(data) {
|
|
659
|
+
osc52Re.lastIndex = 0;
|
|
660
|
+
let match;
|
|
661
|
+
while ((match = osc52Re.exec(data)) !== null) try {
|
|
662
|
+
const decoded = atob(match[1]);
|
|
663
|
+
clipboardWrites.push(decoded);
|
|
664
|
+
} catch {}
|
|
665
|
+
}
|
|
666
|
+
function getText() {
|
|
667
|
+
return backend.getText();
|
|
668
|
+
}
|
|
669
|
+
function getTextRange(startRow, startCol, endRow, endCol) {
|
|
670
|
+
return backend.getTextRange(startRow, startCol, endRow, endCol);
|
|
671
|
+
}
|
|
672
|
+
function getCell(row, col) {
|
|
673
|
+
return backend.getCell(row, col);
|
|
674
|
+
}
|
|
675
|
+
function getLine(row) {
|
|
676
|
+
return backend.getLine(row);
|
|
677
|
+
}
|
|
678
|
+
function getLines() {
|
|
679
|
+
return backend.getLines();
|
|
680
|
+
}
|
|
681
|
+
function getCursor() {
|
|
682
|
+
return backend.getCursor();
|
|
683
|
+
}
|
|
684
|
+
function getMode(mode) {
|
|
685
|
+
return backend.getMode(mode);
|
|
686
|
+
}
|
|
687
|
+
function getTitle() {
|
|
688
|
+
return backend.getTitle();
|
|
689
|
+
}
|
|
690
|
+
function getScrollback() {
|
|
691
|
+
return backend.getScrollback();
|
|
692
|
+
}
|
|
693
|
+
function feed(data) {
|
|
694
|
+
if (closed) throw new Error("Terminal is closed");
|
|
695
|
+
scanOsc52(typeof data === "string" ? data : new TextDecoder().decode(data));
|
|
696
|
+
const bytes = typeof data === "string" ? encoder.encode(data) : data;
|
|
697
|
+
backend.feed(bytes);
|
|
698
|
+
}
|
|
699
|
+
async function spawn(command, spawnOpts) {
|
|
700
|
+
if (closed) throw new Error("Terminal is closed");
|
|
701
|
+
if (ptyHandle) throw new Error("Terminal already has a spawned process");
|
|
702
|
+
ptyHandle = spawnPty({
|
|
703
|
+
command,
|
|
704
|
+
env: spawnOpts?.env,
|
|
705
|
+
cwd: spawnOpts?.cwd,
|
|
706
|
+
cols,
|
|
707
|
+
rows,
|
|
708
|
+
onData: (data) => {
|
|
709
|
+
scanOsc52(new TextDecoder().decode(data));
|
|
710
|
+
backend.feed(data);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
backend.onResponse = (data) => {
|
|
714
|
+
if (ptyHandle?.alive) ptyHandle.write(new TextDecoder().decode(data));
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function press(key) {
|
|
718
|
+
if (closed) throw new Error("Terminal is closed");
|
|
719
|
+
if (!ptyHandle) throw new Error("No PTY spawned — call spawn() first");
|
|
720
|
+
const desc = parseKey(key);
|
|
721
|
+
const encoded = backend.encodeKey(desc);
|
|
722
|
+
if (encoded.length > 0) ptyHandle.write(new TextDecoder().decode(encoded));
|
|
723
|
+
else {
|
|
724
|
+
const ansi = keyToAnsi(desc);
|
|
725
|
+
if (ansi) ptyHandle.write(ansi);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
function type(text) {
|
|
729
|
+
if (closed) throw new Error("Terminal is closed");
|
|
730
|
+
if (!ptyHandle) throw new Error("No PTY spawned — call spawn() first");
|
|
731
|
+
ptyHandle.write(text);
|
|
732
|
+
}
|
|
733
|
+
/** Encode SGR button byte: button number + modifier bits. */
|
|
734
|
+
function sgrButton(options) {
|
|
735
|
+
let btn = options?.button ?? 0;
|
|
736
|
+
if (options?.shift) btn += 4;
|
|
737
|
+
if (options?.alt) btn += 8;
|
|
738
|
+
if (options?.ctrl) btn += 16;
|
|
739
|
+
return btn;
|
|
740
|
+
}
|
|
741
|
+
function requirePty() {
|
|
742
|
+
if (closed) throw new Error("Terminal is closed");
|
|
743
|
+
if (!ptyHandle) throw new Error("No PTY spawned — call spawn() first");
|
|
744
|
+
return ptyHandle;
|
|
745
|
+
}
|
|
746
|
+
function click(x, y, options) {
|
|
747
|
+
const pty = requirePty();
|
|
748
|
+
const col = x + 1;
|
|
749
|
+
const row = y + 1;
|
|
750
|
+
const btn = sgrButton(options);
|
|
751
|
+
pty.write(`\x1b[<${btn};${col};${row}M`);
|
|
752
|
+
pty.write(`\x1b[<${btn};${col};${row}m`);
|
|
753
|
+
}
|
|
754
|
+
async function dblclick(x, y, options) {
|
|
755
|
+
const delay = options?.delay ?? 50;
|
|
756
|
+
click(x, y, options);
|
|
757
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
758
|
+
click(x, y, options);
|
|
759
|
+
}
|
|
760
|
+
function mouseDown(x, y, options) {
|
|
761
|
+
const pty = requirePty();
|
|
762
|
+
const btn = sgrButton(options);
|
|
763
|
+
pty.write(`\x1b[<${btn};${x + 1};${y + 1}M`);
|
|
764
|
+
}
|
|
765
|
+
function mouseUp(x, y, options) {
|
|
766
|
+
const pty = requirePty();
|
|
767
|
+
const btn = sgrButton(options);
|
|
768
|
+
pty.write(`\x1b[<${btn};${x + 1};${y + 1}m`);
|
|
769
|
+
}
|
|
770
|
+
function mouseMove(x, y, options) {
|
|
771
|
+
const pty = requirePty();
|
|
772
|
+
const btn = 32 + sgrButton(options);
|
|
773
|
+
pty.write(`\x1b[<${btn};${x + 1};${y + 1}M`);
|
|
774
|
+
}
|
|
775
|
+
function wheel(deltaX, deltaY, options) {
|
|
776
|
+
const pty = requirePty();
|
|
777
|
+
const col = (options?.x ?? 0) + 1;
|
|
778
|
+
const row = (options?.y ?? 0) + 1;
|
|
779
|
+
let mods = 0;
|
|
780
|
+
if (options?.shift) mods += 4;
|
|
781
|
+
if (options?.alt) mods += 8;
|
|
782
|
+
if (options?.ctrl) mods += 16;
|
|
783
|
+
if (deltaY < 0) for (let i = 0; i < Math.abs(deltaY); i++) pty.write(`\x1b[<${64 + mods};${col};${row}M`);
|
|
784
|
+
else if (deltaY > 0) for (let i = 0; i < deltaY; i++) pty.write(`\x1b[<${65 + mods};${col};${row}M`);
|
|
785
|
+
}
|
|
786
|
+
/** @deprecated Use `await expect(term.screen).toContainText("text", { timeout })` instead. */
|
|
787
|
+
async function waitFor(text, timeout = DEFAULT_WAIT_TIMEOUT) {
|
|
788
|
+
const start = Date.now();
|
|
789
|
+
while (Date.now() - start < timeout) {
|
|
790
|
+
if (getText().includes(text)) return;
|
|
791
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
792
|
+
}
|
|
793
|
+
throw new Error(`Timeout waiting for "${text}" after ${timeout}ms`);
|
|
794
|
+
}
|
|
795
|
+
async function waitForStable(stableMs = DEFAULT_STABLE_MS, timeout = DEFAULT_WAIT_TIMEOUT) {
|
|
796
|
+
const start = Date.now();
|
|
797
|
+
let lastContent = "";
|
|
798
|
+
let stableStart = Date.now();
|
|
799
|
+
while (Date.now() - start < timeout) {
|
|
800
|
+
const content = getText();
|
|
801
|
+
if (content === lastContent) {
|
|
802
|
+
if (Date.now() - stableStart >= stableMs) return;
|
|
803
|
+
} else {
|
|
804
|
+
lastContent = content;
|
|
805
|
+
stableStart = Date.now();
|
|
806
|
+
}
|
|
807
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
808
|
+
}
|
|
809
|
+
throw new Error(`Terminal did not stabilize within ${timeout}ms`);
|
|
810
|
+
}
|
|
811
|
+
function find(text) {
|
|
812
|
+
const lines = getText().split("\n");
|
|
813
|
+
for (let row = 0; row < lines.length; row++) {
|
|
814
|
+
const col = lines[row].indexOf(text);
|
|
815
|
+
if (col !== -1) return {
|
|
816
|
+
row,
|
|
817
|
+
col,
|
|
818
|
+
text
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
function findAll(pattern) {
|
|
824
|
+
const lines = getText().split("\n");
|
|
825
|
+
const results = [];
|
|
826
|
+
for (let row = 0; row < lines.length; row++) {
|
|
827
|
+
const line = lines[row];
|
|
828
|
+
const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
|
|
829
|
+
let match;
|
|
830
|
+
while ((match = re.exec(line)) !== null) {
|
|
831
|
+
results.push({
|
|
832
|
+
row,
|
|
833
|
+
col: match.index,
|
|
834
|
+
text: match[0]
|
|
835
|
+
});
|
|
836
|
+
if (match[0].length === 0) re.lastIndex++;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return results;
|
|
840
|
+
}
|
|
841
|
+
function screenshot(svgOptions) {
|
|
842
|
+
return screenshotSvg(terminal, svgOptions);
|
|
843
|
+
}
|
|
844
|
+
function screenshotAsPng(pngOptions) {
|
|
845
|
+
return screenshotPng(terminal, pngOptions);
|
|
846
|
+
}
|
|
847
|
+
function resize(newCols, newRows) {
|
|
848
|
+
if (closed) throw new Error("Terminal is closed");
|
|
849
|
+
cols = newCols;
|
|
850
|
+
rows = newRows;
|
|
851
|
+
backend.resize(newCols, newRows);
|
|
852
|
+
if (ptyHandle?.alive) ptyHandle.resize(newCols, newRows);
|
|
853
|
+
}
|
|
854
|
+
async function close() {
|
|
855
|
+
if (closed) return;
|
|
856
|
+
closed = true;
|
|
857
|
+
backend.onResponse = void 0;
|
|
858
|
+
if (ptyHandle) {
|
|
859
|
+
await ptyHandle.close();
|
|
860
|
+
ptyHandle = null;
|
|
861
|
+
}
|
|
862
|
+
backend.destroy();
|
|
863
|
+
}
|
|
864
|
+
const terminal = {
|
|
865
|
+
get cols() {
|
|
866
|
+
return cols;
|
|
867
|
+
},
|
|
868
|
+
get rows() {
|
|
869
|
+
return rows;
|
|
870
|
+
},
|
|
871
|
+
get backend() {
|
|
872
|
+
return backend;
|
|
873
|
+
},
|
|
874
|
+
getText,
|
|
875
|
+
getTextRange,
|
|
876
|
+
getCell,
|
|
877
|
+
getLine,
|
|
878
|
+
getLines,
|
|
879
|
+
getCursor,
|
|
880
|
+
getMode,
|
|
881
|
+
getTitle,
|
|
882
|
+
getScrollback,
|
|
883
|
+
get screen() {
|
|
884
|
+
return createScreenView(backend);
|
|
885
|
+
},
|
|
886
|
+
get scrollback() {
|
|
887
|
+
return createScrollbackView(backend);
|
|
888
|
+
},
|
|
889
|
+
get buffer() {
|
|
890
|
+
return createBufferView(backend);
|
|
891
|
+
},
|
|
892
|
+
get viewport() {
|
|
893
|
+
return createViewportView(backend);
|
|
894
|
+
},
|
|
895
|
+
row(n) {
|
|
896
|
+
const { totalLines, screenLines } = backend.getScrollback();
|
|
897
|
+
const base = totalLines - screenLines;
|
|
898
|
+
const screenRow = n >= 0 ? n : screenLines + n;
|
|
899
|
+
return createRowView(backend, base + screenRow, screenRow);
|
|
900
|
+
},
|
|
901
|
+
cell(r, c) {
|
|
902
|
+
const { totalLines, screenLines } = backend.getScrollback();
|
|
903
|
+
const base = totalLines - screenLines;
|
|
904
|
+
const screenRow = r >= 0 ? r : screenLines + r;
|
|
905
|
+
return createCellView(backend.getCell(base + screenRow, c), screenRow, c);
|
|
906
|
+
},
|
|
907
|
+
range(r1, c1, r2, c2) {
|
|
908
|
+
return createRangeView(backend, r1, c1, r2, c2);
|
|
909
|
+
},
|
|
910
|
+
firstRow() {
|
|
911
|
+
return this.row(0);
|
|
912
|
+
},
|
|
913
|
+
lastRow() {
|
|
914
|
+
return this.row(-1);
|
|
915
|
+
},
|
|
916
|
+
feed,
|
|
917
|
+
spawn,
|
|
918
|
+
get alive() {
|
|
919
|
+
return ptyHandle?.alive ?? false;
|
|
920
|
+
},
|
|
921
|
+
get exitInfo() {
|
|
922
|
+
return ptyHandle?.exitInfo ?? null;
|
|
923
|
+
},
|
|
924
|
+
press,
|
|
925
|
+
type,
|
|
926
|
+
clipboardWrites,
|
|
927
|
+
click,
|
|
928
|
+
dblclick,
|
|
929
|
+
mouseDown,
|
|
930
|
+
mouseUp,
|
|
931
|
+
mouseMove,
|
|
932
|
+
wheel,
|
|
933
|
+
waitFor,
|
|
934
|
+
waitForStable,
|
|
935
|
+
find,
|
|
936
|
+
findAll,
|
|
937
|
+
screenshotSvg: screenshot,
|
|
938
|
+
screenshotPng: screenshotAsPng,
|
|
939
|
+
resize,
|
|
940
|
+
close,
|
|
941
|
+
[Symbol.asyncDispose]: close
|
|
942
|
+
};
|
|
943
|
+
return terminal;
|
|
944
|
+
}
|
|
945
|
+
var POLL_INTERVAL, DEFAULT_COLS, DEFAULT_ROWS, DEFAULT_WAIT_TIMEOUT, DEFAULT_STABLE_MS, encoder;
|
|
946
|
+
var init_terminal = __esmMin((() => {
|
|
947
|
+
init_key_mapping();
|
|
948
|
+
init_pty();
|
|
949
|
+
init_svg();
|
|
950
|
+
init_png();
|
|
951
|
+
init_views();
|
|
952
|
+
POLL_INTERVAL = 50;
|
|
953
|
+
DEFAULT_COLS = 80;
|
|
954
|
+
DEFAULT_ROWS = 24;
|
|
955
|
+
DEFAULT_WAIT_TIMEOUT = 5e3;
|
|
956
|
+
DEFAULT_STABLE_MS = 200;
|
|
957
|
+
encoder = new TextEncoder();
|
|
958
|
+
}));
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region ../../termless/src/backends.ts
|
|
961
|
+
/**
|
|
962
|
+
* Backend registry — one function, everything derived.
|
|
963
|
+
*
|
|
964
|
+
* @example
|
|
965
|
+
* ```typescript
|
|
966
|
+
* import { backend } from "termless"
|
|
967
|
+
*
|
|
968
|
+
* const b = await backend("ghostty")
|
|
969
|
+
* const b = await backend("xtermjs", { version: "5.4.0" })
|
|
970
|
+
* ```
|
|
971
|
+
*/
|
|
972
|
+
function manifest() {
|
|
973
|
+
if (_manifest) return _manifest;
|
|
974
|
+
const raw = JSON.parse(readFileSync(MANIFEST_PATH, "utf-8"));
|
|
975
|
+
const backends = {};
|
|
976
|
+
for (const [name, entry] of Object.entries(raw.backends)) backends[name] = {
|
|
977
|
+
package: entry.package,
|
|
978
|
+
upstream: entry.upstream ?? null,
|
|
979
|
+
version: entry.upstreamVersion ?? null,
|
|
980
|
+
type: entry.type,
|
|
981
|
+
default: entry.default,
|
|
982
|
+
platforms: entry.platforms,
|
|
983
|
+
label: entry.label,
|
|
984
|
+
description: entry.description,
|
|
985
|
+
url: entry.url,
|
|
986
|
+
caveat: entry.caveat,
|
|
987
|
+
slug: entry.slug,
|
|
988
|
+
terminal: entry.terminal
|
|
989
|
+
};
|
|
990
|
+
_manifest = {
|
|
991
|
+
version: raw.version,
|
|
992
|
+
backends
|
|
993
|
+
};
|
|
994
|
+
return _manifest;
|
|
995
|
+
}
|
|
996
|
+
function hasFilesWithExt(dir, ext, subdirs = []) {
|
|
997
|
+
const dirs = [dir, ...subdirs.map((s) => join(dir, s))];
|
|
998
|
+
for (const d of dirs) {
|
|
999
|
+
if (!existsSync(d)) continue;
|
|
1000
|
+
try {
|
|
1001
|
+
for (const f of readdirSync(d)) if (f.endsWith(ext)) return true;
|
|
1002
|
+
} catch {}
|
|
1003
|
+
}
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
function resolveModule(pkg, opts) {
|
|
1007
|
+
return async () => {
|
|
1008
|
+
const mod = await import(pkg);
|
|
1009
|
+
return typeof mod.resolve === "function" ? mod.resolve(opts) : mod[Object.keys(mod).find((k) => k.startsWith("create"))](opts);
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
/** Run a backend's build/build.sh, falling back to nix if tools aren't in PATH. */
|
|
1013
|
+
function runBuildScript(pkgDir) {
|
|
1014
|
+
if (!existsSync(join(pkgDir, "build", "build.sh"))) return;
|
|
1015
|
+
console.log(` Building in ${pkgDir}...`);
|
|
1016
|
+
try {
|
|
1017
|
+
execSync(`bash build/build.sh`, {
|
|
1018
|
+
cwd: pkgDir,
|
|
1019
|
+
stdio: "inherit"
|
|
1020
|
+
});
|
|
1021
|
+
} catch {
|
|
1022
|
+
const flakeDir = join(__dirname, "..");
|
|
1023
|
+
if (existsSync(join(flakeDir, "flake.nix"))) execSync(`nix develop ${flakeDir} --command bash build/build.sh`, {
|
|
1024
|
+
cwd: pkgDir,
|
|
1025
|
+
stdio: "inherit"
|
|
1026
|
+
});
|
|
1027
|
+
else throw new Error(`Build failed. Install dependencies or use the nix flake: nix develop`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
/** Build a backend's native artifacts if not already ready. */
|
|
1031
|
+
function buildBackend(name) {
|
|
1032
|
+
const e = manifest().backends[name];
|
|
1033
|
+
if (!e) throw new Error(`Unknown backend: ${name}`);
|
|
1034
|
+
const type = backendTypes[e.type];
|
|
1035
|
+
if (!type) return;
|
|
1036
|
+
const pkgDir = findPackageDir(e.package);
|
|
1037
|
+
if (!pkgDir) return;
|
|
1038
|
+
if (!type.isReady(pkgDir)) type.build(pkgDir);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Resolve a backend by name. The one function you need.
|
|
1042
|
+
*
|
|
1043
|
+
* @example
|
|
1044
|
+
* ```typescript
|
|
1045
|
+
* const b = await backend("ghostty")
|
|
1046
|
+
* const b = await backend("xtermjs", { version: "5.4.0" })
|
|
1047
|
+
* ```
|
|
1048
|
+
*/
|
|
1049
|
+
async function backend(name, opts) {
|
|
1050
|
+
const m = manifest();
|
|
1051
|
+
const entry = m.backends[name];
|
|
1052
|
+
if (!entry) throw new Error(`Unknown backend "${name}". Available: ${Object.keys(m.backends).join(", ")}`);
|
|
1053
|
+
const type = backendTypes[entry.type];
|
|
1054
|
+
if (!type) throw new Error(`Unknown backend type "${entry.type}" for "${name}"`);
|
|
1055
|
+
if (opts?.version && opts.version !== entry.version) return resolveVersioned(name, entry, type, opts.version, opts);
|
|
1056
|
+
try {
|
|
1057
|
+
import.meta.resolve(entry.package);
|
|
1058
|
+
} catch {
|
|
1059
|
+
throw new Error(`Backend "${name}" is not installed.\nRun: bunx termless backend install ${name}`);
|
|
1060
|
+
}
|
|
1061
|
+
const pkgDir = findPackageDir(entry.package);
|
|
1062
|
+
if (pkgDir && !type.isReady(pkgDir)) throw new Error(`Backend "${name}" is installed but not built.\nRun: cd ${pkgDir} && ${getBuildHint(entry)}`);
|
|
1063
|
+
return type.resolve(entry.package, opts);
|
|
1064
|
+
}
|
|
1065
|
+
/** Check if a backend is installed and ready. */
|
|
1066
|
+
function isReady(name) {
|
|
1067
|
+
const entry = manifest().backends[name];
|
|
1068
|
+
if (!entry) return false;
|
|
1069
|
+
try {
|
|
1070
|
+
import.meta.resolve(entry.package);
|
|
1071
|
+
} catch {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
const type = backendTypes[entry.type];
|
|
1075
|
+
if (!type) return false;
|
|
1076
|
+
const pkgDir = findPackageDir(entry.package);
|
|
1077
|
+
return pkgDir ? type.isReady(pkgDir) : false;
|
|
1078
|
+
}
|
|
1079
|
+
/** List all backend names. */
|
|
1080
|
+
function backends() {
|
|
1081
|
+
return Object.keys(manifest().backends);
|
|
1082
|
+
}
|
|
1083
|
+
/** Get entry for a backend. */
|
|
1084
|
+
function entry(name) {
|
|
1085
|
+
return manifest().backends[name];
|
|
1086
|
+
}
|
|
1087
|
+
/** Create a Terminal by backend name. */
|
|
1088
|
+
async function createTerminalByName(name, opts) {
|
|
1089
|
+
return createTerminal({
|
|
1090
|
+
backend: await backend(name, opts),
|
|
1091
|
+
...opts
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
async function resolveVersioned(name, entry, type, version, opts) {
|
|
1095
|
+
if (entry.type === "native") throw new Error(`Version-pinned resolution for native backend "${name}" requires nix.\nRun: nix develop .#${name}-${version.replace(/\./g, "_")}`);
|
|
1096
|
+
if (!entry.upstream) throw new Error(`Backend "${name}" has no upstream to version-pin.`);
|
|
1097
|
+
const cacheDir = ensureCachedVersion(entry.upstream, version);
|
|
1098
|
+
const origNodePath = process.env.NODE_PATH;
|
|
1099
|
+
process.env.NODE_PATH = join(cacheDir, "node_modules") + (origNodePath ? `:${origNodePath}` : "");
|
|
1100
|
+
try {
|
|
1101
|
+
return await type.resolve(entry.package, opts);
|
|
1102
|
+
} finally {
|
|
1103
|
+
if (origNodePath) process.env.NODE_PATH = origNodePath;
|
|
1104
|
+
else delete process.env.NODE_PATH;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Install an upstream package at a specific version to the cache directory.
|
|
1109
|
+
* Returns the cache dir path (contains node_modules/).
|
|
1110
|
+
* Shared by backend() version-pinned resolution and census versioned runs.
|
|
1111
|
+
*/
|
|
1112
|
+
function ensureCachedVersion(upstream, version) {
|
|
1113
|
+
const cacheDir = join(CACHE_DIR, `${upstream.replace(/[/@]/g, "_")}-${version}`);
|
|
1114
|
+
if (!existsSync(join(cacheDir, "node_modules"))) {
|
|
1115
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
1116
|
+
writeFileSync(join(cacheDir, "package.json"), JSON.stringify({
|
|
1117
|
+
private: true,
|
|
1118
|
+
dependencies: { [upstream]: version }
|
|
1119
|
+
}));
|
|
1120
|
+
execSync("bun install --no-save", {
|
|
1121
|
+
cwd: cacheDir,
|
|
1122
|
+
stdio: "pipe"
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
return cacheDir;
|
|
1126
|
+
}
|
|
1127
|
+
function findPackageDir(packageName) {
|
|
1128
|
+
try {
|
|
1129
|
+
let dir = dirname(fileURLToPath(import.meta.resolve(packageName)));
|
|
1130
|
+
for (let i = 0; i < 10; i++) {
|
|
1131
|
+
if (existsSync(join(dir, "package.json"))) try {
|
|
1132
|
+
if (JSON.parse(readFileSync(join(dir, "package.json"), "utf-8")).name === packageName) return dir;
|
|
1133
|
+
} catch {}
|
|
1134
|
+
const parent = dirname(dir);
|
|
1135
|
+
if (parent === dir) break;
|
|
1136
|
+
dir = parent;
|
|
1137
|
+
}
|
|
1138
|
+
} catch {}
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
function getBuildHint(entry) {
|
|
1142
|
+
if (entry.type === "native") return "cargo build --release";
|
|
1143
|
+
if (entry.type === "wasm") return "bash build/build.sh";
|
|
1144
|
+
return "bun install";
|
|
1145
|
+
}
|
|
1146
|
+
var __dirname, MANIFEST_PATH, _manifest, backendTypes, CACHE_DIR;
|
|
1147
|
+
var init_backends = __esmMin((() => {
|
|
1148
|
+
init_terminal();
|
|
1149
|
+
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
1150
|
+
MANIFEST_PATH = join(__dirname, "..", "backends.json");
|
|
1151
|
+
_manifest = null;
|
|
1152
|
+
backendTypes = {
|
|
1153
|
+
js: {
|
|
1154
|
+
isReady: () => true,
|
|
1155
|
+
build: () => {},
|
|
1156
|
+
resolve: async (pkg, opts) => resolveModule(pkg, opts)()
|
|
1157
|
+
},
|
|
1158
|
+
wasm: {
|
|
1159
|
+
isReady: (pkgDir) => {
|
|
1160
|
+
return hasFilesWithExt(pkgDir, ".wasm", ["wasm", "build"]) || !existsSync(join(pkgDir, "build"));
|
|
1161
|
+
},
|
|
1162
|
+
build: (pkgDir) => runBuildScript(pkgDir),
|
|
1163
|
+
resolve: async (pkg, opts) => resolveModule(pkg, opts)()
|
|
1164
|
+
},
|
|
1165
|
+
native: {
|
|
1166
|
+
isReady: (pkgDir) => hasFilesWithExt(pkgDir, ".node", ["build", "native"]),
|
|
1167
|
+
build: (pkgDir) => runBuildScript(pkgDir),
|
|
1168
|
+
resolve: async (pkg, opts) => resolveModule(pkg, opts)()
|
|
1169
|
+
},
|
|
1170
|
+
os: {
|
|
1171
|
+
isReady: () => true,
|
|
1172
|
+
build: () => {},
|
|
1173
|
+
resolve: async (pkg, opts) => resolveModule(pkg, opts)()
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
CACHE_DIR = join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "termless", "backends");
|
|
1177
|
+
}));
|
|
1178
|
+
//#endregion
|
|
1179
|
+
export { screenshotPng as _, ensureCachedVersion as a, isReady as c, init_terminal as d, createCellView as f, init_png as g, init_views as h, createTerminalByName as i, manifest as l, createRowView as m, backends as n, entry as o, createRegionView as p, buildBackend as r, init_backends as s, backend as t, createTerminal as u, init_svg as v, screenshotSvg as y };
|