treesap 0.1.8 → 0.1.9

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,307 @@
1
+ import { WebSocketServer, WebSocket } from 'ws';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { TerminalService } from './terminal.js';
4
+ export class WebSocketTerminalService {
5
+ static wss = null;
6
+ static clients = new Map();
7
+ static sessionClients = new Map(); // sessionId -> Set<clientId>
8
+ static initialize(server) {
9
+ this.wss = new WebSocketServer({
10
+ server,
11
+ path: '/terminal/ws'
12
+ });
13
+ this.wss.on('connection', (ws, request) => {
14
+ const clientId = uuidv4();
15
+ const client = {
16
+ id: clientId,
17
+ ws,
18
+ lastPing: new Date()
19
+ };
20
+ this.clients.set(clientId, client);
21
+ console.log(`WebSocket client connected: ${clientId}`);
22
+ // Set up message handler
23
+ ws.on('message', (data) => {
24
+ this.handleMessage(clientId, data);
25
+ });
26
+ // Set up disconnect handler
27
+ ws.on('close', () => {
28
+ this.handleDisconnect(clientId);
29
+ });
30
+ // Set up error handler
31
+ ws.on('error', (error) => {
32
+ console.error(`WebSocket error for client ${clientId}:`, error);
33
+ this.handleDisconnect(clientId);
34
+ });
35
+ // Send initial connection success
36
+ this.sendToClient(clientId, {
37
+ type: 'pong',
38
+ timestamp: Date.now()
39
+ });
40
+ // Set up ping/pong for connection health
41
+ this.setupPingPong(clientId);
42
+ });
43
+ console.log('WebSocket server initialized for terminal connections');
44
+ }
45
+ static setupPingPong(clientId) {
46
+ const client = this.clients.get(clientId);
47
+ if (!client)
48
+ return;
49
+ const pingInterval = setInterval(() => {
50
+ if (client.ws.readyState === WebSocket.OPEN) {
51
+ client.ws.ping();
52
+ client.lastPing = new Date();
53
+ }
54
+ else {
55
+ clearInterval(pingInterval);
56
+ }
57
+ }, 30000); // Ping every 30 seconds
58
+ client.ws.on('pong', () => {
59
+ client.lastPing = new Date();
60
+ });
61
+ client.ws.on('close', () => {
62
+ clearInterval(pingInterval);
63
+ });
64
+ }
65
+ static handleMessage(clientId, data) {
66
+ try {
67
+ const message = JSON.parse(data.toString());
68
+ const client = this.clients.get(clientId);
69
+ if (!client) {
70
+ console.error(`Client ${clientId} not found`);
71
+ return;
72
+ }
73
+ console.log(`Received message from ${clientId}:`, message.type, message.sessionId);
74
+ switch (message.type) {
75
+ case 'join':
76
+ this.handleJoin(clientId, message);
77
+ break;
78
+ case 'leave':
79
+ this.handleLeave(clientId, message);
80
+ break;
81
+ case 'input':
82
+ this.handleInput(clientId, message);
83
+ break;
84
+ case 'ping':
85
+ this.sendToClient(clientId, {
86
+ type: 'pong',
87
+ timestamp: Date.now()
88
+ });
89
+ break;
90
+ default:
91
+ console.warn(`Unknown message type: ${message.type}`);
92
+ }
93
+ }
94
+ catch (error) {
95
+ console.error(`Error parsing WebSocket message from ${clientId}:`, error);
96
+ }
97
+ }
98
+ static handleJoin(clientId, message) {
99
+ const client = this.clients.get(clientId);
100
+ if (!client || !message.sessionId)
101
+ return;
102
+ console.log(`Client ${clientId} joining session ${message.sessionId}`);
103
+ // Remove client from any existing session
104
+ if (client.sessionId) {
105
+ this.removeClientFromSession(clientId, client.sessionId);
106
+ }
107
+ // Get or create terminal session
108
+ let session = TerminalService.getSession(message.sessionId);
109
+ if (!session) {
110
+ console.log(`Creating new terminal session: ${message.sessionId}`);
111
+ session = TerminalService.createSession(message.sessionId);
112
+ }
113
+ // Update client info
114
+ client.sessionId = message.sessionId;
115
+ client.terminalId = message.terminalId;
116
+ // Add client to session tracking
117
+ this.addClientToSession(clientId, message.sessionId);
118
+ // Set up output listener for this session (if not already set up)
119
+ this.setupSessionOutputListener(message.sessionId);
120
+ // Send connection confirmation
121
+ this.sendToClient(clientId, {
122
+ type: 'connected',
123
+ sessionId: message.sessionId,
124
+ timestamp: Date.now()
125
+ });
126
+ // Notify all clients in this session about client count
127
+ this.broadcastClientCount(message.sessionId);
128
+ }
129
+ static handleLeave(clientId, message) {
130
+ const client = this.clients.get(clientId);
131
+ if (!client)
132
+ return;
133
+ console.log(`Client ${clientId} leaving session ${client.sessionId}`);
134
+ if (client.sessionId) {
135
+ this.removeClientFromSession(clientId, client.sessionId);
136
+ this.broadcastClientCount(client.sessionId);
137
+ }
138
+ client.sessionId = undefined;
139
+ client.terminalId = undefined;
140
+ }
141
+ static handleInput(clientId, message) {
142
+ const client = this.clients.get(clientId);
143
+ if (!client || !message.sessionId || message.data === undefined)
144
+ return;
145
+ console.log(`Input from client ${clientId} to session ${message.sessionId}`);
146
+ // Get the terminal session
147
+ const session = TerminalService.getSession(message.sessionId);
148
+ if (!session) {
149
+ console.error(`Session ${message.sessionId} not found`);
150
+ this.sendToClient(clientId, {
151
+ type: 'error',
152
+ data: 'Terminal session not found',
153
+ timestamp: Date.now()
154
+ });
155
+ return;
156
+ }
157
+ // Send input directly to PTY (raw input like key presses)
158
+ try {
159
+ session.lastActivity = new Date();
160
+ session.process.write(message.data);
161
+ }
162
+ catch (error) {
163
+ console.error(`Failed to send input to session ${message.sessionId}:`, error);
164
+ this.sendToClient(clientId, {
165
+ type: 'error',
166
+ data: 'Failed to send input to terminal',
167
+ timestamp: Date.now()
168
+ });
169
+ }
170
+ }
171
+ static handleDisconnect(clientId) {
172
+ const client = this.clients.get(clientId);
173
+ console.log(`WebSocket client disconnected: ${clientId}`);
174
+ if (client?.sessionId) {
175
+ this.removeClientFromSession(clientId, client.sessionId);
176
+ this.broadcastClientCount(client.sessionId);
177
+ }
178
+ this.clients.delete(clientId);
179
+ }
180
+ static addClientToSession(clientId, sessionId) {
181
+ if (!this.sessionClients.has(sessionId)) {
182
+ this.sessionClients.set(sessionId, new Set());
183
+ }
184
+ this.sessionClients.get(sessionId).add(clientId);
185
+ }
186
+ static removeClientFromSession(clientId, sessionId) {
187
+ const clientSet = this.sessionClients.get(sessionId);
188
+ if (clientSet) {
189
+ clientSet.delete(clientId);
190
+ if (clientSet.size === 0) {
191
+ this.sessionClients.delete(sessionId);
192
+ // Clean up output listener if no clients are connected
193
+ this.cleanupSessionOutputListener(sessionId);
194
+ }
195
+ }
196
+ }
197
+ static setupSessionOutputListener(sessionId) {
198
+ const session = TerminalService.getSession(sessionId);
199
+ if (!session)
200
+ return;
201
+ // Check if listener already exists
202
+ if (session.eventEmitter.listenerCount('output') > 0) {
203
+ return; // Listener already set up
204
+ }
205
+ const handleOutput = (data) => {
206
+ this.broadcastToSession(sessionId, data);
207
+ };
208
+ session.eventEmitter.on('output', handleOutput);
209
+ console.log(`Set up output listener for session ${sessionId}`);
210
+ }
211
+ static cleanupSessionOutputListener(sessionId) {
212
+ const session = TerminalService.getSession(sessionId);
213
+ if (!session)
214
+ return;
215
+ session.eventEmitter.removeAllListeners('output');
216
+ console.log(`Cleaned up output listener for session ${sessionId}`);
217
+ }
218
+ static broadcastToSession(sessionId, data) {
219
+ const clientIds = this.sessionClients.get(sessionId);
220
+ if (!clientIds)
221
+ return;
222
+ const message = {
223
+ ...data,
224
+ timestamp: Date.now()
225
+ };
226
+ for (const clientId of clientIds) {
227
+ this.sendToClient(clientId, message);
228
+ }
229
+ }
230
+ static broadcastClientCount(sessionId) {
231
+ const clientIds = this.sessionClients.get(sessionId);
232
+ const count = clientIds ? clientIds.size : 0;
233
+ if (clientIds) {
234
+ for (const clientId of clientIds) {
235
+ this.sendToClient(clientId, {
236
+ type: 'clients_count',
237
+ count,
238
+ timestamp: Date.now()
239
+ });
240
+ }
241
+ }
242
+ }
243
+ static sendToClient(clientId, message) {
244
+ const client = this.clients.get(clientId);
245
+ if (!client || client.ws.readyState !== WebSocket.OPEN) {
246
+ return;
247
+ }
248
+ try {
249
+ client.ws.send(JSON.stringify(message));
250
+ }
251
+ catch (error) {
252
+ console.error(`Error sending message to client ${clientId}:`, error);
253
+ this.handleDisconnect(clientId);
254
+ }
255
+ }
256
+ // API methods for external control
257
+ static getSessionClients(sessionId) {
258
+ const clientSet = this.sessionClients.get(sessionId);
259
+ return clientSet ? Array.from(clientSet) : [];
260
+ }
261
+ static sendCommandToSession(sessionId, command) {
262
+ // Send command to terminal
263
+ const success = TerminalService.executeCommand(sessionId, command);
264
+ if (success) {
265
+ // The output will be automatically broadcast to all connected clients
266
+ // via the output listener
267
+ console.log(`Command sent to session ${sessionId}: ${command.substring(0, 50)}...`);
268
+ }
269
+ return success;
270
+ }
271
+ static getActiveSessions() {
272
+ return Array.from(this.sessionClients.entries()).map(([sessionId, clients]) => ({
273
+ sessionId,
274
+ clientCount: clients.size
275
+ }));
276
+ }
277
+ static getConnectedClients() {
278
+ return this.clients.size;
279
+ }
280
+ static closeSession(sessionId) {
281
+ const clientIds = this.sessionClients.get(sessionId);
282
+ if (clientIds) {
283
+ // Notify all clients that the session is closing
284
+ for (const clientId of clientIds) {
285
+ this.sendToClient(clientId, {
286
+ type: 'session_closed',
287
+ sessionId,
288
+ timestamp: Date.now()
289
+ });
290
+ }
291
+ // Clean up client tracking
292
+ this.sessionClients.delete(sessionId);
293
+ }
294
+ // Clean up the terminal session
295
+ TerminalService.destroySession(sessionId);
296
+ }
297
+ static cleanup() {
298
+ if (this.wss) {
299
+ console.log('Closing WebSocket server...');
300
+ this.wss.close();
301
+ this.wss = null;
302
+ }
303
+ this.clients.clear();
304
+ this.sessionClients.clear();
305
+ }
306
+ }
307
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/services/websocket.ts"],"names":[],"mappings":"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"}
@@ -1141,10 +1141,6 @@ video {
1141
1141
  margin-left: 0.5rem;
1142
1142
  }
