@cloudflare/sandbox 0.0.0-444d2da → 0.0.0-46eb4e6

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