@cloudflare/sandbox 0.0.0-dc66e8e → 0.0.0-e1fa354
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 +137 -0
- package/Dockerfile +48 -24
- package/README.md +846 -0
- package/container_src/bun.lock +122 -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 +204 -642
- 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/isolation.ts +1038 -0
- package/container_src/jupyter-server.ts +579 -0
- package/container_src/jupyter-service.ts +461 -0
- package/container_src/jupyter_config.py +48 -0
- package/container_src/mime-processor.ts +255 -0
- package/container_src/package.json +9 -0
- package/container_src/shell-escape.ts +42 -0
- package/container_src/startup.sh +84 -0
- package/container_src/types.ts +42 -14
- package/package.json +5 -4
- package/src/client.ts +206 -235
- package/src/errors.ts +218 -0
- package/src/index.ts +59 -15
- package/src/interpreter-types.ts +383 -0
- package/src/interpreter.ts +150 -0
- package/src/jupyter-client.ts +349 -0
- package/src/sandbox.ts +502 -400
- package/src/types.ts +140 -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,15 @@ export class HttpClient {
|
|
|
520
481
|
}
|
|
521
482
|
}
|
|
522
483
|
|
|
523
|
-
|
|
524
484
|
async deleteFile(
|
|
525
485
|
path: string,
|
|
526
|
-
sessionId
|
|
486
|
+
sessionId: string
|
|
527
487
|
): Promise<DeleteFileResponse> {
|
|
528
488
|
try {
|
|
529
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
530
|
-
|
|
531
489
|
const response = await this.doFetch(`/api/delete`, {
|
|
532
490
|
body: JSON.stringify({
|
|
533
491
|
path,
|
|
534
|
-
sessionId
|
|
492
|
+
sessionId,
|
|
535
493
|
} as DeleteFileRequest),
|
|
536
494
|
headers: {
|
|
537
495
|
"Content-Type": "application/json",
|
|
@@ -550,7 +508,7 @@ export class HttpClient {
|
|
|
550
508
|
|
|
551
509
|
const data: DeleteFileResponse = await response.json();
|
|
552
510
|
console.log(
|
|
553
|
-
`[HTTP Client] File deleted: ${path}, Success: ${data.success}`
|
|
511
|
+
`[HTTP Client] File deleted: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
554
512
|
);
|
|
555
513
|
|
|
556
514
|
return data;
|
|
@@ -560,20 +518,17 @@ export class HttpClient {
|
|
|
560
518
|
}
|
|
561
519
|
}
|
|
562
520
|
|
|
563
|
-
|
|
564
521
|
async renameFile(
|
|
565
522
|
oldPath: string,
|
|
566
523
|
newPath: string,
|
|
567
|
-
sessionId
|
|
524
|
+
sessionId: string
|
|
568
525
|
): Promise<RenameFileResponse> {
|
|
569
526
|
try {
|
|
570
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
571
|
-
|
|
572
527
|
const response = await this.doFetch(`/api/rename`, {
|
|
573
528
|
body: JSON.stringify({
|
|
574
529
|
newPath,
|
|
575
530
|
oldPath,
|
|
576
|
-
sessionId
|
|
531
|
+
sessionId,
|
|
577
532
|
} as RenameFileRequest),
|
|
578
533
|
headers: {
|
|
579
534
|
"Content-Type": "application/json",
|
|
@@ -592,7 +547,7 @@ export class HttpClient {
|
|
|
592
547
|
|
|
593
548
|
const data: RenameFileResponse = await response.json();
|
|
594
549
|
console.log(
|
|
595
|
-
`[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
|
|
550
|
+
`[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
596
551
|
);
|
|
597
552
|
|
|
598
553
|
return data;
|
|
@@ -602,20 +557,17 @@ export class HttpClient {
|
|
|
602
557
|
}
|
|
603
558
|
}
|
|
604
559
|
|
|
605
|
-
|
|
606
560
|
async moveFile(
|
|
607
561
|
sourcePath: string,
|
|
608
562
|
destinationPath: string,
|
|
609
|
-
sessionId
|
|
563
|
+
sessionId: string
|
|
610
564
|
): Promise<MoveFileResponse> {
|
|
611
565
|
try {
|
|
612
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
613
|
-
|
|
614
566
|
const response = await this.doFetch(`/api/move`, {
|
|
615
567
|
body: JSON.stringify({
|
|
616
568
|
destinationPath,
|
|
617
|
-
sessionId: targetSessionId,
|
|
618
569
|
sourcePath,
|
|
570
|
+
sessionId,
|
|
619
571
|
} as MoveFileRequest),
|
|
620
572
|
headers: {
|
|
621
573
|
"Content-Type": "application/json",
|
|
@@ -634,7 +586,7 @@ export class HttpClient {
|
|
|
634
586
|
|
|
635
587
|
const data: MoveFileResponse = await response.json();
|
|
636
588
|
console.log(
|
|
637
|
-
`[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}`
|
|
589
|
+
`[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
638
590
|
);
|
|
639
591
|
|
|
640
592
|
return data;
|
|
@@ -644,6 +596,47 @@ export class HttpClient {
|
|
|
644
596
|
}
|
|
645
597
|
}
|
|
646
598
|
|
|
599
|
+
async listFiles(
|
|
600
|
+
path: string,
|
|
601
|
+
sessionId: string,
|
|
602
|
+
options?: {
|
|
603
|
+
recursive?: boolean;
|
|
604
|
+
includeHidden?: boolean;
|
|
605
|
+
}
|
|
606
|
+
): Promise<ListFilesResponse> {
|
|
607
|
+
try {
|
|
608
|
+
const response = await this.doFetch(`/api/list-files`, {
|
|
609
|
+
body: JSON.stringify({
|
|
610
|
+
path,
|
|
611
|
+
options,
|
|
612
|
+
sessionId,
|
|
613
|
+
} as ListFilesRequest),
|
|
614
|
+
headers: {
|
|
615
|
+
"Content-Type": "application/json",
|
|
616
|
+
},
|
|
617
|
+
method: "POST",
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
if (!response.ok) {
|
|
621
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
622
|
+
error?: string;
|
|
623
|
+
};
|
|
624
|
+
throw new Error(
|
|
625
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const data: ListFilesResponse = await response.json();
|
|
630
|
+
console.log(
|
|
631
|
+
`[HTTP Client] Listed ${data.files.length} files in: ${path}, Success: ${data.success}${sessionId ? ` in session: ${sessionId}` : ''}`
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
return data;
|
|
635
|
+
} catch (error) {
|
|
636
|
+
console.error("[HTTP Client] Error listing files:", error);
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
647
640
|
|
|
648
641
|
async exposePort(port: number, name?: string): Promise<ExposePortResponse> {
|
|
649
642
|
try {
|
|
@@ -670,7 +663,9 @@ export class HttpClient {
|
|
|
670
663
|
|
|
671
664
|
const data: ExposePortResponse = await response.json();
|
|
672
665
|
console.log(
|
|
673
|
-
`[HTTP Client] Port exposed: ${port}${
|
|
666
|
+
`[HTTP Client] Port exposed: ${port}${
|
|
667
|
+
name ? ` (${name})` : ""
|
|
668
|
+
}, Success: ${data.success}`
|
|
674
669
|
);
|
|
675
670
|
|
|
676
671
|
return data;
|
|
@@ -732,9 +727,7 @@ export class HttpClient {
|
|
|
732
727
|
}
|
|
733
728
|
|
|
734
729
|
const data: GetExposedPortsResponse = await response.json();
|
|
735
|
-
console.log(
|
|
736
|
-
`[HTTP Client] Got ${data.count} exposed ports`
|
|
737
|
-
);
|
|
730
|
+
console.log(`[HTTP Client] Got ${data.count} exposed ports`);
|
|
738
731
|
|
|
739
732
|
return data;
|
|
740
733
|
} catch (error) {
|
|
@@ -765,48 +758,13 @@ export class HttpClient {
|
|
|
765
758
|
}
|
|
766
759
|
}
|
|
767
760
|
|
|
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
761
|
|
|
804
762
|
// Process management methods
|
|
805
763
|
async startProcess(
|
|
806
764
|
command: string,
|
|
765
|
+
sessionId: string,
|
|
807
766
|
options?: {
|
|
808
767
|
processId?: string;
|
|
809
|
-
sessionId?: string;
|
|
810
768
|
timeout?: number;
|
|
811
769
|
env?: Record<string, string>;
|
|
812
770
|
cwd?: string;
|
|
@@ -815,15 +773,11 @@ export class HttpClient {
|
|
|
815
773
|
}
|
|
816
774
|
): Promise<StartProcessResponse> {
|
|
817
775
|
try {
|
|
818
|
-
const targetSessionId = options?.sessionId || this.sessionId;
|
|
819
|
-
|
|
820
776
|
const response = await this.doFetch("/api/process/start", {
|
|
821
777
|
body: JSON.stringify({
|
|
822
778
|
command,
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
sessionId: targetSessionId,
|
|
826
|
-
},
|
|
779
|
+
sessionId,
|
|
780
|
+
options,
|
|
827
781
|
} as StartProcessRequest),
|
|
828
782
|
headers: {
|
|
829
783
|
"Content-Type": "application/json",
|
|
@@ -852,9 +806,12 @@ export class HttpClient {
|
|
|
852
806
|
}
|
|
853
807
|
}
|
|
854
808
|
|
|
855
|
-
async listProcesses(): Promise<ListProcessesResponse> {
|
|
809
|
+
async listProcesses(sessionId?: string): Promise<ListProcessesResponse> {
|
|
856
810
|
try {
|
|
857
|
-
const
|
|
811
|
+
const url = sessionId
|
|
812
|
+
? `/api/process/list?session=${encodeURIComponent(sessionId)}`
|
|
813
|
+
: "/api/process/list";
|
|
814
|
+
const response = await this.doFetch(url, {
|
|
858
815
|
headers: {
|
|
859
816
|
"Content-Type": "application/json",
|
|
860
817
|
},
|
|
@@ -871,9 +828,7 @@ export class HttpClient {
|
|
|
871
828
|
}
|
|
872
829
|
|
|
873
830
|
const data: ListProcessesResponse = await response.json();
|
|
874
|
-
console.log(
|
|
875
|
-
`[HTTP Client] Listed ${data.processes.length} processes`
|
|
876
|
-
);
|
|
831
|
+
console.log(`[HTTP Client] Listed ${data.processes.length} processes`);
|
|
877
832
|
|
|
878
833
|
return data;
|
|
879
834
|
} catch (error) {
|
|
@@ -902,7 +857,9 @@ export class HttpClient {
|
|
|
902
857
|
|
|
903
858
|
const data: GetProcessResponse = await response.json();
|
|
904
859
|
console.log(
|
|
905
|
-
`[HTTP Client] Got process ${processId}: ${
|
|
860
|
+
`[HTTP Client] Got process ${processId}: ${
|
|
861
|
+
data.process?.status || "not found"
|
|
862
|
+
}`
|
|
906
863
|
);
|
|
907
864
|
|
|
908
865
|
return data;
|
|
@@ -912,7 +869,9 @@ export class HttpClient {
|
|
|
912
869
|
}
|
|
913
870
|
}
|
|
914
871
|
|
|
915
|
-
async killProcess(
|
|
872
|
+
async killProcess(
|
|
873
|
+
processId: string
|
|
874
|
+
): Promise<{ success: boolean; message: string }> {
|
|
916
875
|
try {
|
|
917
876
|
const response = await this.doFetch(`/api/process/${processId}`, {
|
|
918
877
|
headers: {
|
|
@@ -930,10 +889,11 @@ export class HttpClient {
|
|
|
930
889
|
);
|
|
931
890
|
}
|
|
932
891
|
|
|
933
|
-
const data = await response.json() as {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
892
|
+
const data = (await response.json()) as {
|
|
893
|
+
success: boolean;
|
|
894
|
+
message: string;
|
|
895
|
+
};
|
|
896
|
+
console.log(`[HTTP Client] Killed process ${processId}`);
|
|
937
897
|
|
|
938
898
|
return data;
|
|
939
899
|
} catch (error) {
|
|
@@ -942,9 +902,16 @@ export class HttpClient {
|
|
|
942
902
|
}
|
|
943
903
|
}
|
|
944
904
|
|
|
945
|
-
async killAllProcesses(): Promise<{
|
|
905
|
+
async killAllProcesses(sessionId?: string): Promise<{
|
|
906
|
+
success: boolean;
|
|
907
|
+
killedCount: number;
|
|
908
|
+
message: string;
|
|
909
|
+
}> {
|
|
946
910
|
try {
|
|
947
|
-
const
|
|
911
|
+
const url = sessionId
|
|
912
|
+
? `/api/process/kill-all?session=${encodeURIComponent(sessionId)}`
|
|
913
|
+
: "/api/process/kill-all";
|
|
914
|
+
const response = await this.doFetch(url, {
|
|
948
915
|
headers: {
|
|
949
916
|
"Content-Type": "application/json",
|
|
950
917
|
},
|
|
@@ -960,10 +927,12 @@ export class HttpClient {
|
|
|
960
927
|
);
|
|
961
928
|
}
|
|
962
929
|
|
|
963
|
-
const data = await response.json() as {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
930
|
+
const data = (await response.json()) as {
|
|
931
|
+
success: boolean;
|
|
932
|
+
killedCount: number;
|
|
933
|
+
message: string;
|
|
934
|
+
};
|
|
935
|
+
console.log(`[HTTP Client] Killed ${data.killedCount} processes`);
|
|
967
936
|
|
|
968
937
|
return data;
|
|
969
938
|
} catch (error) {
|
|
@@ -991,9 +960,7 @@ export class HttpClient {
|
|
|
991
960
|
}
|
|
992
961
|
|
|
993
962
|
const data: GetProcessLogsResponse = await response.json();
|
|
994
|
-
console.log(
|
|
995
|
-
`[HTTP Client] Got logs for process ${processId}`
|
|
996
|
-
);
|
|
963
|
+
console.log(`[HTTP Client] Got logs for process ${processId}`);
|
|
997
964
|
|
|
998
965
|
return data;
|
|
999
966
|
} catch (error) {
|
|
@@ -1002,14 +969,18 @@ export class HttpClient {
|
|
|
1002
969
|
}
|
|
1003
970
|
}
|
|
1004
971
|
|
|
1005
|
-
async streamProcessLogs(
|
|
972
|
+
async streamProcessLogs(
|
|
973
|
+
processId: string,
|
|
974
|
+
options?: { signal?: AbortSignal }
|
|
975
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
1006
976
|
try {
|
|
1007
977
|
const response = await this.doFetch(`/api/process/${processId}/stream`, {
|
|
1008
978
|
headers: {
|
|
1009
|
-
|
|
979
|
+
Accept: "text/event-stream",
|
|
1010
980
|
"Cache-Control": "no-cache",
|
|
1011
981
|
},
|
|
1012
982
|
method: "GET",
|
|
983
|
+
signal: options?.signal,
|
|
1013
984
|
});
|
|
1014
985
|
|
|
1015
986
|
if (!response.ok) {
|