@sandbox-engine/sdk 0.1.4 → 0.2.1

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/dist/index.d.mts CHANGED
@@ -30,11 +30,31 @@ interface SandboxOptions {
30
30
  */
31
31
  dockerfile?: string;
32
32
  timeout?: number;
33
+ /** Environment variables injected into the container for the entire session. */
34
+ env?: Record<string, string>;
33
35
  }
34
36
  interface SandboxConnectOptions {
35
37
  serverUrl?: string;
36
38
  apiKey?: string;
37
39
  }
40
+ interface SandboxListOptions {
41
+ serverUrl?: string;
42
+ apiKey?: string;
43
+ }
44
+ interface SandboxSummary {
45
+ id: string;
46
+ status: string;
47
+ template: string;
48
+ createdAt: string;
49
+ timeout: number;
50
+ expiresAt: string | null;
51
+ agentUrl: string;
52
+ ports: Record<string, number>;
53
+ }
54
+ interface StreamLogsOptions {
55
+ onStdout?: (line: string) => void;
56
+ onStderr?: (line: string) => void;
57
+ }
38
58
  interface StartProcessOptions {
39
59
  command?: string;
40
60
  cmd?: string;
@@ -92,10 +112,38 @@ interface FileWriteManyResult {
92
112
  error?: string;
93
113
  }>;
94
114
  }
115
+ interface FileStat {
116
+ path: string;
117
+ size: number;
118
+ mtime: string;
119
+ isDirectory: boolean;
120
+ isFile: boolean;
121
+ }
122
+ interface FileWatchEvent {
123
+ type: 'change' | 'rename' | 'error';
124
+ eventType: 'create' | 'modify' | 'delete';
125
+ path: string;
126
+ data?: string;
127
+ }
128
+ interface FileWatchOptions {
129
+ recursive?: boolean;
130
+ onEvent: (event: FileWatchEvent) => void;
131
+ }
132
+ interface ExposePortOptions {
133
+ /** Human-readable label for this port (e.g. 'api', 'frontend'). */
134
+ name?: string;
135
+ /**
136
+ * Access token embedded in the preview URL.
137
+ * Use a stable value to get a consistent URL across sandbox restarts.
138
+ * When set, the URL becomes: https://{port}-{id}-{token}.{domain}
139
+ */
140
+ token?: string;
141
+ }
95
142
  interface PortMapping {
96
143
  containerPort: number;
97
- hostPort: number;
98
144
  url: string;
145
+ name?: string;
146
+ token?: string;
99
147
  }
100
148
  type WsMessageType = 'stdout' | 'stderr' | 'exit' | 'error' | 'input' | 'resize';
