gekto 0.0.1

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.
@@ -0,0 +1,106 @@
1
+ import { WebSocket, WebSocketServer } from 'ws';
2
+ import * as pty from 'node-pty';
3
+ const sessions = new Map();
4
+ export function setupTerminalWebSocket(server, path = '/__gekto/terminal') {
5
+ const wss = new WebSocketServer({ noServer: true });
6
+ // Handle WebSocket upgrade for terminal path
7
+ server.on('upgrade', (request, socket, head) => {
8
+ const url = request.url || '';
9
+ if (url === path || url.startsWith(path + '?')) {
10
+ wss.handleUpgrade(request, socket, head, (ws) => {
11
+ wss.emit('connection', ws, request);
12
+ });
13
+ }
14
+ // Other upgrade requests (like Vite HMR) are handled elsewhere
15
+ });
16
+ wss.on('connection', (ws) => {
17
+ // Create session but don't spawn PTY yet - wait for resize
18
+ const session = {
19
+ pty: null,
20
+ ws,
21
+ cols: 80,
22
+ rows: 24,
23
+ };
24
+ sessions.set(ws, session);
25
+ const spawnShell = () => {
26
+ if (session.pty)
27
+ return; // Already spawned
28
+ try {
29
+ const shell = process.env.SHELL || '/bin/bash';
30
+ const isZsh = shell.endsWith('zsh');
31
+ // For zsh: pass -c to run unsetopt first, then exec interactive shell
32
+ const args = isZsh ? ['-c', 'unsetopt PROMPT_SP PROMPT_CR; exec zsh -i'] : [];
33
+ const ptyProcess = pty.spawn(shell, args, {
34
+ name: 'xterm-256color',
35
+ cols: session.cols,
36
+ rows: session.rows,
37
+ cwd: process.cwd(),
38
+ env: {
39
+ ...process.env,
40
+ TERM: 'xterm',
41
+ COLUMNS: String(session.cols),
42
+ LINES: String(session.rows),
43
+ },
44
+ });
45
+ session.pty = ptyProcess;
46
+ // PTY output → WebSocket
47
+ ptyProcess.onData((data) => {
48
+ if (ws.readyState === WebSocket.OPEN) {
49
+ ws.send(JSON.stringify({ type: 'output', data }));
50
+ }
51
+ });
52
+ ptyProcess.onExit(({ exitCode }) => {
53
+ if (ws.readyState === WebSocket.OPEN) {
54
+ ws.send(JSON.stringify({ type: 'exit', code: exitCode }));
55
+ ws.close();
56
+ }
57
+ sessions.delete(ws);
58
+ });
59
+ }
60
+ catch (err) {
61
+ ws.send(JSON.stringify({
62
+ type: 'error',
63
+ message: `Failed to start terminal: ${err}`
64
+ }));
65
+ ws.close();
66
+ }
67
+ };
68
+ // WebSocket → PTY
69
+ ws.on('message', (message) => {
70
+ try {
71
+ const msg = JSON.parse(message.toString());
72
+ switch (msg.type) {
73
+ case 'input':
74
+ session.pty?.write(msg.data);
75
+ break;
76
+ case 'resize':
77
+ if (msg.cols && msg.rows) {
78
+ session.cols = msg.cols;
79
+ session.rows = msg.rows;
80
+ if (session.pty) {
81
+ session.pty.resize(msg.cols, msg.rows);
82
+ }
83
+ else {
84
+ // First resize - spawn the shell now
85
+ spawnShell();
86
+ }
87
+ }
88
+ break;
89
+ }
90
+ }
91
+ catch {
92
+ // If not JSON, treat as raw input
93
+ session.pty?.write(message.toString());
94
+ }
95
+ });
96
+ ws.on('close', () => {
97
+ session.pty?.kill();
98
+ sessions.delete(ws);
99
+ });
100
+ ws.on('error', () => {
101
+ session.pty?.kill();
102
+ sessions.delete(ws);
103
+ });
104
+ });
105
+ return wss;
106
+ }