@cloudflare/sandbox 0.0.0-7bccc85 → 0.0.0-7de28be

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