diff --git a/README.md b/README.md index 351880c..442b517 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,9 @@ Skills are contextual and auto-loaded based on your conversation. When a request | cloudflare | Comprehensive platform skill covering Workers, Pages, storage (KV, D1, R2), AI (Workers AI, Vectorize, Agents SDK), networking (Tunnel, Spectrum), security (WAF, DDoS), and IaC (Terraform, Pulumi) | | agents-sdk | Building stateful AI agents with state, scheduling, RPC, MCP servers, email, and streaming chat | | durable-objects | Stateful coordination (chat rooms, games, booking), RPC, SQLite, alarms, WebSockets | -| sandbox-sdk | Secure code execution for AI code execution, code interpreters, CI/CD systems, and interactive dev environments | +| sandbox-next | Sandbox on `@cloudflare/sandbox@next` (1.0 preview); recommended for new projects | +| sandbox-stable | Sandbox on the current stable `@cloudflare/sandbox` package | +| sandbox-migrate-to-next | Port a stable Sandbox app to `@cloudflare/sandbox@next` | | wrangler | Deploying and managing Workers, KV, R2, D1, Vectorize, Queues, Workflows | | web-perf | Auditing Core Web Vitals (FCP, LCP, TBT, CLS), render-blocking resources, network chains | | building-mcp-server-on-cloudflare | Building remote MCP servers with tools, OAuth, and deployment | diff --git a/skills/sandbox-migrate-to-next/SKILL.md b/skills/sandbox-migrate-to-next/SKILL.md new file mode 100644 index 0000000..745bcc5 --- /dev/null +++ b/skills/sandbox-migrate-to-next/SKILL.md @@ -0,0 +1,185 @@ +--- +name: sandbox-migrate-to-next +description: Use when porting a Cloudflare Sandbox app from stable @cloudflare/sandbox to @cloudflare/sandbox@next (Sandbox SDK 1.0 preview), or when the user asks to migrate or upgrade to Sandbox 1.0 / @next. Not for day-to-day stable work (sandbox-stable) or new @next apps (sandbox-next). +--- + +# Migrate stable → Sandbox SDK 1.0 preview (`@next`) + +**Perform** the port. Follow the steps in order. Depth lives in docs—fetch the linked page when a step needs detail. + +Human guide: [Migrate](https://developers.cloudflare.com/sandbox/1-0-preview/migrate/) · [1.0 preview](https://developers.cloudflare.com/sandbox/1-0-preview/) + +**New projects** should start on `@next` (**`sandbox-next`**), not this skill. **Day-to-day stable work** → **`sandbox-stable`**. Deprecated-API cleanup **without** moving to `@next` → [2026 deprecation guide](https://developers.cloudflare.com/sandbox/guides/2026-deprecation/) first if needed. + +Existing apps should migrate **when you can**, so you are ready when 1.0 becomes the stable release. Do **not** force production cutover without the user agreeing. + +**Prefer installed `@next` types and the migrate doc over memory.** + +## Workflow + +1. **Review** hard rules and the replacement map +2. **Audit** the codebase; list hits and target shapes +3. **Clarify** with the user (cutover, bridge, Python image, unclear sites) +4. **Upgrade** package, image, and code +5. **Validate** + +Stop after any step that needs a user decision. + +## Hard rules + +- Worker package and container image must be the **same** `@next` line. +- Production cutover uses **immediate** container rollout. Stable and `@next` control protocols are incompatible both ways; gradual rollout leaves a broken mixed window. In-flight container work can stop. +- After cutover, `await sandbox.exec(...)` means process **started**, not command **finished**. +- Argv is as-is (no implicit shell). Shell syntax needs an explicit shell binary. +- Process handles have **no stdin** → terminals for interactive input. +- Observation `timeout` / `AbortSignal` cancel the **wait only**, not the process. +- No single retry loop for every error. +- Do not invent APIs (`gitCheckout` on core, process stdin, string-exec completion helper). +- Self-deployed bridge stays on **stable** (not part of the preview line yet). + +## Replacement map + +| Stable | `@next` | +| ------ | ------- | +| `SANDBOX_TRANSPORT` / `transport` / `setTransport` | Remove — RPC only | +| `await sandbox.exec("cmd")` → buffered result | `await sandbox.exec(argv)` → handle, then `output` / waits | +| `execStream` / `startProcess` | Same handle: `logs`, `waitFor*`, `kill` | +| Default / named sessions | Gone — `cwd`/`env` per launch, or one shell script | +| `sandbox.terminal(request)` / session terminal | `createTerminal` + `terminal.connect(request)` | +| xterm `sessionId` | `terminalId` | +| Interpreter methods on `Sandbox` | `withInterpreter` → `sandbox.interpreter.*` | +| `gitCheckout` | argv `git` via `exec` | +| String kill signals | Numeric only | +| Files, mounts, backups, ports, tunnels, `proxyToSandbox` | Mostly unchanged (ignore session/transport bits on stable pages) | + +Depth: [Migrate](https://developers.cloudflare.com/sandbox/1-0-preview/migrate/) · after port, day-to-day → **`sandbox-next`** + +## Audit + +```sh +rg 'SANDBOX_TRANSPORT|transport:|setTransport|enableDefaultSession|createSession|getSession|deleteSession|execStream\(|startProcess\(|killProcess\(|sandbox\.terminal\(|sessionId|gitCheckout\(|SandboxTransport|ExecutionSession' +``` + +Also: string `exec(`, `cd` then a later `exec`, bare `createCodeContext` / `runCode` on `Sandbox`. + +## Clarify (ask when needed) + +- OK to cut production with `--containers-rollout=immediate` (live processes/terminals/streams may stop)? +- Self-deployed bridge? Leave on stable. +- Python interpreter → **`-python`** image variant? +- Call sites not covered by the map? + +## Upgrade + +### Package and image + +```sh +npm install @cloudflare/sandbox@next +``` + +```dockerfile +FROM cloudflare/sandbox:next +# Python: cloudflare/sandbox:next-python +``` + +Same prerelease tag on Worker and image when not on floating `next`. + +### Code by area + +Apply replacements from the map. For each area, implement from the doc—not from stable habits: + +| Area | Doc | +| ---- | --- | +| Commands / handles / waits | [Processes](https://developers.cloudflare.com/sandbox/1-0-preview/processes/) · [Processes API](https://developers.cloudflare.com/sandbox/1-0-preview/api/processes/) | +| `cwd` / `env` / secrets | [Environment](https://developers.cloudflare.com/sandbox/1-0-preview/environment/) · [Outbound traffic](https://developers.cloudflare.com/sandbox/guides/outbound-traffic/) | +| Drop sessions | [Migrate](https://developers.cloudflare.com/sandbox/1-0-preview/migrate/) · [Lifecycle](https://developers.cloudflare.com/sandbox/1-0-preview/lifecycle/) | +| Terminals | [Terminals](https://developers.cloudflare.com/sandbox/1-0-preview/terminals/) | +| Interpreter | [Interpreter](https://developers.cloudflare.com/sandbox/1-0-preview/interpreter/) | +| Errors | [Errors](https://developers.cloudflare.com/sandbox/1-0-preview/errors/) | +| Durable job across requests | [Process execution — lifetime / durability](https://developers.cloudflare.com/sandbox/1-0-preview/processes/) | + +**Commands (shape):** + +```ts +// Before (stable) +const result = await sandbox.exec("npm test"); + +// After (@next) +const process = await sandbox.exec(["/bin/bash", "-lc", "npm test"]); +const result = await process.output({ encoding: "utf8" }); +``` + +```ts +const server = await sandbox.exec(["/bin/bash", "-lc", "npm run dev"], { + cwd: "/workspace/app", +}); +await server.waitForPort(3000, { timeout: 60_000 }); +await server.kill(); // numeric; default 15 +``` + +**Terminals (shape):** + +```ts +const terminal = await sandbox.createTerminal({ command: ["bash"], cwd: "/workspace" }); +const t = await sandbox.getTerminal(terminal.id); +if (!t) return new Response("terminal gone", { status: 410 }); +return t.connect(request, { cursor, cols, rows }); +``` + +**Interpreter (shape):** + +```ts +import { Sandbox as BaseSandbox } from "@cloudflare/sandbox"; +import { withInterpreter } from "@cloudflare/sandbox/interpreter"; + +export class Sandbox extends BaseSandbox { + interpreter = withInterpreter(this); +} +``` + +**Git (shape):** + +```ts +const clone = await sandbox.exec( + ["git", "clone", "--depth", "1", "--", repoUrl, "/workspace/repo"], + { cwd: "/workspace" }, +); +const result = await clone.output({ encoding: "utf8" }); +``` + +Delete transport settings entirely. Remove session APIs. Isolate users with **separate sandbox IDs**. + +### Deploy cutover + +Staging/branch first. Production is **one** deploy of matching Worker + image: + +```sh +npx wrangler deploy --containers-rollout=immediate +``` + +Leave `rollout_active_grace_period` at default `0` (or set `0` if raised). After cutover, pre-deploy process/terminal IDs are invalid. Details: [Migrate](https://developers.cloudflare.com/sandbox/1-0-preview/migrate/) · [Container rollouts](https://developers.cloudflare.com/containers/platform-details/rollouts/) + +## Validate + +1. Lockfile + Dockerfile on the same `@next` line +2. Typecheck against `@next` +3. Smoke argv `exec` + `output({ encoding: "utf8" })` +4. Smoke long process / terminal / interpreter if used +5. Errors distinguished: unavailable / interrupted-RPC / stale / local wait +6. No live secrets in sandbox env +7. Grep again for removed APIs +8. Production used `--containers-rollout=immediate` + +Then day-to-day work uses **`sandbox-next`**. + +## Red flags — stop and fix + +- Mixing `@next` Worker with stable image (or reverse) +- Gradual container rollout for this cutover +- Treating `await exec` as command completion +- Assuming `cd` / exports persist across `exec` calls +- One retry wrapper for every error +- Inventing `gitCheckout`, process stdin, or undocumented APIs +- Keeping pre-cutover process/terminal IDs after deploy +- Forcing production cutover without user agreement +- Putting live secrets in `setEnvVars` / launch `env` diff --git a/skills/sandbox-next/SKILL.md b/skills/sandbox-next/SKILL.md new file mode 100644 index 0000000..6384327 --- /dev/null +++ b/skills/sandbox-next/SKILL.md @@ -0,0 +1,91 @@ +--- +name: sandbox-next +description: Use when building or changing Cloudflare Sandbox apps on @cloudflare/sandbox@next (Sandbox SDK 1.0 preview)—code execution, AI runners, interpreters, CI-like jobs, terminals, files, mounts, tunnels, preview URLs, lifecycle, or errors. Not for the default stable package (use sandbox-stable) or for porting stable to @next (use sandbox-migrate-to-next). +--- + +# Sandbox SDK — `@next` (1.0 preview) + +Isolated Linux environments on [Cloudflare Containers](https://developers.cloudflare.com/containers/), driven from Workers. + +**Prefer preview docs and installed `@next` types over memory.** APIs change; this skill is a gate, a contract, and a retrieval map—not a full manual. + +We recommend **new projects** on this line. Apps still on the default package use **`sandbox-stable`**. Port only when asked, via **`sandbox-migrate-to-next`**. + +## 1. Gate — confirm the package line + +Before writing code, inspect the app: + +| Check | Must match | +| ----- | ---------- | +| npm dependency | `@cloudflare/sandbox@next` (or another preview tag) | +| Container image | Same line (e.g. `cloudflare/sandbox:next`, `next-python`) | + +| If you find… | Action | +| ------------ | ------ | +| Default `@cloudflare/sandbox` (no `@next`) | **Stop.** Load **`sandbox-stable`**. Do not apply this skill’s APIs. | +| User wants to port stable → `@next` | **Stop.** Load **`sandbox-migrate-to-next`**. | +| Self-deployed **bridge** only | Bridge is **not** on the 1.0 preview line yet. Keep bridge on stable package + image. [Bridge (stable)](https://developers.cloudflare.com/sandbox/bridge/) | + +Never mix an `@next` Worker package with a stable container image (or the reverse). + +Skills install: [Agent setup](https://developers.cloudflare.com/agent-setup/) · [cloudflare/skills](https://github.com/cloudflare/skills) + +## 2. Contract — non-negotiables + +- `sandbox.exec(argv)` takes an **argv** list and resolves when the process **starts**. It returns a **handle**, not a finished command result. +- Collect results with handle methods: `output()`, `logs()`, `waitForExit()`, `waitForPort()`, `waitForLog()`, `kill(signal?)`. +- No implicit shell. Shell syntax needs an explicit shell, e.g. `["/bin/bash", "-lc", script]`. +- Each launch is independent. A `cd` / `export` in one `exec` is not visible to the next. Pass `cwd` and `env` per launch, or one shell script. +- Process handles have **no stdin**. Interactive use → terminals (`createTerminal` + `connect`). +- Local wait `timeout` / `AbortSignal` cancel the **wait only**. They do not kill the process. Use `kill` or `exec`’s remote `timeout`. +- `getProcess` / `listProcesses` / `getTerminal` / `listTerminals` do **not** start a container; they return `null` / `[]` when none is up. +- Process and terminal IDs belong to the **current container**, not forever to a sandbox ID. For work that must survive replace, store the full job (argv, cwd, env, app state)—not only an id. +- Non-secret config only in `setEnvVars` / launch `env`. Live credentials stay in the Worker; use outbound handlers when the sandbox calls external APIs. +- Do **not** invent removed stable APIs (`gitCheckout` on core, string-`exec` completion, session execution, `sandbox.terminal(request)`). +- Do **not** use one retry loop for every error (see Errors docs). + +Minimal shape: + +```ts +import { getSandbox, proxyToSandbox, Sandbox } from "@cloudflare/sandbox"; + +export { Sandbox }; + +const sandbox = getSandbox(env.Sandbox, "user-123"); +const process = await sandbox.exec(["python3", "-c", "print(2 + 2)"]); +const result = await process.output({ encoding: "utf8" }); +// result.stdout, result.exitCode +``` + +Optional **non-exhaustive** cheatsheet (process/terminal/interpreter only): [references/api-quick-ref.md](references/api-quick-ref.md) +Examples index (`next` branch): [references/examples.md](references/examples.md) + +## 3. Retrieve — open the doc for the task + +Fetch the page before implementing. Installed `@next` types win over guesses. + +| You need to… | Open | +| ------------ | ---- | +| Orient / choose preview | [1.0 preview overview](https://developers.cloudflare.com/sandbox/1-0-preview/) | +| First Worker, wrangler, Dockerfile | [Get started](https://developers.cloudflare.com/sandbox/1-0-preview/get-started/) | +| `exec`, handles, readiness, durability | [Process execution](https://developers.cloudflare.com/sandbox/1-0-preview/processes/) | +| Process API signatures | [Processes API](https://developers.cloudflare.com/sandbox/1-0-preview/api/processes/) | +| Sandbox ID vs container vs sleep/destroy | [Lifecycle](https://developers.cloudflare.com/sandbox/1-0-preview/lifecycle/) | +| `cwd` / `env` / `setEnvVars` | [Environment](https://developers.cloudflare.com/sandbox/1-0-preview/environment/) | +| Interactive PTY / browser terminal | [Terminals](https://developers.cloudflare.com/sandbox/1-0-preview/terminals/) · [Terminals API](https://developers.cloudflare.com/sandbox/1-0-preview/api/terminals/) | +| Python/JS code interpreter | [Interpreter](https://developers.cloudflare.com/sandbox/1-0-preview/interpreter/) · [Interpreter API](https://developers.cloudflare.com/sandbox/1-0-preview/api/interpreter/) | +| Extensions model | [Extensions](https://developers.cloudflare.com/sandbox/1-0-preview/extensions/) | +| Error classes and recovery | [Errors](https://developers.cloudflare.com/sandbox/1-0-preview/errors/) · [Errors API](https://developers.cloudflare.com/sandbox/1-0-preview/api/errors/) | +| Common failures | [Troubleshooting](https://developers.cloudflare.com/sandbox/1-0-preview/troubleshooting/) | +| API hub | [API reference](https://developers.cloudflare.com/sandbox/1-0-preview/api/) | +| Files, mounts, backups, ports, tunnels, `proxyToSandbox` | Main docs for shared surfaces (ignore stable-only session/transport/`sandbox.terminal`): [Files](https://developers.cloudflare.com/sandbox/api/files/) · [Storage / mounts](https://developers.cloudflare.com/sandbox/api/storage/) · [Ports](https://developers.cloudflare.com/sandbox/api/ports/) · [Tunnels](https://developers.cloudflare.com/sandbox/api/tunnels/) · [Backups](https://developers.cloudflare.com/sandbox/api/backups/) · [Outbound traffic](https://developers.cloudflare.com/sandbox/guides/outbound-traffic/) · [Expose services](https://developers.cloudflare.com/sandbox/guides/expose-services/) · [Production](https://developers.cloudflare.com/sandbox/guides/production-deployment/) | +| Example apps | [examples on `next`](https://github.com/cloudflare/sandbox-sdk/tree/next/examples) | +| Still on stable package | **`sandbox-stable`** · [Main Sandbox docs](https://developers.cloudflare.com/sandbox/) | +| Porting an existing stable app | **`sandbox-migrate-to-next`** · [Migrate](https://developers.cloudflare.com/sandbox/1-0-preview/migrate/) | + +## 4. Before you ship + +- Lockfile and Dockerfile on the **same** `@next` line +- Typecheck against installed `@next` types +- No live secrets in sandbox env +- Production preview hostnames need wildcard DNS on a custom domain when using those URL patterns diff --git a/skills/sandbox-next/references/api-quick-ref.md b/skills/sandbox-next/references/api-quick-ref.md new file mode 100644 index 0000000..8c2eec1 --- /dev/null +++ b/skills/sandbox-next/references/api-quick-ref.md @@ -0,0 +1,94 @@ +# `@next` cheatsheet (not the full API) + +Scan aid for the **process / terminal / interpreter** shapes that differ most from stable. **Not exhaustive.** + +| For… | Go here | +| ---- | ------- | +| Full signatures and types | Installed `@cloudflare/sandbox@next` package types | +| Preview API hub | https://developers.cloudflare.com/sandbox/1-0-preview/api/ | +| Processes · terminals · errors · interpreter | Same hub (dedicated pages) | +| Files, mounts, backups, ports, tunnels, lifecycle options | Main docs linked from the hub and from **`sandbox-next`** § Retrieve — ignore stable-only session/transport bits | +| Mental model (exec, durability, ID vs container) | https://developers.cloudflare.com/sandbox/1-0-preview/processes/ · https://developers.cloudflare.com/sandbox/1-0-preview/lifecycle/ | +| `Sandbox` extends `Container` | [Cloudflare Containers](https://developers.cloudflare.com/containers/) + Sandbox lifecycle docs above | + +If something is missing here, it is almost certainly **documented elsewhere or in types**—do not invent it from this file. + +--- + +## Lifecycle (common options) + +```ts +getSandbox(binding, sandboxId, options?: { + sleepAfter?: string | number; + keepAlive?: boolean; + normalizeId?: boolean; +}): Sandbox + +await sandbox.destroy(): Promise +``` + +No `transport` / `enableDefaultSession` on `@next`. + +## Processes + +```ts +await sandbox.exec(argv: readonly [string, ...string[]], options?: { + cwd?: string; + env?: Record; + timeout?: number; // remote process lifetime +}): Promise + +await sandbox.getProcess(id: string): Promise +await sandbox.listProcesses(): Promise + +await process.output({ encoding?: "utf8"; maxBytes?; timeout?; signal? }) +await process.logs({ since?; replay?; follow?; signal? }) +await process.waitForExit({ timeout?; signal? }) +await process.waitForPort(port, { mode?: "tcp" | "http"; path?; timeout?; ... }) +await process.waitForLog(pattern, { stream?; timeout?; signal? }) +await process.kill(signal?: number) // default 15 +await process.status() +``` + +`await exec` = launch succeeded, not exit. No process stdin. + +## Terminals + +```ts +await sandbox.createTerminal({ + command: readonly [string, ...string[]]; + cwd?; env?; cols?; rows?; bufferSize?; +}): Promise + +await sandbox.getTerminal(id): Promise +await sandbox.listTerminals(): Promise + +await terminal.connect(request, { cursor?; cols?; rows? }) +await terminal.write(data: Uint8Array) +await terminal.resize(cols, rows) +await terminal.output({ since?; replay?; follow?; signal? }) +await terminal.interrupt() +await terminal.terminate() +``` + +## Interpreter (extension) + +```ts +import { withInterpreter } from "@cloudflare/sandbox/interpreter"; +// subclass: interpreter = withInterpreter(this) + +await sandbox.interpreter.createCodeContext({ language?, cwd? }) +await sandbox.interpreter.runCode(code, { context?, language?, onStdout?, ... }) +await sandbox.interpreter.runCodeStream(code, { context?, language? }) +await sandbox.interpreter.listCodeContexts() +await sandbox.interpreter.deleteCodeContext(id) +``` + +## Environment + +```ts +await sandbox.setEnvVars(Record) // undefined removes +// plus env on exec / createTerminal +``` + +Non-secret config only. Secrets: Worker + outbound handlers. diff --git a/skills/sandbox-next/references/examples.md b/skills/sandbox-next/references/examples.md new file mode 100644 index 0000000..49c75aa --- /dev/null +++ b/skills/sandbox-next/references/examples.md @@ -0,0 +1,15 @@ +# `@next` examples index + +Pointers only—not a full catalog. Prefer the repo tree and docs. + +https://github.com/cloudflare/sandbox-sdk/tree/next/examples + +| Example | Use when | +| ------- | -------- | +| `minimal` | Basic `@next` Worker | +| `code-interpreter` | `withInterpreter` | +| `openai-agents` / `opencode` / `claude-code` / `codex` | Agent harnesses | +| `collaborative-terminal` / `s3-mount` | Terminals / mounts | +| `authentication` | Multi-user sandbox IDs | + +Use the **`next`** branch for `@cloudflare/sandbox@next`. \ No newline at end of file diff --git a/skills/sandbox-sdk/SKILL.md b/skills/sandbox-sdk/SKILL.md deleted file mode 100644 index 2e7e0c3..0000000 --- a/skills/sandbox-sdk/SKILL.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -name: sandbox-sdk -description: Build sandboxed applications for secure code execution. Load when building AI code execution, code interpreters, CI/CD systems, interactive dev environments, or executing untrusted code. Covers Sandbox SDK lifecycle, commands, files, code interpreter, and preview URLs. Biases towards retrieval from Cloudflare docs over pre-trained knowledge. ---- - -# Cloudflare Sandbox SDK - -Build secure, isolated code execution environments on Cloudflare Workers. - -## FIRST: Verify Installation - -```bash -npm install @cloudflare/sandbox -docker info # Must succeed - Docker required for local dev -``` - -## Retrieval Sources - -Your knowledge of the Sandbox SDK may be outdated. **Prefer retrieval over pre-training** for any Sandbox SDK task. - -| Resource | URL | -|----------|-----| -| Docs | https://developers.cloudflare.com/sandbox/ | -| API Reference | https://developers.cloudflare.com/sandbox/api/ | -| Examples | https://github.com/cloudflare/sandbox-sdk/tree/main/examples | -| Get Started | https://developers.cloudflare.com/sandbox/get-started/ | - -When implementing features, fetch the relevant doc page or example first. - -## Required Configuration - -**wrangler.jsonc** (exact - do not modify structure): - -```jsonc -{ - "containers": [{ - "class_name": "Sandbox", - "image": "./Dockerfile", - "instance_type": "lite", - "max_instances": 1 - }], - "durable_objects": { - "bindings": [{ "class_name": "Sandbox", "name": "Sandbox" }] - }, - "migrations": [{ "new_sqlite_classes": ["Sandbox"], "tag": "v1" }] -} -``` - -**Worker entry** - must re-export Sandbox class: - -```typescript -import { getSandbox } from '@cloudflare/sandbox'; -export { Sandbox } from '@cloudflare/sandbox'; // Required export -``` - -## Quick Reference - -| Task | Method | -|------|--------| -| Get sandbox | `getSandbox(env.Sandbox, 'user-123')` | -| Run command | `await sandbox.exec('python script.py')` | -| Run code (interpreter) | `await sandbox.runCode(code, { language: 'python' })` | -| Write file | `await sandbox.writeFile('/workspace/app.py', content)` | -| Read file | `await sandbox.readFile('/workspace/app.py')` | -| Create directory | `await sandbox.mkdir('/workspace/src', { recursive: true })` | -| List files | `await sandbox.listFiles('/workspace')` | -| Expose port | `await sandbox.exposePort(8080)` | -| Destroy | `await sandbox.destroy()` | - -## Core Patterns - -### Execute Commands - -```typescript -const sandbox = getSandbox(env.Sandbox, 'user-123'); -const result = await sandbox.exec('python --version'); -// result: { stdout, stderr, exitCode, success } -``` - -### Code Interpreter (Recommended for AI) - -Use `runCode()` for executing LLM-generated code with rich outputs: - -```typescript -const ctx = await sandbox.createCodeContext({ language: 'python' }); - -await sandbox.runCode('import pandas as pd; data = [1,2,3]', { context: ctx }); -const result = await sandbox.runCode('sum(data)', { context: ctx }); -// result.results[0].text = "6" -``` - -**Languages**: `python`, `javascript`, `typescript` - -State persists within context. Create explicit contexts for production. - -### File Operations - -```typescript -await sandbox.mkdir('/workspace/project', { recursive: true }); -await sandbox.writeFile('/workspace/project/main.py', code); -const file = await sandbox.readFile('/workspace/project/main.py'); -const files = await sandbox.listFiles('/workspace/project'); -``` - -## When to Use What - -| Need | Use | Why | -|------|-----|-----| -| Shell commands, scripts | `exec()` | Direct control, streaming | -| LLM-generated code | `runCode()` | Rich outputs, state persistence | -| Build/test pipelines | `exec()` | Exit codes, stderr capture | -| Data analysis | `runCode()` | Charts, tables, pandas | - -## Extending the Dockerfile - -Base image (`docker.io/cloudflare/sandbox:0.7.0`) includes Python 3.11, Node.js 20, and common tools. - -Add dependencies by extending the Dockerfile: - -```dockerfile -FROM docker.io/cloudflare/sandbox:0.7.0 - -# Python packages -RUN pip install requests beautifulsoup4 - -# Node packages (global) -RUN npm install -g typescript - -# System packages -RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/* - -EXPOSE 8080 # Required for local dev port exposure -``` - -Keep images lean - affects cold start time. - -## Preview URLs (Port Exposure) - -Expose HTTP services running in sandboxes: - -```typescript -const { url } = await sandbox.exposePort(8080); -// Returns preview URL for the service -``` - -**Production requirement**: Preview URLs need a custom domain with wildcard DNS (`*.yourdomain.com`). The `.workers.dev` domain does not support preview URL subdomains. - -See: https://developers.cloudflare.com/sandbox/guides/expose-services/ - -## OpenAI Agents SDK Integration - -The SDK provides helpers for OpenAI Agents at `@cloudflare/sandbox/openai`: - -```typescript -import { Shell, Editor } from '@cloudflare/sandbox/openai'; -``` - -See `examples/openai-agents` for complete integration pattern. - -## Sandbox Lifecycle - -- `getSandbox()` returns immediately - container starts lazily on first operation -- Containers sleep after 10 minutes of inactivity (configurable via `sleepAfter`) -- Use `destroy()` to immediately free resources -- Same `sandboxId` always returns same sandbox instance - -## Anti-Patterns - -- **Don't use internal clients** (`CommandClient`, `FileClient`) - use `sandbox.*` methods -- **Don't skip the Sandbox export** - Worker won't deploy without `export { Sandbox }` -- **Don't hardcode sandbox IDs for multi-user** - use user/session identifiers -- **Don't forget cleanup** - call `destroy()` for temporary sandboxes - -## Detailed References - -- **[references/api-quick-ref.md](references/api-quick-ref.md)** - Full API with options and return types -- **[references/examples.md](references/examples.md)** - Example index with use cases diff --git a/skills/sandbox-sdk/references/api-quick-ref.md b/skills/sandbox-sdk/references/api-quick-ref.md deleted file mode 100644 index 34cf760..0000000 --- a/skills/sandbox-sdk/references/api-quick-ref.md +++ /dev/null @@ -1,113 +0,0 @@ -# Sandbox SDK API Reference - -Detailed API for `@cloudflare/sandbox`. For full docs: https://developers.cloudflare.com/sandbox/api/ - -## Lifecycle - -```typescript -getSandbox(binding: DurableObjectNamespace, sandboxId: string, options?: SandboxOptions): Sandbox - -interface SandboxOptions { - sleepAfter?: string; // Duration before auto-sleep (default: "10m") - keepAlive?: boolean; // Prevent auto-sleep (default: false) - normalizeId?: boolean; // Lowercase IDs for preview URLs (default: false) -} - -await sandbox.destroy(): Promise // Immediately terminate and delete all state -``` - -## Commands - -```typescript -await sandbox.exec(command: string, options?: ExecOptions): Promise - -interface ExecOptions { - cwd?: string; // Working directory - env?: Record; // Environment variables - timeout?: number; // Timeout in ms (no default; runs without timeout if unset) - stdin?: string; // Input to command -} - -interface ExecResult { - stdout: string; - stderr: string; - exitCode: number; - success: boolean; // exitCode === 0 -} -``` - -## Code Interpreter - -```typescript -await sandbox.createCodeContext(options?: CreateContextOptions): Promise - -interface CreateContextOptions { - language?: 'python' | 'javascript' | 'typescript'; // default: 'python' - cwd?: string; // Working directory (default: '/workspace') - envVars?: Record; - timeout?: number; // Request timeout in ms (default: 30000) -} - -await sandbox.runCode(code: string, options?: RunCodeOptions): Promise - -interface RunCodeOptions { - context?: CodeContext; // Reuse context for state persistence - language?: 'python' | 'javascript' | 'typescript'; - timeout?: number; // Execution timeout in ms (default: 60000) -} - -interface ExecutionResult { - code: string; - logs: { stdout: string[]; stderr: string[] }; - results: RichOutput[]; // text, html, png, json, etc. - error?: { name: string; value: string; traceback: string[] }; - executionCount: number; -} -``` - -## Files - -```typescript -await sandbox.writeFile(path: string, content: string | Uint8Array): Promise -await sandbox.readFile(path: string): Promise<{ content: string }> -await sandbox.mkdir(path: string, options?: { recursive?: boolean }): Promise -await sandbox.listFiles(path: string): Promise -await sandbox.deleteFile(path: string): Promise - -interface FileMetadata { - name: string; - path: string; - isDirectory: boolean; - size: number; - modifiedAt: string; -} -``` - -## Ports - -```typescript -await sandbox.exposePort(port: number): Promise<{ url: string; token: string }> -await sandbox.unexposePort(port: number): Promise -await sandbox.listPorts(): Promise -``` - -## Error Handling - -Errors include context about the operation: - -```typescript -try { - await sandbox.exec('invalid-command'); -} catch (error) { - // error.message includes command and sandbox context -} -``` - -For `runCode()`, check `result.error` instead of catching: - -```typescript -const result = await sandbox.runCode('1/0', { language: 'python' }); -if (result.error) { - console.error(result.error.name); // "ZeroDivisionError" -} -``` diff --git a/skills/sandbox-sdk/references/examples.md b/skills/sandbox-sdk/references/examples.md deleted file mode 100644 index e02b54b..0000000 --- a/skills/sandbox-sdk/references/examples.md +++ /dev/null @@ -1,49 +0,0 @@ -# Sandbox SDK Examples - -All examples: https://github.com/cloudflare/sandbox-sdk/tree/main/examples - -## Example Index - -| Example | Use Case | Key File | -|---------|----------|----------| -| `minimal` | Basic setup, exec, file ops | `src/index.ts` | -| `code-interpreter` | AI code execution with Workers AI | `src/index.ts` | -| `openai-agents` | OpenAI Agents SDK integration | `src/index.ts` | -| `opencode` | OpenCode agent integration | `src/index.ts` | -| `claude-code` | Claude Code agent integration | `src/index.ts` | -| `typescript-validator` | TypeScript compilation/validation | `src/index.ts` | -| `authentication` | Auth patterns for sandboxes | `src/index.ts` | - -## When to Use Which Example - -| Building | Start With | -|----------|------------| -| AI code execution | `code-interpreter` | -| Agent with shell + file editing | `openai-agents` | -| Basic command execution | `minimal` | -| Code validation service | `typescript-validator` | -| Multi-user sandboxes | `authentication` | - -## Common Patterns from Examples - -**Sandbox per user/session** (from `openai-agents`): -```typescript -const sandbox = getSandbox(env.Sandbox, `session-${sessionId}`); -``` - -**Code context reuse** (from `code-interpreter`): -```typescript -const pythonCtx = await sandbox.createCodeContext({ language: 'python' }); -const result = await sandbox.runCode(code, { context: pythonCtx }); -``` - -**Resource cleanup** (from `code-interpreter`): -```typescript -try { - // ... use sandbox -} finally { - await sandbox.destroy(); -} -``` - -Fetch the full example source when implementing similar patterns. diff --git a/skills/sandbox-stable/SKILL.md b/skills/sandbox-stable/SKILL.md new file mode 100644 index 0000000..8faf22b --- /dev/null +++ b/skills/sandbox-stable/SKILL.md @@ -0,0 +1,110 @@ +--- +name: sandbox-stable +description: Use when building or changing Cloudflare Sandbox apps on the current stable @cloudflare/sandbox package (default npm tag)—commands, sessions, files, ports, tunnels, terminals, bridge, production, or deprecated-API cleanup while staying on stable. Not for @cloudflare/sandbox@next (use sandbox-next) or for porting to 1.0 (use sandbox-migrate-to-next). +--- + +# Sandbox SDK — stable package + +Isolated Linux environments on [Cloudflare Containers](https://developers.cloudflare.com/containers/), driven from Workers. + +**Prefer the main Sandbox docs and installed stable types over memory.** This skill is a gate, a contract, and a retrieval map—not a full manual. + +This line is the **current stable** default npm package. The main [Sandbox documentation](https://developers.cloudflare.com/sandbox/) describes it. Existing apps can stay here and keep shipping. + +We recommend **new projects** on `@cloudflare/sandbox@next` with **`sandbox-next`**. When you can, plan a move with **`sandbox-migrate-to-next`** so you are ready when 1.0 becomes the stable release. Do not force that port unless the user asks. + +## 1. Gate — confirm the package line + +Before writing code, inspect the app: + +| Check | Must match | +| ----- | ---------- | +| npm dependency | Default `@cloudflare/sandbox` (**not** `@next` / preview tags) | +| Container image | Matching **stable** image (not `cloudflare/sandbox:next`) | + +| If you find… | Action | +| ------------ | ------ | +| `@cloudflare/sandbox@next` or a `next` image | **Stop.** Load **`sandbox-next`**. | +| User wants to port to 1.0 / `@next` | **Stop.** Load **`sandbox-migrate-to-next`**. Do not half-apply preview APIs on a stable package. | +| Only cleaning deprecated stable APIs | Stay here; use the [2026 deprecation guide](https://developers.cloudflare.com/sandbox/guides/2026-deprecation/). That is **not** a move to `@next`. | + +Never mix a stable Worker package with an `@next` container image (or the reverse). + +Skills install: [Agent setup](https://developers.cloudflare.com/agent-setup/) · [cloudflare/skills](https://github.com/cloudflare/skills) + +## 2. Contract — non-negotiables + +- `await sandbox.exec(command)` takes a **command string** and resolves when the command **finishes**, with buffered `stdout` / `stderr` / `exitCode` (and related fields). +- Long-running and streaming work use the **stable** command APIs (`startProcess`, `execStream`, and related helpers)—not the `@next` single-handle model. Open the Commands docs; do not invent `@next` `output()` handles on stable. +- **Sessions** can preserve working directory and environment across commands (default session / `enableDefaultSession`, `createSession`). See Sessions docs when state must carry across calls. +- Interactive browser terminals often use **`sandbox.terminal(request)`** and session/xterm helpers on stable—not preview `createTerminal` unless the package is `@next`. +- Prefer **RPC** transport when using tunnels or large/binary streaming. HTTP/WebSocket transports are deprecated (cleanup guide below). +- Files, mounts, ports, tunnels, backups, lifecycle, and interpreter: use main docs for signatures; trust installed **stable** types. +- Non-secret config in sandbox env; live credentials in the Worker. Use outbound handlers when processes call external APIs. +- Production preview hostnames need wildcard DNS on a custom domain when using those URL patterns. +- Do **not** apply `@next` argv/`process.output()` APIs while the dependency is still stable. +- Self-deployed **bridge** stays on the stable package and image. [Bridge](https://developers.cloudflare.com/sandbox/bridge/) + +Minimal shape: + +```ts +import { getSandbox, proxyToSandbox, Sandbox } from "@cloudflare/sandbox"; + +export { Sandbox }; + +const sandbox = getSandbox(env.Sandbox, "user-123"); +const result = await sandbox.exec('python3 -c "print(2 + 2)"'); +// result.stdout, result.exitCode, result.success +``` + +## 3. Retrieve — open the doc for the task + +Fetch the page before implementing. Installed stable types win over guesses. + +| You need to… | Open | +| ------------ | ---- | +| Orient | [Sandbox overview](https://developers.cloudflare.com/sandbox/) | +| First Worker, template, Docker | [Get started](https://developers.cloudflare.com/sandbox/get-started/) | +| `exec`, streaming, background processes | [Commands API](https://developers.cloudflare.com/sandbox/api/commands/) · [Execute commands](https://developers.cloudflare.com/sandbox/guides/execute-commands/) · [Background processes](https://developers.cloudflare.com/sandbox/guides/background-processes/) · [Streaming output](https://developers.cloudflare.com/sandbox/guides/streaming-output/) | +| Sessions / shell state across commands | [Sessions concept](https://developers.cloudflare.com/sandbox/concepts/sessions/) · [Sessions API](https://developers.cloudflare.com/sandbox/api/sessions/) | +| `getSandbox` options, sleep, destroy | [Lifecycle API](https://developers.cloudflare.com/sandbox/api/lifecycle/) · [Sandbox options](https://developers.cloudflare.com/sandbox/configuration/sandbox-options/) | +| Env vars | [Environment variables](https://developers.cloudflare.com/sandbox/configuration/environment-variables/) | +| Files | [Files API](https://developers.cloudflare.com/sandbox/api/files/) · [Manage files](https://developers.cloudflare.com/sandbox/guides/manage-files/) · [File watching](https://developers.cloudflare.com/sandbox/api/file-watching/) | +| Buckets / mounts | [Storage API](https://developers.cloudflare.com/sandbox/api/storage/) · [Mount buckets](https://developers.cloudflare.com/sandbox/guides/mount-buckets/) | +| Backups | [Backups API](https://developers.cloudflare.com/sandbox/api/backups/) · [Backup and restore](https://developers.cloudflare.com/sandbox/guides/backup-restore/) | +| Ports, preview URLs, expose | [Ports API](https://developers.cloudflare.com/sandbox/api/ports/) · [Expose services](https://developers.cloudflare.com/sandbox/guides/expose-services/) | +| Tunnels | [Tunnels API](https://developers.cloudflare.com/sandbox/api/tunnels/) | +| Proxy / Workers connections | [Proxy requests](https://developers.cloudflare.com/sandbox/guides/proxy-requests/) · [Workers connections](https://developers.cloudflare.com/sandbox/guides/workers-connections/) | +| Browser / PTY terminal | [Terminal API](https://developers.cloudflare.com/sandbox/api/terminal/) · [Terminal concept](https://developers.cloudflare.com/sandbox/concepts/terminal/) · [Browser terminals](https://developers.cloudflare.com/sandbox/guides/browser-terminals/) | +| Code interpreter | [Interpreter API](https://developers.cloudflare.com/sandbox/api/interpreter/) · [Code execution](https://developers.cloudflare.com/sandbox/guides/code-execution/) | +| Git in the sandbox | [Git workflows](https://developers.cloudflare.com/sandbox/guides/git-workflows/) | +| Secrets / egress | [Outbound traffic](https://developers.cloudflare.com/sandbox/guides/outbound-traffic/) | +| WebSockets | [WebSocket connections](https://developers.cloudflare.com/sandbox/guides/websocket-connections/) | +| Docker-in-Docker | [Docker in Docker](https://developers.cloudflare.com/sandbox/guides/docker-in-docker/) | +| Production deploy | [Production deployment](https://developers.cloudflare.com/sandbox/guides/production-deployment/) | +| Containers concept | [Containers](https://developers.cloudflare.com/sandbox/concepts/containers/) | +| How-to index | [Guides](https://developers.cloudflare.com/sandbox/guides/) | +| API index | [API reference](https://developers.cloudflare.com/sandbox/api/) | +| Deprecated APIs **while staying on stable** | [2026 deprecation guide](https://developers.cloudflare.com/sandbox/guides/2026-deprecation/) | +| Self-deployed bridge | [Bridge](https://developers.cloudflare.com/sandbox/bridge/) · [Bridge HTTP API](https://developers.cloudflare.com/sandbox/bridge/http-api/) | +| Examples (stable/`main`) | [examples on GitHub](https://github.com/cloudflare/sandbox-sdk/tree/main/examples) | +| New work on 1.0 preview | **`sandbox-next`** · [1.0 preview](https://developers.cloudflare.com/sandbox/1-0-preview/) | +| Port existing app to `@next` | **`sandbox-migrate-to-next`** · [Migrate](https://developers.cloudflare.com/sandbox/1-0-preview/migrate/) | + +### Deprecated-API cleanup (stay on stable) + +Update package + matching image first, then follow the guide. Typical search: + +```sh +rg 'SANDBOX_TRANSPORT|transport:|exposePort\(|enableDefaultSession|execStream\(|readFileStream|writeFileStream' +``` + +This path does **not** switch you to `@next`. + +## 4. Before you ship + +- Worker package and container image on the **same stable** line +- Typecheck against installed stable types +- No live secrets in sandbox env +- If using deprecated transports/helpers, finish or track [2026 deprecation](https://developers.cloudflare.com/sandbox/guides/2026-deprecation/) cleanup +- When the team is ready for 1.0, use **`sandbox-migrate-to-next`**—do not force cutover unprompted