101
149
  interface WsMessage {
@@ -116,6 +164,12 @@ declare class Filesystem {
116
164
  writeMany(files: FileWriteEntry[]): Promise<FileWriteManyResult>;
117
165
  rename(oldPath: string, newPath: string): Promise<void>;
118
166
  delete(filePath: string): Promise<void>;
167
+ stat(filePath: string): Promise<FileStat>;
168
+ readBytes(filePath: string): Promise<Uint8Array>;
169
+ writeBytes(filePath: string, data: Uint8Array | Buffer): Promise<void>;
170
+ watch(dirPath: string, opts: FileWatchOptions): Promise<{
171
+ close: () => void;
172
+ }>;
119
173
  }
120
174
 
121
175
  declare class Process extends EventEmitter {
@@ -144,6 +198,9 @@ declare class BackgroundProcess {
144
198
  }>;
145
199
  waitForPort(port: number, opts?: WaitForPortOptions): Promise<void>;
146
200
  waitForLog(pattern: string | RegExp, opts?: WaitForLogOptions): Promise<string>;
201
+ streamLogs(opts?: StreamLogsOptions): {
202
+ close: () => void;
203
+ };
147
204
  }
148
205
  declare class ProcessManager {
149
206
  private readonly sandboxId;
@@ -181,6 +238,7 @@ declare class Sandbox {
181
238
  readonly processes: ProcessManager;
182
239
  private constructor();
183
240
  private static resolveClient;
241
+ static list(opts?: SandboxListOptions): Promise<SandboxSummary[]>;
184
242
  static create(opts?: SandboxOptions): Promise<Sandbox>;
185
243
  /**
186
244
  * Reconnect to an existing sandbox by ID.
@@ -196,13 +254,31 @@ declare class Sandbox {
196
254
  getProxyEnv: (secretNames: string[]) => Promise<ProxyEnvResult>;
197
255
  };
198
256
  readonly ports: {
257
+ /**
258
+ * List all currently exposed ports for this sandbox.
259
+ */
199
260
  list: () => Promise<PortMapping[]>;
200
- expose: (containerPort: number) => Promise<void>;
261
+ /**
262
+ * Expose any port (1024–65535) and get a public HTTPS URL.
263
+ *
264
+ * @example
265
+ * const { url } = await sandbox.ports.expose(5173, { name: 'vite', token: 'my-token' })
266
+ * // https://5173-{sandboxId}-my-token.preview.yourdomain.com
267
+ */
268
+ expose: (containerPort: number, opts?: ExposePortOptions) => Promise<PortMapping>;
269
+ /**
270
+ * Remove a port from public access.
271
+ */
201
272
  unexpose: (containerPort: number) => Promise<void>;
273
+ /**
274
+ * Check whether a token grants access to an exposed port.
275
+ * Returns true if the port has no token requirement or if the token matches.
276
+ */
277
+ validateToken: (containerPort: number, token: string) => Promise<boolean>;
202
278
  };
203
279
  info(): Promise<SandboxApiResponse>;
204
280
  keepalive(timeout?: number): Promise<SandboxApiResponse>;
205
281
  close(): Promise<void>;
206
282
  }
207
283
 
208
- export { BackgroundProcess, type BackgroundProcessInfo, type ExecOptions, type ExecResult, type FileEntry, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, ProcessManager, type ProxyEnvResult, Sandbox, type SandboxConnectOptions, type SandboxOptions, type StartProcessOptions, Terminal, type WaitForLogOptions, type WaitForPortOptions, type WsMessage, type WsMessageType };
284
+ export { BackgroundProcess, type BackgroundProcessInfo, type ExecOptions, type ExecResult, type FileEntry, type FileStat, type FileWatchEvent, type FileWatchOptions, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, ProcessManager, type ProxyEnvResult, Sandbox, type SandboxConnectOptions, type SandboxListOptions, type SandboxOptions, type SandboxSummary, type StartProcessOptions, type StreamLogsOptions, Terminal, type WaitForLogOptions, type WaitForPortOptions, type WsMessage, type WsMessageType };
package/dist/index.d.ts CHANGED
@@ -30,11 +30,31 @@ interface SandboxOptions {
30
30
  */
31
31
  dockerfile?: string;
32
32
  timeout?: number;
33
+ /** Environment variables injected into the container for the entire session. */
34
+ env?: Record<string, string>;
33
35
  }
34
36
  interface SandboxConnectOptions {
35
37
  serverUrl?: string;
36
38
  apiKey?: string;
37
39
  }
40
+ interface SandboxListOptions {
41
+ serverUrl?: string;
42
+ apiKey?: string;
43
+ }
44
+ interface SandboxSummary {
45
+ id: string;
46
+ status: string;
47
+ template: string;
48
+ createdAt: string;
49
+ timeout: number;
50
+ expiresAt: string | null;
51
+ agentUrl: string;
52
+ ports: Record<string, number>;
53
+ }
54
+ interface StreamLogsOptions {
55
+ onStdout?: (line: string) => void;
56
+ onStderr?: (line: string) => void;
57
+ }
38
58
  interface StartProcessOptions {
39
59
  command?: string;
40
60
  cmd?: string;
@@ -92,10 +112,38 @@ interface FileWriteManyResult {
92
112
  error?: string;
93
113
  }>;
94
114
  }
115
+ interface FileStat {
116
+ path: string;
117
+ size: number;
118
+ mtime: string;
119
+ isDirectory: boolean;
120
+ isFile: boolean;
121
+ }
122
+ interface FileWatchEvent {
123
+ type: 'change' | 'rename' | 'error';
124
+ eventType: 'create' | 'modify' | 'delete';
125
+ path: string;
126
+ data?: string;
127
+ }
128
+ interface FileWatchOptions {
129
+ recursive?: boolean;
130
+ onEvent: (event: FileWatchEvent) => void;
131
+ }
132
+ interface ExposePortOptions {
133
+ /** Human-readable label for this port (e.g. 'api', 'frontend'). */
134
+ name?: string;
135
+ /**
136
+ * Access token embedded in the preview URL.
137
+ * Use a stable value to get a consistent URL across sandbox restarts.
138
+ * When set, the URL becomes: https://{port}-{id}-{token}.{domain}
139
+ */
140
+ token?: string;
141
+ }
95
142
  interface PortMapping {
96
143
  containerPort: number;
97
- hostPort: number;
98
144
  url: string;
145
+ name?: string;
146
+ token?: string;
99
147
  }
100
148
  type WsMessageType = 'stdout' | 'stderr' | 'exit' | 'error' | 'input' | 'resize';
101
149
  interface WsMessage {
@@ -116,6 +164,12 @@ declare class Filesystem {
116
164
  writeMany(files: FileWriteEntry[]): Promise<FileWriteManyResult>;
117
165
  rename(oldPath: string, newPath: string): Promise<void>;
118
166
  delete(filePath: string): Promise<void>;
167
+ stat(filePath: string): Promise<FileStat>;
168
+ readBytes(filePath: string): Promise<Uint8Array>;
169
+ writeBytes(filePath: string, data: Uint8Array | Buffer): Promise<void>;
170
+ watch(dirPath: string, opts: FileWatchOptions): Promise<{
171
+ close: () => void;
172
+ }>;
119
173
  }
120
174
 
121
175
  declare class Process extends EventEmitter {
@@ -144,6 +198,9 @@ declare class BackgroundProcess {
144
198
  }>;
145
199
  waitForPort(port: number, opts?: WaitForPortOptions): Promise<void>;
146
200
  waitForLog(pattern: string | RegExp, opts?: WaitForLogOptions): Promise<string>;
201
+ streamLogs(opts?: StreamLogsOptions): {
202
+ close: () => void;
203
+ };
147
204
  }
148
205
  declare class ProcessManager {
149
206
  private readonly sandboxId;
@@ -181,6 +238,7 @@ declare class Sandbox {
181
238
  readonly processes: ProcessManager;
182
239
  private constructor();
183
240
  private static resolveClient;
241
+ static list(opts?: SandboxListOptions): Promise<SandboxSummary[]>;
184
242
  static create(opts?: SandboxOptions): Promise<Sandbox>;
185
243
  /**
186
244
  * Reconnect to an existing sandbox by ID.
@@ -196,13 +254,31 @@ declare class Sandbox {
196
254
  getProxyEnv: (secretNames: string[]) => Promise<ProxyEnvResult>;
197
255
  };
198
256
  readonly ports: {
257
+ /**
258
+ * List all currently exposed ports for this sandbox.
259
+ */
199
260
  list: () => Promise<PortMapping[]>;
200
- expose: (containerPort: number) => Promise<void>;
261
+ /**
262
+ * Expose any port (1024–65535) and get a public HTTPS URL.
263
+ *
264
+ * @example
265
+ * const { url } = await sandbox.ports.expose(5173, { name: 'vite', token: 'my-token' })
266
+ * // https://5173-{sandboxId}-my-token.preview.yourdomain.com
267
+ */
268
+ expose: (containerPort: number, opts?: ExposePortOptions) => Promise<PortMapping>;
269
+ /**
270
+ * Remove a port from public access.
271
+ */
201
272
  unexpose: (containerPort: number) => Promise<void>;
273
+ /**
274
+ * Check whether a token grants access to an exposed port.
275
+ * Returns true if the port has no token requirement or if the token matches.
276
+ */
277
+ validateToken: (containerPort: number, token: string) => Promise<boolean>;
202
278
  };
203
279
  info(): Promise<SandboxApiResponse>;
204
280
  keepalive(timeout?: number): Promise<SandboxApiResponse>;
205
281
  close(): Promise<void>;
206
282
  }
207
283
 
208
- export { BackgroundProcess, type BackgroundProcessInfo, type ExecOptions, type ExecResult, type FileEntry, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, ProcessManager, type ProxyEnvResult, Sandbox, type SandboxConnectOptions, type SandboxOptions, type StartProcessOptions, Terminal, type WaitForLogOptions, type WaitForPortOptions, type WsMessage, type WsMessageType };
284
+ export { BackgroundProcess, type BackgroundProcessInfo, type ExecOptions, type ExecResult, type FileEntry, type FileStat, type FileWatchEvent, type FileWatchOptions, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, ProcessManager, type ProxyEnvResult, Sandbox, type SandboxConnectOptions, type SandboxListOptions, type SandboxOptions, type SandboxSummary, type StartProcessOptions, type StreamLogsOptions, Terminal, type WaitForLogOptions, type WaitForPortOptions, type WsMessage, type WsMessageType };
package/dist/index.js CHANGED
@@ -149,6 +149,49 @@ var Filesystem = class {
149
149
  async delete(filePath) {
150
150
  await this.client.delete(`/api/sandboxes/${this.sandboxId}/files`, { path: filePath });
151
151
  }
152
+ async stat(filePath) {
153
+ return this.client.get(
154
+ `/api/sandboxes/${this.sandboxId}/files/stat`,
155
+ { path: filePath }
156
+ );
157
+ }
158
+ async readBytes(filePath) {
159
+ const res = await this.client.get(
160
+ `/api/sandboxes/${this.sandboxId}/files/bytes`,
161
+ { path: filePath }
162
+ );
163
+ return Uint8Array.from(Buffer.from(res.data, "base64"));
164
+ }
165
+ async writeBytes(filePath, data) {
166
+ await this.client.post(`/api/sandboxes/${this.sandboxId}/files/bytes`, {
167
+ path: filePath,
168
+ data: Buffer.from(data).toString("base64")
169
+ });
170
+ }
171
+ async watch(dirPath, opts) {
172
+ const ws = this.client.openWebSocket(`/api/sandboxes/${this.sandboxId}/files/watch`);
173
+ await new Promise((resolve, reject) => {
174
+ ws.once("open", () => {
175
+ ws.send(JSON.stringify({ path: dirPath, recursive: opts.recursive ?? false }));
176
+ resolve();
177
+ });
178
+ ws.once("error", reject);
179
+ });
180
+ ws.on("message", (raw) => {
181
+ try {
182
+ const event = JSON.parse(raw.toString());
183
+ opts.onEvent(event);
184
+ } catch {
185
+ }
186
+ });
187
+ return {
188
+ close: () => {
189
+ if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) {
190
+ ws.close();
191
+ }
192
+ }
193
+ };
194
+ }
152
195
  };
153
196
 
154
197
  // src/process.ts
@@ -239,6 +282,29 @@ var BackgroundProcess = class {
239
282
  );
240
283
  return res.match;
241
284
  }
