@cloudflare/sandbox 0.0.0-d1c7c99 → 0.0.0-d81d2a5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/Dockerfile +97 -11
- package/README.md +743 -24
- package/container_src/bun.lock +122 -0
- package/container_src/handler/exec.ts +340 -0
- package/container_src/handler/file.ts +844 -0
- package/container_src/handler/git.ts +182 -0
- package/container_src/handler/ports.ts +314 -0
- package/container_src/handler/process.ts +640 -0
- package/container_src/index.ts +269 -2644
- package/container_src/jupyter-server.ts +336 -0
- package/container_src/mime-processor.ts +255 -0
- package/container_src/package.json +9 -0
- package/container_src/startup.sh +52 -0
- package/container_src/types.ts +108 -0
- package/package.json +5 -9
- package/src/client.ts +331 -1270
- package/src/index.ts +29 -136
- package/src/interpreter-types.ts +383 -0
- package/src/interpreter.ts +150 -0
- package/src/jupyter-client.ts +266 -0
- package/src/request-handler.ts +144 -0
- package/src/sandbox.ts +778 -0
- package/src/security.ts +113 -0
- package/src/sse-parser.ts +147 -0
- package/src/types.ts +401 -0
- package/tsconfig.json +1 -1
- package/tests/client.example.ts +0 -308
- package/tests/connection-test.ts +0 -81
- package/tests/simple-test.ts +0 -81
- package/tests/test1.ts +0 -281
- package/tests/test2.ts +0 -929
package/container_src/index.ts
CHANGED
|
@@ -1,68 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname } from "node:path";
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
4
2
|
import { serve } from "bun";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
}
|
|
3
|
+
import { handleExecuteRequest, handleStreamingExecuteRequest } from "./handler/exec";
|
|
4
|
+
import {
|
|
5
|
+
handleDeleteFileRequest,
|
|
6
|
+
handleMkdirRequest,
|
|
7
|
+
handleMoveFileRequest,
|
|
8
|
+
handleReadFileRequest,
|
|
9
|
+
handleRenameFileRequest,
|
|
10
|
+
handleWriteFileRequest,
|
|
11
|
+
} from "./handler/file";
|
|
12
|
+
import { handleGitCheckoutRequest } from "./handler/git";
|
|
13
|
+
import {
|
|
14
|
+
handleExposePortRequest,
|
|
15
|
+
handleGetExposedPortsRequest,
|
|
16
|
+
handleProxyRequest,
|
|
17
|
+
handleUnexposePortRequest,
|
|
18
|
+
} from "./handler/ports";
|
|
19
|
+
import {
|
|
20
|
+
handleGetProcessLogsRequest,
|
|
21
|
+
handleGetProcessRequest,
|
|
22
|
+
handleKillAllProcessesRequest,
|
|
23
|
+
handleKillProcessRequest,
|
|
24
|
+
handleListProcessesRequest,
|
|
25
|
+
handleStartProcessRequest,
|
|
26
|
+
handleStreamProcessLogsRequest,
|
|
27
|
+
} from "./handler/process";
|
|
28
|
+
import { type CreateContextRequest, JupyterServer } from "./jupyter-server";
|
|
29
|
+
import type { ProcessRecord, SessionData } from "./types";
|
|
59
30
|
|
|
60
31
|
// In-memory session storage (in production, you'd want to use a proper database)
|
|
61
32
|
const sessions = new Map<string, SessionData>();
|
|
62
33
|
|
|
63
|
-
//
|
|
34
|
+
// In-memory storage for exposed ports
|
|
35
|
+
const exposedPorts = new Map<number, { name?: string; exposedAt: Date }>();
|
|
36
|
+
|
|
37
|
+
// In-memory process storage - cleared on container restart
|
|
38
|
+
const processes = new Map<string, ProcessRecord>();
|
|
39
|
+
|
|
40
|
+
// Generate a unique session ID using cryptographically secure randomness
|
|
64
41
|
function generateSessionId(): string {
|
|
65
|
-
return `session_${Date.now()}_${
|
|
42
|
+
return `session_${Date.now()}_${randomBytes(6).toString('hex')}`;
|
|
66
43
|
}
|
|
67
44
|
|
|
68
45
|
// Clean up old sessions (older than 1 hour)
|
|
@@ -79,8 +56,28 @@ function cleanupOldSessions() {
|
|
|
79
56
|
// Run cleanup every 10 minutes
|
|
80
57
|
setInterval(cleanupOldSessions, 10 * 60 * 1000);
|
|
81
58
|
|
|
59
|
+
// Initialize Jupyter server
|
|
60
|
+
const jupyterServer = new JupyterServer();
|
|
61
|
+
let jupyterInitialized = false;
|
|
62
|
+
|
|
63
|
+
// Initialize Jupyter immediately since startup.sh ensures it's ready
|
|
64
|
+
(async () => {
|
|
65
|
+
try {
|
|
66
|
+
await jupyterServer.initialize();
|
|
67
|
+
jupyterInitialized = true;
|
|
68
|
+
console.log("[Container] Jupyter integration initialized successfully");
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error("[Container] Failed to initialize Jupyter:", error);
|
|
71
|
+
// Log more details to help debug
|
|
72
|
+
if (error instanceof Error) {
|
|
73
|
+
console.error("[Container] Error details:", error.message);
|
|
74
|
+
console.error("[Container] Stack trace:", error.stack);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
|
|
82
79
|
const server = serve({
|
|
83
|
-
fetch(req: Request) {
|
|
80
|
+
async fetch(req: Request) {
|
|
84
81
|
const url = new URL(req.url);
|
|
85
82
|
const pathname = url.pathname;
|
|
86
83
|
|
|
@@ -111,51 +108,6 @@ const server = serve({
|
|
|
111
108
|
},
|
|
112
109
|
});
|
|
113
110
|
|
|
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
111
|
case "/api/session/create":
|
|
160
112
|
if (req.method === "POST") {
|
|
161
113
|
const sessionId = generateSessionId();
|
|
@@ -212,13 +164,13 @@ const server = serve({
|
|
|
212
164
|
|
|
213
165
|
case "/api/execute":
|
|
214
166
|
if (req.method === "POST") {
|
|
215
|
-
return handleExecuteRequest(req, corsHeaders);
|
|
167
|
+
return handleExecuteRequest(sessions, req, corsHeaders);
|
|
216
168
|
}
|
|
217
169
|
break;
|
|
218
170
|
|
|
219
171
|
case "/api/execute/stream":
|
|
220
172
|
if (req.method === "POST") {
|
|
221
|
-
return handleStreamingExecuteRequest(req, corsHeaders);
|
|
173
|
+
return handleStreamingExecuteRequest(sessions, req, corsHeaders);
|
|
222
174
|
}
|
|
223
175
|
break;
|
|
224
176
|
|
|
@@ -228,6 +180,7 @@ const server = serve({
|
|
|
228
180
|
JSON.stringify({
|
|
229
181
|
message: "pong",
|
|
230
182
|
timestamp: new Date().toISOString(),
|
|
183
|
+
jupyter: jupyterInitialized ? "ready" : "not ready",
|
|
231
184
|
}),
|
|
232
185
|
{
|
|
233
186
|
headers: {
|
|
@@ -273,89 +226,250 @@ const server = serve({
|
|
|
273
226
|
|
|
274
227
|
case "/api/git/checkout":
|
|
275
228
|
if (req.method === "POST") {
|
|
276
|
-
return handleGitCheckoutRequest(req, corsHeaders);
|
|
229
|
+
return handleGitCheckoutRequest(sessions, req, corsHeaders);
|
|
277
230
|
}
|
|
278
231
|
break;
|
|
279
232
|
|
|
280
|
-
case "/api/
|
|
233
|
+
case "/api/mkdir":
|
|
281
234
|
if (req.method === "POST") {
|
|
282
|
-
return
|
|
235
|
+
return handleMkdirRequest(sessions, req, corsHeaders);
|
|
283
236
|
}
|
|
284
237
|
break;
|
|
285
238
|
|
|
286
|
-
case "/api/
|
|
239
|
+
case "/api/write":
|
|
287
240
|
if (req.method === "POST") {
|
|
288
|
-
return
|
|
241
|
+
return handleWriteFileRequest(req, corsHeaders);
|
|
289
242
|
}
|
|
290
243
|
break;
|
|
291
244
|
|
|
292
|
-
case "/api/
|
|
245
|
+
case "/api/read":
|
|
293
246
|
if (req.method === "POST") {
|
|
294
|
-
return
|
|
247
|
+
return handleReadFileRequest(req, corsHeaders);
|
|
295
248
|
}
|
|
296
249
|
break;
|
|
297
250
|
|
|
298
|
-
case "/api/
|
|
251
|
+
case "/api/delete":
|
|
299
252
|
if (req.method === "POST") {
|
|
300
|
-
return
|
|
253
|
+
return handleDeleteFileRequest(req, corsHeaders);
|
|
301
254
|
}
|
|
302
255
|
break;
|
|
303
256
|
|
|
304
|
-
case "/api/
|
|
257
|
+
case "/api/rename":
|
|
305
258
|
if (req.method === "POST") {
|
|
306
|
-
return
|
|
259
|
+
return handleRenameFileRequest(req, corsHeaders);
|
|
307
260
|
}
|
|
308
261
|
break;
|
|
309
262
|
|
|
310
|
-
case "/api/
|
|
263
|
+
case "/api/move":
|
|
311
264
|
if (req.method === "POST") {
|
|
312
|
-
return
|
|
265
|
+
return handleMoveFileRequest(req, corsHeaders);
|
|
313
266
|
}
|
|
314
267
|
break;
|
|
315
268
|
|
|
316
|
-
case "/api/
|
|
269
|
+
case "/api/expose-port":
|
|
317
270
|
if (req.method === "POST") {
|
|
318
|
-
return
|
|
271
|
+
return handleExposePortRequest(exposedPorts, req, corsHeaders);
|
|
319
272
|
}
|
|
320
273
|
break;
|
|
321
274
|
|
|
322
|
-
case "/api/
|
|
323
|
-
if (req.method === "
|
|
324
|
-
return
|
|
275
|
+
case "/api/unexpose-port":
|
|
276
|
+
if (req.method === "DELETE") {
|
|
277
|
+
return handleUnexposePortRequest(exposedPorts, req, corsHeaders);
|
|
325
278
|
}
|
|
326
279
|
break;
|
|
327
280
|
|
|
328
|
-
case "/api/
|
|
329
|
-
if (req.method === "
|
|
330
|
-
return
|
|
281
|
+
case "/api/exposed-ports":
|
|
282
|
+
if (req.method === "GET") {
|
|
283
|
+
return handleGetExposedPortsRequest(exposedPorts, req, corsHeaders);
|
|
331
284
|
}
|
|
332
285
|
break;
|
|
333
286
|
|
|
334
|
-
case "/api/
|
|
287
|
+
case "/api/process/start":
|
|
335
288
|
if (req.method === "POST") {
|
|
336
|
-
return
|
|
289
|
+
return handleStartProcessRequest(processes, req, corsHeaders);
|
|
337
290
|
}
|
|
338
291
|
break;
|
|
339
292
|
|
|
340
|
-
case "/api/
|
|
341
|
-
if (req.method === "
|
|
342
|
-
return
|
|
293
|
+
case "/api/process/list":
|
|
294
|
+
if (req.method === "GET") {
|
|
295
|
+
return handleListProcessesRequest(processes, req, corsHeaders);
|
|
343
296
|
}
|
|
344
297
|
break;
|
|
345
298
|
|
|
346
|
-
case "/api/
|
|
299
|
+
case "/api/process/kill-all":
|
|
300
|
+
if (req.method === "DELETE") {
|
|
301
|
+
return handleKillAllProcessesRequest(processes, req, corsHeaders);
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
// Code interpreter endpoints
|
|
306
|
+
case "/api/contexts":
|
|
347
307
|
if (req.method === "POST") {
|
|
348
|
-
|
|
308
|
+
if (!jupyterInitialized) {
|
|
309
|
+
return new Response(
|
|
310
|
+
JSON.stringify({
|
|
311
|
+
error: "Jupyter server is not ready. Please try again in a moment."
|
|
312
|
+
}),
|
|
313
|
+
{
|
|
314
|
+
status: 503,
|
|
315
|
+
headers: {
|
|
316
|
+
"Content-Type": "application/json",
|
|
317
|
+
...corsHeaders,
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const body = await req.json() as CreateContextRequest;
|
|
324
|
+
const context = await jupyterServer.createContext(body);
|
|
325
|
+
return new Response(
|
|
326
|
+
JSON.stringify({
|
|
327
|
+
id: context.id,
|
|
328
|
+
language: context.language,
|
|
329
|
+
cwd: context.cwd,
|
|
330
|
+
createdAt: context.createdAt,
|
|
331
|
+
lastUsed: context.lastUsed
|
|
332
|
+
}),
|
|
333
|
+
{
|
|
334
|
+
headers: {
|
|
335
|
+
"Content-Type": "application/json",
|
|
336
|
+
...corsHeaders,
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
console.error("[Container] Error creating context:", error);
|
|
342
|
+
return new Response(
|
|
343
|
+
JSON.stringify({
|
|
344
|
+
error: error instanceof Error ? error.message : "Failed to create context"
|
|
345
|
+
}),
|
|
346
|
+
{
|
|
347
|
+
status: 500,
|
|
348
|
+
headers: {
|
|
349
|
+
"Content-Type": "application/json",
|
|
350
|
+
...corsHeaders,
|
|
351
|
+
},
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
} else if (req.method === "GET") {
|
|
356
|
+
if (!jupyterInitialized) {
|
|
357
|
+
return new Response(
|
|
358
|
+
JSON.stringify({ contexts: [] }),
|
|
359
|
+
{
|
|
360
|
+
headers: {
|
|
361
|
+
"Content-Type": "application/json",
|
|
362
|
+
...corsHeaders,
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
const contexts = await jupyterServer.listContexts();
|
|
368
|
+
return new Response(
|
|
369
|
+
JSON.stringify({ contexts }),
|
|
370
|
+
{
|
|
371
|
+
headers: {
|
|
372
|
+
"Content-Type": "application/json",
|
|
373
|
+
...corsHeaders,
|
|
374
|
+
},
|
|
375
|
+
}
|
|
376
|
+
);
|
|
349
377
|
}
|
|
350
378
|
break;
|
|
351
379
|
|
|
352
|
-
case "/api/
|
|
380
|
+
case "/api/execute/code":
|
|
353
381
|
if (req.method === "POST") {
|
|
354
|
-
|
|
382
|
+
if (!jupyterInitialized) {
|
|
383
|
+
return new Response(
|
|
384
|
+
JSON.stringify({
|
|
385
|
+
error: "Jupyter server is not ready. Please try again in a moment."
|
|
386
|
+
}),
|
|
387
|
+
{
|
|
388
|
+
status: 503,
|
|
389
|
+
headers: {
|
|
390
|
+
"Content-Type": "application/json",
|
|
391
|
+
...corsHeaders,
|
|
392
|
+
},
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const body = await req.json() as { context_id: string; code: string; language?: string };
|
|
398
|
+
return await jupyterServer.executeCode(body.context_id, body.code, body.language);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error("[Container] Error executing code:", error);
|
|
401
|
+
return new Response(
|
|
402
|
+
JSON.stringify({
|
|
403
|
+
error: error instanceof Error ? error.message : "Failed to execute code"
|
|
404
|
+
}),
|
|
405
|
+
{
|
|
406
|
+
status: 500,
|
|
407
|
+
headers: {
|
|
408
|
+
"Content-Type": "application/json",
|
|
409
|
+
...corsHeaders,
|
|
410
|
+
},
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
355
414
|
}
|
|
356
415
|
break;
|
|
357
416
|
|
|
358
417
|
default:
|
|
418
|
+
// Handle dynamic routes for contexts
|
|
419
|
+
if (pathname.startsWith("/api/contexts/") && pathname.split('/').length === 4) {
|
|
420
|
+
const contextId = pathname.split('/')[3];
|
|
421
|
+
if (req.method === "DELETE") {
|
|
422
|
+
try {
|
|
423
|
+
await jupyterServer.deleteContext(contextId);
|
|
424
|
+
return new Response(
|
|
425
|
+
JSON.stringify({ success: true }),
|
|
426
|
+
{
|
|
427
|
+
headers: {
|
|
428
|
+
"Content-Type": "application/json",
|
|
429
|
+
...corsHeaders,
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
return new Response(
|
|
435
|
+
JSON.stringify({
|
|
436
|
+
error: error instanceof Error ? error.message : "Failed to delete context"
|
|
437
|
+
}),
|
|
438
|
+
{
|
|
439
|
+
status: error instanceof Error && error.message.includes("not found") ? 404 : 500,
|
|
440
|
+
headers: {
|
|
441
|
+
"Content-Type": "application/json",
|
|
442
|
+
...corsHeaders,
|
|
443
|
+
},
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Handle dynamic routes for individual processes
|
|
451
|
+
if (pathname.startsWith("/api/process/")) {
|
|
452
|
+
const segments = pathname.split('/');
|
|
453
|
+
if (segments.length >= 4) {
|
|
454
|
+
const processId = segments[3];
|
|
455
|
+
const action = segments[4]; // Optional: logs, stream, etc.
|
|
456
|
+
|
|
457
|
+
if (!action && req.method === "GET") {
|
|
458
|
+
return handleGetProcessRequest(processes, req, corsHeaders, processId);
|
|
459
|
+
} else if (!action && req.method === "DELETE") {
|
|
460
|
+
return handleKillProcessRequest(processes, req, corsHeaders, processId);
|
|
461
|
+
} else if (action === "logs" && req.method === "GET") {
|
|
462
|
+
return handleGetProcessLogsRequest(processes, req, corsHeaders, processId);
|
|
463
|
+
} else if (action === "stream" && req.method === "GET") {
|
|
464
|
+
return handleStreamProcessLogsRequest(processes, req, corsHeaders, processId);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Check if this is a proxy request for an exposed port
|
|
469
|
+
if (pathname.startsWith("/proxy/")) {
|
|
470
|
+
return handleProxyRequest(exposedPorts, req, corsHeaders);
|
|
471
|
+
}
|
|
472
|
+
|
|
359
473
|
console.log(`[Container] Route not found: ${pathname}`);
|
|
360
474
|
return new Response("Not Found", {
|
|
361
475
|
headers: corsHeaders,
|
|
@@ -381,2504 +495,9 @@ const server = serve({
|
|
|
381
495
|
},
|
|
382
496
|
hostname: "0.0.0.0",
|
|
383
497
|
port: 3000,
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
}
|
|
498
|
+
// We don't need this, but typescript complains
|
|
499
|
+
websocket: { async message() { } },
|
|
500
|
+
});
|
|
2882
501
|
|
|
2883
502
|
console.log(`🚀 Bun server running on http://0.0.0.0:${server.port}`);
|
|
2884
503
|
console.log(`📡 HTTP API endpoints available:`);
|
|
@@ -2887,20 +506,26 @@ console.log(` GET /api/session/list - List all sessions`);
|
|
|
2887
506
|
console.log(` POST /api/execute - Execute a command (non-streaming)`);
|
|
2888
507
|
console.log(` POST /api/execute/stream - Execute a command (streaming)`);
|
|
2889
508
|
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
509
|
console.log(` POST /api/mkdir - Create a directory`);
|
|
2894
|
-
console.log(` POST /api/mkdir/stream - Create a directory (streaming)`);
|
|
2895
510
|
console.log(` POST /api/write - Write a file`);
|
|
2896
|
-
console.log(` POST /api/write/stream - Write a file (streaming)`);
|
|
2897
511
|
console.log(` POST /api/read - Read a file`);
|
|
2898
|
-
console.log(` POST /api/read/stream - Read a file (streaming)`);
|
|
2899
512
|
console.log(` POST /api/delete - Delete a file`);
|
|
2900
|
-
console.log(` POST /api/delete/stream - Delete a file (streaming)`);
|
|
2901
513
|
console.log(` POST /api/rename - Rename a file`);
|
|
2902
|
-
console.log(` POST /api/rename/stream - Rename a file (streaming)`);
|
|
2903
514
|
console.log(` POST /api/move - Move a file`);
|
|
2904
|
-
console.log(` POST /api/
|
|
515
|
+
console.log(` POST /api/expose-port - Expose a port for external access`);
|
|
516
|
+
console.log(` DELETE /api/unexpose-port - Unexpose a port`);
|
|
517
|
+
console.log(` GET /api/exposed-ports - List exposed ports`);
|
|
518
|
+
console.log(` POST /api/process/start - Start a background process`);
|
|
519
|
+
console.log(` GET /api/process/list - List all processes`);
|
|
520
|
+
console.log(` GET /api/process/{id} - Get process status`);
|
|
521
|
+
console.log(` DELETE /api/process/{id} - Kill a process`);
|
|
522
|
+
console.log(` GET /api/process/{id}/logs - Get process logs`);
|
|
523
|
+
console.log(` GET /api/process/{id}/stream - Stream process logs (SSE)`);
|
|
524
|
+
console.log(` DELETE /api/process/kill-all - Kill all processes`);
|
|
525
|
+
console.log(` GET /proxy/{port}/* - Proxy requests to exposed ports`);
|
|
526
|
+
console.log(` POST /api/contexts - Create a code execution context`);
|
|
527
|
+
console.log(` GET /api/contexts - List all contexts`);
|
|
528
|
+
console.log(` DELETE /api/contexts/{id} - Delete a context`);
|
|
529
|
+
console.log(` POST /api/execute/code - Execute code in a context (streaming)`);
|
|
2905
530
|
console.log(` GET /api/ping - Health check`);
|
|
2906
531
|
console.log(` GET /api/commands - List available commands`);
|