@cloudflare/sandbox 0.0.0-0ac3cfa → 0.0.0-12bbd12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/Dockerfile +101 -11
- package/README.md +743 -24
- package/container_src/bun.lock +122 -0
- package/container_src/circuit-breaker.ts +121 -0
- package/container_src/handler/exec.ts +340 -0
- package/container_src/handler/file.ts +1064 -0
- package/container_src/handler/git.ts +182 -0
- package/container_src/handler/ports.ts +314 -0
- package/container_src/handler/process.ts +640 -0
- package/container_src/index.ts +401 -2644
- package/container_src/jupyter-server.ts +579 -0
- package/container_src/jupyter-service.ts +461 -0
- package/container_src/jupyter_config.py +48 -0
- package/container_src/mime-processor.ts +255 -0
- package/container_src/package.json +9 -0
- package/container_src/startup.sh +84 -0
- package/container_src/types.ts +117 -0
- package/package.json +5 -9
- package/src/client.ts +397 -1323
- package/src/errors.ts +218 -0
- package/src/index.ts +56 -129
- package/src/interpreter-types.ts +383 -0
- package/src/interpreter.ts +150 -0
- package/src/jupyter-client.ts +349 -0
- package/src/request-handler.ts +144 -0
- package/src/sandbox.ts +788 -0
- package/src/security.ts +113 -0
- package/src/sse-parser.ts +147 -0
- package/src/types.ts +511 -0
- package/tsconfig.json +1 -1
- package/tests/client.example.ts +0 -308
- package/tests/connection-test.ts +0 -81
- package/tests/simple-test.ts +0 -81
- package/tests/test1.ts +0 -281
- package/tests/test2.ts +0 -929
package/src/client.ts
CHANGED
|
@@ -1,36 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExecuteRequest } from "../container_src/types";
|
|
2
2
|
import type { Sandbox } from "./index";
|
|
3
|
+
import type {
|
|
4
|
+
BaseExecOptions,
|
|
5
|
+
DeleteFileResponse,
|
|
6
|
+
ExecuteResponse,
|
|
7
|
+
GetProcessLogsResponse,
|
|
8
|
+
GetProcessResponse,
|
|
9
|
+
GitCheckoutResponse,
|
|
10
|
+
ListFilesResponse,
|
|
11
|
+
ListProcessesResponse,
|
|
12
|
+
MkdirResponse,
|
|
13
|
+
MoveFileResponse,
|
|
14
|
+
ReadFileResponse,
|
|
15
|
+
RenameFileResponse,
|
|
16
|
+
StartProcessRequest,
|
|
17
|
+
StartProcessResponse,
|
|
18
|
+
WriteFileResponse,
|
|
19
|
+
} from "./types";
|
|
3
20
|
|
|
4
|
-
interface ExecuteRequest {
|
|
5
|
-
command: string;
|
|
6
|
-
args?: string[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ExecuteResponse {
|
|
10
|
-
success: boolean;
|
|
11
|
-
stdout: string;
|
|
12
|
-
stderr: string;
|
|
13
|
-
exitCode: number;
|
|
14
|
-
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
|
-
timestamp: string;
|
|
33
|
-
}
|
|
34
21
|
|
|
35
22
|
interface CommandsResponse {
|
|
36
23
|
availableCommands: string[];
|
|
@@ -44,16 +31,6 @@ interface GitCheckoutRequest {
|
|
|
44
31
|
sessionId?: string;
|
|
45
32
|
}
|
|
46
33
|
|
|
47
|
-
export interface GitCheckoutResponse {
|
|
48
|
-
success: boolean;
|
|
49
|
-
stdout: string;
|
|
50
|
-
stderr: string;
|
|
51
|
-
exitCode: number;
|
|
52
|
-
repoUrl: string;
|
|
53
|
-
branch: string;
|
|
54
|
-
targetDir: string;
|
|
55
|
-
timestamp: string;
|
|
56
|
-
}
|
|
57
34
|
|
|
58
35
|
interface MkdirRequest {
|
|
59
36
|
path: string;
|
|
@@ -61,15 +38,6 @@ interface MkdirRequest {
|
|
|
61
38
|
sessionId?: string;
|
|
62
39
|
}
|
|
63
40
|
|
|
64
|
-
export interface MkdirResponse {
|
|
65
|
-
success: boolean;
|
|
66
|
-
stdout: string;
|
|
67
|
-
stderr: string;
|
|
68
|
-
exitCode: number;
|
|
69
|
-
path: string;
|
|
70
|
-
recursive: boolean;
|
|
71
|
-
timestamp: string;
|
|
72
|
-
}
|
|
73
41
|
|
|
74
42
|
interface WriteFileRequest {
|
|
75
43
|
path: string;
|
|
@@ -78,12 +46,6 @@ interface WriteFileRequest {
|
|
|
78
46
|
sessionId?: string;
|
|
79
47
|
}
|
|
80
48
|
|
|
81
|
-
export interface WriteFileResponse {
|
|
82
|
-
success: boolean;
|
|
83
|
-
exitCode: number;
|
|
84
|
-
path: string;
|
|
85
|
-
timestamp: string;
|
|
86
|
-
}
|
|
87
49
|
|
|
88
50
|
interface ReadFileRequest {
|
|
89
51
|
path: string;
|
|
@@ -91,25 +53,12 @@ interface ReadFileRequest {
|
|
|
91
53
|
sessionId?: string;
|
|
92
54
|
}
|
|
93
55
|
|
|
94
|
-
export interface ReadFileResponse {
|
|
95
|
-
success: boolean;
|
|
96
|
-
exitCode: number;
|
|
97
|
-
path: string;
|
|
98
|
-
content: string;
|
|
99
|
-
timestamp: string;
|
|
100
|
-
}
|
|
101
56
|
|
|
102
57
|
interface DeleteFileRequest {
|
|
103
58
|
path: string;
|
|
104
59
|
sessionId?: string;
|
|
105
60
|
}
|
|
106
61
|
|
|
107
|
-
export interface DeleteFileResponse {
|
|
108
|
-
success: boolean;
|
|
109
|
-
exitCode: number;
|
|
110
|
-
path: string;
|
|
111
|
-
timestamp: string;
|
|
112
|
-
}
|
|
113
62
|
|
|
114
63
|
interface RenameFileRequest {
|
|
115
64
|
oldPath: string;
|
|
@@ -117,13 +66,6 @@ interface RenameFileRequest {
|
|
|
117
66
|
sessionId?: string;
|
|
118
67
|
}
|
|
119
68
|
|
|
120
|
-
export interface RenameFileResponse {
|
|
121
|
-
success: boolean;
|
|
122
|
-
exitCode: number;
|
|
123
|
-
oldPath: string;
|
|
124
|
-
newPath: string;
|
|
125
|
-
timestamp: string;
|
|
126
|
-
}
|
|
127
69
|
|
|
128
70
|
interface MoveFileRequest {
|
|
129
71
|
sourcePath: string;
|
|
@@ -131,45 +73,58 @@ interface MoveFileRequest {
|
|
|
131
73
|
sessionId?: string;
|
|
132
74
|
}
|
|
133
75
|
|
|
134
|
-
|
|
76
|
+
|
|
77
|
+
interface ListFilesRequest {
|
|
78
|
+
path: string;
|
|
79
|
+
options?: {
|
|
80
|
+
recursive?: boolean;
|
|
81
|
+
includeHidden?: boolean;
|
|
82
|
+
};
|
|
83
|
+
sessionId?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
interface PreviewInfo {
|
|
88
|
+
url: string;
|
|
89
|
+
port: number;
|
|
90
|
+
name?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface ExposedPort extends PreviewInfo {
|
|
94
|
+
exposedAt: string;
|
|
95
|
+
timestamp: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface ExposePortResponse {
|
|
135
99
|
success: boolean;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
100
|
+
port: number;
|
|
101
|
+
name?: string;
|
|
102
|
+
exposedAt: string;
|
|
139
103
|
timestamp: string;
|
|
140
104
|
}
|
|
141
105
|
|
|
142
|
-
interface
|
|
143
|
-
|
|
106
|
+
interface UnexposePortResponse {
|
|
107
|
+
success: boolean;
|
|
108
|
+
port: number;
|
|
109
|
+
timestamp: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface GetExposedPortsResponse {
|
|
113
|
+
ports: ExposedPort[];
|
|
114
|
+
count: number;
|
|
144
115
|
timestamp: string;
|
|
145
116
|
}
|
|
146
117
|
|
|
147
|
-
interface
|
|
148
|
-
|
|
149
|
-
|
|
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;
|
|
118
|
+
interface PingResponse {
|
|
119
|
+
message: string;
|
|
120
|
+
timestamp: string;
|
|
166
121
|
}
|
|
167
122
|
|
|
168
123
|
interface HttpClientOptions {
|
|
169
124
|
stub?: Sandbox;
|
|
170
125
|
baseUrl?: string;
|
|
171
126
|
port?: number;
|
|
172
|
-
onCommandStart?: (command: string
|
|
127
|
+
onCommandStart?: (command: string) => void;
|
|
173
128
|
onOutput?: (
|
|
174
129
|
stream: "stdout" | "stderr",
|
|
175
130
|
data: string,
|
|
@@ -180,11 +135,9 @@ interface HttpClientOptions {
|
|
|
180
135
|
exitCode: number,
|
|
181
136
|
stdout: string,
|
|
182
137
|
stderr: string,
|
|
183
|
-
command: string
|
|
184
|
-
args: string[]
|
|
138
|
+
command: string
|
|
185
139
|
) => void;
|
|
186
|
-
onError?: (error: string, command?: string
|
|
187
|
-
onStreamEvent?: (event: StreamEvent) => void;
|
|
140
|
+
onError?: (error: string, command?: string) => void;
|
|
188
141
|
}
|
|
189
142
|
|
|
190
143
|
export class HttpClient {
|
|
@@ -199,11 +152,13 @@ export class HttpClient {
|
|
|
199
152
|
this.baseUrl = this.options.baseUrl!;
|
|
200
153
|
}
|
|
201
154
|
|
|
202
|
-
|
|
155
|
+
protected async doFetch(
|
|
203
156
|
path: string,
|
|
204
157
|
options?: RequestInit
|
|
205
158
|
): Promise<Response> {
|
|
206
|
-
const url = this.options.stub
|
|
159
|
+
const url = this.options.stub
|
|
160
|
+
? `http://localhost:${this.options.port}${path}`
|
|
161
|
+
: `${this.baseUrl}${path}`;
|
|
207
162
|
const method = options?.method || "GET";
|
|
208
163
|
|
|
209
164
|
console.log(`[HTTP Client] Making ${method} request to ${url}`);
|
|
@@ -212,15 +167,23 @@ export class HttpClient {
|
|
|
212
167
|
let response: Response;
|
|
213
168
|
|
|
214
169
|
if (this.options.stub) {
|
|
215
|
-
response = await this.options.stub.containerFetch(
|
|
170
|
+
response = await this.options.stub.containerFetch(
|
|
171
|
+
url,
|
|
172
|
+
options,
|
|
173
|
+
this.options.port
|
|
174
|
+
);
|
|
216
175
|
} else {
|
|
217
|
-
response = await fetch(
|
|
176
|
+
response = await fetch(url, options);
|
|
218
177
|
}
|
|
219
178
|
|
|
220
|
-
console.log(
|
|
179
|
+
console.log(
|
|
180
|
+
`[HTTP Client] Response: ${response.status} ${response.statusText}`
|
|
181
|
+
);
|
|
221
182
|
|
|
222
183
|
if (!response.ok) {
|
|
223
|
-
console.error(
|
|
184
|
+
console.error(
|
|
185
|
+
`[HTTP Client] Request failed: ${method} ${url} - ${response.status} ${response.statusText}`
|
|
186
|
+
);
|
|
224
187
|
}
|
|
225
188
|
|
|
226
189
|
return response;
|
|
@@ -229,117 +192,22 @@ export class HttpClient {
|
|
|
229
192
|
throw error;
|
|
230
193
|
}
|
|
231
194
|
}
|
|
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
195
|
|
|
329
196
|
async execute(
|
|
330
197
|
command: string,
|
|
331
|
-
|
|
332
|
-
sessionId?: string
|
|
198
|
+
options: Pick<BaseExecOptions, "sessionId" | "cwd" | "env">
|
|
333
199
|
): Promise<ExecuteResponse> {
|
|
334
200
|
try {
|
|
335
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
201
|
+
const targetSessionId = options.sessionId || this.sessionId;
|
|
202
|
+
const executeRequest = {
|
|
203
|
+
command,
|
|
204
|
+
sessionId: targetSessionId,
|
|
205
|
+
cwd: options.cwd,
|
|
206
|
+
env: options.env,
|
|
207
|
+
} satisfies ExecuteRequest;
|
|
336
208
|
|
|
337
209
|
const response = await this.doFetch(`/api/execute`, {
|
|
338
|
-
body: JSON.stringify(
|
|
339
|
-
args,
|
|
340
|
-
command,
|
|
341
|
-
sessionId: targetSessionId,
|
|
342
|
-
}),
|
|
210
|
+
body: JSON.stringify(executeRequest),
|
|
343
211
|
headers: {
|
|
344
212
|
"Content-Type": "application/json",
|
|
345
213
|
},
|
|
@@ -366,8 +234,7 @@ export class HttpClient {
|
|
|
366
234
|
data.exitCode,
|
|
367
235
|
data.stdout,
|
|
368
236
|
data.stderr,
|
|
369
|
-
data.command
|
|
370
|
-
data.args
|
|
237
|
+
data.command
|
|
371
238
|
);
|
|
372
239
|
|
|
373
240
|
return data;
|
|
@@ -375,29 +242,27 @@ export class HttpClient {
|
|
|
375
242
|
console.error("[HTTP Client] Error executing command:", error);
|
|
376
243
|
this.options.onError?.(
|
|
377
244
|
error instanceof Error ? error.message : "Unknown error",
|
|
378
|
-
command
|
|
379
|
-
args
|
|
245
|
+
command
|
|
380
246
|
);
|
|
381
247
|
throw error;
|
|
382
248
|
}
|
|
383
249
|
}
|
|
384
250
|
|
|
385
|
-
async
|
|
251
|
+
async executeCommandStream(
|
|
386
252
|
command: string,
|
|
387
|
-
args: string[] = [],
|
|
388
253
|
sessionId?: string
|
|
389
|
-
): Promise<
|
|
254
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
390
255
|
try {
|
|
391
256
|
const targetSessionId = sessionId || this.sessionId;
|
|
392
257
|
|
|
393
258
|
const response = await this.doFetch(`/api/execute/stream`, {
|
|
394
259
|
body: JSON.stringify({
|
|
395
|
-
args,
|
|
396
260
|
command,
|
|
397
261
|
sessionId: targetSessionId,
|
|
398
262
|
}),
|
|
399
263
|
headers: {
|
|
400
264
|
"Content-Type": "application/json",
|
|
265
|
+
Accept: "text/event-stream",
|
|
401
266
|
},
|
|
402
267
|
method: "POST",
|
|
403
268
|
});
|
|
@@ -415,95 +280,11 @@ export class HttpClient {
|
|
|
415
280
|
throw new Error("No response body for streaming request");
|
|
416
281
|
}
|
|
417
282
|
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
}
|
|
283
|
+
console.log(`[HTTP Client] Started command stream: ${command}`);
|
|
284
|
+
|
|
285
|
+
return response.body;
|
|
500
286
|
} catch (error) {
|
|
501
|
-
console.error("[HTTP Client] Error in
|
|
502
|
-
this.options.onError?.(
|
|
503
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
504
|
-
command,
|
|
505
|
-
args
|
|
506
|
-
);
|
|
287
|
+
console.error("[HTTP Client] Error in command stream:", error);
|
|
507
288
|
throw error;
|
|
508
289
|
}
|
|
509
290
|
}
|
|
@@ -523,7 +304,7 @@ export class HttpClient {
|
|
|
523
304
|
repoUrl,
|
|
524
305
|
sessionId: targetSessionId,
|
|
525
306
|
targetDir,
|
|
526
|
-
}),
|
|
307
|
+
} as GitCheckoutRequest),
|
|
527
308
|
headers: {
|
|
528
309
|
"Content-Type": "application/json",
|
|
529
310
|
},
|
|
@@ -551,22 +332,20 @@ export class HttpClient {
|
|
|
551
332
|
}
|
|
552
333
|
}
|
|
553
334
|
|
|
554
|
-
async
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
targetDir?: string,
|
|
335
|
+
async mkdir(
|
|
336
|
+
path: string,
|
|
337
|
+
recursive: boolean = false,
|
|
558
338
|
sessionId?: string
|
|
559
|
-
): Promise<
|
|
339
|
+
): Promise<MkdirResponse> {
|
|
560
340
|
try {
|
|
561
341
|
const targetSessionId = sessionId || this.sessionId;
|
|
562
342
|
|
|
563
|
-
const response = await this.doFetch(`/api/
|
|
343
|
+
const response = await this.doFetch(`/api/mkdir`, {
|
|
564
344
|
body: JSON.stringify({
|
|
565
|
-
|
|
566
|
-
|
|
345
|
+
path,
|
|
346
|
+
recursive,
|
|
567
347
|
sessionId: targetSessionId,
|
|
568
|
-
|
|
569
|
-
}),
|
|
348
|
+
} as MkdirRequest),
|
|
570
349
|
headers: {
|
|
571
350
|
"Content-Type": "application/json",
|
|
572
351
|
},
|
|
@@ -582,119 +361,34 @@ export class HttpClient {
|
|
|
582
361
|
);
|
|
583
362
|
}
|
|
584
363
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
364
|
+
const data: MkdirResponse = await response.json();
|
|
365
|
+
console.log(
|
|
366
|
+
`[HTTP Client] Directory created: ${path}, Success: ${data.success}, Recursive: ${data.recursive}`
|
|
367
|
+
);
|
|
588
368
|
|
|
589
|
-
|
|
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
|
-
}
|
|
369
|
+
return data;
|
|
673
370
|
} catch (error) {
|
|
674
|
-
console.error("[HTTP Client] Error
|
|
675
|
-
this.options.onError?.(
|
|
676
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
677
|
-
"git clone",
|
|
678
|
-
[branch, repoUrl, targetDir || ""]
|
|
679
|
-
);
|
|
371
|
+
console.error("[HTTP Client] Error creating directory:", error);
|
|
680
372
|
throw error;
|
|
681
373
|
}
|
|
682
374
|
}
|
|
683
375
|
|
|
684
|
-
async
|
|
376
|
+
async writeFile(
|
|
685
377
|
path: string,
|
|
686
|
-
|
|
378
|
+
content: string,
|
|
379
|
+
encoding: string = "utf-8",
|
|
687
380
|
sessionId?: string
|
|
688
|
-
): Promise<
|
|
381
|
+
): Promise<WriteFileResponse> {
|
|
689
382
|
try {
|
|
690
383
|
const targetSessionId = sessionId || this.sessionId;
|
|
691
384
|
|
|
692
|
-
const response = await this.doFetch(`/api/
|
|
385
|
+
const response = await this.doFetch(`/api/write`, {
|
|
693
386
|
body: JSON.stringify({
|
|
387
|
+
content,
|
|
388
|
+
encoding,
|
|
694
389
|
path,
|
|
695
|
-
recursive,
|
|
696
390
|
sessionId: targetSessionId,
|
|
697
|
-
}),
|
|
391
|
+
} as WriteFileRequest),
|
|
698
392
|
headers: {
|
|
699
393
|
"Content-Type": "application/json",
|
|
700
394
|
},
|
|
@@ -710,32 +404,32 @@ export class HttpClient {
|
|
|
710
404
|
);
|
|
711
405
|
}
|
|
712
406
|
|
|
713
|
-
const data:
|
|
407
|
+
const data: WriteFileResponse = await response.json();
|
|
714
408
|
console.log(
|
|
715
|
-
`[HTTP Client]
|
|
409
|
+
`[HTTP Client] File written: ${path}, Success: ${data.success}`
|
|
716
410
|
);
|
|
717
411
|
|
|
718
412
|
return data;
|
|
719
413
|
} catch (error) {
|
|
720
|
-
console.error("[HTTP Client] Error
|
|
414
|
+
console.error("[HTTP Client] Error writing file:", error);
|
|
721
415
|
throw error;
|
|
722
416
|
}
|
|
723
417
|
}
|
|
724
418
|
|
|
725
|
-
async
|
|
419
|
+
async readFile(
|
|
726
420
|
path: string,
|
|
727
|
-
|
|
421
|
+
encoding: string = "utf-8",
|
|
728
422
|
sessionId?: string
|
|
729
|
-
): Promise<
|
|
423
|
+
): Promise<ReadFileResponse> {
|
|
730
424
|
try {
|
|
731
425
|
const targetSessionId = sessionId || this.sessionId;
|
|
732
426
|
|
|
733
|
-
const response = await this.doFetch(`/api/
|
|
427
|
+
const response = await this.doFetch(`/api/read`, {
|
|
734
428
|
body: JSON.stringify({
|
|
429
|
+
encoding,
|
|
735
430
|
path,
|
|
736
|
-
recursive,
|
|
737
431
|
sessionId: targetSessionId,
|
|
738
|
-
}),
|
|
432
|
+
} as ReadFileRequest),
|
|
739
433
|
headers: {
|
|
740
434
|
"Content-Type": "application/json",
|
|
741
435
|
},
|
|
@@ -751,117 +445,30 @@ export class HttpClient {
|
|
|
751
445
|
);
|
|
752
446
|
}
|
|
753
447
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
448
|
+
const data: ReadFileResponse = await response.json();
|
|
449
|
+
console.log(
|
|
450
|
+
`[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
|
|
451
|
+
);
|
|
757
452
|
|
|
758
|
-
|
|
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
|
-
}
|
|
453
|
+
return data;
|
|
838
454
|
} catch (error) {
|
|
839
|
-
console.error("[HTTP Client] Error
|
|
840
|
-
this.options.onError?.(
|
|
841
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
842
|
-
"mkdir",
|
|
843
|
-
recursive ? ["-p", path] : [path]
|
|
844
|
-
);
|
|
455
|
+
console.error("[HTTP Client] Error reading file:", error);
|
|
845
456
|
throw error;
|
|
846
457
|
}
|
|
847
458
|
}
|
|
848
459
|
|
|
849
|
-
async
|
|
460
|
+
async deleteFile(
|
|
850
461
|
path: string,
|
|
851
|
-
content: string,
|
|
852
|
-
encoding: string = "utf-8",
|
|
853
462
|
sessionId?: string
|
|
854
|
-
): Promise<
|
|
463
|
+
): Promise<DeleteFileResponse> {
|
|
855
464
|
try {
|
|
856
465
|
const targetSessionId = sessionId || this.sessionId;
|
|
857
466
|
|
|
858
|
-
const response = await this.doFetch(`/api/
|
|
467
|
+
const response = await this.doFetch(`/api/delete`, {
|
|
859
468
|
body: JSON.stringify({
|
|
860
|
-
content,
|
|
861
|
-
encoding,
|
|
862
469
|
path,
|
|
863
470
|
sessionId: targetSessionId,
|
|
864
|
-
}),
|
|
471
|
+
} as DeleteFileRequest),
|
|
865
472
|
headers: {
|
|
866
473
|
"Content-Type": "application/json",
|
|
867
474
|
},
|
|
@@ -877,34 +484,32 @@ export class HttpClient {
|
|
|
877
484
|
);
|
|
878
485
|
}
|
|
879
486
|
|
|
880
|
-
const data:
|
|
487
|
+
const data: DeleteFileResponse = await response.json();
|
|
881
488
|
console.log(
|
|
882
|
-
`[HTTP Client] File
|
|
489
|
+
`[HTTP Client] File deleted: ${path}, Success: ${data.success}`
|
|
883
490
|
);
|
|
884
491
|
|
|
885
492
|
return data;
|
|
886
493
|
} catch (error) {
|
|
887
|
-
console.error("[HTTP Client] Error
|
|
494
|
+
console.error("[HTTP Client] Error deleting file:", error);
|
|
888
495
|
throw error;
|
|
889
496
|
}
|
|
890
497
|
}
|
|
891
498
|
|
|
892
|
-
async
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
encoding: string = "utf-8",
|
|
499
|
+
async renameFile(
|
|
500
|
+
oldPath: string,
|
|
501
|
+
newPath: string,
|
|
896
502
|
sessionId?: string
|
|
897
|
-
): Promise<
|
|
503
|
+
): Promise<RenameFileResponse> {
|
|
898
504
|
try {
|
|
899
505
|
const targetSessionId = sessionId || this.sessionId;
|
|
900
506
|
|
|
901
|
-
const response = await this.doFetch(`/api/
|
|
507
|
+
const response = await this.doFetch(`/api/rename`, {
|
|
902
508
|
body: JSON.stringify({
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
path,
|
|
509
|
+
newPath,
|
|
510
|
+
oldPath,
|
|
906
511
|
sessionId: targetSessionId,
|
|
907
|
-
}),
|
|
512
|
+
} as RenameFileRequest),
|
|
908
513
|
headers: {
|
|
909
514
|
"Content-Type": "application/json",
|
|
910
515
|
},
|
|
@@ -920,114 +525,32 @@ export class HttpClient {
|
|
|
920
525
|
);
|
|
921
526
|
}
|
|
922
527
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
528
|
+
const data: RenameFileResponse = await response.json();
|
|
529
|
+
console.log(
|
|
530
|
+
`[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
|
|
531
|
+
);
|
|
926
532
|
|
|
927
|
-
|
|
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
|
-
}
|
|
533
|
+
return data;
|
|
1006
534
|
} catch (error) {
|
|
1007
|
-
console.error("[HTTP Client] Error
|
|
1008
|
-
this.options.onError?.(
|
|
1009
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
1010
|
-
"write",
|
|
1011
|
-
[path, content, encoding]
|
|
1012
|
-
);
|
|
535
|
+
console.error("[HTTP Client] Error renaming file:", error);
|
|
1013
536
|
throw error;
|
|
1014
537
|
}
|
|
1015
538
|
}
|
|
1016
539
|
|
|
1017
|
-
async
|
|
1018
|
-
|
|
1019
|
-
|
|
540
|
+
async moveFile(
|
|
541
|
+
sourcePath: string,
|
|
542
|
+
destinationPath: string,
|
|
1020
543
|
sessionId?: string
|
|
1021
|
-
): Promise<
|
|
544
|
+
): Promise<MoveFileResponse> {
|
|
1022
545
|
try {
|
|
1023
546
|
const targetSessionId = sessionId || this.sessionId;
|
|
1024
547
|
|
|
1025
|
-
const response = await this.doFetch(`/api/
|
|
548
|
+
const response = await this.doFetch(`/api/move`, {
|
|
1026
549
|
body: JSON.stringify({
|
|
1027
|
-
|
|
1028
|
-
path,
|
|
550
|
+
destinationPath,
|
|
1029
551
|
sessionId: targetSessionId,
|
|
1030
|
-
|
|
552
|
+
sourcePath,
|
|
553
|
+
} as MoveFileRequest),
|
|
1031
554
|
headers: {
|
|
1032
555
|
"Content-Type": "application/json",
|
|
1033
556
|
},
|
|
@@ -1043,32 +566,35 @@ export class HttpClient {
|
|
|
1043
566
|
);
|
|
1044
567
|
}
|
|
1045
568
|
|
|
1046
|
-
const data:
|
|
569
|
+
const data: MoveFileResponse = await response.json();
|
|
1047
570
|
console.log(
|
|
1048
|
-
`[HTTP Client] File
|
|
571
|
+
`[HTTP Client] File moved: ${sourcePath} -> ${destinationPath}, Success: ${data.success}`
|
|
1049
572
|
);
|
|
1050
573
|
|
|
1051
574
|
return data;
|
|
1052
575
|
} catch (error) {
|
|
1053
|
-
console.error("[HTTP Client] Error
|
|
576
|
+
console.error("[HTTP Client] Error moving file:", error);
|
|
1054
577
|
throw error;
|
|
1055
578
|
}
|
|
1056
579
|
}
|
|
1057
580
|
|
|
1058
|
-
async
|
|
581
|
+
async listFiles(
|
|
1059
582
|
path: string,
|
|
1060
|
-
|
|
583
|
+
options?: {
|
|
584
|
+
recursive?: boolean;
|
|
585
|
+
includeHidden?: boolean;
|
|
586
|
+
},
|
|
1061
587
|
sessionId?: string
|
|
1062
|
-
): Promise<
|
|
588
|
+
): Promise<ListFilesResponse> {
|
|
1063
589
|
try {
|
|
1064
590
|
const targetSessionId = sessionId || this.sessionId;
|
|
1065
591
|
|
|
1066
|
-
const response = await this.doFetch(`/api/
|
|
592
|
+
const response = await this.doFetch(`/api/list-files`, {
|
|
1067
593
|
body: JSON.stringify({
|
|
1068
|
-
encoding,
|
|
1069
594
|
path,
|
|
595
|
+
options,
|
|
1070
596
|
sessionId: targetSessionId,
|
|
1071
|
-
}),
|
|
597
|
+
} as ListFilesRequest),
|
|
1072
598
|
headers: {
|
|
1073
599
|
"Content-Type": "application/json",
|
|
1074
600
|
},
|
|
@@ -1084,105 +610,24 @@ export class HttpClient {
|
|
|
1084
610
|
);
|
|
1085
611
|
}
|
|
1086
612
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
613
|
+
const data: ListFilesResponse = await response.json();
|
|
614
|
+
console.log(
|
|
615
|
+
`[HTTP Client] Listed ${data.files.length} files in: ${path}, Success: ${data.success}`
|
|
616
|
+
);
|
|
1090
617
|
|
|
1091
|
-
|
|
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
|
-
}
|
|
618
|
+
return data;
|
|
1164
619
|
} catch (error) {
|
|
1165
|
-
console.error("[HTTP Client] Error
|
|
1166
|
-
this.options.onError?.(
|
|
1167
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
1168
|
-
"read",
|
|
1169
|
-
[path, encoding]
|
|
1170
|
-
);
|
|
620
|
+
console.error("[HTTP Client] Error listing files:", error);
|
|
1171
621
|
throw error;
|
|
1172
622
|
}
|
|
1173
623
|
}
|
|
1174
624
|
|
|
1175
|
-
async
|
|
1176
|
-
path: string,
|
|
1177
|
-
sessionId?: string
|
|
1178
|
-
): Promise<DeleteFileResponse> {
|
|
625
|
+
async exposePort(port: number, name?: string): Promise<ExposePortResponse> {
|
|
1179
626
|
try {
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
const response = await this.doFetch(`/api/delete`, {
|
|
627
|
+
const response = await this.doFetch(`/api/expose-port`, {
|
|
1183
628
|
body: JSON.stringify({
|
|
1184
|
-
|
|
1185
|
-
|
|
629
|
+
port,
|
|
630
|
+
name,
|
|
1186
631
|
}),
|
|
1187
632
|
headers: {
|
|
1188
633
|
"Content-Type": "application/json",
|
|
@@ -1194,36 +639,36 @@ export class HttpClient {
|
|
|
1194
639
|
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1195
640
|
error?: string;
|
|
1196
641
|
};
|
|
642
|
+
console.log(errorData);
|
|
1197
643
|
throw new Error(
|
|
1198
644
|
errorData.error || `HTTP error! status: ${response.status}`
|
|
1199
645
|
);
|
|
1200
646
|
}
|
|
1201
647
|
|
|
1202
|
-
const data:
|
|
648
|
+
const data: ExposePortResponse = await response.json();
|
|
1203
649
|
console.log(
|
|
1204
|
-
`[HTTP Client]
|
|
650
|
+
`[HTTP Client] Port exposed: ${port}${
|
|
651
|
+
name ? ` (${name})` : ""
|
|
652
|
+
}, Success: ${data.success}`
|
|
1205
653
|
);
|
|
1206
654
|
|
|
1207
655
|
return data;
|
|
1208
656
|
} catch (error) {
|
|
1209
|
-
console.error("[HTTP Client] Error
|
|
657
|
+
console.error("[HTTP Client] Error exposing port:", error);
|
|
1210
658
|
throw error;
|
|
1211
659
|
}
|
|
1212
660
|
}
|
|
1213
661
|
|
|
1214
|
-
async
|
|
662
|
+
async unexposePort(port: number): Promise<UnexposePortResponse> {
|
|
1215
663
|
try {
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
const response = await this.doFetch(`/api/delete/stream`, {
|
|
664
|
+
const response = await this.doFetch(`/api/unexpose-port`, {
|
|
1219
665
|
body: JSON.stringify({
|
|
1220
|
-
|
|
1221
|
-
sessionId: targetSessionId,
|
|
666
|
+
port,
|
|
1222
667
|
}),
|
|
1223
668
|
headers: {
|
|
1224
669
|
"Content-Type": "application/json",
|
|
1225
670
|
},
|
|
1226
|
-
method: "
|
|
671
|
+
method: "DELETE",
|
|
1227
672
|
});
|
|
1228
673
|
|
|
1229
674
|
if (!response.ok) {
|
|
@@ -1235,105 +680,25 @@ export class HttpClient {
|
|
|
1235
680
|
);
|
|
1236
681
|
}
|
|
1237
682
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
683
|
+
const data: UnexposePortResponse = await response.json();
|
|
684
|
+
console.log(
|
|
685
|
+
`[HTTP Client] Port unexposed: ${port}, Success: ${data.success}`
|
|
686
|
+
);
|
|
1241
687
|
|
|
1242
|
-
|
|
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();
|
|
1307
|
-
}
|
|
688
|
+
return data;
|
|
1308
689
|
} catch (error) {
|
|
1309
|
-
console.error("[HTTP Client] Error
|
|
1310
|
-
this.options.onError?.(
|
|
1311
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
1312
|
-
"delete",
|
|
1313
|
-
[path]
|
|
1314
|
-
);
|
|
690
|
+
console.error("[HTTP Client] Error unexposing port:", error);
|
|
1315
691
|
throw error;
|
|
1316
692
|
}
|
|
1317
693
|
}
|
|
1318
694
|
|
|
1319
|
-
async
|
|
1320
|
-
oldPath: string,
|
|
1321
|
-
newPath: string,
|
|
1322
|
-
sessionId?: string
|
|
1323
|
-
): Promise<RenameFileResponse> {
|
|
695
|
+
async getExposedPorts(): Promise<GetExposedPortsResponse> {
|
|
1324
696
|
try {
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
const response = await this.doFetch(`/api/rename`, {
|
|
1328
|
-
body: JSON.stringify({
|
|
1329
|
-
newPath,
|
|
1330
|
-
oldPath,
|
|
1331
|
-
sessionId: targetSessionId,
|
|
1332
|
-
}),
|
|
697
|
+
const response = await this.doFetch(`/api/exposed-ports`, {
|
|
1333
698
|
headers: {
|
|
1334
699
|
"Content-Type": "application/json",
|
|
1335
700
|
},
|
|
1336
|
-
method: "
|
|
701
|
+
method: "GET",
|
|
1337
702
|
});
|
|
1338
703
|
|
|
1339
704
|
if (!response.ok) {
|
|
@@ -1345,145 +710,98 @@ export class HttpClient {
|
|
|
1345
710
|
);
|
|
1346
711
|
}
|
|
1347
712
|
|
|
1348
|
-
const data:
|
|
1349
|
-
console.log(
|
|
1350
|
-
`[HTTP Client] File renamed: ${oldPath} -> ${newPath}, Success: ${data.success}`
|
|
1351
|
-
);
|
|
713
|
+
const data: GetExposedPortsResponse = await response.json();
|
|
714
|
+
console.log(`[HTTP Client] Got ${data.count} exposed ports`);
|
|
1352
715
|
|
|
1353
716
|
return data;
|
|
1354
717
|
} catch (error) {
|
|
1355
|
-
console.error("[HTTP Client] Error
|
|
718
|
+
console.error("[HTTP Client] Error getting exposed ports:", error);
|
|
1356
719
|
throw error;
|
|
1357
720
|
}
|
|
1358
721
|
}
|
|
1359
722
|
|
|
1360
|
-
async
|
|
1361
|
-
oldPath: string,
|
|
1362
|
-
newPath: string,
|
|
1363
|
-
sessionId?: string
|
|
1364
|
-
): Promise<void> {
|
|
723
|
+
async ping(): Promise<string> {
|
|
1365
724
|
try {
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
const response = await this.doFetch(`/api/rename/stream`, {
|
|
1369
|
-
body: JSON.stringify({
|
|
1370
|
-
newPath,
|
|
1371
|
-
oldPath,
|
|
1372
|
-
sessionId: targetSessionId,
|
|
1373
|
-
}),
|
|
725
|
+
const response = await this.doFetch(`/api/ping`, {
|
|
1374
726
|
headers: {
|
|
1375
727
|
"Content-Type": "application/json",
|
|
1376
728
|
},
|
|
1377
|
-
method: "
|
|
729
|
+
method: "GET",
|
|
1378
730
|
});
|
|
1379
731
|
|
|
1380
732
|
if (!response.ok) {
|
|
1381
|
-
|
|
1382
|
-
error?: string;
|
|
1383
|
-
};
|
|
1384
|
-
throw new Error(
|
|
1385
|
-
errorData.error || `HTTP error! status: ${response.status}`
|
|
1386
|
-
);
|
|
733
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1387
734
|
}
|
|
1388
735
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
736
|
+
const data: PingResponse = await response.json();
|
|
737
|
+
console.log(`[HTTP Client] Ping response: ${data.message}`);
|
|
738
|
+
return data.timestamp;
|
|
739
|
+
} catch (error) {
|
|
740
|
+
console.error("[HTTP Client] Error pinging server:", error);
|
|
741
|
+
throw error;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
1392
744
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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();
|
|
745
|
+
async getCommands(): Promise<string[]> {
|
|
746
|
+
try {
|
|
747
|
+
const response = await fetch(`${this.baseUrl}/api/commands`, {
|
|
748
|
+
headers: {
|
|
749
|
+
"Content-Type": "application/json",
|
|
750
|
+
},
|
|
751
|
+
method: "GET",
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
if (!response.ok) {
|
|
755
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1461
756
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
"rename",
|
|
1467
|
-
[oldPath, newPath]
|
|
757
|
+
|
|
758
|
+
const data: CommandsResponse = await response.json();
|
|
759
|
+
console.log(
|
|
760
|
+
`[HTTP Client] Available commands: ${data.availableCommands.length}`
|
|
1468
761
|
);
|
|
762
|
+
return data.availableCommands;
|
|
763
|
+
} catch (error) {
|
|
764
|
+
console.error("[HTTP Client] Error getting commands:", error);
|
|
1469
765
|
throw error;
|
|
1470
766
|
}
|
|
1471
767
|
}
|
|
1472
768
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
):
|
|
769
|
+
getSessionId(): string | null {
|
|
770
|
+
return this.sessionId;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
setSessionId(sessionId: string): void {
|
|
774
|
+
this.sessionId = sessionId;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
clearSession(): void {
|
|
778
|
+
this.sessionId = null;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Process management methods
|
|
782
|
+
async startProcess(
|
|
783
|
+
command: string,
|
|
784
|
+
options?: {
|
|
785
|
+
processId?: string;
|
|
786
|
+
sessionId?: string;
|
|
787
|
+
timeout?: number;
|
|
788
|
+
env?: Record<string, string>;
|
|
789
|
+
cwd?: string;
|
|
790
|
+
encoding?: string;
|
|
791
|
+
autoCleanup?: boolean;
|
|
792
|
+
}
|
|
793
|
+
): Promise<StartProcessResponse> {
|
|
1478
794
|
try {
|
|
1479
|
-
const targetSessionId = sessionId || this.sessionId;
|
|
795
|
+
const targetSessionId = options?.sessionId || this.sessionId;
|
|
1480
796
|
|
|
1481
|
-
const response = await this.doFetch(
|
|
797
|
+
const response = await this.doFetch("/api/process/start", {
|
|
1482
798
|
body: JSON.stringify({
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
799
|
+
command,
|
|
800
|
+
options: {
|
|
801
|
+
...options,
|
|
802
|
+
sessionId: targetSessionId,
|
|
803
|
+
},
|
|
804
|
+
} as StartProcessRequest),
|
|
1487
805
|
headers: {
|
|
1488
806
|
"Content-Type": "application/json",
|
|
1489
807
|
},
|
|
@@ -1499,36 +817,25 @@ export class HttpClient {
|
|
|
1499
817
|
);
|
|
1500
818
|
}
|
|
1501
819
|
|
|
1502
|
-
const data:
|
|
820
|
+
const data: StartProcessResponse = await response.json();
|
|
1503
821
|
console.log(
|
|
1504
|
-
`[HTTP Client]
|
|
822
|
+
`[HTTP Client] Process started: ${command}, ID: ${data.process.id}`
|
|
1505
823
|
);
|
|
1506
824
|
|
|
1507
825
|
return data;
|
|
1508
826
|
} catch (error) {
|
|
1509
|
-
console.error("[HTTP Client] Error
|
|
827
|
+
console.error("[HTTP Client] Error starting process:", error);
|
|
1510
828
|
throw error;
|
|
1511
829
|
}
|
|
1512
830
|
}
|
|
1513
831
|
|
|
1514
|
-
async
|
|
1515
|
-
sourcePath: string,
|
|
1516
|
-
destinationPath: string,
|
|
1517
|
-
sessionId?: string
|
|
1518
|
-
): Promise<void> {
|
|
832
|
+
async listProcesses(): Promise<ListProcessesResponse> {
|
|
1519
833
|
try {
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
const response = await this.doFetch(`/api/move/stream`, {
|
|
1523
|
-
body: JSON.stringify({
|
|
1524
|
-
destinationPath,
|
|
1525
|
-
sessionId: targetSessionId,
|
|
1526
|
-
sourcePath,
|
|
1527
|
-
}),
|
|
834
|
+
const response = await this.doFetch("/api/process/list", {
|
|
1528
835
|
headers: {
|
|
1529
836
|
"Content-Type": "application/json",
|
|
1530
837
|
},
|
|
1531
|
-
method: "
|
|
838
|
+
method: "GET",
|
|
1532
839
|
});
|
|
1533
840
|
|
|
1534
841
|
if (!response.ok) {
|
|
@@ -1540,96 +847,19 @@ export class HttpClient {
|
|
|
1540
847
|
);
|
|
1541
848
|
}
|
|
1542
849
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
}
|
|
850
|
+
const data: ListProcessesResponse = await response.json();
|
|
851
|
+
console.log(`[HTTP Client] Listed ${data.processes.length} processes`);
|
|
1546
852
|
|
|
1547
|
-
|
|
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
|
-
}
|
|
853
|
+
return data;
|
|
1619
854
|
} catch (error) {
|
|
1620
|
-
console.error("[HTTP Client] Error
|
|
1621
|
-
this.options.onError?.(
|
|
1622
|
-
error instanceof Error ? error.message : "Unknown error",
|
|
1623
|
-
"move",
|
|
1624
|
-
[sourcePath, destinationPath]
|
|
1625
|
-
);
|
|
855
|
+
console.error("[HTTP Client] Error listing processes:", error);
|
|
1626
856
|
throw error;
|
|
1627
857
|
}
|
|
1628
858
|
}
|
|
1629
859
|
|
|
1630
|
-
async
|
|
860
|
+
async getProcess(processId: string): Promise<GetProcessResponse> {
|
|
1631
861
|
try {
|
|
1632
|
-
const response = await this.doFetch(`/api/
|
|
862
|
+
const response = await this.doFetch(`/api/process/${processId}`, {
|
|
1633
863
|
headers: {
|
|
1634
864
|
"Content-Type": "application/json",
|
|
1635
865
|
},
|
|
@@ -1637,314 +867,158 @@ export class HttpClient {
|
|
|
1637
867
|
});
|
|
1638
868
|
|
|
1639
869
|
if (!response.ok) {
|
|
1640
|
-
|
|
870
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
871
|
+
error?: string;
|
|
872
|
+
};
|
|
873
|
+
throw new Error(
|
|
874
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
875
|
+
);
|
|
1641
876
|
}
|
|
1642
877
|
|
|
1643
|
-
const data:
|
|
1644
|
-
console.log(
|
|
1645
|
-
|
|
878
|
+
const data: GetProcessResponse = await response.json();
|
|
879
|
+
console.log(
|
|
880
|
+
`[HTTP Client] Got process ${processId}: ${
|
|
881
|
+
data.process?.status || "not found"
|
|
882
|
+
}`
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
return data;
|
|
1646
886
|
} catch (error) {
|
|
1647
|
-
console.error("[HTTP Client] Error
|
|
887
|
+
console.error("[HTTP Client] Error getting process:", error);
|
|
1648
888
|
throw error;
|
|
1649
889
|
}
|
|
1650
890
|
}
|
|
1651
891
|
|
|
1652
|
-
async
|
|
892
|
+
async killProcess(
|
|
893
|
+
processId: string
|
|
894
|
+
): Promise<{ success: boolean; message: string }> {
|
|
1653
895
|
try {
|
|
1654
|
-
const response = await
|
|
896
|
+
const response = await this.doFetch(`/api/process/${processId}`, {
|
|
1655
897
|
headers: {
|
|
1656
898
|
"Content-Type": "application/json",
|
|
1657
899
|
},
|
|
1658
|
-
method: "
|
|
900
|
+
method: "DELETE",
|
|
1659
901
|
});
|
|
1660
902
|
|
|
1661
903
|
if (!response.ok) {
|
|
1662
|
-
|
|
904
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
905
|
+
error?: string;
|
|
906
|
+
};
|
|
907
|
+
throw new Error(
|
|
908
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
909
|
+
);
|
|
1663
910
|
}
|
|
1664
911
|
|
|
1665
|
-
const data
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
912
|
+
const data = (await response.json()) as {
|
|
913
|
+
success: boolean;
|
|
914
|
+
message: string;
|
|
915
|
+
};
|
|
916
|
+
console.log(`[HTTP Client] Killed process ${processId}`);
|
|
917
|
+
|
|
918
|
+
return data;
|
|
1670
919
|
} catch (error) {
|
|
1671
|
-
console.error("[HTTP Client] Error
|
|
920
|
+
console.error("[HTTP Client] Error killing process:", error);
|
|
1672
921
|
throw error;
|
|
1673
922
|
}
|
|
1674
923
|
}
|
|
1675
924
|
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
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
|
-
}
|
|
925
|
+
async killAllProcesses(): Promise<{
|
|
926
|
+
success: boolean;
|
|
927
|
+
killedCount: number;
|
|
928
|
+
message: string;
|
|
929
|
+
}> {
|
|
930
|
+
try {
|
|
931
|
+
const response = await this.doFetch("/api/process/kill-all", {
|
|
932
|
+
headers: {
|
|
933
|
+
"Content-Type": "application/json",
|
|
934
|
+
},
|
|
935
|
+
method: "DELETE",
|
|
936
|
+
});
|
|
1758
937
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
)
|
|
1766
|
-
|
|
1767
|
-
await client.createSession();
|
|
1768
|
-
|
|
1769
|
-
try {
|
|
1770
|
-
await client.gitCheckoutStream(repoUrl, branch, targetDir);
|
|
1771
|
-
} finally {
|
|
1772
|
-
client.clearSession();
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
938
|
+
if (!response.ok) {
|
|
939
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
940
|
+
error?: string;
|
|
941
|
+
};
|
|
942
|
+
throw new Error(
|
|
943
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
944
|
+
);
|
|
945
|
+
}
|
|
1775
946
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
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
|
-
}
|
|
947
|
+
const data = (await response.json()) as {
|
|
948
|
+
success: boolean;
|
|
949
|
+
killedCount: number;
|
|
950
|
+
message: string;
|
|
951
|
+
};
|
|
952
|
+
console.log(`[HTTP Client] Killed ${data.killedCount} processes`);
|
|
1791
953
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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();
|
|
954
|
+
return data;
|
|
955
|
+
} catch (error) {
|
|
956
|
+
console.error("[HTTP Client] Error killing all processes:", error);
|
|
957
|
+
throw error;
|
|
958
|
+
}
|
|
1806
959
|
}
|
|
1807
|
-
}
|
|
1808
960
|
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
await client.createSession();
|
|
1818
|
-
|
|
1819
|
-
try {
|
|
1820
|
-
await client.writeFileStream(path, content, encoding);
|
|
1821
|
-
} finally {
|
|
1822
|
-
client.clearSession();
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
961
|
+
async getProcessLogs(processId: string): Promise<GetProcessLogsResponse> {
|
|
962
|
+
try {
|
|
963
|
+
const response = await this.doFetch(`/api/process/${processId}/logs`, {
|
|
964
|
+
headers: {
|
|
965
|
+
"Content-Type": "application/json",
|
|
966
|
+
},
|
|
967
|
+
method: "GET",
|
|
968
|
+
});
|
|
1825
969
|
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
try {
|
|
1836
|
-
return await client.readFile(path, encoding);
|
|
1837
|
-
} finally {
|
|
1838
|
-
client.clearSession();
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
970
|
+
if (!response.ok) {
|
|
971
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
972
|
+
error?: string;
|
|
973
|
+
};
|
|
974
|
+
throw new Error(
|
|
975
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
976
|
+
);
|
|
977
|
+
}
|
|
1841
978
|
|
|
1842
|
-
|
|
1843
|
-
|
|
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
|
-
}
|
|
979
|
+
const data: GetProcessLogsResponse = await response.json();
|
|
980
|
+
console.log(`[HTTP Client] Got logs for process ${processId}`);
|
|
1857
981
|
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
const client = createClient(options);
|
|
1864
|
-
await client.createSession();
|
|
1865
|
-
|
|
1866
|
-
try {
|
|
1867
|
-
return await client.deleteFile(path);
|
|
1868
|
-
} finally {
|
|
1869
|
-
client.clearSession();
|
|
982
|
+
return data;
|
|
983
|
+
} catch (error) {
|
|
984
|
+
console.error("[HTTP Client] Error getting process logs:", error);
|
|
985
|
+
throw error;
|
|
986
|
+
}
|
|
1870
987
|
}
|
|
1871
|
-
}
|
|
1872
988
|
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
client.clearSession();
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
989
|
+
async streamProcessLogs(
|
|
990
|
+
processId: string
|
|
991
|
+
): Promise<ReadableStream<Uint8Array>> {
|
|
992
|
+
try {
|
|
993
|
+
const response = await this.doFetch(`/api/process/${processId}/stream`, {
|
|
994
|
+
headers: {
|
|
995
|
+
Accept: "text/event-stream",
|
|
996
|
+
"Cache-Control": "no-cache",
|
|
997
|
+
},
|
|
998
|
+
method: "GET",
|
|
999
|
+
});
|
|
1887
1000
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
try {
|
|
1898
|
-
return await client.renameFile(oldPath, newPath);
|
|
1899
|
-
} finally {
|
|
1900
|
-
client.clearSession();
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1001
|
+
if (!response.ok) {
|
|
1002
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1003
|
+
error?: string;
|
|
1004
|
+
};
|
|
1005
|
+
throw new Error(
|
|
1006
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1903
1009
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
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
|
-
}
|
|
1010
|
+
if (!response.body) {
|
|
1011
|
+
throw new Error("No response body for streaming request");
|
|
1012
|
+
}
|
|
1919
1013
|
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
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
|
-
}
|
|
1014
|
+
console.log(
|
|
1015
|
+
`[HTTP Client] Started streaming logs for process ${processId}`
|
|
1016
|
+
);
|
|
1935
1017
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
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();
|
|
1018
|
+
return response.body;
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
console.error("[HTTP Client] Error streaming process logs:", error);
|
|
1021
|
+
throw error;
|
|
1022
|
+
}
|
|
1949
1023
|
}
|
|
1950
1024
|
}
|