1143
1143
 
1144
- .mr-1 {
1145
- margin-right: 0.25rem;
1146
- }
1147
-
1148
1144
  .mr-2 {
1149
1145
  margin-right: 0.5rem;
1150
1146
  }
@@ -1251,10 +1247,6 @@ video {
1251
1247
  justify-content: center;
1252
1248
  }
1253
1249
 
1254
- .gap-1 {
1255
- gap: 0.25rem;
1256
- }
1257
-
1258
1250
  .gap-2 {
1259
1251
  gap: 0.5rem;
1260
1252
  }
@@ -1381,10 +1373,6 @@ video {
1381
1373
  --tw-gradient-to: #16a34a var(--tw-gradient-to-position);
1382
1374
  }
1383
1375
 
1384
- .p-0\.5 {
1385
- padding: 0.125rem;
1386
- }
1387
-
1388
1376
  .p-1 {
1389
1377
  padding: 0.25rem;
1390
1378
  }
@@ -1397,10 +1385,6 @@ video {
1397
1385
  padding: 0.75rem;
1398
1386
  }
1399
1387
 
1400
- .p-4 {
1401
- padding: 1rem;
1402
- }
1403
-
1404
1388
  .p-6 {
1405
1389
  padding: 1.5rem;
1406
1390
  }
