@tangle-network/sandbox 0.0.0-develop.20260514223840.b2abd84

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/README.md ADDED
@@ -0,0 +1,736 @@
1
+ # @tangle-network/sandbox
2
+
3
+ TypeScript SDK for the Tangle Sandbox platform. Create isolated dev containers, run AI agents, and build automation workflows.
4
+
5
+ A separate CLI is published as `@tangle-network/sandbox-cli`.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @tangle-network/sandbox
11
+ # or
12
+ pnpm add @tangle-network/sandbox
13
+ # or
14
+ yarn add @tangle-network/sandbox
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ import { Sandbox } from "@tangle-network/sandbox";
21
+
22
+ // Initialize the client
23
+ const client = new Sandbox({
24
+ apiKey: "sk_sandbox_...",
25
+ baseUrl: "https://your-sandbox-api.example.com",
26
+ });
27
+
28
+ // Create a sandbox
29
+ const box = await client.create({
30
+ name: "my-project",
31
+ image: "node:20",
32
+ });
33
+
34
+ // Execute commands
35
+ const result = await box.exec("npm install && npm test");
36
+ console.log(result.stdout);
37
+
38
+ // Run an AI agent task
39
+ const task = await box.task("Fix any failing tests and commit the changes");
40
+ console.log(task.response);
41
+
42
+ // Clean up
43
+ await box.delete();
44
+ ```
45
+
46
+ ## Features
47
+
48
+ - **Sandbox Management** - Create, list, stop, resume, and delete sandboxes
49
+ - **Command Execution** - Run shell commands in isolated containers
50
+ - **AI Agent Tasks** - Multi-turn agent execution with automatic tool use
51
+ - **Snapshots** - Save and restore sandbox state
52
+ - **BYOS3** - Bring your own S3 storage for snapshots
53
+ - **Batch Execution** - Run tasks across multiple sandboxes in parallel
54
+ - **Event Streaming** - Real-time SSE streams for agent events
55
+ - **Collaboration Foundations** - Token issuance and document identity helpers for collaborative editing
56
+ - **Trace Intelligence** - Export raw sandbox and fleet traces, derived insights, timing metrics, and OTEL JSON to customer-owned observability systems
57
+
58
+ ## Collaboration Foundations
59
+
60
+ The SDK now includes the first collaboration primitives for product backends:
61
+
62
+ - collaboration token issuance from `@tangle-network/sandbox/auth`
63
+ - stable document identity helpers from `@tangle-network/sandbox/collaboration`
64
+ - a collaboration API client for bootstrap / token refresh / snapshot calls
65
+ - a headless file bridge for syncing document adapters with sandbox files
66
+
67
+ Example:
68
+
69
+ ```typescript
70
+ import {
71
+ buildCollaborationDocumentId,
72
+ } from "@tangle-network/sandbox/collaboration";
73
+ import { ProductTokenIssuer } from "@tangle-network/sandbox/auth";
74
+
75
+ const issuer = new ProductTokenIssuer({
76
+ productId: "gtm-agent",
77
+ signingSecret: process.env.SANDBOX_SIGNING_SECRET!,
78
+ });
79
+
80
+ const documentId = buildCollaborationDocumentId({
81
+ workspaceId: "ws_123",
82
+ relativePath: "system/operating-system.md",
83
+ });
84
+
85
+ const { token, expiresAt } = issuer.issueCollaboration({
86
+ userId: "user_123",
87
+ sessionId: "sess_456",
88
+ projectId: "ws_123",
89
+ documentId,
90
+ access: "write",
91
+ });
92
+ ```
93
+
94
+ Bootstrap and snapshot helpers:
95
+
96
+ ```typescript
97
+ import { CollaborationClient } from "@tangle-network/sandbox/collaboration";
98
+
99
+ const collab = new CollaborationClient({
100
+ baseUrl: "https://app.example.com",
101
+ headers: () => ({
102
+ Authorization: `Bearer ${sessionToken}`,
103
+ }),
104
+ });
105
+
106
+ const bootstrap = await collab.bootstrap({
107
+ workspaceId: "ws_123",
108
+ relativePath: "system/operating-system.md",
109
+ });
110
+ ```
111
+
112
+ The bridge and client are SDK-side primitives. Product/backend endpoints and CRDT runtime integration still need to be wired by the application.
113
+
114
+ ## Trace Intelligence
115
+
116
+ Sandboxes and fleet runs return raw trace evidence by default. The derived intelligence envelope is opt-in unless you call the explicit intelligence helper. The raw trace is the source of truth; the intelligence envelope is the platform's current analysis over that evidence.
117
+
118
+ ```typescript
119
+ const boxBundle = await box.trace(); // { trace }
120
+ const boxInsights = await box.intelligence(); // intelligence only
121
+ const boxBundleWithInsights = await box.trace({ includeIntelligence: true });
122
+
123
+ const run = await fleet.dispatchExecWithInsights("pytest -q", {
124
+ machines: ["worker-1", "worker-2"],
125
+ });
126
+
127
+ console.log(run.trace.events);
128
+ console.log(run.intelligence.signals);
129
+ console.log(run.intelligence.recommendedActions);
130
+
131
+ const bundle = await fleet.trace(); // { trace }
132
+ const insights = await fleet.intelligence(); // intelligence only
133
+ const bundleWithInsights = await fleet.trace({ includeIntelligence: true });
134
+ ```
135
+
136
+ Sandbox traces cover lifecycle, runtime, usage, timing, and current health snapshots. Fleet traces add machine lifecycle, workspace state, dispatch results, fanout timings, and critical path. The intelligence envelope tells you what to inspect next: reliability, parallelism efficiency for fleets, dispatch failure classes, resource attribution, timing bottlenecks, and recommended actions.
137
+
138
+ The built-in intelligence in this SDK is deterministic platform analysis. It reports `billing.billable: false` and `billing.costUsd: 0`; customers are not charged for generating these envelopes. Products opt into generated intelligence per trace request with `includeIntelligence: true` or by calling `intelligence()`.
139
+
140
+ Customers can keep the complete raw payload or forward it to their own observability stack. Use `format: "tangle"` to preserve the native envelope, or `format: "otel-json"` for OpenTelemetry-style collectors and platforms that accept OTLP JSON.
141
+
142
+ ```typescript
143
+ await box.exportTrace({
144
+ url: "https://collector.example.com/traces",
145
+ headers: { Authorization: `Bearer ${process.env.OBSERVABILITY_TOKEN}` },
146
+ format: "otel-json",
147
+ serviceName: "research-agent",
148
+ });
149
+
150
+ await fleet.exportTrace({
151
+ url: "https://collector.example.com/traces",
152
+ headers: { Authorization: `Bearer ${process.env.OBSERVABILITY_TOKEN}` },
153
+ format: "otel-json",
154
+ serviceName: "research-agent",
155
+ });
156
+ ```
157
+
158
+ For Braintrust, Lemma, Raindrop, Langfuse, Datadog, or a custom warehouse, keep this as a customer-owned sink or webhook. Tangle does not need their vendor credentials: fetch `box.trace()` or `fleet.trace()` and send the raw bundle through their SDK/API, or point `exportTrace()` at a small ingest endpoint that transforms it into the vendor's preferred schema.
159
+
160
+ Agent tools should expose both `trace` and `intelligence` actions on `manageSandboxes`. `trace` returns the full raw bundle for downstream analysis; `intelligence` returns the compact agent-readable next-step summary.
161
+
162
+ ## Core Concepts
163
+
164
+ ### Sandboxes
165
+
166
+ A sandbox is an isolated dev container with:
167
+ - A programmatic runtime API
168
+ - Optional SSH access
169
+ - Optional web terminal
170
+ - Persistent storage with snapshots
171
+
172
+ ```typescript
173
+ const box = await client.create({
174
+ name: "my-sandbox",
175
+ image: "python:3.12",
176
+ env: { DEBUG: "true" },
177
+ sshEnabled: true,
178
+ maxLifetimeSeconds: 7200, // 2 hours
179
+ idleTimeoutSeconds: 1800, // 30 min idle timeout
180
+ resources: {
181
+ cpuCores: 2,
182
+ memoryMB: 4096,
183
+ diskGB: 20,
184
+ },
185
+ });
186
+ ```
187
+
188
+ ### Status Lifecycle
189
+
190
+ ```
191
+ pending -> provisioning -> running -> stopped -> deleted
192
+ |
193
+ v
194
+ failed
195
+ ```
196
+
197
+ ## API Reference
198
+
199
+ ### Client
200
+
201
+ ```typescript
202
+ import { Sandbox } from "@tangle-network/sandbox";
203
+
204
+ const client = new Sandbox({
205
+ apiKey: "sk_sandbox_...",
206
+ baseUrl: "https://your-sandbox-api.example.com", // required
207
+ timeoutMs: 30000, // optional
208
+ });
209
+ ```
210
+
211
+ #### `client.create(options?)`
212
+
213
+ Create a new sandbox.
214
+
215
+ ```typescript
216
+ const box = await client.create({
217
+ name: "my-project",
218
+ image: "node:20", // or "typescript" for pre-built image
219
+ agentIdentifier: "my-agent", // agent to run
220
+ env: { NODE_ENV: "development" },
221
+ sshEnabled: true,
222
+ sshPublicKey: "ssh-ed25519 AAAA...",
223
+ webTerminalEnabled: true,
224
+ maxLifetimeSeconds: 3600,
225
+ idleTimeoutSeconds: 900,
226
+ resources: {
227
+ cpuCores: 2,
228
+ memoryMB: 4096,
229
+ diskGB: 20,
230
+ },
231
+ metadata: { team: "platform" },
232
+ // BYOS3: Customer-provided storage
233
+ storage: {
234
+ type: "s3",
235
+ bucket: "my-snapshots",
236
+ region: "us-east-1",
237
+ credentials: {
238
+ accessKeyId: "AKIA...",
239
+ secretAccessKey: "...",
240
+ },
241
+ },
242
+ fromSnapshot: "snap_abc123", // restore from snapshot
243
+ });
244
+ ```
245
+
246
+ #### `client.list(options?)`
247
+
248
+ List all sandboxes.
249
+
250
+ ```typescript
251
+ const sandboxes = await client.list({
252
+ status: "running", // filter by status
253
+ limit: 10,
254
+ offset: 0,
255
+ });
256
+ ```
257
+
258
+ #### `client.get(id)`
259
+
260
+ Get a sandbox by ID.
261
+
262
+ ```typescript
263
+ const box = await client.get("sandbox_abc123");
264
+ if (box) {
265
+ console.log(box.status);
266
+ }
267
+ ```
268
+
269
+ #### `client.usage()`
270
+
271
+ Get account usage information.
272
+
273
+ ```typescript
274
+ const usage = await client.usage();
275
+ console.log(`Active: ${usage.activeSandboxes}`);
276
+ console.log(`Compute: ${usage.computeMinutes} minutes`);
277
+ ```
278
+
279
+ #### `client.runBatch(tasks, options?)`
280
+
281
+ Run tasks across multiple sandboxes in parallel.
282
+
283
+ ```typescript
284
+ const result = await client.runBatch([
285
+ { id: "task-1", message: "Analyze code quality" },
286
+ { id: "task-2", message: "Run security scan" },
287
+ { id: "task-3", message: "Generate documentation" },
288
+ ], {
289
+ timeoutMs: 300000,
290
+ scalingMode: "balanced", // "fastest" | "balanced" | "cheapest"
291
+ });
292
+
293
+ console.log(`Success rate: ${result.successRate}%`);
294
+ ```
295
+
296
+ ### Sandbox Instance
297
+
298
+ After creating or retrieving a sandbox, you get a `SandboxInstance` with these methods:
299
+
300
+ #### `box.exec(command, options?)`
301
+
302
+ Execute a shell command.
303
+
304
+ ```typescript
305
+ const result = await box.exec("npm install", {
306
+ cwd: "/workspace",
307
+ env: { CI: "true" },
308
+ timeoutMs: 60000,
309
+ });
310
+
311
+ console.log(result.exitCode); // 0
312
+ console.log(result.stdout);
313
+ console.log(result.stderr);
314
+ ```
315
+
316
+ #### `box.prompt(message, options?)`
317
+
318
+ Send a single prompt to the AI agent.
319
+
320
+ ```typescript
321
+ const result = await box.prompt("What files are in this project?", {
322
+ sessionId: "session_123", // for conversation continuity
323
+ model: "anthropic/claude-sonnet-4-20250514",
324
+ timeoutMs: 120000,
325
+ });
326
+
327
+ console.log(result.response);
328
+ console.log(result.usage); // { inputTokens, outputTokens }
329
+ ```
330
+
331
+ #### Backend Selection
332
+
333
+ Each sandbox runs one AI backend. Pass `backend.type` to choose it:
334
+
335
+ | Type | Runtime | When to use |
336
+ |------|---------|-------------|
337
+ | `opencode` | [OpenCode](https://github.com/anomalyco/opencode) | Default. Multi-provider, profile system, MCP support |
338
+ | `claude-code` | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Anthropic-native. Needs ANTHROPIC_API_KEY |
339
+ | `codex` | [Codex CLI](https://github.com/openai/codex) | OpenAI-native. Needs OPENAI_API_KEY |
340
+ | `cursor` | [Cursor Agent SDK](https://cursor.com/changelog/sdk-release) | Cursor-native local/cloud agent. Needs CURSOR_API_KEY |
341
+ | `amp` | [AMP](https://sourcegraph.com/amp) | Sourcegraph AMP agent |
342
+ | `factory-droids` | [Factory](https://factory.ai) | Factory Droid agent |
343
+
344
+ ```typescript
345
+ // Use Claude Code backend
346
+ await box.prompt("Fix the auth bug", {
347
+ backend: { type: "claude-code" },
348
+ });
349
+
350
+ // Use Codex with a named profile
351
+ await box.prompt("Audit this repo", {
352
+ backend: { type: "codex", profile: "browser-codex-fast" },
353
+ });
354
+
355
+ // Use Cursor Agent SDK
356
+ await box.prompt("Implement this change", {
357
+ backend: {
358
+ type: "cursor",
359
+ model: {
360
+ model: "composer-2",
361
+ apiKey: process.env.CURSOR_API_KEY,
362
+ },
363
+ profile: {
364
+ name: "cursor-release-agent",
365
+ prompt: {
366
+ systemPrompt:
367
+ "Use repo rules, configured MCP servers, skills, and subagents when relevant.",
368
+ },
369
+ mcp: {
370
+ docs: { transport: "sse", url: "https://docs.example.com/sse" },
371
+ },
372
+ subagents: {
373
+ reviewer: {
374
+ description: "Reviews changes for correctness and missing tests.",
375
+ prompt: "Review the current diff. Return only blocking findings.",
376
+ model: "composer-2",
377
+ },
378
+ },
379
+ resources: {
380
+ instructions: "Run focused tests before reporting completion.",
381
+ skills: [
382
+ {
383
+ kind: "inline",
384
+ name: "release-check",
385
+ content:
386
+ "---\nname: release-check\ndescription: Validate release readiness.\n---\nRun typecheck and focused tests.",
387
+ },
388
+ ],
389
+ },
390
+ extensions: {
391
+ cursor: {
392
+ runtime: "local",
393
+ local: { settingSources: ["project", "user"] },
394
+ force: true,
395
+ },
396
+ },
397
+ },
398
+ },
399
+ });
400
+
401
+ // Use OpenCode with an inline profile
402
+ await box.prompt("Audit this repo", {
403
+ backend: {
404
+ type: "opencode",
405
+ profile: {
406
+ name: "security-auditor",
407
+ prompt: {
408
+ systemPrompt: "Focus on authorization and sandbox boundary mistakes.",
409
+ },
410
+ tools: { bash: true },
411
+ permissions: { bash: "allow" },
412
+ },
413
+ },
414
+ });
415
+
416
+ // BYOK (Bring Your Own Key)
417
+ await box.prompt("Analyze this", {
418
+ backend: {
419
+ type: "opencode",
420
+ model: {
421
+ provider: "anthropic",
422
+ model: "claude-sonnet-4-20250514",
423
+ apiKey: process.env.MY_ANTHROPIC_KEY,
424
+ },
425
+ },
426
+ });
427
+ ```
428
+
429
+ The SDK serializes `backend.profile` into the required wire format automatically.
430
+
431
+ Cursor profiles map portable MCP, resources, skills, subagents, hooks, permissions,
432
+ and Cursor-native `extensions.cursor` fields into the Cursor Agent SDK. Local
433
+ Cursor runs materialize `.cursor/*` project files inside the sandbox workspace.
434
+ Cloud Cursor runs fail closed when given uncommitted local resources that cannot
435
+ be delivered to the remote Cursor workspace.
436
+
437
+ Provider-native metadata is available through `box.backend` when the backend
438
+ SDK exposes it:
439
+
440
+ ```typescript
441
+ const account = await box.backend.account();
442
+ const models = await box.backend.models();
443
+ const repositories = await box.backend.repositories();
444
+ const agents = await box.backend.agents({ limit: 20 });
445
+ const agent = await box.backend.agent(agents.items[0].agentId);
446
+ const runs = await box.backend.runs(agent.agentId, { limit: 20 });
447
+ const run = await box.backend.run(runs.items[0].id, {
448
+ agentId: agent.agentId,
449
+ });
450
+ const messages = await box.backend.agentMessages(agent.agentId, { limit: 50 });
451
+ const artifacts = await box.backend.artifacts("active-session-id");
452
+ const bytes = await box.backend.downloadArtifact(
453
+ "active-session-id",
454
+ artifacts[0].path,
455
+ );
456
+ ```
457
+
458
+ Unsupported provider-control methods return the backend error; the SDK does not
459
+ fabricate catalog, run, or artifact data for backends that do not expose it.
460
+
461
+ #### `box.task(message, options?)`
462
+
463
+ Run a multi-turn agent task. The agent keeps working until completion.
464
+
465
+ ```typescript
466
+ const result = await box.task("Set up a REST API with authentication", {
467
+ maxTurns: 20, // limit turns (0 = unlimited)
468
+ sessionId: "...", // continue previous session
469
+ });
470
+
471
+ console.log(result.turnsUsed);
472
+ console.log(result.response);
473
+ ```
474
+
475
+ #### `box.streamPrompt(message, options?)`
476
+
477
+ Stream agent events in real-time.
478
+
479
+ ```typescript
480
+ for await (const event of box.streamPrompt("Explain this codebase")) {
481
+ switch (event.type) {
482
+ case "message.part.updated": {
483
+ const part = event.data.part as { type?: string; text?: string };
484
+ if (part.type === "text" && event.data.delta) {
485
+ process.stdout.write(String(event.data.delta));
486
+ }
487
+ if (part.type === "reasoning" && event.data.delta) {
488
+ // Reasoning/thinking tokens from extended thinking models
489
+ process.stderr.write(`[thinking] ${String(event.data.delta)}`);
490
+ }
491
+ break;
492
+ }
493
+ case "result":
494
+ console.log("\nFinal:", event.data.finalText);
495
+ break;
496
+ case "done":
497
+ console.log("\nComplete!");
498
+ break;
499
+ }
500
+ }
501
+ ```
502
+
503
+ #### `box.streamTask(message, options?)`
504
+
505
+ Stream a multi-turn task with real-time events.
506
+
507
+ ```typescript
508
+ for await (const event of box.streamTask("Build a CLI tool")) {
509
+ // Handle events...
510
+ }
511
+ ```
512
+
513
+ #### `box.direct()`
514
+
515
+ Create an explicit advanced direct-runtime view of the sandbox.
516
+
517
+ ```typescript
518
+ const directBox = box.direct();
519
+ const result = await directBox.exec("npm test");
520
+ ```
521
+
522
+ #### `box.events(options?)`
523
+
524
+ Subscribe to sandbox lifecycle events.
525
+
526
+ ```typescript
527
+ for await (const event of box.events({ signal: controller.signal })) {
528
+ console.log(`Event: ${event.type}`, event.data);
529
+ }
530
+ ```
531
+
532
+ ### Snapshots
533
+
534
+ #### `box.snapshot(options?)`
535
+
536
+ Create a snapshot of the sandbox state.
537
+
538
+ ```typescript
539
+ const snapshot = await box.snapshot({
540
+ tags: ["v1.0", "stable"],
541
+ paths: ["/workspace"], // specific paths (default: all)
542
+ });
543
+
544
+ console.log(snapshot.snapshotId);
545
+ console.log(snapshot.sizeBytes);
546
+ ```
547
+
548
+ #### `box.listSnapshots()`
549
+
550
+ List all snapshots for this sandbox.
551
+
552
+ ```typescript
553
+ const snapshots = await box.listSnapshots();
554
+ for (const snap of snapshots) {
555
+ console.log(`${snap.snapshotId}: ${snap.createdAt}`);
556
+ }
557
+ ```
558
+
559
+ ### BYOS3 (Bring Your Own S3)
560
+
561
+ Store snapshots in your own S3-compatible storage. Supports AWS S3, Google Cloud Storage, and Cloudflare R2.
562
+
563
+ #### Creating a sandbox with BYOS3
564
+
565
+ ```typescript
566
+ const box = await client.create({
567
+ name: "my-sandbox",
568
+ storage: {
569
+ type: "s3", // "s3" | "gcs" | "r2"
570
+ bucket: "my-snapshots",
571
+ region: "us-east-1",
572
+ endpoint: "https://s3.us-east-1.amazonaws.com", // optional
573
+ credentials: {
574
+ accessKeyId: "AKIA...",
575
+ secretAccessKey: "...",
576
+ },
577
+ prefix: "sandbox-snapshots/", // optional path prefix
578
+ },
579
+ fromSnapshot: "snap_abc123", // restore from your storage
580
+ });
581
+ ```
582
+
583
+ #### Snapshots with BYOS3
584
+
585
+ When storage is configured, snapshots are written directly to your bucket:
586
+
587
+ ```typescript
588
+ // Create snapshot to your S3
589
+ const snap = await box.snapshot({
590
+ tags: ["production"],
591
+ storage: {
592
+ type: "s3",
593
+ bucket: "my-snapshots",
594
+ credentials: { accessKeyId: "...", secretAccessKey: "..." },
595
+ },
596
+ });
597
+
598
+ // List snapshots from your S3
599
+ const snapshots = await box.listSnapshots({
600
+ type: "s3",
601
+ bucket: "my-snapshots",
602
+ credentials: { ... },
603
+ });
604
+
605
+ // Restore from your S3
606
+ await box.restoreFromStorage({
607
+ type: "s3",
608
+ bucket: "my-snapshots",
609
+ credentials: { ... },
610
+ });
611
+ ```
612
+
613
+ ### Runtime Routing
614
+
615
+ By default, SDK runtime calls like `exec()`, `prompt()`, `task()`, file ops, git, and process management go through the Sandbox API, which dispatches them to the correct running sandbox for the authenticated user.
616
+
617
+ Runtime agent calls can pass a named backend profile or an inline provider-neutral profile object:
618
+
619
+ ```typescript
620
+ const result = await box.prompt("Audit the authentication flow", {
621
+ backend: {
622
+ profile: {
623
+ name: "security-auditor",
624
+ prompt: {
625
+ systemPrompt: "You are a senior application security auditor.",
626
+ instructions: ["Prioritize authz, tenancy, and secret handling."],
627
+ },
628
+ tools: { bash: true },
629
+ permissions: { bash: "allow" },
630
+ },
631
+ },
632
+ });
633
+ ```
634
+
635
+ ### Direct Runtime Access
636
+
637
+ For advanced use cases, use `box.direct()` to make runtime calls directly against the sandbox runtime while keeping normal lifecycle methods on the API client:
638
+
639
+ ```typescript
640
+ const directBox = box.direct();
641
+ const result = await directBox.exec("npm test");
642
+ ```
643
+
644
+ This is the recommended advanced path for power users who want runtime-level access without re-implementing auth/header plumbing themselves.
645
+
646
+ ### Lifecycle Methods
647
+
648
+ ```typescript
649
+ // Stop (preserves state)
650
+ await box.stop();
651
+
652
+ // Resume
653
+ await box.resume();
654
+
655
+ // Delete (destroys everything)
656
+ await box.delete();
657
+
658
+ // Refresh status from API
659
+ await box.refresh();
660
+
661
+ // Wait for specific status
662
+ await box.waitFor("running", { timeoutMs: 60000 });
663
+ ```
664
+
665
+ ### Properties
666
+
667
+ ```typescript
668
+ box.id // Unique identifier
669
+ box.name // Human-readable name
670
+ box.status // "pending" | "provisioning" | "running" | "stopped" | "failed"
671
+ box.connection // raw direct connection info for advanced use
672
+ box.metadata // Custom metadata
673
+ box.createdAt // Date
674
+ box.startedAt // Date | undefined
675
+ box.lastActivityAt // Date | undefined
676
+ box.expiresAt // Date | undefined
677
+ box.error // Error message if failed
678
+ ```
679
+
680
+ ## Error Handling
681
+
682
+ ```typescript
683
+ import {
684
+ AuthError,
685
+ NetworkError,
686
+ NotFoundError,
687
+ QuotaError,
688
+ StateError,
689
+ TimeoutError,
690
+ ValidationError,
691
+ } from "@tangle-network/sandbox";
692
+
693
+ try {
694
+ await box.exec("npm test");
695
+ } catch (err) {
696
+ if (err instanceof TimeoutError) {
697
+ console.log("Command timed out");
698
+ } else if (err instanceof StateError) {
699
+ console.log(`Invalid state: ${err.currentState}`);
700
+ } else if (err instanceof NetworkError) {
701
+ console.log("Connection failed");
702
+ }
703
+ }
704
+ ```
705
+
706
+ ## TypeScript
707
+
708
+ Full TypeScript support with exported types:
709
+
710
+ ```typescript
711
+ import type {
712
+ SandboxClientConfig,
713
+ CreateSandboxOptions,
714
+ SandboxInfo,
715
+ SandboxStatus,
716
+ SandboxConnection,
717
+ ExecResult,
718
+ ExecOptions,
719
+ PromptResult,
720
+ PromptOptions,
721
+ TaskResult,
722
+ TaskOptions,
723
+ SnapshotResult,
724
+ SnapshotOptions,
725
+ SnapshotInfo,
726
+ StorageConfig,
727
+ BatchTask,
728
+ BatchResult,
729
+ BatchOptions,
730
+ UsageInfo,
731
+ } from "@tangle-network/sandbox";
732
+ ```
733
+
734
+ ## License
735
+
736
+ MIT