@hasna/sandboxes 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 (53) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +256 -0
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +3777 -0
  6. package/dist/db/agents.d.ts +8 -0
  7. package/dist/db/agents.d.ts.map +1 -0
  8. package/dist/db/database.d.ts +9 -0
  9. package/dist/db/database.d.ts.map +1 -0
  10. package/dist/db/events.d.ts +16 -0
  11. package/dist/db/events.d.ts.map +1 -0
  12. package/dist/db/projects.d.ts +9 -0
  13. package/dist/db/projects.d.ts.map +1 -0
  14. package/dist/db/sandboxes.d.ts +12 -0
  15. package/dist/db/sandboxes.d.ts.map +1 -0
  16. package/dist/db/sessions.d.ts +11 -0
  17. package/dist/db/sessions.d.ts.map +1 -0
  18. package/dist/db/webhooks.d.ts +7 -0
  19. package/dist/db/webhooks.d.ts.map +1 -0
  20. package/dist/index.d.ts +13 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +1431 -0
  23. package/dist/lib/agent-runner.d.ts +12 -0
  24. package/dist/lib/agent-runner.d.ts.map +1 -0
  25. package/dist/lib/config.d.ts +10 -0
  26. package/dist/lib/config.d.ts.map +1 -0
  27. package/dist/lib/keepalive.d.ts +5 -0
  28. package/dist/lib/keepalive.d.ts.map +1 -0
  29. package/dist/lib/stream.d.ts +13 -0
  30. package/dist/lib/stream.d.ts.map +1 -0
  31. package/dist/lib/webhook.d.ts +14 -0
  32. package/dist/lib/webhook.d.ts.map +1 -0
  33. package/dist/mcp/index.d.ts +3 -0
  34. package/dist/mcp/index.d.ts.map +1 -0
  35. package/dist/mcp/index.js +5546 -0
  36. package/dist/providers/daytona.d.ts +17 -0
  37. package/dist/providers/daytona.d.ts.map +1 -0
  38. package/dist/providers/e2b.d.ts +17 -0
  39. package/dist/providers/e2b.d.ts.map +1 -0
  40. package/dist/providers/index.d.ts +5 -0
  41. package/dist/providers/index.d.ts.map +1 -0
  42. package/dist/providers/modal.d.ts +25 -0
  43. package/dist/providers/modal.d.ts.map +1 -0
  44. package/dist/providers/types.d.ts +30 -0
  45. package/dist/providers/types.d.ts.map +1 -0
  46. package/dist/server/index.d.ts +3 -0
  47. package/dist/server/index.d.ts.map +1 -0
  48. package/dist/server/index.js +1552 -0
  49. package/dist/server/serve.d.ts +2 -0
  50. package/dist/server/serve.d.ts.map +1 -0
  51. package/dist/types/index.d.ts +199 -0
  52. package/dist/types/index.d.ts.map +1 -0
  53. package/package.json +78 -0
