@cloudflare/sandbox 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/Dockerfile +73 -9
  3. package/container_src/handler/exec.ts +337 -0
  4. package/container_src/handler/file.ts +844 -0
  5. package/container_src/handler/git.ts +182 -0
  6. package/container_src/handler/ports.ts +314 -0
  7. package/container_src/handler/process.ts +640 -0
  8. package/container_src/index.ts +102 -2647
  9. package/container_src/types.ts +103 -0
  10. package/dist/chunk-6THNBO4S.js +46 -0
  11. package/dist/chunk-6THNBO4S.js.map +1 -0
  12. package/dist/chunk-6UAWTJ5S.js +85 -0
  13. package/dist/chunk-6UAWTJ5S.js.map +1 -0
  14. package/dist/chunk-G4XT4SP7.js +638 -0
  15. package/dist/chunk-G4XT4SP7.js.map +1 -0
  16. package/dist/chunk-ISFOIYQC.js +585 -0
  17. package/dist/chunk-ISFOIYQC.js.map +1 -0
  18. package/dist/chunk-NNGBXDMY.js +89 -0
  19. package/dist/chunk-NNGBXDMY.js.map +1 -0
  20. package/dist/client-Da-mLX4p.d.ts +210 -0
  21. package/dist/client.d.ts +2 -1
  22. package/dist/client.js +3 -37
  23. package/dist/index.d.ts +5 -200
  24. package/dist/index.js +17 -106
  25. package/dist/index.js.map +1 -1
  26. package/dist/request-handler.d.ts +16 -0
  27. package/dist/request-handler.js +12 -0
  28. package/dist/request-handler.js.map +1 -0
  29. package/dist/sandbox.d.ts +3 -0
  30. package/dist/sandbox.js +12 -0
  31. package/dist/sandbox.js.map +1 -0
  32. package/dist/security.d.ts +30 -0
  33. package/dist/security.js +13 -0
  34. package/dist/security.js.map +1 -0
  35. package/dist/sse-parser.d.ts +28 -0
  36. package/dist/sse-parser.js +11 -0
  37. package/dist/sse-parser.js.map +1 -0
  38. package/dist/types.d.ts +284 -0
  39. package/dist/types.js +19 -0
  40. package/dist/types.js.map +1 -0
  41. package/package.json +2 -7
  42. package/src/client.ts +320 -1242
  43. package/src/index.ts +20 -136
  44. package/src/request-handler.ts +144 -0
  45. package/src/sandbox.ts +645 -0
  46. package/src/security.ts +113 -0
  47. package/src/sse-parser.ts +147 -0
  48. package/src/types.ts +386 -0
  49. package/README.md +0 -65
  50. package/dist/chunk-7WZJ3TRE.js +0 -1364
  51. package/dist/chunk-7WZJ3TRE.js.map +0 -1
  52. package/tests/client.example.ts +0 -308
  53. package/tests/connection-test.ts +0 -81
  54. package/tests/simple-test.ts +0 -81
  55. package/tests/test1.ts +0 -281
  56. package/tests/test2.ts +0 -929
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Security utilities for URL construction and input validation
3
+ *
4
+ * This module contains critical security functions to prevent:
5
+ * - URL injection attacks
6
+ * - SSRF (Server-Side Request Forgery) attacks
7
+ * - DNS rebinding attacks
8
+ * - Host header injection
9
+ * - Open redirect vulnerabilities
10
+ */
11
+
12
+ export class SecurityError extends Error {
13
+ constructor(message: string, public readonly code?: string) {
14
+ super(message);
15
+ this.name = 'SecurityError';
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Validates port numbers for sandbox services
21
+ * Only allows non-system ports to prevent conflicts and security issues
22
+ */
23
+ export function validatePort(port: number): boolean {
24
+ // Must be a valid integer
25
+ if (!Number.isInteger(port)) {
26
+ return false;
27
+ }
28
+
29
+ // Only allow non-system ports (1024-65535)
30
+ if (port < 1024 || port > 65535) {
31
+ return false;
32
+ }
33
+
34
+ // Exclude ports reserved by our system
35
+ const reservedPorts = [
36
+ 3000, // Control plane port
37
+ 8787, // Common wrangler dev port
38
+ ];
39
+
40
+ if (reservedPorts.includes(port)) {
41
+ return false;
42
+ }
43
+
44
+ return true;
45
+ }
46
+
47
+ /**
48
+ * Sanitizes and validates sandbox IDs for DNS compliance and security
49
+ * Only enforces critical requirements - allows maximum developer flexibility
50
+ */
51
+ export function sanitizeSandboxId(id: string): string {
52
+ // Basic validation: not empty, reasonable length limit (DNS subdomain limit is 63 chars)
53
+ if (!id || id.length > 63) {
54
+ throw new SecurityError(
55
+ 'Sandbox ID must be 1-63 characters long.',
56
+ 'INVALID_SANDBOX_ID_LENGTH'
57
+ );
58
+ }
59
+
60
+ // DNS compliance: cannot start or end with hyphens (RFC requirement)
61
+ if (id.startsWith('-') || id.endsWith('-')) {
62
+ throw new SecurityError(
63
+ 'Sandbox ID cannot start or end with hyphens (DNS requirement).',
64
+ 'INVALID_SANDBOX_ID_HYPHENS'
65
+ );
66
+ }
67
+
68
+ // Prevent reserved names that cause technical conflicts
69
+ const reservedNames = [
70
+ 'www', 'api', 'admin', 'root', 'system',
71
+ 'cloudflare', 'workers'
72
+ ];
73
+
74
+ const lowerCaseId = id.toLowerCase();
75
+ if (reservedNames.includes(lowerCaseId)) {
76
+ throw new SecurityError(
77
+ `Reserved sandbox ID '${id}' is not allowed.`,
78
+ 'RESERVED_SANDBOX_ID'
79
+ );
80
+ }
81
+
82
+ return id;
83
+ }
84
+
85
+
86
+ /**
87
+ * Logs security events for monitoring
88
+ */
89
+ export function logSecurityEvent(
90
+ event: string,
91
+ details: Record<string, any>,
92
+ severity: 'low' | 'medium' | 'high' | 'critical' = 'medium'
93
+ ): void {
94
+ const logEntry = {
95
+ timestamp: new Date().toISOString(),
96
+ event,
97
+ severity,
98
+ ...details
99
+ };
100
+
101
+ switch (severity) {
102
+ case 'critical':
103
+ case 'high':
104
+ console.error(`[SECURITY:${severity.toUpperCase()}] ${event}:`, JSON.stringify(logEntry));
105
+ break;
106
+ case 'medium':
107
+ console.warn(`[SECURITY:${severity.toUpperCase()}] ${event}:`, JSON.stringify(logEntry));
108
+ break;
109
+ case 'low':
110
+ console.info(`[SECURITY:${severity.toUpperCase()}] ${event}:`, JSON.stringify(logEntry));
111
+ break;
112
+ }
113
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Server-Sent Events (SSE) parser for streaming responses
3
+ * Converts ReadableStream<Uint8Array> to typed AsyncIterable<T>
4
+ */
5
+
6
+ /**
7
+ * Parse a ReadableStream of SSE events into typed AsyncIterable
8
+ * @param stream - The ReadableStream from fetch response
9
+ * @param signal - Optional AbortSignal for cancellation
10
+ */
11
+ export async function* parseSSEStream<T>(
12
+ stream: ReadableStream<Uint8Array>,
13
+ signal?: AbortSignal
14
+ ): AsyncIterable<T> {
15
+ const reader = stream.getReader();
16
+ const decoder = new TextDecoder();
17
+ let buffer = '';
18
+
19
+ try {
20
+ while (true) {
21
+ // Check for cancellation
22
+ if (signal?.aborted) {
23
+ throw new Error('Operation was aborted');
24
+ }
25
+
26
+ const { done, value } = await reader.read();
27
+ if (done) break;
28
+
29
+ // Decode chunk and add to buffer
30
+ buffer += decoder.decode(value, { stream: true });
31
+
32
+ // Process complete SSE events in buffer
33
+ const lines = buffer.split('\n');
34
+
35
+ // Keep the last incomplete line in buffer
36
+ buffer = lines.pop() || '';
37
+
38
+ for (const line of lines) {
39
+ // Skip empty lines
40
+ if (line.trim() === '') continue;
41
+
42
+ // Process SSE data lines
43
+ if (line.startsWith('data: ')) {
44
+ const data = line.substring(6);
45
+
46
+ // Skip [DONE] markers or empty data
47
+ if (data === '[DONE]' || data.trim() === '') continue;
48
+
49
+ try {
50
+ const event = JSON.parse(data) as T;
51
+ yield event;
52
+ } catch (error) {
53
+ // Log parsing errors but continue processing
54
+ console.error('Failed to parse SSE event:', data, error);
55
+ // Optionally yield an error event
56
+ // yield { type: 'error', data: `Parse error: ${error.message}` } as T;
57
+ }
58
+ }
59
+ // Handle other SSE fields if needed (event:, id:, retry:)
60
+ // For now, we only care about data: lines
61
+ }
62
+ }
63
+
64
+ // Process any remaining data in buffer
65
+ if (buffer.trim() && buffer.startsWith('data: ')) {
66
+ const data = buffer.substring(6);
67
+ if (data !== '[DONE]' && data.trim()) {
68
+ try {
69
+ const event = JSON.parse(data) as T;
70
+ yield event;
71
+ } catch (error) {
72
+ console.error('Failed to parse final SSE event:', data, error);
73
+ }
74
+ }
75
+ }
76
+ } finally {
77
+ // Clean up resources
78
+ reader.releaseLock();
79
+ }
80
+ }
81
+
82
+
83
+ /**
84
+ * Helper to convert a Response with SSE stream directly to AsyncIterable
85
+ * @param response - Response object with SSE stream
86
+ * @param signal - Optional AbortSignal for cancellation
87
+ */
88
+ export async function* responseToAsyncIterable<T>(
89
+ response: Response,
90
+ signal?: AbortSignal
91
+ ): AsyncIterable<T> {
92
+ if (!response.ok) {
93
+ throw new Error(`Response not ok: ${response.status} ${response.statusText}`);
94
+ }
95
+
96
+ if (!response.body) {
97
+ throw new Error('No response body');
98
+ }
99
+
100
+ yield* parseSSEStream<T>(response.body, signal);
101
+ }
102
+
103
+ /**
104
+ * Create an SSE-formatted ReadableStream from an AsyncIterable
105
+ * (Useful for Worker endpoints that need to forward AsyncIterable as SSE)
106
+ * @param events - AsyncIterable of events
107
+ * @param options - Stream options
108
+ */
109
+ export function asyncIterableToSSEStream<T>(
110
+ events: AsyncIterable<T>,
111
+ options?: {
112
+ signal?: AbortSignal;
113
+ serialize?: (event: T) => string;
114
+ }
115
+ ): ReadableStream<Uint8Array> {
116
+ const encoder = new TextEncoder();
117
+ const serialize = options?.serialize || JSON.stringify;
118
+
119
+ return new ReadableStream({
120
+ async start(controller) {
121
+ try {
122
+ for await (const event of events) {
123
+ if (options?.signal?.aborted) {
124
+ controller.error(new Error('Operation was aborted'));
125
+ break;
126
+ }
127
+
128
+ const data = serialize(event);
129
+ const sseEvent = `data: ${data}\n\n`;
130
+ controller.enqueue(encoder.encode(sseEvent));
131
+ }
132
+
133
+ // Send completion marker
134
+ controller.enqueue(encoder.encode('data: [DONE]\n\n'));
135
+ } catch (error) {
136
+ controller.error(error);
137
+ } finally {
138
+ controller.close();
139
+ }
140
+ },
141
+
142
+ cancel() {
143
+ // Handle stream cancellation
144
+ console.log('SSE stream cancelled');
145
+ }
146
+ });
147
+ }
package/src/types.ts ADDED
@@ -0,0 +1,386 @@
1
+ // Core Types
2
+
3
+ export interface BaseExecOptions {
4
+ /**
5
+ * Session ID for grouping related commands
6
+ */
7
+ sessionId?: string;
8
+
9
+ /**
10
+ * Maximum execution time in milliseconds
11
+ */
12
+ timeout?: number;
13
+
14
+ /**
15
+ * Environment variables for the command
16
+ */
17
+ env?: Record<string, string>;
18
+
19
+ /**
20
+ * Working directory for command execution
21
+ */
22
+ cwd?: string;
23
+
24
+ /**
25
+ * Text encoding for output (default: 'utf8')
26
+ */
27
+ encoding?: string;
28
+ }
29
+
30
+ export interface ExecOptions extends BaseExecOptions {
31
+ /**
32
+ * Enable real-time output streaming via callbacks
33
+ */
34
+ stream?: boolean;
35
+
36
+ /**
37
+ * Callback for real-time output data
38
+ */
39
+ onOutput?: (stream: 'stdout' | 'stderr', data: string) => void;
40
+
41
+ /**
42
+ * Callback when command completes (only when stream: true)
43
+ */
44
+ onComplete?: (result: ExecResult) => void;
45
+
46
+ /**
47
+ * Callback for execution errors
48
+ */
49
+ onError?: (error: Error) => void;
50
+
51
+ /**
52
+ * AbortSignal for cancelling execution
53
+ */
54
+ signal?: AbortSignal;
55
+ }
56
+
57
+ export interface ExecResult {
58
+ /**
59
+ * Whether the command succeeded (exitCode === 0)
60
+ */
61
+ success: boolean;
62
+
63
+ /**
64
+ * Process exit code
65
+ */
66
+ exitCode: number;
67
+
68
+ /**
69
+ * Standard output content
70
+ */
71
+ stdout: string;
72
+
73
+ /**
74
+ * Standard error content
75
+ */
76
+ stderr: string;
77
+
78
+ /**
79
+ * Command that was executed
80
+ */
81
+ command: string;
82
+
83
+
84
+ /**
85
+ * Execution duration in milliseconds
86
+ */
87
+ duration: number;
88
+
89
+ /**
90
+ * ISO timestamp when command started
91
+ */
92
+ timestamp: string;
93
+
94
+ /**
95
+ * Session ID if provided
96
+ */
97
+ sessionId?: string;
98
+ }
99
+
100
+ // Background Process Types
101
+
102
+ export interface ProcessOptions extends BaseExecOptions {
103
+ /**
104
+ * Custom process ID for later reference
105
+ * If not provided, a UUID will be generated
106
+ */
107
+ processId?: string;
108
+
109
+ /**
110
+ * Automatically cleanup process record after exit (default: true)
111
+ */
112
+ autoCleanup?: boolean;
113
+
114
+ /**
115
+ * Callback when process exits
116
+ */
117
+ onExit?: (code: number | null) => void;
118
+
119
+ /**
120
+ * Callback for real-time output (background processes)
121
+ */
122
+ onOutput?: (stream: 'stdout' | 'stderr', data: string) => void;
123
+
124
+ /**
125
+ * Callback when process starts successfully
126
+ */
127
+ onStart?: (process: Process) => void;
128
+
129
+ /**
130
+ * Callback for process errors
131
+ */
132
+ onError?: (error: Error) => void;
133
+ }
134
+
135
+ export type ProcessStatus =
136
+ | 'starting' // Process is being initialized
137
+ | 'running' // Process is actively running
138
+ | 'completed' // Process exited successfully (code 0)
139
+ | 'failed' // Process exited with non-zero code
140
+ | 'killed' // Process was terminated by signal
141
+ | 'error'; // Process failed to start or encountered error
142
+
143
+ export interface Process {
144
+ /**
145
+ * Unique process identifier
146
+ */
147
+ readonly id: string;
148
+
149
+ /**
150
+ * System process ID (if available and running)
151
+ */
152
+ readonly pid?: number;
153
+
154
+ /**
155
+ * Command that was executed
156
+ */
157
+ readonly command: string;
158
+
159
+
160
+ /**
161
+ * Current process status
162
+ */
163
+ readonly status: ProcessStatus;
164
+
165
+ /**
166
+ * When the process was started
167
+ */
168
+ readonly startTime: Date;
169
+
170
+ /**
171
+ * When the process ended (if completed)
172
+ */
173
+ readonly endTime?: Date;
174
+
175
+ /**
176
+ * Process exit code (if completed)
177
+ */
178
+ readonly exitCode?: number;
179
+
180
+ /**
181
+ * Session ID if provided
182
+ */
183
+ readonly sessionId?: string;
184
+
185
+ /**
186
+ * Kill the process
187
+ */
188
+ kill(signal?: string): Promise<void>;
189
+
190
+ /**
191
+ * Get current process status (refreshed)
192
+ */
193
+ getStatus(): Promise<ProcessStatus>;
194
+
195
+ /**
196
+ * Get accumulated logs
197
+ */
198
+ getLogs(): Promise<{ stdout: string; stderr: string }>;
199
+ }
200
+
201
+ // Streaming Types
202
+
203
+ export interface ExecEvent {
204
+ type: 'start' | 'stdout' | 'stderr' | 'complete' | 'error';
205
+ timestamp: string;
206
+ data?: string;
207
+ command?: string;
208
+ exitCode?: number;
209
+ result?: ExecResult;
210
+ error?: string; // Changed to string for serialization
211
+ sessionId?: string;
212
+ }
213
+
214
+ export interface LogEvent {
215
+ type: 'stdout' | 'stderr' | 'exit' | 'error';
216
+ timestamp: string;
217
+ data: string;
218
+ processId: string;
219
+ sessionId?: string;
220
+ exitCode?: number; // For 'exit' events
221
+ }
222
+
223
+ export interface StreamOptions extends BaseExecOptions {
224
+ /**
225
+ * Buffer size for streaming output
226
+ */
227
+ bufferSize?: number;
228
+
229
+ /**
230
+ * AbortSignal for cancelling stream
231
+ */
232
+ signal?: AbortSignal;
233
+ }
234
+
235
+ // Error Types
236
+
237
+ export class SandboxError extends Error {
238
+ constructor(message: string, public code?: string) {
239
+ super(message);
240
+ this.name = 'SandboxError';
241
+ }
242
+ }
243
+
244
+ export class ProcessNotFoundError extends SandboxError {
245
+ constructor(processId: string) {
246
+ super(`Process not found: ${processId}`, 'PROCESS_NOT_FOUND');
247
+ this.name = 'ProcessNotFoundError';
248
+ }
249
+ }
250
+
251
+ export class ProcessAlreadyExistsError extends SandboxError {
252
+ constructor(processId: string) {
253
+ super(`Process already exists: ${processId}`, 'PROCESS_EXISTS');
254
+ this.name = 'ProcessAlreadyExistsError';
255
+ }
256
+ }
257
+
258
+ export class ExecutionTimeoutError extends SandboxError {
259
+ constructor(timeout: number) {
260
+ super(`Execution timed out after ${timeout}ms`, 'EXECUTION_TIMEOUT');
261
+ this.name = 'ExecutionTimeoutError';
262
+ }
263
+ }
264
+
265
+ // Internal Container Types
266
+
267
+ export interface ProcessRecord {
268
+ id: string;
269
+ pid?: number;
270
+ command: string;
271
+ status: ProcessStatus;
272
+ startTime: Date;
273
+ endTime?: Date;
274
+ exitCode?: number;
275
+ sessionId?: string;
276
+
277
+ // Internal fields
278
+ childProcess?: any; // Node.js ChildProcess
279
+ stdout: string; // Accumulated output (ephemeral)
280
+ stderr: string; // Accumulated output (ephemeral)
281
+
282
+ // Streaming
283
+ outputListeners: Set<(stream: 'stdout' | 'stderr', data: string) => void>;
284
+ statusListeners: Set<(status: ProcessStatus) => void>;
285
+ }
286
+
287
+ // Container Request/Response Types
288
+
289
+ export interface StartProcessRequest {
290
+ command: string;
291
+ options?: {
292
+ processId?: string;
293
+ sessionId?: string;
294
+ timeout?: number;
295
+ env?: Record<string, string>;
296
+ cwd?: string;
297
+ encoding?: string;
298
+ autoCleanup?: boolean;
299
+ };
300
+ }
301
+
302
+ export interface StartProcessResponse {
303
+ process: {
304
+ id: string;
305
+ pid?: number;
306
+ command: string;
307
+ status: ProcessStatus;
308
+ startTime: string;
309
+ sessionId?: string;
310
+ };
311
+ }
312
+
313
+ export interface ListProcessesResponse {
314
+ processes: Array<{
315
+ id: string;
316
+ pid?: number;
317
+ command: string;
318
+ status: ProcessStatus;
319
+ startTime: string;
320
+ endTime?: string;
321
+ exitCode?: number;
322
+ sessionId?: string;
323
+ }>;
324
+ }
325
+
326
+ export interface GetProcessResponse {
327
+ process: {
328
+ id: string;
329
+ pid?: number;
330
+ command: string;
331
+ status: ProcessStatus;
332
+ startTime: string;
333
+ endTime?: string;
334
+ exitCode?: number;
335
+ sessionId?: string;
336
+ } | null;
337
+ }
338
+
339
+ export interface GetProcessLogsResponse {
340
+ stdout: string;
341
+ stderr: string;
342
+ processId: string;
343
+ }
344
+
345
+ // Main Sandbox Interface
346
+
347
+ export interface ISandbox {
348
+ // Enhanced execution API
349
+ exec(command: string, options?: ExecOptions): Promise<ExecResult>;
350
+
351
+ // Background process management
352
+ startProcess(command: string, options?: ProcessOptions): Promise<Process>;
353
+ listProcesses(): Promise<Process[]>;
354
+ getProcess(id: string): Promise<Process | null>;
355
+ killProcess(id: string, signal?: string): Promise<void>;
356
+ killAllProcesses(): Promise<number>;
357
+
358
+ // Advanced streaming - returns ReadableStream that can be converted to AsyncIterable
359
+ execStream(command: string, options?: StreamOptions): Promise<ReadableStream<Uint8Array>>;
360
+ streamProcessLogs(processId: string, options?: { signal?: AbortSignal }): Promise<ReadableStream<Uint8Array>>;
361
+
362
+ // Utility methods
363
+ cleanupCompletedProcesses(): Promise<number>;
364
+ getProcessLogs(id: string): Promise<{ stdout: string; stderr: string }>;
365
+ }
366
+
367
+ // Type Guards
368
+
369
+ export function isExecResult(value: any): value is ExecResult {
370
+ return value &&
371
+ typeof value.success === 'boolean' &&
372
+ typeof value.exitCode === 'number' &&
373
+ typeof value.stdout === 'string' &&
374
+ typeof value.stderr === 'string';
375
+ }
376
+
377
+ export function isProcess(value: any): value is Process {
378
+ return value &&
379
+ typeof value.id === 'string' &&
380
+ typeof value.command === 'string' &&
381
+ typeof value.status === 'string';
382
+ }
383
+
384
+ export function isProcessStatus(value: string): value is ProcessStatus {
385
+ return ['starting', 'running', 'completed', 'failed', 'killed', 'error'].includes(value);
386
+ }
package/README.md DELETED
@@ -1,65 +0,0 @@
1
- ## @cloudflare/sandbox
2
-
3
- > **⚠️ Experimental** - This library is currently experimental and we're actively seeking feedback. Please try it out and let us know what you think!
4
-
5
- A library to spin up a sandboxed environment.
6
-
7
- First, setup your wrangler.json to use the sandbox:
8
-
9
- ```jsonc
10
- {
11
- // ...
12
- "containers": [
13
- {
14
- "class_name": "Sandbox",
15
- "image": "./node_modules/@cloudflare/sandbox/Dockerfile",
16
- "name": "sandbox"
17
- }
18
- ],
19
- "durable_objects": {
20
- "bindings": [
21
- {
22
- "class_name": "Sandbox",
23
- "name": "Sandbox"
24
- }
25
- ]
26
- },
27
- "migrations": [
28
- {
29
- "new_sqlite_classes": ["Sandbox"],
30
- "tag": "v1"
31
- }
32
- ]
33
- }
34
- ```
35
-
36
- Then, export the Sandbox class in your worker:
37
-
38
- ```ts
39
- export { Sandbox } from "@cloudflare/sandbox";
40
- ```
41
-
42
- You can then use the Sandbox class in your worker:
43
-
44
- ```ts
45
- import { getSandbox } from "@cloudflare/sandbox";
46
-
47
- export default {
48
- async fetch(request: Request, env: Env) {
49
- const sandbox = getSandbox(env.Sandbox, "my-sandbox");
50
- return sandbox.exec("ls", ["-la"]);
51
- },
52
- };
53
- ```
54
-
55
- ### Methods:
56
-
57
- - `exec(command: string, args: string[], options?: { stream?: boolean })`: Execute a command in the sandbox.
58
- - `gitCheckout(repoUrl: string, options: { branch?: string; targetDir?: string; stream?: boolean })`: Checkout a git repository in the sandbox.
59
- - `mkdir(path: string, options: { recursive?: boolean; stream?: boolean })`: Create a directory in the sandbox.
60
- - `writeFile(path: string, content: string, options: { encoding?: string; stream?: boolean })`: Write content to a file in the sandbox.
61
- - `readFile(path: string, options: { encoding?: string; stream?: boolean })`: Read content from a file in the sandbox.
62
- - `deleteFile(path: string, options?: { stream?: boolean })`: Delete a file from the sandbox.
63
- - `renameFile(oldPath: string, newPath: string, options?: { stream?: boolean })`: Rename a file in the sandbox.
64
- - `moveFile(sourcePath: string, destinationPath: string, options?: { stream?: boolean })`: Move a file from one location to another in the sandbox.
65
- - `ping()`: Ping the sandbox.