@cloudflare/sandbox 0.0.0-cbb7fcd → 0.0.0-cdb8197

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +203 -0
  2. package/Dockerfile +103 -10
  3. package/README.md +858 -24
  4. package/container_src/bun.lock +76 -0
  5. package/container_src/circuit-breaker.ts +121 -0
  6. package/container_src/control-process.ts +784 -0
  7. package/container_src/handler/exec.ts +185 -0
  8. package/container_src/handler/file.ts +457 -0
  9. package/container_src/handler/git.ts +130 -0
  10. package/container_src/handler/ports.ts +314 -0
  11. package/container_src/handler/process.ts +568 -0
  12. package/container_src/handler/session.ts +92 -0
  13. package/container_src/index.ts +433 -2738
  14. package/container_src/interpreter-service.ts +276 -0
  15. package/container_src/isolation.ts +1213 -0
  16. package/container_src/mime-processor.ts +255 -0
  17. package/container_src/package.json +9 -0
  18. package/container_src/runtime/executors/javascript/node_executor.ts +123 -0
  19. package/container_src/runtime/executors/python/ipython_executor.py +338 -0
  20. package/container_src/runtime/executors/typescript/ts_executor.ts +138 -0
  21. package/container_src/runtime/process-pool.ts +464 -0
  22. package/container_src/shell-escape.ts +42 -0
  23. package/container_src/startup.sh +11 -0
  24. package/container_src/types.ts +131 -0
  25. package/package.json +5 -9
  26. package/src/client.ts +464 -1366
  27. package/src/errors.ts +219 -0
  28. package/src/file-stream.ts +162 -0
  29. package/src/index.ts +78 -131
  30. package/src/interpreter-client.ts +352 -0
  31. package/src/interpreter-types.ts +390 -0
  32. package/src/interpreter.ts +150 -0
  33. package/src/request-handler.ts +144 -0
  34. package/src/sandbox.ts +756 -0
  35. package/src/security.ts +113 -0
  36. package/src/sse-parser.ts +147 -0
  37. package/src/types.ts +571 -0
  38. package/tsconfig.json +1 -1
  39. package/tests/client.example.ts +0 -308
  40. package/tests/connection-test.ts +0 -81
  41. package/tests/simple-test.ts +0 -81
  42. package/tests/test1.ts +0 -281
  43. package/tests/test2.ts +0 -929
@@ -1,86 +1,94 @@
1
- import { spawn } from "node:child_process";
2
- import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
- import { dirname } from "node:path";
4
1
  import { serve } from "bun";
2
+ import {
3
+ handleExecuteRequest,
4
+ handleStreamingExecuteRequest,
5
+ } from "./handler/exec";
6
+ import {
7
+ handleDeleteFileRequest,
8
+ handleListFilesRequest,
9
+ handleMkdirRequest,
10
+ handleMoveFileRequest,
11
+ handleReadFileRequest,
12
+ handleReadFileStreamRequest,
13
+ handleRenameFileRequest,
14
+ handleWriteFileRequest,
15
+ } from "./handler/file";
16
+ import { handleGitCheckoutRequest } from "./handler/git";
17
+ import {
18
+ handleExposePortRequest,
19
+ handleGetExposedPortsRequest,
20
+ handleProxyRequest,
21
+ handleUnexposePortRequest,
22
+ } from "./handler/ports";
23
+ import {
24
+ handleGetProcessLogsRequest,
25
+ handleGetProcessRequest,
26
+ handleKillAllProcessesRequest,
27
+ handleKillProcessRequest,
28
+ handleListProcessesRequest,
29
+ handleStartProcessRequest,
30
+ handleStreamProcessLogsRequest,
31
+ } from "./handler/process";
32
+ import { handleCreateSession, handleListSessions } from "./handler/session";
33
+ import type { CreateContextRequest } from "./interpreter-service";
34
+ import {
35
+ InterpreterNotReadyError,
36
+ InterpreterService,
37
+ } from "./interpreter-service";
38
+ import { hasNamespaceSupport, SessionManager } from "./isolation";
39
+
40
+ // In-memory storage for exposed ports
41
+ const exposedPorts = new Map<number, { name?: string; exposedAt: Date }>();
42
+
43
+ // Check isolation capabilities on startup
44
+ const isolationAvailable = hasNamespaceSupport();
45
+ console.log(
46
+ `[Container] Process isolation: ${
47
+ isolationAvailable
48
+ ? "ENABLED (production mode)"
49
+ : "DISABLED (development mode)"
50
+ }`
51
+ );
5
52
 
6
- interface ExecuteRequest {
7
- command: string;
8
- args?: string[];
9
- }
10
-
11
- interface GitCheckoutRequest {
12
- repoUrl: string;
13
- branch?: string;
14
- targetDir?: string;
15
- sessionId?: string;
16
- }
17
-
18
- interface MkdirRequest {
19
- path: string;
20
- recursive?: boolean;
21
- sessionId?: string;
22
- }
23
-
24
- interface WriteFileRequest {
25
- path: string;
26
- content: string;
27
- encoding?: string;
28
- sessionId?: string;
29
- }
30
-
31
- interface ReadFileRequest {
32
- path: string;
33
- encoding?: string;
34
- sessionId?: string;
35
- }
36
-
37
- interface DeleteFileRequest {
38
- path: string;
39
- sessionId?: string;
40
- }
41
-
42
- interface RenameFileRequest {
43
- oldPath: string;
44
- newPath: string;
45
- sessionId?: string;
46
- }
47
-
48
- interface MoveFileRequest {
49
- sourcePath: string;
50
- destinationPath: string;
51
- sessionId?: string;
52
- }
53
-
54
- interface SessionData {
55
- sessionId: string;
56
- activeProcess: any | null;
57
- createdAt: Date;
58
- }
59
-
60
- // In-memory session storage (in production, you'd want to use a proper database)
61
- const sessions = new Map<string, SessionData>();
62
-
63
- // Generate a unique session ID
64
- function generateSessionId(): string {
65
- return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
66
- }
67
-
68
- // Clean up old sessions (older than 1 hour)
69
- function cleanupOldSessions() {
70
- const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
71
- for (const [sessionId, session] of sessions.entries()) {
72
- if (session.createdAt < oneHourAgo && !session.activeProcess) {
73
- sessions.delete(sessionId);
74
- console.log(`[Server] Cleaned up old session: ${sessionId}`);
75
- }
76
- }
77
- }
78
-
79
- // Run cleanup every 10 minutes
80
- setInterval(cleanupOldSessions, 10 * 60 * 1000);
53
+ // Session manager for secure execution with isolation
54
+ const sessionManager = new SessionManager();
55
+
56
+ // Graceful shutdown handler
57
+ const SHUTDOWN_GRACE_PERIOD_MS = 5000; // Grace period for cleanup (5 seconds for proper async cleanup)
58
+
59
+ process.on("SIGTERM", async () => {
60
+ console.log("[Container] SIGTERM received, cleaning up sessions...");
61
+ await sessionManager.destroyAll();
62
+ setTimeout(() => {
63
+ process.exit(0);
64
+ }, SHUTDOWN_GRACE_PERIOD_MS);
65
+ });
66
+
67
+ process.on("SIGINT", async () => {
68
+ console.log("[Container] SIGINT received, cleaning up sessions...");
69
+ await sessionManager.destroyAll();
70
+ setTimeout(() => {
71
+ process.exit(0);
72
+ }, SHUTDOWN_GRACE_PERIOD_MS);
73
+ });
74
+
75
+ // Cleanup on uncaught exceptions (log but still exit)
76
+ process.on("uncaughtException", async (error) => {
77
+ console.error("[Container] Uncaught exception:", error);
78
+ await sessionManager.destroyAll();
79
+ process.exit(1);
80
+ });
81
+
82
+ // Initialize interpreter service
83
+ const interpreterService = new InterpreterService();
84
+
85
+ // No initialization needed - service is ready immediately!
86
+ console.log("[Container] Interpreter service ready - no cold start!");
87
+ console.log("[Container] All API endpoints available immediately");
81
88
 