@@ -0,0 +1,1552 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true,
9
+ configurable: true,
10
+ set: (newValue) => all[name] = () => newValue
11
+ });
12
+ };
13
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
14
+
15
+ // src/types/index.ts
16
+ var SandboxNotFoundError, SessionNotFoundError, ProviderError, AgentNotFoundError, ProjectNotFoundError, WebhookNotFoundError;
17
+ var init_types = __esm(() => {
18
+ SandboxNotFoundError = class SandboxNotFoundError extends Error {
19
+ constructor(id) {
20
+ super(`Sandbox not found: ${id}`);
21
+ this.name = "SandboxNotFoundError";
22
+ }
23
+ };
24
+ SessionNotFoundError = class SessionNotFoundError extends Error {
25
+ constructor(id) {
26
+ super(`Session not found: ${id}`);
27
+ this.name = "SessionNotFoundError";
28
+ }
29
+ };
30
+ ProviderError = class ProviderError extends Error {
31
+ provider;
32
+ constructor(provider, message) {
33
+ super(`[${provider}] ${message}`);
34
+ this.name = "ProviderError";
35
+ this.provider = provider;
36
+ }
37
+ };
38
+ AgentNotFoundError = class AgentNotFoundError extends Error {
39
+ constructor(id) {
40
+ super(`Agent not found: ${id}`);
41
+ this.name = "AgentNotFoundError";
42
+ }
43
+ };
44
+ ProjectNotFoundError = class ProjectNotFoundError extends Error {
45
+ constructor(id) {
46
+ super(`Project not found: ${id}`);
47
+ this.name = "ProjectNotFoundError";
48
+ }
49
+ };
50
+ WebhookNotFoundError = class WebhookNotFoundError extends Error {
51
+ constructor(id) {
52
+ super(`Webhook not found: ${id}`);
53
+ this.name = "WebhookNotFoundError";
54
+ }
55
+ };
56
+ });
57
+
58
+ // src/providers/e2b.ts
59
+ var exports_e2b = {};
60
+ __export(exports_e2b, {
61
+ E2BProvider: () => E2BProvider
62
+ });
63
+ import { Sandbox as E2BSandbox } from "@e2b/code-interpreter";
64
+
65
+ class E2BProvider {
66
+ name = "e2b";
67
+ apiKey;
68
+ constructor(apiKey) {
69
+ this.apiKey = apiKey;
70
+ }
71
+ async create(opts) {
72
+ try {
73
+ const sandbox = await E2BSandbox.create({
74
+ apiKey: this.apiKey,
75
+ timeoutMs: (opts?.timeout || 3600) * 1000
76
+ });
77
+ instanceCache.set(sandbox.sandboxId, sandbox);
78
+ return {
79
+ id: sandbox.sandboxId,
80
+ status: "running"
81
+ };
82
+ } catch (err) {
83
+ throw new ProviderError("e2b", `Failed to create sandbox: ${err.message}`);
84
+ }
85
+ }
86
+ async getInstance(sandboxId) {
87
+ const cached = instanceCache.get(sandboxId);
88
+ if (cached)
89
+ return cached;
90
+ try {
91
+ const sandbox = await E2BSandbox.connect(sandboxId, {
92
+ apiKey: this.apiKey
93
+ });
94
+ instanceCache.set(sandboxId, sandbox);
95
+ return sandbox;
96
+ } catch (err) {
97
+ throw new ProviderError("e2b", `Failed to connect to sandbox ${sandboxId}: ${err.message}`);
98
+ }
99
+ }
100
+ async exec(sandboxId, command, opts) {
101
+ const sandbox = await this.getInstance(sandboxId);
102
+ try {
103
+ if (opts?.background) {
104
+ const handle = await sandbox.commands.run(command, {
105
+ background: true,
106
+ onStdout: opts.onStdout ? (data) => opts.onStdout(data) : undefined,
107
+ onStderr: opts.onStderr ? (data) => opts.onStderr(data) : undefined,
108
+ envs: opts.env,
109
+ cwd: opts.cwd,
110
+ timeoutMs: opts.timeout ? opts.timeout * 1000 : undefined
111
+ });
112
+ return {
113
+ kill: async () => {
114
+ await handle.kill();
115
+ },
116
+ wait: async () => {
117
+ const result2 = await handle.wait();
118
+ return {
119
+ exit_code: result2.exitCode,
120
+ stdout: result2.stdout,
121
+ stderr: result2.stderr
122
+ };
123
+ }
124
+ };
125
+ }
126
+ const result = await sandbox.commands.run(command, {
127
+ onStdout: opts?.onStdout ? (data) => opts.onStdout(data) : undefined,
128
+ onStderr: opts?.onStderr ? (data) => opts.onStderr(data) : undefined,
129
+ envs: opts?.env,
130
+ cwd: opts?.cwd,
131
+ timeoutMs: opts?.timeout ? opts.timeout * 1000 : undefined
132
+ });
133
+ return {
134
+ exit_code: result.exitCode,
135
+ stdout: result.stdout,
136
+ stderr: result.stderr
137
+ };
138
+ } catch (err) {
139
+ throw new ProviderError("e2b", `Failed to exec command: ${err.message}`);
140
+ }
141
+ }
142
+ async readFile(sandboxId, path) {
143
+ const sandbox = await this.getInstance(sandboxId);
144
+ try {
145
+ return await sandbox.files.read(path, { format: "text" });
146
+ } catch (err) {
147
+ throw new ProviderError("e2b", `Failed to read file ${path}: ${err.message}`);
148
+ }
149
+ }
150
+ async writeFile(sandboxId, path, content) {
151
+ const sandbox = await this.getInstance(sandboxId);
152
+ try {
153
+ await sandbox.files.write(path, content);
154
+ } catch (err) {
155
+ throw new ProviderError("e2b", `Failed to write file ${path}: ${err.message}`);
156
+ }
157
+ }
158
+ async listFiles(sandboxId, path) {
159
+ const sandbox = await this.getInstance(sandboxId);
160
+ try {
161
+ const entries = await sandbox.files.list(path);
162
+ return entries.map((e) => ({
163
+ path: e.path,
164
+ name: e.name,
165
+ is_dir: e.type === "dir",
166
+ size: 0
167
+ }));
168
+ } catch (err) {
169
+ throw new ProviderError("e2b", `Failed to list files at ${path}: ${err.message}`);
170
+ }
171
+ }
172
+ async stop(sandboxId) {
173
+ const sandbox = await this.getInstance(sandboxId);
174
+ try {
175
+ await sandbox.kill();
176
+ instanceCache.delete(sandboxId);
177
+ } catch (err) {
178
+ throw new ProviderError("e2b", `Failed to stop sandbox: ${err.message}`);
179
+ }
180
+ }
181
+ async delete(sandboxId) {
182
+ await this.stop(sandboxId);
183
+ }
184
+ async keepAlive(sandboxId, durationMs) {
185
+ const sandbox = await this.getInstance(sandboxId);
186
+ try {
187
+ await sandbox.keepAlive(durationMs || 300000);
188
+ } catch (err) {
189
+ throw new ProviderError("e2b", `Failed to keep alive: ${err.message}`);
190
+ }
191
+ }
192
+ }
193
+ var instanceCache;
194
+ var init_e2b = __esm(() => {
195
+ init_types();
196
+ instanceCache = new Map;
197
+ });
198
+
199
+ // src/providers/daytona.ts
200
+ var exports_daytona = {};
201
+ __export(exports_daytona, {
202
+ DaytonaProvider: () => DaytonaProvider
203
+ });
204
+ import { Daytona } from "@daytonaio/sdk";
205
+
206
+ class DaytonaProvider {
207
+ name = "daytona";
208
+ client;
209
+ constructor(apiKey) {
210
+ process.env.DAYTONA_API_KEY = apiKey;
211
+ this.client = new Daytona({ apiKey });
212
+ }
213
+ async create(opts) {
214
+ try {
215
+ const sandbox = await this.client.create({
216
+ language: "typescript",
217
+ image: opts?.image,
218
+ envVars: opts?.envVars,
219
+ autoStopInterval: 0
220
+ }, opts?.timeout || 60);
221
+ instanceCache2.set(sandbox.id, sandbox);
222
+ return {
223
+ id: sandbox.id,
224
+ status: "running"
225
+ };
226
+ } catch (err) {
227
+ throw new ProviderError("daytona", `Failed to create sandbox: ${err.message}`);
228
+ }
229
+ }
230
+ async getInstance(sandboxId) {
231
+ const cached = instanceCache2.get(sandboxId);
232
+ if (cached)
233
+ return cached;
234
+ try {
235
+ const sandbox = await this.client.get(sandboxId);
236
+ instanceCache2.set(sandboxId, sandbox);
237
+ return sandbox;
238
+ } catch (err) {
239
+ throw new ProviderError("daytona", `Failed to connect to sandbox ${sandboxId}: ${err.message}`);
240
+ }
241
+ }
242
+ async exec(sandboxId, command, opts) {
243
+ const sandbox = await this.getInstance(sandboxId);
244
+ try {
245
+ if (opts?.background) {
246
+ const sessionId = `bg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
247
+ await sandbox.process.createSession(sessionId);
248
+ const response = await sandbox.process.executeSessionCommand(sessionId, { command, runAsync: true }, opts.timeout);
249
+ const cmdId = response.cmdId;
250
+ if (opts.onStdout || opts.onStderr) {
251
+ const logCallback = (chunk) => {
252
+ if (opts.onStdout)
253
+ opts.onStdout(chunk);
254
+ };
255
+ sandbox.process.getSessionCommandLogs(sessionId, cmdId, logCallback).catch(() => {});
256
+ }
257
+ return {
258
+ kill: async () => {
259
+ try {
260
+ await sandbox.process.deleteSession(sessionId);
261
+ } catch {}
262
+ },
263
+ wait: async () => {
264
+ let exitCode;
265
+ let output = "";
266
+ while (true) {
267
+ const cmd = await sandbox.process.getSessionCommand(sessionId, cmdId);
268
+ if (cmd.exitCode !== undefined && cmd.exitCode !== null) {
269
+ exitCode = cmd.exitCode;
270
+ break;
271
+ }
272
+ await new Promise((r) => setTimeout(r, 500));
273
+ }
274
+ try {
275
+ output = await sandbox.process.getSessionCommandLogs(sessionId, cmdId);
276
+ } catch {}
277
+ return {
278
+ exit_code: exitCode,
279
+ stdout: output,
280
+ stderr: ""
281
+ };
282
+ }
283
+ };
284
+ }
285
+ const result = await sandbox.process.executeCommand(command, opts?.cwd, opts?.env, opts?.timeout);
286
+ return {
287
+ exit_code: result.exitCode,
288
+ stdout: result.result,
289
+ stderr: ""
290
+ };
291
+ } catch (err) {
292
+ throw new ProviderError("daytona", `Failed to exec command: ${err.message}`);
293
+ }
294
+ }
295
+ async readFile(sandboxId, path) {
296
+ const sandbox = await this.getInstance(sandboxId);
297
+ try {
298
+ const buffer = await sandbox.fs.downloadFile(path);
299
+ return buffer.toString("utf-8");
300
+ } catch (err) {
301
+ throw new ProviderError("daytona", `Failed to read file ${path}: ${err.message}`);
302
+ }
303
+ }
304
+ async writeFile(sandboxId, path, content) {
305
+ const sandbox = await this.getInstance(sandboxId);
306
+ try {
307
+ await sandbox.fs.uploadFile(Buffer.from(content, "utf-8"), path);
308
+ } catch (err) {
309
+ throw new ProviderError("daytona", `Failed to write file ${path}: ${err.message}`);
310
+ }
311
+ }
312
+ async listFiles(sandboxId, path) {
313
+ const sandbox = await this.getInstance(sandboxId);
314
+ try {
315
+ const entries = await sandbox.fs.listFiles(path);
316
+ return entries.map((e) => ({
317
+ path: `${path.replace(/\/$/, "")}/${e.name}`,
318
+ name: e.name,
319
+ is_dir: e.isDir,
320
+ size: e.size
321
+ }));
322
+ } catch (err) {
323
+ throw new ProviderError("daytona", `Failed to list files at ${path}: ${err.message}`);
324
+ }
325
+ }
326
+ async stop(sandboxId) {
327
+ const sandbox = await this.getInstance(sandboxId);
328
+ try {
329
+ await sandbox.stop();
330
+ instanceCache2.delete(sandboxId);
331
+ } catch (err) {
332
+ throw new ProviderError("daytona", `Failed to stop sandbox: ${err.message}`);
333
+ }
334
+ }
335
+ async delete(sandboxId) {
336
+ const sandbox = await this.getInstance(sandboxId);
337
+ try {
338
+ await sandbox.delete();
339
+ instanceCache2.delete(sandboxId);
340
+ } catch (err) {
341
+ throw new ProviderError("daytona", `Failed to delete sandbox: ${err.message}`);
342
+ }
343
+ }
344
+ async keepAlive(sandboxId, durationMs) {
345
+ const sandbox = await this.getInstance(sandboxId);
346
+ try {
347
+ const minutes = durationMs ? Math.ceil(durationMs / 60000) : 0;
348
+ await sandbox.setAutostopInterval(minutes);
349
+ } catch (err) {
350
+ throw new ProviderError("daytona", `Failed to keep alive: ${err.message}`);
351
+ }
352
+ }
353
+ }
354
+ var instanceCache2;
355
+ var init_daytona = __esm(() => {
356
+ init_types();
357
+ instanceCache2 = new Map;
358
+ });
359
+
360
+ // src/providers/modal.ts
361
+ var exports_modal = {};
362
+ __export(exports_modal, {
363
+ ModalProvider: () => ModalProvider
364
+ });
365
+ import { ModalClient } from "modal";
366
+
367
+ class ModalProvider {
368
+ name = "modal";
369
+ client;
370
+ constructor(apiKey) {
371
+ if (apiKey) {
372
+ process.env.MODAL_TOKEN_SECRET = apiKey;
373
+ }
374
+ this.client = new ModalClient;
375
+ }
376
+ async create(opts) {
377
+ try {
378
+ const app = await this.client.apps.fromName("open-sandboxes", {
379
+ createIfMissing: true
380
+ });
381
+ const imageName = opts?.image || "ubuntu:22.04";
382
+ const image = this.client.images.fromRegistry(imageName);
383
+ const timeout = opts?.timeout || 3600;
384
+ const createOpts = { timeout };
385
+ if (opts?.envVars && Object.keys(opts.envVars).length > 0) {
386
+ createOpts.envVars = opts.envVars;
387
+ }
388
+ const sandbox = await this.client.sandboxes.create(app, image, createOpts);
389
+ const sandboxId = sandbox.id || sandbox.sandboxId || String(sandbox);
390
+ sandboxCache.set(sandboxId, sandbox);
391
+ return {
392
+ id: sandboxId,
393
+ status: "running"
394
+ };
395
+ } catch (err) {
396
+ throw new ProviderError("modal", `Failed to create sandbox: ${err.message}`);
397
+ }
398
+ }
399
+ getSandbox(sandboxId) {
400
+ const cached = sandboxCache.get(sandboxId);
401
+ if (!cached) {
402
+ throw new ProviderError("modal", `Sandbox ${sandboxId} not found in cache. Modal sandboxes must be created via this provider.`);
403
+ }
404
+ return cached;
405
+ }
406
+ async exec(sandboxId, command, opts) {
407
+ const sandbox = this.getSandbox(sandboxId);
408
+ try {
409
+ const parts = this.parseCommand(command);
410
+ const program = parts[0];
411
+ const args = parts.slice(1);
412
+ if (opts?.background) {
413
+ const proc2 = await sandbox.exec(program, ...args);
414
+ return {
415
+ kill: async () => {
416
+ try {
417
+ if (proc2.kill)
418
+ await proc2.kill();
419
+ else if (proc2.terminate)
420
+ await proc2.terminate();
421
+ } catch {}
422
+ },
423
+ wait: async () => {
424
+ let stdout2 = "";
425
+ let stderr2 = "";
426
+ try {
427
+ if (proc2.stdout && proc2.stdout.read) {
428
+ stdout2 = await proc2.stdout.read();
429
+ }
430
+ if (proc2.stderr && proc2.stderr.read) {
431
+ stderr2 = await proc2.stderr.read();
432
+ }
433
+ } catch {}
434
+ const exitCode2 = proc2.exitCode ?? proc2.exit_code ?? proc2.returncode ?? 0;
435
+ if (opts?.onStdout && stdout2)
436
+ opts.onStdout(stdout2);
437
+ if (opts?.onStderr && stderr2)
438
+ opts.onStderr(stderr2);
439
+ return {
440
+ exit_code: exitCode2,
441
+ stdout: stdout2,
442
+ stderr: stderr2
443
+ };
444
+ }
445
+ };
446
+ }
447
+ const proc = await sandbox.exec(program, ...args);
448
+ let stdout = "";
449
+ let stderr = "";
450
+ if (proc.stdout && proc.stdout.read) {
451
+ stdout = await proc.stdout.read();
452
+ }
453
+ if (proc.stderr && proc.stderr.read) {
454
+ stderr = await proc.stderr.read();
455
+ }
456
+ const exitCode = proc.exitCode ?? proc.exit_code ?? proc.returncode ?? 0;
457
+ if (opts?.onStdout && stdout)
458
+ opts.onStdout(stdout);
459
+ if (opts?.onStderr && stderr)
460
+ opts.onStderr(stderr);
461
+ return {
462
+ exit_code: exitCode,
463
+ stdout,
464
+ stderr
465
+ };
466
+ } catch (err) {
467
+ throw new ProviderError("modal", `Failed to exec command: ${err.message}`);
468
+ }
469
+ }
470
+ async readFile(sandboxId, path) {
471
+ try {
472
+ const result = await this.exec(sandboxId, `cat ${this.shellEscape(path)}`);
473
+ const execResult = result;
474
+ if (execResult.exit_code !== 0) {
475
+ throw new Error(execResult.stderr || `cat exited with code ${execResult.exit_code}`);
476
+ }
477
+ return execResult.stdout;
478
+ } catch (err) {
479
+ if (err instanceof ProviderError)
480
+ throw err;
481
+ throw new ProviderError("modal", `Failed to read file ${path}: ${err.message}`);
482
+ }
483
+ }
484
+ async writeFile(sandboxId, path, content) {
485
+ try {
486
+ const encoded = Buffer.from(content).toString("base64");
487
+ const dirPath = path.substring(0, path.lastIndexOf("/")) || "/";
488
+ const cmd = `mkdir -p ${this.shellEscape(dirPath)} && echo ${encoded} | base64 -d > ${this.shellEscape(path)}`;
489
+ const result = await this.exec(sandboxId, `sh -c ${this.shellEscape(cmd)}`);
490
+ const execResult = result;
491
+ if (execResult.exit_code !== 0) {
492
+ throw new Error(execResult.stderr || `write exited with code ${execResult.exit_code}`);
493
+ }
494
+ } catch (err) {
495
+ if (err instanceof ProviderError)
496
+ throw err;
497
+ throw new ProviderError("modal", `Failed to write file ${path}: ${err.message}`);
498
+ }
499
+ }
500
+ async listFiles(sandboxId, path) {
501
+ try {
502
+ const result = await this.exec(sandboxId, `ls -la ${this.shellEscape(path)}`);
503
+ const execResult = result;
504
+ if (execResult.exit_code !== 0) {
505
+ throw new Error(execResult.stderr || `ls exited with code ${execResult.exit_code}`);
506
+ }
507
+ const lines = execResult.stdout.split(`
508
+ `).filter((l) => l.trim());
509
+ const files = [];
510
+ for (const line of lines) {
511
+ if (line.startsWith("total "))
512
+ continue;
513
+ const parts = line.split(/\s+/);
514
+ if (parts.length < 9)
515
+ continue;
516
+ const permissions = parts[0] || "";
517
+ const size = parseInt(parts[4] || "0", 10) || 0;
518
+ const name = parts.slice(8).join(" ");
519
+ if (name === "." || name === "..")
520
+ continue;
521
+ const isDir = permissions.startsWith("d");
522
+ const normalizedPath = path.endsWith("/") ? path : path + "/";
523
+ files.push({
524
+ path: normalizedPath + name,
525
+ name,
526
+ is_dir: isDir,
527
+ size
528
+ });
529
+ }
530
+ return files;
531
+ } catch (err) {
532
+ if (err instanceof ProviderError)
533
+ throw err;
534
+ throw new ProviderError("modal", `Failed to list files at ${path}: ${err.message}`);
535
+ }
536
+ }
537
+ async stop(sandboxId) {
538
+ const sandbox = this.getSandbox(sandboxId);
539
+ try {
540
+ await sandbox.terminate();
541
+ sandboxCache.delete(sandboxId);
542
+ } catch (err) {
543
+ throw new ProviderError("modal", `Failed to stop sandbox: ${err.message}`);
544
+ }
545
+ }
546
+ async delete(sandboxId) {
547
+ await this.stop(sandboxId);
548
+ }
549
+ async keepAlive(_sandboxId, _durationMs) {}
550
+ parseCommand(command) {
551
+ const args = [];
552
+ let current = "";
553
+ let inSingle = false;
554
+ let inDouble = false;
555
+ let escape = false;
556
+ for (const char of command) {
557
+ if (escape) {
558
+ current += char;
559
+ escape = false;
560
+ continue;
561
+ }
562
+ if (char === "\\") {
563
+ escape = true;
564
+ continue;
565
+ }
566
+ if (char === "'" && !inDouble) {
567
+ inSingle = !inSingle;
568
+ continue;
569
+ }
570
+ if (char === '"' && !inSingle) {
571
+ inDouble = !inDouble;
572
+ continue;
573
+ }
574
+ if (char === " " && !inSingle && !inDouble) {
575
+ if (current) {
576
+ args.push(current);
577
+ current = "";
578
+ }
579
+ continue;
580
+ }
581
+ current += char;
582
+ }
583
+ if (current)
584
+ args.push(current);
585
+ return args;
586
+ }
587
+ shellEscape(str) {
588
+ return "'" + str.replace(/'/g, "'\\''") + "'";
589
+ }
590
+ }
591
+ var sandboxCache;
592
+ var init_modal = __esm(() => {
593
+ init_types();
594
+ sandboxCache = new Map;
595
+ });
596
+
597
+ // src/server/index.ts
598
+ import { parseArgs } from "util";
599
+
600
+ // src/db/database.ts
601
+ import { Database } from "bun:sqlite";
602
+ import { existsSync, mkdirSync } from "fs";
603
+ import { dirname, join, resolve } from "path";
604
+ import { randomUUID } from "crypto";
605
+ function isInMemoryDb(path) {
606
+ return path === ":memory:" || path.startsWith("file::memory:");
607
+ }
608
+ function findNearestDb(startDir) {
609
+ let dir = resolve(startDir);
610
+ while (true) {
611
+ const candidate = join(dir, ".sandboxes", "sandboxes.db");
612
+ if (existsSync(candidate))
613
+ return candidate;
614
+ const parent = dirname(dir);
615
+ if (parent === dir)
616
+ break;
617
+ dir = parent;
618
+ }
619
+ return null;
620
+ }
621
+ function getDbPath() {
622
+ if (process.env["SANDBOXES_DB_PATH"]) {
623
+ return process.env["SANDBOXES_DB_PATH"];
624
+ }
625
+ const cwd = process.cwd();
626
+ const nearest = findNearestDb(cwd);
627
+ if (nearest)
628
+ return nearest;
629
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
630
+ return join(home, ".sandboxes", "sandboxes.db");
631
+ }
632
+ function ensureDir(filePath) {
633
+ if (isInMemoryDb(filePath))
634
+ return;
635
+ const dir = dirname(resolve(filePath));
636
+ if (!existsSync(dir)) {
637
+ mkdirSync(dir, { recursive: true });
638
+ }
639
+ }
640
+ var MIGRATIONS = [
641
+ `
642
+ CREATE TABLE IF NOT EXISTS projects (
643
+ id TEXT PRIMARY KEY,
644
+ name TEXT NOT NULL,
645
+ path TEXT UNIQUE NOT NULL,
646
+ description TEXT,
647
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
648
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
649
+ );
650
+
651
+ CREATE TABLE IF NOT EXISTS agents (
652
+ id TEXT PRIMARY KEY,
653
+ name TEXT NOT NULL UNIQUE,
654
+ description TEXT,
655
+ metadata TEXT DEFAULT '{}',
656
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
657
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now'))
658
+ );
659
+ CREATE INDEX IF NOT EXISTS idx_agents_name ON agents(name);
660
+
661
+ CREATE TABLE IF NOT EXISTS sandboxes (
662
+ id TEXT PRIMARY KEY,
663
+ provider TEXT NOT NULL CHECK(provider IN ('e2b', 'daytona', 'modal')),
664
+ provider_sandbox_id TEXT,
665
+ name TEXT,
666
+ status TEXT NOT NULL DEFAULT 'creating' CHECK(status IN ('creating', 'running', 'paused', 'stopped', 'deleted', 'error')),
667
+ image TEXT,
668
+ timeout INTEGER DEFAULT 3600,
669
+ config TEXT DEFAULT '{}',
670
+ env_vars TEXT DEFAULT '{}',
671
+ keep_alive_until TEXT,
672
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
673
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
674
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
675
+ );
676
+ CREATE INDEX IF NOT EXISTS idx_sandboxes_status ON sandboxes(status);
677
+ CREATE INDEX IF NOT EXISTS idx_sandboxes_provider ON sandboxes(provider);
678
+ CREATE INDEX IF NOT EXISTS idx_sandboxes_project ON sandboxes(project_id);
679
+
680
+ CREATE TABLE IF NOT EXISTS sandbox_sessions (
681
+ id TEXT PRIMARY KEY,
682
+ sandbox_id TEXT NOT NULL REFERENCES sandboxes(id) ON DELETE CASCADE,
683
+ agent_name TEXT,
684
+ agent_type TEXT CHECK(agent_type IN ('claude', 'codex', 'gemini', 'custom')),
685
+ command TEXT,
686
+ status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running', 'completed', 'failed', 'killed')),
687
+ exit_code INTEGER,
688
+ started_at TEXT NOT NULL DEFAULT (datetime('now')),
689
+ ended_at TEXT
690
+ );
691
+ CREATE INDEX IF NOT EXISTS idx_sessions_sandbox ON sandbox_sessions(sandbox_id);
692
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sandbox_sessions(status);
693
+
694
+ CREATE TABLE IF NOT EXISTS sandbox_events (
695
+ id TEXT PRIMARY KEY,
696
+ sandbox_id TEXT NOT NULL REFERENCES sandboxes(id) ON DELETE CASCADE,
697
+ session_id TEXT REFERENCES sandbox_sessions(id) ON DELETE CASCADE,
698
+ type TEXT NOT NULL CHECK(type IN ('stdout', 'stderr', 'lifecycle', 'agent')),
699
+ data TEXT,
700
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
701
+ );
702
+ CREATE INDEX IF NOT EXISTS idx_events_sandbox ON sandbox_events(sandbox_id);
703
+ CREATE INDEX IF NOT EXISTS idx_events_session ON sandbox_events(session_id);
704
+ CREATE INDEX IF NOT EXISTS idx_events_type ON sandbox_events(type);
705
+
706
+ CREATE TABLE IF NOT EXISTS webhooks (
707
+ id TEXT PRIMARY KEY,
708
+ url TEXT NOT NULL,
709
+ events TEXT NOT NULL DEFAULT '[]',
710
+ secret TEXT,
711
+ active INTEGER NOT NULL DEFAULT 1,
712
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
713
+ );
714
+
715
+ CREATE TABLE IF NOT EXISTS _migrations (
716
+ id INTEGER PRIMARY KEY,
717
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
718
+ );
719
+
720
+ INSERT OR IGNORE INTO _migrations (id) VALUES (1);
721
+ `
722
+ ];
723
+ var db = null;
724
+ function runMigrations(database) {
725
+ database.exec("CREATE TABLE IF NOT EXISTS _migrations (id INTEGER PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (datetime('now')))");
726
+ const applied = new Set(database.query("SELECT id FROM _migrations").all().map((r) => r.id));
727
+ for (let i = 0;i < MIGRATIONS.length; i++) {
728
+ const migrationId = i + 1;
729
+ if (!applied.has(migrationId)) {
730
+ database.exec(MIGRATIONS[i]);
731
+ }
732
+ }
733
+ }
734
+ function getDatabase() {
735
+ if (db)
736
+ return db;
737
+ const dbPath = getDbPath();
738
+ ensureDir(dbPath);
739
+ db = new Database(dbPath);
740
+ db.exec("PRAGMA journal_mode = WAL");
741
+ db.exec("PRAGMA foreign_keys = ON");
742
+ runMigrations(db);
743
+ return db;
744
+ }
745
+ function uuid() {
746
+ return randomUUID();
747
+ }
748
+ function now() {
749
+ return new Date().toISOString().replace("T", " ").replace("Z", "");
750
+ }
751
+ function resolvePartialId(table, partialId) {
752
+ const database = getDatabase();
753
+ const rows = database.query(`SELECT id FROM ${table} WHERE id LIKE ? || '%'`).all(partialId);
754
+ if (rows.length === 1)
755
+ return rows[0].id;
756
+ if (rows.length === 0)
757
+ return null;
758
+ const exact = rows.find((r) => r.id === partialId);
759
+ if (exact)
760
+ return exact.id;
761
+ return null;
762
+ }
763
+
764
+ // src/db/sandboxes.ts
765
+ init_types();
766
+ function rowToSandbox(row) {
767
+ return {
768
+ id: row.id,
769
+ provider: row.provider,
770
+ provider_sandbox_id: row.provider_sandbox_id,
771
+ name: row.name,
772
+ status: row.status,
773
+ image: row.image,
774
+ timeout: row.timeout,
775
+ config: JSON.parse(row.config),
776
+ env_vars: JSON.parse(row.env_vars),
777
+ keep_alive_until: row.keep_alive_until,
778
+ project_id: row.project_id,
779
+ created_at: row.created_at,
780
+ updated_at: row.updated_at
781
+ };
782
+ }
783
+ function createSandbox(input) {
784
+ const db2 = getDatabase();
785
+ const id = uuid();
786
+ const timestamp = now();
787
+ const provider = input.provider ?? "e2b";
788
+ const name = input.name ?? null;
789
+ const image = input.image ?? null;
790
+ const timeout = input.timeout ?? 3600;
791
+ const config = JSON.stringify(input.config ?? {});
792
+ const env_vars = JSON.stringify(input.env_vars ?? {});
793
+ const project_id = input.project_id ?? null;
794
+ db2.query(`INSERT INTO sandboxes (id, provider, name, status, image, timeout, config, env_vars, project_id, created_at, updated_at)
795
+ VALUES (?, ?, ?, 'creating', ?, ?, ?, ?, ?, ?, ?)`).run(id, provider, name, image, timeout, config, env_vars, project_id, timestamp, timestamp);
796
+ return getSandbox(id);
797
+ }
798
+ function getSandbox(id) {
799
+ const db2 = getDatabase();
800
+ const resolvedId = resolvePartialId("sandboxes", id);
801
+ if (!resolvedId)
802
+ throw new SandboxNotFoundError(id);
803
+ const row = db2.query("SELECT * FROM sandboxes WHERE id = ?").get(resolvedId);
804
+ if (!row)
805
+ throw new SandboxNotFoundError(id);
806
+ return rowToSandbox(row);
807
+ }
808
+ function listSandboxes(opts) {
809
+ const db2 = getDatabase();
810
+ const conditions = [];
811
+ const params = [];
812
+ if (opts?.status) {
813
+ conditions.push("status = ?");
814
+ params.push(opts.status);
815
+ }
816
+ if (opts?.provider) {
817
+ conditions.push("provider = ?");
818
+ params.push(opts.provider);
819
+ }
820
+ if (opts?.project_id) {
821
+ conditions.push("project_id = ?");
822
+ params.push(opts.project_id);
823
+ }
824
+ const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
825
+ const rows = db2.query(`SELECT * FROM sandboxes${where} ORDER BY created_at DESC`).all(...params);
826
+ return rows.map(rowToSandbox);
827
+ }
828
+ function updateSandbox(id, updates) {
829
+ const db2 = getDatabase();
830
+ const resolvedId = resolvePartialId("sandboxes", id);
831
+ if (!resolvedId)
832
+ throw new SandboxNotFoundError(id);
833
+ const setClauses = [];
834
+ const params = [];
835
+ if (updates.status !== undefined) {
836
+ setClauses.push("status = ?");
837
+ params.push(updates.status);
838
+ }
839
+ if (updates.provider_sandbox_id !== undefined) {
840
+ setClauses.push("provider_sandbox_id = ?");
841
+ params.push(updates.provider_sandbox_id);
842
+ }
843
+ if (updates.name !== undefined) {
844
+ setClauses.push("name = ?");
845
+ params.push(updates.name);
846
+ }
847
+ if (updates.image !== undefined) {
848
+ setClauses.push("image = ?");
849
+ params.push(updates.image);
850
+ }
851
+ if (updates.timeout !== undefined) {
852
+ setClauses.push("timeout = ?");
853
+ params.push(updates.timeout);
854
+ }
855
+ if (updates.config !== undefined) {
856
+ setClauses.push("config = ?");
857
+ params.push(JSON.stringify(updates.config));
858
+ }
859
+ if (updates.env_vars !== undefined) {
860
+ setClauses.push("env_vars = ?");
861
+ params.push(JSON.stringify(updates.env_vars));
862
+ }
863
+ if (updates.keep_alive_until !== undefined) {
864
+ setClauses.push("keep_alive_until = ?");
865
+ params.push(updates.keep_alive_until);
866
+ }
867
+ if (setClauses.length === 0) {
868
+ return getSandbox(resolvedId);
869
+ }
870
+ setClauses.push("updated_at = ?");
871
+ params.push(now());
872
+ params.push(resolvedId);
873
+ db2.query(`UPDATE sandboxes SET ${setClauses.join(", ")} WHERE id = ?`).run(...params);
874
+ return getSandbox(resolvedId);
875
+ }
876
+ function deleteSandbox(id) {
877
+ const db2 = getDatabase();
878
+ const resolvedId = resolvePartialId("sandboxes", id);
879
+ if (!resolvedId)
880
+ throw new SandboxNotFoundError(id);
881
+ db2.query("DELETE FROM sandboxes WHERE id = ?").run(resolvedId);
882
+ }
883
+
884
+ // src/db/sessions.ts
885
+ init_types();
886
+ function rowToSession(row) {
887
+ return {
888
+ ...row,
889
+ agent_type: row.agent_type,
890
+ status: row.status
891
+ };
892
+ }
893
+ function createSession(input) {
894
+ const db2 = getDatabase();
895
+ const id = uuid();
896
+ const startedAt = now();
897
+ db2.query(`INSERT INTO sandbox_sessions (id, sandbox_id, agent_name, agent_type, command, status, started_at)
898
+ VALUES (?, ?, ?, ?, ?, 'running', ?)`).run(id, input.sandbox_id, input.agent_name ?? null, input.agent_type ?? null, input.command ?? null, startedAt);
899
+ return getSession(id);
900
+ }
901
+ function getSession(id) {
902
+ const db2 = getDatabase();
903
+ const resolvedId = resolvePartialId("sandbox_sessions", id) ?? id;
904
+ const row = db2.query("SELECT * FROM sandbox_sessions WHERE id = ?").get(resolvedId);
905
+ if (!row)
906
+ throw new SessionNotFoundError(id);
907
+ return rowToSession(row);
908
+ }
909
+ function listSessions(opts) {
910
+ const db2 = getDatabase();
911
+ const conditions = [];
912
+ const params = [];
913
+ if (opts?.sandbox_id) {
914
+ conditions.push("sandbox_id = ?");
915
+ params.push(opts.sandbox_id);
916
+ }
917
+ if (opts?.status) {
918
+ conditions.push("status = ?");
919
+ params.push(opts.status);
920
+ }
921
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
922
+ const rows = db2.query(`SELECT * FROM sandbox_sessions ${where} ORDER BY started_at DESC`).all(...params);
923
+ return rows.map(rowToSession);
924
+ }
925
+ function endSession(id, exit_code, status) {
926
+ const session = getSession(id);
927
+ const endedAt = now();
928
+ const finalStatus = status ?? "completed";
929
+ const db2 = getDatabase();
930
+ db2.query(`UPDATE sandbox_sessions SET status = ?, exit_code = ?, ended_at = ? WHERE id = ?`).run(finalStatus, exit_code, endedAt, session.id);
931
+ return getSession(session.id);
932
+ }
933
+
934
+ // src/db/events.ts
935
+ function rowToEvent(row) {
936
+ return {
937
+ id: row.id,
938
+ sandbox_id: row.sandbox_id,
939
+ session_id: row.session_id,
940
+ type: row.type,
941
+ data: row.data,
942
+ created_at: row.created_at
943
+ };
944
+ }
945
+ function addEvent(input) {
946
+ const db2 = getDatabase();
947
+ const id = uuid();
948
+ db2.query(`INSERT INTO sandbox_events (id, sandbox_id, session_id, type, data)
949
+ VALUES (?, ?, ?, ?, ?)`).run(id, input.sandbox_id, input.session_id ?? null, input.type, input.data ?? null);
950
+ const row = db2.query("SELECT * FROM sandbox_events WHERE id = ?").get(id);
951
+ return rowToEvent(row);
952
+ }
953
+ function listEvents(opts) {
954
+ const db2 = getDatabase();
955
+ const conditions = [];
956
+ const params = [];
957
+ if (opts?.sandbox_id) {
958
+ conditions.push("sandbox_id = ?");
959
+ params.push(opts.sandbox_id);
960
+ }
961
+ if (opts?.session_id) {
962
+ conditions.push("session_id = ?");
963
+ params.push(opts.session_id);
964
+ }
965
+ if (opts?.type) {
966
+ conditions.push("type = ?");
967
+ params.push(opts.type);
968
+ }
969
+ const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
970
+ const limit = opts?.limit ?? 100;
971
+ const offset = opts?.offset ?? 0;
972
+ const rows = db2.query(`SELECT * FROM sandbox_events${where} ORDER BY created_at ASC LIMIT ? OFFSET ?`).all(...[...params, limit, offset]);
973
+ return rows.map(rowToEvent);
974
+ }
975
+
976
+ // src/db/agents.ts
977
+ init_types();
978
+ function rowToAgent(row) {
979
+ return {
980
+ id: row.id,
981
+ name: row.name,
982
+ description: row.description,
983
+ metadata: JSON.parse(row.metadata),
984
+ created_at: row.created_at,
985
+ last_seen_at: row.last_seen_at
986
+ };
987
+ }
988
+ function registerAgent(input) {
989
+ const db2 = getDatabase();
990
+ const timestamp = now();
991
+ const existing = getAgentByName(input.name);
992
+ if (existing) {
993
+ db2.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(timestamp, existing.id);
994
+ return getAgent(existing.id);
995
+ }
996
+ const id = uuid();
997
+ const description = input.description ?? null;
998
+ db2.query(`INSERT OR IGNORE INTO agents (id, name, description, metadata, created_at, last_seen_at)
999
+ VALUES (?, ?, ?, '{}', ?, ?)`).run(id, input.name, description, timestamp, timestamp);
1000
+ return getAgent(id);
1001
+ }
1002
+ function getAgent(id) {
1003
+ const db2 = getDatabase();
1004
+ const resolvedId = resolvePartialId("agents", id);
1005
+ if (!resolvedId)
1006
+ throw new AgentNotFoundError(id);
1007
+ const row = db2.query("SELECT * FROM agents WHERE id = ?").get(resolvedId);
1008
+ if (!row)
1009
+ throw new AgentNotFoundError(id);
1010
+ return rowToAgent(row);
1011
+ }
1012
+ function getAgentByName(name) {
1013
+ const db2 = getDatabase();
1014
+ const row = db2.query("SELECT * FROM agents WHERE name = ?").get(name);
1015
+ if (!row)
1016
+ return null;
1017
+ return rowToAgent(row);
1018
+ }
1019
+ function listAgents() {
1020
+ const db2 = getDatabase();
1021
+ const rows = db2.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
1022
+ return rows.map(rowToAgent);
1023
+ }
1024
+
1025
+ // src/db/projects.ts
1026
+ init_types();
1027
+ function rowToProject(row) {
1028
+ return {
1029
+ id: row.id,
1030
+ name: row.name,
1031
+ path: row.path,
1032
+ description: row.description,
1033
+ created_at: row.created_at,
1034
+ updated_at: row.updated_at
1035
+ };
1036
+ }
1037
+ function createProject(input) {
1038
+ const db2 = getDatabase();
1039
+ const id = uuid();
1040
+ const timestamp = now();
1041
+ const description = input.description ?? null;
1042
+ db2.query(`INSERT INTO projects (id, name, path, description, created_at, updated_at)
1043
+ VALUES (?, ?, ?, ?, ?, ?)`).run(id, input.name, input.path, description, timestamp, timestamp);
1044
+ return getProject(id);
1045
+ }
1046
+ function getProject(id) {
1047
+ const db2 = getDatabase();
1048
+ const resolvedId = resolvePartialId("projects", id);
1049
+ if (!resolvedId)
1050
+ throw new ProjectNotFoundError(id);
1051
+ const row = db2.query("SELECT * FROM projects WHERE id = ?").get(resolvedId);
1052
+ if (!row)
1053
+ throw new ProjectNotFoundError(id);
1054
+ return rowToProject(row);
1055
+ }
1056
+ function getProjectByPath(path) {
1057
+ const db2 = getDatabase();
1058
+ const row = db2.query("SELECT * FROM projects WHERE path = ?").get(path);
1059
+ if (!row)
1060
+ return null;
1061
+ return rowToProject(row);
1062
+ }
1063
+ function listProjects() {
1064
+ const db2 = getDatabase();
1065
+ const rows = db2.query("SELECT * FROM projects ORDER BY created_at DESC").all();
1066
+ return rows.map(rowToProject);
1067
+ }
1068
+ function ensureProject(name, path) {
1069
+ const existing = getProjectByPath(path);
1070
+ if (existing)
1071
+ return existing;
1072
+ return createProject({ name, path });
1073
+ }
1074
+
1075
+ // src/db/webhooks.ts
1076
+ import { randomBytes } from "crypto";
1077
+ init_types();
1078
+ function rowToWebhook(row) {
1079
+ return {
1080
+ id: row.id,
1081
+ url: row.url,
1082
+ events: JSON.parse(row.events),
1083
+ secret: row.secret,
1084
+ active: row.active === 1,
1085
+ created_at: row.created_at
1086
+ };
1087
+ }
1088
+ function createWebhook(input) {
1089
+ const db2 = getDatabase();
1090
+ const id = uuid();
1091
+ const timestamp = now();
1092
+ const events = JSON.stringify(input.events ?? []);
1093
+ const secret = input.secret ?? randomBytes(32).toString("hex");
1094
+ db2.query(`INSERT INTO webhooks (id, url, events, secret, active, created_at)
1095
+ VALUES (?, ?, ?, ?, 1, ?)`).run(id, input.url, events, secret, timestamp);
1096
+ return getWebhook(id);
1097
+ }
1098
+ function getWebhook(id) {
1099
+ const db2 = getDatabase();
1100
+ const resolvedId = resolvePartialId("webhooks", id);
1101
+ if (!resolvedId)
1102
+ throw new WebhookNotFoundError(id);
1103
+ const row = db2.query("SELECT * FROM webhooks WHERE id = ?").get(resolvedId);
1104
+ if (!row)
1105
+ throw new WebhookNotFoundError(id);
1106
+ return rowToWebhook(row);
1107
+ }
1108
+ function listWebhooks() {
1109
+ const db2 = getDatabase();
1110
+ const rows = db2.query("SELECT * FROM webhooks ORDER BY created_at DESC").all();
1111
+ return rows.map(rowToWebhook);
1112
+ }
1113
+ function deleteWebhook(id) {
1114
+ const db2 = getDatabase();
1115
+ const resolvedId = resolvePartialId("webhooks", id);
1116
+ if (!resolvedId)
1117
+ throw new WebhookNotFoundError(id);
1118
+ db2.query("DELETE FROM webhooks WHERE id = ?").run(resolvedId);
1119
+ }
1120
+
1121
+ // src/providers/index.ts
1122
+ init_types();
1123
+
1124
+ // src/lib/config.ts
1125
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
1126
+ import { dirname as dirname2, join as join2 } from "path";
1127
+ var ENV_KEYS = {
1128
+ e2b: "E2B_API_KEY",
1129
+ daytona: "DAYTONA_API_KEY",
1130
+ modal: "MODAL_TOKEN_ID"
1131
+ };
1132
+ function getConfigPath() {
1133
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
1134
+ return join2(home, ".sandboxes", "config.json");
1135
+ }
1136
+ function loadConfig() {
1137
+ const configPath = getConfigPath();
1138
+ if (!existsSync2(configPath))
1139
+ return {};
1140
+ try {
1141
+ const raw = readFileSync(configPath, "utf-8");
1142
+ return JSON.parse(raw);
1143
+ } catch {
1144
+ return {};
1145
+ }
1146
+ }
1147
+ function getDefaultProvider() {
1148
+ const config = loadConfig();
1149
+ return config.default_provider || "e2b";
1150
+ }
1151
+ function getDefaultTimeout() {
1152
+ const config = loadConfig();
1153
+ return config.default_timeout || 3600;
1154
+ }
1155
+ function getProviderApiKey(provider) {
1156
+ const config = loadConfig();
1157
+ const providerConfig = config.providers?.[provider];
1158
+ if (providerConfig?.api_key)
1159
+ return providerConfig.api_key;
1160
+ const envKey = ENV_KEYS[provider];
1161
+ if (envKey)
1162
+ return process.env[envKey];
1163
+ return;
1164
+ }
1165
+
1166
+ // src/providers/index.ts
1167
+ var providerCache = new Map;
1168
+ async function getProvider(name, apiKey) {
1169
+ const key = apiKey || getProviderApiKey(name);
1170
+ const cacheKey = `${name}:${key || "default"}`;
1171
+ const cached = providerCache.get(cacheKey);
1172
+ if (cached)
1173
+ return cached;
1174
+ let provider;
1175
+ switch (name) {
1176
+ case "e2b": {
1177
+ if (!key)
1178
+ throw new ProviderError("e2b", "API key required. Set E2B_API_KEY or configure via `sandboxes config set providers.e2b.api_key <key>`");
1179
+ const { E2BProvider: E2BProvider2 } = await Promise.resolve().then(() => (init_e2b(), exports_e2b));
1180
+ provider = new E2BProvider2(key);
1181
+ break;
1182
+ }
1183
+ case "daytona": {
1184
+ if (!key)
1185
+ throw new ProviderError("daytona", "API key required. Set DAYTONA_API_KEY or configure via `sandboxes config set providers.daytona.api_key <key>`");
1186
+ const { DaytonaProvider: DaytonaProvider2 } = await Promise.resolve().then(() => (init_daytona(), exports_daytona));
1187
+ provider = new DaytonaProvider2(key);
1188
+ break;
1189
+ }
1190
+ case "modal": {
1191
+ const { ModalProvider: ModalProvider2 } = await Promise.resolve().then(() => (init_modal(), exports_modal));
1192
+ provider = new ModalProvider2(key);
1193
+ break;
1194
+ }
1195
+ default:
1196
+ throw new ProviderError(name, `Unknown provider: ${name}`);
1197
+ }
1198
+ providerCache.set(cacheKey, provider);
1199
+ return provider;
1200
+ }
1201
+
1202
+ // src/lib/stream.ts
1203
+ var listeners = new Map;
1204
+ function addStreamListener(sandboxId, listener) {
1205
+ if (!listeners.has(sandboxId)) {
1206
+ listeners.set(sandboxId, new Set);
1207
+ }
1208
+ listeners.get(sandboxId).add(listener);
1209
+ return () => {
1210
+ listeners.get(sandboxId)?.delete(listener);
1211
+ if (listeners.get(sandboxId)?.size === 0) {
1212
+ listeners.delete(sandboxId);
1213
+ }
1214
+ };
1215
+ }
1216
+ function notifyListeners(sandboxId, type, data) {
1217
+ const sandboxListeners = listeners.get(sandboxId);
1218
+ if (sandboxListeners) {
1219
+ for (const listener of sandboxListeners) {
1220
+ try {
1221
+ listener(type, data);
1222
+ } catch {}
1223
+ }
1224
+ }
1225
+ }
1226
+ function createStreamCollector(sandboxId, sessionId) {
1227
+ let stdout = "";
1228
+ let stderr = "";
1229
+ return {
1230
+ onStdout: (data) => {
1231
+ stdout += data;
1232
+ addEvent({
1233
+ sandbox_id: sandboxId,
1234
+ session_id: sessionId,
1235
+ type: "stdout",
1236
+ data
1237
+ });
1238
+ notifyListeners(sandboxId, "stdout", data);
1239
+ },
1240
+ onStderr: (data) => {
1241
+ stderr += data;
1242
+ addEvent({
1243
+ sandbox_id: sandboxId,
1244
+ session_id: sessionId,
1245
+ type: "stderr",
1246
+ data
1247
+ });
1248
+ notifyListeners(sandboxId, "stderr", data);
1249
+ },
1250
+ getOutput: () => ({ stdout, stderr })
1251
+ };
1252
+ }
1253
+ function emitLifecycleEvent(sandboxId, message) {
1254
+ addEvent({
1255
+ sandbox_id: sandboxId,
1256
+ type: "lifecycle",
1257
+ data: message
1258
+ });
1259
+ notifyListeners(sandboxId, "lifecycle", message);
1260
+ }
1261
+
1262
+ // src/server/serve.ts
1263
+ function json(data, status = 200) {
1264
+ return new Response(JSON.stringify(data), {
1265
+ status,
1266
+ headers: {
1267
+ "Content-Type": "application/json",
1268
+ "Access-Control-Allow-Origin": "*",
1269
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
1270
+ "Access-Control-Allow-Headers": "Content-Type"
1271
+ }
1272
+ });
1273
+ }
1274
+ function error(message, status = 400) {
1275
+ return json({ error: message }, status);
1276
+ }
1277
+ async function body(req) {
1278
+ return await req.json();
1279
+ }
1280
+ function matchRoute(pathname, method, pattern, expectedMethod) {
1281
+ if (method !== expectedMethod)
1282
+ return null;
1283
+ const patternParts = pattern.split("/");
1284
+ const pathParts = pathname.split("/");
1285
+ if (patternParts.length !== pathParts.length)
1286
+ return null;
1287
+ const params = {};
1288
+ for (let i = 0;i < patternParts.length; i++) {
1289
+ const pp = patternParts[i];
1290
+ const pathPart = pathParts[i];
1291
+ if (pp.startsWith(":")) {
1292
+ params[pp.slice(1)] = pathPart;
1293
+ } else if (pp !== pathPart) {
1294
+ return null;
1295
+ }
1296
+ }
1297
+ return params;
1298
+ }
1299
+ async function handleRequest(req) {
1300
+ const url = new URL(req.url);
1301
+ const { pathname } = url;
1302
+ const method = req.method;
1303
+ if (method === "OPTIONS") {
1304
+ return json({ ok: true });
1305
+ }
1306
+ if (pathname === "/api/health" && method === "GET") {
1307
+ return json({ status: "ok", version: "0.1.0" });
1308
+ }
1309
+ if (pathname === "/api/sandboxes" && method === "GET") {
1310
+ const status = url.searchParams.get("status") || undefined;
1311
+ const provider = url.searchParams.get("provider") || undefined;
1312
+ const result = listSandboxes({
1313
+ status,
1314
+ provider
1315
+ });
1316
+ return json(result);
1317
+ }
1318
+ if (pathname === "/api/sandboxes" && method === "POST") {
1319
+ try {
1320
+ const input = await body(req);
1321
+ const providerName = input.provider || getDefaultProvider();
1322
+ const timeout = input.timeout || getDefaultTimeout();
1323
+ const sandbox = createSandbox({ ...input, provider: providerName, timeout });
1324
+ const provider = await getProvider(providerName);
1325
+ const providerSandbox = await provider.create({
1326
+ image: input.image,
1327
+ timeout,
1328
+ envVars: input.env_vars
1329
+ });
1330
+ const updated = updateSandbox(sandbox.id, {
1331
+ provider_sandbox_id: providerSandbox.id,
1332
+ status: "running"
1333
+ });
1334
+ emitLifecycleEvent(sandbox.id, `Sandbox created with provider ${providerName}`);
1335
+ return json(updated, 201);
1336
+ } catch (err) {
1337
+ return error(err.message, 500);
1338
+ }
1339
+ }
1340
+ let params = matchRoute(pathname, method, "/api/sandboxes/:id", "GET");
1341
+ if (params) {
1342
+ try {
1343
+ return json(getSandbox(params["id"]));
1344
+ } catch (err) {
1345
+ return error(err.message, 404);
1346
+ }
1347
+ }
1348
+ params = matchRoute(pathname, method, "/api/sandboxes/:id", "DELETE");
1349
+ if (params) {
1350
+ try {
1351
+ const sandbox = getSandbox(params["id"]);
1352
+ if (sandbox.provider_sandbox_id) {
1353
+ const provider = await getProvider(sandbox.provider);
1354
+ await provider.delete(sandbox.provider_sandbox_id);
1355
+ }
1356
+ deleteSandbox(sandbox.id);
1357
+ emitLifecycleEvent(sandbox.id, "Sandbox deleted");
1358
+ return json({ ok: true });
1359
+ } catch (err) {
1360
+ return error(err.message, 500);
1361
+ }
1362
+ }
1363
+ params = matchRoute(pathname, method, "/api/sandboxes/:id/stop", "POST");
1364
+ if (params) {
1365
+ try {
1366
+ const sandbox = getSandbox(params["id"]);
1367
+ if (sandbox.provider_sandbox_id) {
1368
+ const provider = await getProvider(sandbox.provider);
1369
+ await provider.stop(sandbox.provider_sandbox_id);
1370
+ }
1371
+ const updated = updateSandbox(sandbox.id, { status: "stopped" });
1372
+ emitLifecycleEvent(sandbox.id, "Sandbox stopped");
1373
+ return json(updated);
1374
+ } catch (err) {
1375
+ return error(err.message, 500);
1376
+ }
1377
+ }
1378
+ params = matchRoute(pathname, method, "/api/sandboxes/:id/exec", "POST");
1379
+ if (params) {
1380
+ try {
1381
+ const sandbox = getSandbox(params["id"]);
1382
+ if (!sandbox.provider_sandbox_id) {
1383
+ return error("Sandbox has no provider instance", 400);
1384
+ }
1385
+ const { command } = await body(req);
1386
+ const session = createSession({ sandbox_id: sandbox.id, command });
1387
+ const collector = createStreamCollector(sandbox.id, session.id);
1388
+ const provider = await getProvider(sandbox.provider);
1389
+ const result = await provider.exec(sandbox.provider_sandbox_id, command, {
1390
+ onStdout: collector.onStdout,
1391
+ onStderr: collector.onStderr
1392
+ });
1393
+ if ("exit_code" in result) {
1394
+ endSession(session.id, result.exit_code);
1395
+ return json({ session_id: session.id, ...result });
1396
+ }
1397
+ return json({ session_id: session.id, status: "running" });
1398
+ } catch (err) {
1399
+ return error(err.message, 500);
1400
+ }
1401
+ }
1402
+ params = matchRoute(pathname, method, "/api/sandboxes/:id/keep-alive", "POST");
1403
+ if (params) {
1404
+ try {
1405
+ const sandbox = getSandbox(params["id"]);
1406
+ if (!sandbox.provider_sandbox_id) {
1407
+ return error("Sandbox has no provider instance", 400);
1408
+ }
1409
+ const { duration_seconds } = await body(req);
1410
+ const provider = await getProvider(sandbox.provider);
1411
+ await provider.keepAlive(sandbox.provider_sandbox_id, (duration_seconds || 300) * 1000);
1412
+ return json({ ok: true });
1413
+ } catch (err) {
1414
+ return error(err.message, 500);
1415
+ }
1416
+ }
1417
+ params = matchRoute(pathname, method, "/api/sandboxes/:id/logs", "GET");
1418
+ if (params) {
1419
+ const sessionId = url.searchParams.get("session_id") || undefined;
1420
+ const limit = parseInt(url.searchParams.get("limit") || "100", 10);
1421
+ const events = listEvents({
1422
+ sandbox_id: params["id"],
1423
+ session_id: sessionId,
1424
+ limit
1425
+ });
1426
+ return json(events);
1427
+ }
1428
+ params = matchRoute(pathname, method, "/api/sandboxes/:id/sessions", "GET");
1429
+ if (params) {
1430
+ const sessions = listSessions({ sandbox_id: params["id"] });
1431
+ return json(sessions);
1432
+ }
1433
+ params = matchRoute(pathname, method, "/api/sandboxes/:id/files", "GET");
1434
+ if (params) {
1435
+ try {
1436
+ const sandbox = getSandbox(params["id"]);
1437
+ if (!sandbox.provider_sandbox_id)
1438
+ return error("No provider instance", 400);
1439
+ const path = url.searchParams.get("path") || "/";
1440
+ const provider = await getProvider(sandbox.provider);
1441
+ const files = await provider.listFiles(sandbox.provider_sandbox_id, path);
1442
+ return json(files);
1443
+ } catch (err) {
1444
+ return error(err.message, 500);
1445
+ }
1446
+ }
1447
+ if (pathname === "/api/agents" && method === "GET") {
1448
+ return json(listAgents());
1449
+ }
1450
+ if (pathname === "/api/agents" && method === "POST") {
1451
+ try {
1452
+ const input = await body(req);
1453
+ return json(registerAgent(input), 201);
1454
+ } catch (err) {
1455
+ return error(err.message, 500);
1456
+ }
1457
+ }
1458
+ if (pathname === "/api/projects" && method === "GET") {
1459
+ return json(listProjects());
1460
+ }
1461
+ if (pathname === "/api/projects" && method === "POST") {
1462
+ try {
1463
+ const input = await body(req);
1464
+ return json(ensureProject(input.name, input.path), 201);
1465
+ } catch (err) {
1466
+ return error(err.message, 500);
1467
+ }
1468
+ }
1469
+ if (pathname === "/api/webhooks" && method === "GET") {
1470
+ return json(listWebhooks());
1471
+ }
1472
+ if (pathname === "/api/webhooks" && method === "POST") {
1473
+ try {
1474
+ const input = await body(req);
1475
+ return json(createWebhook(input), 201);
1476
+ } catch (err) {
1477
+ return error(err.message, 500);
1478
+ }
1479
+ }
1480
+ params = matchRoute(pathname, method, "/api/webhooks/:id", "DELETE");
1481
+ if (params) {
1482
+ try {
1483
+ deleteWebhook(params["id"]);
1484
+ return json({ ok: true });
1485
+ } catch (err) {
1486
+ return error(err.message, 500);
1487
+ }
1488
+ }
1489
+ params = matchRoute(pathname, method, "/api/sandboxes/:id/stream", "GET");
1490
+ if (params) {
1491
+ try {
1492
+ const sandbox = getSandbox(params["id"]);
1493
+ const stream = new ReadableStream({
1494
+ start(controller) {
1495
+ const encoder = new TextEncoder;
1496
+ const send = (type, data) => {
1497
+ controller.enqueue(encoder.encode(`event: ${type}
1498
+ data: ${JSON.stringify(data)}
1499
+
1500
+ `));
1501
+ };
1502
+ const removeListener = addStreamListener(sandbox.id, (type, data) => {
1503
+ send(type, data);
1504
+ });
1505
+ const heartbeat = setInterval(() => {
1506
+ controller.enqueue(encoder.encode(`: heartbeat
1507
+
1508
+ `));
1509
+ }, 30000);
1510
+ const cleanup = () => {
1511
+ removeListener();
1512
+ clearInterval(heartbeat);
1513
+ };
1514
+ setTimeout(() => {
1515
+ cleanup();
1516
+ try {
1517
+ controller.close();
1518
+ } catch {}
1519
+ }, 3600000);
1520
+ }
1521
+ });
1522
+ return new Response(stream, {
1523
+ headers: {
1524
+ "Content-Type": "text/event-stream",
1525
+ "Cache-Control": "no-cache",
1526
+ Connection: "keep-alive",
1527
+ "Access-Control-Allow-Origin": "*"
1528
+ }
1529
+ });
1530
+ } catch (err) {
1531
+ return error(err.message, 404);
1532
+ }
1533
+ }
1534
+ return error("Not found", 404);
1535
+ }
1536
+ function startServer(port) {
1537
+ const server = Bun.serve({
1538
+ port,
1539
+ fetch: handleRequest
1540
+ });
1541
+ console.log(`sandboxes-serve listening on http://localhost:${server.port}`);
1542
+ }
1543
+
1544
+ // src/server/index.ts
1545
+ var { values } = parseArgs({
1546
+ args: process.argv.slice(2),
1547
+ options: {
1548
+ port: { type: "string", short: "p", default: "19430" }
1549
+ }
1550
+ });
1551
+ var port = parseInt(values.port || "19430", 10);
1552
+ startServer(port);