285
+ streamLogs(opts = {}) {
286
+ const ws = this.client.openWebSocket(
287
+ `/api/sandboxes/${this.sandboxId}/processes/logs/stream`
288
+ );
289
+ ws.on("open", () => {
290
+ ws.send(JSON.stringify({ processId: this.id }));
291
+ });
292
+ ws.on("message", (raw) => {
293
+ try {
294
+ const msg = JSON.parse(raw.toString());
295
+ if (msg.type === "stdout") opts.onStdout?.(msg.data ?? "");
296
+ else if (msg.type === "stderr") opts.onStderr?.(msg.data ?? "");
297
+ } catch {
298
+ }
299
+ });
300
+ return {
301
+ close: () => {
302
+ if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) {
303
+ ws.close();
304
+ }
305
+ }
306
+ };
307
+ }
242
308
  };
243
309
  var ProcessManager = class {
244
310
  constructor(sandboxId, client) {
@@ -329,12 +395,17 @@ var Sandbox = class _Sandbox {
329
395
  const apiKey = opts.apiKey ?? process.env.SANDBOX_API_KEY ?? "dev-api-key";
330
396
  return new HttpClient(serverUrl, apiKey);
331
397
  }
398
+ static async list(opts = {}) {
399
+ const client = _Sandbox.resolveClient(opts);
400
+ return client.get("/api/sandboxes");
401
+ }
332
402
  static async create(opts = {}) {
333
403
  const client = _Sandbox.resolveClient(opts);
334
404
  const data = await client.post("/api/sandboxes", {
335
405
  template: opts.template,
336
406
  dockerfile: opts.dockerfile,
337
- timeout: opts.timeout
407
+ timeout: opts.timeout,
408
+ env: opts.env
338
409
  });
339
410
  return new _Sandbox(client, data.id);
340
411
  }
@@ -398,22 +469,45 @@ var Sandbox = class _Sandbox {
398
469
  }
399
470
  };
400
471
  ports = {
472
+ /**
473
+ * List all currently exposed ports for this sandbox.
474
+ */
401
475
  list: async () => {
402
476
  const res = await this.client.get(
403
477
  `/api/sandboxes/${this.id}/ports`
404
478
  );
405
479
  return res.ports;
406
480
  },
407
- expose: async (containerPort) => {
408
- await this.client.post(
481
+ /**
482
+ * Expose any port (1024–65535) and get a public HTTPS URL.
483
+ *
484
+ * @example
485
+ * const { url } = await sandbox.ports.expose(5173, { name: 'vite', token: 'my-token' })
486
+ * // https://5173-{sandboxId}-my-token.preview.yourdomain.com
487
+ */
488
+ expose: async (containerPort, opts = {}) => {
489
+ return this.client.post(
409
490
  `/api/sandboxes/${this.id}/ports/${containerPort}/expose`,
410
- {}
491
+ opts
411
492
  );
412
493
  },
494
+ /**
495
+ * Remove a port from public access.
496
+ */
413
497
  unexpose: async (containerPort) => {
414
498
  await this.client.delete(
415
499
  `/api/sandboxes/${this.id}/ports/${containerPort}/expose`
416
500
  );
501
+ },
502
+ /**
503
+ * Check whether a token grants access to an exposed port.
504
+ * Returns true if the port has no token requirement or if the token matches.
505
+ */
506
+ validateToken: async (containerPort, token) => {
507
+ const res = await this.client.get(
508
+ `/api/sandboxes/${this.id}/ports/${containerPort}/validate?token=${encodeURIComponent(token)}`
509
+ );
510
+ return res.valid;
417
511
  }
418
512
  };
419
513
  async info() {
package/dist/index.mjs CHANGED
@@ -108,6 +108,49 @@ var Filesystem = class {
108
108
  async delete(filePath) {
109
109
  await this.client.delete(`/api/sandboxes/${this.sandboxId}/files`, { path: filePath });
110
110
  }
111
+ async stat(filePath) {
112
+ return this.client.get(
113
+ `/api/sandboxes/${this.sandboxId}/files/stat`,
114
+ { path: filePath }
115
+ );
116
+ }
117
+ async readBytes(filePath) {
118
+ const res = await this.client.get(
119
+ `/api/sandboxes/${this.sandboxId}/files/bytes`,
120
+ { path: filePath }
121
+ );
122
+ return Uint8Array.from(Buffer.from(res.data, "base64"));
123
+ }
124
+ async writeBytes(filePath, data) {
125
+ await this.client.post(`/api/sandboxes/${this.sandboxId}/files/bytes`, {
126
+ path: filePath,
127
+ data: Buffer.from(data).toString("base64")
128
+ });
129
+ }
130
+ async watch(dirPath, opts) {
131
+ const ws = this.client.openWebSocket(`/api/sandboxes/${this.sandboxId}/files/watch`);
132
+ await new Promise((resolve, reject) => {
133
+ ws.once("open", () => {
134
+ ws.send(JSON.stringify({ path: dirPath, recursive: opts.recursive ?? false }));
135
+ resolve();
136
+ });
137
+ ws.once("error", reject);
138
+ });
139
+ ws.on("message", (raw) => {
140
+ try {
141
+ const event = JSON.parse(raw.toString());
142
+ opts.onEvent(event);
143
+ } catch {
144
+ }
145
+ });
146
+ return {
147
+ close: () => {
148
+ if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) {
149
+ ws.close();
150
+ }
151
+ }
152
+ };
153
+ }
111
154
  };
112
155
 
113
156
  // src/process.ts
@@ -198,6 +241,29 @@ var BackgroundProcess = class {
198
241
  );
199
242
  return res.match;
200
243
  }
244
+ streamLogs(opts = {}) {
245
+ const ws = this.client.openWebSocket(
246
+ `/api/sandboxes/${this.sandboxId}/processes/logs/stream`
247
+ );
248
+ ws.on("open", () => {
249
+ ws.send(JSON.stringify({ processId: this.id }));
250
+ });
251
+ ws.on("message", (raw) => {
252
+ try {
253
+ const msg = JSON.parse(raw.toString());
254
+ if (msg.type === "stdout") opts.onStdout?.(msg.data ?? "");
255
+ else if (msg.type === "stderr") opts.onStderr?.(msg.data ?? "");
256
+ } catch {
257
+ }
258
+ });
259
+ return {
260
+ close: () => {
261
+ if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) {
262
+ ws.close();
263
+ }
264
+ }
265
+ };
266
+ }
201
267
  };