82
89
  const server = serve({
83
- fetch(req: Request) {
90
+ idleTimeout: 255,
91
+ async fetch(req: Request) {
84
92
  const url = new URL(req.url);
85
93
  const pathname = url.pathname;
86
94
 
@@ -111,123 +119,44 @@ const server = serve({
111
119
  },
112
120
  });
113
121
 
114
- case "/api/hello":
115
- return new Response(
116
- JSON.stringify({
117
- message: "Hello from API!",
118
- timestamp: new Date().toISOString(),
119
- }),
120
- {
121
- headers: {
122
- "Content-Type": "application/json",
123
- ...corsHeaders,
124
- },
125
- }
126
- );
127
-
128
- case "/api/users":
129
- if (req.method === "GET") {
130
- return new Response(
131
- JSON.stringify([
132
- { id: 1, name: "Alice" },
133
- { id: 2, name: "Bob" },
134
- { id: 3, name: "Charlie" },
135
- ]),
136
- {
137
- headers: {
138
- "Content-Type": "application/json",
139
- ...corsHeaders,
140
- },
141
- }
142
- );
143
- } else if (req.method === "POST") {
144
- return new Response(
145
- JSON.stringify({
146
- message: "User created successfully",
147
- method: "POST",
148
- }),
149
- {
150
- headers: {
151
- "Content-Type": "application/json",
152
- ...corsHeaders,
153
- },
154
- }
155
- );
156
- }
157
- break;
158
-
159
122
  case "/api/session/create":
160
123
  if (req.method === "POST") {
161
- const sessionId = generateSessionId();
162
- const sessionData: SessionData = {
163
- activeProcess: null,
164
- createdAt: new Date(),
165
- sessionId,
166
- };
167
- sessions.set(sessionId, sessionData);
168
-
169
- console.log(`[Server] Created new session: ${sessionId}`);
170
-
171
- return new Response(
172
- JSON.stringify({
173
- message: "Session created successfully",
174
- sessionId,
175
- timestamp: new Date().toISOString(),
176
- }),
177
- {
178
- headers: {
179
- "Content-Type": "application/json",
180
- ...corsHeaders,
181
- },
182
- }
183
- );
124
+ return handleCreateSession(req, corsHeaders, sessionManager);
184
125
  }
185
126
  break;
186
127
 
187
128
  case "/api/session/list":
188
129
  if (req.method === "GET") {
189
- const sessionList = Array.from(sessions.values()).map(
190
- (session) => ({
191
- createdAt: session.createdAt.toISOString(),
192
- hasActiveProcess: !!session.activeProcess,
193
- sessionId: session.sessionId,
194
- })
195
- );
196
-
197
- return new Response(
198
- JSON.stringify({
199
- count: sessionList.length,
200
- sessions: sessionList,
201
- timestamp: new Date().toISOString(),
202
- }),
203
- {
204
- headers: {
205
- "Content-Type": "application/json",
206
- ...corsHeaders,
207
- },
208
- }
209
- );
130
+ return handleListSessions(corsHeaders, sessionManager);
210
131
  }
211
132
  break;
212
133
 
213
134
  case "/api/execute":
214
135
  if (req.method === "POST") {
215
- return handleExecuteRequest(req, corsHeaders);
136
+ return handleExecuteRequest(req, corsHeaders, sessionManager);
216
137
  }
217
138
  break;
218
139
 
219
140
  case "/api/execute/stream":
220
141
  if (req.method === "POST") {
221
- return handleStreamingExecuteRequest(req, corsHeaders);
142
+ return handleStreamingExecuteRequest(
143
+ req,
144
+ sessionManager,
145
+ corsHeaders
146
+ );
222
147
  }
223
148
  break;
224
149
 
225
150
  case "/api/ping":
226
151
  if (req.method === "GET") {
152
+ const health = await interpreterService.getHealthStatus();
227
153
  return new Response(
228
154
  JSON.stringify({
229
155
  message: "pong",
230
156
  timestamp: new Date().toISOString(),
157
+ system: "interpreter (70x faster)",
158
+ status: health.ready ? "ready" : "initializing",
159
+ progress: health.progress,
231
160
  }),
232
161
  {
233
162
  headers: {
@@ -239,123 +168,373 @@ const server = serve({
239
168
  }
240
169
  break;
241
170
 
242
- case "/api/commands":
243
- if (req.method === "GET") {
244
- return new Response(
245
- JSON.stringify({
246
- availableCommands: [
247
- "ls",
248
- "pwd",
249
- "echo",
250
- "cat",
251
- "grep",
252
- "find",
253
- "whoami",
254
- "date",
255
- "uptime",
256
- "ps",
257
- "top",
258
- "df",
259
- "du",
260
- "free",
261
- ],
262
- timestamp: new Date().toISOString(),
263
- }),
264
- {
265
- headers: {
266
- "Content-Type": "application/json",
267
- ...corsHeaders,
268
- },
269
- }
270
- );
171
+ case "/api/git/checkout":
172
+ if (req.method === "POST") {
173
+ return handleGitCheckoutRequest(req, corsHeaders, sessionManager);
271
174
  }
272
175
  break;
273
176
 
274
- case "/api/git/checkout":
177
+ case "/api/mkdir":
275
178
  if (req.method === "POST") {
276
- return handleGitCheckoutRequest(req, corsHeaders);
179
+ return handleMkdirRequest(req, corsHeaders, sessionManager);
277
180
  }
278
181
  break;
279
182
 
280
- case "/api/git/checkout/stream":
183
+ case "/api/write":
281
184
  if (req.method === "POST") {
282
- return handleStreamingGitCheckoutRequest(req, corsHeaders);
185
+ return handleWriteFileRequest(req, corsHeaders, sessionManager);
283
186
  }
284
187
  break;
285
188
 
286
- case "/api/mkdir":
189
+ case "/api/read":
287
190
  if (req.method === "POST") {
288
- return handleMkdirRequest(req, corsHeaders);
191
+ return handleReadFileRequest(req, corsHeaders, sessionManager);
289
192
  }
290
193
  break;
291
194
 
292
- case "/api/mkdir/stream":
195
+ case "/api/read/stream":
293
196
  if (req.method === "POST") {
294
- return handleStreamingMkdirRequest(req, corsHeaders);
197
+ return handleReadFileStreamRequest(req, corsHeaders, sessionManager);
295
198
  }
296
199
  break;
297
200
 
298
- case "/api/write":
201
+ case "/api/delete":
299
202
  if (req.method === "POST") {
300
- return handleWriteFileRequest(req, corsHeaders);
203
+ return handleDeleteFileRequest(req, corsHeaders, sessionManager);
301
204
  }
302
205
  break;
303
206
 
304
- case "/api/write/stream":
207
+ case "/api/rename":
305
208
  if (req.method === "POST") {
306
- return handleStreamingWriteFileRequest(req, corsHeaders);
209
+ return handleRenameFileRequest(req, corsHeaders, sessionManager);
307
210
  }
308
211
  break;
309
212
 
310
- case "/api/read":
213
+ case "/api/move":
311
214
  if (req.method === "POST") {
312
- return handleReadFileRequest(req, corsHeaders);
215
+ return handleMoveFileRequest(req, corsHeaders, sessionManager);
313
216
  }
314
217
  break;
315
218
 
316
- case "/api/read/stream":
219
+ case "/api/list-files":
317
220
  if (req.method === "POST") {
318
- return handleStreamingReadFileRequest(req, corsHeaders);
221
+ return handleListFilesRequest(req, corsHeaders, sessionManager);
319
222
  }
320
223
  break;
321
224
 
322
- case "/api/delete":
225
+ case "/api/expose-port":
323
226
  if (req.method === "POST") {
324
- return handleDeleteFileRequest(req, corsHeaders);
227
+ return handleExposePortRequest(exposedPorts, req, corsHeaders);
325
228
  }
326
229
  break;
327
230
 
328
- case "/api/delete/stream":
329
- if (req.method === "POST") {
330
- return handleStreamingDeleteFileRequest(req, corsHeaders);
231
+ case "/api/unexpose-port":
232
+ if (req.method === "DELETE") {
233
+ return handleUnexposePortRequest(exposedPorts, req, corsHeaders);
331
234
  }
332
235
  break;
333
236
 
334
- case "/api/rename":
335
- if (req.method === "POST") {
336
- return handleRenameFileRequest(req, corsHeaders);
237
+ case "/api/exposed-ports":
238
+ if (req.method === "GET") {
239
+ return handleGetExposedPortsRequest(exposedPorts, req, corsHeaders);
337
240
  }
338
241
  break;
339
242
 
340
- case "/api/rename/stream":
243
+ case "/api/process/start":
341
244
  if (req.method === "POST") {
342
- return handleStreamingRenameFileRequest(req, corsHeaders);
245
+ return handleStartProcessRequest(req, corsHeaders, sessionManager);
343
246
  }
344
247
  break;
345
248
 
346
- case "/api/move":
249
+ case "/api/process/list":
250
+ if (req.method === "GET") {
251
+ return handleListProcessesRequest(req, corsHeaders, sessionManager);
252
+ }
253
+ break;
254
+
255
+ case "/api/process/kill-all":
256
+ if (req.method === "DELETE") {
257
+ return handleKillAllProcessesRequest(
258
+ req,
259
+ corsHeaders,
260
+ sessionManager
261
+ );
262
+ }
263
+ break;
264
+
265
+ case "/api/contexts":
347
266
  if (req.method === "POST") {
348
- return handleMoveFileRequest(req, corsHeaders);
267
+ try {
268
+ const body = (await req.json()) as CreateContextRequest;
269
+ const context = await interpreterService.createContext(body);
270
+ return new Response(
271
+ JSON.stringify({
272
+ id: context.id,
273
+ language: context.language,
274
+ cwd: context.cwd,
275
+ createdAt: context.createdAt,
276
+ lastUsed: context.lastUsed,
277
+ }),
278
+ {
279
+ headers: {
280
+ "Content-Type": "application/json",
281
+ ...corsHeaders,
282
+ },
283
+ }
284
+ );
285
+ } catch (error) {
286
+ if (error instanceof InterpreterNotReadyError) {
287
+ console.log(
288
+ `[Container] Request timed out waiting for interpreter (${error.progress}% complete)`
289
+ );
290
+ return new Response(
291
+ JSON.stringify({
292
+ error: error.message,
293
+ status: "initializing",
294
+ progress: error.progress,
295
+ }),
296
+ {
297
+ status: 503,
298
+ headers: {
299
+ "Content-Type": "application/json",
300
+ "Retry-After": String(error.retryAfter),
301
+ ...corsHeaders,
302
+ },
303
+ }
304
+ );
305
+ }
306
+
307
+ // Check if it's a circuit breaker error
308
+ if (
309
+ error instanceof Error &&
310
+ error.message.includes("Circuit breaker is open")
311
+ ) {
312
+ console.log(
313
+ "[Container] Circuit breaker is open:",
314
+ error.message
315
+ );
316
+ return new Response(
317
+ JSON.stringify({
318
+ error:
319
+ "Service temporarily unavailable due to high error rate. Please try again later.",
320
+ status: "circuit_open",
321
+ details: error.message,
322
+ }),
323
+ {
324
+ status: 503,
325
+ headers: {
326
+ "Content-Type": "application/json",
327
+ "Retry-After": "60",
328
+ ...corsHeaders,
329
+ },
330
+ }
331
+ );
332
+ }
333
+
334
+ // Only log actual errors with stack traces
335
+ console.error("[Container] Error creating context:", error);
336
+ return new Response(
337
+ JSON.stringify({
338
+ error:
339
+ error instanceof Error
340
+ ? error.message
341
+ : "Failed to create context",
342
+ }),
343
+ {
344
+ status: 500,
345
+ headers: {
346
+ "Content-Type": "application/json",
347
+ ...corsHeaders,
348
+ },
349
+ }
350
+ );
351
+ }
352
+ } else if (req.method === "GET") {
353
+ const contexts = await interpreterService.listContexts();
354
+ return new Response(JSON.stringify({ contexts }), {
355
+ headers: {
356
+ "Content-Type": "application/json",
357
+ ...corsHeaders,
358
+ },
359
+ });
349
360
  }
350
361
  break;
351
362
 
352
- case "/api/move/stream":
363
+ case "/api/execute/code":
353
364
  if (req.method === "POST") {
354
- return handleStreamingMoveFileRequest(req, corsHeaders);
365
+ try {
366
+ const body = (await req.json()) as {
367
+ context_id: string;
368
+ code: string;
369
+ language?: string;
370
+ };
371
+ return await interpreterService.executeCode(
372
+ body.context_id,
373
+ body.code,
374
+ body.language
375
+ );
376
+ } catch (error) {
377
+ // Check if it's a circuit breaker error
378
+ if (
379
+ error instanceof Error &&
380
+ error.message.includes("Circuit breaker is open")
381
+ ) {
382
+ console.log(
383
+ "[Container] Circuit breaker is open for code execution:",
384
+ error.message
385
+ );
386
+ return new Response(
387
+ JSON.stringify({
388
+ error:
389
+ "Service temporarily unavailable due to high error rate. Please try again later.",
390
+ status: "circuit_open",
391
+ details: error.message,
392
+ }),
393
+ {
394
+ status: 503,
395
+ headers: {
396
+ "Content-Type": "application/json",
397
+ "Retry-After": "30",
398
+ ...corsHeaders,
399
+ },
400
+ }
401
+ );
402
+ }
403
+
404
+ // Don't log stack traces for expected initialization state
405
+ if (
406
+ error instanceof Error &&
407
+ error.message.includes("initializing")
408
+ ) {
409
+ console.log(
410
+ "[Container] Code execution deferred - service still initializing"
411
+ );
412
+ } else {
413
+ console.error("[Container] Error executing code:", error);
414
+ }
415
+ // Error response is already handled by service.executeCode for not ready state
416
+ return new Response(
417
+ JSON.stringify({
418
+ error:
419
+ error instanceof Error
420
+ ? error.message
421
+ : "Failed to execute code",
422
+ }),
423
+ {
424
+ status: 500,
425
+ headers: {
426
+ "Content-Type": "application/json",
427
+ ...corsHeaders,
428
+ },
429
+ }
430
+ );
431
+ }
355
432
  }
356
433
  break;
357
434
 
358
435
  default:
436
+ // Handle dynamic routes for contexts
437
+ if (
438
+ pathname.startsWith("/api/contexts/") &&
439
+ pathname.split("/").length === 4
440
+ ) {
441
+ const contextId = pathname.split("/")[3];
442
+ if (req.method === "DELETE") {
443
+ try {
444
+ await interpreterService.deleteContext(contextId);
445
+ return new Response(JSON.stringify({ success: true }), {
446
+ headers: {
447
+ "Content-Type": "application/json",
448
+ ...corsHeaders,
449
+ },
450
+ });
451
+ } catch (error) {
452
+ if (error instanceof InterpreterNotReadyError) {
453
+ console.log(
454
+ `[Container] Request timed out waiting for interpreter (${error.progress}% complete)`
455
+ );
456
+ return new Response(
457
+ JSON.stringify({
458
+ error: error.message,
459
+ status: "initializing",
460
+ progress: error.progress,
461
+ }),
462
+ {
463
+ status: 503,
464
+ headers: {
465
+ "Content-Type": "application/json",
466
+ "Retry-After": "5",
467
+ ...corsHeaders,
468
+ },
469
+ }
470
+ );
471
+ }
472
+ return new Response(
473
+ JSON.stringify({
474
+ error:
475
+ error instanceof Error
476
+ ? error.message
477
+ : "Failed to delete context",
478
+ }),
479
+ {
480
+ status:
481
+ error instanceof Error &&
482
+ error.message.includes("not found")
483
+ ? 404
484
+ : 500,
485
+ headers: {
486
+ "Content-Type": "application/json",
487
+ ...corsHeaders,
488
+ },
489
+ }
490
+ );
491
+ }
492
+ }
493
+ }
494
+
495
+ // Handle dynamic routes for individual processes
496
+ if (pathname.startsWith("/api/process/")) {
497
+ const segments = pathname.split("/");
498
+ if (segments.length >= 4) {
499
+ const processId = segments[3];
500
+ const action = segments[4]; // Optional: logs, stream, etc.
501
+
502
+ if (!action && req.method === "GET") {
503
+ return handleGetProcessRequest(
504
+ req,
505
+ corsHeaders,
506
+ processId,
507
+ sessionManager
508
+ );
509
+ } else if (!action && req.method === "DELETE") {
510
+ return handleKillProcessRequest(
511
+ req,
512
+ corsHeaders,
513
+ processId,
514
+ sessionManager
515
+ );
516
+ } else if (action === "logs" && req.method === "GET") {
517
+ return handleGetProcessLogsRequest(
518
+ req,
519
+ corsHeaders,
520
+ processId,
521
+ sessionManager
522
+ );
523
+ } else if (action === "stream" && req.method === "GET") {
524
+ return handleStreamProcessLogsRequest(
525
+ req,
526
+ corsHeaders,
527
+ processId,
528
+ sessionManager
529
+ );
530
+ }
531
+ }
532
+ }
533
+ // Check if this is a proxy request for an exposed port
534
+ if (pathname.startsWith("/proxy/")) {
535
+ return handleProxyRequest(exposedPorts, req, corsHeaders);
536
+ }
537
+
359
538
  console.log(`[Container] Route not found: ${pathname}`);
360
539
  return new Response("Not Found", {
361
540
  headers: corsHeaders,
@@ -363,7 +542,10 @@ const server = serve({
363
542
  });
364
543
  }
365
544
  } catch (error) {
366
- console.error(`[Container] Error handling ${req.method} ${pathname}:`, error);
545
+ console.error(
546
+ `[Container] Error handling ${req.method} ${pathname}:`,
547
+ error
548
+ );
367
549
  return new Response(
368
550
  JSON.stringify({
369
551
  error: "Internal server error",
@@ -381,2504 +563,9 @@ const server = serve({
381
563
  },
382
564
  hostname: "0.0.0.0",
383
565
  port: 3000,
384
- } as any);
385
-
386
- async function handleExecuteRequest(
387
- req: Request,
388
- corsHeaders: Record<string, string>
389
- ): Promise<Response> {
390
- try {
391
- const body = (await req.json()) as ExecuteRequest & { sessionId?: string };
392
- const { command, args = [], sessionId } = body;
393
-
394
- if (!command || typeof command !== "string") {
395
- return new Response(
396
- JSON.stringify({
397
- error: "Command is required and must be a string",
398
- }),
399
- {
400
- headers: {
401
- "Content-Type": "application/json",
402
- ...corsHeaders,
403
- },
404
- status: 400,
405
- }
406
- );
407
- }
408
-
409
- // Basic safety check - prevent dangerous commands
410
- const dangerousCommands = [
411
- "rm",
412
- "rmdir",
413
- "del",
414
- "format",
415
- "shutdown",
416
- "reboot",
417
- ];
418
- const lowerCommand = command.toLowerCase();
419
-
420
- if (
421
- dangerousCommands.some((dangerous) => lowerCommand.includes(dangerous))
422
- ) {
423
- return new Response(
424
- JSON.stringify({
425
- error: "Dangerous command not allowed",
426
- }),
427
- {
428
- headers: {
429
- "Content-Type": "application/json",
430
- ...corsHeaders,
431
- },
432
- status: 400,
433
- }
434
- );
435
- }
436
-
437
- console.log(`[Server] Executing command: ${command} ${args.join(" ")}`);
438
-
439
- const result = await executeCommand(command, args, sessionId);
440
-
441
- return new Response(
442
- JSON.stringify({
443
- args,
444
- command,
445
- exitCode: result.exitCode,
446
- stderr: result.stderr,
447
- stdout: result.stdout,
448
- success: result.success,
449
- timestamp: new Date().toISOString(),
450
- }),
451
- {
452
- headers: {
453
- "Content-Type": "application/json",
454
- ...corsHeaders,
455
- },
456
- }
457
- );
458
- } catch (error) {
459
- console.error("[Server] Error in handleExecuteRequest:", error);
460
- return new Response(
461
- JSON.stringify({
462
- error: "Failed to execute command",
463
- message: error instanceof Error ? error.message : "Unknown error",
464
- }),
465
- {
466
- headers: {
467
- "Content-Type": "application/json",
468
- ...corsHeaders,
469
- },
470
- status: 500,
471
- }
472
- );
473
- }
474
- }
475
-
476
- async function handleStreamingExecuteRequest(
477
- req: Request,
478
- corsHeaders: Record<string, string>
479
- ): Promise<Response> {
480
- try {
481
- const body = (await req.json()) as ExecuteRequest & { sessionId?: string };
482
- const { command, args = [], sessionId } = body;
483
-
484
- if (!command || typeof command !== "string") {
485
- return new Response(
486
- JSON.stringify({
487
- error: "Command is required and must be a string",
488
- }),
489
- {
490
- headers: {
491
- "Content-Type": "application/json",
492
- ...corsHeaders,
493
- },
494
- status: 400,
495
- }
496
- );
497
- }
498
-
499
- // Basic safety check - prevent dangerous commands
500
- const dangerousCommands = [
501
- "rm",
502
- "rmdir",
503
- "del",
504
- "format",
505
- "shutdown",
506
- "reboot",
507
- ];
508
- const lowerCommand = command.toLowerCase();
509
-
510
- if (
511
- dangerousCommands.some((dangerous) => lowerCommand.includes(dangerous))
512
- ) {
513
- return new Response(
514
- JSON.stringify({
515
- error: "Dangerous command not allowed",
516
- }),
517
- {
518
- headers: {
519
- "Content-Type": "application/json",
520
- ...corsHeaders,
521
- },
522
- status: 400,
523
- }
524
- );
525
- }
526
-
527
- console.log(
528
- `[Server] Executing streaming command: ${command} ${args.join(" ")}`
529
- );
530
-
531
- const stream = new ReadableStream({
532
- start(controller) {
533
- const child = spawn(command, args, {
534
- shell: true,
535
- stdio: ["pipe", "pipe", "pipe"],
536
- });
537
-
538
- // Store the process reference for cleanup if sessionId is provided
539
- if (sessionId && sessions.has(sessionId)) {
540
- const session = sessions.get(sessionId)!;
541
- session.activeProcess = child;
542
- }
543
-
544
- let stdout = "";
545
- let stderr = "";
546
-
547
- // Send command start event
548
- controller.enqueue(
549
- new TextEncoder().encode(
550
- `data: ${JSON.stringify({
551
- args,
552
- command,
553
- timestamp: new Date().toISOString(),
554
- type: "command_start",
555
- })}\n\n`
556
- )
557
- );
558
-
559
- child.stdout?.on("data", (data) => {
560
- const output = data.toString();
561
- stdout += output;
562
-
563
- // Send real-time output
564
- controller.enqueue(
565
- new TextEncoder().encode(
566
- `data: ${JSON.stringify({
567
- command,
568
- data: output,
569
- stream: "stdout",
570
- type: "output",
571
- })}\n\n`
572
- )
573
- );
574
- });
575
-
576
- child.stderr?.on("data", (data) => {
577
- const output = data.toString();
578
- stderr += output;
579
-
580
- // Send real-time error output
581
- controller.enqueue(
582
- new TextEncoder().encode(
583
- `data: ${JSON.stringify({
584
- command,
585
- data: output,
586
- stream: "stderr",
587
- type: "output",
588
- })}\n\n`
589
- )
590
- );
591
- });
592
-
593
- child.on("close", (code) => {
594
- // Clear the active process reference
595
- if (sessionId && sessions.has(sessionId)) {
596
- const session = sessions.get(sessionId)!;
597
- session.activeProcess = null;
598
- }
599
-
600
- console.log(
601
- `[Server] Command completed: ${command}, Exit code: ${code}`
602
- );
603
-
604
- // Send command completion event
605
- controller.enqueue(
606
- new TextEncoder().encode(
607
- `data: ${JSON.stringify({
608
- args,
609
- command,
610
- exitCode: code,
611
- stderr,
612
- stdout,
613
- success: code === 0,
614
- timestamp: new Date().toISOString(),
615
- type: "command_complete",
616
- })}\n\n`
617
- )
618
- );
619
-
620
- controller.close();
621
- });
622
-
623
- child.on("error", (error) => {
624
- // Clear the active process reference
625
- if (sessionId && sessions.has(sessionId)) {
626
- const session = sessions.get(sessionId)!;
627
- session.activeProcess = null;
628
- }
629
-
630
- controller.enqueue(
631
- new TextEncoder().encode(
632
- `data: ${JSON.stringify({
633
- args,
634
- command,
635
- error: error.message,
636
- type: "error",
637
- })}\n\n`
638
- )
639
- );
640
-
641
- controller.close();
642
- });
643
- },
644
- });
645
-
646
- return new Response(stream, {
647
- headers: {
648
- "Cache-Control": "no-cache",
649
- Connection: "keep-alive",
650
- "Content-Type": "text/event-stream",
651
- ...corsHeaders,
652
- },
653
- });
654
- } catch (error) {
655
- console.error("[Server] Error in handleStreamingExecuteRequest:", error);
656
- return new Response(
657
- JSON.stringify({
658
- error: "Failed to execute streaming command",
659
- message: error instanceof Error ? error.message : "Unknown error",
660
- }),
661
- {
662
- headers: {
663
- "Content-Type": "application/json",
664
- ...corsHeaders,
665
- },
666
- status: 500,
667
- }
668
- );
669
- }
670
- }
671
-
672
- async function handleGitCheckoutRequest(
673
- req: Request,
674
- corsHeaders: Record<string, string>
675
- ): Promise<Response> {
676
- try {
677
- const body = (await req.json()) as GitCheckoutRequest;
678
- const { repoUrl, branch = "main", targetDir, sessionId } = body;
679
-
680
- if (!repoUrl || typeof repoUrl !== "string") {
681
- return new Response(
682
- JSON.stringify({
683
- error: "Repository URL is required and must be a string",
684
- }),
685
- {
686
- headers: {
687
- "Content-Type": "application/json",
688
- ...corsHeaders,
689
- },
690
- status: 400,
691
- }
692
- );
693
- }
694
-
695
- // Validate repository URL format
696
- const urlPattern =
697
- /^(https?:\/\/|git@|ssh:\/\/).*\.git$|^https?:\/\/.*\/.*$/;
698
- if (!urlPattern.test(repoUrl)) {
699
- return new Response(
700
- JSON.stringify({
701
- error: "Invalid repository URL format",
702
- }),
703
- {
704
- headers: {
705
- "Content-Type": "application/json",
706
- ...corsHeaders,
707
- },
708
- status: 400,
709
- }
710
- );
711
- }
712
-
713
- // Generate target directory if not provided
714
- const checkoutDir =
715
- targetDir ||
716
- `repo_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
717
-
718
- console.log(
719
- `[Server] Checking out repository: ${repoUrl} to ${checkoutDir}`
720
- );
721
-
722
- const result = await executeGitCheckout(
723
- repoUrl,
724
- branch,
725
- checkoutDir,
726
- sessionId
727
- );
728
-
729
- return new Response(
730
- JSON.stringify({
731
- branch,
732
- exitCode: result.exitCode,
733
- repoUrl,
734
- stderr: result.stderr,
735
- stdout: result.stdout,
736
- success: result.success,
737
- targetDir: checkoutDir,
738
- timestamp: new Date().toISOString(),
739
- }),
740
- {
741
- headers: {
742
- "Content-Type": "application/json",
743
- ...corsHeaders,
744
- },
745
- }
746
- );
747
- } catch (error) {
748
- console.error("[Server] Error in handleGitCheckoutRequest:", error);
749
- return new Response(
750
- JSON.stringify({
751
- error: "Failed to checkout repository",
752
- message: error instanceof Error ? error.message : "Unknown error",
753
- }),
754
- {
755
- headers: {
756
- "Content-Type": "application/json",
757
- ...corsHeaders,
758
- },
759
- status: 500,
760
- }
761
- );
762
- }
763
- }
764
-
765
- async function handleStreamingGitCheckoutRequest(
766
- req: Request,
767
- corsHeaders: Record<string, string>
768
- ): Promise<Response> {
769
- try {
770
- const body = (await req.json()) as GitCheckoutRequest;
771
- const { repoUrl, branch = "main", targetDir, sessionId } = body;
772
-
773
- if (!repoUrl || typeof repoUrl !== "string") {
774
- return new Response(
775
- JSON.stringify({
776
- error: "Repository URL is required and must be a string",
777
- }),
778
- {
779
- headers: {
780
- "Content-Type": "application/json",
781
- ...corsHeaders,
782
- },
783
- status: 400,
784
- }
785
- );
786
- }
787
-
788
- // Validate repository URL format
789
- const urlPattern =
790
- /^(https?:\/\/|git@|ssh:\/\/).*\.git$|^https?:\/\/.*\/.*$/;
791
- if (!urlPattern.test(repoUrl)) {
792
- return new Response(
793
- JSON.stringify({
794
- error: "Invalid repository URL format",
795
- }),
796
- {
797
- headers: {
798
- "Content-Type": "application/json",
799
- ...corsHeaders,
800
- },
801
- status: 400,
802
- }
803
- );
804
- }
805
-
806
- // Generate target directory if not provided
807
- const checkoutDir =
808
- targetDir ||
809
- `repo_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
810
-
811
- console.log(
812
- `[Server] Checking out repository: ${repoUrl} to ${checkoutDir}`
813
- );
814
-
815
- const stream = new ReadableStream({
816
- start(controller) {
817
- const child = spawn(
818
- "git",
819
- ["clone", "-b", branch, repoUrl, checkoutDir],
820
- {
821
- shell: true,
822
- stdio: ["pipe", "pipe", "pipe"],
823
- }
824
- );
825
-
826
- // Store the process reference for cleanup if sessionId is provided
827
- if (sessionId && sessions.has(sessionId)) {
828
- const session = sessions.get(sessionId)!;
829
- session.activeProcess = child;
830
- }
831
-
832
- let stdout = "";
833
- let stderr = "";
834
-
835
- // Send command start event
836
- controller.enqueue(
837
- new TextEncoder().encode(
838
- `data: ${JSON.stringify({
839
- args: [branch, repoUrl, checkoutDir],
840
- command: "git clone",
841
- timestamp: new Date().toISOString(),
842
- type: "command_start",
843
- })}\n\n`
844
- )
845
- );
846
-
847
- child.stdout?.on("data", (data) => {
848
- const output = data.toString();
849
- stdout += output;
850
-
851
- // Send real-time output
852
- controller.enqueue(
853
- new TextEncoder().encode(
854
- `data: ${JSON.stringify({
855
- command: "git clone",
856
- data: output,
857
- stream: "stdout",
858
- type: "output",
859
- })}\n\n`
860
- )
861
- );
862
- });
863
-
864
- child.stderr?.on("data", (data) => {
865
- const output = data.toString();
866
- stderr += output;
867
-
868
- // Send real-time error output
869
- controller.enqueue(
870
- new TextEncoder().encode(
871
- `data: ${JSON.stringify({
872
- command: "git clone",
873
- data: output,
874
- stream: "stderr",
875
- type: "output",
876
- })}\n\n`
877
- )
878
- );
879
- });
880
-
881
- child.on("close", (code) => {
882
- // Clear the active process reference
883
- if (sessionId && sessions.has(sessionId)) {
884
- const session = sessions.get(sessionId)!;
885
- session.activeProcess = null;
886
- }
887
-
888
- console.log(
889
- `[Server] Command completed: git clone, Exit code: ${code}`
890
- );
891
-
892
- // Send command completion event
893
- controller.enqueue(
894
- new TextEncoder().encode(
895
- `data: ${JSON.stringify({
896
- args: [branch, repoUrl, checkoutDir],
897
- command: "git clone",
898
- exitCode: code,
899
- stderr,
900
- stdout,
901
- success: code === 0,
902
- timestamp: new Date().toISOString(),
903
- type: "command_complete",
904
- })}\n\n`
905
- )
906
- );
907
-
908
- controller.close();
909
- });
910
-
911
- child.on("error", (error) => {
912
- // Clear the active process reference
913
- if (sessionId && sessions.has(sessionId)) {
914
- const session = sessions.get(sessionId)!;
915
- session.activeProcess = null;
916
- }
917
-
918
- controller.enqueue(
919
- new TextEncoder().encode(
920
- `data: ${JSON.stringify({
921
- args: [branch, repoUrl, checkoutDir],
922
- command: "git clone",
923
- error: error.message,
924
- type: "error",
925
- })}\n\n`
926
- )
927
- );
928
-
929
- controller.close();
930
- });
931
- },
932
- });
933
-
934
- return new Response(stream, {
935
- headers: {
936
- "Cache-Control": "no-cache",
937
- Connection: "keep-alive",
938
- "Content-Type": "text/event-stream",
939
- ...corsHeaders,
940
- },
941
- });
942
- } catch (error) {
943
- console.error(
944
- "[Server] Error in handleStreamingGitCheckoutRequest:",
945
- error
946
- );
947
- return new Response(
948
- JSON.stringify({
949
- error: "Failed to checkout repository",
950
- message: error instanceof Error ? error.message : "Unknown error",
951
- }),
952
- {
953
- headers: {
954
- "Content-Type": "application/json",
955
- ...corsHeaders,
956
- },
957
- status: 500,
958
- }
959
- );
960
- }
961
- }
962
-
963
- async function handleMkdirRequest(
964
- req: Request,
965
- corsHeaders: Record<string, string>
966
- ): Promise<Response> {
967
- try {
968
- const body = (await req.json()) as MkdirRequest;
969
- const { path, recursive = false, sessionId } = body;
970
-
971
- if (!path || typeof path !== "string") {
972
- return new Response(
973
- JSON.stringify({
974
- error: "Path is required and must be a string",
975
- }),
976
- {
977
- headers: {
978
- "Content-Type": "application/json",
979
- ...corsHeaders,
980
- },
981
- status: 400,
982
- }
983
- );
984
- }
985
-
986
- // Basic safety check - prevent dangerous paths
987
- const dangerousPatterns = [
988
- /^\/$/, // Root directory
989
- /^\/etc/, // System directories
990
- /^\/var/, // System directories
991
- /^\/usr/, // System directories
992
- /^\/bin/, // System directories
993
- /^\/sbin/, // System directories
994
- /^\/boot/, // System directories
995
- /^\/dev/, // System directories
996
- /^\/proc/, // System directories
997
- /^\/sys/, // System directories
998
- /^\/tmp\/\.\./, // Path traversal attempts
999
- /\.\./, // Path traversal attempts
1000
- ];
1001
-
1002
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1003
- return new Response(
1004
- JSON.stringify({
1005
- error: "Dangerous path not allowed",
1006
- }),
1007
- {
1008
- headers: {
1009
- "Content-Type": "application/json",
1010
- ...corsHeaders,
1011
- },
1012
- status: 400,
1013
- }
1014
- );
1015
- }
1016
-
1017
- console.log(
1018
- `[Server] Creating directory: ${path} (recursive: ${recursive})`
1019
- );
1020
-
1021
- const result = await executeMkdir(path, recursive, sessionId);
1022
-
1023
- return new Response(
1024
- JSON.stringify({
1025
- exitCode: result.exitCode,
1026
- path,
1027
- recursive,
1028
- stderr: result.stderr,
1029
- stdout: result.stdout,
1030
- success: result.success,
1031
- timestamp: new Date().toISOString(),
1032
- }),
1033
- {
1034
- headers: {
1035
- "Content-Type": "application/json",
1036
- ...corsHeaders,
1037
- },
1038
- }
1039
- );
1040
- } catch (error) {
1041
- console.error("[Server] Error in handleMkdirRequest:", error);
1042
- return new Response(
1043
- JSON.stringify({
1044
- error: "Failed to create directory",
1045
- message: error instanceof Error ? error.message : "Unknown error",
1046
- }),
1047
- {
1048
- headers: {
1049
- "Content-Type": "application/json",
1050
- ...corsHeaders,
1051
- },
1052
- status: 500,
1053
- }
1054
- );
1055
- }
1056
- }
1057
-
1058
- async function handleStreamingMkdirRequest(
1059
- req: Request,
1060
- corsHeaders: Record<string, string>
1061
- ): Promise<Response> {
1062
- try {
1063
- const body = (await req.json()) as MkdirRequest;
1064
- const { path, recursive = false, sessionId } = body;
1065
-
1066
- if (!path || typeof path !== "string") {
1067
- return new Response(
1068
- JSON.stringify({
1069
- error: "Path is required and must be a string",
1070
- }),
1071
- {
1072
- headers: {
1073
- "Content-Type": "application/json",
1074
- ...corsHeaders,
1075
- },
1076
- status: 400,
1077
- }
1078
- );
1079
- }
1080
-
1081
- // Basic safety check - prevent dangerous paths
1082
- const dangerousPatterns = [
1083
- /^\/$/, // Root directory
1084
- /^\/etc/, // System directories
1085
- /^\/var/, // System directories
1086
- /^\/usr/, // System directories
1087
- /^\/bin/, // System directories
1088
- /^\/sbin/, // System directories
1089
- /^\/boot/, // System directories
1090
- /^\/dev/, // System directories
1091
- /^\/proc/, // System directories
1092
- /^\/sys/, // System directories
1093
- /^\/tmp\/\.\./, // Path traversal attempts
1094
- /\.\./, // Path traversal attempts
1095
- ];
1096
-
1097
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1098
- return new Response(
1099
- JSON.stringify({
1100
- error: "Dangerous path not allowed",
1101
- }),
1102
- {
1103
- headers: {
1104
- "Content-Type": "application/json",
1105
- ...corsHeaders,
1106
- },
1107
- status: 400,
1108
- }
1109
- );
1110
- }
1111
-
1112
- console.log(
1113
- `[Server] Creating directory: ${path} (recursive: ${recursive})`
1114
- );
1115
-
1116
- const stream = new ReadableStream({
1117
- start(controller) {
1118
- const args = recursive ? ["-p", path] : [path];
1119
- const child = spawn("mkdir", args, {
1120
- shell: true,
1121
- stdio: ["pipe", "pipe", "pipe"],
1122
- });
1123
-
1124
- // Store the process reference for cleanup if sessionId is provided
1125
- if (sessionId && sessions.has(sessionId)) {
1126
- const session = sessions.get(sessionId)!;
1127
- session.activeProcess = child;
1128
- }
1129
-
1130
- let stdout = "";
1131
- let stderr = "";
1132
-
1133
- // Send command start event
1134
- controller.enqueue(
1135
- new TextEncoder().encode(
1136
- `data: ${JSON.stringify({
1137
- args,
1138
- command: "mkdir",
1139
- timestamp: new Date().toISOString(),
1140
- type: "command_start",
1141
- })}\n\n`
1142
- )
1143
- );
1144
-
1145
- child.stdout?.on("data", (data) => {
1146
- const output = data.toString();
1147
- stdout += output;
1148
-
1149
- // Send real-time output
1150
- controller.enqueue(
1151
- new TextEncoder().encode(
1152
- `data: ${JSON.stringify({
1153
- command: "mkdir",
1154
- data: output,
1155
- stream: "stdout",
1156
- type: "output",
1157
- })}\n\n`
1158
- )
1159
- );
1160
- });
1161
-
1162
- child.stderr?.on("data", (data) => {
1163
- const output = data.toString();
1164
- stderr += output;
1165
-
1166
- // Send real-time error output
1167
- controller.enqueue(
1168
- new TextEncoder().encode(
1169
- `data: ${JSON.stringify({
1170
- command: "mkdir",
1171
- data: output,
1172
- stream: "stderr",
1173
- type: "output",
1174
- })}\n\n`
1175
- )
1176
- );
1177
- });
1178
-
1179
- child.on("close", (code) => {
1180
- // Clear the active process reference
1181
- if (sessionId && sessions.has(sessionId)) {
1182
- const session = sessions.get(sessionId)!;
1183
- session.activeProcess = null;
1184
- }
1185
-
1186
- console.log(`[Server] Command completed: mkdir, Exit code: ${code}`);
1187
-
1188
- // Send command completion event
1189
- controller.enqueue(
1190
- new TextEncoder().encode(
1191
- `data: ${JSON.stringify({
1192
- args,
1193
- command: "mkdir",
1194
- exitCode: code,
1195
- stderr,
1196
- stdout,
1197
- success: code === 0,
1198
- timestamp: new Date().toISOString(),
1199
- type: "command_complete",
1200
- })}\n\n`
1201
- )
1202
- );
1203
-
1204
- controller.close();
1205
- });
1206
-
1207
- child.on("error", (error) => {
1208
- // Clear the active process reference
1209
- if (sessionId && sessions.has(sessionId)) {
1210
- const session = sessions.get(sessionId)!;
1211
- session.activeProcess = null;
1212
- }
1213
-
1214
- controller.enqueue(
1215
- new TextEncoder().encode(
1216
- `data: ${JSON.stringify({
1217
- args,
1218
- command: "mkdir",
1219
- error: error.message,
1220
- type: "error",
1221
- })}\n\n`
1222
- )
1223
- );
1224
-
1225
- controller.close();
1226
- });
1227
- },
1228
- });
1229
-
1230
- return new Response(stream, {
1231
- headers: {
1232
- "Cache-Control": "no-cache",
1233
- Connection: "keep-alive",
1234
- "Content-Type": "text/event-stream",
1235
- ...corsHeaders,
1236
- },
1237
- });
1238
- } catch (error) {
1239
- console.error("[Server] Error in handleStreamingMkdirRequest:", error);
1240
- return new Response(
1241
- JSON.stringify({
1242
- error: "Failed to create directory",
1243
- message: error instanceof Error ? error.message : "Unknown error",
1244
- }),
1245
- {
1246
- headers: {
1247
- "Content-Type": "application/json",
1248
- ...corsHeaders,
1249
- },
1250
- status: 500,
1251
- }
1252
- );
1253
- }
1254
- }
1255
-
1256
- async function handleWriteFileRequest(
1257
- req: Request,
1258
- corsHeaders: Record<string, string>
1259
- ): Promise<Response> {
1260
- try {
1261
- const body = (await req.json()) as WriteFileRequest;
1262
- const { path, content, encoding = "utf-8", sessionId } = body;
1263
-
1264
- if (!path || typeof path !== "string") {
1265
- return new Response(
1266
- JSON.stringify({
1267
- error: "Path is required and must be a string",
1268
- }),
1269
- {
1270
- headers: {
1271
- "Content-Type": "application/json",
1272
- ...corsHeaders,
1273
- },
1274
- status: 400,
1275
- }
1276
- );
1277
- }
1278
-
1279
- // Basic safety check - prevent dangerous paths
1280
- const dangerousPatterns = [
1281
- /^\/$/, // Root directory
1282
- /^\/etc/, // System directories
1283
- /^\/var/, // System directories
1284
- /^\/usr/, // System directories
1285
- /^\/bin/, // System directories
1286
- /^\/sbin/, // System directories
1287
- /^\/boot/, // System directories
1288
- /^\/dev/, // System directories
1289
- /^\/proc/, // System directories
1290
- /^\/sys/, // System directories
1291
- /^\/tmp\/\.\./, // Path traversal attempts
1292
- /\.\./, // Path traversal attempts
1293
- ];
1294
-
1295
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1296
- return new Response(
1297
- JSON.stringify({
1298
- error: "Dangerous path not allowed",
1299
- }),
1300
- {
1301
- headers: {
1302
- "Content-Type": "application/json",
1303
- ...corsHeaders,
1304
- },
1305
- status: 400,
1306
- }
1307
- );
1308
- }
1309
-
1310
- console.log(
1311
- `[Server] Writing file: ${path} (content length: ${content.length})`
1312
- );
1313
-
1314
- const result = await executeWriteFile(path, content, encoding, sessionId);
1315
-
1316
- return new Response(
1317
- JSON.stringify({
1318
- exitCode: result.exitCode,
1319
- path,
1320
- success: result.success,
1321
- timestamp: new Date().toISOString(),
1322
- }),
1323
- {
1324
- headers: {
1325
- "Content-Type": "application/json",
1326
- ...corsHeaders,
1327
- },
1328
- }
1329
- );
1330
- } catch (error) {
1331
- console.error("[Server] Error in handleWriteFileRequest:", error);
1332
- return new Response(
1333
- JSON.stringify({
1334
- error: "Failed to write file",
1335
- message: error instanceof Error ? error.message : "Unknown error",
1336
- }),
1337
- {
1338
- headers: {
1339
- "Content-Type": "application/json",
1340
- ...corsHeaders,
1341
- },
1342
- status: 500,
1343
- }
1344
- );
1345
- }
1346
- }
1347
-
1348
- async function handleStreamingWriteFileRequest(
1349
- req: Request,
1350
- corsHeaders: Record<string, string>
1351
- ): Promise<Response> {
1352
- try {
1353
- const body = (await req.json()) as WriteFileRequest;
1354
- const { path, content, encoding = "utf-8", sessionId } = body;
1355
-
1356
- if (!path || typeof path !== "string") {
1357
- return new Response(
1358
- JSON.stringify({
1359
- error: "Path is required and must be a string",
1360
- }),
1361
- {
1362
- headers: {
1363
- "Content-Type": "application/json",
1364
- ...corsHeaders,
1365
- },
1366
- status: 400,
1367
- }
1368
- );
1369
- }
1370
-
1371
- // Basic safety check - prevent dangerous paths
1372
- const dangerousPatterns = [
1373
- /^\/$/, // Root directory
1374
- /^\/etc/, // System directories
1375
- /^\/var/, // System directories
1376
- /^\/usr/, // System directories
1377
- /^\/bin/, // System directories
1378
- /^\/sbin/, // System directories
1379
- /^\/boot/, // System directories
1380
- /^\/dev/, // System directories
1381
- /^\/proc/, // System directories
1382
- /^\/sys/, // System directories
1383
- /^\/tmp\/\.\./, // Path traversal attempts
1384
- /\.\./, // Path traversal attempts
1385
- ];
1386
-
1387
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1388
- return new Response(
1389
- JSON.stringify({
1390
- error: "Dangerous path not allowed",
1391
- }),
1392
- {
1393
- headers: {
1394
- "Content-Type": "application/json",
1395
- ...corsHeaders,
1396
- },
1397
- status: 400,
1398
- }
1399
- );
1400
- }
1401
-
1402
- console.log(
1403
- `[Server] Writing file (streaming): ${path} (content length: ${content.length})`
1404
- );
1405
-
1406
- const stream = new ReadableStream({
1407
- start(controller) {
1408
- (async () => {
1409
- try {
1410
- // Send command start event
1411
- controller.enqueue(
1412
- new TextEncoder().encode(
1413
- `data: ${JSON.stringify({
1414
- path,
1415
- timestamp: new Date().toISOString(),
1416
- type: "command_start",
1417
- })}\n\n`
1418
- )
1419
- );
1420
-
1421
- // Ensure the directory exists
1422
- const dir = dirname(path);
1423
- if (dir !== ".") {
1424
- await mkdir(dir, { recursive: true });
1425
-
1426
- // Send directory creation event
1427
- controller.enqueue(
1428
- new TextEncoder().encode(
1429
- `data: ${JSON.stringify({
1430
- message: `Created directory: ${dir}`,
1431
- type: "output",
1432
- })}\n\n`
1433
- )
1434
- );
1435
- }
1436
-
1437
- // Write the file
1438
- await writeFile(path, content, {
1439
- encoding: encoding as BufferEncoding,
1440
- });
1441
-
1442
- console.log(`[Server] File written successfully: ${path}`);
1443
-
1444
- // Send command completion event
1445
- controller.enqueue(
1446
- new TextEncoder().encode(
1447
- `data: ${JSON.stringify({
1448
- path,
1449
- success: true,
1450
- timestamp: new Date().toISOString(),
1451
- type: "command_complete",
1452
- })}\n\n`
1453
- )
1454
- );
1455
-
1456
- controller.close();
1457
- } catch (error) {
1458
- console.error(`[Server] Error writing file: ${path}`, error);
1459
-
1460
- controller.enqueue(
1461
- new TextEncoder().encode(
1462
- `data: ${JSON.stringify({
1463
- error:
1464
- error instanceof Error ? error.message : "Unknown error",
1465
- path,
1466
- type: "error",
1467
- })}\n\n`
1468
- )
1469
- );
1470
-
1471
- controller.close();
1472
- }
1473
- })();
1474
- },
1475
- });
1476
-
1477
- return new Response(stream, {
1478
- headers: {
1479
- "Cache-Control": "no-cache",
1480
- Connection: "keep-alive",
1481
- "Content-Type": "text/event-stream",
1482
- ...corsHeaders,
1483
- },
1484
- });
1485
- } catch (error) {
1486
- console.error("[Server] Error in handleStreamingWriteFileRequest:", error);
1487
- return new Response(
1488
- JSON.stringify({
1489
- error: "Failed to write file",
1490
- message: error instanceof Error ? error.message : "Unknown error",
1491
- }),
1492
- {
1493
- headers: {
1494
- "Content-Type": "application/json",
1495
- ...corsHeaders,
1496
- },
1497
- status: 500,
1498
- }
1499
- );
1500
- }
1501
- }
1502
-
1503
- async function handleReadFileRequest(
1504
- req: Request,
1505
- corsHeaders: Record<string, string>
1506
- ): Promise<Response> {
1507
- try {
1508
- const body = (await req.json()) as ReadFileRequest;
1509
- const { path, encoding = "utf-8", sessionId } = body;
1510
-
1511
- if (!path || typeof path !== "string") {
1512
- return new Response(
1513
- JSON.stringify({
1514
- error: "Path is required and must be a string",
1515
- }),
1516
- {
1517
- headers: {
1518
- "Content-Type": "application/json",
1519
- ...corsHeaders,
1520
- },
1521
- status: 400,
1522
- }
1523
- );
1524
- }
1525
-
1526
- // Basic safety check - prevent dangerous paths
1527
- const dangerousPatterns = [
1528
- /^\/$/, // Root directory
1529
- /^\/etc/, // System directories
1530
- /^\/var/, // System directories
1531
- /^\/usr/, // System directories
1532
- /^\/bin/, // System directories
1533
- /^\/sbin/, // System directories
1534
- /^\/boot/, // System directories
1535
- /^\/dev/, // System directories
1536
- /^\/proc/, // System directories
1537
- /^\/sys/, // System directories
1538
- /^\/tmp\/\.\./, // Path traversal attempts
1539
- /\.\./, // Path traversal attempts
1540
- ];
1541
-
1542
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1543
- return new Response(
1544
- JSON.stringify({
1545
- error: "Dangerous path not allowed",
1546
- }),
1547
- {
1548
- headers: {
1549
- "Content-Type": "application/json",
1550
- ...corsHeaders,
1551
- },
1552
- status: 400,
1553
- }
1554
- );
1555
- }
1556
-
1557
- console.log(`[Server] Reading file: ${path}`);
1558
-
1559
- const result = await executeReadFile(path, encoding, sessionId);
1560
-
1561
- return new Response(
1562
- JSON.stringify({
1563
- content: result.content,
1564
- exitCode: result.exitCode,
1565
- path,
1566
- success: result.success,
1567
- timestamp: new Date().toISOString(),
1568
- }),
1569
- {
1570
- headers: {
1571
- "Content-Type": "application/json",
1572
- ...corsHeaders,
1573
- },
1574
- }
1575
- );
1576
- } catch (error) {
1577
- console.error("[Server] Error in handleReadFileRequest:", error);
1578
- return new Response(
1579
- JSON.stringify({
1580
- error: "Failed to read file",
1581
- message: error instanceof Error ? error.message : "Unknown error",
1582
- }),
1583
- {
1584
- headers: {
1585
- "Content-Type": "application/json",
1586
- ...corsHeaders,
1587
- },
1588
- status: 500,
1589
- }
1590
- );
1591
- }
1592
- }
1593
-
1594
- async function handleStreamingReadFileRequest(
1595
- req: Request,
1596
- corsHeaders: Record<string, string>
1597
- ): Promise<Response> {
1598
- try {
1599
- const body = (await req.json()) as ReadFileRequest;
1600
- const { path, encoding = "utf-8", sessionId } = body;
1601
-
1602
- if (!path || typeof path !== "string") {
1603
- return new Response(
1604
- JSON.stringify({
1605
- error: "Path is required and must be a string",
1606
- }),
1607
- {
1608
- headers: {
1609
- "Content-Type": "application/json",
1610
- ...corsHeaders,
1611
- },
1612
- status: 400,
1613
- }
1614
- );
1615
- }
1616
-
1617
- // Basic safety check - prevent dangerous paths
1618
- const dangerousPatterns = [
1619
- /^\/$/, // Root directory
1620
- /^\/etc/, // System directories
1621
- /^\/var/, // System directories
1622
- /^\/usr/, // System directories
1623
- /^\/bin/, // System directories
1624
- /^\/sbin/, // System directories
1625
- /^\/boot/, // System directories
1626
- /^\/dev/, // System directories
1627
- /^\/proc/, // System directories
1628
- /^\/sys/, // System directories
1629
- /^\/tmp\/\.\./, // Path traversal attempts
1630
- /\.\./, // Path traversal attempts
1631
- ];
1632
-
1633
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1634
- return new Response(
1635
- JSON.stringify({
1636
- error: "Dangerous path not allowed",
1637
- }),
1638
- {
1639
- headers: {
1640
- "Content-Type": "application/json",
1641
- ...corsHeaders,
1642
- },
1643
- status: 400,
1644
- }
1645
- );
1646
- }
1647
-
1648
- console.log(`[Server] Reading file (streaming): ${path}`);
1649
-
1650
- const stream = new ReadableStream({
1651
- start(controller) {
1652
- (async () => {
1653
- try {
1654
- // Send command start event
1655
- controller.enqueue(
1656
- new TextEncoder().encode(
1657
- `data: ${JSON.stringify({
1658
- path,
1659
- timestamp: new Date().toISOString(),
1660
- type: "command_start",
1661
- })}\n\n`
1662
- )
1663
- );
1664
-
1665
- // Read the file
1666
- const content = await readFile(path, {
1667
- encoding: encoding as BufferEncoding,
1668
- });
1669
-
1670
- console.log(`[Server] File read successfully: ${path}`);
1671
-
1672
- // Send command completion event
1673
- controller.enqueue(
1674
- new TextEncoder().encode(
1675
- `data: ${JSON.stringify({
1676
- content,
1677
- path,
1678
- success: true,
1679
- timestamp: new Date().toISOString(),
1680
- type: "command_complete",
1681
- })}\n\n`
1682
- )
1683
- );
1684
-
1685
- controller.close();
1686
- } catch (error) {
1687
- console.error(`[Server] Error reading file: ${path}`, error);
1688
-
1689
- controller.enqueue(
1690
- new TextEncoder().encode(
1691
- `data: ${JSON.stringify({
1692
- error:
1693
- error instanceof Error ? error.message : "Unknown error",
1694
- path,
1695
- type: "error",
1696
- })}\n\n`
1697
- )
1698
- );
1699
-
1700
- controller.close();
1701
- }
1702
- })();
1703
- },
1704
- });
1705
-
1706
- return new Response(stream, {
1707
- headers: {
1708
- "Cache-Control": "no-cache",
1709
- Connection: "keep-alive",
1710
- "Content-Type": "text/event-stream",
1711
- ...corsHeaders,
1712
- },
1713
- });
1714
- } catch (error) {
1715
- console.error("[Server] Error in handleStreamingReadFileRequest:", error);
1716
- return new Response(
1717
- JSON.stringify({
1718
- error: "Failed to read file",
1719
- message: error instanceof Error ? error.message : "Unknown error",
1720
- }),
1721
- {
1722
- headers: {
1723
- "Content-Type": "application/json",
1724
- ...corsHeaders,
1725
- },
1726
- status: 500,
1727
- }
1728
- );
1729
- }
1730
- }
1731
-
1732
- async function handleDeleteFileRequest(
1733
- req: Request,
1734
- corsHeaders: Record<string, string>
1735
- ): Promise<Response> {
1736
- try {
1737
- const body = (await req.json()) as DeleteFileRequest;
1738
- const { path, sessionId } = body;
1739
-
1740
- if (!path || typeof path !== "string") {
1741
- return new Response(
1742
- JSON.stringify({
1743
- error: "Path is required and must be a string",
1744
- }),
1745
- {
1746
- headers: {
1747
- "Content-Type": "application/json",
1748
- ...corsHeaders,
1749
- },
1750
- status: 400,
1751
- }
1752
- );
1753
- }
1754
-
1755
- // Basic safety check - prevent dangerous paths
1756
- const dangerousPatterns = [
1757
- /^\/$/, // Root directory
1758
- /^\/etc/, // System directories
1759
- /^\/var/, // System directories
1760
- /^\/usr/, // System directories
1761
- /^\/bin/, // System directories
1762
- /^\/sbin/, // System directories
1763
- /^\/boot/, // System directories
1764
- /^\/dev/, // System directories
1765
- /^\/proc/, // System directories
1766
- /^\/sys/, // System directories
1767
- /^\/tmp\/\.\./, // Path traversal attempts
1768
- /\.\./, // Path traversal attempts
1769
- ];
1770
-
1771
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1772
- return new Response(
1773
- JSON.stringify({
1774
- error: "Dangerous path not allowed",
1775
- }),
1776
- {
1777
- headers: {
1778
- "Content-Type": "application/json",
1779
- ...corsHeaders,
1780
- },
1781
- status: 400,
1782
- }
1783
- );
1784
- }
1785
-
1786
- console.log(`[Server] Deleting file: ${path}`);
1787
-
1788
- const result = await executeDeleteFile(path, sessionId);
1789
-
1790
- return new Response(
1791
- JSON.stringify({
1792
- exitCode: result.exitCode,
1793
- path,
1794
- success: result.success,
1795
- timestamp: new Date().toISOString(),
1796
- }),
1797
- {
1798
- headers: {
1799
- "Content-Type": "application/json",
1800
- ...corsHeaders,
1801
- },
1802
- }
1803
- );
1804
- } catch (error) {
1805
- console.error("[Server] Error in handleDeleteFileRequest:", error);
1806
- return new Response(
1807
- JSON.stringify({
1808
- error: "Failed to delete file",
1809
- message: error instanceof Error ? error.message : "Unknown error",
1810
- }),
1811
- {
1812
- headers: {
1813
- "Content-Type": "application/json",
1814
- ...corsHeaders,
1815
- },
1816
- status: 500,
1817
- }
1818
- );
1819
- }
1820
- }
1821
-
1822
- async function handleStreamingDeleteFileRequest(
1823
- req: Request,
1824
- corsHeaders: Record<string, string>
1825
- ): Promise<Response> {
1826
- try {
1827
- const body = (await req.json()) as DeleteFileRequest;
1828
- const { path, sessionId } = body;
1829
-
1830
- if (!path || typeof path !== "string") {
1831
- return new Response(
1832
- JSON.stringify({
1833
- error: "Path is required and must be a string",
1834
- }),
1835
- {
1836
- headers: {
1837
- "Content-Type": "application/json",
1838
- ...corsHeaders,
1839
- },
1840
- status: 400,
1841
- }
1842
- );
1843
- }
1844
-
1845
- // Basic safety check - prevent dangerous paths
1846
- const dangerousPatterns = [
1847
- /^\/$/, // Root directory
1848
- /^\/etc/, // System directories
1849
- /^\/var/, // System directories
1850
- /^\/usr/, // System directories
1851
- /^\/bin/, // System directories
1852
- /^\/sbin/, // System directories
1853
- /^\/boot/, // System directories
1854
- /^\/dev/, // System directories
1855
- /^\/proc/, // System directories
1856
- /^\/sys/, // System directories
1857
- /^\/tmp\/\.\./, // Path traversal attempts
1858
- /\.\./, // Path traversal attempts
1859
- ];
1860
-
1861
- if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1862
- return new Response(
1863
- JSON.stringify({
1864
- error: "Dangerous path not allowed",
1865
- }),
1866
- {
1867
- headers: {
1868
- "Content-Type": "application/json",
1869
- ...corsHeaders,
1870
- },
1871
- status: 400,
1872
- }
1873
- );
1874
- }
1875
-
1876
- console.log(`[Server] Deleting file (streaming): ${path}`);
1877
-
1878
- const stream = new ReadableStream({
1879
- start(controller) {
1880
- (async () => {
1881
- try {
1882
- // Send command start event
1883
- controller.enqueue(
1884
- new TextEncoder().encode(
1885
- `data: ${JSON.stringify({
1886
- path,
1887
- timestamp: new Date().toISOString(),
1888
- type: "command_start",
1889
- })}\n\n`
1890
- )
1891
- );
1892
-
1893
- // Delete the file
1894
- await executeDeleteFile(path, sessionId);
1895
-
1896
- console.log(`[Server] File deleted successfully: ${path}`);
1897
-
1898
- // Send command completion event
1899
- controller.enqueue(
1900
- new TextEncoder().encode(
1901
- `data: ${JSON.stringify({
1902
- path,
1903
- success: true,
1904
- timestamp: new Date().toISOString(),
1905
- type: "command_complete",
1906
- })}\n\n`
1907
- )
1908
- );
1909
-
1910
- controller.close();
1911
- } catch (error) {
1912
- console.error(`[Server] Error deleting file: ${path}`, error);
1913
-
1914
- controller.enqueue(
1915
- new TextEncoder().encode(
1916
- `data: ${JSON.stringify({
1917
- error:
1918
- error instanceof Error ? error.message : "Unknown error",
1919
- path,
1920
- type: "error",
1921
- })}\n\n`
1922
- )
1923
- );
1924
-
1925
- controller.close();
1926
- }
1927
- })();
1928
- },
1929
- });
1930
-
1931
- return new Response(stream, {
1932
- headers: {
1933
- "Cache-Control": "no-cache",
1934
- Connection: "keep-alive",
1935
- "Content-Type": "text/event-stream",
1936
- ...corsHeaders,
1937
- },
1938
- });
1939
- } catch (error) {
1940
- console.error("[Server] Error in handleStreamingDeleteFileRequest:", error);
1941
- return new Response(
1942
- JSON.stringify({
1943
- error: "Failed to delete file",
1944
- message: error instanceof Error ? error.message : "Unknown error",
1945
- }),
1946
- {
1947
- headers: {
1948
- "Content-Type": "application/json",
1949
- ...corsHeaders,
1950
- },
1951
- status: 500,
1952
- }
1953
- );
1954
- }
1955
- }
1956
-
1957
- async function handleRenameFileRequest(
1958
- req: Request,
1959
- corsHeaders: Record<string, string>
1960
- ): Promise<Response> {
1961
- try {
1962
- const body = (await req.json()) as RenameFileRequest;
1963
- const { oldPath, newPath, sessionId } = body;
1964
-
1965
- if (!oldPath || typeof oldPath !== "string") {
1966
- return new Response(
1967
- JSON.stringify({
1968
- error: "Old path is required and must be a string",
1969
- }),
1970
- {
1971
- headers: {
1972
- "Content-Type": "application/json",
1973
- ...corsHeaders,
1974
- },
1975
- status: 400,
1976
- }
1977
- );
1978
- }
1979
-
1980
- if (!newPath || typeof newPath !== "string") {
1981
- return new Response(
1982
- JSON.stringify({
1983
- error: "New path is required and must be a string",
1984
- }),
1985
- {
1986
- headers: {
1987
- "Content-Type": "application/json",
1988
- ...corsHeaders,
1989
- },
1990
- status: 400,
1991
- }
1992
- );
1993
- }
1994
-
1995
- // Basic safety check - prevent dangerous paths
1996
- const dangerousPatterns = [
1997
- /^\/$/, // Root directory
1998
- /^\/etc/, // System directories
1999
- /^\/var/, // System directories
2000
- /^\/usr/, // System directories
2001
- /^\/bin/, // System directories
2002
- /^\/sbin/, // System directories
2003
- /^\/boot/, // System directories
2004
- /^\/dev/, // System directories
2005
- /^\/proc/, // System directories
2006
- /^\/sys/, // System directories
2007
- /^\/tmp\/\.\./, // Path traversal attempts
2008
- /\.\./, // Path traversal attempts
2009
- ];
2010
-
2011
- if (
2012
- dangerousPatterns.some(
2013
- (pattern) => pattern.test(oldPath) || pattern.test(newPath)
2014
- )
2015
- ) {
2016
- return new Response(
2017
- JSON.stringify({
2018
- error: "Dangerous path not allowed",
2019
- }),
2020
- {
2021
- headers: {
2022
- "Content-Type": "application/json",
2023
- ...corsHeaders,
2024
- },
2025
- status: 400,
2026
- }
2027
- );
2028
- }
2029
-
2030
- console.log(`[Server] Renaming file: ${oldPath} -> ${newPath}`);
2031
-
2032
- const result = await executeRenameFile(oldPath, newPath, sessionId);
2033
-
2034
- return new Response(
2035
- JSON.stringify({
2036
- exitCode: result.exitCode,
2037
- newPath,
2038
- oldPath,
2039
- success: result.success,
2040
- timestamp: new Date().toISOString(),
2041
- }),
2042
- {
2043
- headers: {
2044
- "Content-Type": "application/json",
2045
- ...corsHeaders,
2046
- },
2047
- }
2048
- );
2049
- } catch (error) {
2050
- console.error("[Server] Error in handleRenameFileRequest:", error);
2051
- return new Response(
2052
- JSON.stringify({
2053
- error: "Failed to rename file",
2054
- message: error instanceof Error ? error.message : "Unknown error",
2055
- }),
2056
- {
2057
- headers: {
2058
- "Content-Type": "application/json",
2059
- ...corsHeaders,
2060
- },
2061
- status: 500,
2062
- }
2063
- );
2064
- }
2065
- }
2066
-
2067
- async function handleStreamingRenameFileRequest(
2068
- req: Request,
2069
- corsHeaders: Record<string, string>
2070
- ): Promise<Response> {
2071
- try {
2072
- const body = (await req.json()) as RenameFileRequest;
2073
- const { oldPath, newPath, sessionId } = body;
2074
-
2075
- if (!oldPath || typeof oldPath !== "string") {
2076
- return new Response(
2077
- JSON.stringify({
2078
- error: "Old path is required and must be a string",
2079
- }),
2080
- {
2081
- headers: {
2082
- "Content-Type": "application/json",
2083
- ...corsHeaders,
2084
- },
2085
- status: 400,
2086
- }
2087
- );
2088
- }
2089
-
2090
- if (!newPath || typeof newPath !== "string") {
2091
- return new Response(
2092
- JSON.stringify({
2093
- error: "New path is required and must be a string",
2094
- }),
2095
- {
2096
- headers: {
2097
- "Content-Type": "application/json",
2098
- ...corsHeaders,
2099
- },
2100
- status: 400,
2101
- }
2102
- );
2103
- }
2104
-
2105
- // Basic safety check - prevent dangerous paths
2106
- const dangerousPatterns = [
2107
- /^\/$/, // Root directory
2108
- /^\/etc/, // System directories
2109
- /^\/var/, // System directories
2110
- /^\/usr/, // System directories
2111
- /^\/bin/, // System directories
2112
- /^\/sbin/, // System directories
2113
- /^\/boot/, // System directories
2114
- /^\/dev/, // System directories
2115
- /^\/proc/, // System directories
2116
- /^\/sys/, // System directories
2117
- /^\/tmp\/\.\./, // Path traversal attempts
2118
- /\.\./, // Path traversal attempts
2119
- ];
2120
-
2121
- if (
2122
- dangerousPatterns.some(
2123
- (pattern) => pattern.test(oldPath) || pattern.test(newPath)
2124
- )
2125
- ) {
2126
- return new Response(
2127
- JSON.stringify({
2128
- error: "Dangerous path not allowed",
2129
- }),
2130
- {
2131
- headers: {
2132
- "Content-Type": "application/json",
2133
- ...corsHeaders,
2134
- },
2135
- status: 400,
2136
- }
2137
- );
2138
- }
2139
-
2140
- console.log(`[Server] Renaming file (streaming): ${oldPath} -> ${newPath}`);
2141
-
2142
- const stream = new ReadableStream({
2143
- start(controller) {
2144
- (async () => {
2145
- try {
2146
- // Send command start event
2147
- controller.enqueue(
2148
- new TextEncoder().encode(
2149
- `data: ${JSON.stringify({
2150
- newPath,
2151
- oldPath,
2152
- timestamp: new Date().toISOString(),
2153
- type: "command_start",
2154
- })}\n\n`
2155
- )
2156
- );
2157
-
2158
- // Rename the file
2159
- await executeRenameFile(oldPath, newPath, sessionId);
2160
-
2161
- console.log(
2162
- `[Server] File renamed successfully: ${oldPath} -> ${newPath}`
2163
- );
2164
-
2165
- // Send command completion event
2166
- controller.enqueue(
2167
- new TextEncoder().encode(
2168
- `data: ${JSON.stringify({
2169
- newPath,
2170
- oldPath,
2171
- success: true,
2172
- timestamp: new Date().toISOString(),
2173
- type: "command_complete",
2174
- })}\n\n`
2175
- )
2176
- );
2177
-
2178
- controller.close();
2179
- } catch (error) {
2180
- console.error(
2181
- `[Server] Error renaming file: ${oldPath} -> ${newPath}`,
2182
- error
2183
- );
2184
-
2185
- controller.enqueue(
2186
- new TextEncoder().encode(
2187
- `data: ${JSON.stringify({
2188
- error:
2189
- error instanceof Error ? error.message : "Unknown error",
2190
- newPath,
2191
- oldPath,
2192
- type: "error",
2193
- })}\n\n`
2194
- )
2195
- );
2196
-
2197
- controller.close();
2198
- }
2199
- })();
2200
- },
2201
- });
2202
-
2203
- return new Response(stream, {
2204
- headers: {
2205
- "Cache-Control": "no-cache",
2206
- Connection: "keep-alive",
2207
- "Content-Type": "text/event-stream",
2208
- ...corsHeaders,
2209
- },
2210
- });
2211
- } catch (error) {
2212
- console.error("[Server] Error in handleStreamingRenameFileRequest:", error);
2213
- return new Response(
2214
- JSON.stringify({
2215
- error: "Failed to rename file",
2216
- message: error instanceof Error ? error.message : "Unknown error",
2217
- }),
2218
- {
2219
- headers: {
2220
- "Content-Type": "application/json",
2221
- ...corsHeaders,
2222
- },
2223
- status: 500,
2224
- }
2225
- );
2226
- }
2227
- }
2228
-
2229
- async function handleMoveFileRequest(
2230
- req: Request,
2231
- corsHeaders: Record<string, string>
2232
- ): Promise<Response> {
2233
- try {
2234
- const body = (await req.json()) as MoveFileRequest;
2235
- const { sourcePath, destinationPath, sessionId } = body;
2236
-
2237
- if (!sourcePath || typeof sourcePath !== "string") {
2238
- return new Response(
2239
- JSON.stringify({
2240
- error: "Source path is required and must be a string",
2241
- }),
2242
- {
2243
- headers: {
2244
- "Content-Type": "application/json",
2245
- ...corsHeaders,
2246
- },
2247
- status: 400,
2248
- }
2249
- );
2250
- }
2251
-
2252
- if (!destinationPath || typeof destinationPath !== "string") {
2253
- return new Response(
2254
- JSON.stringify({
2255
- error: "Destination path is required and must be a string",
2256
- }),
2257
- {
2258
- headers: {
2259
- "Content-Type": "application/json",
2260
- ...corsHeaders,
2261
- },
2262
- status: 400,
2263
- }
2264
- );
2265
- }
2266
-
2267
- // Basic safety check - prevent dangerous paths
2268
- const dangerousPatterns = [
2269
- /^\/$/, // Root directory
2270
- /^\/etc/, // System directories
2271
- /^\/var/, // System directories
2272
- /^\/usr/, // System directories
2273
- /^\/bin/, // System directories
2274
- /^\/sbin/, // System directories
2275
- /^\/boot/, // System directories
2276
- /^\/dev/, // System directories
2277
- /^\/proc/, // System directories
2278
- /^\/sys/, // System directories
2279
- /^\/tmp\/\.\./, // Path traversal attempts
2280
- /\.\./, // Path traversal attempts
2281
- ];
2282
-
2283
- if (
2284
- dangerousPatterns.some(
2285
- (pattern) => pattern.test(sourcePath) || pattern.test(destinationPath)
2286
- )
2287
- ) {
2288
- return new Response(
2289
- JSON.stringify({
2290
- error: "Dangerous path not allowed",
2291
- }),
2292
- {
2293
- headers: {
2294
- "Content-Type": "application/json",
2295
- ...corsHeaders,
2296
- },
2297
- status: 400,
2298
- }
2299
- );
2300
- }
2301
-
2302
- console.log(`[Server] Moving file: ${sourcePath} -> ${destinationPath}`);
2303
-
2304
- const result = await executeMoveFile(
2305
- sourcePath,
2306
- destinationPath,
2307
- sessionId
2308
- );
2309
-
2310
- return new Response(
2311
- JSON.stringify({
2312
- destinationPath,
2313
- exitCode: result.exitCode,
2314
- sourcePath,
2315
- success: result.success,
2316
- timestamp: new Date().toISOString(),
2317
- }),
2318
- {
2319
- headers: {
2320
- "Content-Type": "application/json",
2321
- ...corsHeaders,
2322
- },
2323
- }
2324
- );
2325
- } catch (error) {
2326
- console.error("[Server] Error in handleMoveFileRequest:", error);
2327
- return new Response(
2328
- JSON.stringify({
2329
- error: "Failed to move file",
2330
- message: error instanceof Error ? error.message : "Unknown error",
2331
- }),
2332
- {
2333
- headers: {
2334
- "Content-Type": "application/json",
2335
- ...corsHeaders,
2336
- },
2337
- status: 500,
2338
- }
2339
- );
2340
- }
2341
- }
2342
-
2343
- async function handleStreamingMoveFileRequest(
2344
- req: Request,
2345
- corsHeaders: Record<string, string>
2346
- ): Promise<Response> {
2347
- try {
2348
- const body = (await req.json()) as MoveFileRequest;
2349
- const { sourcePath, destinationPath, sessionId } = body;
2350
-
2351
- if (!sourcePath || typeof sourcePath !== "string") {
2352
- return new Response(
2353
- JSON.stringify({
2354
- error: "Source path is required and must be a string",
2355
- }),
2356
- {
2357
- headers: {
2358
- "Content-Type": "application/json",
2359
- ...corsHeaders,
2360
- },
2361
- status: 400,
2362
- }
2363
- );
2364
- }
2365
-
2366
- if (!destinationPath || typeof destinationPath !== "string") {
2367
- return new Response(
2368
- JSON.stringify({
2369
- error: "Destination path is required and must be a string",
2370
- }),
2371
- {
2372
- headers: {
2373
- "Content-Type": "application/json",
2374
- ...corsHeaders,
2375
- },
2376
- status: 400,
2377
- }
2378
- );
2379
- }
2380
-
2381
- // Basic safety check - prevent dangerous paths
2382
- const dangerousPatterns = [
2383
- /^\/$/, // Root directory
2384
- /^\/etc/, // System directories
2385
- /^\/var/, // System directories
2386
- /^\/usr/, // System directories
2387
- /^\/bin/, // System directories
2388
- /^\/sbin/, // System directories
2389
- /^\/boot/, // System directories
2390
- /^\/dev/, // System directories
2391
- /^\/proc/, // System directories
2392
- /^\/sys/, // System directories
2393
- /^\/tmp\/\.\./, // Path traversal attempts
2394
- /\.\./, // Path traversal attempts
2395
- ];
2396
-
2397
- if (
2398
- dangerousPatterns.some(
2399
- (pattern) => pattern.test(sourcePath) || pattern.test(destinationPath)
2400
- )
2401
- ) {
2402
- return new Response(
2403
- JSON.stringify({
2404
- error: "Dangerous path not allowed",
2405
- }),
2406
- {
2407
- headers: {
2408
- "Content-Type": "application/json",
2409
- ...corsHeaders,
2410
- },
2411
- status: 400,
2412
- }
2413
- );
2414
- }
2415
-
2416
- console.log(
2417
- `[Server] Moving file (streaming): ${sourcePath} -> ${destinationPath}`
2418
- );
2419
-
2420
- const stream = new ReadableStream({
2421
- start(controller) {
2422
- (async () => {
2423
- try {
2424
- // Send command start event
2425
- controller.enqueue(
2426
- new TextEncoder().encode(
2427
- `data: ${JSON.stringify({
2428
- destinationPath,
2429
- sourcePath,
2430
- timestamp: new Date().toISOString(),
2431
- type: "command_start",
2432
- })}\n\n`
2433
- )
2434
- );
2435
-
2436
- // Move the file
2437
- await executeMoveFile(sourcePath, destinationPath, sessionId);
2438
-
2439
- console.log(
2440
- `[Server] File moved successfully: ${sourcePath} -> ${destinationPath}`
2441
- );
2442
-
2443
- // Send command completion event
2444
- controller.enqueue(
2445
- new TextEncoder().encode(
2446
- `data: ${JSON.stringify({
2447
- destinationPath,
2448
- sourcePath,
2449
- success: true,
2450
- timestamp: new Date().toISOString(),
2451
- type: "command_complete",
2452
- })}\n\n`
2453
- )
2454
- );
2455
-
2456
- controller.close();
2457
- } catch (error) {
2458
- console.error(
2459
- `[Server] Error moving file: ${sourcePath} -> ${destinationPath}`,
2460
- error
2461
- );
2462
-
2463
- controller.enqueue(
2464
- new TextEncoder().encode(
2465
- `data: ${JSON.stringify({
2466
- destinationPath,
2467
- error:
2468
- error instanceof Error ? error.message : "Unknown error",
2469
- sourcePath,
2470
- type: "error",
2471
- })}\n\n`
2472
- )
2473
- );
2474
-
2475
- controller.close();
2476
- }
2477
- })();
2478
- },
2479
- });
2480
-
2481
- return new Response(stream, {
2482
- headers: {
2483
- "Cache-Control": "no-cache",
2484
- Connection: "keep-alive",
2485
- "Content-Type": "text/event-stream",
2486
- ...corsHeaders,
2487
- },
2488
- });
2489
- } catch (error) {
2490
- console.error("[Server] Error in handleStreamingMoveFileRequest:", error);
2491
- return new Response(
2492
- JSON.stringify({
2493
- error: "Failed to move file",
2494
- message: error instanceof Error ? error.message : "Unknown error",
2495
- }),
2496
- {
2497
- headers: {
2498
- "Content-Type": "application/json",
2499
- ...corsHeaders,
2500
- },
2501
- status: 500,
2502
- }
2503
- );
2504
- }
2505
- }
2506
-
2507
- function executeCommand(
2508
- command: string,
2509
- args: string[],
2510
- sessionId?: string
2511
- ): Promise<{
2512
- success: boolean;
2513
- stdout: string;
2514
- stderr: string;
2515
- exitCode: number;
2516
- }> {
2517
- return new Promise((resolve, reject) => {
2518
- const child = spawn(command, args, {
2519
- shell: true,
2520
- stdio: ["pipe", "pipe", "pipe"],
2521
- });
2522
-
2523
- // Store the process reference for cleanup if sessionId is provided
2524
- if (sessionId && sessions.has(sessionId)) {
2525
- const session = sessions.get(sessionId)!;
2526
- session.activeProcess = child;
2527
- }
2528
-
2529
- let stdout = "";
2530
- let stderr = "";
2531
-
2532
- child.stdout?.on("data", (data) => {
2533
- stdout += data.toString();
2534
- });
2535
-
2536
- child.stderr?.on("data", (data) => {
2537
- stderr += data.toString();
2538
- });
2539
-
2540
- child.on("close", (code) => {
2541
- // Clear the active process reference
2542
- if (sessionId && sessions.has(sessionId)) {
2543
- const session = sessions.get(sessionId)!;
2544
- session.activeProcess = null;
2545
- }
2546
-
2547
- console.log(`[Server] Command completed: ${command}, Exit code: ${code}`);
2548
-
2549
- resolve({
2550
- exitCode: code || 0,
2551
- stderr,
2552
- stdout,
2553
- success: code === 0,
2554
- });
2555
- });
2556
-
2557
- child.on("error", (error) => {
2558
- // Clear the active process reference
2559
- if (sessionId && sessions.has(sessionId)) {
2560
- const session = sessions.get(sessionId)!;
2561
- session.activeProcess = null;
2562
- }
2563
-
2564
- reject(error);
2565
- });
2566
- });
2567
- }
2568
-
2569
- function executeGitCheckout(
2570
- repoUrl: string,
2571
- branch: string,
2572
- targetDir: string,
2573
- sessionId?: string
2574
- ): Promise<{
2575
- success: boolean;
2576
- stdout: string;
2577
- stderr: string;
2578
- exitCode: number;
2579
- }> {
2580
- return new Promise((resolve, reject) => {
2581
- // First, clone the repository
2582
- const cloneChild = spawn(
2583
- "git",
2584
- ["clone", "-b", branch, repoUrl, targetDir],
2585
- {
2586
- shell: true,
2587
- stdio: ["pipe", "pipe", "pipe"],
2588
- }
2589
- );
2590
-
2591
- // Store the process reference for cleanup if sessionId is provided
2592
- if (sessionId && sessions.has(sessionId)) {
2593
- const session = sessions.get(sessionId)!;
2594
- session.activeProcess = cloneChild;
2595
- }
2596
-
2597
- let stdout = "";
2598
- let stderr = "";
2599
-
2600
- cloneChild.stdout?.on("data", (data) => {
2601
- stdout += data.toString();
2602
- });
2603
-
2604
- cloneChild.stderr?.on("data", (data) => {
2605
- stderr += data.toString();
2606
- });
2607
-
2608
- cloneChild.on("close", (code) => {
2609
- // Clear the active process reference
2610
- if (sessionId && sessions.has(sessionId)) {
2611
- const session = sessions.get(sessionId)!;
2612
- session.activeProcess = null;
2613
- }
2614
-
2615
- if (code === 0) {
2616
- console.log(
2617
- `[Server] Repository cloned successfully: ${repoUrl} to ${targetDir}`
2618
- );
2619
- resolve({
2620
- exitCode: code || 0,
2621
- stderr,
2622
- stdout,
2623
- success: true,
2624
- });
2625
- } else {
2626
- console.error(
2627
- `[Server] Failed to clone repository: ${repoUrl}, Exit code: ${code}`
2628
- );
2629
- resolve({
2630
- exitCode: code || 1,
2631
- stderr,
2632
- stdout,
2633
- success: false,
2634
- });
2635
- }
2636
- });
2637
-
2638
- cloneChild.on("error", (error) => {
2639
- // Clear the active process reference
2640
- if (sessionId && sessions.has(sessionId)) {
2641
- const session = sessions.get(sessionId)!;
2642
- session.activeProcess = null;
2643
- }
2644
-
2645
- console.error(`[Server] Error cloning repository: ${repoUrl}`, error);
2646
- reject(error);
2647
- });
2648
- });
2649
- }
2650
-
2651
- function executeMkdir(
2652
- path: string,
2653
- recursive: boolean,
2654
- sessionId?: string
2655
- ): Promise<{
2656
- success: boolean;
2657
- stdout: string;
2658
- stderr: string;
2659
- exitCode: number;
2660
- }> {
2661
- return new Promise((resolve, reject) => {
2662
- const args = recursive ? ["-p", path] : [path];
2663
- const mkdirChild = spawn("mkdir", args, {
2664
- shell: true,
2665
- stdio: ["pipe", "pipe", "pipe"],
2666
- });
2667
-
2668
- // Store the process reference for cleanup if sessionId is provided
2669
- if (sessionId && sessions.has(sessionId)) {
2670
- const session = sessions.get(sessionId)!;
2671
- session.activeProcess = mkdirChild;
2672
- }
2673
-
2674
- let stdout = "";
2675
- let stderr = "";
2676
-
2677
- mkdirChild.stdout?.on("data", (data) => {
2678
- stdout += data.toString();
2679
- });
2680
-
2681
- mkdirChild.stderr?.on("data", (data) => {
2682
- stderr += data.toString();
2683
- });
2684
-
2685
- mkdirChild.on("close", (code) => {
2686
- // Clear the active process reference
2687
- if (sessionId && sessions.has(sessionId)) {
2688
- const session = sessions.get(sessionId)!;
2689
- session.activeProcess = null;
2690
- }
2691
-
2692
- if (code === 0) {
2693
- console.log(`[Server] Directory created successfully: ${path}`);
2694
- resolve({
2695
- exitCode: code || 0,
2696
- stderr,
2697
- stdout,
2698
- success: true,
2699
- });
2700
- } else {
2701
- console.error(
2702
- `[Server] Failed to create directory: ${path}, Exit code: ${code}`
2703
- );
2704
- resolve({
2705
- exitCode: code || 1,
2706
- stderr,
2707
- stdout,
2708
- success: false,
2709
- });
2710
- }
2711
- });
2712
-
2713
- mkdirChild.on("error", (error) => {
2714
- // Clear the active process reference
2715
- if (sessionId && sessions.has(sessionId)) {
2716
- const session = sessions.get(sessionId)!;
2717
- session.activeProcess = null;
2718
- }
2719
-
2720
- console.error(`[Server] Error creating directory: ${path}`, error);
2721
- reject(error);
2722
- });
2723
- });
2724
- }
2725
-
2726
- function executeWriteFile(
2727
- path: string,
2728
- content: string,
2729
- encoding: string,
2730
- sessionId?: string
2731
- ): Promise<{
2732
- success: boolean;
2733
- exitCode: number;
2734
- }> {
2735
- return new Promise((resolve, reject) => {
2736
- (async () => {
2737
- try {
2738
- // Ensure the directory exists
2739
- const dir = dirname(path);
2740
- if (dir !== ".") {
2741
- await mkdir(dir, { recursive: true });
2742
- }
2743
-
2744
- // Write the file
2745
- await writeFile(path, content, {
2746
- encoding: encoding as BufferEncoding,
2747
- });
2748
-
2749
- console.log(`[Server] File written successfully: ${path}`);
2750
- resolve({
2751
- exitCode: 0,
2752
- success: true,
2753
- });
2754
- } catch (error) {
2755
- console.error(`[Server] Error writing file: ${path}`, error);
2756
- reject(error);
2757
- }
2758
- })();
2759
- });
2760
- }
2761
-
2762
- function executeReadFile(
2763
- path: string,
2764
- encoding: string,
2765
- sessionId?: string
2766
- ): Promise<{
2767
- success: boolean;
2768
- exitCode: number;
2769
- content: string;
2770
- }> {
2771
- return new Promise((resolve, reject) => {
2772
- (async () => {
2773
- try {
2774
- // Read the file
2775
- const content = await readFile(path, {
2776
- encoding: encoding as BufferEncoding,
2777
- });
2778
-
2779
- console.log(`[Server] File read successfully: ${path}`);
2780
- resolve({
2781
- content,
2782
- exitCode: 0,
2783
- success: true,
2784
- });
2785
- } catch (error) {
2786
- console.error(`[Server] Error reading file: ${path}`, error);
2787
- reject(error);
2788
- }
2789
- })();
2790
- });
2791
- }
2792
-
2793
- function executeDeleteFile(
2794
- path: string,
2795
- sessionId?: string
2796
- ): Promise<{
2797
- success: boolean;
2798
- exitCode: number;
2799
- }> {
2800
- return new Promise((resolve, reject) => {
2801
- (async () => {
2802
- try {
2803
- // Delete the file
2804
- await unlink(path);
2805
-
2806
- console.log(`[Server] File deleted successfully: ${path}`);
2807
- resolve({
2808
- exitCode: 0,
2809
- success: true,
2810
- });
2811
- } catch (error) {
2812
- console.error(`[Server] Error deleting file: ${path}`, error);
2813
- reject(error);
2814
- }
2815
- })();
2816
- });
2817
- }
2818
-
2819
- function executeRenameFile(
2820
- oldPath: string,
2821
- newPath: string,
2822
- sessionId?: string
2823
- ): Promise<{
2824
- success: boolean;
2825
- exitCode: number;
2826
- }> {
2827
- return new Promise((resolve, reject) => {
2828
- (async () => {
2829
- try {
2830
- // Rename the file
2831
- await rename(oldPath, newPath);
2832
-
2833
- console.log(
2834
- `[Server] File renamed successfully: ${oldPath} -> ${newPath}`
2835
- );
2836
- resolve({
2837
- exitCode: 0,
2838
- success: true,
2839
- });
2840
- } catch (error) {
2841
- console.error(
2842
- `[Server] Error renaming file: ${oldPath} -> ${newPath}`,
2843
- error
2844
- );
2845
- reject(error);
2846
- }
2847
- })();
2848
- });
2849
- }
2850
-
2851
- function executeMoveFile(
2852
- sourcePath: string,
2853
- destinationPath: string,
2854
- sessionId?: string
2855
- ): Promise<{
2856
- success: boolean;
2857
- exitCode: number;
2858
- }> {
2859
- return new Promise((resolve, reject) => {
2860
- (async () => {
2861
- try {
2862
- // Move the file
2863
- await rename(sourcePath, destinationPath);
2864
-
2865
- console.log(
2866
- `[Server] File moved successfully: ${sourcePath} -> ${destinationPath}`
2867
- );
2868
- resolve({
2869
- exitCode: 0,
2870
- success: true,
2871
- });
2872
- } catch (error) {
2873
- console.error(
2874
- `[Server] Error moving file: ${sourcePath} -> ${destinationPath}`,
2875
- error
2876
- );
2877
- reject(error);
2878
- }
2879
- })();
2880
- });
2881
- }
566
+ // We don't need this, but typescript complains
567
+ websocket: { async message() {} },
568
+ });
2882
569
 
2883
570
  console.log(`🚀 Bun server running on http://0.0.0.0:${server.port}`);
2884
571
  console.log(`📡 HTTP API endpoints available:`);
@@ -2887,20 +574,28 @@ console.log(` GET /api/session/list - List all sessions`);
2887
574
  console.log(` POST /api/execute - Execute a command (non-streaming)`);
2888
575
  console.log(` POST /api/execute/stream - Execute a command (streaming)`);
2889
576
  console.log(` POST /api/git/checkout - Checkout a git repository`);
2890
- console.log(
2891
- ` POST /api/git/checkout/stream - Checkout a git repository (streaming)`
2892
- );
2893
577
  console.log(` POST /api/mkdir - Create a directory`);
2894
- console.log(` POST /api/mkdir/stream - Create a directory (streaming)`);
2895
578
  console.log(` POST /api/write - Write a file`);
2896
- console.log(` POST /api/write/stream - Write a file (streaming)`);
2897
579
  console.log(` POST /api/read - Read a file`);
2898
- console.log(` POST /api/read/stream - Read a file (streaming)`);
580
+ console.log(` POST /api/read/stream - Stream a file (SSE)`);
2899
581
  console.log(` POST /api/delete - Delete a file`);
2900
- console.log(` POST /api/delete/stream - Delete a file (streaming)`);
2901
582
  console.log(` POST /api/rename - Rename a file`);
2902
- console.log(` POST /api/rename/stream - Rename a file (streaming)`);
2903
583
  console.log(` POST /api/move - Move a file`);
2904
- console.log(` POST /api/move/stream - Move a file (streaming)`);
584
+ console.log(` POST /api/expose-port - Expose a port for external access`);
585
+ console.log(` DELETE /api/unexpose-port - Unexpose a port`);
586
+ console.log(` GET /api/exposed-ports - List exposed ports`);
587
+ console.log(` POST /api/process/start - Start a background process`);
588
+ console.log(` GET /api/process/list - List all processes`);
589
+ console.log(` GET /api/process/{id} - Get process status`);
590
+ console.log(` DELETE /api/process/{id} - Kill a process`);
591
+ console.log(` GET /api/process/{id}/logs - Get process logs`);
592
+ console.log(` GET /api/process/{id}/stream - Stream process logs (SSE)`);
593
+ console.log(` DELETE /api/process/kill-all - Kill all processes`);
594
+ console.log(` GET /proxy/{port}/* - Proxy requests to exposed ports`);
595
+ console.log(` POST /api/contexts - Create a code execution context`);
596
+ console.log(` GET /api/contexts - List all contexts`);
597
+ console.log(` DELETE /api/contexts/{id} - Delete a context`);
598
+ console.log(
599
+ ` POST /api/execute/code - Execute code in a context (streaming)`
600
+ );
2905
601
  console.log(` GET /api/ping - Health check`);
2906
- console.log(` GET /api/commands - List available commands`);