@cloudflare/sandbox 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/Dockerfile +73 -9
  3. package/container_src/handler/exec.ts +337 -0
  4. package/container_src/handler/file.ts +844 -0
  5. package/container_src/handler/git.ts +182 -0
  6. package/container_src/handler/ports.ts +314 -0
  7. package/container_src/handler/process.ts +640 -0
  8. package/container_src/index.ts +102 -2647
  9. package/container_src/types.ts +103 -0
  10. package/dist/chunk-6THNBO4S.js +46 -0
  11. package/dist/chunk-6THNBO4S.js.map +1 -0
  12. package/dist/chunk-6UAWTJ5S.js +85 -0
  13. package/dist/chunk-6UAWTJ5S.js.map +1 -0
  14. package/dist/chunk-G4XT4SP7.js +638 -0
  15. package/dist/chunk-G4XT4SP7.js.map +1 -0
  16. package/dist/chunk-ISFOIYQC.js +585 -0
  17. package/dist/chunk-ISFOIYQC.js.map +1 -0
  18. package/dist/chunk-NNGBXDMY.js +89 -0
  19. package/dist/chunk-NNGBXDMY.js.map +1 -0
  20. package/dist/client-Da-mLX4p.d.ts +210 -0
  21. package/dist/client.d.ts +2 -1
  22. package/dist/client.js +3 -37
  23. package/dist/index.d.ts +5 -200
  24. package/dist/index.js +17 -106
  25. package/dist/index.js.map +1 -1
  26. package/dist/request-handler.d.ts +16 -0
  27. package/dist/request-handler.js +12 -0
  28. package/dist/request-handler.js.map +1 -0
  29. package/dist/sandbox.d.ts +3 -0
  30. package/dist/sandbox.js +12 -0
  31. package/dist/sandbox.js.map +1 -0
  32. package/dist/security.d.ts +30 -0
  33. package/dist/security.js +13 -0
  34. package/dist/security.js.map +1 -0
  35. package/dist/sse-parser.d.ts +28 -0
  36. package/dist/sse-parser.js +11 -0
  37. package/dist/sse-parser.js.map +1 -0
  38. package/dist/types.d.ts +284 -0
  39. package/dist/types.js +19 -0
  40. package/dist/types.js.map +1 -0
  41. package/package.json +2 -7
  42. package/src/client.ts +320 -1242
  43. package/src/index.ts +20 -136
  44. package/src/request-handler.ts +144 -0
  45. package/src/sandbox.ts +645 -0
  46. package/src/security.ts +113 -0
  47. package/src/sse-parser.ts +147 -0
  48. package/src/types.ts +386 -0
  49. package/README.md +0 -65
  50. package/dist/chunk-7WZJ3TRE.js +0 -1364
  51. package/dist/chunk-7WZJ3TRE.js.map +0 -1
  52. package/tests/client.example.ts +0 -308
  53. package/tests/connection-test.ts +0 -81
  54. package/tests/simple-test.ts +0 -81
  55. package/tests/test1.ts +0 -281
  56. package/tests/test2.ts +0 -929
package/src/client.ts CHANGED
@@ -1,9 +1,15 @@
1
- import type { DurableObject } from "cloudflare:workers";
2
1
  import type { Sandbox } from "./index";
2
+ import type {
3
+ GetProcessLogsResponse,
4
+ GetProcessResponse,
5
+ ListProcessesResponse,
6
+ StartProcessRequest,
7
+ StartProcessResponse
8
+ } from "./types";
3
9
 
4
10
  interface ExecuteRequest {
5
11
  command: string;
6
- args?: string[];
12
+ sessionId?: string;
7
13
  }
8
14
 
