@cloudflare/sandbox 0.0.0-c5bd973 → 0.0.0-c87db11

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