webtty 0.0.0-development
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/README.md +3 -0
- package/dist/cli.js +44 -0
- package/dist/cli.js.map +10 -0
- package/dist/server.js +381 -0
- package/dist/server.js.map +14 -0
- package/package.json +20 -0
package/README.md
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
9
|
+
var __dirname2 = path.dirname(__filename2);
|
|
10
|
+
var PORT = Number(process.env.PORT) || 2346;
|
|
11
|
+
var BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
12
|
+
var command = process.argv[2];
|
|
13
|
+
if (command === "start") {
|
|
14
|
+
const isTs = __filename2.endsWith(".ts");
|
|
15
|
+
const serverEntry = path.resolve(__dirname2, isTs ? "server.ts" : "server.js");
|
|
16
|
+
if (!fs.existsSync(serverEntry)) {
|
|
17
|
+
console.error(`wtty: server entry not found at ${serverEntry}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const child = spawn(process.execPath, [serverEntry], {
|
|
21
|
+
detached: true,
|
|
22
|
+
stdio: "ignore",
|
|
23
|
+
env: { ...process.env, PORT: String(PORT) }
|
|
24
|
+
});
|
|
25
|
+
child.unref();
|
|
26
|
+
console.log("wtty started");
|
|
27
|
+
} else if (command === "stop") {
|
|
28
|
+
try {
|
|
29
|
+
const res = await fetch(`${BASE_URL}/api/server/stop`, { method: "POST" });
|
|
30
|
+
if (res.ok) {
|
|
31
|
+
console.log("wtty stopped");
|
|
32
|
+
} else {
|
|
33
|
+
console.error(`wtty stop failed (status: ${res.status})`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
console.log("wtty is not running");
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
console.error("Usage: wtty start | wtty stop");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//# debugId=1DF946E2C6FEAE1864756E2164756E21
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/cli.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst PORT = Number(process.env.PORT) || 2346;\nconst BASE_URL = `http://127.0.0.1:${PORT}`;\n\nconst command = process.argv[2];\n\nif (command === 'start') {\n const isTs = __filename.endsWith('.ts');\n const serverEntry = path.resolve(__dirname, isTs ? 'server.ts' : 'server.js');\n if (!fs.existsSync(serverEntry)) {\n console.error(`wtty: server entry not found at ${serverEntry}`);\n process.exit(1);\n }\n const child = spawn(process.execPath, [serverEntry], {\n detached: true,\n stdio: 'ignore',\n env: { ...process.env, PORT: String(PORT) },\n });\n child.unref();\n console.log('wtty started');\n} else if (command === 'stop') {\n try {\n const res = await fetch(`${BASE_URL}/api/server/stop`, { method: 'POST' });\n if (res.ok) {\n console.log('wtty stopped');\n } else {\n console.error(`wtty stop failed (status: ${res.status})`);\n process.exit(1);\n }\n } catch {\n console.log('wtty is not running');\n }\n} else {\n console.error('Usage: wtty start | wtty stop');\n process.exit(1);\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;AAAA;AACA;AACA;AACA;AAEA,IAAM,cAAa,cAAc,YAAY,GAAG;AAChD,IAAM,aAAY,KAAK,QAAQ,WAAU;AAEzC,IAAM,OAAO,OAAO,QAAQ,IAAI,IAAI,KAAK;AACzC,IAAM,WAAW,oBAAoB;AAErC,IAAM,UAAU,QAAQ,KAAK;AAE7B,IAAI,YAAY,SAAS;AAAA,EACvB,MAAM,OAAO,YAAW,SAAS,KAAK;AAAA,EACtC,MAAM,cAAc,KAAK,QAAQ,YAAW,OAAO,cAAc,WAAW;AAAA,EAC5E,IAAI,CAAC,GAAG,WAAW,WAAW,GAAG;AAAA,IAC/B,QAAQ,MAAM,mCAAmC,aAAa;AAAA,IAC9D,QAAQ,KAAK,CAAC;AAAA,EAChB;AAAA,EACA,MAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,WAAW,GAAG;AAAA,IACnD,UAAU;AAAA,IACV,OAAO;AAAA,IACP,KAAK,KAAK,QAAQ,KAAK,MAAM,OAAO,IAAI,EAAE;AAAA,EAC5C,CAAC;AAAA,EACD,MAAM,MAAM;AAAA,EACZ,QAAQ,IAAI,cAAc;AAC5B,EAAO,SAAI,YAAY,QAAQ;AAAA,EAC7B,IAAI;AAAA,IACF,MAAM,MAAM,MAAM,MAAM,GAAG,4BAA4B,EAAE,QAAQ,OAAO,CAAC;AAAA,IACzE,IAAI,IAAI,IAAI;AAAA,MACV,QAAQ,IAAI,cAAc;AAAA,IAC5B,EAAO;AAAA,MACL,QAAQ,MAAM,6BAA6B,IAAI,SAAS;AAAA,MACxD,QAAQ,KAAK,CAAC;AAAA;AAAA,IAEhB,MAAM;AAAA,IACN,QAAQ,IAAI,qBAAqB;AAAA;AAErC,EAAO;AAAA,EACL,QAAQ,MAAM,+BAA+B;AAAA,EAC7C,QAAQ,KAAK,CAAC;AAAA;",
|
|
8
|
+
"debugId": "1DF946E2C6FEAE1864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
|
|
18
|
+
// src/pty/bun.ts
|
|
19
|
+
var exports_bun = {};
|
|
20
|
+
__export(exports_bun, {
|
|
21
|
+
spawn: () => spawn
|
|
22
|
+
});
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
function spawn(shell, cols, rows) {
|
|
25
|
+
let onDataCb;
|
|
26
|
+
let onExitCb;
|
|
27
|
+
const proc = Bun.spawn([shell], {
|
|
28
|
+
terminal: {
|
|
29
|
+
cols,
|
|
30
|
+
rows,
|
|
31
|
+
data(_term, data) {
|
|
32
|
+
onDataCb?.(Buffer.from(data).toString("utf8"));
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
cwd: homedir(),
|
|
36
|
+
env: { ...process.env, TERM: "xterm-256color", COLORTERM: "truecolor" }
|
|
37
|
+
});
|
|
38
|
+
proc.exited.then((exitCode) => {
|
|
39
|
+
onExitCb?.({ exitCode: exitCode ?? 0 });
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
onData(cb) {
|
|
43
|
+
onDataCb = cb;
|
|
44
|
+
},
|
|
45
|
+
onExit(cb) {
|
|
46
|
+
onExitCb = cb;
|
|
47
|
+
},
|
|
48
|
+
write(data) {
|
|
49
|
+
proc.terminal?.write(data);
|
|
50
|
+
},
|
|
51
|
+
resize(cols2, rows2) {
|
|
52
|
+
proc.terminal?.resize(cols2, rows2);
|
|
53
|
+
},
|
|
54
|
+
kill() {
|
|
55
|
+
proc.kill();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
var init_bun = () => {};
|
|
60
|
+
|
|
61
|
+
// src/pty/node.ts
|
|
62
|
+
var exports_node = {};
|
|
63
|
+
__export(exports_node, {
|
|
64
|
+
spawn: () => spawn2
|
|
65
|
+
});
|
|
66
|
+
import { homedir as homedir2 } from "node:os";
|
|
67
|
+
import nodePty from "@lydell/node-pty";
|
|
68
|
+
function spawn2(shell, cols, rows) {
|
|
69
|
+
const ptyProc = nodePty.spawn(shell, [], {
|
|
70
|
+
name: "xterm-256color",
|
|
71
|
+
cols,
|
|
72
|
+
rows,
|
|
73
|
+
cwd: homedir2(),
|
|
74
|
+
env: { ...process.env, TERM: "xterm-256color", COLORTERM: "truecolor" }
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
onData(cb) {
|
|
78
|
+
ptyProc.onData(cb);
|
|
79
|
+
},
|
|
80
|
+
onExit(cb) {
|
|
81
|
+
ptyProc.onExit(cb);
|
|
82
|
+
},
|
|
83
|
+
write(data) {
|
|
84
|
+
ptyProc.write(data);
|
|
85
|
+
},
|
|
86
|
+
resize(cols2, rows2) {
|
|
87
|
+
ptyProc.resize(cols2, rows2);
|
|
88
|
+
},
|
|
89
|
+
kill() {
|
|
90
|
+
ptyProc.kill();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
var init_node = () => {};
|
|
95
|
+
|
|
96
|
+
// src/server.ts
|
|
97
|
+
import fs from "node:fs";
|
|
98
|
+
import http from "node:http";
|
|
99
|
+
import { createRequire } from "node:module";
|
|
100
|
+
import path2 from "node:path";
|
|
101
|
+
import { fileURLToPath } from "node:url";
|
|
102
|
+
import { WebSocketServer } from "ws";
|
|
103
|
+
|
|
104
|
+
// src/pty/index.ts
|
|
105
|
+
var isBun = !!process.versions.bun;
|
|
106
|
+
console.log(`pty: ${isBun ? "Bun.Terminal" : "node-pty"}`);
|
|
107
|
+
var { spawn: _spawn } = await (isBun ? Promise.resolve().then(() => (init_bun(), exports_bun)) : Promise.resolve().then(() => (init_node(), exports_node)));
|
|
108
|
+
var spawn3 = _spawn;
|
|
109
|
+
|
|
110
|
+
// src/utils.ts
|
|
111
|
+
import path from "node:path";
|
|
112
|
+
var MIME_TYPES = {
|
|
113
|
+
".html": "text/html",
|
|
114
|
+
".js": "application/javascript",
|
|
115
|
+
".mjs": "application/javascript",
|
|
116
|
+
".css": "text/css",
|
|
117
|
+
".json": "application/json",
|
|
118
|
+
".wasm": "application/wasm",
|
|
119
|
+
".png": "image/png",
|
|
120
|
+
".svg": "image/svg+xml",
|
|
121
|
+
".ico": "image/x-icon"
|
|
122
|
+
};
|
|
123
|
+
function mimeType(filePath) {
|
|
124
|
+
const ext = path.extname(filePath);
|
|
125
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
126
|
+
}
|
|
127
|
+
function ghosttyWebRootFromMain(mainPath) {
|
|
128
|
+
return mainPath.replace(/[/\\]dist[/\\].*$/, "");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/server.ts
|
|
132
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
133
|
+
var __dirname2 = path2.dirname(__filename2);
|
|
134
|
+
var HTTP_PORT = Number(process.env.PORT) || 2346;
|
|
135
|
+
var require2 = createRequire(import.meta.url);
|
|
136
|
+
function findGhosttyWeb() {
|
|
137
|
+
try {
|
|
138
|
+
const ghosttyWebMain = require2.resolve("ghostty-web");
|
|
139
|
+
const ghosttyWebRoot = ghosttyWebRootFromMain(ghosttyWebMain);
|
|
140
|
+
const distPath = path2.join(ghosttyWebRoot, "dist");
|
|
141
|
+
const wasmPath = path2.join(ghosttyWebRoot, "ghostty-vt.wasm");
|
|
142
|
+
if (fs.existsSync(path2.join(distPath, "ghostty-web.js")) && fs.existsSync(wasmPath)) {
|
|
143
|
+
return { distPath, wasmPath };
|
|
144
|
+
}
|
|
145
|
+
} catch {}
|
|
146
|
+
console.error("Error: Could not find ghostty-web package.");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
var { distPath, wasmPath } = findGhosttyWeb();
|
|
150
|
+
var HTML_TEMPLATE = `<!doctype html>
|
|
151
|
+
<html lang="en">
|
|
152
|
+
<head>
|
|
153
|
+
<meta charset="UTF-8" />
|
|
154
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
155
|
+
<title>wtty</title>
|
|
156
|
+
<style>
|
|
157
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
158
|
+
|
|
159
|
+
html, body, #terminal { width: 100%; height: 100%; overflow: hidden; background: #282A36; }
|
|
160
|
+
|
|
161
|
+
#terminal canvas { display: block; }
|
|
162
|
+
</style>
|
|
163
|
+
</head>
|
|
164
|
+
<body>
|
|
165
|
+
<div id="terminal"></div>
|
|
166
|
+
|
|
167
|
+
<script type="module">
|
|
168
|
+
import { init, Terminal, FitAddon } from '/dist/ghostty-web.js';
|
|
169
|
+
|
|
170
|
+
await init();
|
|
171
|
+
const term = new Terminal({
|
|
172
|
+
cols: 80,
|
|
173
|
+
rows: 24,
|
|
174
|
+
cursorBlink: true,
|
|
175
|
+
fontSize: 14,
|
|
176
|
+
fontFamily: "'FiraMono Nerd Font', Menlo, Monaco, 'Courier New', monospace",
|
|
177
|
+
scrollback: 10000,
|
|
178
|
+
theme: {
|
|
179
|
+
background: '#282A36',
|
|
180
|
+
foreground: '#F8F8F2',
|
|
181
|
+
cursor: '#F8F8F2',
|
|
182
|
+
selection: '#44475A',
|
|
183
|
+
black: '#21222C',
|
|
184
|
+
red: '#FF5555',
|
|
185
|
+
green: '#50FA7B',
|
|
186
|
+
yellow: '#F1FA8C',
|
|
187
|
+
blue: '#BD93F9',
|
|
188
|
+
purple: '#FF79C6',
|
|
189
|
+
cyan: '#8BE9FD',
|
|
190
|
+
white: '#F8F8F2',
|
|
191
|
+
brightBlack: '#6272A4',
|
|
192
|
+
brightRed: '#FF6E6E',
|
|
193
|
+
brightGreen: '#69FF94',
|
|
194
|
+
brightYellow: '#FFFFA5',
|
|
195
|
+
brightBlue: '#D6ACFF',
|
|
196
|
+
brightPurple: '#FF92DF',
|
|
197
|
+
brightCyan: '#A4FFFF',
|
|
198
|
+
brightWhite: '#FFFFFF',
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const fitAddon = new FitAddon();
|
|
203
|
+
term.loadAddon(fitAddon);
|
|
204
|
+
|
|
205
|
+
const container = document.getElementById('terminal');
|
|
206
|
+
await term.open(container);
|
|
207
|
+
fitAddon.fit();
|
|
208
|
+
fitAddon.observeResize();
|
|
209
|
+
|
|
210
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
211
|
+
const wsUrl = protocol + '//' + window.location.host + '/ws?cols=' + term.cols + '&rows=' + term.rows;
|
|
212
|
+
let ws;
|
|
213
|
+
|
|
214
|
+
function connect() {
|
|
215
|
+
ws = new WebSocket(wsUrl);
|
|
216
|
+
|
|
217
|
+
ws.onopen = () => {
|
|
218
|
+
console.log('[wtty] connected');
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
ws.onmessage = (event) => {
|
|
222
|
+
term.write(event.data);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
ws.onclose = () => {
|
|
226
|
+
console.log('[wtty] disconnected, reconnecting in 2s...');
|
|
227
|
+
term.write('\\r\\n\\x1b[31mConnection closed. Reconnecting in 2s...\\x1b[0m\\r\\n');
|
|
228
|
+
setTimeout(connect, 2000);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
ws.onerror = () => {
|
|
232
|
+
console.error('[wtty] websocket error');
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
connect();
|
|
237
|
+
|
|
238
|
+
term.onData((data) => {
|
|
239
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
240
|
+
ws.send(data);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
term.onResize(({ cols, rows }) => {
|
|
245
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
246
|
+
ws.send(JSON.stringify({ type: 'resize', cols, rows }));
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
window.addEventListener('resize', () => {
|
|
251
|
+
fitAddon.fit();
|
|
252
|
+
});
|
|
253
|
+
</script>
|
|
254
|
+
</body>
|
|
255
|
+
</html>`;
|
|
256
|
+
var httpServer = http.createServer((req, res) => {
|
|
257
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
258
|
+
const pathname = url.pathname;
|
|
259
|
+
if (req.method === "POST" && pathname === "/api/server/stop") {
|
|
260
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
261
|
+
res.end("stopping");
|
|
262
|
+
for (const [ws, session] of sessions.entries()) {
|
|
263
|
+
session.pty.kill();
|
|
264
|
+
ws.close();
|
|
265
|
+
}
|
|
266
|
+
wss.close();
|
|
267
|
+
httpServer.close(() => process.exit(0));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
271
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
272
|
+
res.end(HTML_TEMPLATE);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (pathname.startsWith("/dist/")) {
|
|
276
|
+
serveFile(path2.join(distPath, pathname.slice(6)), res);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (pathname === "/ghostty-vt.wasm") {
|
|
280
|
+
serveFile(wasmPath, res);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
res.writeHead(404);
|
|
284
|
+
res.end("Not Found");
|
|
285
|
+
});
|
|
286
|
+
function serveFile(filePath, res) {
|
|
287
|
+
const contentType = mimeType(filePath);
|
|
288
|
+
fs.readFile(filePath, (err, data) => {
|
|
289
|
+
if (err) {
|
|
290
|
+
res.writeHead(404);
|
|
291
|
+
res.end("Not Found");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
295
|
+
res.end(data);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
var sessions = new Map;
|
|
299
|
+
function createPtySession(cols, rows) {
|
|
300
|
+
const shell = process.platform === "win32" ? process.env.COMSPEC ?? "cmd.exe" : process.env.SHELL ?? "/bin/bash";
|
|
301
|
+
return spawn3(shell, cols, rows);
|
|
302
|
+
}
|
|
303
|
+
var wss = new WebSocketServer({ noServer: true });
|
|
304
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
305
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
306
|
+
if (url.pathname === "/ws") {
|
|
307
|
+
wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
|
|
308
|
+
} else {
|
|
309
|
+
socket.destroy();
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
wss.on("connection", (ws, req) => {
|
|
313
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
314
|
+
const cols = Number.parseInt(url.searchParams.get("cols") ?? "80", 10);
|
|
315
|
+
const rows = Number.parseInt(url.searchParams.get("rows") ?? "24", 10);
|
|
316
|
+
const ptyProcess = createPtySession(cols, rows);
|
|
317
|
+
sessions.set(ws, { pty: ptyProcess });
|
|
318
|
+
ptyProcess.onData((data) => {
|
|
319
|
+
if (ws.readyState === ws.OPEN)
|
|
320
|
+
ws.send(data, { binary: false });
|
|
321
|
+
});
|
|
322
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
323
|
+
if (ws.readyState === ws.OPEN) {
|
|
324
|
+
ws.send(`\r
|
|
325
|
+
\x1B[33mShell exited (code: ${exitCode})\x1B[0m\r
|
|
326
|
+
`);
|
|
327
|
+
ws.close();
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
ws.on("message", (data) => {
|
|
331
|
+
const message = data.toString("utf8");
|
|
332
|
+
if (message.startsWith("{")) {
|
|
333
|
+
try {
|
|
334
|
+
const msg = JSON.parse(message);
|
|
335
|
+
if (msg.type === "resize") {
|
|
336
|
+
ptyProcess.resize(msg.cols, msg.rows);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
} catch {}
|
|
340
|
+
}
|
|
341
|
+
ptyProcess.write(message);
|
|
342
|
+
});
|
|
343
|
+
ws.on("close", () => {
|
|
344
|
+
sessions.get(ws)?.pty.kill();
|
|
345
|
+
sessions.delete(ws);
|
|
346
|
+
});
|
|
347
|
+
ws.on("error", () => {});
|
|
348
|
+
const C = "\x1B[1;36m";
|
|
349
|
+
const G = "\x1B[1;32m";
|
|
350
|
+
const Y = "\x1B[1;33m";
|
|
351
|
+
const R = "\x1B[0m";
|
|
352
|
+
ws.send(`${C}╔══════════════════════════════════════════════════════════════╗${R}\r
|
|
353
|
+
`);
|
|
354
|
+
ws.send(`${C}║${R} ${G}Welcome to wtty!${R} ${C}║${R}\r
|
|
355
|
+
`);
|
|
356
|
+
ws.send(`${C}║${R} ${C}║${R}\r
|
|
357
|
+
`);
|
|
358
|
+
ws.send(`${C}║${R} You have a real shell session with full PTY support. ${C}║${R}\r
|
|
359
|
+
`);
|
|
360
|
+
ws.send(`${C}║${R} Try: ${Y}ls${R}, ${Y}cd${R}, ${Y}top${R}, ${Y}vim${R}, or any command! ${C}║${R}\r
|
|
361
|
+
`);
|
|
362
|
+
ws.send(`${C}╚══════════════════════════════════════════════════════════════╝${R}\r
|
|
363
|
+
\r
|
|
364
|
+
`);
|
|
365
|
+
});
|
|
366
|
+
process.on("SIGINT", () => {
|
|
367
|
+
console.log(`
|
|
368
|
+
|
|
369
|
+
Shutting down...`);
|
|
370
|
+
for (const [ws, session] of sessions.entries()) {
|
|
371
|
+
session.pty.kill();
|
|
372
|
+
ws.close();
|
|
373
|
+
}
|
|
374
|
+
wss.close();
|
|
375
|
+
process.exit(0);
|
|
376
|
+
});
|
|
377
|
+
httpServer.listen(HTTP_PORT, "127.0.0.1", () => {
|
|
378
|
+
console.log(`listening on http://127.0.0.1:${HTTP_PORT}`);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
//# debugId=76EEF695BF016C4664756E2164756E21
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pty/bun.ts", "../src/pty/node.ts", "../src/server.ts", "../src/pty/index.ts", "../src/utils.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { homedir } from 'node:os';\nimport type { PtyProcess } from './types';\n\nexport function spawn(shell: string, cols: number, rows: number): PtyProcess {\n let onDataCb: ((data: string) => void) | undefined;\n let onExitCb: ((e: { exitCode: number }) => void) | undefined;\n\n const proc = Bun.spawn([shell], {\n terminal: {\n cols,\n rows,\n data(_term: unknown, data: Uint8Array) {\n onDataCb?.(Buffer.from(data).toString('utf8'));\n },\n },\n cwd: homedir(),\n env: { ...process.env, TERM: 'xterm-256color', COLORTERM: 'truecolor' },\n });\n\n proc.exited.then((exitCode) => {\n onExitCb?.({ exitCode: exitCode ?? 0 });\n });\n\n return {\n onData(cb) {\n onDataCb = cb;\n },\n onExit(cb) {\n onExitCb = cb;\n },\n write(data) {\n proc.terminal?.write(data);\n },\n resize(cols, rows) {\n proc.terminal?.resize(cols, rows);\n },\n kill() {\n proc.kill();\n },\n };\n}\n",
|
|
6
|
+
"import { homedir } from 'node:os';\nimport nodePty from '@lydell/node-pty';\nimport type { PtyProcess } from './types';\n\nexport function spawn(shell: string, cols: number, rows: number): PtyProcess {\n const ptyProc = nodePty.spawn(shell, [], {\n name: 'xterm-256color',\n cols,\n rows,\n cwd: homedir(),\n env: { ...process.env, TERM: 'xterm-256color', COLORTERM: 'truecolor' },\n });\n\n return {\n onData(cb) {\n ptyProc.onData(cb);\n },\n onExit(cb) {\n ptyProc.onExit(cb);\n },\n write(data) {\n ptyProc.write(data);\n },\n resize(cols, rows) {\n ptyProc.resize(cols, rows);\n },\n kill() {\n ptyProc.kill();\n },\n };\n}\n",
|
|
7
|
+
"import fs from 'node:fs';\nimport http from 'node:http';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { WebSocket as WS } from 'ws';\nimport { WebSocketServer } from 'ws';\nimport { type PtyProcess, spawn as spawnPty } from './pty';\nimport { ghosttyWebRootFromMain, mimeType } from './utils';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst HTTP_PORT = Number(process.env.PORT) || 2346;\n\nconst require = createRequire(import.meta.url);\n\nfunction findGhosttyWeb(): { distPath: string; wasmPath: string } {\n try {\n const ghosttyWebMain = require.resolve('ghostty-web') as string;\n const ghosttyWebRoot = ghosttyWebRootFromMain(ghosttyWebMain);\n const distPath = path.join(ghosttyWebRoot, 'dist');\n const wasmPath = path.join(ghosttyWebRoot, 'ghostty-vt.wasm');\n if (fs.existsSync(path.join(distPath, 'ghostty-web.js')) && fs.existsSync(wasmPath)) {\n return { distPath, wasmPath };\n }\n } catch {\n // fall through\n }\n console.error('Error: Could not find ghostty-web package.');\n process.exit(1);\n}\n\nconst { distPath, wasmPath } = findGhosttyWeb();\n\nconst HTML_TEMPLATE = `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>wtty</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n html, body, #terminal { width: 100%; height: 100%; overflow: hidden; background: #282A36; }\n\n #terminal canvas { display: block; }\n </style>\n </head>\n <body>\n <div id=\"terminal\"></div>\n\n <script type=\"module\">\n import { init, Terminal, FitAddon } from '/dist/ghostty-web.js';\n\n await init();\n const term = new Terminal({\n cols: 80,\n rows: 24,\n cursorBlink: true,\n fontSize: 14,\n fontFamily: \"'FiraMono Nerd Font', Menlo, Monaco, 'Courier New', monospace\",\n scrollback: 10000,\n theme: {\n background: '#282A36',\n foreground: '#F8F8F2',\n cursor: '#F8F8F2',\n selection: '#44475A',\n black: '#21222C',\n red: '#FF5555',\n green: '#50FA7B',\n yellow: '#F1FA8C',\n blue: '#BD93F9',\n purple: '#FF79C6',\n cyan: '#8BE9FD',\n white: '#F8F8F2',\n brightBlack: '#6272A4',\n brightRed: '#FF6E6E',\n brightGreen: '#69FF94',\n brightYellow: '#FFFFA5',\n brightBlue: '#D6ACFF',\n brightPurple: '#FF92DF',\n brightCyan: '#A4FFFF',\n brightWhite: '#FFFFFF',\n },\n });\n\n const fitAddon = new FitAddon();\n term.loadAddon(fitAddon);\n\n const container = document.getElementById('terminal');\n await term.open(container);\n fitAddon.fit();\n fitAddon.observeResize();\n\n const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n const wsUrl = protocol + '//' + window.location.host + '/ws?cols=' + term.cols + '&rows=' + term.rows;\n let ws;\n\n function connect() {\n ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {\n console.log('[wtty] connected');\n };\n\n ws.onmessage = (event) => {\n term.write(event.data);\n };\n\n ws.onclose = () => {\n console.log('[wtty] disconnected, reconnecting in 2s...');\n term.write('\\\\r\\\\n\\\\x1b[31mConnection closed. Reconnecting in 2s...\\\\x1b[0m\\\\r\\\\n');\n setTimeout(connect, 2000);\n };\n\n ws.onerror = () => {\n console.error('[wtty] websocket error');\n };\n }\n\n connect();\n\n term.onData((data) => {\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(data);\n }\n });\n\n term.onResize(({ cols, rows }) => {\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: 'resize', cols, rows }));\n }\n });\n\n window.addEventListener('resize', () => {\n fitAddon.fit();\n });\n </script>\n </body>\n</html>`;\n\nconst httpServer = http.createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n const pathname = url.pathname;\n\n if (req.method === 'POST' && pathname === '/api/server/stop') {\n res.writeHead(200, { 'Content-Type': 'text/plain' });\n res.end('stopping');\n for (const [ws, session] of sessions.entries()) {\n session.pty.kill();\n ws.close();\n }\n wss.close();\n httpServer.close(() => process.exit(0));\n return;\n }\n\n if (pathname === '/' || pathname === '/index.html') {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(HTML_TEMPLATE);\n return;\n }\n\n if (pathname.startsWith('/dist/')) {\n serveFile(path.join(distPath, pathname.slice(6)), res);\n return;\n }\n\n if (pathname === '/ghostty-vt.wasm') {\n serveFile(wasmPath, res);\n return;\n }\n\n res.writeHead(404);\n res.end('Not Found');\n});\n\nfunction serveFile(filePath: string, res: http.ServerResponse): void {\n const contentType = mimeType(filePath);\n fs.readFile(filePath, (err, data) => {\n if (err) {\n res.writeHead(404);\n res.end('Not Found');\n return;\n }\n res.writeHead(200, { 'Content-Type': contentType });\n res.end(data);\n });\n}\n\nconst sessions = new Map<WS, { pty: PtyProcess }>();\n\nfunction createPtySession(cols: number, rows: number): PtyProcess {\n const shell =\n process.platform === 'win32'\n ? (process.env.COMSPEC ?? 'cmd.exe')\n : (process.env.SHELL ?? '/bin/bash');\n return spawnPty(shell, cols, rows);\n}\n\nconst wss = new WebSocketServer({ noServer: true });\n\nhttpServer.on('upgrade', (req, socket, head) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n if (url.pathname === '/ws') {\n wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));\n } else {\n socket.destroy();\n }\n});\n\nwss.on('connection', (ws: WS, req: http.IncomingMessage) => {\n const url = new URL(req.url ?? '/', `http://${req.headers.host}`);\n const cols = Number.parseInt(url.searchParams.get('cols') ?? '80', 10);\n const rows = Number.parseInt(url.searchParams.get('rows') ?? '24', 10);\n\n const ptyProcess = createPtySession(cols, rows);\n sessions.set(ws, { pty: ptyProcess });\n\n ptyProcess.onData((data: string) => {\n if (ws.readyState === ws.OPEN) ws.send(data, { binary: false });\n });\n\n ptyProcess.onExit(({ exitCode }) => {\n if (ws.readyState === ws.OPEN) {\n ws.send(`\\r\\n\\x1b[33mShell exited (code: ${exitCode})\\x1b[0m\\r\\n`);\n ws.close();\n }\n });\n\n ws.on('message', (data: Buffer) => {\n const message = data.toString('utf8');\n if (message.startsWith('{')) {\n try {\n const msg = JSON.parse(message) as { type: string; cols: number; rows: number };\n if (msg.type === 'resize') {\n ptyProcess.resize(msg.cols, msg.rows);\n return;\n }\n } catch {\n // fall through\n }\n }\n ptyProcess.write(message);\n });\n\n ws.on('close', () => {\n sessions.get(ws)?.pty.kill();\n sessions.delete(ws);\n });\n\n ws.on('error', () => {});\n\n const C = '\\x1b[1;36m';\n const G = '\\x1b[1;32m';\n const Y = '\\x1b[1;33m';\n const R = '\\x1b[0m';\n ws.send(`${C}╔══════════════════════════════════════════════════════════════╗${R}\\r\\n`);\n ws.send(\n `${C}║${R} ${G}Welcome to wtty!${R} ${C}║${R}\\r\\n`,\n );\n ws.send(`${C}║${R} ${C}║${R}\\r\\n`);\n ws.send(`${C}║${R} You have a real shell session with full PTY support. ${C}║${R}\\r\\n`);\n ws.send(\n `${C}║${R} Try: ${Y}ls${R}, ${Y}cd${R}, ${Y}top${R}, ${Y}vim${R}, or any command! ${C}║${R}\\r\\n`,\n );\n ws.send(`${C}╚══════════════════════════════════════════════════════════════╝${R}\\r\\n\\r\\n`);\n});\n\nprocess.on('SIGINT', () => {\n console.log('\\n\\nShutting down...');\n for (const [ws, session] of sessions.entries()) {\n session.pty.kill();\n ws.close();\n }\n wss.close();\n process.exit(0);\n});\n\nhttpServer.listen(HTTP_PORT, '127.0.0.1', () => {\n console.log(`listening on http://127.0.0.1:${HTTP_PORT}`);\n});\n",
|
|
8
|
+
"export type { PtyProcess } from './types';\n\nconst isBun = !!process.versions.bun;\nconsole.log(`pty: ${isBun ? 'Bun.Terminal' : 'node-pty'}`);\n\nconst { spawn: _spawn } = await (isBun ? import('./bun') : import('./node'));\n\nexport const spawn = _spawn;\n",
|
|
9
|
+
"import path from 'node:path';\n\nexport const MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.mjs': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.wasm': 'application/wasm',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n};\n\nexport function mimeType(filePath: string): string {\n const ext = path.extname(filePath);\n return MIME_TYPES[ext] ?? 'application/octet-stream';\n}\n\nexport function ghosttyWebRootFromMain(mainPath: string): string {\n return mainPath.replace(/[/\\\\]dist[/\\\\].*$/, '');\n}\n"
|
|
10
|
+
],
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "76EEF695BF016C4664756E2164756E21",
|
|
13
|
+
"names": []
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webtty",
|
|
3
|
+
"version": "0.0.0-development",
|
|
4
|
+
"description": "Web TTY for running CLI/TUI applications in a browser tab, across platforms",
|
|
5
|
+
"bin": {
|
|
6
|
+
"webtty": "./dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@lydell/node-pty": "1.2.0-beta.3",
|
|
17
|
+
"ghostty-web": "0.4.0-next.14.g6a1a50d",
|
|
18
|
+
"ws": "8.20.0"
|
|
19
|
+
}
|
|
20
|
+
}
|