9
15
  export interface ExecuteResponse {
@@ -12,7 +18,6 @@ export interface ExecuteResponse {
12
18
  stderr: string;
13
19
  exitCode: number;
14
20
  command: string;
15
- args: string[];
16
21
  timestamp: string;
17
22
  }
18
23
 
@@ -139,37 +144,47 @@ export interface MoveFileResponse {
139
144
  timestamp: string;
140
145
  }
141
146
 
142
- interface PingResponse {
143
- message: string;
147
+ interface PreviewInfo {
148
+ url: string;
149
+ port: number;
150
+ name?: string;
151
+ }
152
+
153
+ interface ExposedPort extends PreviewInfo {
154
+ exposedAt: string;
155
+ timestamp: string;
156
+ }
157
+
158
+ interface ExposePortResponse {
159
+ success: boolean;
160
+ port: number;
161
+ name?: string;
162
+ exposedAt: string;
163
+ timestamp: string;
164
+ }
165
+
166
+ interface UnexposePortResponse {
167
+ success: boolean;
168
+ port: number;
169
+ timestamp: string;
170
+ }
171
+
172
+ interface GetExposedPortsResponse {
173
+ ports: ExposedPort[];
174
+ count: number;
144
175
  timestamp: string;
145
176
  }
146
177
 
147
- interface StreamEvent {
148
- type: "command_start" | "output" | "command_complete" | "error";
149
- command?: string;
150
- args?: string[];
151
- stream?: "stdout" | "stderr";
152
- data?: string;
153
- message?: string;
154
- path?: string;
155
- oldPath?: string;
156
- newPath?: string;
157
- sourcePath?: string;
158
- destinationPath?: string;
159
- content?: string;
160
- success?: boolean;
161
- exitCode?: number;
162
- stdout?: string;
163
- stderr?: string;
164
- error?: string;
165
- timestamp?: string;
178
+ interface PingResponse {
179
+ message: string;
180
+ timestamp: string;
166
181
  }
167
182
 
168
183
  interface HttpClientOptions {
169
184
  stub?: Sandbox;
170
185
  baseUrl?: string;
171
186
  port?: number;
172
- onCommandStart?: (command: string, args: string[]) => void;
187
+ onCommandStart?: (command: string) => void;
173
188
  onOutput?: (
174
189
  stream: "stdout" | "stderr",
175
190
  data: string,
@@ -180,11 +195,9 @@ interface HttpClientOptions {
180
195
  exitCode: number,
181
196
  stdout: string,
182
197
  stderr: string,
183
- command: string,
184
- args: string[]
198
+ command: string
185
199
  ) => void;
186
- onError?: (error: string, command?: string, args?: string[]) => void;
187
- onStreamEvent?: (event: StreamEvent) => void;
200
+ onError?: (error: string, command?: string) => void;
188
201
  }
189
202
 
190
203
  export class HttpClient {
@@ -239,106 +252,9 @@ export class HttpClient {
239
252
  throw error;
240
253
  }
241
254
  }
242
- // Public methods to set event handlers
243
- setOnOutput(
244
- handler: (
245
- stream: "stdout" | "stderr",
246
- data: string,
247
- command: string
248
- ) => void
249
- ): void {
250
- this.options.onOutput = handler;
251
- }
252
-
253
- setOnCommandComplete(
254
- handler: (
255
- success: boolean,
256
- exitCode: number,
257
- stdout: string,
258
- stderr: string,
259
- command: string,
260
- args: string[]
261
- ) => void
262
- ): void {
263
- this.options.onCommandComplete = handler;
264
- }
265
-
266
- setOnStreamEvent(handler: (event: StreamEvent) => void): void {
267
- this.options.onStreamEvent = handler;
268
- }
269
-
270
- // Public getter methods
271
- getOnOutput():
272
- | ((stream: "stdout" | "stderr", data: string, command: string) => void)
273
- | undefined {
274
- return this.options.onOutput;
275
- }
276
-
277
- getOnCommandComplete():
278
- | ((
279
- success: boolean,
280
- exitCode: number,
281
- stdout: string,
282
- stderr: string,
283
- command: string,
284
- args: string[]
285
- ) => void)
286
- | undefined {
287
- return this.options.onCommandComplete;
288
- }
289
-
290
- getOnStreamEvent(): ((event: StreamEvent) => void) | undefined {
291
- return this.options.onStreamEvent;
292
- }
293
-
294
- async createSession(): Promise<string> {
295
- try {
296
- const response = await this.doFetch(`/api/session/create`, {
297
- headers: {
298
- "Content-Type": "application/json",
299
- },
300
- method: "POST",
301
- });
302
-
303
- if (!response.ok) {
304
- throw new Error(`HTTP error! status: ${response.status}`);
305
- }
306
-
307
- const data: SessionResponse = await response.json();
308
- this.sessionId = data.sessionId;
309
- console.log(`[HTTP Client] Created session: ${this.sessionId}`);
310
- return this.sessionId;
311
- } catch (error) {
312
- console.error("[HTTP Client] Error creating session:", error);
313
- throw error;
314
- }
315
- }
316
-
317
- async listSessions(): Promise<SessionListResponse> {
318
- try {
319
- const response = await this.doFetch(`/api/session/list`, {
320
- headers: {
321
- "Content-Type": "application/json",
322
- },
323
- method: "GET",
324
- });
325
-
326
- if (!response.ok) {
327
- throw new Error(`HTTP error! status: ${response.status}`);
328
- }
329
-
330
- const data: SessionListResponse = await response.json();
331
- console.log(`[HTTP Client] Listed ${data.count} sessions`);
332
- return data;
333
- } catch (error) {
334
- console.error("[HTTP Client] Error listing sessions:", error);
335
- throw error;
336
- }
337
- }
338
255
 
339
256
  async execute(
340
257
  command: string,
341
- args: string[] = [],
342
258
  sessionId?: string
343
259
  ): Promise<ExecuteResponse> {
344
260
  try {
@@ -346,10 +262,9 @@ export class HttpClient {
346
262
 
347
263
  const response = await this.doFetch(`/api/execute`, {
348
264
  body: JSON.stringify({
349
- args,
350
265
  command,
351
266
  sessionId: targetSessionId,
352
- }),
267
+ } as ExecuteRequest),
353
268
  headers: {
354
269
  "Content-Type": "application/json",
355
270
  },
@@ -376,8 +291,7 @@ export class HttpClient {
376
291
  data.exitCode,
377
292
  data.stdout,
378
293
  data.stderr,
379
- data.command,
380
- data.args
294
+ data.command
381
295
  );
382
296
 
383
297
  return data;
@@ -385,29 +299,28 @@ export class HttpClient {
385
299
  console.error("[HTTP Client] Error executing command:", error);
386
300
  this.options.onError?.(
387
301
  error instanceof Error ? error.message : "Unknown error",
388
- command,
389
- args
302
+ command
390
303
  );
391
304
  throw error;
392
305
  }
393
306
  }
394
307
 
395
- async executeStream(
308
+
309
+ async executeCommandStream(
396
310
  command: string,
397
- args: string[] = [],
398
311
  sessionId?: string
399
- ): Promise<void> {
312
+ ): Promise<ReadableStream<Uint8Array>> {
400
313
  try {
401
314
  const targetSessionId = sessionId || this.sessionId;
402
315
 
403
316
  const response = await this.doFetch(`/api/execute/stream`, {
404
317
  body: JSON.stringify({
405
- args,
406
318
  command,
407
319
  sessionId: targetSessionId,
408
320
  }),
409
321
  headers: {
410
322
  "Content-Type": "application/json",
323
+ "Accept": "text/event-stream",
411
324
  },
412
325
  method: "POST",
413
326
  });
@@ -425,95 +338,13 @@ export class HttpClient {
425
338
  throw new Error("No response body for streaming request");
426
339
  }
427
340
 
428
- const reader = response.body.getReader();
429
- const decoder = new TextDecoder();
430
-
431
- try {
432
- while (true) {
433
- const { done, value } = await reader.read();
434
-
435
- if (done) {
436
- break;
437
- }
438
-
439
- const chunk = decoder.decode(value, { stream: true });
440
- const lines = chunk.split("\n");
441
-
442
- for (const line of lines) {
443
- if (line.startsWith("data: ")) {
444
- try {
445
- const eventData = line.slice(6); // Remove 'data: ' prefix
446
- const event: StreamEvent = JSON.parse(eventData);
447
-
448
- console.log(`[HTTP Client] Stream event: ${event.type}`);
449
- this.options.onStreamEvent?.(event);
450
-
451
- switch (event.type) {
452
- case "command_start":
453
- console.log(
454
- `[HTTP Client] Command started: ${
455
- event.command
456
- } ${event.args?.join(" ")}`
457
- );
458
- this.options.onCommandStart?.(
459
- event.command!,
460
- event.args || []
461
- );
462
- break;
463
-
464
- case "output":
465
- console.log(`[${event.stream}] ${event.data}`);
466
- this.options.onOutput?.(
467
- event.stream!,
468
- event.data!,
469
- event.command!
470
- );
471
- break;
472
-
473
- case "command_complete":
474
- console.log(
475
- `[HTTP Client] Command completed: ${event.command}, Success: ${event.success}, Exit code: ${event.exitCode}`
476
- );
477
- this.options.onCommandComplete?.(
478
- event.success!,
479
- event.exitCode!,
480
- event.stdout!,
481
- event.stderr!,
482
- event.command!,
483
- event.args || []
484
- );
485
- break;
486
-
487
- case "error":
488
- console.error(
489
- `[HTTP Client] Command error: ${event.error}`
490
- );
491
- this.options.onError?.(
492
- event.error!,
493
- event.command,
494
- event.args
495
- );
496
- break;
497
- }
498
- } catch (parseError) {
499
- console.warn(
500
- "[HTTP Client] Failed to parse stream event:",
501
- parseError
502
- );
503
- }
504
- }
505
- }
506
- }
507
- } finally {
508
- reader.releaseLock();
509
- }
510
- } catch (error) {
511
- console.error("[HTTP Client] Error in streaming execution:", error);
512
- this.options.onError?.(
513
- error instanceof Error ? error.message : "Unknown error",
514
- command,
515
- args
341
+ console.log(
342
+ `[HTTP Client] Started command stream: ${command}`
516
343
  );
344
+
345
+ return response.body;
346
+ } catch (error) {
347
+ console.error("[HTTP Client] Error in command stream:", error);
517
348
  throw error;
518
349
  }
519
350
  }
@@ -533,7 +364,7 @@ export class HttpClient {
533
364
  repoUrl,
534
365
  sessionId: targetSessionId,
535
366
  targetDir,
536
- }),
367
+ } as GitCheckoutRequest),
537
368
  headers: {
538
369
  "Content-Type": "application/json",
539
370
  },
@@ -561,22 +392,21 @@ export class HttpClient {
561
392
  }
562
393
  }
563
394
 
564
- async gitCheckoutStream(
565
- repoUrl: string,
566
- branch: string = "main",
567
- targetDir?: string,
395
+
396
+ async mkdir(
397
+ path: string,
398
+ recursive: boolean = false,
568
399
  sessionId?: string
569
- ): Promise<void> {
400
+ ): Promise<MkdirResponse> {
570
401
  try {
571
402
  const targetSessionId = sessionId || this.sessionId;
572
403
 
573
- const response = await this.doFetch(`/api/git/checkout/stream`, {
404
+ const response = await this.doFetch(`/api/mkdir`, {
574
405
  body: JSON.stringify({
575
- branch,
576
- repoUrl,
406
+ path,
407
+ recursive,
577
408
  sessionId: targetSessionId,
578
- targetDir,
579
- }),
409
+ } as MkdirRequest),
580
410
  headers: {
581
411
  "Content-Type": "application/json",
582
412
  },
@@ -592,119 +422,35 @@ export class HttpClient {
592
422
  );
593
423
  }
594
424
 
595
- if (!response.body) {
596
- throw new Error("No response body for streaming request");
597
- }
425
+ const data: MkdirResponse = await response.json();
426
+ console.log(
427
+ `[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}`
428
+ );
598
429
 
599
- const reader = response.body.getReader();
600
- const decoder = new TextDecoder();
601
-
602
- try {
603
- while (true) {
604
- const { done, value } = await reader.read();
605
-
606
- if (done) {
607
- break;
608
- }
609
-
610
- const chunk = decoder.decode(value, { stream: true });
611
- const lines = chunk.split("\n");
612
-
613
- for (const line of lines) {
614
- if (line.startsWith("data: ")) {
615
- try {
616
- const eventData = line.slice(6); // Remove 'data: ' prefix
617
- const event: StreamEvent = JSON.parse(eventData);
618
-
619
- console.log(
620
- `[HTTP Client] Git checkout stream event: ${event.type}`
621
- );
622
- this.options.onStreamEvent?.(event);
623
-
624
- switch (event.type) {
625
- case "command_start":
626
- console.log(
627
- `[HTTP Client] Git checkout started: ${
628
- event.command
629
- } ${event.args?.join(" ")}`
630
- );
631
- this.options.onCommandStart?.(
632
- event.command!,
633
- event.args || []
634
- );
635
- break;
636
-
637
- case "output":
638
- console.log(`[${event.stream}] ${event.data}`);
639
- this.options.onOutput?.(
640
- event.stream!,
641
- event.data!,
642
- event.command!
643
- );
644
- break;
645
-
646
- case "command_complete":
647
- console.log(
648
- `[HTTP Client] Git checkout completed: ${event.command}, Success: ${event.success}, Exit code: ${event.exitCode}`
649
- );
650
- this.options.onCommandComplete?.(
651
- event.success!,
652
- event.exitCode!,
653
- event.stdout!,
654
- event.stderr!,
655
- event.command!,
656
- event.args || []
657
- );
658
- break;
659
-
660
- case "error":
661
- console.error(
662
- `[HTTP Client] Git checkout error: ${event.error}`
663
- );
664
- this.options.onError?.(
665
- event.error!,
666
- event.command,
667
- event.args
668
- );
669
- break;
670
- }
671
- } catch (parseError) {
672
- console.warn(
673
- "[HTTP Client] Failed to parse git checkout stream event:",
674
- parseError
675
- );
676
- }
677
- }
678
- }
679
- }
680
- } finally {
681
- reader.releaseLock();
682
- }
430
+ return data;
683
431
  } catch (error) {
684
- console.error("[HTTP Client] Error in streaming git checkout:", error);
685
- this.options.onError?.(
686
- error instanceof Error ? error.message : "Unknown error",
687
- "git clone",
688
- [branch, repoUrl, targetDir || ""]
689
- );
432
+ console.error("[HTTP Client] Error creating directory:", error);
690
433
  throw error;
691
434
  }
692
435
  }
693
436
 
694
- async mkdir(
437
+
438
+ async writeFile(
695
439
  path: string,
696
- recursive: boolean = false,
440
+ content: string,
441
+ encoding: string = "utf-8",
697
442
  sessionId?: string
698
- ): Promise<MkdirResponse> {
443
+ ): Promise<WriteFileResponse> {
699
444
  try {
700
445
  const targetSessionId = sessionId || this.sessionId;
701
446
 
702
- const response = await this.doFetch(`/api/mkdir`, {
447
+ const response = await this.doFetch(`/api/write`, {
703
448
  body: JSON.stringify({
449
+ content,
450
+ encoding,
704
451
  path,
705
- recursive,
706
452
  sessionId: targetSessionId,
707
- }),
453
+ } as WriteFileRequest),
708
454
  headers: {
709
455
  "Content-Type": "application/json",
710
456
  },
@@ -720,32 +466,33 @@ export class HttpClient {
720
466
  );
721
467
  }
722
468
 
723
- const data: MkdirResponse = await response.json();
469
+ const data: WriteFileResponse = await response.json();
724
470
  console.log(
725
- `[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}`
471
+ `[HTTP Client] File written: ${path}, Success: ${data.success}`
726
472
  );
727
473
 
728
474
  return data;
729
475
  } catch (error) {
730
- console.error("[HTTP Client] Error creating directory:", error);
476
+ console.error("[HTTP Client] Error writing file:", error);
731
477
  throw error;
732
478
  }
733
479
  }
734
480
 
735
- async mkdirStream(
481
+
482
+ async readFile(
736
483
  path: string,
737
- recursive: boolean = false,
484
+ encoding: string = "utf-8",
738
485
  sessionId?: string
739
- ): Promise<void> {
486
+ ): Promise<ReadFileResponse> {
740
487
  try {
741
488
  const targetSessionId = sessionId || this.sessionId;
742
489
 
743
- const response = await this.doFetch(`/api/mkdir/stream`, {
490
+ const response = await this.doFetch(`/api/read`, {
744
491
  body: JSON.stringify({
492
+ encoding,
745
493
  path,
746
- recursive,
747
494
  sessionId: targetSessionId,
748
- }),
495
+ } as ReadFileRequest),
749
496
  headers: {
750
497
  "Content-Type": "application/json",
751
498
  },
@@ -761,117 +508,31 @@ export class HttpClient {
761
508
  );
762
509
  }
763
510
 
764
- if (!response.body) {
765
- throw new Error("No response body for streaming request");
766
- }
511
+ const data: ReadFileResponse = await response.json();
512
+ console.log(
513
+ `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
514
+ );
767
515
 
768
- const reader = response.body.getReader();
769
- const decoder = new TextDecoder();
770
-
771
- try {
772
- while (true) {
773
- const { done, value } = await reader.read();
774
-
775
- if (done) {
776
- break;
777
- }
778
-
779
- const chunk = decoder.decode(value, { stream: true });
780
- const lines = chunk.split("\n");
781
-
782
- for (const line of lines) {
783
- if (line.startsWith("data: ")) {
784
- try {
785
- const eventData = line.slice(6); // Remove 'data: ' prefix
786
- const event: StreamEvent = JSON.parse(eventData);
787
-
788
- console.log(`[HTTP Client] Mkdir stream event: ${event.type}`);
789
- this.options.onStreamEvent?.(event);
790
-
791
- switch (event.type) {
792
- case "command_start":
793
- console.log(
794
- `[HTTP Client] Mkdir started: ${
795
- event.command
796
- } ${event.args?.join(" ")}`
797
- );
798
- this.options.onCommandStart?.(
799
- event.command!,
800
- event.args || []
801
- );
802
- break;
803
-
804
- case "output":
805
- console.log(`[${event.stream}] ${event.data}`);
806
- this.options.onOutput?.(
807
- event.stream!,
808
- event.data!,
809
- event.command!
810
- );
811
- break;
812
-
813
- case "command_complete":
814
- console.log(
815
- `[HTTP Client] Mkdir completed: ${event.command}, Success: ${event.success}, Exit code: ${event.exitCode}`
816
- );
817
- this.options.onCommandComplete?.(
818
- event.success!,
819
- event.exitCode!,
820
- event.stdout!,
821
- event.stderr!,
822
- event.command!,
823
- event.args || []
824
- );
825
- break;
826
-
827
- case "error":
828
- console.error(`[HTTP Client] Mkdir error: ${event.error}`);
829
- this.options.onError?.(
830
- event.error!,
831
- event.command,
832
- event.args
833
- );
834
- break;
835
- }
836
- } catch (parseError) {
837
- console.warn(
838
- "[HTTP Client] Failed to parse mkdir stream event:",
839
- parseError
840
- );
841
- }
842
- }
843
- }
844
- }
845
- } finally {
846
- reader.releaseLock();
847
- }
516
+ return data;
848
517
  } catch (error) {
849
- console.error("[HTTP Client] Error in streaming mkdir:", error);
850
- this.options.onError?.(
851
- error instanceof Error ? error.message : "Unknown error",
852
- "mkdir",
853
- recursive ? ["-p", path] : [path]
854
- );
518
+ console.error("[HTTP Client] Error reading file:", error);
855
519
  throw error;
856
520
  }
857
521
  }
858
522
 
859
- async writeFile(
523
+
524
+ async deleteFile(
860
525
  path: string,
861
- content: string,
862
- encoding: string = "utf-8",
863
526
  sessionId?: string
864
- ): Promise<WriteFileResponse> {
527
+ ): Promise<DeleteFileResponse> {
865
528
  try {
866
529
  const targetSessionId = sessionId || this.sessionId;
867
530
 
868
- const response = await this.doFetch(`/api/write`, {
531
+ const response = await this.doFetch(`/api/delete`, {
869
532
  body: JSON.stringify({
870
- content,
871
- encoding,
872
533
  path,
873
534
  sessionId: targetSessionId,
874
- }),
535
+ } as DeleteFileRequest),
875
536
  headers: {
876
537
  "Content-Type": "application/json",
877
538
  },
@@ -887,34 +548,33 @@ export class HttpClient {
887
548
  );
888
549
  }
889
550
 
890
- const data: WriteFileResponse = await response.json();
551
+ const data: DeleteFileResponse = await response.json();
891
552
  console.log(
892
- `[HTTP Client] File written: ${path}, Success: ${data.success}`
553
+ `[HTTP Client] File deleted: ${path}, Success: ${data.success}`
893
554
  );
894
555
 
895
556
  return data;
896
557
  } catch (error) {
897
- console.error("[HTTP Client] Error writing file:", error);
558
+ console.error("[HTTP Client] Error deleting file:", error);
898
559
  throw error;
899
560
  }
900
561
  }
901
562
 
902
- async writeFileStream(
903
- path: string,
904
- content: string,
905
- encoding: string = "utf-8",
563
+
564
+ async renameFile(
565
+ oldPath: string,
566
+ newPath: string,
906
567
  sessionId?: string
907
- ): Promise<void> {
568
+ ): Promise<RenameFileResponse> {
908
569
  try {
909
570
  const targetSessionId = sessionId || this.sessionId;
910
571
 
911
- const response = await this.doFetch(`/api/write/stream`, {
572
+ const response = await this.doFetch(`/api/rename`, {
912
573
  body: JSON.stringify({
913
- content,
914
- encoding,
915
- path,
574
+ newPath,
575
+ oldPath,
916
576
  sessionId: targetSessionId,
917
- }),
577
+ } as RenameFileRequest),
918
578
  headers: {
919
579
  "Content-Type": "application/json",
920
580
  },
@@ -930,114 +590,33 @@ export class HttpClient {
930
590
  );
931
591
  }
932
592
 
933
- if (!response.body) {
934
- throw new Error("No response body for streaming request");
935
- }
593
+ const data: RenameFileResponse = await response.json();
594
+ console.log(
595
+ `[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
596
+ );
936
597
 
937
- const reader = response.body.getReader();
938
- const decoder = new TextDecoder();
939
-
940
- try {
941
- while (true) {
942
- const { done, value } = await reader.read();
943
-
944
- if (done) {
945
- break;
946
- }
947
-
948
- const chunk = decoder.decode(value, { stream: true });
949
- const lines = chunk.split("\n");
950
-
951
- for (const line of lines) {
952
- if (line.startsWith("data: ")) {
953
- try {
954
- const eventData = line.slice(6); // Remove 'data: ' prefix
955
- const event: StreamEvent = JSON.parse(eventData);
956
-
957
- console.log(
958
- `[HTTP Client] Write file stream event: ${event.type}`
959
- );
960
- this.options.onStreamEvent?.(event);
961
-
962
- switch (event.type) {
963
- case "command_start":
964
- console.log(
965
- `[HTTP Client] Write file started: ${event.path}`
966
- );
967
- this.options.onCommandStart?.("write", [
968
- path,
969
- content,
970
- encoding,
971
- ]);
972
- break;
973
-
974
- case "output":
975
- console.log(`[output] ${event.message}`);
976
- this.options.onOutput?.("stdout", event.message!, "write");
977
- break;
978
-
979
- case "command_complete":
980
- console.log(
981
- `[HTTP Client] Write file completed: ${event.path}, Success: ${event.success}`
982
- );
983
- this.options.onCommandComplete?.(
984
- event.success!,
985
- 0,
986
- "",
987
- "",
988
- "write",
989
- [path, content, encoding]
990
- );
991
- break;
992
-
993
- case "error":
994
- console.error(
995
- `[HTTP Client] Write file error: ${event.error}`
996
- );
997
- this.options.onError?.(event.error!, "write", [
998
- path,
999
- content,
1000
- encoding,
1001
- ]);
1002
- break;
1003
- }
1004
- } catch (parseError) {
1005
- console.warn(
1006
- "[HTTP Client] Failed to parse write file stream event:",
1007
- parseError
1008
- );
1009
- }
1010
- }
1011
- }
1012
- }
1013
- } finally {
1014
- reader.releaseLock();
1015
- }
598
+ return data;
1016
599
  } catch (error) {
1017
- console.error("[HTTP Client] Error in streaming write file:", error);
1018
- this.options.onError?.(
1019
- error instanceof Error ? error.message : "Unknown error",
1020
- "write",
1021
- [path, content, encoding]
1022
- );
600
+ console.error("[HTTP Client] Error renaming file:", error);
1023
601
  throw error;
1024
602
  }
1025
603
  }
1026
604
 
1027
- async readFile(
1028
- path: string,
1029
- encoding: string = "utf-8",
605
+
606
+ async moveFile(
607
+ sourcePath: string,
608
+ destinationPath: string,
1030
609
  sessionId?: string
1031
- ): Promise<ReadFileResponse> {
610
+ ): Promise<MoveFileResponse> {
1032
611
  try {
1033
612
  const targetSessionId = sessionId || this.sessionId;
1034
613
 
1035
- const response = await this.doFetch(`/api/read`, {
614
+ const response = await this.doFetch(`/api/move`, {
1036
615
  body: JSON.stringify({
1037
- encoding,
1038
- path,
616
+ destinationPath,
1039
617
  sessionId: targetSessionId,
1040
- }),
618
+ sourcePath,
619
+ } as MoveFileRequest),
1041
620
  headers: {
1042
621
  "Content-Type": "application/json",
1043
622
  },
@@ -1053,31 +632,25 @@ export class HttpClient {
1053
632
  );
1054
633
  }
1055
634
 
1056
- const data: ReadFileResponse = await response.json();
635
+ const data: MoveFileResponse = await response.json();
1057
636
  console.log(
1058
- `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
637
+ `[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}`
1059
638
  );
1060
639
 
1061
640
  return data;
1062
641
  } catch (error) {
1063
- console.error("[HTTP Client] Error reading file:", error);
642
+ console.error("[HTTP Client] Error moving file:", error);
1064
643
  throw error;
1065
644
  }
1066
645
  }
1067
646
 
1068
- async readFileStream(
1069
- path: string,
1070
- encoding: string = "utf-8",
1071
- sessionId?: string
1072
- ): Promise<void> {
1073
- try {
1074
- const targetSessionId = sessionId || this.sessionId;
1075
647
 
1076
- const response = await this.doFetch(`/api/read/stream`, {
648
+ async exposePort(port: number, name?: string): Promise<ExposePortResponse> {
649
+ try {
650
+ const response = await this.doFetch(`/api/expose-port`, {
1077
651
  body: JSON.stringify({
1078
- encoding,
1079
- path,
1080
- sessionId: targetSessionId,
652
+ port,
653
+ name,
1081
654
  }),
1082
655
  headers: {
1083
656
  "Content-Type": "application/json",
@@ -1089,115 +662,34 @@ export class HttpClient {
1089
662
  const errorData = (await response.json().catch(() => ({}))) as {
1090
663
  error?: string;
1091
664
  };
665
+ console.log(errorData);
1092
666
  throw new Error(
1093
667
  errorData.error || `HTTP error! status: ${response.status}`
1094
668
  );
1095
669
  }
1096
670
 
1097
- if (!response.body) {
1098
- throw new Error("No response body for streaming request");
1099
- }
671
+ const data: ExposePortResponse = await response.json();
672
+ console.log(
673
+ `[HTTP Client] Port exposed: ${port}${name ? ` (${name})` : ""}, Success: ${data.success}`
674
+ );
1100
675
 
1101
- const reader = response.body.getReader();
1102
- const decoder = new TextDecoder();
1103
-
1104
- try {
1105
- while (true) {
1106
- const { done, value } = await reader.read();
1107
-
1108
- if (done) {
1109
- break;
1110
- }
1111
-
1112
- const chunk = decoder.decode(value, { stream: true });
1113
- const lines = chunk.split("\n");
1114
-
1115
- for (const line of lines) {
1116
- if (line.startsWith("data: ")) {
1117
- try {
1118
- const eventData = line.slice(6); // Remove 'data: ' prefix
1119
- const event: StreamEvent = JSON.parse(eventData);
1120
-
1121
- console.log(
1122
- `[HTTP Client] Read file stream event: ${event.type}`
1123
- );
1124
- this.options.onStreamEvent?.(event);
1125
-
1126
- switch (event.type) {
1127
- case "command_start":
1128
- console.log(
1129
- `[HTTP Client] Read file started: ${event.path}`
1130
- );
1131
- this.options.onCommandStart?.("read", [path, encoding]);
1132
- break;
1133
-
1134
- case "command_complete":
1135
- console.log(
1136
- `[HTTP Client] Read file completed: ${
1137
- event.path
1138
- }, Success: ${event.success}, Content length: ${
1139
- event.content?.length || 0
1140
- }`
1141
- );
1142
- this.options.onCommandComplete?.(
1143
- event.success!,
1144
- 0,
1145
- event.content || "",
1146
- "",
1147
- "read",
1148
- [path, encoding]
1149
- );
1150
- break;
1151
-
1152
- case "error":
1153
- console.error(
1154
- `[HTTP Client] Read file error: ${event.error}`
1155
- );
1156
- this.options.onError?.(event.error!, "read", [
1157
- path,
1158
- encoding,
1159
- ]);
1160
- break;
1161
- }
1162
- } catch (parseError) {
1163
- console.warn(
1164
- "[HTTP Client] Failed to parse read file stream event:",
1165
- parseError
1166
- );
1167
- }
1168
- }
1169
- }
1170
- }
1171
- } finally {
1172
- reader.releaseLock();
1173
- }
676
+ return data;
1174
677
  } catch (error) {
1175
- console.error("[HTTP Client] Error in streaming read file:", error);
1176
- this.options.onError?.(
1177
- error instanceof Error ? error.message : "Unknown error",
1178
- "read",
1179
- [path, encoding]
1180
- );
678
+ console.error("[HTTP Client] Error exposing port:", error);
1181
679
  throw error;
1182
680
  }
1183
681
  }
1184
682
 
1185
- async deleteFile(
1186
- path: string,
1187
- sessionId?: string
1188
- ): Promise<DeleteFileResponse> {
683
+ async unexposePort(port: number): Promise<UnexposePortResponse> {
1189
684
  try {
1190
- const targetSessionId = sessionId || this.sessionId;
1191
-
1192
- const response = await this.doFetch(`/api/delete`, {
685
+ const response = await this.doFetch(`/api/unexpose-port`, {
1193
686
  body: JSON.stringify({
1194
- path,
1195
- sessionId: targetSessionId,
687
+ port,
1196
688
  }),
1197
689
  headers: {
1198
690
  "Content-Type": "application/json",
1199
691
  },
1200
- method: "POST",
692
+ method: "DELETE",
1201
693
  });
1202
694
 
1203
695
  if (!response.ok) {
@@ -1209,31 +701,25 @@ export class HttpClient {
1209
701
  );
1210
702
  }
1211
703
 
1212
- const data: DeleteFileResponse = await response.json();
704
+ const data: UnexposePortResponse = await response.json();
1213
705
  console.log(
1214
- `[HTTP Client] File deleted: ${path}, Success: ${data.success}`
706
+ `[HTTP Client] Port unexposed: ${port}, Success: ${data.success}`
1215
707
  );
1216
708
 
1217
709
  return data;
1218
710
  } catch (error) {
1219
- console.error("[HTTP Client] Error deleting file:", error);
711
+ console.error("[HTTP Client] Error unexposing port:", error);
1220
712
  throw error;
1221
713
  }
1222
714
  }
1223
715
 
1224
- async deleteFileStream(path: string, sessionId?: string): Promise<void> {
716
+ async getExposedPorts(): Promise<GetExposedPortsResponse> {
1225
717
  try {
1226
- const targetSessionId = sessionId || this.sessionId;
1227
-
1228
- const response = await this.doFetch(`/api/delete/stream`, {
1229
- body: JSON.stringify({
1230
- path,
1231
- sessionId: targetSessionId,
1232
- }),
718
+ const response = await this.doFetch(`/api/exposed-ports`, {
1233
719
  headers: {
1234
720
  "Content-Type": "application/json",
1235
721
  },
1236
- method: "POST",
722
+ method: "GET",
1237
723
  });
1238
724
 
1239
725
  if (!response.ok) {
@@ -1245,101 +731,100 @@ export class HttpClient {
1245
731
  );
1246
732
  }
1247
733
 
1248
- if (!response.body) {
1249
- throw new Error("No response body for streaming request");
1250
- }
734
+ const data: GetExposedPortsResponse = await response.json();
735
+ console.log(
736
+ `[HTTP Client] Got ${data.count} exposed ports`
737
+ );
738
+
739
+ return data;
740
+ } catch (error) {
741
+ console.error("[HTTP Client] Error getting exposed ports:", error);
742
+ throw error;
743
+ }
744
+ }
745
+
746
+ async ping(): Promise<string> {
747
+ try {
748
+ const response = await this.doFetch(`/api/ping`, {
749
+ headers: {
750
+ "Content-Type": "application/json",
751
+ },
752
+ method: "GET",
753
+ });
1251
754
 
1252
- const reader = response.body.getReader();
1253
- const decoder = new TextDecoder();
1254
-
1255
- try {
1256
- while (true) {
1257
- const { done, value } = await reader.read();
1258
-
1259
- if (done) {
1260
- break;
1261
- }
1262
-
1263
- const chunk = decoder.decode(value, { stream: true });
1264
- const lines = chunk.split("\n");
1265
-
1266
- for (const line of lines) {
1267
- if (line.startsWith("data: ")) {
1268
- try {
1269
- const eventData = line.slice(6); // Remove 'data: ' prefix
1270
- const event: StreamEvent = JSON.parse(eventData);
1271
-
1272
- console.log(
1273
- `[HTTP Client] Delete file stream event: ${event.type}`
1274
- );
1275
- this.options.onStreamEvent?.(event);
1276
-
1277
- switch (event.type) {
1278
- case "command_start":
1279
- console.log(
1280
- `[HTTP Client] Delete file started: ${event.path}`
1281
- );
1282
- this.options.onCommandStart?.("delete", [path]);
1283
- break;
1284
-
1285
- case "command_complete":
1286
- console.log(
1287
- `[HTTP Client] Delete file completed: ${event.path}, Success: ${event.success}`
1288
- );
1289
- this.options.onCommandComplete?.(
1290
- event.success!,
1291
- 0,
1292
- "",
1293
- "",
1294
- "delete",
1295
- [path]
1296
- );
1297
- break;
1298
-
1299
- case "error":
1300
- console.error(
1301
- `[HTTP Client] Delete file error: ${event.error}`
1302
- );
1303
- this.options.onError?.(event.error!, "delete", [path]);
1304
- break;
1305
- }
1306
- } catch (parseError) {
1307
- console.warn(
1308
- "[HTTP Client] Failed to parse delete file stream event:",
1309
- parseError
1310
- );
1311
- }
1312
- }
1313
- }
1314
- }
1315
- } finally {
1316
- reader.releaseLock();
755
+ if (!response.ok) {
756
+ throw new Error(`HTTP error! status: ${response.status}`);
1317
757
  }
758
+
759
+ const data: PingResponse = await response.json();
760
+ console.log(`[HTTP Client] Ping response: ${data.message}`);
761
+ return data.timestamp;
1318
762
  } catch (error) {
1319
- console.error("[HTTP Client] Error in streaming delete file:", error);
1320
- this.options.onError?.(
1321
- error instanceof Error ? error.message : "Unknown error",
1322
- "delete",
1323
- [path]
763
+ console.error("[HTTP Client] Error pinging server:", error);
764
+ throw error;
765
+ }
766
+ }
767
+
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}`
1324
784
  );
785
+ return data.availableCommands;
786
+ } catch (error) {
787
+ console.error("[HTTP Client] Error getting commands:", error);
1325
788
  throw error;
1326
789
  }
1327
790
  }
1328
791
 
1329
- async renameFile(
1330
- oldPath: string,
1331
- newPath: string,
1332
- sessionId?: string
1333
- ): Promise<RenameFileResponse> {
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
+
804
+ // Process management methods
805
+ async startProcess(
806
+ command: string,
807
+ options?: {
808
+ processId?: string;
809
+ sessionId?: string;
810
+ timeout?: number;
811
+ env?: Record<string, string>;
812
+ cwd?: string;
813
+ encoding?: string;
814
+ autoCleanup?: boolean;
815
+ }
816
+ ): Promise<StartProcessResponse> {
1334
817
  try {
1335
- const targetSessionId = sessionId || this.sessionId;
818
+ const targetSessionId = options?.sessionId || this.sessionId;
1336
819
 
1337
- const response = await this.doFetch(`/api/rename`, {
820
+ const response = await this.doFetch("/api/process/start", {
1338
821
  body: JSON.stringify({
1339
- newPath,
1340
- oldPath,
1341
- sessionId: targetSessionId,
1342
- }),
822
+ command,
823
+ options: {
824
+ ...options,
825
+ sessionId: targetSessionId,
826
+ },
827
+ } as StartProcessRequest),
1343
828
  headers: {
1344
829
  "Content-Type": "application/json",
1345
830
  },
@@ -1355,36 +840,25 @@ export class HttpClient {
1355
840
  );
1356
841
  }
1357
842
 
1358
- const data: RenameFileResponse = await response.json();
843
+ const data: StartProcessResponse = await response.json();
1359
844
  console.log(
1360
- `[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
845
+ `[HTTP Client] Process started: ${command}, ID: ${data.process.id}`
1361
846
  );
1362
847
 
1363
848
  return data;
1364
849
  } catch (error) {
1365
- console.error("[HTTP Client] Error renaming file:", error);
850
+ console.error("[HTTP Client] Error starting process:", error);
1366
851
  throw error;
1367
852
  }
1368
853
  }
1369
854
 
1370
- async renameFileStream(
1371
- oldPath: string,
1372
- newPath: string,
1373
- sessionId?: string
1374
- ): Promise<void> {
855
+ async listProcesses(): Promise<ListProcessesResponse> {
1375
856
  try {
1376
- const targetSessionId = sessionId || this.sessionId;
1377
-
1378
- const response = await this.doFetch(`/api/rename/stream`, {
1379
- body: JSON.stringify({
1380
- newPath,
1381
- oldPath,
1382
- sessionId: targetSessionId,
1383
- }),
857
+ const response = await this.doFetch("/api/process/list", {
1384
858
  headers: {
1385
859
  "Content-Type": "application/json",
1386
860
  },
1387
- method: "POST",
861
+ method: "GET",
1388
862
  });
1389
863
 
1390
864
  if (!response.ok) {
@@ -1396,108 +870,25 @@ export class HttpClient {
1396
870
  );
1397
871
  }
1398
872
 
1399
- if (!response.body) {
1400
- throw new Error("No response body for streaming request");
1401
- }
873
+ const data: ListProcessesResponse = await response.json();
874
+ console.log(
875
+ `[HTTP Client] Listed ${data.processes.length} processes`
876
+ );
1402
877
 
1403
- const reader = response.body.getReader();
1404
- const decoder = new TextDecoder();
1405
-
1406
- try {
1407
- while (true) {
1408
- const { done, value } = await reader.read();
1409
-
1410
- if (done) {
1411
- break;
1412
- }
1413
-
1414
- const chunk = decoder.decode(value, { stream: true });
1415
- const lines = chunk.split("\n");
1416
-
1417
- for (const line of lines) {
1418
- if (line.startsWith("data: ")) {
1419
- try {
1420
- const eventData = line.slice(6); // Remove 'data: ' prefix
1421
- const event: StreamEvent = JSON.parse(eventData);
1422
-
1423
- console.log(
1424
- `[HTTP Client] Rename file stream event: ${event.type}`
1425
- );
1426
- this.options.onStreamEvent?.(event);
1427
-
1428
- switch (event.type) {
1429
- case "command_start":
1430
- console.log(
1431
- `[HTTP Client] Rename file started: ${event.oldPath} -> ${event.newPath}`
1432
- );
1433
- this.options.onCommandStart?.("rename", [oldPath, newPath]);
1434
- break;
1435
-
1436
- case "command_complete":
1437
- console.log(
1438
- `[HTTP Client] Rename file completed: ${event.oldPath} -> ${event.newPath}, Success: ${event.success}`
1439
- );
1440
- this.options.onCommandComplete?.(
1441
- event.success!,
1442
- 0,
1443
- "",
1444
- "",
1445
- "rename",
1446
- [oldPath, newPath]
1447
- );
1448
- break;
1449
-
1450
- case "error":
1451
- console.error(
1452
- `[HTTP Client] Rename file error: ${event.error}`
1453
- );
1454
- this.options.onError?.(event.error!, "rename", [
1455
- oldPath,
1456
- newPath,
1457
- ]);
1458
- break;
1459
- }
1460
- } catch (parseError) {
1461
- console.warn(
1462
- "[HTTP Client] Failed to parse rename file stream event:",
1463
- parseError
1464
- );
1465
- }
1466
- }
1467
- }
1468
- }
1469
- } finally {
1470
- reader.releaseLock();
1471
- }
878
+ return data;
1472
879
  } catch (error) {
1473
- console.error("[HTTP Client] Error in streaming rename file:", error);
1474
- this.options.onError?.(
1475
- error instanceof Error ? error.message : "Unknown error",
1476
- "rename",
1477
- [oldPath, newPath]
1478
- );
880
+ console.error("[HTTP Client] Error listing processes:", error);
1479
881
  throw error;
1480
882
  }
1481
883
  }
1482
884
 
1483
- async moveFile(
1484
- sourcePath: string,
1485
- destinationPath: string,
1486
- sessionId?: string
1487
- ): Promise<MoveFileResponse> {
885
+ async getProcess(processId: string): Promise<GetProcessResponse> {
1488
886
  try {
1489
- const targetSessionId = sessionId || this.sessionId;
1490
-
1491
- const response = await this.doFetch(`/api/move`, {
1492
- body: JSON.stringify({
1493
- destinationPath,
1494
- sessionId: targetSessionId,
1495
- sourcePath,
1496
- }),
887
+ const response = await this.doFetch(`/api/process/${processId}`, {
1497
888
  headers: {
1498
889
  "Content-Type": "application/json",
1499
890
  },
1500
- method: "POST",
891
+ method: "GET",
1501
892
  });
1502
893
 
1503
894
  if (!response.ok) {
@@ -1509,36 +900,25 @@ export class HttpClient {
1509
900
  );
1510
901
  }
1511
902
 
1512
- const data: MoveFileResponse = await response.json();
903
+ const data: GetProcessResponse = await response.json();
1513
904
  console.log(
1514
- `[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}`
905
+ `[HTTP Client] Got process ${processId}: ${data.process?.status || 'not found'}`
1515
906
  );
1516
907
 
1517
908
  return data;
1518
909
  } catch (error) {
1519
- console.error("[HTTP Client] Error moving file:", error);
910
+ console.error("[HTTP Client] Error getting process:", error);
1520
911
  throw error;
1521
912
  }
1522
913
  }
1523
914
 
1524
- async moveFileStream(
1525
- sourcePath: string,
1526
- destinationPath: string,
1527
- sessionId?: string
1528
- ): Promise<void> {
915
+ async killProcess(processId: string): Promise<{ success: boolean; message: string }> {
1529
916
  try {
1530
- const targetSessionId = sessionId || this.sessionId;
1531
-
1532
- const response = await this.doFetch(`/api/move/stream`, {
1533
- body: JSON.stringify({
1534
- destinationPath,
1535
- sessionId: targetSessionId,
1536
- sourcePath,
1537
- }),
917
+ const response = await this.doFetch(`/api/process/${processId}`, {
1538
918
  headers: {
1539
919
  "Content-Type": "application/json",
1540
920
  },
1541
- method: "POST",
921
+ method: "DELETE",
1542
922
  });
1543
923
 
1544
924
  if (!response.ok) {
@@ -1550,118 +930,51 @@ export class HttpClient {
1550
930
  );
1551
931
  }
1552
932
 
1553
- if (!response.body) {
1554
- throw new Error("No response body for streaming request");
1555
- }
933
+ const data = await response.json() as { success: boolean; message: string };
934
+ console.log(
935
+ `[HTTP Client] Killed process ${processId}`
936
+ );
1556
937
 
1557
- const reader = response.body.getReader();
1558
- const decoder = new TextDecoder();
1559
-
1560
- try {
1561
- while (true) {
1562
- const { done, value } = await reader.read();
1563
-
1564
- if (done) {
1565
- break;
1566
- }
1567
-
1568
- const chunk = decoder.decode(value, { stream: true });
1569
- const lines = chunk.split("\n");
1570
-
1571
- for (const line of lines) {
1572
- if (line.startsWith("data: ")) {
1573
- try {
1574
- const eventData = line.slice(6); // Remove 'data: ' prefix
1575
- const event: StreamEvent = JSON.parse(eventData);
1576
-
1577
- console.log(
1578
- `[HTTP Client] Move file stream event: ${event.type}`
1579
- );
1580
- this.options.onStreamEvent?.(event);
1581
-
1582
- switch (event.type) {
1583
- case "command_start":
1584
- console.log(
1585
- `[HTTP Client] Move file started: ${event.sourcePath} -> ${event.destinationPath}`
1586
- );
1587
- this.options.onCommandStart?.("move", [
1588
- sourcePath,
1589
- destinationPath,
1590
- ]);
1591
- break;
1592
-
1593
- case "command_complete":
1594
- console.log(
1595
- `[HTTP Client] Move file completed: ${event.sourcePath} -> ${event.destinationPath}, Success: ${event.success}`
1596
- );
1597
- this.options.onCommandComplete?.(
1598
- event.success!,
1599
- 0,
1600
- "",
1601
- "",
1602
- "move",
1603
- [sourcePath, destinationPath]
1604
- );
1605
- break;
1606
-
1607
- case "error":
1608
- console.error(
1609
- `[HTTP Client] Move file error: ${event.error}`
1610
- );
1611
- this.options.onError?.(event.error!, "move", [
1612
- sourcePath,
1613
- destinationPath,
1614
- ]);
1615
- break;
1616
- }
1617
- } catch (parseError) {
1618
- console.warn(
1619
- "[HTTP Client] Failed to parse move file stream event:",
1620
- parseError
1621
- );
1622
- }
1623
- }
1624
- }
1625
- }
1626
- } finally {
1627
- reader.releaseLock();
1628
- }
938
+ return data;
1629
939
  } catch (error) {
1630
- console.error("[HTTP Client] Error in streaming move file:", error);
1631
- this.options.onError?.(
1632
- error instanceof Error ? error.message : "Unknown error",
1633
- "move",
1634
- [sourcePath, destinationPath]
1635
- );
940
+ console.error("[HTTP Client] Error killing process:", error);
1636
941
  throw error;
1637
942
  }
1638
943
  }
1639
944
 
1640
- async ping(): Promise<string> {
945
+ async killAllProcesses(): Promise<{ success: boolean; killedCount: number; message: string }> {
1641
946
  try {
1642
- const response = await this.doFetch(`/api/ping`, {
947
+ const response = await this.doFetch("/api/process/kill-all", {
1643
948
  headers: {
1644
949
  "Content-Type": "application/json",
1645
950
  },
1646
- method: "GET",
951
+ method: "DELETE",
1647
952
  });
1648
953
 
1649
954
  if (!response.ok) {
1650
- throw new Error(`HTTP error! status: ${response.status}`);
955
+ const errorData = (await response.json().catch(() => ({}))) as {
956
+ error?: string;
957
+ };
958
+ throw new Error(
959
+ errorData.error || `HTTP error! status: ${response.status}`
960
+ );
1651
961
  }
1652
962
 
1653
- const data: PingResponse = await response.json();
1654
- console.log(`[HTTP Client] Ping response: ${data.message}`);
1655
- return data.timestamp;
963
+ const data = await response.json() as { success: boolean; killedCount: number; message: string };
964
+ console.log(
965
+ `[HTTP Client] Killed ${data.killedCount} processes`
966
+ );
967
+
968
+ return data;
1656
969
  } catch (error) {
1657
- console.error("[HTTP Client] Error pinging server:", error);
970
+ console.error("[HTTP Client] Error killing all processes:", error);
1658
971
  throw error;
1659
972
  }
1660
973
  }
1661
974
 
1662
- async getCommands(): Promise<string[]> {
975
+ async getProcessLogs(processId: string): Promise<GetProcessLogsResponse> {
1663
976
  try {
1664
- const response = await fetch(`${this.baseUrl}/api/commands`, {
977
+ const response = await this.doFetch(`/api/process/${processId}/logs`, {
1665
978
  headers: {
1666
979
  "Content-Type": "application/json",
1667
980
  },
@@ -1669,292 +982,57 @@ export class HttpClient {
1669
982
  });
1670
983
 
1671
984
  if (!response.ok) {
1672
- throw new Error(`HTTP error! status: ${response.status}`);
985
+ const errorData = (await response.json().catch(() => ({}))) as {
986
+ error?: string;
987
+ };
988
+ throw new Error(
989
+ errorData.error || `HTTP error! status: ${response.status}`
990
+ );
1673
991
  }
1674
992
 
1675
- const data: CommandsResponse = await response.json();
993
+ const data: GetProcessLogsResponse = await response.json();
1676
994
  console.log(
1677
- `[HTTP Client] Available commands: ${data.availableCommands.length}`
995
+ `[HTTP Client] Got logs for process ${processId}`
1678
996
  );
1679
- return data.availableCommands;
997
+
998
+ return data;
1680
999
  } catch (error) {
1681
- console.error("[HTTP Client] Error getting commands:", error);
1000
+ console.error("[HTTP Client] Error getting process logs:", error);
1682
1001
  throw error;
1683
1002
  }
1684
1003
  }
1685
1004
 
1686
- getSessionId(): string | null {
1687
- return this.sessionId;
1688
- }
1689
-
1690
- setSessionId(sessionId: string): void {
1691
- this.sessionId = sessionId;
1692
- }
1693
-
1694
- clearSession(): void {
1695
- this.sessionId = null;
1696
- }
1697
- }
1698
-
1699
- // Example usage and utility functions
1700
- export function createClient(options?: HttpClientOptions): HttpClient {
1701
- return new HttpClient(options);
1702
- }
1703
-
1704
- // Convenience function for quick command execution
1705
- export async function quickExecute(
1706
- command: string,
1707
- args: string[] = [],
1708
- options?: HttpClientOptions
1709
- ): Promise<ExecuteResponse> {
1710
- const client = createClient(options);
1711
- await client.createSession();
1712
-
1713
- try {
1714
- return await client.execute(command, args);
1715
- } finally {
1716
- client.clearSession();
1717
- }
1718
- }
1719
-
1720
- // Convenience function for quick streaming command execution
1721
- export async function quickExecuteStream(
1722
- command: string,
1723
- args: string[] = [],
1724
- options?: HttpClientOptions
1725
- ): Promise<void> {
1726
- const client = createClient(options);
1727
- await client.createSession();
1728
-
1729
- try {
1730
- await client.executeStream(command, args);
1731
- } finally {
1732
- client.clearSession();
1733
- }
1734
- }
1735
-
1736
- // Convenience function for quick git checkout
1737
- export async function quickGitCheckout(
1738
- repoUrl: string,
1739
- branch: string = "main",
1740
- targetDir?: string,
1741
- options?: HttpClientOptions
1742
- ): Promise<GitCheckoutResponse> {
1743
- const client = createClient(options);
1744
- await client.createSession();
1745
-
1746
- try {
1747
- return await client.gitCheckout(repoUrl, branch, targetDir);
1748
- } finally {
1749
- client.clearSession();
1750
- }
1751
- }
1752
-
1753
- // Convenience function for quick directory creation
1754
- export async function quickMkdir(
1755
- path: string,
1756
- recursive: boolean = false,
1757
- options?: HttpClientOptions
1758
- ): Promise<MkdirResponse> {
1759
- const client = createClient(options);
1760
- await client.createSession();
1761
-
1762
- try {
1763
- return await client.mkdir(path, recursive);
1764
- } finally {
1765
- client.clearSession();
1766
- }
1767
- }
1768
-
1769
- // Convenience function for quick streaming git checkout
1770
- export async function quickGitCheckoutStream(
1771
- repoUrl: string,
1772
- branch: string = "main",
1773
- targetDir?: string,
1774
- options?: HttpClientOptions
1775
- ): Promise<void> {
1776
- const client = createClient(options);
1777
- await client.createSession();
1778
-
1779
- try {
1780
- await client.gitCheckoutStream(repoUrl, branch, targetDir);
1781
- } finally {
1782
- client.clearSession();
1783
- }
1784
- }
1785
-
1786
- // Convenience function for quick streaming directory creation
1787
- export async function quickMkdirStream(
1788
- path: string,
1789
- recursive: boolean = false,
1790
- options?: HttpClientOptions
1791
- ): Promise<void> {
1792
- const client = createClient(options);
1793
- await client.createSession();
1794
-
1795
- try {
1796
- await client.mkdirStream(path, recursive);
1797
- } finally {
1798
- client.clearSession();
1799
- }
1800
- }
1801
-
1802
- // Convenience function for quick file writing
1803
- export async function quickWriteFile(
1804
- path: string,
1805
- content: string,
1806
- encoding: string = "utf-8",
1807
- options?: HttpClientOptions
1808
- ): Promise<WriteFileResponse> {
1809
- const client = createClient(options);
1810
- await client.createSession();
1811
-
1812
- try {
1813
- return await client.writeFile(path, content, encoding);
1814
- } finally {
1815
- client.clearSession();
1816
- }
1817
- }
1818
-
1819
- // Convenience function for quick streaming file writing
1820
- export async function quickWriteFileStream(
1821
- path: string,
1822
- content: string,
1823
- encoding: string = "utf-8",
1824
- options?: HttpClientOptions
1825
- ): Promise<void> {
1826
- const client = createClient(options);
1827
- await client.createSession();
1828
-
1829
- try {
1830
- await client.writeFileStream(path, content, encoding);
1831
- } finally {
1832
- client.clearSession();
1833
- }
1834
- }
1835
-
1836
- // Convenience function for quick file reading
1837
- export async function quickReadFile(
1838
- path: string,
1839
- encoding: string = "utf-8",
1840
- options?: HttpClientOptions
1841
- ): Promise<ReadFileResponse> {
1842
- const client = createClient(options);
1843
- await client.createSession();
1844
-
1845
- try {
1846
- return await client.readFile(path, encoding);
1847
- } finally {
1848
- client.clearSession();
1849
- }
1850
- }
1851
-
1852
- // Convenience function for quick streaming file reading
1853
- export async function quickReadFileStream(
1854
- path: string,
1855
- encoding: string = "utf-8",
1856
- options?: HttpClientOptions
1857
- ): Promise<void> {
1858
- const client = createClient(options);
1859
- await client.createSession();
1860
-
1861
- try {
1862
- await client.readFileStream(path, encoding);
1863
- } finally {
1864
- client.clearSession();
1865
- }
1866
- }
1867
-
1868
- // Convenience function for quick file deletion
1869
- export async function quickDeleteFile(
1870
- path: string,
1871
- options?: HttpClientOptions
1872
- ): Promise<DeleteFileResponse> {
1873
- const client = createClient(options);
1874
- await client.createSession();
1875
-
1876
- try {
1877
- return await client.deleteFile(path);
1878
- } finally {
1879
- client.clearSession();
1880
- }
1881
- }
1882
-
1883
- // Convenience function for quick streaming file deletion
1884
- export async function quickDeleteFileStream(
1885
- path: string,
1886
- options?: HttpClientOptions
1887
- ): Promise<void> {
1888
- const client = createClient(options);
1889
- await client.createSession();
1890
-
1891
- try {
1892
- await client.deleteFileStream(path);
1893
- } finally {
1894
- client.clearSession();
1895
- }
1896
- }
1005
+ async streamProcessLogs(processId: string): Promise<ReadableStream<Uint8Array>> {
1006
+ try {
1007
+ const response = await this.doFetch(`/api/process/${processId}/stream`, {
1008
+ headers: {
1009
+ "Accept": "text/event-stream",
1010
+ "Cache-Control": "no-cache",
1011
+ },
1012
+ method: "GET",
1013
+ });
1897
1014
 
1898
- // Convenience function for quick file renaming
1899
- export async function quickRenameFile(
1900
- oldPath: string,
1901
- newPath: string,
1902
- options?: HttpClientOptions
1903
- ): Promise<RenameFileResponse> {
1904
- const client = createClient(options);
1905
- await client.createSession();
1906
-
1907
- try {
1908
- return await client.renameFile(oldPath, newPath);
1909
- } finally {
1910
- client.clearSession();
1911
- }
1912
- }
1015
+ if (!response.ok) {
1016
+ const errorData = (await response.json().catch(() => ({}))) as {
1017
+ error?: string;
1018
+ };
1019
+ throw new Error(
1020
+ errorData.error || `HTTP error! status: ${response.status}`
1021
+ );
1022
+ }
1913
1023
 
1914
- // Convenience function for quick streaming file renaming
1915
- export async function quickRenameFileStream(
1916
- oldPath: string,
1917
- newPath: string,
1918
- options?: HttpClientOptions
1919
- ): Promise<void> {
1920
- const client = createClient(options);
1921
- await client.createSession();
1922
-
1923
- try {
1924
- await client.renameFileStream(oldPath, newPath);
1925
- } finally {
1926
- client.clearSession();
1927
- }
1928
- }
1024
+ if (!response.body) {
1025
+ throw new Error("No response body for streaming request");
1026
+ }
1929
1027
 
1930
- // Convenience function for quick file moving
1931
- export async function quickMoveFile(
1932
- sourcePath: string,
1933
- destinationPath: string,
1934
- options?: HttpClientOptions
1935
- ): Promise<MoveFileResponse> {
1936
- const client = createClient(options);
1937
- await client.createSession();
1938
-
1939
- try {
1940
- return await client.moveFile(sourcePath, destinationPath);
1941
- } finally {
1942
- client.clearSession();
1943
- }
1944
- }
1028
+ console.log(
1029
+ `[HTTP Client] Started streaming logs for process ${processId}`
1030
+ );
1945
1031
 
1946
- // Convenience function for quick streaming file moving
1947
- export async function quickMoveFileStream(
1948
- sourcePath: string,
1949
- destinationPath: string,
1950
- options?: HttpClientOptions
1951
- ): Promise<void> {
1952
- const client = createClient(options);
1953
- await client.createSession();
1954
-
1955
- try {
1956
- await client.moveFileStream(sourcePath, destinationPath);
1957
- } finally {
1958
- client.clearSession();
1032
+ return response.body;
1033
+ } catch (error) {
1034
+ console.error("[HTTP Client] Error streaming process logs:", error);
1035
+ throw error;
1036
+ }
1959
1037
  }
1960
1038
  }