Skip to content

Commit 9af1cdd

Browse files
feat: Debug Sandboxes through CLI (#163)
* wip * working version
1 parent 017b905 commit 9af1cdd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+6579
-4691
lines changed

CHANGELOG.md

Lines changed: 42 additions & 73 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,25 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
55
## Development Commands
66

77
### Build Commands
8+
89
- `npm run build` - Full production build (clean, esbuild, generate types for both CJS and ESM)
910
- `npm run build:esbuild` - Run esbuild bundling only
1011
- `npm run clean` - Remove dist directory
1112
- `npm run dev:cli` - Development mode with file watching for CLI (watches src/bin)
1213

1314
### Type Checking and Linting
15+
1416
- `npm run typecheck` - Run TypeScript type checking without emitting files
1517
- `npm run format` - Format code using Prettier
1618

1719
### CLI Development
20+
1821
- `npm run dev:cli` - Start development mode with file watching specifically for CLI changes
1922
- The CLI binary is built to `dist/bin/codesandbox.mjs` and made executable
2023
- CLI can be run with `./dist/bin/codesandbox.mjs` or just `csb` if installed globally
2124

2225
### API Client Generation
26+
2327
- `npm run build-openapi` - Generate API clients from production OpenAPI spec
2428
- `npm run build-openapi:staging` - Generate API clients from staging OpenAPI spec
2529
- Generated clients are placed in `src/api-clients/` directory
@@ -29,35 +33,41 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
2933
### Core Components
3034

3135
**SDK Entry Points:**
36+
3237
- `src/index.ts` - Main SDK export with `CodeSandbox` class
3338
- `src/browser/index.ts` - Browser-specific SDK build
3439
- `src/node/index.ts` - Node.js-specific SDK build
3540

3641
**Key Classes:**
42+
3743
- `CodeSandbox` - Main SDK class providing `sandboxes` and `hosts` properties
38-
- `Sandboxes` (src/Sandboxes.ts) - Manages sandbox operations (create, list, get, fork)
44+
- `Sandboxes` (src/Sandboxes.ts) - Manages sandbox operations (create, list, get, fork)
3945
- `Sandbox` (src/Sandbox.ts) - Individual sandbox instance with connection and session management
4046
- `SandboxClient` (src/SandboxClient/index.ts) - High-level client for sandbox interactions
4147
- `API` (src/API.ts) - Low-level API wrapper for all CodeSandbox REST endpoints
4248

4349
**Communication Layer:**
50+
4451
- `src/pitcher-protocol/` - WebSocket protocol definitions and message types for real-time sandbox communication
4552
- `src/AgentClient/` - WebSocket client implementation for connecting to sandbox agents
4653

4754
**CLI:**
55+
4856
- `src/bin/main.tsx` - CLI entry point using Ink (React for CLI)
4957
- `src/bin/commands/` - Individual CLI commands (build, sandbox, previewHosts, hostTokens)
5058
- `src/bin/ui/` - Ink-based UI components for the interactive CLI
5159

5260
### Build System
5361

5462
**Multi-Format Output:**
63+
5564
- ESM build: `dist/esm/` (primary format, "type": "module")
5665
- CommonJS build: `dist/cjs/` (compatibility)
5766
- Browser build: Separate bundles with browser polyfills
5867
- CLI build: Standalone executable with shebang
5968

6069
**Key Build Features:**
70+
6171
- esbuild for bundling with custom plugins for module replacement
6272
- Separate TypeScript compilation for type definitions
6373
- Browser polyfills for Node.js modules (os, path, crypto, etc.)
@@ -66,22 +76,26 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
6676
### Type System
6777

6878
**Configuration:**
69-
- Main tsconfig.json excludes `src/bin` and `src/api-clients`
79+
80+
- Main tsconfig.json excludes `src/bin` and `src/api-clients`
7081
- Separate build configs for CJS (`tsconfig.build-cjs.json`) and ESM (`tsconfig.build-esm.json`)
7182
- Strict TypeScript configuration with comprehensive type checking
7283

7384
**Generated Types:**
85+
7486
- `src/api-clients/` contains auto-generated API clients and types from OpenAPI specs
7587
- Multiple API client modules for different sandbox services (fs, git, shell, etc.)
7688

7789
### Authentication & Configuration
7890

7991
**API Authentication:**
92+
8093
- Requires CodeSandbox API token (CSB_API_KEY environment variable)
8194
- Token creation: https://codesandbox.io/t/api
8295
- Automatic User-Agent header injection with SDK version
8396

8497
**Environment Detection:**
98+
8599
- Automatic base URL inference based on API key format
86100
- Support for different CodeSandbox environments (production, staging)
87101

@@ -93,24 +107,28 @@ The codebase includes a `test-template/` directory with a Vite + React + TypeScr
93107

94108
**SDK Documentation:** https://codesandbox.io/docs/sdk
95109

96-
**Contributing to Documentation:**
110+
**Contributing to Documentation:**
111+
97112
- Documentation source is located at https://github.com/codesandbox/docs
98113
- SDK-specific docs are in `packages/projects-docs/pages/sdk/` folder
99114
- Update documentation there when making changes to SDK functionality
100115

101116
## Key Patterns
102117

103118
**Error Handling:**
119+
104120
- `handleResponse` utility for consistent API error handling
105121
- Retry logic with exponential backoff for critical operations
106122
- OpenTelemetry tracing integration for observability
107123

108124
**Session Management:**
125+
109126
- Support for custom sessions with git credentials and environment variables
110127
- Browser session creation for web-based sandbox interactions
111128
- Automatic session cleanup and disposal
112129

113130
**Resource Management:**
114-
- VM tier scaling and hibernation timeout management
131+
132+
- VM tier scaling and hibernation timeout management
115133
- Cluster and bootup type tracking
116-
- Agent version management and update detection
134+
- Agent version management and update detection

package-lock.json

Lines changed: 26 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codesandbox/sdk",
3-
"version": "2.0.7",
3+
"version": "2.1.0-rc.4",
44
"description": "The CodeSandbox SDK",
55
"author": "CodeSandbox",
66
"license": "MIT",
@@ -97,6 +97,8 @@
9797
"@inkjs/ui": "^2.0.0",
9898
"@msgpack/msgpack": "^3.1.0",
9999
"@opentelemetry/api": "^1.9.0",
100+
"@xterm/addon-serialize": "^0.13.0",
101+
"@xterm/headless": "^5.5.0",
100102
"blessed": "^0.1.81",
101103
"blessed-contrib": "^4.11.0",
102104
"chalk": "^5.4.1",

src/Sandbox.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ export class Sandbox {
9090
async updateTier(tier: VMTier): Promise<void> {
9191
return this.withSpan(
9292
"sandbox.updateTier",
93-
{
93+
{
9494
"sandbox.id": this.id,
95-
"tier.name": tier.name
95+
"tier.name": tier.name,
9696
},
9797
async () => {
9898
await this.api.updateSpecs(this.id, {
@@ -111,7 +111,7 @@ export class Sandbox {
111111
"sandbox.updateHibernationTimeout",
112112
{
113113
"sandbox.id": this.id,
114-
"hibernation.timeoutSeconds": timeoutSeconds
114+
"hibernation.timeoutSeconds": timeoutSeconds,
115115
},
116116
async () => {
117117
await this.api.updateHibernationTimeout(this.id, {
@@ -126,8 +126,12 @@ export class Sandbox {
126126
session: SandboxSession
127127
) {
128128
const client = await SandboxClient.create(
129-
session,
130-
async () => this.getSession(await this.api.startVm(this.id, { retryDelay: 200 }), customSession),
129+
session,
130+
async () =>
131+
this.getSession(
132+
await this.api.startVm(this.id, { retryDelay: 200 }),
133+
customSession
134+
),
131135
undefined,
132136
this.tracer
133137
);
@@ -220,7 +224,7 @@ export class Sandbox {
220224
{
221225
"sandbox.id": this.id,
222226
"session.hasCustomSession": !!customSession,
223-
"session.id": customSession?.id || "default"
227+
"session.id": customSession?.id || "default",
224228
},
225229
async () => {
226230
return await retryWithDelay(
@@ -234,14 +238,21 @@ export class Sandbox {
234238

235239
// We might create a client here if git or env is configured, we can reuse that
236240
if (customSession) {
237-
client = await this.initializeCustomSession(customSession, session);
241+
client = await this.initializeCustomSession(
242+
customSession,
243+
session
244+
);
238245
}
239246

240247
return (
241248
client ||
242249
SandboxClient.create(
243-
session,
244-
async () => this.getSession(await this.api.startVm(this.id, { retryDelay: 200 }), customSession),
250+
session,
251+
async () =>
252+
this.getSession(
253+
await this.api.startVm(this.id, { retryDelay: 200 }),
254+
customSession
255+
),
245256
undefined,
246257
this.tracer
247258
)
@@ -271,7 +282,7 @@ export class Sandbox {
271282
"session.hasCustomSession": !!customSession,
272283
"session.id": customSession?.id || "default",
273284
"session.hasGit": !!customSession?.git,
274-
"session.hasEnv": !!customSession?.env
285+
"session.hasEnv": !!customSession?.env,
275286
},
276287
async () => {
277288
if (customSession?.git || customSession?.env) {

src/SandboxClient/commands.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class SandboxCommands {
8888
"command.text": cmdString,
8989
"command.cwd": opts?.cwd || "/project",
9090
"command.asGlobalSession": opts?.asGlobalSession || false,
91-
"command.name": opts?.name || ""
91+
"command.name": opts?.name || "",
9292
},
9393
async () => {
9494
const disposableStore = new DisposableStore();
@@ -168,7 +168,7 @@ export class SandboxCommands {
168168
{
169169
"command.text": cmdString,
170170
"command.cwd": opts?.cwd || "/project",
171-
"command.asGlobalSession": opts?.asGlobalSession || false
171+
"command.asGlobalSession": opts?.asGlobalSession || false,
172172
},
173173
async () => {
174174
const cmd = await this.runBackground(command, opts);
@@ -181,21 +181,23 @@ export class SandboxCommands {
181181
* Get all running commands.
182182
*/
183183
async getAll(): Promise<Command[]> {
184-
return this.withSpan(
185-
"commands.getAll",
186-
{},
187-
async () => {
188-
const shells = await this.agentClient.shells.getShells();
189-
190-
return shells
191-
.filter(
192-
(shell) => shell.shellType === "TERMINAL" && isCommandShell(shell)
193-
)
194-
.map(
195-
(shell) => new Command(this.agentClient, shell, JSON.parse(shell.name), this.tracer)
196-
);
197-
}
198-
);
184+
return this.withSpan("commands.getAll", {}, async () => {
185+
const shells = await this.agentClient.shells.getShells();
186+
187+
return shells
188+
.filter(
189+
(shell) => shell.shellType === "TERMINAL" && isCommandShell(shell)
190+
)
191+
.map(
192+
(shell) =>
193+
new Command(
194+
this.agentClient,
195+
shell,
196+
JSON.parse(shell.name),
197+
this.tracer
198+
)
199+
);
200+
});
199201
}
200202
}
201203

@@ -348,7 +350,7 @@ export class Command {
348350
"command.shellId": this.shell.shellId,
349351
"command.text": this.command,
350352
"command.dimensions.cols": dimensions.cols,
351-
"command.dimensions.rows": dimensions.rows
353+
"command.dimensions.rows": dimensions.rows,
352354
},
353355
async () => {
354356
const shell = await this.agentClient.shells.open(
@@ -372,7 +374,7 @@ export class Command {
372374
{
373375
"command.shellId": this.shell.shellId,
374376
"command.text": this.command,
375-
"command.status": this.status
377+
"command.status": this.status,
376378
},
377379
async () => {
378380
await this.barrier.wait();
@@ -403,7 +405,7 @@ export class Command {
403405
{
404406
"command.shellId": this.shell.shellId,
405407
"command.text": this.command,
406-
"command.status": this.status
408+
"command.status": this.status,
407409
},
408410
async () => {
409411
this.disposable.dispose();
@@ -421,7 +423,7 @@ export class Command {
421423
{
422424
"command.shellId": this.shell.shellId,
423425
"command.text": this.command,
424-
"command.status": this.status
426+
"command.status": this.status,
425427
},
426428
async () => {
427429
if (this.status !== "RUNNING") {

0 commit comments

Comments
 (0)