@aigne/afs-ui 1.11.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/aup-protocol.cjs +235 -0
- package/dist/aup-protocol.d.cts +78 -0
- package/dist/aup-protocol.d.cts.map +1 -0
- package/dist/aup-protocol.d.mts +78 -0
- package/dist/aup-protocol.d.mts.map +1 -0
- package/dist/aup-protocol.mjs +235 -0
- package/dist/aup-protocol.mjs.map +1 -0
- package/dist/aup-registry.cjs +2489 -0
- package/dist/aup-registry.mjs +2487 -0
- package/dist/aup-registry.mjs.map +1 -0
- package/dist/aup-spec.cjs +1467 -0
- package/dist/aup-spec.mjs +1466 -0
- package/dist/aup-spec.mjs.map +1 -0
- package/dist/aup-types.cjs +165 -0
- package/dist/aup-types.d.cts +157 -0
- package/dist/aup-types.d.cts.map +1 -0
- package/dist/aup-types.d.mts +157 -0
- package/dist/aup-types.d.mts.map +1 -0
- package/dist/aup-types.mjs +157 -0
- package/dist/aup-types.mjs.map +1 -0
- package/dist/backend.cjs +14 -0
- package/dist/backend.d.cts +104 -0
- package/dist/backend.d.cts.map +1 -0
- package/dist/backend.d.mts +104 -0
- package/dist/backend.d.mts.map +1 -0
- package/dist/backend.mjs +13 -0
- package/dist/backend.mjs.map +1 -0
- package/dist/degradation.cjs +85 -0
- package/dist/degradation.d.cts +17 -0
- package/dist/degradation.d.cts.map +1 -0
- package/dist/degradation.d.mts +17 -0
- package/dist/degradation.d.mts.map +1 -0
- package/dist/degradation.mjs +84 -0
- package/dist/degradation.mjs.map +1 -0
- package/dist/index.cjs +36 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +13 -0
- package/dist/runtime.cjs +117 -0
- package/dist/runtime.d.cts +59 -0
- package/dist/runtime.d.cts.map +1 -0
- package/dist/runtime.d.mts +59 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +118 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/session.cjs +159 -0
- package/dist/session.d.cts +80 -0
- package/dist/session.d.cts.map +1 -0
- package/dist/session.d.mts +80 -0
- package/dist/session.d.mts.map +1 -0
- package/dist/session.mjs +159 -0
- package/dist/session.mjs.map +1 -0
- package/dist/snapshot.cjs +162 -0
- package/dist/snapshot.mjs +163 -0
- package/dist/snapshot.mjs.map +1 -0
- package/dist/term-page.cjs +264 -0
- package/dist/term-page.mjs +264 -0
- package/dist/term-page.mjs.map +1 -0
- package/dist/term.cjs +295 -0
- package/dist/term.d.cts +84 -0
- package/dist/term.d.cts.map +1 -0
- package/dist/term.d.mts +84 -0
- package/dist/term.d.mts.map +1 -0
- package/dist/term.mjs +296 -0
- package/dist/term.mjs.map +1 -0
- package/dist/tty.cjs +136 -0
- package/dist/tty.d.cts +53 -0
- package/dist/tty.d.cts.map +1 -0
- package/dist/tty.d.mts +53 -0
- package/dist/tty.d.mts.map +1 -0
- package/dist/tty.mjs +135 -0
- package/dist/tty.mjs.map +1 -0
- package/dist/ui-provider.cjs +4615 -0
- package/dist/ui-provider.d.cts +307 -0
- package/dist/ui-provider.d.cts.map +1 -0
- package/dist/ui-provider.d.mts +307 -0
- package/dist/ui-provider.d.mts.map +1 -0
- package/dist/ui-provider.mjs +4616 -0
- package/dist/ui-provider.mjs.map +1 -0
- package/dist/web-page/core.cjs +1388 -0
- package/dist/web-page/core.mjs +1387 -0
- package/dist/web-page/core.mjs.map +1 -0
- package/dist/web-page/css.cjs +1699 -0
- package/dist/web-page/css.mjs +1698 -0
- package/dist/web-page/css.mjs.map +1 -0
- package/dist/web-page/icons.cjs +248 -0
- package/dist/web-page/icons.mjs +248 -0
- package/dist/web-page/icons.mjs.map +1 -0
- package/dist/web-page/overlay-themes.cjs +514 -0
- package/dist/web-page/overlay-themes.mjs +513 -0
- package/dist/web-page/overlay-themes.mjs.map +1 -0
- package/dist/web-page/renderers/action.cjs +72 -0
- package/dist/web-page/renderers/action.mjs +72 -0
- package/dist/web-page/renderers/action.mjs.map +1 -0
- package/dist/web-page/renderers/broadcast.cjs +160 -0
- package/dist/web-page/renderers/broadcast.mjs +160 -0
- package/dist/web-page/renderers/broadcast.mjs.map +1 -0
- package/dist/web-page/renderers/calendar.cjs +137 -0
- package/dist/web-page/renderers/calendar.mjs +137 -0
- package/dist/web-page/renderers/calendar.mjs.map +1 -0
- package/dist/web-page/renderers/canvas.cjs +173 -0
- package/dist/web-page/renderers/canvas.mjs +173 -0
- package/dist/web-page/renderers/canvas.mjs.map +1 -0
- package/dist/web-page/renderers/cdn-loader.cjs +25 -0
- package/dist/web-page/renderers/cdn-loader.mjs +25 -0
- package/dist/web-page/renderers/cdn-loader.mjs.map +1 -0
- package/dist/web-page/renderers/chart.cjs +101 -0
- package/dist/web-page/renderers/chart.mjs +101 -0
- package/dist/web-page/renderers/chart.mjs.map +1 -0
- package/dist/web-page/renderers/deck.cjs +390 -0
- package/dist/web-page/renderers/deck.mjs +390 -0
- package/dist/web-page/renderers/deck.mjs.map +1 -0
- package/dist/web-page/renderers/device.cjs +1015 -0
- package/dist/web-page/renderers/device.mjs +1015 -0
- package/dist/web-page/renderers/device.mjs.map +1 -0
- package/dist/web-page/renderers/editor.cjs +127 -0
- package/dist/web-page/renderers/editor.mjs +127 -0
- package/dist/web-page/renderers/editor.mjs.map +1 -0
- package/dist/web-page/renderers/finance-chart.cjs +178 -0
- package/dist/web-page/renderers/finance-chart.mjs +178 -0
- package/dist/web-page/renderers/finance-chart.mjs.map +1 -0
- package/dist/web-page/renderers/frame.cjs +274 -0
- package/dist/web-page/renderers/frame.mjs +274 -0
- package/dist/web-page/renderers/frame.mjs.map +1 -0
- package/dist/web-page/renderers/globe.cjs +119 -0
- package/dist/web-page/renderers/globe.mjs +119 -0
- package/dist/web-page/renderers/globe.mjs.map +1 -0
- package/dist/web-page/renderers/input.cjs +137 -0
- package/dist/web-page/renderers/input.mjs +137 -0
- package/dist/web-page/renderers/input.mjs.map +1 -0
- package/dist/web-page/renderers/list.cjs +1243 -0
- package/dist/web-page/renderers/list.mjs +1243 -0
- package/dist/web-page/renderers/list.mjs.map +1 -0
- package/dist/web-page/renderers/map.cjs +126 -0
- package/dist/web-page/renderers/map.mjs +126 -0
- package/dist/web-page/renderers/map.mjs.map +1 -0
- package/dist/web-page/renderers/media.cjs +106 -0
- package/dist/web-page/renderers/media.mjs +106 -0
- package/dist/web-page/renderers/media.mjs.map +1 -0
- package/dist/web-page/renderers/moonphase.cjs +105 -0
- package/dist/web-page/renderers/moonphase.mjs +105 -0
- package/dist/web-page/renderers/moonphase.mjs.map +1 -0
- package/dist/web-page/renderers/natal-chart.cjs +222 -0
- package/dist/web-page/renderers/natal-chart.mjs +222 -0
- package/dist/web-page/renderers/natal-chart.mjs.map +1 -0
- package/dist/web-page/renderers/overlay.cjs +531 -0
- package/dist/web-page/renderers/overlay.mjs +531 -0
- package/dist/web-page/renderers/overlay.mjs.map +1 -0
- package/dist/web-page/renderers/table.cjs +74 -0
- package/dist/web-page/renderers/table.mjs +74 -0
- package/dist/web-page/renderers/table.mjs.map +1 -0
- package/dist/web-page/renderers/terminal.cjs +30 -0
- package/dist/web-page/renderers/terminal.mjs +30 -0
- package/dist/web-page/renderers/terminal.mjs.map +1 -0
- package/dist/web-page/renderers/text.cjs +109 -0
- package/dist/web-page/renderers/text.mjs +109 -0
- package/dist/web-page/renderers/text.mjs.map +1 -0
- package/dist/web-page/renderers/ticker.cjs +133 -0
- package/dist/web-page/renderers/ticker.mjs +133 -0
- package/dist/web-page/renderers/ticker.mjs.map +1 -0
- package/dist/web-page/renderers/time.cjs +69 -0
- package/dist/web-page/renderers/time.mjs +69 -0
- package/dist/web-page/renderers/time.mjs.map +1 -0
- package/dist/web-page/renderers/unknown.cjs +20 -0
- package/dist/web-page/renderers/unknown.mjs +20 -0
- package/dist/web-page/renderers/unknown.mjs.map +1 -0
- package/dist/web-page/renderers/view.cjs +161 -0
- package/dist/web-page/renderers/view.mjs +161 -0
- package/dist/web-page/renderers/view.mjs.map +1 -0
- package/dist/web-page/renderers/wm.cjs +669 -0
- package/dist/web-page/renderers/wm.mjs +669 -0
- package/dist/web-page/renderers/wm.mjs.map +1 -0
- package/dist/web-page/skeleton.cjs +103 -0
- package/dist/web-page/skeleton.mjs +103 -0
- package/dist/web-page/skeleton.mjs.map +1 -0
- package/dist/web-page.cjs +114 -0
- package/dist/web-page.d.cts +19 -0
- package/dist/web-page.d.cts.map +1 -0
- package/dist/web-page.d.mts +19 -0
- package/dist/web-page.d.mts.map +1 -0
- package/dist/web-page.mjs +115 -0
- package/dist/web-page.mjs.map +1 -0
- package/dist/web.cjs +827 -0
- package/dist/web.d.cts +144 -0
- package/dist/web.d.cts.map +1 -0
- package/dist/web.d.mts +144 -0
- package/dist/web.d.mts.map +1 -0
- package/dist/web.mjs +828 -0
- package/dist/web.mjs.map +1 -0
- package/dist/wm-state.cjs +172 -0
- package/dist/wm-state.mjs +171 -0
- package/dist/wm-state.mjs.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
//#region src/term-page.ts
|
|
2
|
+
/**
|
|
3
|
+
* Inline HTML template for the AFS UI terminal client.
|
|
4
|
+
*
|
|
5
|
+
* Self-contained — CDN dependencies for xterm.js terminal emulator.
|
|
6
|
+
* Ported from AOS terminal surface, adapted for AFS UIBackend WS protocol.
|
|
7
|
+
*
|
|
8
|
+
* WS Protocol (server→client):
|
|
9
|
+
* { type: "output", data: "text" } — write text to terminal
|
|
10
|
+
* { type: "prompt", message, promptType, options } — prompt request
|
|
11
|
+
* { type: "clear" } — clear terminal
|
|
12
|
+
* { type: "notify", message } — notification
|
|
13
|
+
*
|
|
14
|
+
* WS Protocol (client→server):
|
|
15
|
+
* { type: "line", content: "user input" } — complete line
|
|
16
|
+
* { type: "prompt_response", value: ... } — prompt answer
|
|
17
|
+
* { type: "resize", cols, rows } — terminal resize
|
|
18
|
+
*/
|
|
19
|
+
const TERM_CLIENT_HTML = `<!DOCTYPE html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="utf-8">
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
24
|
+
<title>AFS Terminal</title>
|
|
25
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css">
|
|
26
|
+
<style>
|
|
27
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
28
|
+
html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0e14; }
|
|
29
|
+
#terminal { width: 100%; height: 100%; }
|
|
30
|
+
</style>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<div id="terminal"></div>
|
|
34
|
+
|
|
35
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/lib/xterm.min.js"><\/script>
|
|
36
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"><\/script>
|
|
37
|
+
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js"><\/script>
|
|
38
|
+
<script>
|
|
39
|
+
var term = new window.Terminal({
|
|
40
|
+
cursorBlink: true,
|
|
41
|
+
fontSize: 14,
|
|
42
|
+
lineHeight: 1.2,
|
|
43
|
+
fontFamily: 'Menlo, "Fira Code", "Cascadia Code", monospace',
|
|
44
|
+
theme: {
|
|
45
|
+
background: '#0a0e14',
|
|
46
|
+
foreground: '#b3b1ad',
|
|
47
|
+
cursor: '#e6b450',
|
|
48
|
+
selectionBackground: '#1d3b53',
|
|
49
|
+
green: '#91b362',
|
|
50
|
+
brightGreen: '#a6cc70',
|
|
51
|
+
red: '#f07178',
|
|
52
|
+
brightRed: '#ff8f80',
|
|
53
|
+
yellow: '#e6b450',
|
|
54
|
+
brightYellow: '#ffee99',
|
|
55
|
+
blue: '#59c2ff',
|
|
56
|
+
brightBlue: '#73d0ff',
|
|
57
|
+
magenta: '#d2a6ff',
|
|
58
|
+
brightMagenta: '#dfbfff',
|
|
59
|
+
cyan: '#95e6cb',
|
|
60
|
+
brightCyan: '#a8e6cf',
|
|
61
|
+
white: '#b3b1ad',
|
|
62
|
+
brightWhite: '#ffffff',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
var fitAddon = new window.FitAddon.FitAddon();
|
|
66
|
+
term.loadAddon(fitAddon);
|
|
67
|
+
term.open(document.getElementById('terminal'));
|
|
68
|
+
|
|
69
|
+
// WebGL renderer for pixel-perfect rendering
|
|
70
|
+
try {
|
|
71
|
+
var webglAddon = new window.WebglAddon.WebglAddon();
|
|
72
|
+
webglAddon.onContextLoss(function() { webglAddon.dispose(); });
|
|
73
|
+
term.loadAddon(webglAddon);
|
|
74
|
+
} catch(e) {
|
|
75
|
+
// WebGL not available — fall back to canvas
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fitAddon.fit();
|
|
79
|
+
|
|
80
|
+
// ── State ──
|
|
81
|
+
var lineBuffer = '';
|
|
82
|
+
var ws = null;
|
|
83
|
+
var currentPrompt = null;
|
|
84
|
+
|
|
85
|
+
function writeAnsi(text) {
|
|
86
|
+
// Convert \\n to \\r\\n for xterm
|
|
87
|
+
var lines = text.split('\\n');
|
|
88
|
+
for (var i = 0; i < lines.length; i++) {
|
|
89
|
+
if (i > 0) term.write('\\r\\n');
|
|
90
|
+
term.write(lines[i]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function writePromptText() {
|
|
95
|
+
if (currentPrompt) return;
|
|
96
|
+
term.write('\\x1b[33m> \\x1b[0m');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Replace the current line buffer, updating the terminal display. */
|
|
100
|
+
function replaceLine(text) {
|
|
101
|
+
if (lineBuffer.length > 0) {
|
|
102
|
+
term.write('\\x1b[' + lineBuffer.length + 'D');
|
|
103
|
+
term.write('\\x1b[K');
|
|
104
|
+
}
|
|
105
|
+
lineBuffer = text;
|
|
106
|
+
term.write(text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── WebSocket ──
|
|
110
|
+
function connect() {
|
|
111
|
+
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
112
|
+
ws = new WebSocket(proto + '//' + location.host);
|
|
113
|
+
|
|
114
|
+
ws.onopen = function() {
|
|
115
|
+
term.writeln('\\x1b[32m● connected\\x1b[0m');
|
|
116
|
+
writePromptText();
|
|
117
|
+
// Send initial size
|
|
118
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
119
|
+
ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
ws.onclose = function() {
|
|
124
|
+
term.writeln('\\r\\n\\x1b[31m● disconnected — reconnecting...\\x1b[0m');
|
|
125
|
+
setTimeout(connect, 2000);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
ws.onerror = function() {
|
|
129
|
+
term.writeln('\\r\\n\\x1b[31m● connection error\\x1b[0m');
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
ws.onmessage = function(evt) {
|
|
133
|
+
var msg;
|
|
134
|
+
try { msg = JSON.parse(evt.data); } catch(e) { return; }
|
|
135
|
+
|
|
136
|
+
if (msg.type === 'output') {
|
|
137
|
+
writeAnsi(msg.data);
|
|
138
|
+
if (!msg.data.endsWith('\\n')) term.write('\\r\\n');
|
|
139
|
+
writePromptText();
|
|
140
|
+
} else if (msg.type === 'prompt') {
|
|
141
|
+
handlePrompt(msg);
|
|
142
|
+
} else if (msg.type === 'clear') {
|
|
143
|
+
term.clear();
|
|
144
|
+
writePromptText();
|
|
145
|
+
} else if (msg.type === 'notify') {
|
|
146
|
+
term.write('\\x1b[36m[notice] ' + msg.message + '\\x1b[0m\\r\\n');
|
|
147
|
+
writePromptText();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function handlePrompt(msg) {
|
|
153
|
+
currentPrompt = msg;
|
|
154
|
+
var promptType = msg.promptType || 'text';
|
|
155
|
+
|
|
156
|
+
if (promptType === 'confirm') {
|
|
157
|
+
writeAnsi(msg.message + ' (y/n) ');
|
|
158
|
+
} else if (promptType === 'select' && msg.options) {
|
|
159
|
+
writeAnsi(msg.message + '\\n');
|
|
160
|
+
for (var i = 0; i < msg.options.length; i++) {
|
|
161
|
+
writeAnsi(' ' + (i + 1) + '. ' + msg.options[i] + '\\n');
|
|
162
|
+
}
|
|
163
|
+
term.write('Choice: ');
|
|
164
|
+
} else if (promptType === 'multiselect' && msg.options) {
|
|
165
|
+
writeAnsi(msg.message + '\\n');
|
|
166
|
+
for (var i = 0; i < msg.options.length; i++) {
|
|
167
|
+
writeAnsi(' ' + (i + 1) + '. ' + msg.options[i] + '\\n');
|
|
168
|
+
}
|
|
169
|
+
term.write('Choices (comma-separated): ');
|
|
170
|
+
} else {
|
|
171
|
+
// text or password
|
|
172
|
+
writeAnsi(msg.message + ' ');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function sendPromptResponse(value) {
|
|
177
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
178
|
+
ws.send(JSON.stringify({ type: 'prompt_response', value: value }));
|
|
179
|
+
}
|
|
180
|
+
currentPrompt = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function submitLine() {
|
|
184
|
+
var text = lineBuffer;
|
|
185
|
+
lineBuffer = '';
|
|
186
|
+
term.writeln('');
|
|
187
|
+
|
|
188
|
+
if (currentPrompt) {
|
|
189
|
+
var promptType = currentPrompt.promptType || 'text';
|
|
190
|
+
if (promptType === 'confirm') {
|
|
191
|
+
sendPromptResponse(text.trim().toLowerCase().indexOf('y') === 0);
|
|
192
|
+
} else if (promptType === 'select') {
|
|
193
|
+
var idx = parseInt(text.trim(), 10) - 1;
|
|
194
|
+
if (idx >= 0 && currentPrompt.options && idx < currentPrompt.options.length) {
|
|
195
|
+
sendPromptResponse(currentPrompt.options[idx]);
|
|
196
|
+
} else if (currentPrompt.options && currentPrompt.options.length > 0) {
|
|
197
|
+
sendPromptResponse(currentPrompt.options[0]);
|
|
198
|
+
}
|
|
199
|
+
} else if (promptType === 'multiselect') {
|
|
200
|
+
var indices = text.split(',').map(function(s) { return parseInt(s.trim(), 10) - 1; });
|
|
201
|
+
var selected = [];
|
|
202
|
+
for (var i = 0; i < indices.length; i++) {
|
|
203
|
+
if (indices[i] >= 0 && currentPrompt.options && indices[i] < currentPrompt.options.length) {
|
|
204
|
+
selected.push(currentPrompt.options[indices[i]]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
sendPromptResponse(selected);
|
|
208
|
+
} else {
|
|
209
|
+
sendPromptResponse(text);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
213
|
+
ws.send(JSON.stringify({ type: 'line', content: text }));
|
|
214
|
+
}
|
|
215
|
+
writePromptText();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Input handling ──
|
|
220
|
+
term.onData(function(data) {
|
|
221
|
+
if (data === '\\r') {
|
|
222
|
+
// Enter
|
|
223
|
+
submitLine();
|
|
224
|
+
} else if (data === '\\x7f') {
|
|
225
|
+
// Backspace
|
|
226
|
+
if (lineBuffer.length > 0) {
|
|
227
|
+
lineBuffer = lineBuffer.slice(0, -1);
|
|
228
|
+
term.write('\\b \\b');
|
|
229
|
+
}
|
|
230
|
+
} else if (data === '\\x03') {
|
|
231
|
+
// Ctrl-C — clear line
|
|
232
|
+
lineBuffer = '';
|
|
233
|
+
term.write('^C\\r\\n');
|
|
234
|
+
writePromptText();
|
|
235
|
+
} else if (data >= ' ') {
|
|
236
|
+
// Printable characters
|
|
237
|
+
if (currentPrompt && currentPrompt.promptType === 'password') {
|
|
238
|
+
lineBuffer += data;
|
|
239
|
+
term.write('*');
|
|
240
|
+
} else {
|
|
241
|
+
lineBuffer += data;
|
|
242
|
+
term.write(data);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ── Resize ──
|
|
248
|
+
window.addEventListener('resize', function() {
|
|
249
|
+
fitAddon.fit();
|
|
250
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
251
|
+
ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
term.writeln('\\x1b[1;33mAFS Terminal\\x1b[0m');
|
|
256
|
+
term.writeln('');
|
|
257
|
+
connect();
|
|
258
|
+
<\/script>
|
|
259
|
+
</body>
|
|
260
|
+
</html>`;
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
export { TERM_CLIENT_HTML };
|
|
264
|
+
//# sourceMappingURL=term-page.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"term-page.mjs","names":[],"sources":["../src/term-page.ts"],"sourcesContent":["/**\n * Inline HTML template for the AFS UI terminal client.\n *\n * Self-contained — CDN dependencies for xterm.js terminal emulator.\n * Ported from AOS terminal surface, adapted for AFS UIBackend WS protocol.\n *\n * WS Protocol (server→client):\n * { type: \"output\", data: \"text\" } — write text to terminal\n * { type: \"prompt\", message, promptType, options } — prompt request\n * { type: \"clear\" } — clear terminal\n * { type: \"notify\", message } — notification\n *\n * WS Protocol (client→server):\n * { type: \"line\", content: \"user input\" } — complete line\n * { type: \"prompt_response\", value: ... } — prompt answer\n * { type: \"resize\", cols, rows } — terminal resize\n */\nexport const TERM_CLIENT_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>AFS Terminal</title>\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/@xterm/xterm@5/css/xterm.min.css\">\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0e14; }\n #terminal { width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <div id=\"terminal\"></div>\n\n <script src=\"https://cdn.jsdelivr.net/npm/@xterm/xterm@5/lib/xterm.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0/lib/addon-webgl.min.js\"></script>\n <script>\n var term = new window.Terminal({\n cursorBlink: true,\n fontSize: 14,\n lineHeight: 1.2,\n fontFamily: 'Menlo, \"Fira Code\", \"Cascadia Code\", monospace',\n theme: {\n background: '#0a0e14',\n foreground: '#b3b1ad',\n cursor: '#e6b450',\n selectionBackground: '#1d3b53',\n green: '#91b362',\n brightGreen: '#a6cc70',\n red: '#f07178',\n brightRed: '#ff8f80',\n yellow: '#e6b450',\n brightYellow: '#ffee99',\n blue: '#59c2ff',\n brightBlue: '#73d0ff',\n magenta: '#d2a6ff',\n brightMagenta: '#dfbfff',\n cyan: '#95e6cb',\n brightCyan: '#a8e6cf',\n white: '#b3b1ad',\n brightWhite: '#ffffff',\n },\n });\n var fitAddon = new window.FitAddon.FitAddon();\n term.loadAddon(fitAddon);\n term.open(document.getElementById('terminal'));\n\n // WebGL renderer for pixel-perfect rendering\n try {\n var webglAddon = new window.WebglAddon.WebglAddon();\n webglAddon.onContextLoss(function() { webglAddon.dispose(); });\n term.loadAddon(webglAddon);\n } catch(e) {\n // WebGL not available — fall back to canvas\n }\n\n fitAddon.fit();\n\n // ── State ──\n var lineBuffer = '';\n var ws = null;\n var currentPrompt = null;\n\n function writeAnsi(text) {\n // Convert \\\\n to \\\\r\\\\n for xterm\n var lines = text.split('\\\\n');\n for (var i = 0; i < lines.length; i++) {\n if (i > 0) term.write('\\\\r\\\\n');\n term.write(lines[i]);\n }\n }\n\n function writePromptText() {\n if (currentPrompt) return;\n term.write('\\\\x1b[33m> \\\\x1b[0m');\n }\n\n /** Replace the current line buffer, updating the terminal display. */\n function replaceLine(text) {\n if (lineBuffer.length > 0) {\n term.write('\\\\x1b[' + lineBuffer.length + 'D');\n term.write('\\\\x1b[K');\n }\n lineBuffer = text;\n term.write(text);\n }\n\n // ── WebSocket ──\n function connect() {\n var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';\n ws = new WebSocket(proto + '//' + location.host);\n\n ws.onopen = function() {\n term.writeln('\\\\x1b[32m● connected\\\\x1b[0m');\n writePromptText();\n // Send initial size\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));\n }\n };\n\n ws.onclose = function() {\n term.writeln('\\\\r\\\\n\\\\x1b[31m● disconnected — reconnecting...\\\\x1b[0m');\n setTimeout(connect, 2000);\n };\n\n ws.onerror = function() {\n term.writeln('\\\\r\\\\n\\\\x1b[31m● connection error\\\\x1b[0m');\n };\n\n ws.onmessage = function(evt) {\n var msg;\n try { msg = JSON.parse(evt.data); } catch(e) { return; }\n\n if (msg.type === 'output') {\n writeAnsi(msg.data);\n if (!msg.data.endsWith('\\\\n')) term.write('\\\\r\\\\n');\n writePromptText();\n } else if (msg.type === 'prompt') {\n handlePrompt(msg);\n } else if (msg.type === 'clear') {\n term.clear();\n writePromptText();\n } else if (msg.type === 'notify') {\n term.write('\\\\x1b[36m[notice] ' + msg.message + '\\\\x1b[0m\\\\r\\\\n');\n writePromptText();\n }\n };\n }\n\n function handlePrompt(msg) {\n currentPrompt = msg;\n var promptType = msg.promptType || 'text';\n\n if (promptType === 'confirm') {\n writeAnsi(msg.message + ' (y/n) ');\n } else if (promptType === 'select' && msg.options) {\n writeAnsi(msg.message + '\\\\n');\n for (var i = 0; i < msg.options.length; i++) {\n writeAnsi(' ' + (i + 1) + '. ' + msg.options[i] + '\\\\n');\n }\n term.write('Choice: ');\n } else if (promptType === 'multiselect' && msg.options) {\n writeAnsi(msg.message + '\\\\n');\n for (var i = 0; i < msg.options.length; i++) {\n writeAnsi(' ' + (i + 1) + '. ' + msg.options[i] + '\\\\n');\n }\n term.write('Choices (comma-separated): ');\n } else {\n // text or password\n writeAnsi(msg.message + ' ');\n }\n }\n\n function sendPromptResponse(value) {\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: 'prompt_response', value: value }));\n }\n currentPrompt = null;\n }\n\n function submitLine() {\n var text = lineBuffer;\n lineBuffer = '';\n term.writeln('');\n\n if (currentPrompt) {\n var promptType = currentPrompt.promptType || 'text';\n if (promptType === 'confirm') {\n sendPromptResponse(text.trim().toLowerCase().indexOf('y') === 0);\n } else if (promptType === 'select') {\n var idx = parseInt(text.trim(), 10) - 1;\n if (idx >= 0 && currentPrompt.options && idx < currentPrompt.options.length) {\n sendPromptResponse(currentPrompt.options[idx]);\n } else if (currentPrompt.options && currentPrompt.options.length > 0) {\n sendPromptResponse(currentPrompt.options[0]);\n }\n } else if (promptType === 'multiselect') {\n var indices = text.split(',').map(function(s) { return parseInt(s.trim(), 10) - 1; });\n var selected = [];\n for (var i = 0; i < indices.length; i++) {\n if (indices[i] >= 0 && currentPrompt.options && indices[i] < currentPrompt.options.length) {\n selected.push(currentPrompt.options[indices[i]]);\n }\n }\n sendPromptResponse(selected);\n } else {\n sendPromptResponse(text);\n }\n } else {\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: 'line', content: text }));\n }\n writePromptText();\n }\n }\n\n // ── Input handling ──\n term.onData(function(data) {\n if (data === '\\\\r') {\n // Enter\n submitLine();\n } else if (data === '\\\\x7f') {\n // Backspace\n if (lineBuffer.length > 0) {\n lineBuffer = lineBuffer.slice(0, -1);\n term.write('\\\\b \\\\b');\n }\n } else if (data === '\\\\x03') {\n // Ctrl-C — clear line\n lineBuffer = '';\n term.write('^C\\\\r\\\\n');\n writePromptText();\n } else if (data >= ' ') {\n // Printable characters\n if (currentPrompt && currentPrompt.promptType === 'password') {\n lineBuffer += data;\n term.write('*');\n } else {\n lineBuffer += data;\n term.write(data);\n }\n }\n });\n\n // ── Resize ──\n window.addEventListener('resize', function() {\n fitAddon.fit();\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));\n }\n });\n\n term.writeln('\\\\x1b[1;33mAFS Terminal\\\\x1b[0m');\n term.writeln('');\n connect();\n </script>\n</body>\n</html>`;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,MAAa,mBAAmB"}
|
package/dist/term.cjs
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const require_term_page = require('./term-page.cjs');
|
|
2
|
+
const require_tty = require('./tty.cjs');
|
|
3
|
+
let node_http = require("node:http");
|
|
4
|
+
let ws = require("ws");
|
|
5
|
+
|
|
6
|
+
//#region src/term.ts
|
|
7
|
+
/**
|
|
8
|
+
* TermBackend — HTTP + WebSocket based xterm.js terminal.
|
|
9
|
+
*
|
|
10
|
+
* Serves an xterm.js web page and communicates via WebSocket.
|
|
11
|
+
* Line-based I/O: client sends complete lines on Enter, server writes output text.
|
|
12
|
+
*/
|
|
13
|
+
var TermBackend = class {
|
|
14
|
+
type = "term";
|
|
15
|
+
supportedFormats = ["text"];
|
|
16
|
+
capabilities = ["text"];
|
|
17
|
+
port;
|
|
18
|
+
host;
|
|
19
|
+
server = null;
|
|
20
|
+
wss = null;
|
|
21
|
+
clients = /* @__PURE__ */ new Set();
|
|
22
|
+
sockets = /* @__PURE__ */ new Set();
|
|
23
|
+
inputSource;
|
|
24
|
+
outputHandler;
|
|
25
|
+
/** Queue for messages sent before any client connects */
|
|
26
|
+
pendingMessages = [];
|
|
27
|
+
/** Pending prompt resolve */
|
|
28
|
+
promptResolve = null;
|
|
29
|
+
/** Pending prompt message — re-sent on client reconnect */
|
|
30
|
+
pendingPromptMessage = null;
|
|
31
|
+
/** Viewport info from last resize */
|
|
32
|
+
viewportCols = 80;
|
|
33
|
+
viewportRows = 24;
|
|
34
|
+
testMode;
|
|
35
|
+
_url = null;
|
|
36
|
+
/** Per-client session tracking */
|
|
37
|
+
sessionForClient = /* @__PURE__ */ new Map();
|
|
38
|
+
createSessionCallback = null;
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.port = options.port ?? 0;
|
|
41
|
+
this.host = options.host ?? "localhost";
|
|
42
|
+
if (options.inputSource) {
|
|
43
|
+
this.testMode = true;
|
|
44
|
+
this.inputSource = options.inputSource;
|
|
45
|
+
this.outputHandler = options.stdout ? (data) => {
|
|
46
|
+
options.stdout.write(data);
|
|
47
|
+
} : () => {};
|
|
48
|
+
} else {
|
|
49
|
+
this.testMode = false;
|
|
50
|
+
this.inputSource = require_tty.createMockInputSource();
|
|
51
|
+
this.outputHandler = (data) => {
|
|
52
|
+
const payload = JSON.stringify({
|
|
53
|
+
type: "output",
|
|
54
|
+
data
|
|
55
|
+
});
|
|
56
|
+
if (this.clients.size === 0) this.pendingMessages.push(payload);
|
|
57
|
+
else this.broadcast(payload);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** URL of the running server, or null if not started. */
|
|
62
|
+
get url() {
|
|
63
|
+
return this._url;
|
|
64
|
+
}
|
|
65
|
+
/** Register a factory that creates a session for each new WebSocket client. */
|
|
66
|
+
setSessionFactory(fn) {
|
|
67
|
+
this.createSessionCallback = fn;
|
|
68
|
+
}
|
|
69
|
+
/** Start the HTTP + WebSocket server. */
|
|
70
|
+
async listen() {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const server = (0, node_http.createServer)((req, res) => {
|
|
73
|
+
if (req.url === "/" || req.url === "/index.html") {
|
|
74
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
75
|
+
res.end(require_term_page.TERM_CLIENT_HTML);
|
|
76
|
+
} else {
|
|
77
|
+
res.writeHead(404);
|
|
78
|
+
res.end("Not Found");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
server.on("connection", (socket) => {
|
|
82
|
+
this.sockets.add(socket);
|
|
83
|
+
socket.on("close", () => this.sockets.delete(socket));
|
|
84
|
+
});
|
|
85
|
+
server.on("error", (err) => {
|
|
86
|
+
server.close();
|
|
87
|
+
reject(err);
|
|
88
|
+
});
|
|
89
|
+
const bindHost = this.host === "localhost" ? "127.0.0.1" : this.host;
|
|
90
|
+
server.listen({
|
|
91
|
+
port: this.port,
|
|
92
|
+
host: bindHost,
|
|
93
|
+
exclusive: true
|
|
94
|
+
}, () => {
|
|
95
|
+
this.server = server;
|
|
96
|
+
const addr = server.address();
|
|
97
|
+
if (typeof addr === "object" && addr) this.port = addr.port;
|
|
98
|
+
this._url = `http://127.0.0.1:${this.port}`;
|
|
99
|
+
this.wss = new ws.WebSocketServer({ server });
|
|
100
|
+
this.wss.on("connection", (ws$1, req) => this.onConnection(ws$1, req));
|
|
101
|
+
resolve({
|
|
102
|
+
port: this.port,
|
|
103
|
+
host: this.host
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Shut down the server and disconnect all clients. */
|
|
109
|
+
async close() {
|
|
110
|
+
for (const ws$1 of this.clients) ws$1.terminate();
|
|
111
|
+
this.clients.clear();
|
|
112
|
+
this.sessionForClient.clear();
|
|
113
|
+
if (this.wss) {
|
|
114
|
+
this.wss.close();
|
|
115
|
+
this.wss = null;
|
|
116
|
+
}
|
|
117
|
+
if (this.server) {
|
|
118
|
+
this.server.close();
|
|
119
|
+
for (const socket of this.sockets) socket.destroy();
|
|
120
|
+
this.sockets.clear();
|
|
121
|
+
this.server = null;
|
|
122
|
+
}
|
|
123
|
+
this._url = null;
|
|
124
|
+
}
|
|
125
|
+
async write(content, options) {
|
|
126
|
+
if (options?.format && options.format !== "text") throw new Error(`Term backend does not support format: ${options.format}`);
|
|
127
|
+
if (this.testMode) {
|
|
128
|
+
this.outputHandler(content);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.outputHandler(content);
|
|
132
|
+
}
|
|
133
|
+
async read(options) {
|
|
134
|
+
const timeout = options?.timeout ?? 0;
|
|
135
|
+
if (timeout > 0) return withTimeout(this.inputSource.readLine(), timeout);
|
|
136
|
+
return this.inputSource.readLine();
|
|
137
|
+
}
|
|
138
|
+
async prompt(options) {
|
|
139
|
+
if (this.testMode) return this.ttyStylePrompt(options);
|
|
140
|
+
const msg = JSON.stringify({
|
|
141
|
+
type: "prompt",
|
|
142
|
+
message: options.message,
|
|
143
|
+
promptType: options.type,
|
|
144
|
+
options: options.options
|
|
145
|
+
});
|
|
146
|
+
this.pendingPromptMessage = msg;
|
|
147
|
+
if (this.clients.size > 0) this.broadcast(msg);
|
|
148
|
+
else this.pendingMessages.push(msg);
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
this.promptResolve = resolve;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async notify(message) {
|
|
154
|
+
if (this.testMode) {
|
|
155
|
+
this.outputHandler(`${message}\n`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.broadcast(JSON.stringify({
|
|
159
|
+
type: "notify",
|
|
160
|
+
message
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
async clear() {
|
|
164
|
+
if (this.testMode) {
|
|
165
|
+
this.outputHandler("\x1B[2J\x1B[H");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.broadcast(JSON.stringify({ type: "clear" }));
|
|
169
|
+
}
|
|
170
|
+
hasPendingInput() {
|
|
171
|
+
return this.inputSource.hasPending();
|
|
172
|
+
}
|
|
173
|
+
getViewport() {
|
|
174
|
+
return {
|
|
175
|
+
cols: this.viewportCols,
|
|
176
|
+
rows: this.viewportRows
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async dispose() {
|
|
180
|
+
await this.close();
|
|
181
|
+
}
|
|
182
|
+
onConnection(ws$1, req) {
|
|
183
|
+
const origin = req.headers.origin;
|
|
184
|
+
if (typeof origin === "string" && origin && !this.isAllowedWsOrigin(origin)) {
|
|
185
|
+
ws$1.close(1008, "Invalid origin");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.clients.add(ws$1);
|
|
189
|
+
let sessionId;
|
|
190
|
+
if (this.createSessionCallback) {
|
|
191
|
+
const created = this.createSessionCallback(this.type);
|
|
192
|
+
sessionId = created.sessionId;
|
|
193
|
+
this.sessionForClient.set(ws$1, sessionId);
|
|
194
|
+
ws$1.send(JSON.stringify({
|
|
195
|
+
type: "session",
|
|
196
|
+
sessionId,
|
|
197
|
+
sessionToken: created.sessionToken ?? null
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
for (const msg of this.pendingMessages) ws$1.send(msg);
|
|
201
|
+
this.pendingMessages = [];
|
|
202
|
+
if (this.pendingPromptMessage && this.promptResolve) ws$1.send(this.pendingPromptMessage);
|
|
203
|
+
ws$1.on("message", (data) => {
|
|
204
|
+
try {
|
|
205
|
+
const msg = JSON.parse(data.toString());
|
|
206
|
+
this.onMessage(msg);
|
|
207
|
+
} catch {}
|
|
208
|
+
});
|
|
209
|
+
ws$1.on("close", () => {
|
|
210
|
+
this.clients.delete(ws$1);
|
|
211
|
+
if (sessionId) this.sessionForClient.delete(ws$1);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
onMessage(msg) {
|
|
215
|
+
switch (msg.type) {
|
|
216
|
+
case "line": {
|
|
217
|
+
const content = String(msg.content ?? "");
|
|
218
|
+
this.inputSource.push?.(content);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case "prompt_response":
|
|
222
|
+
if (this.promptResolve) {
|
|
223
|
+
const resolve = this.promptResolve;
|
|
224
|
+
this.promptResolve = null;
|
|
225
|
+
this.pendingPromptMessage = null;
|
|
226
|
+
resolve(msg.value);
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
case "resize":
|
|
230
|
+
if (typeof msg.cols === "number") this.viewportCols = msg.cols;
|
|
231
|
+
if (typeof msg.rows === "number") this.viewportRows = msg.rows;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
broadcast(data) {
|
|
236
|
+
for (const ws$1 of this.clients) if (ws$1.readyState === 1) ws$1.send(data);
|
|
237
|
+
}
|
|
238
|
+
isLoopbackHost(hostname) {
|
|
239
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
240
|
+
}
|
|
241
|
+
isAllowedWsOrigin(origin) {
|
|
242
|
+
try {
|
|
243
|
+
const u = new URL(origin);
|
|
244
|
+
if (!this.isLoopbackHost(u.hostname)) return false;
|
|
245
|
+
return (u.port || (u.protocol === "https:" ? "443" : "80")) === String(this.port);
|
|
246
|
+
} catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** TTY-style prompt for test mode */
|
|
251
|
+
async ttyStylePrompt(options) {
|
|
252
|
+
const { message, type } = options;
|
|
253
|
+
switch (type) {
|
|
254
|
+
case "text":
|
|
255
|
+
case "password":
|
|
256
|
+
this.outputHandler(`${message} `);
|
|
257
|
+
return (await this.read()).trim();
|
|
258
|
+
case "confirm":
|
|
259
|
+
this.outputHandler(`${message} (y/n) `);
|
|
260
|
+
return (await this.read()).trim().toLowerCase().startsWith("y");
|
|
261
|
+
case "select": {
|
|
262
|
+
if (!options.options || options.options.length === 0) throw new Error("select prompt requires options");
|
|
263
|
+
this.outputHandler(`${message}\n`);
|
|
264
|
+
for (let i = 0; i < options.options.length; i++) this.outputHandler(` ${i + 1}. ${options.options[i]}\n`);
|
|
265
|
+
this.outputHandler("Choice: ");
|
|
266
|
+
const input = await this.read();
|
|
267
|
+
const idx = Number.parseInt(input.trim(), 10) - 1;
|
|
268
|
+
if (idx >= 0 && idx < options.options.length) return options.options[idx];
|
|
269
|
+
return options.options[0];
|
|
270
|
+
}
|
|
271
|
+
case "multiselect":
|
|
272
|
+
if (!options.options || options.options.length === 0) throw new Error("multiselect prompt requires options");
|
|
273
|
+
this.outputHandler(`${message}\n`);
|
|
274
|
+
for (let i = 0; i < options.options.length; i++) this.outputHandler(` ${i + 1}. ${options.options[i]}\n`);
|
|
275
|
+
this.outputHandler("Choices (comma-separated): ");
|
|
276
|
+
return (await this.read()).split(",").map((s) => Number.parseInt(s.trim(), 10) - 1).filter((i) => i >= 0 && i < options.options.length).map((i) => options.options[i]);
|
|
277
|
+
default: throw new Error(`Unknown prompt type: ${type}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
function withTimeout(promise, ms) {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Input timeout")), ms);
|
|
284
|
+
promise.then((val) => {
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
resolve(val);
|
|
287
|
+
}, (err) => {
|
|
288
|
+
clearTimeout(timer);
|
|
289
|
+
reject(err);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
//#endregion
|
|
295
|
+
exports.TermBackend = TermBackend;
|
package/dist/term.d.cts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { PromptOptions, PromptResult, ReadOptions, UIBackend, ViewportInfo, WriteOptions } from "./backend.cjs";
|
|
2
|
+
import { TTYInputSource } from "./tty.cjs";
|
|
3
|
+
|
|
4
|
+
//#region src/term.d.ts
|
|
5
|
+
interface TermBackendOptions {
|
|
6
|
+
/** Port to listen on (0 = OS-assigned random port) */
|
|
7
|
+
port?: number;
|
|
8
|
+
/** Host to bind to */
|
|
9
|
+
host?: string;
|
|
10
|
+
/** For testing: custom input source (bypasses WebSocket) */
|
|
11
|
+
inputSource?: TTYInputSource & {
|
|
12
|
+
push?: (line: string) => void;
|
|
13
|
+
};
|
|
14
|
+
/** For testing: custom output handler (bypasses WebSocket) */
|
|
15
|
+
stdout?: {
|
|
16
|
+
write(data: string): boolean;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* TermBackend — HTTP + WebSocket based xterm.js terminal.
|
|
21
|
+
*
|
|
22
|
+
* Serves an xterm.js web page and communicates via WebSocket.
|
|
23
|
+
* Line-based I/O: client sends complete lines on Enter, server writes output text.
|
|
24
|
+
*/
|
|
25
|
+
declare class TermBackend implements UIBackend {
|
|
26
|
+
readonly type = "term";
|
|
27
|
+
readonly supportedFormats: string[];
|
|
28
|
+
readonly capabilities: string[];
|
|
29
|
+
private port;
|
|
30
|
+
private host;
|
|
31
|
+
private server;
|
|
32
|
+
private wss;
|
|
33
|
+
private clients;
|
|
34
|
+
private sockets;
|
|
35
|
+
private inputSource;
|
|
36
|
+
private outputHandler;
|
|
37
|
+
/** Queue for messages sent before any client connects */
|
|
38
|
+
private pendingMessages;
|
|
39
|
+
/** Pending prompt resolve */
|
|
40
|
+
private promptResolve;
|
|
41
|
+
/** Pending prompt message — re-sent on client reconnect */
|
|
42
|
+
private pendingPromptMessage;
|
|
43
|
+
/** Viewport info from last resize */
|
|
44
|
+
private viewportCols;
|
|
45
|
+
private viewportRows;
|
|
46
|
+
private testMode;
|
|
47
|
+
private _url;
|
|
48
|
+
/** Per-client session tracking */
|
|
49
|
+
private sessionForClient;
|
|
50
|
+
private createSessionCallback;
|
|
51
|
+
constructor(options?: TermBackendOptions);
|
|
52
|
+
/** URL of the running server, or null if not started. */
|
|
53
|
+
get url(): string | null;
|
|
54
|
+
/** Register a factory that creates a session for each new WebSocket client. */
|
|
55
|
+
setSessionFactory(fn: (endpoint: string, requestedSessionId?: string, requestedSessionToken?: string, caps?: Record<string, unknown>) => {
|
|
56
|
+
sessionId: string;
|
|
57
|
+
sessionToken?: string;
|
|
58
|
+
}): void;
|
|
59
|
+
/** Start the HTTP + WebSocket server. */
|
|
60
|
+
listen(): Promise<{
|
|
61
|
+
port: number;
|
|
62
|
+
host: string;
|
|
63
|
+
}>;
|
|
64
|
+
/** Shut down the server and disconnect all clients. */
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
write(content: string, options?: WriteOptions): Promise<void>;
|
|
67
|
+
read(options?: ReadOptions): Promise<string>;
|
|
68
|
+
prompt(options: PromptOptions): Promise<PromptResult>;
|
|
69
|
+
notify(message: string): Promise<void>;
|
|
70
|
+
clear(): Promise<void>;
|
|
71
|
+
hasPendingInput(): boolean;
|
|
72
|
+
getViewport(): ViewportInfo;
|
|
73
|
+
dispose(): Promise<void>;
|
|
74
|
+
private onConnection;
|
|
75
|
+
private onMessage;
|
|
76
|
+
private broadcast;
|
|
77
|
+
private isLoopbackHost;
|
|
78
|
+
private isAllowedWsOrigin;
|
|
79
|
+
/** TTY-style prompt for test mode */
|
|
80
|
+
private ttyStylePrompt;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { TermBackend, TermBackendOptions };
|
|
84
|
+
//# sourceMappingURL=term.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"term.d.cts","names":[],"sources":["../src/term.ts"],"mappings":";;;;UAciB,kBAAA;;EAEf,IAAA;EAFiC;EAIjC,IAAA;EAE4B;EAA5B,WAAA,GAAc,cAAA;IAAmB,IAAA,IAAQ,IAAA;EAAA;EAA3B;EAEd,MAAA;IAAW,KAAA,CAAM,IAAA;EAAA;AAAA;;;;AASnB;;;cAAa,WAAA,YAAuB,SAAA;EAAA,SACzB,IAAA;EAAA,SACA,gBAAA;EAAA,SACA,YAAA;EAAA,QAED,IAAA;EAAA,QACA,IAAA;EAAA,QACA,MAAA;EAAA,QACA,GAAA;EAAA,QACA,OAAA;EAAA,QACA,OAAA;EAAA,QAEA,WAAA;EAAA,QACA,aAAA;EAmMO;EAAA,QAhMP,eAAA;EAgNS;EAAA,QA7MT,aAAA;EAnBmC;EAAA,QAqBnC,oBAAA;EArB0B;EAAA,QAwB1B,YAAA;EAAA,QACA,YAAA;EAAA,QAEA,QAAA;EAAA,QACA,IAAA;EAtBA;EAAA,QAyBA,gBAAA;EAAA,QACA,qBAAA;cASI,OAAA,GAAS,kBAAA;EA/Bb;EAAA,IA2DJ,GAAA,CAAA;EAxDI;EA6DR,iBAAA,CACE,EAAA,GACE,QAAA,UACA,kBAAA,WACA,qBAAA,WACA,IAAA,GAAO,MAAA;IACF,SAAA;IAAmB,YAAA;EAAA;EAvDpB;EA6DF,MAAA,CAAA,GAAU,OAAA;IAAU,IAAA;IAAc,IAAA;EAAA;;EA0ClC,KAAA,CAAA,GAAS,OAAA;EA0BT,KAAA,CAAM,OAAA,UAAiB,OAAA,GAAU,YAAA,GAAe,OAAA;EAahD,IAAA,CAAK,OAAA,GAAU,WAAA,GAAc,OAAA;EAQ7B,MAAA,CAAO,OAAA,EAAS,aAAA,GAAgB,OAAA,CAAQ,YAAA;EAyBxC,MAAA,CAAO,OAAA,WAAkB,OAAA;EAQzB,KAAA,CAAA,GAAS,OAAA;EAQf,eAAA,CAAA;EAIA,WAAA,CAAA,GAAe,YAAA;EAIT,OAAA,CAAA,GAAW,OAAA;EAAA,QAMT,YAAA;EAAA,QAkDA,SAAA;EAAA,QAwBA,SAAA;EAAA,QAQA,cAAA;EAAA,QAIA,iBAAA;EAtOkB;EAAA,QAkPZ,cAAA;AAAA"}
|