@@ -1409,11 +1393,6 @@ video {
1409
1393
  padding: 2rem;
1410
1394
  }
1411
1395
 
1412
- .px-2 {
1413
- padding-left: 0.5rem;
1414
- padding-right: 0.5rem;
1415
- }
1416
-
1417
1396
  .px-3 {
1418
1397
  padding-left: 0.75rem;
1419
1398
  padding-right: 0.75rem;
@@ -1593,10 +1572,6 @@ video {
1593
1572
  outline-offset: 2px;
1594
1573
  }
1595
1574
 
1596
- .disabled\:opacity-50:disabled {
1597
- opacity: 0.5;
1598
- }
1599
-
1600
1575
  .group:hover .group-hover\:text-white {
1601
1576
  --tw-text-opacity: 1;
1602
1577
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "treesap",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "AI Agent Framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,10 +37,14 @@
37
37
  "commander": "^12.1.0",
38
38
  "dotenv": "^16.4.7",
39
39
  "hono": "^4.9.1",
40
- "node-pty": "^1.1.0-beta34"
40
+ "node-pty": "^1.1.0-beta34",
41
+ "uuid": "^11.1.0",
42
+ "ws": "^8.18.3"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@types/node": "^22.15.15",
46
+ "@types/uuid": "^10.0.0",
47
+ "@types/ws": "^8.18.1",
44
48
  "tailwindcss": "^3.4.17",
45
49
  "tsx": "^4.19.2",
46
50
  "typescript": "^5.7.3"
@@ -14,7 +14,7 @@ export default function BaseLayout(props: BaseLayoutProps) {
14
14
  <title>{props.title || "Treesap"}</title>
15
15
  <meta name="description" content={props.description || "A modern web application"} />
16
16
  <meta charset="UTF-8" />
17
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
17
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
18
18
  <link rel="stylesheet" href="/styles/main.css" />
19
19
  {/* Sapling Islands */}
20
20
  <script type="module" src="https://sapling-is.land"></script>
@@ -53,99 +53,12 @@ export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
53
53
  </div>
54
54
  </div>
55
55
 
56
- {/* Tab Headers */}
57
- <div class="flex py-2">
58
- <button class="px-3 py-2 text-[#cccccc] disabled:opacity-50 cursor-not-allowed flex items-center justify-center">
59
- <iconify-icon icon="tabler:folder" width="20" height="20"></iconify-icon>
60
- </button>
61
- <button class="px-3 py-2 text-[#cccccc] disabled:opacity-50 cursor-not-allowed flex items-center justify-center">
62
- <iconify-icon icon="tabler:search" width="20" height="20"></iconify-icon>
63
- </button>
64
- <button class="px-3 py-2 text-white flex items-center justify-center">
65
- <iconify-icon icon="tabler:terminal" width="20" height="20"></iconify-icon>
66
- </button>
67
- <button class="px-3 py-2 text-[#cccccc] disabled:opacity-50 cursor-not-allowed flex items-center justify-center">
68
- <iconify-icon icon="tabler:git-merge" width="20" height="20"></iconify-icon>
69
- </button>
70
- </div>
71
56
 
72
57
  {/* Tab Content */}
73
58
  <div class="flex-1 overflow-hidden bg-[#1e1e1e]">
74
- {/* Terminal Tab Content (Active) */}
75
- <div class="h-full flex flex-col">
76
-
77
- {/* Terminal Tabs Bar */}
78
- <div class="border-b border-[#3c3c3c] bg-[#252526] px-3 py-1">
79
- <div class="flex items-center gap-1">
80
- {/* Terminal Tab 1 */}
81
- <button
82
- id="terminal-tab-1"
83
- class="terminal-tab flex items-center px-3 py-1 text-sm text-white bg-[#1e1e1e] border-t-2 border-[#0e639c] rounded-t-sm hover:bg-[#2d2d30] transition-colors"
84
- data-terminal-index="1"
85
- >
86
- <iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
87
- Terminal 1
88
- <button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="1" style="display: none;">
89
- <iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
90
- </button>
91
- </button>
92
-
93
- {/* Terminal Tab 2 */}
94
- <button
95
- id="terminal-tab-2"
96
- class="terminal-tab flex items-center px-3 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded-t-sm"
97
- data-terminal-index="2"
98
- style="display: none;"
99
- >
100
- <iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
101
- Terminal 2
102
- <button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="2">
103
- <iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
104
- </button>
105
- </button>
106
-
107
- {/* Terminal Tab 3 */}
108
- <button
109
- id="terminal-tab-3"
110
- class="terminal-tab flex items-center px-3 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded-t-sm"
111
- data-terminal-index="3"
112
- style="display: none;"
113
- >
114
- <iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
115
- Terminal 3
116
- <button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="3">
117
- <iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
118
- </button>
119
- </button>
120
-
121
- {/* Add Terminal Button */}
122
- <button
123
- id="add-terminal-btn"
124
- class="flex items-center px-2 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded"
125
- title="New Terminal"
126
- >
127
- <iconify-icon icon="tabler:plus" width="14" height="14"></iconify-icon>
128
- </button>
129
- </div>
130
- </div>
131
-
132
- {/* Terminal Content Area */}
133
- <div class="flex-1 overflow-hidden relative">
134
- {/* Terminal 1 Container */}
135
- <div id="terminal-container-1" class="h-full p-4 terminal-container" data-terminal-index="1">
136
- <TerminalComponent index={1} />
137
- </div>
138
-
139
- {/* Terminal 2 Container */}
140
- <div id="terminal-container-2" class="h-full p-4 terminal-container" data-terminal-index="2" style="display: none;">
141
- <TerminalComponent index={2} />
142
- </div>
143
-
144
- {/* Terminal 3 Container */}
145
- <div id="terminal-container-3" class="h-full p-4 terminal-container" data-terminal-index="3" style="display: none;">
146
- <TerminalComponent index={3} />
147
- </div>
148
- </div>
59
+ {/* Single Terminal Content */}
60
+ <div class="h-full">
61
+ <TerminalComponent index={1} />
149
62
  </div>
150
63
  </div>
151
64
  </div>
@@ -153,9 +66,6 @@ export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
153
66
  {/* Right Pane - Live Preview */}
154
67
  <SimpleLivePreview id="live-preview" previewPort={previewPort} />
155
68
  </div>
156
-
157
- {/* Terminal Tabs Management Script */}
158
- <script type="module" src="/components/TerminalTabs.js"></script>
159
69
  </Layout>
160
70
  );
161
71
  }