@cloudflare/sandbox 0.0.0-ab0979d → 0.0.0-af082ab
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 +179 -0
- package/Dockerfile +47 -24
- package/README.md +899 -0
- package/container_src/bun.lock +76 -0
- package/container_src/circuit-breaker.ts +121 -0
- package/container_src/control-process.ts +784 -0
- package/container_src/handler/exec.ts +99 -251
- package/container_src/handler/file.ts +253 -640
- package/container_src/handler/git.ts +28 -80
- package/container_src/handler/process.ts +443 -515
- package/container_src/handler/session.ts +92 -0
- package/container_src/index.ts +363 -123
- package/container_src/interpreter-service.ts +276 -0
- package/container_src/isolation.ts +1213 -0
- package/container_src/mime-processor.ts +255 -0
- package/container_src/package.json +9 -0
- package/container_src/runtime/executors/javascript/node_executor.ts +123 -0
- package/container_src/runtime/executors/python/ipython_executor.py +338 -0
- package/container_src/runtime/executors/typescript/ts_executor.ts +138 -0
- package/container_src/runtime/process-pool.ts +464 -0
- package/container_src/shell-escape.ts +42 -0
- package/container_src/startup.sh +11 -0
- package/container_src/types.ts +42 -14
- package/package.json +5 -4
- package/src/client.ts +244 -234
- package/src/errors.ts +219 -0
- package/src/file-stream.ts +162 -0
- package/src/index.ts +76 -15
- package/src/interpreter-client.ts +352 -0
- package/src/interpreter-types.ts +390 -0
- package/src/interpreter.ts +150 -0
- package/src/sandbox.ts +512 -401
- package/src/types.ts +209 -24
- package/tsconfig.json +1 -1
package/src/client.ts
CHANGED
|
@@ -1,41 +1,23 @@
|
|
|
1
|
+
import type { ExecuteRequest } from "../container_src/types";
|
|
1
2
|
import type { Sandbox } from "./index";
|
|
2
3
|
import type {
|
|
4
|
+
BaseExecOptions,
|
|
5
|
+
DeleteFileResponse,
|
|
6
|
+
ExecuteResponse,
|
|
3
7
|
GetProcessLogsResponse,
|
|
4
8
|
GetProcessResponse,
|
|
9
|
+
GitCheckoutResponse,
|
|
10
|
+
ListFilesResponse,
|
|
5
11
|
ListProcessesResponse,
|
|
12
|
+
MkdirResponse,
|
|
13
|
+
MoveFileResponse,
|
|
14
|
+
ReadFileResponse,
|
|
15
|
+
RenameFileResponse,
|
|
6
16
|
StartProcessRequest,
|
|
7
|
-
StartProcessResponse
|
|
17
|
+
StartProcessResponse,
|
|
18
|
+
WriteFileResponse,
|
|
8
19
|
} from "./types";
|
|
9
20
|
|
|
10
|
-
interface ExecuteRequest {
|
|
11
|
-
command: string;
|
|
12
|
-
sessionId?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ExecuteResponse {
|
|
16
|
-
success: boolean;
|
|
17
|
-
stdout: string;
|
|
18
|
-
stderr: string;
|
|
19
|
-
exitCode: number;
|
|
20
|
-
command: string;
|
|
21
|
-
timestamp: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface SessionResponse {
|
|
25
|
-
sessionId: string;
|
|
26
|
-
message: string;
|
|
27
|
-
timestamp: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface SessionListResponse {
|
|
31
|
-
sessions: Array<{
|
|
32
|
-
sessionId: string;
|
|
33
|
-
hasActiveProcess: boolean;
|
|
34
|
-
createdAt: string;
|
|
35
|
-
}>;
|
|
36
|
-
count: number;
|
|
37
|
-
timestamp: string;
|
|
38
|
-
}
|
|
39
21
|
|
|
40
22
|
interface CommandsResponse {
|
|
41
23
|
availableCommands: string[];
|
|
@@ -46,104 +28,62 @@ interface GitCheckoutRequest {
|
|
|
46
28
|
repoUrl: string;
|
|
47
29
|
branch?: string;
|
|
48
30
|
targetDir?: string;
|
|
49
|
-
sessionId
|
|
31
|
+
sessionId: string;
|
|
50
32
|
}
|
|
51
33
|
|
|
52
|
-
export interface GitCheckoutResponse {
|
|
53
|
-
success: boolean;
|
|
54
|
-
stdout: string;
|
|
55
|
-
stderr: string;
|
|
56
|
-
exitCode: number;
|
|
57
|
-
repoUrl: string;
|
|
58
|
-
branch: string;
|
|
59
|
-
targetDir: string;
|
|
60
|
-
timestamp: string;
|
|
61
|
-
}
|
|
62
34
|
|
|
63
35
|
interface MkdirRequest {
|
|
64
36
|
path: string;
|
|
65
37
|
recursive?: boolean;
|
|
66
|
-
sessionId
|
|
38
|
+
sessionId: string;
|
|
67
39
|
}
|
|
68
40
|
|
|
69
|
-
export interface MkdirResponse {
|
|
70
|
-
success: boolean;
|
|
71
|
-
stdout: string;
|
|
72
|
-
stderr: string;
|
|
73
|
-
exitCode: number;
|
|
74
|
-
path: string;
|
|
75
|
-
recursive: boolean;
|
|
76
|
-
timestamp: string;
|
|
77
|
-
}
|
|
78
41
|
|
|
79
42
|
interface WriteFileRequest {
|
|
80
43
|
path: string;
|
|
81
44
|
content: string;
|
|
82
45
|
encoding?: string;
|
|
83
|
-
sessionId
|
|
46
|
+
sessionId: string;
|
|
84
47
|
}
|
|
85
48
|
|
|
86
|
-
export interface WriteFileResponse {
|
|
87
|
-
success: boolean;
|
|
88
|
-
exitCode: number;
|
|
89
|
-
path: string;
|
|
90
|
-
timestamp: string;
|
|
91
|
-
}
|
|
92
49
|
|
|
93
50
|
interface ReadFileRequest {
|
|
94
51
|
path: string;
|
|
95
52
|
encoding?: string;
|
|
96
|
-
sessionId
|
|
53
|
+
sessionId: string;
|
|
97
54
|
}
|
|
98
55
|
|
|
99
|
-
export interface ReadFileResponse {
|
|
100
|
-
success: boolean;
|
|
101
|
-
exitCode: number;
|
|
102
|
-
path: string;
|
|
103
|
-
content: string;
|
|
104
|
-
timestamp: string;
|
|
105
|
-
}
|
|
106
56
|
|
|
107
57
|
interface DeleteFileRequest {
|
|
108
58
|
path: string;
|
|
109
|
-
sessionId
|
|
59
|
+
sessionId: string;
|
|
110
60
|
}
|
|
111
61
|
|
|
112
|
-
export interface DeleteFileResponse {
|
|
113
|
-
success: boolean;
|
|
114
|
-
exitCode: number;
|
|
115
|
-
path: string;
|
|
116
|
-
timestamp: string;
|
|
117
|
-
}
|
|
118
62
|
|
|
119
63
|
interface RenameFileRequest {
|
|
120
64
|
oldPath: string;
|
|
121
65
|
newPath: string;
|
|
122
|
-
sessionId
|
|
66
|
+
sessionId: string;
|
|
123
67
|
}
|
|
124
68
|
|
|
125
|
-
export interface RenameFileResponse {
|
|
126
|
-
success: boolean;
|
|
127
|
-
exitCode: number;
|
|
128
|
-
oldPath: string;
|
|
129
|
-
newPath: string;
|
|
130
|
-
timestamp: string;
|
|
131
|
-
}
|
|
132
69
|
|
|
133
70
|
interface MoveFileRequest {
|
|
134
71
|
sourcePath: string;
|
|
135
72
|
destinationPath: string;
|
|
136
|
-
sessionId
|
|
73
|
+
sessionId: string;
|
|
137
74
|
}
|
|
138
75
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
76
|
+
|
|
77
|
+
interface ListFilesRequest {
|
|
78
|
+
path: string;
|
|
79
|
+
options?: {
|
|
80
|
+
recursive?: boolean;
|
|
81
|
+
includeHidden?: boolean;
|
|
82
|
+
};
|
|
83
|
+
sessionId: string;
|
|
145
84
|
}
|
|
146
85
|
|
|
86
|
+
|
|
147
87
|
interface PreviewInfo {
|
|
148
88
|
url: string;
|
|
149
89
|
port: number;
|
|
@@ -203,7 +143,6 @@ interface HttpClientOptions {
|
|
|
203
143
|
export class HttpClient {
|
|
204
144
|
private baseUrl: string;
|
|
205
145
|
private options: HttpClientOptions;
|
|
206
|
-
private sessionId: string | null = null;
|
|
207
146
|
|
|
208
147
|
constructor(options: HttpClientOptions = {}) {
|
|
209
148
|
this.options = {
|
|
@@ -212,7 +151,7 @@ export class HttpClient {
|
|
|
212
151
|
this.baseUrl = this.options.baseUrl!;
|
|
213
152
|
}
|
|
214
153
|
|
|
215
|
-
|
|
154
|
+
protected async doFetch(
|
|
216
155
|
path: string,
|
|
217
156
|
options?: RequestInit
|
|
218
157
|
): Promise<Response> {
|
|
@@ -253,22 +192,52 @@ export class HttpClient {
|
|
|
253
192
|
}
|
|
254
193
|
}
|
|
255
194
|
|
|
256
|
-
async
|
|
195
|
+
async createSession(options: {
|
|
196
|
+
id: string;
|
|
197
|
+
env?: Record<string, string>;
|
|
198
|
+
cwd?: string;
|
|
199
|
+
isolation?: boolean;
|
|
200
|
+
}): Promise<{ success: boolean; id: string; message: string }> {
|
|
201
|
+
try {
|
|
202
|
+
const response = await this.doFetch(`/api/session/create`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: {
|
|
205
|
+
"Content-Type": "application/json",
|
|
206
|
+
},
|
|
207
|
+
body: JSON.stringify(options),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
212
|
+
error?: string;
|
|
213
|
+
};
|
|
214
|
+
throw new Error(
|
|
215
|
+
errorData.error || `Failed to create session: ${response.status}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const data = await response.json() as { success: boolean; id: string; message: string };
|
|
220
|
+
console.log(`[HTTP Client] Session created: ${options.id}`);
|
|
221
|
+
return data;
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error("[HTTP Client] Error creating session:", error);
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async exec(
|
|
229
|
+
sessionId: string,
|
|
257
230
|
command: string,
|
|
258
|
-
|
|
231
|
+
options?: Pick<BaseExecOptions, "cwd" | "env">
|
|
259
232
|
): Promise<ExecuteResponse> {
|
|
260
233
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
234
|
+
// Always use session-specific endpoint
|
|
263
235
|
const response = await this.doFetch(`/api/execute`, {
|
|
264
|
-
|
|
265
|
-
command,
|
|
266
|
-
sessionId: targetSessionId,
|
|
267
|
-
} as ExecuteRequest),
|
|
236
|
+
method: "POST",
|
|
268
237
|
headers: {
|
|
269
238
|
"Content-Type": "application/json",
|
|
270
239
|
},
|
|
271
|
-
|
|
240
|
+
body: JSON.stringify({ id: sessionId, command }),
|
|
272
241
|
});
|
|
273
242
|
|
|
274
243
|
if (!response.ok) {
|
|
@@ -276,27 +245,34 @@ export class HttpClient {
|
|
|
276
245
|
error?: string;
|
|
277
246
|
};
|
|
278
247
|
throw new Error(
|
|
279
|
-
errorData.error || `
|
|
248
|
+
errorData.error || `Failed to execute in session: ${response.status}`
|
|
280
249
|
);
|
|
281
250
|
}
|
|
282
251
|
|
|
283
|
-
const data
|
|
252
|
+
const data = await response.json() as { stdout: string; stderr: string; exitCode: number; success: boolean };
|
|
284
253
|
console.log(
|
|
285
|
-
`[HTTP Client] Command executed
|
|
254
|
+
`[HTTP Client] Command executed in session ${sessionId}: ${command}`
|
|
286
255
|
);
|
|
256
|
+
|
|
257
|
+
// Convert to ExecuteResponse format for consistency
|
|
258
|
+
const executeResponse: ExecuteResponse = {
|
|
259
|
+
...data,
|
|
260
|
+
command,
|
|
261
|
+
timestamp: new Date().toISOString()
|
|
262
|
+
};
|
|
287
263
|
|
|
288
264
|
// Call the callback if provided
|
|
289
265
|
this.options.onCommandComplete?.(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
266
|
+
executeResponse.success,
|
|
267
|
+
executeResponse.exitCode,
|
|
268
|
+
executeResponse.stdout,
|
|
269
|
+
executeResponse.stderr,
|
|
270
|
+
executeResponse.command
|
|
295
271
|
);
|
|
296
272
|
|
|
297
|
-
return
|
|
273
|
+
return executeResponse;
|
|
298
274
|
} catch (error) {
|
|
299
|
-
console.error("[HTTP Client] Error executing
|
|
275
|
+
console.error("[HTTP Client] Error executing in session:", error);
|
|
300
276
|
this.options.onError?.(
|
|
301
277
|
error instanceof Error ? error.message : "Unknown error",
|
|
302
278
|
command
|
|
@@ -305,24 +281,21 @@ export class HttpClient {
|
|
|
305
281
|
}
|
|
306
282
|
}
|
|
307
283
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
command: string
|
|
311
|
-
sessionId?: string
|
|
284
|
+
async execStream(
|
|
285
|
+
sessionId: string,
|
|
286
|
+
command: string
|
|
312
287
|
): Promise<ReadableStream<Uint8Array>> {
|
|
313
288
|
try {
|
|
314
|
-
|
|
315
|
-
|
|
289
|
+
// Always use session-specific streaming endpoint
|
|
316
290
|
const response = await this.doFetch(`/api/execute/stream`, {
|
|
317
|
-
|
|
318
|
-
command,
|
|
319
|
-
sessionId: targetSessionId,
|
|
320
|
-
}),
|
|
291
|
+
method: "POST",
|
|
321
292
|
headers: {
|
|
322
293
|
"Content-Type": "application/json",
|
|
323
|
-
"Accept": "text/event-stream",
|
|
324
294
|
},
|
|
325
|
-
|
|
295
|
+
body: JSON.stringify({
|
|
296
|
+
id: sessionId,
|
|
297
|
+
command
|
|
298
|
+
}),
|
|
326
299
|
});
|
|
327
300
|
|
|
328
301
|
if (!response.ok) {
|
|
@@ -330,40 +303,37 @@ export class HttpClient {
|
|
|
330
303
|
error?: string;
|
|
331
304
|
};
|
|
332
305
|
throw new Error(
|
|
333
|
-
errorData.error || `
|
|
306
|
+
errorData.error || `Failed to stream execute in session: ${response.status}`
|
|
334
307
|
);
|
|
335
308
|
}
|
|
336
309
|
|
|
337
310
|
if (!response.body) {
|
|
338
|
-
throw new Error("No response body for streaming
|
|
311
|
+
throw new Error("No response body for streaming execution");
|
|
339
312
|
}
|
|
340
313
|
|
|
341
314
|
console.log(
|
|
342
|
-
`[HTTP Client] Started command
|
|
315
|
+
`[HTTP Client] Started streaming command in session ${sessionId}: ${command}`
|
|
343
316
|
);
|
|
344
|
-
|
|
345
317
|
return response.body;
|
|
346
318
|
} catch (error) {
|
|
347
|
-
console.error("[HTTP Client] Error in
|
|
319
|
+
console.error("[HTTP Client] Error streaming execute in session:", error);
|
|
348
320
|
throw error;
|
|
349
321
|
}
|
|
350
322
|
}
|
|
351
323
|
|
|
352
324
|
async gitCheckout(
|
|
353
325
|
repoUrl: string,
|
|
326
|
+
sessionId: string,
|
|
354
327
|
branch: string = "main",
|
|
355
|
-
targetDir?: string
|
|
356
|
-
sessionId?: string
|
|
328
|
+
targetDir?: string
|
|
357
329
|
): Promise<GitCheckoutResponse> {
|
|
358
330
|
try {
|
|
359
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
360
|
-
|
|
361
331
|
const response = await this.doFetch(`/api/git/checkout`, {
|
|
362
332
|
body: JSON.stringify({
|
|
363
333
|
branch,
|
|
364
334
|
repoUrl,
|
|
365
|
-
sessionId: targetSessionId,
|
|
366
335
|
targetDir,
|
|
336
|
+
sessionId,
|
|
367
337
|
} as GitCheckoutRequest),
|
|
368
338
|
headers: {
|
|
369
339
|
"Content-Type": "application/json",
|
|
@@ -392,20 +362,17 @@ export class HttpClient {
|
|
|
392
362
|
}
|
|
393
363
|
}
|
|
394
364
|
|
|
395
|
-
|
|
396
365
|
async mkdir(
|
|
397
366
|
path: string,
|
|
398
367
|
recursive: boolean = false,
|
|
399
|
-
sessionId
|
|
368
|
+
sessionId: string
|
|
400
369
|
): Promise<MkdirResponse> {
|
|
401
370
|
try {
|
|
402
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
403
|
-
|
|
404
371
|
const response = await this.doFetch(`/api/mkdir`, {
|
|
405
372
|
body: JSON.stringify({
|
|
406
373
|
path,
|
|
407
374
|
recursive,
|
|
408
|
-
sessionId
|
|
375
|
+
sessionId,
|
|
409
376
|
} as MkdirRequest),
|
|
410
377
|
headers: {
|
|
411
378
|
"Content-Type": "application/json",
|
|
@@ -424,7 +391,7 @@ export class HttpClient {
|
|
|
424
391
|
|
|
425
392
|
const data: MkdirResponse = await response.json();
|
|
426
393
|
console.log(
|
|
427
|
-
`[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}`
|
|
394
|
+
`[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
428
395
|
);
|
|
429
396
|
|
|
430
397
|
return data;
|
|
@@ -434,22 +401,19 @@ export class HttpClient {
|
|
|
434
401
|
}
|
|
435
402
|
}
|
|
436
403
|
|
|
437
|
-
|
|
438
404
|
async writeFile(
|
|
439
405
|
path: string,
|
|
440
406
|
content: string,
|
|
441
407
|
encoding: string = "utf-8",
|
|
442
|
-
sessionId
|
|
408
|
+
sessionId: string
|
|
443
409
|
): Promise<WriteFileResponse> {
|
|
444
410
|
try {
|
|
445
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
446
|
-
|
|
447
411
|
const response = await this.doFetch(`/api/write`, {
|
|
448
412
|
body: JSON.stringify({
|
|
449
413
|
content,
|
|
450
414
|
encoding,
|
|
451
415
|
path,
|
|
452
|
-
sessionId
|
|
416
|
+
sessionId,
|
|
453
417
|
} as WriteFileRequest),
|
|
454
418
|
headers: {
|
|
455
419
|
"Content-Type": "application/json",
|
|
@@ -468,7 +432,7 @@ export class HttpClient {
|
|
|
468
432
|
|
|
469
433
|
const data: WriteFileResponse = await response.json();
|
|
470
434
|
console.log(
|
|
471
|
-
`[HTTP Client] File written: ${path}, Success: ${data.success}`
|
|
435
|
+
`[HTTP Client] File written: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
472
436
|
);
|
|
473
437
|
|
|
474
438
|
return data;
|
|
@@ -478,20 +442,17 @@ export class HttpClient {
|
|
|
478
442
|
}
|
|
479
443
|
}
|
|
480
444
|
|
|
481
|
-
|
|
482
445
|
async readFile(
|
|
483
446
|
path: string,
|
|
484
447
|
encoding: string = "utf-8",
|
|
485
|
-
sessionId
|
|
448
|
+
sessionId: string
|
|
486
449
|
): Promise<ReadFileResponse> {
|
|
487
450
|
try {
|
|
488
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
489
|
-
|
|
490
451
|
const response = await this.doFetch(`/api/read`, {
|
|
491
452
|
body: JSON.stringify({
|
|
492
453
|
encoding,
|
|
493
454
|
path,
|
|
494
|
-
sessionId
|
|
455
|
+
sessionId,
|
|
495
456
|
} as ReadFileRequest),
|
|
496
457
|
headers: {
|
|
497
458
|
"Content-Type": "application/json",
|
|
@@ -510,7 +471,7 @@ export class HttpClient {
|
|
|
510
471
|
|
|
511
472
|
const data: ReadFileResponse = await response.json();
|
|
512
473
|
console.log(
|
|
513
|
-
`[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
|
|
474
|
+
`[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
514
475
|
);
|
|
515
476
|
|
|
516
477
|
return data;
|
|
@@ -520,18 +481,54 @@ export class HttpClient {
|
|
|
520
481
|
}
|
|
521
482
|
}
|
|
522
483
|
|
|
484
|
+
async readFileStream(
|
|
485
|
+
path: string,
|
|
486
|
+
sessionId: string
|
|
487
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
488
|
+
try {
|
|
489
|
+
const response = await this.doFetch(`/api/read/stream`, {
|
|
490
|
+
method: "POST",
|
|
491
|
+
headers: {
|
|
492
|
+
"Content-Type": "application/json",
|
|
493
|
+
},
|
|
494
|
+
body: JSON.stringify({
|
|
495
|
+
path,
|
|
496
|
+
sessionId,
|
|
497
|
+
} as ReadFileRequest),
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (!response.ok) {
|
|
501
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
502
|
+
error?: string;
|
|
503
|
+
};
|
|
504
|
+
throw new Error(
|
|
505
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (!response.body) {
|
|
510
|
+
throw new Error("No response body for file streaming");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
console.log(
|
|
514
|
+
`[HTTP Client] Started streaming file: ${path}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
515
|
+
);
|
|
516
|
+
return response.body;
|
|
517
|
+
} catch (error) {
|
|
518
|
+
console.error("[HTTP Client] Error streaming file:", error);
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
523
522
|
|
|
524
523
|
async deleteFile(
|
|
525
524
|
path: string,
|
|
526
|
-
sessionId
|
|
525
|
+
sessionId: string
|
|
527
526
|
): Promise<DeleteFileResponse> {
|
|
528
527
|
try {
|
|
529
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
530
|
-
|
|
531
528
|
const response = await this.doFetch(`/api/delete`, {
|
|
532
529
|
body: JSON.stringify({
|
|
533
530
|
path,
|
|
534
|
-
sessionId
|
|
531
|
+
sessionId,
|
|
535
532
|
} as DeleteFileRequest),
|
|
536
533
|
headers: {
|
|
537
534
|
"Content-Type": "application/json",
|
|
@@ -550,7 +547,7 @@ export class HttpClient {
|
|
|
550
547
|
|
|
551
548
|
const data: DeleteFileResponse = await response.json();
|
|
552
549
|
console.log(
|
|
553
|
-
`[HTTP Client] File deleted: ${path}, Success: ${data.success}`
|
|
550
|
+
`[HTTP Client] File deleted: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
554
551
|
);
|
|
555
552
|
|
|
556
553
|
return data;
|
|
@@ -560,20 +557,17 @@ export class HttpClient {
|
|
|
560
557
|
}
|
|
561
558
|
}
|
|
562
559
|
|
|
563
|
-
|
|
564
560
|
async renameFile(
|
|
565
561
|
oldPath: string,
|
|
566
562
|
newPath: string,
|
|
567
|
-
sessionId
|
|
563
|
+
sessionId: string
|
|
568
564
|
): Promise<RenameFileResponse> {
|
|
569
565
|
try {
|
|
570
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
571
|
-
|
|
572
566
|
const response = await this.doFetch(`/api/rename`, {
|
|
573
567
|
body: JSON.stringify({
|
|
574
568
|
newPath,
|
|
575
569
|
oldPath,
|
|
576
|
-
sessionId
|
|
570
|
+
sessionId,
|
|
577
571
|
} as RenameFileRequest),
|
|
578
572
|
headers: {
|
|
579
573
|
"Content-Type": "application/json",
|
|
@@ -592,7 +586,7 @@ export class HttpClient {
|
|
|
592
586
|
|
|
593
587
|
const data: RenameFileResponse = await response.json();
|
|
594
588
|
console.log(
|
|
595
|
-
`[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
|
|
589
|
+
`[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
596
590
|
);
|
|
597
591
|
|
|
598
592
|
return data;
|
|
@@ -602,20 +596,17 @@ export class HttpClient {
|
|
|
602
596
|
}
|
|
603
597
|
}
|
|
604
598
|
|
|
605
|
-
|
|
606
599
|
async moveFile(
|
|
607
600
|
sourcePath: string,
|
|
608
601
|
destinationPath: string,
|
|
609
|
-
sessionId
|
|
602
|
+
sessionId: string
|
|
610
603
|
): Promise<MoveFileResponse> {
|
|
611
604
|
try {
|
|
612
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
613
|
-
|
|
614
605
|
const response = await this.doFetch(`/api/move`, {
|
|
615
606
|
body: JSON.stringify({
|
|
616
607
|
destinationPath,
|
|
617
|
-
sessionId: targetSessionId,
|
|
618
608
|
sourcePath,
|
|
609
|
+
sessionId,
|
|
619
610
|
} as MoveFileRequest),
|
|
620
611
|
headers: {
|
|
621
612
|
"Content-Type": "application/json",
|
|
@@ -634,7 +625,7 @@ export class HttpClient {
|
|
|
634
625
|
|
|
635
626
|
const data: MoveFileResponse = await response.json();
|
|
636
627
|
console.log(
|
|
637
|
-
`[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}`
|
|
628
|
+
`[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
638
629
|
);
|
|
639
630
|
|
|
640
631
|
return data;
|
|
@@ -644,6 +635,47 @@ export class HttpClient {
|
|
|
644
635
|
}
|
|
645
636
|
}
|
|
646
637
|
|
|
638
|
+
async listFiles(
|
|
639
|
+
path: string,
|
|
640
|
+
sessionId: string,
|
|
641
|
+
options?: {
|
|
642
|
+
recursive?: boolean;
|
|
643
|
+
includeHidden?: boolean;
|
|
644
|
+
}
|
|
645
|
+
): Promise<ListFilesResponse> {
|
|
646
|
+
try {
|
|
647
|
+
const response = await this.doFetch(`/api/list-files`, {
|
|
648
|
+
body: JSON.stringify({
|
|
649
|
+
path,
|
|
650
|
+
options,
|
|
651
|
+
sessionId,
|
|
652
|
+
} as ListFilesRequest),
|
|
653
|
+
headers: {
|
|
654
|
+
"Content-Type": "application/json",
|
|
655
|
+
},
|
|
656
|
+
method: "POST",
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
if (!response.ok) {
|
|
660
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
661
|
+
error?: string;
|
|
662
|
+
};
|
|
663
|
+
throw new Error(
|
|
664
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const data: ListFilesResponse = await response.json();
|
|
669
|
+
console.log(
|
|
670
|
+
`[HTTP Client] Listed ${data.files.length} files in: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
return data;
|
|
674
|
+
} catch (error) {
|
|
675
|
+
console.error("[HTTP Client] Error listing files:", error);
|
|
676
|
+
throw error;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
647
679
|
|
|
648
680
|
async exposePort(port: number, name?: string): Promise<ExposePortResponse> {
|
|
649
681
|
try {
|
|
@@ -670,7 +702,9 @@ export class HttpClient {
|
|
|
670
702
|
|
|
671
703
|
const data: ExposePortResponse = await response.json();
|
|
672
704
|
console.log(
|
|
673
|
-
`[HTTP Client] Port exposed: ${port}${
|
|
705
|
+
`[HTTP Client] Port exposed: ${port}${
|
|
706
|
+
name ? ` (${name})` : ""
|
|
707
|
+
}, Success: ${data.success}`
|
|
674
708
|
);
|
|
675
709
|
|
|
676
710
|
return data;
|
|
@@ -732,9 +766,7 @@ export class HttpClient {
|
|
|
732
766
|
}
|
|
733
767
|
|
|
734
768
|
const data: GetExposedPortsResponse = await response.json();
|
|
735
|
-
console.log(
|
|
736
|
-
`[HTTP Client] Got ${data.count} exposed ports`
|
|
737
|
-
);
|
|
769
|
+
console.log(`[HTTP Client] Got ${data.count} exposed ports`);
|
|
738
770
|
|
|
739
771
|
return data;
|
|
740
772
|
} catch (error) {
|
|
@@ -765,48 +797,13 @@ export class HttpClient {
|
|
|
765
797
|
}
|
|
766
798
|
}
|
|
767
799
|
|
|
768
|
-
async getCommands(): Promise<string[]> {
|
|
769
|
-
try {
|
|
770
|
-
const response = await fetch(`${this.baseUrl}/api/commands`, {
|
|
771
|
-
headers: {
|
|
772
|
-
"Content-Type": "application/json",
|
|
773
|
-
},
|
|
774
|
-
method: "GET",
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
if (!response.ok) {
|
|
778
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
const data: CommandsResponse = await response.json();
|
|
782
|
-
console.log(
|
|
783
|
-
`[HTTP Client] Available commands: ${data.availableCommands.length}`
|
|
784
|
-
);
|
|
785
|
-
return data.availableCommands;
|
|
786
|
-
} catch (error) {
|
|
787
|
-
console.error("[HTTP Client] Error getting commands:", error);
|
|
788
|
-
throw error;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
getSessionId(): string | null {
|
|
793
|
-
return this.sessionId;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
setSessionId(sessionId: string): void {
|
|
797
|
-
this.sessionId = sessionId;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
clearSession(): void {
|
|
801
|
-
this.sessionId = null;
|
|
802
|
-
}
|
|
803
800
|
|
|
804
801
|
// Process management methods
|
|
805
802
|
async startProcess(
|
|
806
803
|
command: string,
|
|
804
|
+
sessionId: string,
|
|
807
805
|
options?: {
|
|
808
806
|
processId?: string;
|
|
809
|
-
sessionId?: string;
|
|
810
807
|
timeout?: number;
|
|
811
808
|
env?: Record<string, string>;
|
|
812
809
|
cwd?: string;
|
|
@@ -815,15 +812,11 @@ export class HttpClient {
|
|
|
815
812
|
}
|
|
816
813
|
): Promise<StartProcessResponse> {
|
|
817
814
|
try {
|
|
818
|
-
const targetSessionId = options?.sessionId || this.sessionId;
|
|
819
|
-
|
|
820
815
|
const response = await this.doFetch("/api/process/start", {
|
|
821
816
|
body: JSON.stringify({
|
|
822
817
|
command,
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
sessionId: targetSessionId,
|
|
826
|
-
},
|
|
818
|
+
sessionId,
|
|
819
|
+
options,
|
|
827
820
|
} as StartProcessRequest),
|
|
828
821
|
headers: {
|
|
829
822
|
"Content-Type": "application/json",
|
|
@@ -852,9 +845,12 @@ export class HttpClient {
|
|
|
852
845
|
}
|
|
853
846
|
}
|
|
854
847
|
|
|
855
|
-
async listProcesses(): Promise<ListProcessesResponse> {
|
|
848
|
+
async listProcesses(sessionId?: string): Promise<ListProcessesResponse> {
|
|
856
849
|
try {
|
|
857
|
-
const
|
|
850
|
+
const url = sessionId
|
|
851
|
+
? `/api/process/list?session=${encodeURIComponent(sessionId)}`
|
|
852
|
+
: "/api/process/list";
|
|
853
|
+
const response = await this.doFetch(url, {
|
|
858
854
|
headers: {
|
|
859
855
|
"Content-Type": "application/json",
|
|
860
856
|
},
|
|
@@ -871,9 +867,7 @@ export class HttpClient {
|
|
|
871
867
|
}
|
|
872
868
|
|
|
873
869
|
const data: ListProcessesResponse = await response.json();
|
|
874
|
-
console.log(
|
|
875
|
-
`[HTTP Client] Listed ${data.processes.length} processes`
|
|
876
|
-
);
|
|
870
|
+
console.log(`[HTTP Client] Listed ${data.processes.length} processes`);
|
|
877
871
|
|
|
878
872
|
return data;
|
|
879
873
|
} catch (error) {
|
|
@@ -902,7 +896,9 @@ export class HttpClient {
|
|
|
902
896
|
|
|
903
897
|
const data: GetProcessResponse = await response.json();
|
|
904
898
|
console.log(
|
|
905
|
-
`[HTTP Client] Got process ${processId}: ${
|
|
899
|
+
`[HTTP Client] Got process ${processId}: ${
|
|
900
|
+
data.process?.status || "not found"
|
|
901
|
+
}`
|
|
906
902
|
);
|
|
907
903
|
|
|
908
904
|
return data;
|
|
@@ -912,7 +908,9 @@ export class HttpClient {
|
|
|
912
908
|
}
|
|
913
909
|
}
|
|
914
910
|
|
|
915
|
-
async killProcess(
|
|
911
|
+
async killProcess(
|
|
912
|
+
processId: string
|
|
913
|
+
): Promise<{ success: boolean; message: string }> {
|
|
916
914
|
try {
|
|
917
915
|
const response = await this.doFetch(`/api/process/${processId}`, {
|
|
918
916
|
headers: {
|
|
@@ -930,10 +928,11 @@ export class HttpClient {
|
|
|
930
928
|
);
|
|
931
929
|
}
|
|
932
930
|
|
|
933
|
-
const data = await response.json() as {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
931
|
+
const data = (await response.json()) as {
|
|
932
|
+
success: boolean;
|
|
933
|
+
message: string;
|
|
934
|
+
};
|
|
935
|
+
console.log(`[HTTP Client] Killed process ${processId}`);
|
|
937
936
|
|
|
938
937
|
return data;
|
|
939
938
|
} catch (error) {
|
|
@@ -942,9 +941,16 @@ export class HttpClient {
|
|
|
942
941
|
}
|
|
943
942
|
}
|
|
944
943
|
|
|
945
|
-
async killAllProcesses(): Promise<{
|
|
944
|
+
async killAllProcesses(sessionId?: string): Promise<{
|
|
945
|
+
success: boolean;
|
|
946
|
+
killedCount: number;
|
|
947
|
+
message: string;
|
|
948
|
+
}> {
|
|
946
949
|
try {
|
|
947
|
-
const
|
|
950
|
+
const url = sessionId
|
|
951
|
+
? `/api/process/kill-all?session=${encodeURIComponent(sessionId)}`
|
|
952
|
+
: "/api/process/kill-all";
|
|
953
|
+
const response = await this.doFetch(url, {
|
|
948
954
|
headers: {
|
|
949
955
|
"Content-Type": "application/json",
|
|
950
956
|
},
|
|
@@ -960,10 +966,12 @@ export class HttpClient {
|
|
|
960
966
|
);
|
|
961
967
|
}
|
|
962
968
|
|
|
963
|
-
const data = await response.json() as {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
969
|
+
const data = (await response.json()) as {
|
|
970
|
+
success: boolean;
|
|
971
|
+
killedCount: number;
|
|
972
|
+
message: string;
|
|
973
|
+
};
|
|
974
|
+
console.log(`[HTTP Client] Killed ${data.killedCount} processes`);
|
|
967
975
|
|
|
968
976
|
return data;
|
|
969
977
|
} catch (error) {
|
|
@@ -991,9 +999,7 @@ export class HttpClient {
|
|
|
991
999
|
}
|
|
992
1000
|
|
|
993
1001
|
const data: GetProcessLogsResponse = await response.json();
|
|
994
|
-
console.log(
|
|
995
|
-
`[HTTP Client] Got logs for process ${processId}`
|
|
996
|
-
);
|
|
1002
|
+
console.log(`[HTTP Client] Got logs for process ${processId}`);
|
|
997
1003
|
|
|
998
1004
|
return data;
|
|
999
1005
|
} catch (error) {
|
|
@@ -1002,14 +1008,18 @@ export class HttpClient {
|
|
|
1002
1008
|
}
|
|
1003
1009
|
}
|
|
1004
1010
|
|
|
1005
|
-
async streamProcessLogs(
|
|
1011
|
+
async streamProcessLogs(
|
|
1012
|
+
processId: string,
|
|
1013
|
+
options?: { signal?: AbortSignal }
|
|
1014
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
1006
1015
|
try {
|
|
1007
1016
|
const response = await this.doFetch(`/api/process/${processId}/stream`, {
|
|
1008
1017
|
headers: {
|
|
1009
|
-
|
|
1018
|
+
Accept: "text/event-stream",
|
|
1010
1019
|
"Cache-Control": "no-cache",
|
|
1011
1020
|
},
|
|
1012
1021
|
method: "GET",
|
|
1022
|
+
signal: options?.signal,
|
|
1013
1023
|
});
|
|
1014
1024
|
|
|
1015
1025
|
if (!response.ok) {
|