202
268
  var ProcessManager = class {
203
269
  constructor(sandboxId, client) {
@@ -288,12 +354,17 @@ var Sandbox = class _Sandbox {
288
354
  const apiKey = opts.apiKey ?? process.env.SANDBOX_API_KEY ?? "dev-api-key";
289
355
  return new HttpClient(serverUrl, apiKey);
290
356
  }
357
+ static async list(opts = {}) {
358
+ const client = _Sandbox.resolveClient(opts);
359
+ return client.get("/api/sandboxes");
360
+ }
291
361
  static async create(opts = {}) {
292
362
  const client = _Sandbox.resolveClient(opts);
293
363
  const data = await client.post("/api/sandboxes", {
294
364
  template: opts.template,
295
365
  dockerfile: opts.dockerfile,
296
- timeout: opts.timeout
366
+ timeout: opts.timeout,
367
+ env: opts.env
297
368
  });
298
369
  return new _Sandbox(client, data.id);
299
370
  }
@@ -357,22 +428,45 @@ var Sandbox = class _Sandbox {
357
428
  }
358
429
  };
359
430
  ports = {
431
+ /**
432
+ * List all currently exposed ports for this sandbox.
433
+ */
360
434
  list: async () => {
361
435
  const res = await this.client.get(
362
436
  `/api/sandboxes/${this.id}/ports`
363
437
  );
364
438
  return res.ports;
365
439
  },
366
- expose: async (containerPort) => {
367
- await this.client.post(
440
+ /**
441
+ * Expose any port (1024–65535) and get a public HTTPS URL.
442
+ *
443
+ * @example
444
+ * const { url } = await sandbox.ports.expose(5173, { name: 'vite', token: 'my-token' })
445
+ * // https://5173-{sandboxId}-my-token.preview.yourdomain.com
446
+ */
447
+ expose: async (containerPort, opts = {}) => {
448
+ return this.client.post(
368
449
  `/api/sandboxes/${this.id}/ports/${containerPort}/expose`,
369
- {}
450
+ opts
370
451
  );
371
452
  },
453
+ /**
454
+ * Remove a port from public access.
455
+ */
372
456
  unexpose: async (containerPort) => {
373
457
  await this.client.delete(
374
458
  `/api/sandboxes/${this.id}/ports/${containerPort}/expose`
375
459
  );
460
+ },
461
+ /**
462
+ * Check whether a token grants access to an exposed port.
463
+ * Returns true if the port has no token requirement or if the token matches.
464
+ */
465
+ validateToken: async (containerPort, token) => {
466
+ const res = await this.client.get(
467
+ `/api/sandboxes/${this.id}/ports/${containerPort}/validate?token=${encodeURIComponent(token)}`
468
+ );
469
+ return res.valid;
376
470
  }
377
471
  };
378
472
  async info() {
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@sandbox-engine/sdk",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "SDK for creating and managing isolated sandbox environments",
5
- "keywords": ["sandbox", "docker", "code-execution", "isolated", "e2b"],
5
+ "keywords": [
6
+ "sandbox",
7
+ "docker",
8
+ "code-execution",
9
+ "isolated",
10
+ "e2b"
11
+ ],
6
12
  "license": "MIT",
7
13
  "main": "dist/index.js",
8
14
  "module": "dist/index.mjs",