diff --git a/.cursor/rules/tailwind-ide-suggestions.mdc b/.cursor/rules/tailwind-ide-suggestions.mdc new file mode 100644 index 00000000..6ed2c580 --- /dev/null +++ b/.cursor/rules/tailwind-ide-suggestions.mdc @@ -0,0 +1,31 @@ +--- +description: Apply Tailwind IDE/linter class shorthand suggestions when writing or editing className strings +alwaysApply: true +--- + +# Tailwind: use IDE shorthand suggestions + +When Tailwind CSS IntelliSense or the linter says a class **can be written as** +a shorter form, apply that suggestion. Do not leave the verbose form. + +## Do this + +- Prefer the suggested class over the longer arbitrary variant. +- Apply the rewrite while editing, not as a follow-up unless you only spotted it + in review. +- If several classes in the same string have suggestions, fix all of them. + +## Examples + +| Verbose | Prefer | +| ----------------------- | --------------- | +| `[&_:first-child]:mt-0` | `**:first:mt-0` | +| `[&_:last-child]:mb-0` | `**:last:mb-0` | + +Same rule for any other `can be written as …` Tailwind diagnostic the IDE shows. + +## Scope + +- Applies to `className`, `class`, `@apply`, and `cn(...)` / similar helpers. +- Do not invent shorthands the tooling did not suggest. +- Unrelated arbitrary variants with no IDE suggestion may stay as-is. diff --git a/website/README.md b/website/README.md index 5761abed..34ff5c1c 100644 --- a/website/README.md +++ b/website/README.md @@ -1,6 +1,6 @@ -# Sandbox Kit docs site +# Docs site -User guide for the Sandbox Kit SDK—a lightweight, provider-pluggable client for cloud sandboxes. Fumadocs + TanStack Start. Source pages: `content/docs`. +Fumadocs + TanStack Start site for the Sandbox Kit SDK user guide. Pages live in `content/docs`. ```sh npm install @@ -13,16 +13,6 @@ Open http://localhost:3000. npm run build:pages ``` -GitHub Actions publishes from `main` to https://sandbox-kit.github.io/kit/. Set Pages to deploy from GitHub Actions. +GitHub Actions deploys `main` to https://sandbox-kit.github.io/kit/. Set Pages to deploy from GitHub Actions. -## Structure - -| Section | Pages | -| ---------- | ------------------------------------------------------------------------- | -| Quickstart | `index` | -| Concepts | `concepts` | -| Providers | `daytona`, `modal` | -| Guides | `configuration`, `create`, `lifecycle`, `filesystem`, `process`, `errors` | -| Examples | `examples` | - -Language tabs (TypeScript / Go) persist in the browser. Behavior reference for the same API lives in the repository `docs/` directory. Runnable projects live in `examples/`. +Language and provider tabs persist in the browser. Runnable samples are linked from the Examples page into the repo `examples/` tree. diff --git a/website/content/docs/concepts.mdx b/website/content/docs/concepts.mdx index 85985927..5474d76a 100644 --- a/website/content/docs/concepts.mdx +++ b/website/content/docs/concepts.mdx @@ -1,62 +1,587 @@ --- title: Concepts -description: Lightweight SDK model—client, handle, pluggable providers, capabilities, close. +description: Client, handle, filesystem, process, providers, concurrency, and close. --- -Sandbox Kit is a lightweight SDK over provider native clients. Depend on `@sandbox-kit/sandbox` (or the Go `sandbox` module) plus a provider module. Swap the provider; keep the same client surface. +Sandbox Kit is an SDK. You construct a client with a provider and config; the client calls that provider for create, lifecycle, filesystem, and process work. Application code stays on the shared client surface. ## Client -Construct one client with a provider and config. Invalid config fails immediately. Credentials are not verified until the first provider request. +Credentials are checked on the first provider call, not at construction. Details: [Configuration](/docs/configuration). -| Language | Construct | -| ---------- | --------------------------------------------------- | -| TypeScript | `new Client({ provider, ... })` | -| Go | `sandbox.NewClient(provider, &sandbox.Config{...})` | + + -Shared config fields: `auth`, `scope`, `endpoint`, `region`, `timeout`. See [Configuration](/docs/configuration). + + +```ts +import { daytona } from "@sandbox-kit/daytona"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: daytona(), + auth: { apiKey: { key: process.env.DAYTONA_API_KEY! } }, + region: process.env.DAYTONA_TARGET, + timeout: 120_000, // default client deadline (ms) +}); +``` + + + + + +```ts +import { modal } from "@sandbox-kit/modal"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: modal(), + auth: { + tokenPair: { + id: process.env.MODAL_TOKEN_ID!, + secret: process.env.MODAL_TOKEN_SECRET!, + }, + }, + scope: { + appName: process.env.MODAL_APP_NAME!, + environment: "main", + }, + region: process.env.MODAL_REGION, + timeout: 120_000, // default client deadline (ms) +}); +``` + + + + + + + + +```go +import ( + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/daytona" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +client, err := sandbox.NewClient(daytona.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("DAYTONA_API_KEY")}, + }}, + Region: sandbox.Value(os.Getenv("DAYTONA_TARGET")), + Timeout: sandbox.Duration(2 * time.Minute), +}) +if err != nil { + return err +} +``` + + + + + +```go +import ( + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/modal" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +client, err := sandbox.NewClient(modal.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{ + TokenPair: &sandbox.TokenPairCredentials{ + Id: os.Getenv("MODAL_TOKEN_ID"), + Secret: os.Getenv("MODAL_TOKEN_SECRET"), + }, + }}, + Scope: &sandbox.Scope{ + AppName: sandbox.Value(os.Getenv("MODAL_APP_NAME")), + Environment: sandbox.Value("main"), + }, + Region: sandbox.Value(os.Getenv("MODAL_REGION")), + Timeout: sandbox.Duration(2 * time.Minute), +}) +if err != nil { + return err +} +``` + + + + + ## Sandbox handle -`create` and the provider's open method (for example Daytona `get`, Modal `attach`) return a handle. Use that handle for lifecycle, filesystem, and process calls. +`create` returns a handle. To reopen an existing sandbox, use `get` or `attach`—see [Lifecycle](/docs/lifecycle). Use the handle for lifecycle, filesystem, and process calls. + + + + + + +```ts +const sandbox = await client.create({ + source: { image: { reference: "daytonaio/playground:latest" } }, +}); +console.log(sandbox.id); +``` + + + + + +```ts +const sandbox = await client.create({ + source: { image: { reference: "alpine:3.21" } }, +}); +console.log(sandbox.id); +``` + + + + + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "daytonaio/playground:latest"}, + }}, +}) +if err != nil { + return err +} +fmt.Println(instance.ID()) +``` -| Language | Handle API | -| ---------- | --------------------------------------------------------------------------------- | -| TypeScript | `sandbox.id`, `sandbox.filesystem`, `sandbox.process`, lifecycle methods | -| Go | `instance.ID()`, `instance.Filesystem()`, `instance.Process()`, lifecycle methods | + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "alpine:3.21"}, + }}, +}) +if err != nil { + return err +} +fmt.Println(instance.ID()) +``` + + + + + + +## Filesystem + +Sandbox paths are absolute. Write, read, copy, list, and remove go through the handle. More: [Filesystem](/docs/filesystem). + + + + +```ts +const body = new TextEncoder().encode("hello from kit\n"); +await sandbox.filesystem.writeFile("/tmp/note.txt", body); + +const bytes = await sandbox.filesystem.readFile("/tmp/note.txt"); +console.log(new TextDecoder().decode(bytes)); +``` + + + + +```go +err = instance.Filesystem().WriteFile(ctx, "/tmp/note.txt", []byte("hello from kit\n"), nil) +if err != nil { + return err +} + +data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", nil) +if err != nil { + return err +} +fmt.Printf("%s", data) +``` + + + + +## Process + +Run one command and wait for it to finish. A non-zero exit code is **not** an SDK error—check `exitCode` yourself. More: [Process](/docs/process). + + + + +```ts +const result = await sandbox.process.run({ + argv: ["echo", "hello from kit"], + workingDirectory: "/tmp", + environment: { KIT_DEMO: "from-kit" }, + timeout: 5_000, +}); +// result: { exitCode, stdout, stderr } +console.log(result); +``` + + + + +```go +result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "hello from kit"}, + WorkingDirectory: sandbox.Value("/tmp"), + Environment: map[string]string{"KIT_DEMO": "from-kit"}, + Timeout: sandbox.Duration(5 * time.Second), +}) +if err != nil { + return err +} +// result: ExitCode, Stdout, Stderr +fmt.Println(result) +``` + + + ## Providers -Providers are pluggable. Each one is its own package that adapts a native SDK to the shared client. Depend only on the provider you need; add another module when you add a backend. +Each provider is its own package. Pass it into the client. Auth, create defaults, and which lifecycle calls exist depend on the provider; the client methods stay the same. + +The client talks to Daytona or Modal directly. There is no separate Kit cloud in the middle. + + + + + + +```ts +import { daytona } from "@sandbox-kit/daytona"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: daytona(), + auth: { apiKey: { key: process.env.DAYTONA_API_KEY! } }, +}); +``` + +Setup: [Daytona](/docs/daytona). + + + + -| Provider | Open existing sandbox | Native SDK | -| ------------------------ | ------------------------- | -------------------- | -| [Daytona](/docs/daytona) | `get` (provider-verified) | Daytona SDK v0.225.0 | -| [Modal](/docs/modal) | `attach` (id only) | Modal SDK v0.11.1 | +```ts +import { modal } from "@sandbox-kit/modal"; +import { Client } from "@sandbox-kit/sandbox"; -Apps construct `Client` / `NewClient` with the chosen provider. Provider modules stay outside the core SDK. +await using client = new Client({ + provider: modal(), + auth: { + tokenPair: { + id: process.env.MODAL_TOKEN_ID!, + secret: process.env.MODAL_TOKEN_SECRET!, + }, + }, + scope: { appName: process.env.MODAL_APP_NAME!, environment: "main" }, +}); +``` + +Setup: [Modal](/docs/modal). + + + + + + + + +```go +import ( + "github.com/sandbox-kit/kit/sdks/go/providers/daytona" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +client, err := sandbox.NewClient(daytona.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("DAYTONA_API_KEY")}, + }}, +}) +``` + +Setup: [Daytona](/docs/daytona). + + + + + +```go +import ( + "github.com/sandbox-kit/kit/sdks/go/providers/modal" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +client, err := sandbox.NewClient(modal.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{ + TokenPair: &sandbox.TokenPairCredentials{ + Id: os.Getenv("MODAL_TOKEN_ID"), + Secret: os.Getenv("MODAL_TOKEN_SECRET"), + }, + }}, + Scope: &sandbox.Scope{ + AppName: sandbox.Value(os.Getenv("MODAL_APP_NAME")), + Environment: sandbox.Value("main"), + }, +}) +``` + +Setup: [Modal](/docs/modal). + + + + + ## Capabilities -`supports` / `Supports` reports whether the provider implements an operation. An unsupported call fails in Kit before any provider request. Daytona can still reject a supported lifecycle call for a specific sandbox; that rejection returns as a provider error. +Unsupported calls fail before anything reaches the cloud. The cloud can still refuse a supported call for a particular sandbox; that comes back as a provider error. + + + + + + +```ts +import { LifecycleCapability } from "@sandbox-kit/sandbox"; + +if (!sandbox.supports(LifecycleCapability.Stop)) { + throw new Error(`sandbox ${sandbox.id} cannot be stopped`); +} +``` + + + + + +```ts +import { LifecycleCapability } from "@sandbox-kit/sandbox"; + +if (!sandbox.supports(LifecycleCapability.Terminate)) { + throw new Error(`sandbox ${sandbox.id} cannot be terminated`); +} +``` + + + + + + + + +```go +import "github.com/sandbox-kit/kit/sdks/go/sandbox" + +if !instance.Supports(sandbox.LifecycleCapabilityStop) { + return fmt.Errorf("sandbox %s cannot be stopped", instance.ID()) +} +``` + + + + + +```go +import "github.com/sandbox-kit/kit/sdks/go/sandbox" + +if !instance.Supports(sandbox.LifecycleCapabilityTerminate) { + return fmt.Errorf("sandbox %s cannot be terminated", instance.ID()) +} +``` + + + + + ## Concurrency -Calls on one handle run one at a time (lifecycle, filesystem, and process). A second handle for the same sandbox id does not serialize with the first. Abort or deadline rejects the TypeScript `await` (and ends the Go wait), but the handle stays busy until that provider call settles. +Calls on **one handle** run one at a time. Starting two awaits does not overlap them—the second waits until the first settles. Abort or cancel rejects your wait promptly; the handle stays busy until the provider call finishes. A second handle for the same sandbox id has its own queue, so those calls can run together. + + + + +```ts +// Queued on one handle — `two` starts only after `one` finishes. +const one = sandbox.process.run({ argv: ["echo", "one"] }); +const two = sandbox.process.run({ argv: ["echo", "two"] }); +const [first, second] = await Promise.all([one, two]); +console.log(first, second); + +// Abort ends the wait; the handle stays busy until the provider call settles. +const ac = new AbortController(); +const pending = sandbox.process.run({ + argv: ["sleep", "30"], + signal: ac.signal, +}); +ac.abort(); +await pending.catch(() => {}); +``` + + + +```ts +// Parallel work: another handle for the same id has its own queue. +const other = await client.get(sandbox.id); +await Promise.all([ + sandbox.process.run({ argv: ["echo", "a"] }), + other.process.run({ argv: ["echo", "b"] }), +]); +``` + + + + + +```ts +// Parallel work: another handle for the same id has its own queue. +const other = await client.attach(sandbox.id); +await Promise.all([ + sandbox.process.run({ argv: ["echo", "a"] }), + other.process.run({ argv: ["echo", "b"] }), +]); +``` + + + + + + +```go +// Two goroutines still serialize on one handle — the second waits for the first. +errCh := make(chan error, 2) +go func() { + _, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "one"}, + }) + errCh <- err +}() +go func() { + _, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "two"}, + }) + errCh <- err +}() +for range 2 { + if err := <-errCh; err != nil { + return err + } +} + +// Cancel ends the wait; the handle stays busy until the provider call settles. +runCtx, cancel := context.WithCancel(ctx) +defer cancel() +go func() { + time.Sleep(10 * time.Millisecond) + cancel() +}() +_, _ = instance.Process().Run(runCtx, &sandbox.ExecOptions{ + Argv: []string{"sleep", "30"}, +}) +``` + + + +```go +// Parallel work: another handle for the same id has its own queue. +other, err := client.Get(ctx, instance.ID()) +if err != nil { + return err +} +errCh = make(chan error, 2) +go func() { + _, err := instance.Process().Run(ctx, &sandbox.ExecOptions{Argv: []string{"echo", "a"}}) + errCh <- err +}() +go func() { + _, err := other.Process().Run(ctx, &sandbox.ExecOptions{Argv: []string{"echo", "b"}}) + errCh <- err +}() +for range 2 { + if err := <-errCh; err != nil { + return err + } +} +``` + + + + + +```go +// Parallel work: another handle for the same id has its own queue. +other, err := client.Attach(ctx, instance.ID()) +if err != nil { + return err +} +errCh = make(chan error, 2) +go func() { + _, err := instance.Process().Run(ctx, &sandbox.ExecOptions{Argv: []string{"echo", "a"}}) + errCh <- err +}() +go func() { + _, err := other.Process().Run(ctx, &sandbox.ExecOptions{Argv: []string{"echo", "b"}}) + errCh <- err +}() +for range 2 { + if err := <-errCh; err != nil { + return err + } +} +``` + + + + + ## Close -| Call | Effect | -| ------------------------ | ---------------------------------------- | -| Client `close` / `Close` | Closes the local provider SDK connection | -| Handle `close` / `Close` | Releases local handle resources | +Neither call stops, archives, or deletes the cloud sandbox. Use an explicit lifecycle method for that. + + + + +```ts +await client.close(); // local provider connection +await sandbox.close(); // local handle resources only +``` + + + + +```go +defer client.Close(context.Background()) // local provider connection +_ = instance.Close(context.Background()) // local handle resources only +``` -Neither call stops, terminates, archives, or deletes the cloud sandbox. You can keep using the handle after handle `close` fails only if the provider still accepts calls; prefer an explicit lifecycle method to reclaim the sandbox. + + ## Errors -Failed calls throw `SandboxError` in TypeScript and return `*sandbox.Error` in Go. Both expose `kind`, `field`, `operation`, and `message`. See [Errors](/docs/errors). +Kinds, field paths, and handling: [Errors](/docs/errors). -## Out of scope +## SDK reference -Streaming stdin/stdout, long-lived process sessions, PTY, Daytona `code_run`, GPU requests, resizing, and browsers are not part of this client. +Types, methods, parameters, and return values: [SDK reference](/docs/sdk) ([Client](/docs/sdk/client), [Sandbox](/docs/sdk/sandbox), [Filesystem](/docs/sdk/filesystem), [Process](/docs/sdk/process)). diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 29306757..22951eca 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -1,20 +1,56 @@ --- title: Configuration -description: Auth, scope, endpoint, region, and default timeouts on Client construction. +description: Auth, scope, endpoint, region, and timeouts on the client. --- -Pass provider + config at construction. Later mutations of the config object do not affect a running client. TypeScript throws on invalid config; Go returns `(*Client, error)`. +Pass a provider and config at construction. Changing the config object afterward does not update a running client. Invalid config: [Errors](/docs/errors#invalid-config). -| Field | Purpose | -| ---------- | ----------------------------------------------------------------------------------------------------- | -| `provider` | `daytona()` / `modal()` (or Go `New()`) | -| `auth` | One of `apiKey`, `tokenPair`, `bearerToken`, `oauthRefresh`. Omit to use provider SDK env credentials | -| `scope` | App, environment, or organization | -| `endpoint` | API URL when the provider allows it | -| `region` | Target / default placement | -| `timeout` | Default deadline (TS: ms, Go: `sandbox.Duration`) | + + + +```ts +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider, + timeout: 120_000, // default client deadline (ms) +}); +``` -Switch Daytona or Modal above for auth and scope samples. First client walkthrough: [Daytona](/docs/daytona) or [Modal](/docs/modal). + + + +```go +import ( + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +client, err := sandbox.NewClient(provider, &sandbox.Config{ + Timeout: sandbox.Duration(2 * time.Minute), // default client deadline +}) +if err != nil { + return err +} +``` + + + + +| Field | Purpose | +| ---------- | ------------------------------------------------------------------------------------------- | +| `provider` | Provider constructor | +| `auth` | One of `apiKey`, `tokenPair`, `bearerToken`, `oauthRefresh`. Omit to use provider env creds | +| `scope` | App, environment, or organization | +| `endpoint` | Provider endpoint URL override | +| `region` | Target / default placement | +| `timeout` | Default deadline for calls that omit their own | + + + Provider setup: [Daytona](/docs/daytona). + +Provider setup: [Modal](/docs/modal). ## Client @@ -127,46 +163,305 @@ client, err := sandbox.NewClient(modal.New(), config) -| Setting | What to pass | -| ------------ | -------------------------------------------------------------------- | -| API key | `auth.apiKey.key` / `APIKey` | -| Bearer token | `auth.bearerToken.token` / `BearerToken`, plus an organization id | -| Endpoint | `endpoint` / `Config.Endpoint`. Default `https://app.daytona.io/api` | -| Target | `region` / `Config.Region`. Leave it unset for the default target | -| Organization | `scope.organizationId` / `Scope.OrganizationId` | + + + +| Setting | What to pass | +| ------------ | ------------------------------------------------- | +| `apiKey` | `auth.apiKey.key` | +| Bearer token | `auth.bearerToken.token`, plus an organization id | +| Endpoint | `endpoint`. Default `https://app.daytona.io/api` | +| Target | `region`. Omit for the default target | +| Organization | `scope.organizationId` | + +```ts +await using client = new Client({ + provider: daytona(), + auth: { bearerToken: { token: process.env.DAYTONA_JWT! } }, + scope: { organizationId: process.env.DAYTONA_ORG_ID! }, + endpoint: "https://app.daytona.io/api", + region: process.env.DAYTONA_TARGET, + timeout: 120_000, +}); +``` + + + + +| Setting | What to pass | +| ------------ | ------------------------------------------------------- | +| `APIKey` | `APIKey` | +| Bearer token | `BearerToken`, plus an organization id | +| Endpoint | `Config.Endpoint`. Default `https://app.daytona.io/api` | +| Target | `Config.Region`. Omit for the default target | +| Organization | `Scope.OrganizationId` | + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.BearerToken{ + BearerToken: &sandbox.BearerTokenCredentials{Token: os.Getenv("DAYTONA_JWT")}, + }}, + Scope: &sandbox.Scope{ + OrganizationId: sandbox.Value(os.Getenv("DAYTONA_ORG_ID")), + }, + Endpoint: sandbox.Value("https://app.daytona.io/api"), + Region: sandbox.Value(os.Getenv("DAYTONA_TARGET")), + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(daytona.New(), config) +``` + + + -A remote endpoint uses HTTPS. HTTP is accepted for localhost and loopback. A URL with a username, password, or fragment is rejected. Sandbox Kit does not read the Daytona URL from the environment. +Remote endpoints must be HTTPS. HTTP is fine for localhost and loopback. URLs with a username, password, or fragment are rejected. Sandbox Kit does not read the Daytona URL from the environment. -| Setting | What to pass | -| ------------- | ------------------------------------------------------------------------ | -| Token pair | `auth.tokenPair` / `TokenPair` | -| OAuth refresh | Client id, plus one client secret or one JWT key | -| App | `scope.appName` / `Scope.AppName`, required | -| Environment | `scope.environment` / `Scope.Environment`, often `main` | -| Region | `region` / `Config.Region`. Leave it unset for Modal's default placement | + + -`endpoint` is rejected. You can still set cloud and region on a single [create](/docs/create) request. +| Setting | What to pass | +| ------------- | ------------------------------------------------ | +| Token pair | `auth.tokenPair` | +| OAuth refresh | Client id, plus one client secret or one JWT key | +| App | `scope.appName`, required | +| Environment | `scope.environment`, often `main` | +| Region | `region`. Omit for Modal's default placement | + +```ts +await using client = new Client({ + provider: modal(), + auth: { + oauthRefresh: { + refreshToken: process.env.MODAL_REFRESH_TOKEN!, + clientId: process.env.MODAL_CLIENT_ID!, + clientSecret: process.env.MODAL_CLIENT_SECRET!, + }, + }, + scope: { + appName: process.env.MODAL_APP_NAME!, + environment: "main", + }, + region: process.env.MODAL_REGION, + timeout: 120_000, +}); +``` + + + + +| Setting | What to pass | +| ------------- | --------------------------------------------------- | +| Token pair | `TokenPair` | +| OAuth refresh | Client id, plus one client secret or one JWT key | +| App | `Scope.AppName`, required | +| Environment | `Scope.Environment`, often `main` | +| Region | `Config.Region`. Omit for Modal's default placement | + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.OAuthRefresh{ + OauthRefresh: &sandbox.OAuthCredentials{ + RefreshToken: os.Getenv("MODAL_REFRESH_TOKEN"), + ClientId: os.Getenv("MODAL_CLIENT_ID"), + ClientSecret: sandbox.Value(os.Getenv("MODAL_CLIENT_SECRET")), + }, + }}, + Scope: &sandbox.Scope{ + AppName: sandbox.Value(os.Getenv("MODAL_APP_NAME")), + Environment: sandbox.Value("main"), + }, + Region: sandbox.Value(os.Getenv("MODAL_REGION")), + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(modal.New(), config) +``` + + + + +`endpoint` is rejected. Set cloud and region on a single [create](/docs/create) request instead. ## Timeouts -`timeout` on the client is the default deadline. A create request can override it. A lifecycle call can override it again. +`timeout` on the client is the default deadline. Create and lifecycle calls can override it. + +Explicit zero means no deadline from the client. A context deadline or abort signal that fires earlier still wins. + + + + + + +```ts +await using client = new Client({ + provider: daytona(), + timeout: 120_000, // default for calls that omit timeout +}); + +const sandbox = await client.create( + { source: { image: { reference: "daytonaio/playground:latest" } } }, + { timeout: 60_000 }, // override for this call; 0 = no deadline +); +``` + + + + + +```ts +await using client = new Client({ + provider: modal(), + timeout: 120_000, // default for calls that omit timeout +}); + +const sandbox = await client.create( + { source: { image: { reference: "alpine:3.21" } } }, + { timeout: 60_000 }, // override for this call; 0 = no deadline +); +``` + + + + + + + + +```go +client, err := sandbox.NewClient(daytona.New(), &sandbox.Config{ + Timeout: sandbox.Duration(2 * time.Minute), // default deadline +}) +if err != nil { + return err +} + +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "daytonaio/playground:latest"}, + }}, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), // override; 0 = no deadline + }, +}) +``` + + + + + +```go +client, err := sandbox.NewClient(modal.New(), &sandbox.Config{ + Timeout: sandbox.Duration(2 * time.Minute), // default deadline +}) +if err != nil { + return err +} + +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "alpine:3.21"}, + }}, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), // override; 0 = no deadline + }, +}) +``` + + -Explicit zero adds no deadline of Sandbox Kit's own. A context deadline or an abort signal that arrives earlier still applies. + + Daytona keeps its 60-second default for create, start, stop, pause, and delete - when you omit the timeout, and when you pass explicit zero. A Daytona timeout - that is not a whole number of seconds is rejected. + when you omit the timeout or pass zero. Fractional-second Daytona timeouts are + rejected. ## Credentials -Do not log the config object—it contains secrets. TypeScript configs are plain objects (`JSON.stringify` includes keys). +Construction does not contact the provider. Bad credentials fail on the first create or open. See [Errors](/docs/errors). -Construction does not contact the provider. Bad credentials fail on the first create/get/attach. See [Errors](/docs/errors). + + + + + +```ts +const config = { + provider: daytona(), + auth: { apiKey: { key: process.env.DAYTONA_API_KEY! } }, + timeout: 120_000, +}; + +await using client = new Client(config); +``` + + + + + +```ts +const config = { + provider: modal(), + auth: { + tokenPair: { + id: process.env.MODAL_TOKEN_ID!, + secret: process.env.MODAL_TOKEN_SECRET!, + }, + }, + scope: { appName: process.env.MODAL_APP_NAME!, environment: "main" }, + timeout: 120_000, +}; + +await using client = new Client(config); +``` + + + + + + + + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("DAYTONA_API_KEY")}, + }}, + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(daytona.New(), config) +``` + + + + + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{ + TokenPair: &sandbox.TokenPairCredentials{ + Id: os.Getenv("MODAL_TOKEN_ID"), + Secret: os.Getenv("MODAL_TOKEN_SECRET"), + }, + }}, + Scope: &sandbox.Scope{ + AppName: sandbox.Value(os.Getenv("MODAL_APP_NAME")), + Environment: sandbox.Value("main"), + }, + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(modal.New(), config) +``` + + + + + diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index 927503ba..cd36e918 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -1,20 +1,95 @@ --- title: Create a sandbox -description: Image or snapshot, resources, lifetime, and placement options for create. +description: Image or snapshot, resources, lifetime, and placement for create. --- -`client.create` / `Client.Create` returns a sandbox handle. Build the client first ([Daytona](/docs/daytona), [Modal](/docs/modal), or [Configuration](/docs/configuration)). +Create returns a sandbox handle. Build the client first. -Omitted fields keep provider defaults. Unsupported fields fail in Kit before the provider call ([Errors](/docs/errors)). + + Provider setup: [Daytona](/docs/daytona). + +Provider setup: [Modal](/docs/modal). + +Omitted fields keep provider defaults. Unsupported fields fail before the provider call. + + + + + + +```ts +const sandbox = await client.create( + { + source: { image: { reference: "daytonaio/playground:latest" } }, + resources: { cpuCores: 1, memoryMib: 512n }, + }, + { timeout: 60_000 }, // milliseconds +); +``` + + + + + +```ts +const sandbox = await client.create( + { + source: { image: { reference: "alpine:3.21" } }, + resources: { cpuCores: 1, memoryMib: 512n }, + }, + { timeout: 60_000 }, // milliseconds +); +``` + + -| Language | Durations | Optionals | -| ---------- | ---------------------------------------------------------------- | ------------------ | -| TypeScript | milliseconds; missing property = omitted | nested objects | -| Go | `sandbox.Duration`; numbers via `sandbox.Value` (including zero) | pointers / `Value` | + + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "daytonaio/playground:latest"}, + }}, + Resources: &sandbox.Resources{ + CpuCores: sandbox.Value(1.0), + MemoryMib: sandbox.Value(uint64(512)), + }, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), + }, +}) +``` + + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "alpine:3.21"}, + }}, + Resources: &sandbox.Resources{ + CpuCores: sandbox.Value(1.0), + MemoryMib: sandbox.Value(uint64(512)), + }, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), + }, +}) +``` + + + + + ## Units -Switch Daytona or Modal above for units and create samples. Account quotas, image names, regions, and capacity are checked by the provider. +Account quotas, image names, regions, and capacity are enforced by the provider. @@ -29,6 +104,60 @@ Switch Daytona or Modal above for units and create samples. Account quotas, imag A size override requires an image. A snapshot create keeps the snapshot's size. + + + +```ts +import { PolicyMode } from "@sandbox-kit/sandbox"; + +await client.create({ + source: { image: { reference: "daytonaio/playground:latest" } }, + resources: { + cpuCores: 2, // whole cores + memoryMib: 4096n, // 4 GiB as MiB + diskMib: 8192n, // 8 GiB as MiB + }, + lifetime: { + idlePause: { mode: PolicyMode.Disabled }, + stoppedDelete: { mode: PolicyMode.After, after: 10 * 60_000 }, + }, + runtime: { + workingDirectory: "/home/daytona", + entrypoint: ["sleep", "infinity"], + }, +}); +``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "daytonaio/playground:latest"}, + }}, + Resources: &sandbox.Resources{ + CpuCores: sandbox.Value(2.0), // whole cores + MemoryMib: sandbox.Value(uint64(4096)), // 4 GiB as MiB + DiskMib: sandbox.Value(uint64(8192)), // 8 GiB as MiB + }, + Lifetime: &sandbox.LifetimePolicy{ + IdlePause: &sandbox.AutomaticAction{Mode: sandbox.PolicyModeDisabled}, + StoppedDelete: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(10 * time.Minute), + }, + }, + Runtime: &sandbox.RuntimeConfig{ + WorkingDirectory: sandbox.Value("/home/daytona"), + Entrypoint: []string{"sleep", "infinity"}, + }, +}) +``` + + + + @@ -42,7 +171,64 @@ A size override requires an image. A snapshot create keeps the snapshot's size. | Lifetime | Maximum lifetime and idle termination | | Placement | `placement` on this request. Client `region` is the default | -Pass a registry image. The app name belongs on the client, not on this request. Leave a resource out to keep Modal's default. +Pass a registry image. Put the app name on the client, not on this request. Leave a resource out to keep Modal's default. + + + + +```ts +import { PolicyMode } from "@sandbox-kit/sandbox"; + +await client.create({ + source: { image: { reference: "alpine:3.21" } }, + resources: { + cpuCores: 0.5, // fractional, min 0.125 + memoryMib: 512n, // min 128 when set + }, + placement: { cloud: "aws", regions: ["us-east-1"] }, + lifetime: { + maximumLifetime: 5 * 60_000, // milliseconds + idleTerminate: { mode: PolicyMode.After, after: 60_000 }, + }, + runtime: { + workingDirectory: "/tmp", + entrypoint: ["sleep", "infinity"], + }, +}); +``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "alpine:3.21"}, + }}, + Resources: &sandbox.Resources{ + CpuCores: sandbox.Value(0.5), // fractional, min 0.125 + MemoryMib: sandbox.Value(uint64(512)), // min 128 when set + }, + Placement: &sandbox.Placement{ + Cloud: sandbox.Value("aws"), + Regions: []string{"us-east-1"}, + }, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(5 * time.Minute), + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(time.Minute), + }, + }, + Runtime: &sandbox.RuntimeConfig{ + WorkingDirectory: sandbox.Value("/tmp"), + Entrypoint: []string{"sleep", "infinity"}, + }, +}) +``` + + + @@ -54,13 +240,23 @@ Pass a registry image. The app name belongs on the client, not on this request. ```ts +import { PolicyMode } from "@sandbox-kit/sandbox"; + const sandbox = await client.create({ source: { image: { reference: "daytonaio/playground:latest" } }, resources: { - cpuCores: 2, - memoryMib: 4096n, + cpuCores: 2, // whole cores + memoryMib: 4096n, // 4 GiB as MiB diskMib: 8192n, }, + lifetime: { + idlePause: { mode: PolicyMode.Disabled }, + stoppedDelete: { mode: PolicyMode.After, after: 10 * 60_000 }, + }, + runtime: { + workingDirectory: "/home/daytona", + entrypoint: ["sleep", "infinity"], + }, }); ``` @@ -79,9 +275,10 @@ const sandbox = await client.create({ entrypoint: ["sleep", "infinity"], }, lifetime: { - maximumLifetime: 5 * 60_000, + maximumLifetime: 5 * 60_000, // milliseconds idleTerminate: { mode: PolicyMode.After, after: 60_000 }, }, + placement: { cloud: "aws", regions: ["us-east-1"] }, }); ``` @@ -95,7 +292,11 @@ const sandbox = await client.create({ ```go -import "github.com/sandbox-kit/kit/sdks/go/sandbox" +import ( + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) instance, err := client.Create(ctx, &sandbox.CreateOptions{ Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ @@ -106,6 +307,17 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ MemoryMib: sandbox.Value(uint64(4096)), DiskMib: sandbox.Value(uint64(8192)), }, + Lifetime: &sandbox.LifetimePolicy{ + IdlePause: &sandbox.AutomaticAction{Mode: sandbox.PolicyModeDisabled}, + StoppedDelete: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(10 * time.Minute), + }, + }, + Runtime: &sandbox.RuntimeConfig{ + WorkingDirectory: sandbox.Value("/home/daytona"), + Entrypoint: []string{"sleep", "infinity"}, + }, }) ``` @@ -139,6 +351,10 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ After: sandbox.Duration(time.Minute), }, }, + Placement: &sandbox.Placement{ + Cloud: sandbox.Value("aws"), + Regions: []string{"us-east-1"}, + }, }) ``` @@ -149,11 +365,38 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ -Name a snapshot by its reference, for example `daytona-small`. In TypeScript that is `source: { snapshot: { reference } }`. Set the toolbox language and user on `runtime`. +Name a snapshot by reference (for example `daytona-small`). Set toolbox language and user on `runtime`. + + + + +```ts +const sandbox = await client.create({ + source: { + snapshot: { reference: "daytona-small" }, + }, +}); +``` + + + -A linked sandbox must be ephemeral. The client rejects the request otherwise. +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Snapshot{ + Snapshot: &sandbox.SnapshotSource{Reference: "daytona-small"}, + }}, +}) +``` -Idle stop, idle pause, stopped archive, and stopped delete use `PolicyMode` from `@sandbox-kit/sandbox` (`sandbox.PolicyModeAfter` and `sandbox.PolicyModeDisabled` in Go). A positive stop delay and a positive pause delay cannot be combined. Projects for [snapshots](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-from-snapshot), [policies](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-policies), [labels](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-labels), and [network](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-network) are in the repository. Go projects sit beside them under `examples/go/daytona/`. + + + +A linked sandbox must be ephemeral; Kit rejects the request otherwise. + +Idle stop, idle pause, stopped archive, and stopped delete use `PolicyMode` (`After` / `Disabled`). You cannot combine a positive stop delay with a positive pause delay. + +See [Examples](/docs/examples). @@ -161,14 +404,85 @@ Idle stop, idle pause, stopped archive, and stopped delete use `PolicyMode` from Set cloud and region on `placement`. A ready wait needs a probe and a positive timeout. -Modal rejects a disk size, an API endpoint, and Daytona stop or archive policies. Projects for [resources](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-resources), [idle timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-idle), [placement](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-placement), and [readiness](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-ready) are in the repository. Go projects sit beside them under `examples/go/modal/`. +Disk size, an endpoint override, and stop / pause / archive / delete policies are rejected. + + + + +```ts +import { WaitCondition } from "@sandbox-kit/sandbox"; + +await client.create({ + source: { image: { reference: "alpine:3.21" } }, + placement: { + cloud: "aws", + regions: ["us-east-1"], + }, + runtime: { entrypoint: ["sleep", "300"] }, + readiness: { command: { argv: ["true"] } }, + provisioning: { timeout: 120_000, waitFor: WaitCondition.Ready }, +}); +``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "alpine:3.21"}, + }}, + Placement: &sandbox.Placement{ + Cloud: sandbox.Value("aws"), + Regions: []string{"us-east-1"}, + }, + Runtime: &sandbox.RuntimeConfig{ + Entrypoint: []string{"sleep", "300"}, + }, + Readiness: &sandbox.ReadinessProbe{ + Check: &sandbox.Command{ + Command: &sandbox.CommandProbe{Argv: []string{"true"}}, + }, + }, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(2 * time.Minute), + WaitFor: sandbox.WaitConditionReady, + }, +}) +``` + + + + +See [Examples](/docs/examples). ## Result -`info()` / `Info()` returns a **copy** of metadata. Mutating it does not change the sandbox. Reported CPU/memory can differ from requested values. +Metadata from info is a **copy**. Mutating it does not change the sandbox. Reported CPU and memory can differ from what you requested. -After create: [Filesystem](/docs/filesystem), [Run a command](/docs/process), [Lifecycle](/docs/lifecycle). + + + +```ts +const info = sandbox.info(); +// info: { id, provider, name?, providerState?, region?, isolation?, +// resources?, labels, createdAt?, providerMetadata?, state, terminal? } +console.log(info); +``` + + + + +```go +info := instance.Info() +// info: Id, Provider, Name, ProviderState, Region, Isolation, +// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal +fmt.Println(info) +``` + + + -Not in this surface: streaming sessions, PTY, GPU, resize, extra storage, browsers. +After create: [Filesystem](/docs/filesystem) · [Run a command](/docs/process) · [Lifecycle](/docs/lifecycle). diff --git a/website/content/docs/daytona.mdx b/website/content/docs/daytona.mdx index b963db8c..ff85951c 100644 --- a/website/content/docs/daytona.mdx +++ b/website/content/docs/daytona.mdx @@ -1,9 +1,9 @@ --- title: Daytona -description: Install, authenticate, and create a Daytona sandbox with the shared client. +description: Install, authenticate, and create a Daytona sandbox. --- -Provider package for Daytona sandboxes. Default create uses Daytona's default snapshot. Native SDK: Daytona v0.225.0. +Use Daytona when you want default snapshots, verified `get`, and start / stop / pause / archive. ## Install @@ -27,7 +27,7 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/daytona@latest ## Create -Set `DAYTONA_API_KEY`. +Set `DAYTONA_API_KEY`. Omit create options to use Daytona's default snapshot. @@ -46,7 +46,7 @@ const sandbox = await client.create(); console.log(sandbox.id); ``` -`timeout` is milliseconds. Prefer `await using`; otherwise `await client.close()` in `finally`. +`timeout` is milliseconds. Prefer `await using`; otherwise call `await client.close()` when finished. @@ -84,40 +84,118 @@ func main() { } ``` -Nil create options → default snapshot. Optional strings use `sandbox.Value`. +Optional strings use `sandbox.Value`. -Omit `auth` to let the Daytona SDK resolve credentials from its environment. Closing the client does not delete the sandbox. +Omit `auth` to let the Daytona SDK pick credentials from its environment. Closing the client does not delete the sandbox. ## Configuration -| Setting | TypeScript | Go | -| ------------ | ------------------------ | ---------------------- | -| API URL | `endpoint` | `Config.Endpoint` | -| Target | `region` | `Config.Region` | -| Organization | `scope.organizationId` | `Scope.OrganizationId` | -| Bearer token | `auth.bearerToken.token` | `BearerToken` | + + + +| Setting | Field | +| ------------ | ------------------------ | +| Endpoint | `endpoint` | +| Target | `region` | +| Organization | `scope.organizationId` | +| Bearer token | `auth.bearerToken.token` | + +```ts +await using client = new Client({ + provider: daytona(), + auth: { apiKey: { key: process.env.DAYTONA_API_KEY! } }, + endpoint: "https://app.daytona.io/api", // default; HTTPS required remotely + region: process.env.DAYTONA_TARGET, // optional target + scope: { organizationId: process.env.DAYTONA_ORG_ID }, + timeout: 120_000, +}); +``` + +Bearer token auth: + +```ts +await using client = new Client({ + provider: daytona(), + auth: { bearerToken: { token: process.env.DAYTONA_JWT! } }, + scope: { organizationId: process.env.DAYTONA_ORG_ID! }, + timeout: 120_000, +}); +``` + + + + +| Setting | Field | +| ------------ | ---------------------- | +| Endpoint | `Config.Endpoint` | +| Target | `Config.Region` | +| Organization | `Scope.OrganizationId` | +| Bearer token | `BearerToken` | -Default API URL: `https://app.daytona.io/api`. Remote URLs require HTTPS. HTTP is allowed for localhost/loopback. Bearer tokens require an organization id. Full rules: [Configuration](/docs/configuration). +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("DAYTONA_API_KEY")}, + }}, + Endpoint: sandbox.Value("https://app.daytona.io/api"), // default; HTTPS remotely + Region: sandbox.Value(os.Getenv("DAYTONA_TARGET")), + Scope: &sandbox.Scope{ + OrganizationId: sandbox.Value(os.Getenv("DAYTONA_ORG_ID")), + }, + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(daytona.New(), config) +``` + +Bearer token auth: + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.BearerToken{ + BearerToken: &sandbox.BearerTokenCredentials{Token: os.Getenv("DAYTONA_JWT")}, + }}, + Scope: &sandbox.Scope{ + OrganizationId: sandbox.Value(os.Getenv("DAYTONA_ORG_ID")), + }, + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(daytona.New(), config) +``` + + + + +Default endpoint: `https://app.daytona.io/api`. Remote URLs need HTTPS; HTTP is allowed for localhost. Bearer tokens need an organization id. Full rules: [Configuration](/docs/configuration). -## Operations +## What works -| Area | Support | -| --------- | ----------------------------------------------------------------- | -| Create | Default snapshot, named snapshot, or image (+ resources) | -| Open | `get` (verified). `attach` unsupported | -| Lifecycle | `refresh`, `start`, `stop`, `pause`, `archive`, `delete`, `close` | -| Files | Full filesystem API — [Filesystem](/docs/filesystem) | -| Process | `run` — [Run a command](/docs/process) | +| Area | Notes | +| --------- | ----------------------------------------------------------- | +| Create | Default snapshot, named snapshot, or image (with resources) | +| Open | `get` — verified. `attach` is unsupported | +| Lifecycle | `refresh`, `start`, `stop`, `pause`, `archive`, `delete` | +| Files | Filesystem methods — [Filesystem](/docs/filesystem) | +| Process | `run` — [Run a command](/docs/process) | -CPU: whole cores. Memory/disk: whole GiB as MiB. Resource overrides require an image source. +CPU is whole cores. Memory and disk are whole GiB expressed as MiB. Resource overrides require an image source. ## Related -| Topic | Link | -| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Image, size, policies | [Create](/docs/create#daytona) | -| get / stop / delete | [Lifecycle](/docs/lifecycle) | -| Example project | [create-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-sandbox) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-sandbox) | +- Image, size, and policies — [Create](/docs/create) +- get / stop / delete — [Lifecycle](/docs/lifecycle) + + + + +Example: [create-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-sandbox) + + + + +Example: [create-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-sandbox) + + + diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index f50686e4..e1ed9b58 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -3,27 +3,81 @@ title: Errors description: SandboxError / *sandbox.Error kinds, fields, and provider causes. --- -TypeScript throws `SandboxError`. Go returns `*sandbox.Error`. Both include `kind`, `provider`, `operation`, `message`, and `field` when a setting was rejected. +Failed calls surface a typed error with `kind`, `provider`, `operation`, `message`, and `field` when a setting was rejected. -Kinds are shared across providers. A kind is a category—not a retry policy. Kit does not retry. +Kinds are categories, not retry advice. Kit does not retry. | Kind | What happened | | ------------------- | ------------------------------------------------------ | | `InvalidArgument` | The request shape is wrong | | `Unsupported` | This provider does not implement the call or the field | -| `Authentication` | The credentials were rejected | +| `Authentication` | Credentials were rejected | | `PermissionDenied` | The account cannot perform this operation | -| `NotFound` | The sandbox or the file path was not found | -| `RateLimited` | The provider rate limit was reached | -| `ResourceExhausted` | The provider quota or capacity was reached | +| `NotFound` | The sandbox or file path was not found | +| `RateLimited` | Provider rate limit reached | +| `ResourceExhausted` | Provider quota or capacity reached | | `Canceled` | The caller canceled the operation | | `Timeout` | The deadline fired | +## Invalid config + +Bad client config fails before any provider call. TypeScript throws; Go returns `(*Client, error)`. + + + + +```ts +import { Client, SandboxError } from "@sandbox-kit/sandbox"; + +try { + new Client({ provider, timeout: -1 }); +} catch (error) { + if (error instanceof SandboxError) { + // error: { kind, provider, operation, field, message, rule?, + // statusCode?, providerCode?, providerSource?, cause? } + console.error(error); + } + throw error; +} +``` + + + + +```go +import ( + "errors" + "fmt" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +client, err := sandbox.NewClient(provider, &sandbox.Config{ + Timeout: sandbox.Duration(-1 * time.Second), +}) +if err != nil { + var detail *sandbox.Error + if errors.As(err, &detail) { + // detail: Kind, Provider, Operation, Field, Message, StatusCode, + // ProviderCode, ProviderSource, Rule (+ Unwrap cause) + fmt.Println(detail) + } + return err +} +_ = client +``` + + + + ## Handle by kind +Throws `SandboxError`. + ```ts import { ErrorKind, SandboxError } from "@sandbox-kit/sandbox"; @@ -41,22 +95,20 @@ try { console.error("Check your provider credentials."); break; case ErrorKind.PermissionDenied: - console.error( - "Your account does not have permission for this operation.", - ); + console.error("Your account cannot perform this operation."); break; case ErrorKind.NotFound: - console.error("The sandbox or file path was not found."); + console.error("Sandbox or file path not found."); break; case ErrorKind.RateLimited: case ErrorKind.ResourceExhausted: - console.error("The provider's rate or resource limit was reached."); + console.error("Provider rate or resource limit reached."); break; case ErrorKind.Canceled: - console.error("The operation was canceled."); + console.error("Operation canceled."); break; case ErrorKind.Timeout: - console.error("The operation timed out."); + console.error("Operation timed out."); break; default: console.error(error); @@ -64,11 +116,13 @@ try { } ``` -The provider's own error is `error.cause`. +The underlying cloud error is `error.cause`. +Returns `*sandbox.Error`. + ```go import ( "errors" @@ -85,15 +139,15 @@ if errors.As(err, &detail) { case sandbox.ErrorKindAuthentication: fmt.Println("Check your provider credentials.") case sandbox.ErrorKindPermissionDenied: - fmt.Println("Your account does not have permission for this operation.") + fmt.Println("Your account cannot perform this operation.") case sandbox.ErrorKindNotFound: - fmt.Println("The sandbox or file path was not found.") + fmt.Println("Sandbox or file path not found.") case sandbox.ErrorKindRateLimited, sandbox.ErrorKindResourceExhausted: - fmt.Println("The provider's rate or resource limit was reached.") + fmt.Println("Provider rate or resource limit reached.") case sandbox.ErrorKindCanceled: - fmt.Println("The operation was canceled.") + fmt.Println("Operation canceled.") case sandbox.ErrorKindTimeout: - fmt.Println("The operation timed out.") + fmt.Println("Operation timed out.") default: fmt.Println(err) } @@ -107,12 +161,139 @@ if errors.As(err, &detail) { ## Field paths -Field strings match across languages (for example `resources.cpu_cores`). Rules that cover several settings use the parent (`network`). TypeScript exports constants such as `CreateFieldResourcesCpuCores` for comparisons. +Field strings are shared (for example `resources.cpu_cores`). Rules that cover several settings use the parent (`network`). + + + + +```ts +import { + CreateFieldResourcesCpuCores, + SandboxError, +} from "@sandbox-kit/sandbox"; + +try { + await client.create({ resources: { cpuCores: -1 } }); +} catch (error) { + if ( + error instanceof SandboxError && + error.field === CreateFieldResourcesCpuCores + ) { + console.error("bad cpuCores:", error.message); + } +} +``` + + + + +```go +_, err := client.Create(ctx, &sandbox.CreateOptions{ + Resources: &sandbox.Resources{CpuCores: sandbox.Value(-1.0)}, +}) +var detail *sandbox.Error +if errors.As(err, &detail) && detail.Field == sandbox.CreateFieldResourcesCpuCores { + fmt.Println("bad cpuCores:", detail.Message) +} +``` + + + ## After failure An error does not prove no sandbox was created—the provider may have succeeded before a later step failed, and the error does not include that id. Lifecycle failures can occur after the provider already mutated state. -On Daytona, `get` before retry. Modal `attach` does not verify existence. + + +`get` before retry so you know whether the sandbox exists. + + + + +```ts +try { + await client.create(request); +} catch (error) { + // create may have succeeded before a later step failed — no id on the error + if (knownId) { + const sandbox = await client.get(knownId); + // info: { id, provider, name?, providerState?, region?, isolation?, + // resources?, labels, createdAt?, providerMetadata?, state, terminal? } + console.log(sandbox.info()); + } + throw error; +} +``` + + + + +```go +instance, err := client.Create(ctx, opts) +if err != nil { + // create may have succeeded before a later step failed — no id on the error + if knownID != "" { + existing, getErr := client.Get(ctx, knownID) + if getErr == nil { + // Info(): Id, Provider, Name, ProviderState, Region, Isolation, + // Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal + fmt.Println(existing.Info()) + } + } + return err +} +_ = instance +``` + + + + +See [Examples](/docs/examples). + + + + + +`attach` does not verify existence. + + + + +```ts +try { + await client.create(request); +} catch (error) { + // attach builds a handle from an id only — it does not check Modal + if (knownId) { + const sandbox = await client.attach(knownId); + console.log(sandbox.id); + } + throw error; +} +``` + + + + +```go +instance, err := client.Create(ctx, opts) +if err != nil { + // Attach builds a handle from an id only — it does not check Modal + if knownID != "" { + existing, attachErr := client.Attach(ctx, knownID) + if attachErr == nil { + fmt.Println(existing.ID()) + } + } + return err +} +_ = instance +``` + + + + +See [Examples](/docs/examples). -Examples: [Daytona](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/errors/handle-daytona-errors) · [Modal](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/errors/handle-modal-errors) · Go twins under `examples/go/`. + diff --git a/website/content/docs/examples.mdx b/website/content/docs/examples.mdx index 72c280b3..97f6cc9a 100644 --- a/website/content/docs/examples.mdx +++ b/website/content/docs/examples.mdx @@ -1,74 +1,401 @@ --- title: Examples -description: One runnable project per operation. Clone, set .env, run. +description: Clone an example project, set .env, and run one operation. --- -Each example is a small project under `examples////`. It loads its own `.env` and demonstrates one call. +Each example is a small project that loads its own `.env` and demonstrates one call against a provider. Creation examples provision a real sandbox. Filesystem, process, and lifecycle examples need a sandbox id in `.env`. Error examples fail locally. -| Area | Runtime | -| ------------------------------------ | ----------------------------- | -| `configuration` | Provisions a real sandbox | -| `filesystem`, `process`, `lifecycle` | Uses `*_SANDBOX_ID` in `.env` | -| `errors` | Fails locally; no cloud work | +Destructive ops (stop, terminate, archive, delete, remove) can destroy the sandbox or path you configure. -Destructive ops (stop, terminate, archive, delete, remove) can destroy the sandbox or path you configure. Full catalog: [examples/README.md](https://github.com/sandbox-kit/kit/blob/main/examples/README.md). - -## Daytona +## Run -| Operation | TypeScript | Go | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | -| Create | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-sandbox) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-sandbox) | -| Run command | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-command) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-command) | -| Read file | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/read-daytona-file) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/read-daytona-file) | -| From image | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-from-image) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-from-image) | -| From snapshot | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-from-snapshot) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-from-snapshot) | -| Policies | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-policies) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-with-policies) | -| Get | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/get-daytona-sandbox) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/get-daytona-sandbox) | -| Stop | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/stop-daytona-sandbox) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/stop-daytona-sandbox) | -| Delete | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/delete-daytona-sandbox) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/delete-daytona-sandbox) | -| Errors | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/errors/handle-daytona-errors) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/errors/handle-daytona-errors) | +Skip `cp` if `.env` already exists. -Process variants (shell, cwd, env, timeout, exit): [typescript/daytona/process](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process) · [go/daytona/process](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process). + + -## Modal +Requires Node.js 20/22/24.15+ and npm 11.19.0. -| Operation | TypeScript | Go | -| ----------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| Create | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-sandbox) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-sandbox) | -| Run command | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-command) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-command) | -| Read file | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/read-modal-file) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/read-modal-file) | -| Resources | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-resources) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-resources) | -| Attach | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/lifecycle/attach-modal-sandbox) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/lifecycle/attach-modal-sandbox) | -| Terminate | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/lifecycle/terminate-modal-sandbox) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/lifecycle/terminate-modal-sandbox) | -| Errors | [project](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/errors/handle-modal-errors) | [project](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/errors/handle-modal-errors) | + -Process variants: [typescript/modal/process](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process) · [go/modal/process](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process). +```sh +git clone https://github.com/sandbox-kit/kit.git +cd kit/examples/typescript/daytona/configuration/create-daytona-sandbox +cp .env.example .env # set DAYTONA_API_KEY +npm install && npm run build && npm start +``` -## Run + -Requirements: Node.js 20/22/24.15+ (npm 11.19.0) or Go 1.26.0+. `` is `filesystem`, `process`, `configuration`, `lifecycle`, or `errors`. Skip `cp` if `.env` exists. - - - + ```sh git clone https://github.com/sandbox-kit/kit.git -cd kit/examples/typescript/// -cp .env.example .env +cd kit/examples/typescript/modal/configuration/create-modal-sandbox +cp .env.example .env # set MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, MODAL_APP_NAME npm install && npm run build && npm start ``` + + +Requires Go 1.26.0+. + + + ```sh git clone https://github.com/sandbox-kit/kit.git -cd kit/examples/go/// -cp .env.example .env +cd kit/examples/go/daytona/configuration/create-daytona-sandbox +cp .env.example .env # set DAYTONA_API_KEY go run . ``` + + + + +```sh +git clone https://github.com/sandbox-kit/kit.git +cd kit/examples/go/modal/configuration/create-modal-sandbox +cp .env.example .env # set MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, MODAL_APP_NAME +go run . +``` + + + + + + + + +## Catalog + +### Create + + + + +| Example | Project | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Creation | [create-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-sandbox) | +| Snapshot | [create-daytona-from-snapshot](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-from-snapshot) | +| Image | [create-daytona-from-image](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-from-image) | +| Runtime | [create-daytona-with-runtime](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-runtime) | +| Ephemeral | [create-daytona-ephemeral](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-ephemeral) | +| Linked | [create-daytona-linked](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-linked) | +| Policies | [create-daytona-with-policies](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-policies) | +| Network | [create-daytona-with-network](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-network) | +| Labels | [create-daytona-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-labels) | +| Secrets | [create-daytona-with-secrets](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-secrets) | +| KVM | [create-daytona-with-kvm](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-kvm) | +| TTL | [create-daytona-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/configuration/create-daytona-with-ttl) | + + + + +| Example | Project | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| Creation | [create-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-sandbox) | +| Snapshot | [create-daytona-from-snapshot](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-from-snapshot) | +| Image | [create-daytona-from-image](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-from-image) | +| Runtime | [create-daytona-with-runtime](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-with-runtime) | +| Ephemeral | [create-daytona-ephemeral](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-ephemeral) | +| Linked | [create-daytona-linked](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-linked) | +| Policies | [create-daytona-with-policies](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-with-policies) | +| Network | [create-daytona-with-network](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-with-network) | +| Labels | [create-daytona-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-with-labels) | +| Secrets | [create-daytona-with-secrets](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-with-secrets) | +| KVM | [create-daytona-with-kvm](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-with-kvm) | +| TTL | [create-daytona-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/configuration/create-daytona-with-ttl) | + + + + +### Process + + + + +| Example | Project | +| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| Run command | [run-daytona-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-command) | +| Shell command | [run-daytona-shell-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-shell-command) | +| Run with cwd | [run-daytona-with-cwd](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-with-cwd) | +| Run with env | [run-daytona-with-env](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-with-env) | +| Run with timeout | [run-daytona-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-with-timeout) | +| Check exit | [run-daytona-check-exit](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-check-exit) | + + + + +| Example | Project | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Run command | [run-daytona-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-command) | +| Shell command | [run-daytona-shell-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-shell-command) | +| Run with cwd | [run-daytona-with-cwd](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-with-cwd) | +| Run with env | [run-daytona-with-env](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-with-env) | +| Run with timeout | [run-daytona-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-with-timeout) | +| Check exit | [run-daytona-check-exit](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-check-exit) | + + + + +### Filesystem + + + + +| Example | Project | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| Read file | [read-daytona-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/read-daytona-file) | +| Write file | [write-daytona-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/write-daytona-file) | +| Copy in | [copy-file-into-daytona](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/copy-file-into-daytona) | +| Copy out | [copy-file-from-daytona](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/copy-file-from-daytona) | +| Directory | [make-daytona-directory](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/make-daytona-directory) | +| List | [list-daytona-directory](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/list-daytona-directory) | +| Stat | [stat-daytona-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/stat-daytona-file) | +| Remove | [remove-daytona-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/remove-daytona-file) | + + + + +| Example | Project | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------- | +| Read file | [read-daytona-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/read-daytona-file) | +| Write file | [write-daytona-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/write-daytona-file) | +| Copy in | [copy-file-into-daytona](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/copy-file-into-daytona) | +| Copy out | [copy-file-from-daytona](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/copy-file-from-daytona) | +| Directory | [make-daytona-directory](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/make-daytona-directory) | +| List | [list-daytona-directory](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/list-daytona-directory) | +| Stat | [stat-daytona-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/stat-daytona-file) | +| Remove | [remove-daytona-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/remove-daytona-file) | + + + + +### Lifecycle + + + + +| Example | Project | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| Get | [get-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/get-daytona-sandbox) | +| Refresh | [refresh-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/refresh-daytona-sandbox) | +| Start | [start-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/start-daytona-sandbox) | +| Start timeout | [start-daytona-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/start-daytona-with-timeout) | +| Stop | [stop-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/stop-daytona-sandbox) | +| Force stop | [force-stop-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/force-stop-daytona-sandbox) | +| Pause | [pause-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/pause-daytona-sandbox) | +| Archive | [archive-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/archive-daytona-sandbox) | +| Delete | [delete-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/delete-daytona-sandbox) | +| Delete request | [request-daytona-deletion](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/request-daytona-deletion) | +| Close handle | [close-daytona-handle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/lifecycle/close-daytona-handle) | + + + + +| Example | Project | +| -------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| Get | [get-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/get-daytona-sandbox) | +| Refresh | [refresh-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/refresh-daytona-sandbox) | +| Start | [start-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/start-daytona-sandbox) | +| Start timeout | [start-daytona-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/start-daytona-with-timeout) | +| Stop | [stop-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/stop-daytona-sandbox) | +| Force stop | [force-stop-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/force-stop-daytona-sandbox) | +| Pause | [pause-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/pause-daytona-sandbox) | +| Archive | [archive-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/archive-daytona-sandbox) | +| Delete | [delete-daytona-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/delete-daytona-sandbox) | +| Delete request | [request-daytona-deletion](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/request-daytona-deletion) | +| Close handle | [close-daytona-handle](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/lifecycle/close-daytona-handle) | + + + + +### Errors + + + + +| Example | Project | +| ------- | ------------------------------------------------------------------------------------------------------------------------------ | +| Errors | [handle-daytona-errors](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/errors/handle-daytona-errors) | + + + + +| Example | Project | +| ------- | ---------------------------------------------------------------------------------------------------------------------- | +| Errors | [handle-daytona-errors](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/errors/handle-daytona-errors) | + + + + + + + + +## Catalog + +### Create + + + + +| Example | Project | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| Creation | [create-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-sandbox) | +| Resources | [create-modal-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-resources) | +| Runtime | [create-modal-with-runtime](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-runtime) | +| GVisor | [create-modal-gvisor](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-gvisor) | +| Linux VM | [create-modal-linux-vm](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-linux-vm) | +| Placement | [create-modal-with-placement](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-placement) | +| Labels | [create-modal-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-labels) | +| Network | [create-modal-with-network](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-network) | +| Idle timeout | [create-modal-idle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-idle) | +| Readiness | [create-modal-ready](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-ready) | +| TCP readiness | [create-modal-tcp-ready](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-tcp-ready) | +| Client timeout wait | [create-modal-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-client-timeout) | +| Ports | [create-modal-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-ports) | +| Allowlists | [create-modal-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-allowlists) | +| Secrets | [create-modal-with-secrets](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-secrets) | +| Verbose | [create-modal-verbose](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-verbose) | +| Identity | [create-modal-with-identity](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-with-identity) | + + + + +| Example | Project | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| Creation | [create-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-sandbox) | +| Resources | [create-modal-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-resources) | +| Runtime | [create-modal-with-runtime](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-runtime) | +| GVisor | [create-modal-gvisor](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-gvisor) | +| Linux VM | [create-modal-linux-vm](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-linux-vm) | +| Placement | [create-modal-with-placement](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-placement) | +| Labels | [create-modal-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-labels) | +| Network | [create-modal-with-network](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-network) | +| Idle timeout | [create-modal-idle](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-idle) | +| Readiness | [create-modal-ready](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-ready) | +| TCP readiness | [create-modal-tcp-ready](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-tcp-ready) | +| Client timeout wait | [create-modal-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-client-timeout) | +| Ports | [create-modal-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-ports) | +| Allowlists | [create-modal-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-allowlists) | +| Secrets | [create-modal-with-secrets](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-secrets) | +| Verbose | [create-modal-verbose](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-verbose) | +| Identity | [create-modal-with-identity](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-with-identity) | + + + + +### Process + + + + +| Example | Project | +| ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| Run command | [run-modal-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-command) | +| Shell command | [run-modal-shell-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-shell-command) | +| Run with cwd | [run-modal-with-cwd](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-with-cwd) | +| Run with env | [run-modal-with-env](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-with-env) | +| Run with timeout | [run-modal-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-with-timeout) | +| Check exit | [run-modal-check-exit](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-check-exit) | + + + + +| Example | Project | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------- | +| Run command | [run-modal-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-command) | +| Shell command | [run-modal-shell-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-shell-command) | +| Run with cwd | [run-modal-with-cwd](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-with-cwd) | +| Run with env | [run-modal-with-env](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-with-env) | +| Run with timeout | [run-modal-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-with-timeout) | +| Check exit | [run-modal-check-exit](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-check-exit) | + + + + +### Filesystem + + + + +| Example | Project | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------ | +| Read file | [read-modal-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/read-modal-file) | +| Write file | [write-modal-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/write-modal-file) | +| Copy in | [copy-file-into-modal](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/copy-file-into-modal) | +| Copy out | [copy-file-from-modal](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/copy-file-from-modal) | +| Directory | [make-modal-directory](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/make-modal-directory) | +| List | [list-modal-directory](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/list-modal-directory) | +| Stat | [stat-modal-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/stat-modal-file) | +| Remove | [remove-modal-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/remove-modal-file) | + + + + +| Example | Project | +| ---------- | ---------------------------------------------------------------------------------------------------------------------- | +| Read file | [read-modal-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/read-modal-file) | +| Write file | [write-modal-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/write-modal-file) | +| Copy in | [copy-file-into-modal](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/copy-file-into-modal) | +| Copy out | [copy-file-from-modal](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/copy-file-from-modal) | +| Directory | [make-modal-directory](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/make-modal-directory) | +| List | [list-modal-directory](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/list-modal-directory) | +| Stat | [stat-modal-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/stat-modal-file) | +| Remove | [remove-modal-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/remove-modal-file) | + + + + +### Lifecycle + + + + +| Example | Project | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| Attach | [attach-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/lifecycle/attach-modal-sandbox) | +| Terminate | [terminate-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/lifecycle/terminate-modal-sandbox) | +| Terminate timeout | [terminate-modal-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/lifecycle/terminate-modal-with-timeout) | +| Termination request | [request-modal-termination](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/lifecycle/request-modal-termination) | +| Close handle | [close-modal-handle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/lifecycle/close-modal-handle) | + + + + +| Example | Project | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Attach | [attach-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/lifecycle/attach-modal-sandbox) | +| Terminate | [terminate-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/lifecycle/terminate-modal-sandbox) | +| Terminate timeout | [terminate-modal-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/lifecycle/terminate-modal-with-timeout) | +| Termination request | [request-modal-termination](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/lifecycle/request-modal-termination) | +| Close handle | [close-modal-handle](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/lifecycle/close-modal-handle) | + + + + +### Errors + + + + +| Example | Project | +| ------- | ------------------------------------------------------------------------------------------------------------------------ | +| Errors | [handle-modal-errors](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/errors/handle-modal-errors) | + + + + +| Example | Project | +| ------- | ---------------------------------------------------------------------------------------------------------------- | +| Errors | [handle-modal-errors](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/errors/handle-modal-errors) | + -Daytona create needs `DAYTONA_API_KEY`. Modal create needs `MODAL_TOKEN_ID`, `MODAL_TOKEN_SECRET`, and `MODAL_APP_NAME`. + diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index 91bd8124..f3ac43a0 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -3,7 +3,7 @@ title: Filesystem description: Read, write, copy, mkdir, list, stat, and remove paths inside a sandbox. --- -Use `sandbox.filesystem` / `Filesystem()` on an open handle. Providers implement the same methods; provider-specific timeout rules are below. +Use the filesystem methods on an open handle. | Method | Purpose | | --------------------------------------- | ------------------------ | @@ -16,11 +16,7 @@ Use `sandbox.filesystem` / `Filesystem()` on an open handle. Providers implement | [`stat`](#read-metadata) | Path metadata | | [`remove`](#delete-a-file-or-directory) | Delete a path | -Prerequisite: [create](/docs/create) or [open](/docs/lifecycle) a sandbox. - -Calls on one handle run one at a time with lifecycle and process. Abort or -deadline rejects the TypeScript `await` (and ends the Go wait), but the handle -stays busy until that provider call settles. +You need a sandbox from [create](/docs/create) or [lifecycle](/docs/lifecycle). ## Paths and bytes @@ -29,58 +25,83 @@ stays busy until that provider call settles. | Sandbox | Absolute (`/tmp/note.txt`). Relative, blank, or NUL rejected | `path` | | Local (copy) | May be relative (`./note.txt`). Non-blank, no NUL | `local_path` | -`readFile` / `writeFile` transfer bytes—encode/decode text in your app. Copy methods use files on disk. - -## Timeouts +`readFile` and `writeFile` transfer bytes—encode and decode text in your app. Copy methods use files on disk. -| Language | Unit | -| ---------- | --------------- | -| TypeScript | milliseconds | -| Go | `time.Duration` | + + -Omit timeout → client deadline. Explicit `0` → no extra Kit deadline. +```ts +const body = new TextEncoder().encode("hello\n"); +await sandbox.filesystem.writeFile("/tmp/note.txt", body); // absolute sandbox path +await sandbox.filesystem.copyFromLocal("./note.txt", "/tmp/note.txt"); // local may be relative +``` - + + -| Method / language | Behavior | -| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| TS `readFile`, `writeFile`, `copyFromLocal`, `copyToLocal` | Whole seconds to Daytona. `2000` OK; `1500` rejected (`timeout`). Omit/zero → `0` (no transfer limit). Abort signal still applies when omit | -| TS `makeDirectory`, `listDirectory`, `stat`, `remove` | Abort signal. `1500` ms ends at 1.5s | -| All Go file calls | Context deadline. `1500 * time.Millisecond` ends at 1.5s | +```go +err := instance.Filesystem().WriteFile(ctx, "/tmp/note.txt", []byte("hello\n"), nil) +err = instance.Filesystem().CopyFromLocal(ctx, "note.txt", "/tmp/note.txt", nil) // local may be relative +``` - + + - +## Timeouts -| Method / language | Behavior | -| ----------------------------------------------------- | -------------------------------------------------------- | -| TS transfer and directory ops | Abort signal. `1500` ms ends at 1.5s | -| All Go file calls | Context deadline. `1500 * time.Millisecond` ends at 1.5s | +Omit timeout → client deadline. Explicit `0` → no deadline from the client. - + +Timeouts are milliseconds. + +| Method | Behavior | +| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `readFile`, `writeFile`, `copyFromLocal`, `copyToLocal` | Whole seconds to Daytona. `2000` OK; `1500` rejected (`timeout`). Omit/zero → `0` (no transfer limit). Abort signal still applies when omit | +| `makeDirectory`, `listDirectory`, `stat`, `remove` | Abort signal. `1500` ms ends at 1.5s | + ```ts await sandbox.filesystem.readFile("/tmp/note.txt", { timeout: 2_000 }); await sandbox.filesystem.listDirectory("/tmp", { timeout: 1_500 }); ``` - -`readFile` with `1500` is rejected on Daytona TypeScript. Use a whole-second value such as `2000`. - - +Timeouts use `time.Duration` via `sandbox.Duration`. Every file call honors the context deadline. `1500 * time.Millisecond` ends at 1.5s. + ```go -import ( - "time" +data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileReadOptions{ + Timeout: sandbox.Duration(1500 * time.Millisecond), +}) +``` + + + + + + + + + + - "github.com/sandbox-kit/kit/sdks/go/sandbox" -) +Timeouts are milliseconds. Transfer and directory ops use the abort signal. `1500` ms ends at 1.5s. +```ts +await sandbox.filesystem.readFile("/tmp/note.txt", { timeout: 1_500 }); +await sandbox.filesystem.listDirectory("/tmp", { timeout: 1_500 }); +``` + + + + +Timeouts use `time.Duration` via `sandbox.Duration`. Every file call honors the context deadline. `1500 * time.Millisecond` ends at 1.5s. + +```go data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileReadOptions{ Timeout: sandbox.Duration(1500 * time.Millisecond), }) @@ -89,11 +110,11 @@ data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileR -Calls on one handle are serialized with lifecycle and process. Snippets below assume an open `sandbox` / `instance`. See [Examples](/docs/examples). + ## Read a file -`readFile` returns the bytes of one file. A missing file comes back as not found. A directory path fails at the provider. +Returns the bytes of one file. A missing file is not found. A directory path fails at the provider. @@ -120,17 +141,9 @@ fmt.Printf("%s\n", data) - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/read-daytona-file) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/read-daytona-file) - - - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/read-modal-file) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/read-modal-file) - - ## Write a file -`writeFile` replaces the file and creates missing parent directories. An existing file at that path is overwritten. +Replaces the file and creates missing parent directories. An existing file is overwritten. @@ -144,24 +157,15 @@ await sandbox.filesystem.writeFile("/tmp/kit-files/note.txt", body); ```go -body := []byte("hello\n") -err := instance.Filesystem().WriteFile(ctx, "/tmp/kit-files/note.txt", body, nil) +err := instance.Filesystem().WriteFile(ctx, "/tmp/kit-files/note.txt", []byte("hello\n"), nil) ``` - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/write-daytona-file) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/write-daytona-file) - - - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/write-modal-file) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/write-modal-file) - - ## Copy a file in -`copyFromLocal` reads a file on your machine and writes it in the sandbox. The local path is the first argument. The remote path is absolute. An existing remote file is replaced, and missing parent directories of that path are created. A missing local file is not found. The error field is `local_path`. A local directory is an invalid argument with the same field. +Reads a file on your machine and writes it in the sandbox. Local path first; remote path absolute. Replaces an existing remote file and creates missing parents. A missing local file or a local directory fails with field `local_path`. @@ -180,17 +184,9 @@ err := instance.Filesystem().CopyFromLocal(ctx, "note.txt", "/tmp/note.txt", nil - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/copy-file-into-daytona) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/copy-file-into-daytona) - - - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/copy-file-into-modal) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/copy-file-into-modal) - - ## Copy a file out -`copyToLocal` copies a sandbox file onto your disk. The remote path comes first. An existing local file is replaced. A missing local parent directory is created. A local directory is an invalid argument, and the field is `local_path`. +Copies a sandbox file onto your disk. Remote path first. Replaces an existing local file and creates a missing local parent. A local directory fails with field `local_path`. @@ -209,24 +205,18 @@ err := instance.Filesystem().CopyToLocal(ctx, "/tmp/note.txt", "note.txt", nil) - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/copy-file-from-daytona) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/copy-file-from-daytona) - - - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/copy-file-from-modal) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/copy-file-from-modal) - - ## Create a directory -`parents` defaults to true, so the call creates missing parents. +`parents` defaults to true. -Daytona returns an error when the directory already exists. Daytona rejects `parents: false` before the call (field `parents`). + Daytona errors if the directory already exists, and rejects `parents: false` + before the call (field `parents`). -Modal accepts a directory that already exists. With `parents: false`, Modal requires the parent and errors if the new directory is already there. + Modal accepts a directory that already exists. With `parents: false`, the + parent must exist and the new directory must not. @@ -268,24 +258,31 @@ err = instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files/nested", &sandbox - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/make-daytona-directory) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/make-daytona-directory) - +## List a directory - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/make-modal-directory) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/make-modal-directory) - +Returns entries in that one directory—no recursion. `size` is a byte count. -## List a directory + + -`listDirectory` returns the entries in that one directory. It does not walk into subdirectories. `size` is a byte count. In TypeScript it is a `bigint`. +`size` is a `bigint`. + + + + +`size` is an integer byte count on the entry. + + + ```ts const entries = await sandbox.filesystem.listDirectory("/tmp/kit-files"); -for (const entry of entries) console.log(entry.name, entry.size); +// each entry: { name, path, type, size?, mode?, permissions?, +// owner?, group?, modifiedAt? } +console.log(entries); ``` @@ -298,32 +295,33 @@ entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp/kit-files", nil) if err != nil { return err } -for _, entry := range entries { - fmt.Println(entry.GetName(), entry.GetSize()) -} +// each entry: Name, Path, Type, Size, Mode, Permissions, Owner, Group, ModifiedAt +fmt.Println(entries) ``` +## Read metadata + +`stat` returns one path. `mode` is an octal string when supplied (`0644`). `permissions`, `owner`, `group`, and `modifiedAt` appear when the cloud returns them. + -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/list-daytona-directory) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/list-daytona-directory) + A symlink path is reported as a file or a directory. -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/list-modal-directory) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/list-modal-directory) + A symlink result describes the symlink when Modal reports one. -## Read metadata - -`stat` returns one path. A symlink result describes the symlink. Modal can report a symlink. Daytona reports a file or a directory. `mode` is an octal string when the provider supplies one, such as `0644`. `permissions`, `owner`, and `group` are set when the provider supplies them. `modifiedAt` is set when the provider supplies a time. - ```ts const info = await sandbox.filesystem.stat("/tmp/note.txt"); -console.log(info.type, info.size, info.mode); +// info: { name, path, type, size?, mode?, permissions?, +// owner?, group?, modifiedAt? } +console.log(info); ``` @@ -336,23 +334,16 @@ info, err := instance.Filesystem().Stat(ctx, "/tmp/note.txt", nil) if err != nil { return err } -fmt.Println(info.GetType(), info.GetSize(), info.GetMode()) +// info: Name, Path, Type, Size, Mode, Permissions, Owner, Group, ModifiedAt +fmt.Println(info) ``` - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/stat-daytona-file) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/stat-daytona-file) - - - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/stat-modal-file) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/stat-modal-file) - - ## Delete a file or directory -`remove` deletes a file, or an empty directory. Set `recursive` to delete a directory and its contents. A directory that still has entries fails when you leave the flag unset. A missing path comes back as not found. +Deletes a file or an empty directory. Set `recursive` to delete a directory and its contents. A non-empty directory fails without the flag. A missing path is not found. @@ -382,10 +373,6 @@ if err := files.Remove(ctx, "/tmp/kit-files", &sandbox.RemoveOptions{ - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/filesystem/remove-daytona-file) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/filesystem/remove-daytona-file) - +## Examples - -Examples: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/filesystem/remove-modal-file) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/filesystem/remove-modal-file) - +Runnable projects: [Examples](/docs/examples). diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx index 802e54c5..feb585a2 100644 --- a/website/content/docs/index.mdx +++ b/website/content/docs/index.mdx @@ -3,18 +3,24 @@ title: Quickstart description: Install Sandbox Kit, create a sandbox, run a command, and write a file. --- -Sandbox Kit is a lightweight TypeScript and Go SDK for cloud sandboxes. The core package defines the client; providers plug in as separate packages. Install both, construct a client, then create or open sandboxes. +Sandbox Kit is an SDK for cloud sandboxes. Install the core package and a provider, create a sandbox, then run commands and read files through the same client. -| Requirement | Version | -| ----------- | ------------------------- | -| TypeScript | Node.js 20, 22, or 24.15+ | -| Go | Go 1.26.0+ | +Use the language tabs and the provider control to switch snippets. Your choices stick in the browser. -Language tabs and the Daytona / Modal menu keep your picks in the browser. The menu switches install snippets, create samples, and provider notes. + + -## 1. Install +Requires Node.js 20, 22, or 24.15+. -Install the core package and a provider. + + + +Requires Go 1.26.0+. + + + + +## 1. Install @@ -61,10 +67,10 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest ## 2. Create a sandbox -Set `DAYTONA_API_KEY`, then run: +Set `DAYTONA_API_KEY`, then: - Set a Modal token pair and app name (see [Modal](/docs/modal)), then run: + Set a Modal token pair and app name ([Modal](/docs/modal)), then: @@ -79,7 +85,7 @@ import { Client } from "@sandbox-kit/sandbox"; await using client = new Client({ provider: daytona(), auth: { apiKey: { key: process.env.DAYTONA_API_KEY! } }, - timeout: 120_000, + timeout: 120_000, // default client deadline (ms) }); const sandbox = await client.create(); @@ -106,7 +112,7 @@ await using client = new Client({ appName: process.env.MODAL_APP_NAME!, environment: process.env.MODAL_ENVIRONMENT ?? "main", }, - timeout: 120_000, + timeout: 120_000, // default client deadline (ms) }); const sandbox = await client.create({ @@ -210,7 +216,10 @@ func main() { -Provider setup details: [Daytona](/docs/daytona) · [Modal](/docs/modal). + + Provider setup: [Daytona](/docs/daytona). + +Provider setup: [Modal](/docs/modal). ## 3. Run a command @@ -221,7 +230,8 @@ Provider setup details: [Daytona](/docs/daytona) · [Modal](/docs/modal). const result = await sandbox.process.run({ argv: ["echo", "hello from kit"], }); -console.log(result.exitCode, new TextDecoder().decode(result.stdout)); +// result: { exitCode, stdout, stderr } +console.log(result); ``` @@ -242,13 +252,14 @@ result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions if err != nil { log.Fatal(err) } -fmt.Println(result.GetExitCode(), string(result.GetStdout())) +// result: ExitCode, Stdout, Stderr +fmt.Println(result) ``` -A non-zero exit code is not an SDK error. Check `exitCode` / `exit_code` yourself. Details: [Run a command](/docs/process). +A non-zero exit code is not an SDK error—check `exitCode` yourself. More options: [Run a command](/docs/process). ## 4. Write a file @@ -279,20 +290,21 @@ err = instance.Filesystem().WriteFile( -Paths inside the sandbox are absolute. Details: [Filesystem](/docs/filesystem). +Sandbox paths are absolute. More: [Filesystem](/docs/filesystem). - `client.close()` / `Client.Close` and handle `close` release local resources - only. The cloud sandbox keeps running until you stop, terminate, archive, - delete, or a lifetime policy fires. See [Concepts](/docs/concepts). + `client.close()` and handle `close` only release local resources. The cloud + sandbox keeps running until an explicit lifecycle call or a lifetime policy + ends it. See [Concepts](/docs/concepts) and [Lifecycle](/docs/lifecycle). ## Next -| Task | Page | -| ----------------------------------- | ------------------------------------ | -| Client, handle, and close semantics | [Concepts](/docs/concepts) | -| Daytona setup | [Daytona](/docs/daytona) | -| Modal setup | [Modal](/docs/modal) | -| Auth, timeout, region, scope | [Configuration](/docs/configuration) | -| Runnable projects | [Examples](/docs/examples) | +| | | +| ------------------------- | -------------------------------------------------------------------------------------- | +| How the client works | [Concepts](/docs/concepts) | +| Types and methods | [SDK reference](/docs/sdk) · [Client](/docs/sdk/client) · [Sandbox](/docs/sdk/sandbox) | +| Daytona setup | [Daytona](/docs/daytona) | +| Modal setup | [Modal](/docs/modal) | +| Auth, region, timeouts | [Configuration](/docs/configuration) | +| Runnable example projects | [Examples](/docs/examples) | diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index 79af6d54..40a9da20 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -3,64 +3,66 @@ title: Lifecycle description: get, attach, start, stop, pause, archive, delete, terminate, and close. --- -Operate on a handle from `create`, or open one by id. Switch Daytona or Modal above for open, destroy, and capability notes. +Operate on a handle from `create`, or reopen one with `get` / `attach`. Unsupported calls fail before the provider runs. -| Operation | Daytona | Modal | -| ------------------------------ | ------------------------------------------ | ------------------------------------------------- | -| Open existing | [`get`](#open-a-sandbox) (provider lookup) | [`attach`](#open-a-sandbox) (id only, no lookup) | -| start / stop / pause / archive | [Supported](#stop-start-pause-and-archive) | Rejected in Kit | -| Destroy | [`delete`](#delete) | [`terminate`](#terminate) or create-time lifetime | -| Local disconnect | [`close`](#close-the-client) | [`close`](#close-the-client) | + + -Check support with `supports` / `Supports` (`LifecycleCapability*` in Go). Unsupported ops fail before the provider call. Daytona may still reject a supported op for a given sandbox. +Check support with `supports`. -Calls on one handle run one at a time with filesystem and process. Abort or -deadline rejects the TypeScript `await` (and ends the Go wait), but the handle -stays busy until that provider call settles. + + -Snippets assume a constructed `client` ([Daytona](/docs/daytona) / [Modal](/docs/modal)). +Check support with `Supports`. -## Open a sandbox + + -`get` loads a sandbox that already exists. `refresh` loads it again. `Attach` is not available. +| Operation | Support | +| ------------------------------ | ------------------------------------------ | +| Open existing | [`get`](#open-a-sandbox) (provider lookup) | +| start / stop / pause / archive | [Supported](#stop-start-pause-and-archive) | +| Destroy | [`delete`](#delete) | +| Local disconnect | [`close`](#close-the-client) | + +Daytona may still reject a supported op for a given sandbox. Snippets assume a constructed client — [Daytona](/docs/daytona). -`attach` builds a handle from an id. It does not ask Modal whether that sandbox exists. Modal has no `get` and no `refresh`. +| Operation | Support | +| ---------------- | ------------------------------------------------- | +| Open existing | [`attach`](#open-a-sandbox) (id only) | +| Destroy | [`terminate`](#terminate) or create-time lifetime | +| Local disconnect | [`close`](#close-the-client) | + +`start`, `stop`, `pause`, `archive`, and `delete` are rejected in Kit. Snippets assume a constructed client — [Modal](/docs/modal). - - +## Open a sandbox -```ts -const sandbox = await client.get(process.env.DAYTONA_SANDBOX_ID!); -const info = await sandbox.refresh(); -console.log(sandbox.id, info.state); -``` - - +`get` loads a sandbox that already exists. `refresh` loads it again. `attach` is not available. - + + ```ts -const sandbox = await client.attach(process.env.MODAL_SANDBOX_ID!); -console.log(sandbox.id); +const sandbox = await client.get(process.env.DAYTONA_SANDBOX_ID!); +const info = await sandbox.refresh(); +// info: { id, provider, name?, providerState?, region?, isolation?, +// resources?, labels, createdAt?, providerMetadata?, state, terminal? } +console.log(info); ``` - - - - ```go import "os" @@ -71,12 +73,31 @@ if err != nil { if err := instance.Refresh(ctx); err != nil { return err } +// Info(): Id, Provider, Name, ProviderState, Region, Isolation, +// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal +fmt.Println(instance.Info()) ``` + + + +`attach` builds a handle from an id. It does not ask Modal whether that sandbox exists. `get` and `refresh` are not available. + + + + +```ts +const sandbox = await client.attach(process.env.MODAL_SANDBOX_ID!); +console.log(sandbox.id); +``` + + + + ```go import "os" @@ -86,16 +107,16 @@ if err != nil { } ``` - - -## Stop, start, pause, and archive + -These four calls are Daytona calls. `stop` asks Daytona to stop the sandbox. The sandbox stays available to start again. `start` brings a stopped or archived sandbox back. `pause` keeps memory and stops the sandbox. `archive` moves a retained sandbox into archive storage. +## Stop, start, pause, and archive + +`stop` leaves the sandbox available to start again. `start` brings a stopped or archived sandbox back. `pause` keeps memory. `archive` moves a retained sandbox into archive storage. @@ -110,10 +131,12 @@ if (!sandbox.supports(LifecycleCapability.Stop)) { } const info = await sandbox.stop({ timeout: 60_000 }); -console.log(sandbox.id, info.state); +// info: { id, provider, name?, providerState?, region?, isolation?, +// resources?, labels, createdAt?, providerMetadata?, state, terminal? } +console.log(info); ``` -`timeout` is milliseconds. Pass `force: true` to request an immediate stop. Check `supports` the same way before `start`, `pause`, or `archive`. +`timeout` is milliseconds. Pass `force: true` for an immediate stop. Check `supports` the same way before `start`, `pause`, or `archive`. @@ -139,27 +162,24 @@ if err := instance.Stop(ctx, &sandbox.StopOptions{ }); err != nil { return err } +// Info(): Id, Provider, Name, ProviderState, Region, Isolation, +// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal +fmt.Println(instance.Info()) ``` -`Start`, `Pause`, and `Archive` take the same kind of options. Check `Supports` before each one. +`Start`, `Pause`, and `Archive` take the same kind of options. -Daytona decides whether that sandbox can pause, archive, or stop. An ephemeral sandbox can still be removed by the lifetime you set at [create](/docs/create) time. +Daytona decides whether that sandbox can pause, archive, or stop. An ephemeral sandbox can still end via the lifetime you set at [create](/docs/create). - - -Modal rejects `stop`, `start`, `pause`, and `archive` in Kit. Use [terminate](#terminate) or create-time lifetime policies instead. - - + ## Delete - - `delete` removes a Daytona sandbox. @@ -199,15 +219,9 @@ if err := instance.Delete(ctx, &sandbox.DeleteOptions{ -Modal rejects `delete`. Use [terminate](#terminate) instead. - - - ## Terminate - - -`terminate` finishes a Modal sandbox. It is not a stop, and it is not a delete. A maximum lifetime or an idle timeout set at [create](/docs/create) time also ends the sandbox. +`terminate` finishes a Modal sandbox. A maximum lifetime or idle timeout set at [create](/docs/create) also ends it. @@ -222,10 +236,11 @@ if (!sandbox.supports(LifecycleCapability.Terminate)) { } const exit = await sandbox.terminate({ wait: true, timeout: 60_000 }); -console.log(sandbox.id, exit.exitCode); +// exit: { exitCode? } +console.log(exit); ``` -`wait: true` polls until the sandbox exits or the timeout fires. The timeout is how long the client waits. Modal does not receive that duration as its own terminate timeout. +`wait: true` polls until the sandbox exits or the timeout fires. The timeout is how long the client waits—Modal does not receive it as its own terminate timeout. @@ -253,7 +268,8 @@ exit, err := instance.Terminate(ctx, &sandbox.TerminateOptions{ if err != nil { return err } -fmt.Println(exit.GetExitCode()) +// exit: ExitCode +fmt.Println(exit) ``` @@ -261,12 +277,6 @@ fmt.Println(exit.GetExitCode()) - - -Daytona rejects `terminate`. Use [delete](#delete) or create-time lifetime policies instead. - - - ## Close the client @@ -294,4 +304,61 @@ defer client.Close(context.Background()) ## Read the state -`info.state` is the portable state; the provider's native string sits beside it. After a lifecycle call, refresh or re-fetch before assuming the sandbox is stopped or gone. A failed call can still mean the provider already changed state. See [Errors](/docs/errors). +`info.state` is the portable lifecycle state. `providerState` is the raw string from +the cloud when one is set. A failed call can still mean the cloud already changed +state. See [Errors](/docs/errors). + + + After a lifecycle call, call `refresh` (or `get` again) before assuming the + sandbox is stopped or gone. + + + + Use the info returned by the call, or `info()` on the handle. There is no + `refresh` or `get` on Modal. + + + + + +```ts +const info = sandbox.info(); +// info: { id, provider, name?, providerState?, region?, isolation?, +// resources?, labels, createdAt?, providerMetadata?, state, terminal? } +console.log(info); +``` + + + +```ts +const refreshed = await sandbox.refresh(); +// refreshed: same SandboxInfo shape as info() +console.log(refreshed); +``` + + + + + + +```go +info := instance.Info() +// info: Id, Provider, Name, ProviderState, Region, Isolation, +// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal +fmt.Println(info) +``` + + + +```go +if err := instance.Refresh(ctx); err != nil { + return err +} +// Info(): same SandboxInfo fields as above +fmt.Println(instance.Info()) +``` + + + + + diff --git a/website/content/docs/meta.json b/website/content/docs/meta.json index d9a7128d..77885484 100644 --- a/website/content/docs/meta.json +++ b/website/content/docs/meta.json @@ -14,6 +14,8 @@ "process", "errors", "---Examples---", - "examples" + "examples", + "---SDK reference---", + "...sdk" ] } diff --git a/website/content/docs/modal.mdx b/website/content/docs/modal.mdx index 0f9ca39c..1a24beb6 100644 --- a/website/content/docs/modal.mdx +++ b/website/content/docs/modal.mdx @@ -3,7 +3,7 @@ title: Modal description: Install, authenticate, and create a Modal sandbox from a registry image. --- -Provider package for Modal sandboxes. Create requires a registry image, a token pair, and an existing Modal app. Native SDK: Modal v0.11.1. +Use Modal when you create from a registry image, attach by id, and terminate (or rely on create-time lifetime). ## Install @@ -27,7 +27,7 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest ## Create -Set `MODAL_TOKEN_ID`, `MODAL_TOKEN_SECRET`, and `MODAL_APP_NAME`. Default environment: `main`. +Set `MODAL_TOKEN_ID`, `MODAL_TOKEN_SECRET`, and `MODAL_APP_NAME`. Default environment is `main`. Create needs a registry image. @@ -119,37 +119,141 @@ func main() { -Omit `auth` to use Modal SDK / profile credentials. `scope.appName` remains required. `endpoint` is rejected. Closing the client does not terminate the sandbox. +Omit `auth` to use Modal SDK / profile credentials. `scope.appName` is still required. `endpoint` is rejected. Closing the client does not terminate the sandbox. ## Configuration -| Setting | Notes | -| ------------- | -------------------------------------------------- | -| Token pair | `auth.tokenPair` / `TokenPair` | -| OAuth refresh | Supported with client id and one secret or JWT key | -| App | `scope.appName` — required | -| Environment | `scope.environment` (often `main`) | -| Region | `region` — default placement preference | -| Endpoint | Unsupported | + + + +| Setting | Notes | +| ------------- | ------------------------------------ | +| Token pair | `auth.tokenPair` | +| OAuth refresh | Client id plus one secret or JWT key | +| App | `scope.appName` — required | +| Environment | `scope.environment` (often `main`) | +| Region | `region` — default placement | +| Endpoint | Unsupported | + +```ts +await using client = new Client({ + provider: modal(), + auth: { + tokenPair: { + id: process.env.MODAL_TOKEN_ID!, + secret: process.env.MODAL_TOKEN_SECRET!, + }, + }, + scope: { + appName: process.env.MODAL_APP_NAME!, // required + environment: "main", + }, + region: process.env.MODAL_REGION, // optional default placement + timeout: 120_000, +}); +``` + +OAuth refresh auth (exactly one of `clientSecret` or `jwtKey`): + +```ts +await using client = new Client({ + provider: modal(), + auth: { + oauthRefresh: { + refreshToken: process.env.MODAL_REFRESH_TOKEN!, + clientId: process.env.MODAL_CLIENT_ID!, + clientSecret: process.env.MODAL_CLIENT_SECRET!, + }, + }, + scope: { + appName: process.env.MODAL_APP_NAME!, + environment: "main", + }, + timeout: 120_000, +}); +``` + + + + +| Setting | Notes | +| ------------- | ------------------------------------ | +| Token pair | `TokenPair` | +| OAuth refresh | Client id plus one secret or JWT key | +| App | `Scope.AppName` — required | +| Environment | `Scope.Environment` (often `main`) | +| Region | `Config.Region` — default placement | +| Endpoint | Unsupported | + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{ + TokenPair: &sandbox.TokenPairCredentials{ + Id: os.Getenv("MODAL_TOKEN_ID"), + Secret: os.Getenv("MODAL_TOKEN_SECRET"), + }, + }}, + Scope: &sandbox.Scope{ + AppName: sandbox.Value(os.Getenv("MODAL_APP_NAME")), // required + Environment: sandbox.Value("main"), + }, + Region: sandbox.Value(os.Getenv("MODAL_REGION")), // optional + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(modal.New(), config) +``` + +OAuth refresh auth (exactly one of `ClientSecret` or `JwtKey`): + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.OAuthRefresh{ + OauthRefresh: &sandbox.OAuthCredentials{ + RefreshToken: os.Getenv("MODAL_REFRESH_TOKEN"), + ClientId: os.Getenv("MODAL_CLIENT_ID"), + ClientSecret: sandbox.Value(os.Getenv("MODAL_CLIENT_SECRET")), + }, + }}, + Scope: &sandbox.Scope{ + AppName: sandbox.Value(os.Getenv("MODAL_APP_NAME")), + Environment: sandbox.Value("main"), + }, + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(modal.New(), config) +``` + + + Full rules: [Configuration](/docs/configuration). -## Operations +## What works -| Area | Support | -| --------- | ----------------------------------------------------------------------------------------- | -| Create | Registry image; runtime, isolation, resources, network, ports, secrets, readiness, labels | -| Open | `attach` (id only). `get` / `refresh` unsupported | -| Lifecycle | `terminate`, `close`. Start/stop/pause/archive/delete unsupported | -| Files | Full filesystem API — [Filesystem](/docs/filesystem) | -| Process | `run` — [Run a command](/docs/process) | +| Area | Notes | +| --------- | ------------------------------------------------------------------------------ | +| Create | Registry image; runtime, resources, network, ports, secrets, readiness, labels | +| Open | `attach` by id. `get` and `refresh` unsupported | +| Lifecycle | `terminate`. Start, stop, pause, archive, and delete are unsupported | +| Files | Filesystem methods — [Filesystem](/docs/filesystem) | +| Process | `run` — [Run a command](/docs/process) | -When set, resources require ≥ 0.125 CPU and ≥ 128 MiB. Disk cannot be raised at create. +When you set resources: at least 0.125 CPU and 128 MiB. Disk cannot be raised at create. ## Related -| Topic | Link | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Resources, idle, ports | [Create](/docs/create#modal) | -| attach / terminate | [Lifecycle](/docs/lifecycle) | -| Example project | [create-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-sandbox) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-sandbox) | +- Resources, idle, ports — [Create](/docs/create) +- attach / terminate — [Lifecycle](/docs/lifecycle) + + + + +Example: [create-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/configuration/create-modal-sandbox) + + + + +Example: [create-modal-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/configuration/create-modal-sandbox) + + + diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index bf8a26f2..30edebab 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -3,13 +3,22 @@ title: Run a command description: Blocking Process.run — argv or shell, cwd, env, timeouts, and exit codes. --- -Use `sandbox.process.run` (TypeScript) or `Process().Run` (Go) on an open handle. Providers share the same options and result shape; switch Daytona or Modal above for mapping and edge cases. +Run a command on an open handle. You need a sandbox from [create](/docs/create) or [lifecycle](/docs/lifecycle). -| Method | Returns | -| ------------- | ---------------------------------------------------------------------- | -| `run` / `Run` | `exitCode` / `exit_code`, `stdout`, `stderr` when the command finishes | + + + +Call `sandbox.process.run`. The result includes `exitCode`, `stdout`, and `stderr` +(providers may leave `stderr` empty—see [How providers run it](#how-providers-run-it)). + + + -Prerequisite: create or open a sandbox ([Daytona](/docs/daytona), [Modal](/docs/modal), or [Lifecycle](/docs/lifecycle)). +Call `Process().Run`. The result includes `exit_code`, `stdout`, and `stderr` +(providers may leave `stderr` empty—see [How providers run it](#how-providers-run-it)). + + + ## Options @@ -20,13 +29,48 @@ Set **exactly one** of: | `argv` | Program + arguments | `["echo", "hello"]` | | `shell` | One shell command string | `"echo hello"` | -| Field | Rules | -| ------------------ | ------------------------------------------------------------------------------------- | -| Both or neither | Rejected. Field: `argv` | -| Blank `shell` | Rejected after trim (whitespace-only too). Field: `shell` | -| `workingDirectory` | Absolute when set (`/workspace`). Relative, blank, or NUL → field `working_directory` | -| `environment` | For this call only. Omit → provider default. Empty map → add no variables | -| `timeout` | TypeScript: ms. Go: `time.Duration`. See [Timeouts and cancel](#timeouts-and-cancel) | +| Field | Rules | +| ------------------ | ------------------------------------------------------------------------- | +| Both or neither | Rejected. Field: `argv` | +| Blank `shell` | Rejected after trim. Field: `shell` | +| `workingDirectory` | Absolute when set. Relative, blank, or NUL → field `working_directory` | +| `environment` | For this call only. Omit → provider default. Empty map → add no variables | +| `timeout` | See [Timeouts](#timeouts-and-cancel) | + + + + +```ts +const result = await sandbox.process.run({ + argv: ["echo", "hello"], // or shell: 'echo "hello"' — not both + workingDirectory: "/tmp", // absolute when set + environment: { KIT_DEMO: "from-kit" }, + timeout: 5_000, // milliseconds; whole seconds required + signal: AbortSignal.timeout(5_000), +}); +// result: { exitCode, stdout, stderr } +console.log(result); +``` + + + + +```go +result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "hello"}, // or Shell — not both + WorkingDirectory: sandbox.Value("/tmp"), // absolute when set + Environment: map[string]string{"KIT_DEMO": "from-kit"}, + Timeout: sandbox.Duration(5 * time.Second), // whole seconds +}) +if err != nil { + return err +} +// result: ExitCode, Stdout, Stderr +fmt.Println(result) +``` + + + ### How providers run it @@ -39,36 +83,77 @@ Set **exactly one** of: | omit / zero timeout | no command limit (`0`, so Daytona does not use its 10s default) | | fractional (`1500` ms) | rejected (`timeout`) | +Daytona returns stdout only; `stderr` is empty. stdout may include merged streams. + + + + +```ts +const result = await sandbox.process.run({ + argv: ["echo", "hello"], // sent as a POSIX-quoted shell string +}); +// result: { exitCode, stdout, stderr } — stderr often empty on Daytona +console.log(result); +``` + + + + +```go +result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "hello"}, // sent as a POSIX-quoted shell string +}) +if err != nil { + return err +} +// result: ExitCode, Stdout, Stderr — Stderr often empty on Daytona +fmt.Println(result) +``` + + + + -| You pass | On Modal | -| ---------------------- | --------------------------------------------------------------------- | -| `argv` | argument list as-is | -| `shell` | `bash -c` | -| omit / zero timeout | no command limit (TypeScript omits `timeoutMs`; Go uses `Timeout: 0`) | -| fractional (`1500` ms) | rejected (`timeout`) | +| You pass | On Modal | +| ---------------------- | -------------------- | +| `argv` | argument list as-is | +| `shell` | `bash -c` | +| omit / zero timeout | no command limit | +| fractional (`1500` ms) | rejected (`timeout`) | - +Modal returns both `stdout` and `stderr`. -### Result + + -| Field | Notes | -| ------------------------ | -------------------------------- | -| `exitCode` / `exit_code` | Non-zero is **not** an SDK error | -| `stdout` | Bytes | -| `stderr` | See provider note below | +```ts +const result = await sandbox.process.run({ + argv: ["echo", "hello"], // argument list as-is +}); +// result: { exitCode, stdout, stderr } +console.log(result); +``` - - Daytona returns stdout only; `stderr` is empty (Go: non-nil empty slice). - stdout may include merged streams. - + + -Modal returns both `stdout` and `stderr`. +```go +result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "hello"}, // argument list as-is +}) +// result: ExitCode, Stdout, Stderr +fmt.Println(result) +``` + + + -Calls on one handle run one at a time with lifecycle and filesystem. A second -handle for the same sandbox does not wait. + + +A non-zero exit code is **not** an SDK error. ## Timeouts and cancel @@ -78,20 +163,43 @@ handle for the same sandbox does not wait. | `0` | No extra client deadline for this call | | Positive value | Cap this call; must be whole seconds (`5000` ok, `1500` rejected) | -Omit or zero also means no separate provider command limit (see the mapping -table above). +Omit or zero also means no separate provider command limit. - - Cancel the Go context to end the wait. In TypeScript, abort rejects the - `await`; the remote command may keep running. Either way the handle stays busy - until that provider call settles. - + + - - Cancel the Go context to end the wait. In TypeScript, abort rejects the - `await`; the remote command may keep running. Either way the handle stays busy - until that provider call settles. - +Abort rejects the `await`; the remote command may keep running. The handle stays busy until the in-flight call finishes. + +```ts +const ac = new AbortController(); +const pending = sandbox.process.run({ + argv: ["sleep", "30"], + timeout: 0, // no deadline; still abortable + signal: ac.signal, +}); +ac.abort(); +await pending.catch(() => {}); +``` + + + + +Cancel the context to end the wait. The handle stays busy until the in-flight call finishes. + +```go +ctx, cancel := context.WithCancel(ctx) +defer cancel() + +result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"sleep", "30"}, + Timeout: sandbox.Duration(0), // no deadline; still cancelable via ctx +}) +_ = result +_ = err +``` + + + ## argv @@ -103,7 +211,8 @@ const result = await sandbox.process.run({ argv: ["echo", "hello from kit"], timeout: 5_000, }); -console.log(result.exitCode, new TextDecoder().decode(result.stdout)); +// result: { exitCode, stdout, stderr } +console.log(result); ``` @@ -124,26 +233,13 @@ result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ if err != nil { return err } -fmt.Printf("exit=%d stdout=%q\n", result.GetExitCode(), string(result.GetStdout())) +// result: ExitCode, Stdout, Stderr +fmt.Println(result) ``` - - Examples: - [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-command) - · - [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-command) - - - - Examples: - [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-command) - · - [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-command) - - ## shell Do not set `argv` on the same call. @@ -155,45 +251,33 @@ Do not set `argv` on the same call. const result = await sandbox.process.run({ shell: 'echo "hello from shell"', }); +// result: { exitCode, stdout, stderr } +console.log(result); ``` - - Daytona runs the string as shell. On Modal the same call uses `bash -c`. - - - - Modal runs this as `bash -c`. On Daytona the same call uses the shell string - as-is. - - ```go -import "github.com/sandbox-kit/kit/sdks/go/sandbox" +import ( + "fmt" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ Shell: sandbox.Value(`echo "hello from shell"`), }) +if err != nil { + return err +} +// result: ExitCode, Stdout, Stderr +fmt.Println(result) ``` - - Examples: - [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-shell-command) - · - [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-shell-command) - - - - Examples: - [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-shell-command) - · - [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-shell-command) - - ## Working directory and environment Applies to that call only. @@ -207,79 +291,35 @@ const result = await sandbox.process.run({ workingDirectory: "/tmp", environment: { KIT_DEMO: "from-kit" }, }); +// result: { exitCode, stdout, stderr } +console.log(result); ``` ```go -import "github.com/sandbox-kit/kit/sdks/go/sandbox" +import ( + "fmt" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ Argv: []string{"printenv", "KIT_DEMO"}, WorkingDirectory: sandbox.Value("/tmp"), Environment: map[string]string{"KIT_DEMO": "from-kit"}, }) +if err != nil { + return err +} +// result: ExitCode, Stdout, Stderr +fmt.Println(result) ``` - -cwd: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-with-cwd) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-with-cwd) - -env: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-with-env) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-with-env) - - - - -cwd: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-with-cwd) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-with-cwd) - -env: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-with-env) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-with-env) - - - -## Timeout - -Cap one call with a whole-second `timeout`. Fractional values are rejected (`timeout`). - - - - -```ts -const result = await sandbox.process.run({ - argv: ["echo", "hello with timeout"], - timeout: 5_000, -}); -``` - - - - -```go -result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ - Argv: []string{"echo", "hello with timeout"}, - Timeout: sandbox.Duration(5 * time.Second), -}) -``` - - - - - - Examples: - [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-with-timeout) - · - [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-with-timeout) - - - - Examples: - [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-with-timeout) - · - [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-with-timeout) - - ## Non-zero exit code Transport and validation failures still return errors. A finished command with exit `7` does not. @@ -318,24 +358,6 @@ if result.GetExitCode() != 7 { - - Examples: - [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/daytona/process/run-daytona-check-exit) - · - [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/daytona/process/run-daytona-check-exit) - +## Examples - - Examples: - [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/modal/process/run-modal-check-exit) - · - [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/modal/process/run-modal-check-exit) - - -## Limits - -Not supported: streaming I/O, interactive sessions, PTY. - - - Daytona `code_run` is not available on this API. - +Runnable projects: [Examples](/docs/examples). diff --git a/website/content/docs/sdk/client.mdx b/website/content/docs/sdk/client.mdx new file mode 100644 index 00000000..dd6812fb --- /dev/null +++ b/website/content/docs/sdk/client.mdx @@ -0,0 +1,379 @@ +--- +title: Client +description: Construct a client; create, get or attach, and close. +--- + +Creates sandboxes and reopens them by id (`get` or `attach`). Closing the client only drops the local connection—cloud sandboxes keep running. + +## Construction + + + + +```ts +new Client(config: ClientConfig) +``` + +Validates config and initializes the selected provider. Throws on invalid config. + +**Parameters** + +| Name | Type | Description | +| -------- | ----------------------------------------- | -------------------------------------------------------------------------------- | +| `config` | [`ClientConfig`](/docs/sdk/configuration) | Provider plus shared settings (`auth`, `scope`, `endpoint`, `region`, `timeout`) | + +**Returns** + +`Client` + +**Example** + +```ts +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider, + timeout: 120_000, // default client deadline (ms) +}); +``` + +`await using` calls `close` on dispose. + + + + +```go +func NewClient(provider Provider, config *Config) (*Client, error) +``` + +Validates config and initializes the provider. Invalid config returns a nil client and an error. + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------------------------ | ------------------------------------------------------------------ | +| `provider` | `Provider` | Provider constructor from a provider package | +| `config` | [`*Config`](/docs/sdk/configuration) | Shared settings (`Auth`, `Scope`, `Endpoint`, `Region`, `Timeout`) | + +**Returns** + +`(*Client, error)` + +**Example** + +```go +client, err := sandbox.NewClient(provider, &sandbox.Config{ + Timeout: sandbox.Duration(2 * time.Minute), // default client deadline +}) +if err != nil { + return err +} +defer client.Close(context.Background()) +``` + + + + +## Properties + + + + +| Name | Type | Description | +| -------------- | -------- | ----------------------------------- | +| `providerName` | `string` | Identifier of the selected provider | + + + + +| Name | Signature | Description | +| -------------- | ---------------- | ----------------------------------- | +| `ProviderName` | `func () string` | Identifier of the selected provider | + + + + +## Methods + +### create + + + + +```ts +create(request?: CreateOptions, options?: OperationOptions): Promise +``` + +Creates one sandbox and returns its handle. Omit the request (or pass `{}`) to use provider defaults. + +**Parameters** + +| Name | Type | Description | +| ---------- | --------------------------------------------------------- | --------------------------------------------------- | +| `request?` | [`CreateOptions`](/docs/sdk/create-options) | Create body. Omitted fields keep provider defaults. | +| `options?` | [`OperationOptions`](/docs/sdk/sandbox#operation-options) | Call `timeout` (ms) and optional `signal` | + +**Returns** + +`Promise<`[`Sandbox`](/docs/sdk/sandbox)`>` — handle for the new sandbox + +**Example** + + + +```ts +const sandbox = await client.create( + { source: { image: { reference: "daytonaio/playground:latest" } } }, + { timeout: 60_000 }, // deadline for this call (ms) +); +console.log(sandbox.id); +``` + + + + + +```ts +const sandbox = await client.create( + { source: { image: { reference: "alpine:3.21" } } }, + { timeout: 60_000 }, // deadline for this call (ms) +); +console.log(sandbox.id); +``` + + + + + + +```go +func (c *Client) Create(ctx context.Context, request *CreateOptions) (*Sandbox, error) +``` + +Creates one sandbox and returns its handle. A nil request uses provider defaults. + +**Parameters** + +| Name | Type | Description | +| --------- | -------------------------------------------- | ----------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `request` | [`*CreateOptions`](/docs/sdk/create-options) | Create body; nil for defaults | + +**Returns** + +`(*Sandbox, error)` — handle for the new sandbox + +**Example** + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "daytonaio/playground:latest"}, + }}, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), // deadline for this call + }, +}) +``` + + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "alpine:3.21"}, + }}, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), // deadline for this call + }, +}) +``` + + + + + + + + +### get + +Looks up a sandbox by id and returns a handle the provider has verified. + + + + +```ts +get(id: string, options?: OperationOptions): Promise +``` + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------ | ----------------------- | +| `id` | `string` | Sandbox identifier | +| `options?` | `OperationOptions` | Call timeout and signal | + +**Returns** + +`Promise` + +**Example** + +```ts +const sandbox = await client.get(knownId); +const info = sandbox.info(); +// info: { id, provider, name?, providerState?, region?, isolation?, +// resources?, labels, createdAt?, providerMetadata?, state, terminal? } +console.log(info); +``` + + + + +```go +func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) +``` + +**Parameters** + +| Name | Type | Description | +| ----- | ----------------- | ------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `id` | `string` | Sandbox identifier | + +**Returns** + +`(*Sandbox, error)` + +**Example** + +```go +instance, err := client.Get(ctx, knownID) +// Info(): Id, Provider, Name, ProviderState, Region, Isolation, +// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal +fmt.Println(instance.Info()) +``` + + + + + + + + +### attach + +Builds a handle for an existing sandbox id without a lookup round trip. It does not ask the provider whether that sandbox exists. + + + + +```ts +attach(id: string, options?: OperationOptions): Promise +``` + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------ | ----------------------- | +| `id` | `string` | Sandbox identifier | +| `options?` | `OperationOptions` | Call timeout and signal | + +**Returns** + +`Promise` + +**Example** + +```ts +const sandbox = await client.attach(knownId); +``` + + + + +```go +func (c *Client) Attach(ctx context.Context, id string) (*Sandbox, error) +``` + +**Parameters** + +| Name | Type | Description | +| ----- | ----------------- | ------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `id` | `string` | Sandbox identifier | + +**Returns** + +`(*Sandbox, error)` + +**Example** + +```go +instance, err := client.Attach(ctx, knownID) +``` + + + + + + +### close + + + + +```ts +close(options?: OperationOptions): Promise +``` + +Closes the local provider connection. Does not stop, archive, or delete cloud sandboxes. + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------ | ----------------------- | +| `options?` | `OperationOptions` | Call timeout and signal | + +**Returns** + +`Promise` + +**Example** + +```ts +await client.close(); +``` + + + + +```go +func (c *Client) Close(ctx context.Context) error +``` + +Closes the local provider connection. Does not stop, archive, or delete cloud sandboxes. + +**Parameters** + +| Name | Type | Description | +| ----- | ----------------- | ------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | + +**Returns** + +`error` + +**Example** + +```go +defer client.Close(context.Background()) +``` + + + diff --git a/website/content/docs/sdk/configuration.mdx b/website/content/docs/sdk/configuration.mdx new file mode 100644 index 00000000..fcca0720 --- /dev/null +++ b/website/content/docs/sdk/configuration.mdx @@ -0,0 +1,189 @@ +--- +title: Client config +description: ClientConfig, Config, auth credentials, and Scope. +--- + +Passed at client construction. Credentials are checked on the first provider call, not at construction. + +## Config + + + + +```ts +type ClientConfig = Config & { provider: Provider }; + +type Config = { + auth?: AuthInput; + endpoint?: string; + region?: string; + scope?: Scope; + timeout?: number; // milliseconds +}; +``` + +| Field | Type | Description | +| ----------- | -------------------- | ---------------------------------------------------------------------- | +| `provider` | `Provider` | Provider constructor from a provider package | +| `auth?` | [`AuthInput`](#auth) | Credential set. Omit to use env/profile credentials from the provider. | +| `endpoint?` | `string` | Provider endpoint URL override. Remote URLs must be HTTPS. | +| `region?` | `string` | Default region or target preference | +| `scope?` | [`Scope`](#scope) | App, environment, organization, or project | +| `timeout?` | `number` | Default deadline (ms) for calls that omit their own timeout | + +**Example** + +```ts +await using client = new Client({ + provider, + auth: { apiKey: { key: process.env.API_KEY! } }, + region: "us", + timeout: 120_000, // default client deadline (ms) +}); +``` + + + This sets the client deadline only. If a create/start/stop/pause/delete call + omits its own timeout, Daytona still uses a **60s** default on its side. + + + + + +```go +type Config struct { + Auth *AuthConfig + Endpoint *string + Region *string + Scope *Scope + Timeout *durationpb.Duration +} +``` + +Pass `config` to `NewClient(provider, config)`. + +| Field | Description | +| ---------- | ------------------------------------------------------------------- | +| `Auth` | Credential set. Nil uses env/profile credentials from the provider. | +| `Endpoint` | Provider endpoint URL override | +| `Region` | Default region or target preference | +| `Scope` | App, environment, organization, or project | +| `Timeout` | Default client deadline via `sandbox.Duration` | + +**Example** + +```go +client, err := sandbox.NewClient(provider, &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("API_KEY")}, + }}, + Region: sandbox.Value("us"), + Timeout: sandbox.Duration(2 * time.Minute), // default client deadline +}) +``` + + + This sets the client deadline only. If a create/start/stop/pause/delete call + omits its own timeout, Daytona still uses a **60s** default on its side. + + + + + +## Auth + +Exactly one credential alternative when auth is set. + + + + +```ts +type AuthInput = + | { apiKey: { key: string } } + | { tokenPair: { id: string; secret: string } } + | { bearerToken: { token: string } } + | { + oauthRefresh: { + refreshToken: string; + clientId: string; + clientSecret?: string; // exactly one of clientSecret | jwtKey + jwtKey?: string; + }; + }; +``` + +| Variant | Description | +| -------------- | --------------------------------------------------- | +| `apiKey` | Single key string | +| `tokenPair` | Id and secret pair | +| `bearerToken` | Bearer or JWT token | +| `oauthRefresh` | Refresh token plus client id and one secret/JWT key | + + + + +```go +// AuthConfig.Credentials is one of: +// *APIKey{ ApiKey: &APIKeyCredentials{ Key } } +// *TokenPair{ TokenPair: &TokenPairCredentials{ Id, Secret } } +// *BearerToken{ BearerToken: &BearerTokenCredentials{ Token } } +// *OAuthRefresh{ OauthRefresh: &OAuthCredentials{ ... } } +``` + +`OAuthCredentials` requires `RefreshToken`, `ClientId`, and exactly one of `ClientSecret` or `JwtKey`. + +| Variant | Description | +| -------------- | --------------------------------------------------- | +| `APIKey` | Single key string | +| `TokenPair` | Id and secret pair | +| `BearerToken` | Bearer or JWT token | +| `OAuthRefresh` | Refresh token plus client id and one secret/JWT key | + + + + +## Scope + + + + +| Field | Description | +| ---------------- | ------------------------------- | +| `appName` | Application name | +| `environment` | Environment name (often `main`) | +| `organizationId` | Organization id | +| `projectId` | Project id | + + + + +| Field | Description | +| ---------------- | ------------------------------- | +| `AppName` | Application name | +| `Environment` | Environment name (often `main`) | +| `OrganizationId` | Organization id | +| `ProjectId` | Project id | + + + + +## Helpers + + + + +| Helper | Description | +| ------------------------ | ---------------------------------- | +| `validateConfig(config)` | Throws if client config is invalid | + + + + +| Helper | Description | +| ------------------------ | ---------------------------------------------------- | +| `ValidateConfig(config)` | Returns an error if invalid | +| `Duration(d)` | Build a timeout field from `time.Duration` | +| `Value(v)` | Pointer helper for optional scalars (including zero) | + + + diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx new file mode 100644 index 00000000..672adb69 --- /dev/null +++ b/website/content/docs/sdk/create-options.mdx @@ -0,0 +1,488 @@ +--- +title: CreateOptions +description: Create request body and nested option types. +--- + +Request body for [`Client.create`](/docs/sdk/client#create). Omitted fields keep provider defaults. Unsupported fields fail before the provider call. + +## CreateOptions + + + + +```ts +type CreateOptions = { + name?: string; + source?: SandboxSourceInput; + runtime?: RuntimeConfig; + isolation?: IsolationConfig; + resources?: Resources; + placement?: Placement; + environment?: Record; + labels?: Record; + network?: NetworkConfigInput; + security?: SecurityConfig; + lifetime?: LifetimePolicyInput; + provisioning?: ProvisioningOptionsInput; + readiness?: ReadinessProbeInput; + observability?: ObservabilityConfig; +}; +``` + +Durations on this type are **milliseconds**. Allowlists under `network` are plain `string[]`. + +| Field | Description | +| --------------- | ------------------------------------------------------- | +| `name` | Display name. Explicit empty string asks for unnamed. | +| `source` | Image, snapshot, warm pool, or provider default | +| `runtime` | Language hint, user, working directory, entrypoint, PTY | +| `isolation` | Isolation class | +| `resources` | CPU, memory MiB, disk MiB | +| `placement` | Cloud, preferred regions, or target | +| `environment` | Process environment for the sandbox | +| `labels` | Caller labels/tags | +| `network` | Egress, allowlists, ports, links | +| `security` | Public access, workload identity, secrets | +| `lifetime` | Max lifetime and idle/stopped actions | +| `provisioning` | Create deadline, queue timeout, wait condition | +| `readiness` | Probe when waiting until ready | +| `observability` | Verbose logging / telemetry | + +**Example** + + + +```ts +const sandbox = await client.create({ + source: { image: { reference: "daytonaio/playground:latest" } }, + resources: { cpuCores: 1, memoryMib: 512n }, +}); +``` + + + + + +```ts +const sandbox = await client.create({ + source: { image: { reference: "alpine:3.21" } }, + resources: { cpuCores: 1, memoryMib: 512n }, + runtime: { workingDirectory: "/tmp", entrypoint: ["sleep", "infinity"] }, +}); +``` + + + + + + +```go +type CreateOptions struct { + Name *string + Source *SandboxSource + Runtime *RuntimeConfig + Isolation *IsolationConfig + Resources *Resources + Placement *Placement + Environment map[string]string + Labels map[string]string + Network *NetworkConfig + Security *SecurityConfig + Lifetime *LifetimePolicy + Provisioning *ProvisioningOptions + Readiness *ReadinessProbe + Observability *ObservabilityConfig +} +``` + +Durations use `sandbox.Duration`. Optional numbers use `sandbox.Value`. + +| Field | Description | +| --------------- | ------------------------------------------------------- | +| `Name` | Display name. Explicit empty string asks for unnamed. | +| `Source` | Image, snapshot, warm pool, or provider default | +| `Runtime` | Language hint, user, working directory, entrypoint, PTY | +| `Isolation` | Isolation class | +| `Resources` | CPU, memory MiB, disk MiB | +| `Placement` | Cloud, preferred regions, or target | +| `Environment` | Process environment for the sandbox | +| `Labels` | Caller labels/tags | +| `Network` | Egress, allowlists, ports, links | +| `Security` | Public access, workload identity, secrets | +| `Lifetime` | Max lifetime and idle/stopped actions | +| `Provisioning` | Create deadline, queue timeout, wait condition | +| `Readiness` | Probe when waiting until ready | +| `Observability` | Verbose logging / telemetry | + +**Example** + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "daytonaio/playground:latest"}, + }}, + Resources: &sandbox.Resources{ + CpuCores: sandbox.Value(1.0), + MemoryMib: sandbox.Value(uint64(512)), + }, +}) +``` + + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "alpine:3.21"}, + }}, + Resources: &sandbox.Resources{ + CpuCores: sandbox.Value(1.0), + MemoryMib: sandbox.Value(uint64(512)), + }, +}) +``` + + + + + + +## Source + +Set exactly one variant. Unsupported variants fail before the provider call. + + + + +| Variant | Description | +| ----------------- | ------------------------------------- | +| `image` | Container image `reference` | +| `snapshot` | Snapshot `reference`, optional `kind` | +| `warmPool` | Warm pool `reference` | +| `providerDefault` | Use the account default source | + + + + +| Variant | Description | +| ----------------- | ------------------------------------- | +| `Image` | Container image `Reference` | +| `Snapshot` | Snapshot `Reference`, optional `Kind` | +| `WarmPool` | Warm pool `Reference` | +| `ProviderDefault` | Use the account default source | + + + + + + Daytona accepts `image`, `snapshot`, and `providerDefault` when the account + allows them. Memory and disk requests must be whole GiB expressed as MiB; CPU + must be whole cores. Per-request `placement` is rejected—set client `region` + instead. + + + + Modal requires an `image` source (`providerDefault` is rejected). `diskMib` + and placement `target` are unsupported. Lifetime supports `maximumLifetime` + and `idleTerminate` only. + + +## RuntimeConfig + + + + +| Field | Description | +| ------------------ | ------------------------------------------------- | +| `language` | Language or toolbox hint (not an install promise) | +| `version` | Version hint | +| `user` | OS user | +| `workingDirectory` | Working directory | +| `entrypoint` | Entrypoint argv | +| `pty` | Request a PTY when supported | + + + + +| Field | Description | +| ------------------ | ------------------------------------------------- | +| `Language` | Language or toolbox hint (not an install promise) | +| `Version` | Version hint | +| `User` | OS user | +| `WorkingDirectory` | Working directory | +| `Entrypoint` | Entrypoint argv | +| `Pty` | Request a PTY when supported | + + + + +## Resources + + + + +| Field | Description | +| ---------------- | ---------------------------------------------- | +| `cpuCores` | Requested CPU cores | +| `cpuLimitCores` | Optional CPU ceiling (≥ request when positive) | +| `memoryMib` | Requested memory in MiB | +| `memoryLimitMib` | Optional memory ceiling | +| `diskMib` | Requested disk in MiB | + + + + +| Field | Description | +| ---------------- | ---------------------------------------------- | +| `CpuCores` | Requested CPU cores | +| `CpuLimitCores` | Optional CPU ceiling (≥ request when positive) | +| `MemoryMib` | Requested memory in MiB | +| `MemoryLimitMib` | Optional memory ceiling | +| `DiskMib` | Requested disk in MiB | + + + + +## Placement + + + + +| Field | Description | +| --------- | -------------------------- | +| `cloud` | Cloud name preference | +| `regions` | Preferred regions in order | +| `target` | Provider target string | + + + + +| Field | Description | +| --------- | -------------------------- | +| `Cloud` | Cloud name preference | +| `Regions` | Preferred regions in order | +| `Target` | Provider target string | + + + + +## LifetimePolicy + + + + +| Field | Description | +| ----------------- | ------------------------------------------------------ | +| `maximumLifetime` | Cap on how long the sandbox may exist | +| `ephemeral` | Prefer deletion when the sandbox stops, when supported | +| `idleTerminate` | End after idle | +| `idleStop` | Stop after idle | +| `idlePause` | Pause after idle | +| `stoppedArchive` | Archive after stop | +| `stoppedDelete` | Delete after stop | + + + Daytona maps lifetime delays as **whole minutes**. Supported policies include + ephemeral, idle stop/pause, and stopped archive/delete (`stoppedArchive` max + 43200 minutes). `idleTerminate` is not used. + + + + Modal supports `maximumLifetime` (whole seconds, 1…86400) and `idleTerminate` + only. Ephemeral, idle stop/pause, and stopped archive/delete are rejected. + + + + + +| Field | Description | +| ----------------- | ------------------------------------------------------ | +| `MaximumLifetime` | Cap on how long the sandbox may exist | +| `Ephemeral` | Prefer deletion when the sandbox stops, when supported | +| `IdleTerminate` | End after idle | +| `IdleStop` | Stop after idle | +| `IdlePause` | Pause after idle | +| `StoppedArchive` | Archive after stop | +| `StoppedDelete` | Delete after stop | + + + Daytona maps lifetime delays as **whole minutes**. Supported policies include + ephemeral, idle stop/pause, and stopped archive/delete (`StoppedArchive` max + 43200 minutes). `IdleTerminate` is not used. + + + + Modal supports `MaximumLifetime` (whole seconds, 1…86400) and `IdleTerminate` + only. Ephemeral, idle stop/pause, and stopped archive/delete are rejected. + + + + + +### AutomaticAction + + + + +| Field | Description | +| ------- | ----------------------------------------------------------------------------- | +| `mode` | [`PolicyMode`](/docs/sdk/enums#policymode): `Default`, `Disabled`, or `After` | +| `after` | Delay when mode is `After`. Zero means immediate, not disabled. | + + + + +| Field | Description | +| ------- | ----------------------------------------------------------------------------- | +| `Mode` | [`PolicyMode`](/docs/sdk/enums#policymode): `Default`, `Disabled`, or `After` | +| `After` | Delay when mode is `After`. Zero means immediate, not disabled. | + + + + +## ProvisioningOptions + + + + +| Field | Description | +| -------------- | ----------------------------------------------------------------- | +| `timeout` | Deadline for the create call | +| `queueTimeout` | How long creation may sit in a queue | +| `waitFor` | [`WaitCondition`](/docs/sdk/enums#waitcondition) before returning | + + + `timeout` must be whole seconds. Omit or `0` still leaves Daytona's **60s** + create default. `queueTimeout` is whole minutes and must be greater than 0 + when set. + + + + `timeout` is how long this create/ready wait may run. A ready wait needs a + positive timeout. `queueTimeout` is unsupported. + + + + + +| Field | Description | +| -------------- | ----------------------------------------------------------------- | +| `Timeout` | Deadline for the create call | +| `QueueTimeout` | How long creation may sit in a queue | +| `WaitFor` | [`WaitCondition`](/docs/sdk/enums#waitcondition) before returning | + + + `Timeout` must be whole seconds. Omit or zero still leaves Daytona's **60s** + create default. `QueueTimeout` is whole minutes and must be greater than 0 + when set. + + + + `Timeout` is how long this create/ready wait may run. A ready wait needs a + positive timeout. `QueueTimeout` is unsupported. + + + + + +## ReadinessProbe + +Set either a TCP port or a command. + + + + +| Variant | Description | +| -------------- | ------------------------------ | +| `tcpPort` | Port `1`–`65535` | +| `command.argv` | Command argv that must succeed | + + + + +| Variant | Description | +| --------------------- | ------------------------------ | +| `TcpPort` | Port `1`–`65535` | +| `Command` with `Argv` | Command argv that must succeed | + + + + +## NetworkConfig + + + + +| Field | Description | +| ------------------ | ----------------------------------------------------------- | +| `egress` | [`EgressMode`](/docs/sdk/enums#egressmode) | +| `outboundCidrs` | Outbound CIDR allowlist. Present empty list means deny-all. | +| `outboundDomains` | Outbound domain allowlist | +| `inboundCidrs` | Inbound CIDR allowlist | +| `privateNetwork` | Request a private network when supported | +| `linkedSandbox` | Link to another known sandbox id | +| `proxyReference` | Provider-managed proxy name | +| `outboundProxyUrl` | Outbound proxy URL | +| `customDomain` | Custom domain when supported | +| `ports` | Published ports (`port`, optional `transport`) | + + + + +| Field | Description | +| ------------------ | ----------------------------------------------------------- | +| `Egress` | [`EgressMode`](/docs/sdk/enums#egressmode) | +| `OutboundCidrs` | Outbound CIDR allowlist. Present empty list means deny-all. | +| `OutboundDomains` | Outbound domain allowlist | +| `InboundCidrs` | Inbound CIDR allowlist | +| `PrivateNetwork` | Request a private network when supported | +| `LinkedSandbox` | Link to another known sandbox id | +| `ProxyReference` | Provider-managed proxy name | +| `OutboundProxyUrl` | Outbound proxy URL | +| `CustomDomain` | Custom domain when supported | +| `Ports` | Published ports (`Port`, optional `Transport`) | + + + + +## SecurityConfig + + + + +| Field | Description | +| ------------------ | ------------------------------------------------------------------------- | +| `publicAccess` | Request public access when true | +| `workloadIdentity` | Request a workload identity when supported | +| `secrets` | Provider-managed secret references (`reference`, injection, env var name) | + + + + +| Field | Description | +| ------------------ | ------------------------------------------------------------------------- | +| `PublicAccess` | Request public access when true | +| `WorkloadIdentity` | Request a workload identity when supported | +| `Secrets` | Provider-managed secret references (`Reference`, injection, env var name) | + + + + +## Helpers + + + + +`validateCreateOptions(request)` — throws if the create body is invalid. + + + + +`ValidateCreateOptions(request)` — returns an error if invalid. + + + diff --git a/website/content/docs/sdk/enums.mdx b/website/content/docs/sdk/enums.mdx new file mode 100644 index 00000000..b79e3bd3 --- /dev/null +++ b/website/content/docs/sdk/enums.mdx @@ -0,0 +1,156 @@ +--- +title: Enums +description: Shared public enums for create, lifecycle, and info. +--- + +## SandboxState + +Portable lifecycle state on [`SandboxInfo`](/docs/sdk/sandbox-info). + +| Value | Description | +| ------------- | --------------------------------- | +| `Unknown` | State is not known | +| `Queued` | Waiting to be scheduled | +| `Starting` | Booting or coming online | +| `Running` | Available for work | +| `Stopping` | Stop in progress | +| `Stopped` | Stopped and can be started again | +| `Pausing` | Pause in progress | +| `Paused` | Paused with retained state | +| `Archiving` | Archive in progress | +| `Archived` | Archived to colder storage | +| `Terminating` | Permanent end in progress | +| `Terminated` | Permanently ended | +| `Deleting` | Delete in progress | +| `Deleted` | Permanently removed | +| `Error` | Provider reported a failure state | + +## LifecycleCapability + +Used with [Capabilities](/docs/sdk/sandbox#capabilities). File and process calls are not capabilities. + +| Value | Description | +| ----------- | -------------------------------------- | +| `Unknown` | Unspecified capability | +| `Refresh` | Reload metadata from the provider | +| `Start` | Start a stopped sandbox | +| `Stop` | Stop a running sandbox | +| `Pause` | Pause a sandbox | +| `Terminate` | Permanently end the sandbox | +| `Archive` | Move retained state to archive storage | +| `Delete` | Permanently delete the sandbox | + +## PolicyMode + +Idle/stopped automatic actions under [`LifetimePolicy`](/docs/sdk/create-options#lifetimepolicy). + +| Value | Description | +| ---------- | ------------------------------------------ | +| `Default` | Omit the policy / leave it to the provider | +| `Disabled` | Turn the policy off | +| `After` | Run after the configured delay | + +## WaitCondition + +How far provisioning wait-for waits before create returns. + +| Value | Description | +| ----------- | ----------------------------------------------------------------------- | +| `Default` | Provider default wait depth | +| `Submitted` | Return once the create request is accepted | +| `Scheduled` | Return once the sandbox is scheduled | +| `Started` | Return once the sandbox has started | +| `Ready` | Return once readiness succeeds (probe + positive timeout when required) | + +## EgressMode + +Outbound network policy on create. + +| Value | Description | +| ------------ | -------------------------------------------------------------- | +| `Default` | Leave outbound policy to the provider | +| `AllowAll` | Allow outbound traffic | +| `BlockAll` | Block outbound traffic (including private links when enforced) | +| `Restricted` | Allow only listed CIDRs and/or domains | + +## PortTransport + +Transport for a published port. + +| Value | Description | +| ---------- | ----------------------------------- | +| `Default` | Leave the transport to the provider | +| `Tls` | TLS | +| `Http2Tls` | HTTP/2 over TLS | +| `Plain` | Unencrypted / plain transport | + +## IsolationKind + +Sandbox isolation class. + +| Value | Description | +| ------------------ | --------------------------------------------------- | +| `Default` | Leave the class to the provider | +| `Container` | Container isolation | +| `MicroVM` | MicroVM isolation | +| `LinuxVm` | Full Linux VM isolation | +| `Worker` | Worker-style isolation | +| `ProviderSpecific` | Provider-defined class (see provider kind when set) | + +## SnapshotKind + +Classification for a snapshot source. + +| Value | Description | +| ------------------ | ------------------------------- | +| `Default` | Leave the class to the provider | +| `Filesystem` | Filesystem snapshot | +| `Memory` | Memory snapshot | +| `Image` | Image snapshot | +| `Machine` | Machine snapshot | +| `Checkpoint` | Checkpoint snapshot | +| `ProviderSpecific` | Provider-defined snapshot class | + +## SecretInjection + +How a create-time secret reference is exposed. + +| Value | Description | +| ------------------- | --------------------------------- | +| `Default` | Leave injection to the provider | +| `Environment` | Inject as an environment variable | +| `EgressPlaceholder` | Inject as an egress placeholder | + +## FileType + +Filesystem entry kind on [`FileInfo`](/docs/sdk/filesystem#fileinfo). + +| Value | Description | +| ------------- | --------------------- | +| `Unspecified` | Type was not reported | +| `File` | Regular file | +| `Directory` | Directory | +| `Symlink` | Symbolic link | + +## ErrorKind + +Error category on the typed error. Not retry advice. See also [Errors](/docs/sdk/errors). + +| Value | Description | +| -------------------- | ------------------------------------------------------ | +| `Unknown` | Does not match a known category | +| `InvalidArgument` | The request shape is wrong | +| `Unsupported` | This provider does not implement the call or the field | +| `Authentication` | Credentials were rejected | +| `PermissionDenied` | The account cannot perform this operation | +| `NotFound` | The sandbox or file path was not found | +| `AlreadyExists` | The named resource already exists | +| `Conflict` | Current state rejects this operation | +| `RateLimited` | Provider rate limit reached | +| `ResourceExhausted` | Provider quota or capacity reached | +| `Timeout` | The deadline fired | +| `Canceled` | The caller canceled the operation | +| `Unavailable` | The provider could not be reached | +| `InvalidResponse` | The provider response cannot be used | +| `Internal` | Unclassified failure | +| `FailedPrecondition` | Client or sandbox is not in a usable state | diff --git a/website/content/docs/sdk/errors.mdx b/website/content/docs/sdk/errors.mdx new file mode 100644 index 00000000..d963f68c --- /dev/null +++ b/website/content/docs/sdk/errors.mdx @@ -0,0 +1,127 @@ +--- +title: Errors +description: Typed error fields and ErrorKind values. +--- + +Failed calls surface a typed error. Kinds are categories, not retry advice. Handling patterns: [Errors guide](/docs/errors). + +## Shape + + + + +```ts +class SandboxError extends Error { + kind: ErrorKind; + provider: string; + operation: string; + field: string; + rule?: string; + statusCode?: number; + providerCode?: string; + providerSource?: string; + // cause?: unknown — provider error when present +} +``` + +Throws from the client and sandbox handle. + +| Field | Description | +| ---------------- | -------------------------------------------------------------------------------------------- | +| `kind` | [`ErrorKind`](#errorkind) category | +| `provider` | Provider id, or empty for local failures | +| `operation` | Call name (`create`, `initialize`, `run`, …) | +| `field` | Canonical path of the rejected setting (for example `resources.cpu_cores`). Empty when none. | +| `message` | Human-readable explanation | +| `rule` | Stable validation/policy id when applicable | +| `statusCode` | HTTP/transport status when reported | +| `providerCode` | Provider's own error code when reported | +| `providerSource` | Provider component when reported | + +**Example** + +```ts +import { ErrorKind, SandboxError } from "@sandbox-kit/sandbox"; + +try { + await client.create(request); +} catch (error) { + if (!(error instanceof SandboxError)) throw error; + // error: { kind, provider, operation, field, message, rule?, + // statusCode?, providerCode?, providerSource?, cause? } + console.error(error); + if (error.kind === ErrorKind.InvalidArgument) { + // fix the field named by error.field + } +} +``` + + + + +```go +type Error struct { + Kind ErrorKind + Provider string + Operation string + Field string + Message string + StatusCode *uint32 + ProviderCode *string + ProviderSource *string + Rule *string + // Unwrap() returns the provider/validator cause when present +} +``` + +Use `errors.As(err, &detail)` to read fields. + +| Field | Description | +| ---------------- | -------------------------------------------------------------------------------------------- | +| `Kind` | [`ErrorKind`](#errorkind) category | +| `Provider` | Provider id, or empty for local failures | +| `Operation` | Call name (`create`, `initialize`, `run`, …) | +| `Field` | Canonical path of the rejected setting (for example `resources.cpu_cores`). Empty when none. | +| `Message` | Human-readable explanation | +| `Rule` | Stable validation/policy id when applicable | +| `StatusCode` | HTTP/transport status when reported | +| `ProviderCode` | Provider's own error code when reported | +| `ProviderSource` | Provider component when reported | + +**Example** + +```go +var detail *sandbox.Error +if errors.As(err, &detail) { + // detail: Kind, Provider, Operation, Field, Message, StatusCode, + // ProviderCode, ProviderSource, Rule (+ Unwrap cause) + fmt.Println(detail) + if detail.Kind == sandbox.ErrorKindInvalidArgument { + // fix the field named by detail.Field + } +} +``` + + + + +## ErrorKind + +| Kind | Description | +| -------------------- | ------------------------------------------------------ | +| `InvalidArgument` | The request shape is wrong | +| `Unsupported` | This provider does not implement the call or the field | +| `Authentication` | Credentials were rejected | +| `PermissionDenied` | The account cannot perform this operation | +| `NotFound` | The sandbox or file path was not found | +| `AlreadyExists` | The named resource already exists | +| `Conflict` | Current state rejects this operation | +| `RateLimited` | Provider rate limit reached | +| `ResourceExhausted` | Provider quota or capacity reached | +| `Timeout` | The deadline fired | +| `Canceled` | The caller canceled the operation | +| `Unavailable` | The provider could not be reached | +| `InvalidResponse` | The provider response cannot be used | +| `Internal` | Unclassified failure | +| `FailedPrecondition` | Client or sandbox is not in a usable state | +| `Unknown` | Does not match a known category | diff --git a/website/content/docs/sdk/filesystem.mdx b/website/content/docs/sdk/filesystem.mdx new file mode 100644 index 00000000..90055e93 --- /dev/null +++ b/website/content/docs/sdk/filesystem.mdx @@ -0,0 +1,486 @@ +--- +title: Filesystem +description: Read, write, copy, list, stat, and remove files in a sandbox. +--- + +Paths inside the sandbox must be absolute. Local paths for copy may be relative. Omitted timeouts use the client default. + + + Access as `sandbox.filesystem`. + Access as `instance.Filesystem()`. + + +## Methods + +### Read a file + + + + +```ts +readFile(path: string, options?: FileReadOptions & OperationOptions): Promise +``` + +Returns the bytes of the file at `path`. + +**Parameters** + +| Name | Type | Description | +| ---------- | -------------------------------------------- | --------------------- | +| `path` | `string` | Absolute sandbox path | +| `options?` | `{ timeout?: number; signal?: AbortSignal }` | Call deadline | + +**Returns** + +`Promise` — file contents + +**Example** + +```ts +const bytes = await sandbox.filesystem.readFile("/tmp/note.txt"); +console.log(new TextDecoder().decode(bytes)); +``` + + + + +```go +func (f *Filesystem) ReadFile(ctx context.Context, path string, options *FileReadOptions) ([]byte, error) +``` + +Returns the bytes of the file at `path`. + +**Parameters** + +| Name | Type | Description | +| --------- | ------------------ | ------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `path` | `string` | Absolute sandbox path | +| `options` | `*FileReadOptions` | Optional `Timeout` | + +**Returns** + +`([]byte, error)` + +**Example** + +```go +data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", nil) +``` + + + + +### Write a file + + + + +```ts +writeFile(path: string, data: Uint8Array, options?: FileWriteOptions & OperationOptions): Promise +``` + +Replaces the file at `path` and creates missing parent directories. + +**Parameters** + +| Name | Type | Description | +| ---------- | ----------------------- | --------------------- | +| `path` | `string` | Absolute sandbox path | +| `data` | `Uint8Array` | Bytes to write | +| `options?` | `{ timeout?; signal? }` | Call deadline | + +**Returns** + +`Promise` + +**Example** + +```ts +await sandbox.filesystem.writeFile( + "/tmp/note.txt", + new TextEncoder().encode("hello\n"), +); +``` + + + + +```go +func (f *Filesystem) WriteFile(ctx context.Context, path string, data []byte, options *FileWriteOptions) error +``` + +Replaces the file at `path` and creates missing parent directories. + +**Parameters** + +| Name | Type | Description | +| --------- | ------------------- | --------------------- | +| `path` | `string` | Absolute sandbox path | +| `data` | `[]byte` | Bytes to write | +| `options` | `*FileWriteOptions` | Optional `Timeout` | + +**Returns** + +`error` + +**Example** + +```go +err := instance.Filesystem().WriteFile(ctx, "/tmp/note.txt", []byte("hello\n"), nil) +``` + + + + +### Copy from local + + + + +```ts +copyFromLocal(localPath: string, remotePath: string, options?: FileCopyOptions & OperationOptions): Promise +``` + +Copies a file from the caller into the sandbox. + +**Parameters** + +| Name | Type | Description | +| ------------ | ----------------------- | ---------------------------- | +| `localPath` | `string` | Local path (may be relative) | +| `remotePath` | `string` | Absolute sandbox path | +| `options?` | `{ timeout?; signal? }` | Call deadline | + +**Returns** + +`Promise` + +**Example** + +```ts +await sandbox.filesystem.copyFromLocal("./note.txt", "/tmp/note.txt"); +``` + + + + +```go +func (f *Filesystem) CopyFromLocal(ctx context.Context, localPath, remotePath string, options *FileCopyOptions) error +``` + +Copies a file from the caller into the sandbox. + +**Parameters** + +| Name | Type | Description | +| ------------ | ------------------ | ---------------------------- | +| `localPath` | `string` | Local path (may be relative) | +| `remotePath` | `string` | Absolute sandbox path | +| `options` | `*FileCopyOptions` | Optional `Timeout` | + +**Returns** + +`error` + + + + +### Copy to local + + + + +```ts +copyToLocal(remotePath: string, localPath: string, options?: FileCopyOptions & OperationOptions): Promise +``` + +Copies a sandbox file onto the caller's disk. Missing local parents are created. An existing local directory is rejected. + +**Parameters** + +| Name | Type | Description | +| ------------ | ----------------------- | ---------------------- | +| `remotePath` | `string` | Absolute sandbox path | +| `localPath` | `string` | Local destination path | +| `options?` | `{ timeout?; signal? }` | Call deadline | + +**Returns** + +`Promise` + + + + +```go +func (f *Filesystem) CopyToLocal(ctx context.Context, remotePath, localPath string, options *FileCopyOptions) error +``` + +Copies a sandbox file onto the caller's disk. Missing local parents are created. + +**Returns** + +`error` + + + + +### Make a directory + + + + +```ts +makeDirectory(path: string, options?: MakeDirectoryOptions & OperationOptions): Promise +``` + +Creates the directory at `path`. An omitted `parents` flag is `true` (create missing parents). + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------------------------------ | ------------------------- | +| `path` | `string` | Absolute sandbox path | +| `options?` | `{ parents?: boolean; timeout?; signal? }` | Parents flag and deadline | + +**Returns** + +`Promise` + + + Daytona rejects `parents: false` before the call (field `parents`) and errors + if the directory already exists. Transfer timeouts must be whole seconds; + omit/zero passes `0` so Daytona does not apply its 30-minute upload/download + default. + + + + Modal accepts a directory that already exists. With `parents: false`, the + parent must already exist. Fractional-millisecond deadlines are allowed on + filesystem calls. + + + + + +```go +func (f *Filesystem) MakeDirectory(ctx context.Context, path string, options *MakeDirectoryOptions) error +``` + +Creates the directory at `path`. An omitted `Parents` flag is true. + +**Parameters** + +| Name | Type | Description | +| --------- | ----------------------- | ----------------------------------- | +| `options` | `*MakeDirectoryOptions` | `Timeout`, optional `Parents *bool` | + +**Returns** + +`error` + + + Daytona rejects `Parents: false` before the call (field `parents`) and errors + if the directory already exists. + + + + Modal accepts a directory that already exists. With `Parents: false`, the + parent must already exist. + + + + + +### List a directory + + + + +```ts +listDirectory(path: string, options?: ListDirectoryOptions & OperationOptions): Promise +``` + +Lists entries in one directory. + +**Returns** + +`Promise` + +**Example** + +```ts +const entries = await sandbox.filesystem.listDirectory("/tmp"); +// entries[]: { name, path, type, size, mode?, permissions?, owner?, group?, +// modifiedAt?, symlinkTarget? } +console.log(entries); +``` + + + + +```go +func (f *Filesystem) ListDirectory(ctx context.Context, path string, options *ListDirectoryOptions) ([]*FileInfo, error) +``` + +Lists entries in one directory. + +**Returns** + +`([]*FileInfo, error)` + +**Example** + +```go +entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp", nil) +// each entry: Name, Path, Type, Size, Mode, Permissions, Owner, Group, ModifiedAt, SymlinkTarget +fmt.Println(entries) +``` + + + + +### Stat a path + + + + +```ts +stat(path: string, options?: StatOptions & OperationOptions): Promise +``` + +Returns metadata for one path. A symlink result describes the symlink. + +**Returns** + +`Promise` + +**Example** + +```ts +const info = await sandbox.filesystem.stat("/tmp/note.txt"); +// info: { name, path, type, size, mode?, permissions?, owner?, group?, +// modifiedAt?, symlinkTarget? } +console.log(info); +``` + + + + +```go +func (f *Filesystem) Stat(ctx context.Context, path string, options *StatOptions) (*FileInfo, error) +``` + +Returns metadata for one path. A symlink result describes the symlink. + +**Returns** + +`(*FileInfo, error)` + +**Example** + +```go +info, err := instance.Filesystem().Stat(ctx, "/tmp/note.txt", nil) +// info: Name, Path, Type, Size, Mode, Permissions, Owner, Group, ModifiedAt, SymlinkTarget +fmt.Println(info) +``` + + + + +### Remove a path + + + + +```ts +remove(path: string, options?: RemoveOptions & OperationOptions): Promise +``` + +Removes a file, or a directory when `recursive` is set. An omitted `recursive` flag removes a file or an empty directory. + +**Parameters** + +| Name | Type | Description | +| ---------- | -------------------------------------------- | --------------------------- | +| `path` | `string` | Absolute sandbox path | +| `options?` | `{ recursive?: boolean; timeout?; signal? }` | Recursive flag and deadline | + +**Returns** + +`Promise` + +**Example** + +```ts +await sandbox.filesystem.remove("/tmp/dir", { recursive: true }); +``` + + + + +```go +func (f *Filesystem) Remove(ctx context.Context, path string, options *RemoveOptions) error +``` + +Removes a file, or a directory when `Recursive` is set. + +**Parameters** + +| Name | Type | Description | +| --------- | ---------------- | ------------------------------------- | +| `options` | `*RemoveOptions` | `Timeout`, optional `Recursive *bool` | + +**Returns** + +`error` + +**Example** + +```go +err := instance.Filesystem().Remove(ctx, "/tmp/dir", &sandbox.RemoveOptions{ + Recursive: sandbox.Value(true), +}) +``` + + + + +## FileInfo + +Metadata for one file, directory, or symlink. + + + + +| Field | Description | +| --------------- | ------------------------------------------------------------------- | +| `name` | Base name | +| `path` | Absolute path in the sandbox | +| `type` | [`FileType`](/docs/sdk/enums#filetype): file, directory, or symlink | +| `size` | Size in bytes | +| `mode` | Unix mode string when reported (for example `0644`) | +| `permissions` | Symbolic permissions when reported | +| `owner` | Ownership when reported | +| `group` | Group when reported | +| `modifiedAt` | Last modification time when reported | +| `symlinkTarget` | Target path when the entry is a symlink | + + + + +| Field | Description | +| --------------- | ------------------------------------------------------------------- | +| `Name` | Base name | +| `Path` | Absolute path in the sandbox | +| `Type` | [`FileType`](/docs/sdk/enums#filetype): file, directory, or symlink | +| `Size` | Size in bytes | +| `Mode` | Unix mode string when reported (for example `0644`) | +| `Permissions` | Symbolic permissions when reported | +| `Owner` | Ownership when reported | +| `Group` | Group when reported | +| `ModifiedAt` | Last modification time when reported | +| `SymlinkTarget` | Target path when the entry is a symlink | + + + diff --git a/website/content/docs/sdk/index.mdx b/website/content/docs/sdk/index.mdx new file mode 100644 index 00000000..025fce78 --- /dev/null +++ b/website/content/docs/sdk/index.mdx @@ -0,0 +1,33 @@ +--- +title: Overview +description: User-facing types and methods for the Sandbox Kit core SDK. +--- + +Public surface of the core sandbox package. Guides show how pieces fit together; this section documents signatures, parameters, and return values. + +Use the language tabs on each page. Unsupported calls return a typed error—see [Errors](/docs/sdk/errors) and the [Errors guide](/docs/errors). + + + + Package: `@sandbox-kit/sandbox`. Durations and timeouts are + **milliseconds**. + + + Package: `github.com/sandbox-kit/kit/sdks/go/sandbox`. Durations use + `sandbox.Duration`. Optional scalars use `sandbox.Value`. + + + +## Modules + +| Name | Description | +| ----------------------------------------- | ------------------------------------------------------------ | +| [Client](/docs/sdk/client) | Construct a provider connection; create and open sandboxes | +| [Sandbox](/docs/sdk/sandbox) | Handle for one sandbox: lifecycle, info, filesystem, process | +| [Filesystem](/docs/sdk/filesystem) | Read, write, copy, list, stat, and remove files | +| [Process](/docs/sdk/process) | Run a command and collect exit code and output | +| [Client config](/docs/sdk/configuration) | Client config, auth, and scope types | +| [CreateOptions](/docs/sdk/create-options) | Create request body and nested option types | +| [SandboxInfo](/docs/sdk/sandbox-info) | Metadata returned by info and lifecycle calls | +| [Errors](/docs/sdk/errors) | Typed error shape and kinds | +| [Enums](/docs/sdk/enums) | Shared enums (`SandboxState`, `PolicyMode`, …) | diff --git a/website/content/docs/sdk/meta.json b/website/content/docs/sdk/meta.json new file mode 100644 index 00000000..9d983b6b --- /dev/null +++ b/website/content/docs/sdk/meta.json @@ -0,0 +1,15 @@ +{ + "title": "SDK", + "pages": [ + "index", + "client", + "sandbox", + "filesystem", + "process", + "configuration", + "create-options", + "sandbox-info", + "errors", + "enums" + ] +} diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx new file mode 100644 index 00000000..bc0eac08 --- /dev/null +++ b/website/content/docs/sdk/process.mdx @@ -0,0 +1,148 @@ +--- +title: Process +description: Run a command in a sandbox and collect exit code and output. +--- + +A non-zero exit code is **not** an error. Set exactly one of argv or shell. + + + + Access as `sandbox.process`. Set exactly one of `argv` or `shell`. + + + Access as `instance.Process()`. Set exactly one of `Argv` or `Shell`. + + + +## Methods + +### Run a command + + + + +```ts +run(options: ExecOptions & OperationOptions): Promise +``` + +Runs one command and waits for it to finish. + +**Parameters** + +| Name | Type | Description | +| --------------------------- | ------------------------ | ------------------------------------------------------- | +| `options.argv?` | `string[]` | Program and arguments. Mutually exclusive with `shell`. | +| `options.shell?` | `string` | Shell command string. Mutually exclusive with `argv`. | +| `options.workingDirectory?` | `string` | Absolute working directory inside the sandbox | +| `options.environment?` | `Record` | Env vars for this command only | +| `options.timeout?` | `number` | Kit + command deadline (ms); whole seconds required | +| `options.signal?` | `AbortSignal` | Abort the wait | + +**Returns** + +[`Promise`](#execresult) + +**Example** + +```ts +const result = await sandbox.process.run({ + argv: ["echo", "hello"], + workingDirectory: "/tmp", + environment: { KIT_DEMO: "from-kit" }, + timeout: 5_000, +}); +// result: { exitCode, stdout, stderr } +console.log(result); +``` + + + + +```go +func (p *Process) Run(ctx context.Context, options *ExecOptions) (*ExecResult, error) +``` + +Runs one command and waits for it to finish. + +**Parameters** + +| Name | Type | Description | +| -------------------------- | ---------------------- | ------------------------------------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `options.Argv` | `[]string` | Program and arguments. Mutually exclusive with `Shell`. | +| `options.Shell` | `*string` | Shell command string | +| `options.WorkingDirectory` | `*string` | Absolute working directory | +| `options.Environment` | `map[string]string` | Env vars for this command only | +| `options.Timeout` | `*durationpb.Duration` | Via `sandbox.Duration`; whole seconds | + +**Returns** + +[`(*ExecResult, error)`](#execresult) + +**Example** + +```go +result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "hello"}, + WorkingDirectory: sandbox.Value("/tmp"), + Environment: map[string]string{"KIT_DEMO": "from-kit"}, + Timeout: sandbox.Duration(5 * time.Second), +}) +if err != nil { + return err +} +// result: ExitCode, Stdout, Stderr +fmt.Println(result) +``` + + + + +## ExecResult + +Outcome of a finished command. + + + + +| Field | Description | +| ---------- | ------------------------------- | +| `exitCode` | Process exit status | +| `stdout` | Collected standard output bytes | +| `stderr` | Collected standard error bytes | + + + Daytona returns stdout only; `stderr` is empty (stdout may include merged + streams). `argv` is sent as a POSIX-quoted shell string. Omit/zero timeout + sends `0` so Daytona does not apply its 10s command default. + + + + Modal returns both `stdout` and `stderr`. `argv` is passed as-is; `shell` runs + via `bash -c`. Omit/zero timeout omits Modal's `timeoutMs` (Modal rejects an + explicit 0). + + + + + +| Field | Description | +| ---------- | ------------------------------- | +| `ExitCode` | Process exit status | +| `Stdout` | Collected standard output bytes | +| `Stderr` | Collected standard error bytes | + + + Daytona returns Stdout only; Stderr is empty (Stdout may include merged + streams). Argv is sent as a POSIX-quoted shell string. Omit/zero Timeout sends + 0 so Daytona does not apply its 10s command default. + + + + Modal returns both Stdout and Stderr. Argv is passed as-is; Shell runs via + `bash -c`. Omit/zero Timeout omits Modal's timeoutMs (Modal rejects an + explicit 0). + + + + diff --git a/website/content/docs/sdk/sandbox-info.mdx b/website/content/docs/sdk/sandbox-info.mdx new file mode 100644 index 00000000..47fbe0de --- /dev/null +++ b/website/content/docs/sdk/sandbox-info.mdx @@ -0,0 +1,95 @@ +--- +title: SandboxInfo +description: Metadata returned by info and lifecycle calls. +--- + +Returned by info and many lifecycle methods. Values are a **copy**; mutating them does not change the handle. Reported resources can differ from what you requested. + + + + From [`sandbox.info()`](/docs/sdk/sandbox#read-metadata). + + + From [`instance.Info()`](/docs/sdk/sandbox#read-metadata). + + + +## Fields + + + + +| Field | Description | +| ------------------ | ------------------------------------------------------- | +| `id` | Sandbox identifier | +| `provider` | Provider identifier | +| `name` | Display name when reported | +| `providerState` | Raw state string from the cloud | +| `region` | Region when reported | +| `isolation` | Reported isolation config | +| `resources` | Reported size (`cpuCores`, `memoryMib`, …) | +| `labels` | Labels when reported | +| `createdAt` | Creation time when reported | +| `providerMetadata` | Extra provider metadata object | +| `state` | Portable [`SandboxState`](/docs/sdk/enums#sandboxstate) | +| `terminal` | Whether the lifecycle is permanently complete | + +**Example** + +```ts +const info = sandbox.info(); +// info: { id, provider, name?, providerState?, region?, isolation?, +// resources?, labels, createdAt?, providerMetadata?, state, terminal? } +console.log(info); +``` + + + + +| Field | Description | +| ------------------ | ------------------------------------------------------- | +| `Id` | Sandbox identifier | +| `Provider` | Provider identifier | +| `Name` | Display name when reported | +| `ProviderState` | Raw state string from the cloud | +| `Region` | Region when reported | +| `Isolation` | Reported isolation config | +| `Resources` | Reported size (`CpuCores`, `MemoryMib`, …) | +| `Labels` | Labels when reported | +| `CreatedAt` | Creation time when reported | +| `ProviderMetadata` | Extra provider metadata object | +| `State` | Portable [`SandboxState`](/docs/sdk/enums#sandboxstate) | +| `Terminal` | Whether the lifecycle is permanently complete | + +**Example** + +```go +info := instance.Info() +// info: Id, Provider, Name, ProviderState, Region, Isolation, +// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal +fmt.Println(info) +``` + + + + +## ExitResult + +Returned by terminate. + + + + +| Field | Description | +| ---------- | -------------------------------------------------------------------------------- | +| `exitCode` | Process exit code when the call waited for completion. Absent when not reported. | + + + + +| Field | Description | +| ---------- | -------------------------------------------------------------------------------- | +| `ExitCode` | Process exit code when the call waited for completion. Absent when not reported. | + + + diff --git a/website/content/docs/sdk/sandbox.mdx b/website/content/docs/sdk/sandbox.mdx new file mode 100644 index 00000000..0b1173b5 --- /dev/null +++ b/website/content/docs/sdk/sandbox.mdx @@ -0,0 +1,568 @@ +--- +title: Sandbox +description: Handle for one sandbox — lifecycle, info, filesystem, and process. +--- + +Handle from [`Client.create`](/docs/sdk/client#create), [`get`](/docs/sdk/client#get), or [`attach`](/docs/sdk/client#attach). Lifecycle methods that return info also update the handle. `close` only drops local resources—the cloud sandbox keeps running. Call `supports` before a lifecycle method that might be unavailable. + +## Properties + + + + +| Name | Type | Description | +| -------------- | ------------------------------------ | -------------------------------- | +| `id` | `string` | Sandbox identifier | +| `providerName` | `string` | Provider that owns this sandbox | +| `filesystem` | [`Filesystem`](/docs/sdk/filesystem) | File operations for this sandbox | +| `process` | [`Process`](/docs/sdk/process) | Command runner for this sandbox | + + + + +| Name | Signature | Description | +| -------------- | --------------------- | -------------------------------- | +| `ID` | `func () string` | Sandbox identifier | +| `ProviderName` | `func () string` | Provider that owns this sandbox | +| `Filesystem` | `func () *Filesystem` | File operations for this sandbox | +| `Process` | `func () *Process` | Command runner for this sandbox | + + + + +## Methods + +### Read metadata + + + + +```ts +info(): SandboxInfo +``` + +Returns a **copy** of current metadata. Mutating it does not change the handle. + +**Returns** + +[`SandboxInfo`](/docs/sdk/sandbox-info) + +**Example** + +```ts +const info = sandbox.info(); +// info: { id, provider, name?, providerState?, region?, isolation?, +// resources?, labels, createdAt?, providerMetadata?, state, terminal? } +console.log(info); +``` + + + + +```go +func (s *Sandbox) Info() *SandboxInfo +``` + +Returns a **copy** of current metadata. Mutating it does not change the handle. + +**Returns** + +[`*SandboxInfo`](/docs/sdk/sandbox-info) + +**Example** + +```go +info := instance.Info() +// info: Id, Provider, Name, ProviderState, Region, Isolation, +// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal +fmt.Println(info) +``` + + + + +### Capabilities + + + + +```ts +capabilities(): readonly LifecycleCapability[] +supports(capability: LifecycleCapability): boolean +``` + +Lists lifecycle operations this handle can attempt, or checks one. File and process calls are not capabilities—use `filesystem` and `process`. + +**Parameters** + +| Name | Type | Description | +| ------------ | ------------------------------------------------------------ | ------------------------------------------- | +| `capability` | [`LifecycleCapability`](/docs/sdk/enums#lifecyclecapability) | Capability to test (`Stop`, `Terminate`, …) | + +**Returns** + +Capability list, or `boolean` for `supports` + +**Example** + + + +```ts +if (!sandbox.supports(LifecycleCapability.Stop)) { + throw new Error(`cannot stop ${sandbox.id}`); +} +``` + + + + + +```ts +if (!sandbox.supports(LifecycleCapability.Terminate)) { + throw new Error(`cannot terminate ${sandbox.id}`); +} +``` + + + + + + +```go +func (s *Sandbox) Capabilities() []LifecycleCapability +func (s *Sandbox) Supports(capability LifecycleCapability) bool +``` + +Lists lifecycle operations this handle can attempt, or checks one. + +**Parameters** + +| Name | Type | Description | +| ------------ | ------------------------------------------------------------ | ------------------ | +| `capability` | [`LifecycleCapability`](/docs/sdk/enums#lifecyclecapability) | Capability to test | + +**Returns** + +Capability slice, or `bool` for `Supports` + +**Example** + + + +```go +if !instance.Supports(sandbox.LifecycleCapabilityStop) { + return fmt.Errorf("cannot stop %s", instance.ID()) +} +``` + + + + + +```go +if !instance.Supports(sandbox.LifecycleCapabilityTerminate) { + return fmt.Errorf("cannot terminate %s", instance.ID()) +} +``` + + + + + + + + +### Refresh + + + + +```ts +refresh(options?: OperationOptions): Promise +``` + +Reloads metadata from the provider into this handle. + +**Parameters** + +| Name | Type | Description | +| ---------- | ---------------------------------------- | ----------------------- | +| `options?` | [`OperationOptions`](#operation-options) | Call timeout and signal | + +**Returns** + +`Promise` — updated metadata (also stored on the handle) + + + + +```go +func (s *Sandbox) Refresh(ctx context.Context) error +``` + +Reloads metadata from the provider into this handle. Read it with `Info()` afterward. + +**Parameters** + +| Name | Type | Description | +| ----- | ----------------- | ------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | + +**Returns** + +`error` + + + + +### Start + + + + +```ts +start(options?: OperationOptions): Promise +``` + +Starts a stopped sandbox and stores the updated metadata. + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------ | ----------------------- | +| `options?` | `OperationOptions` | Call timeout and signal | + +**Returns** + +`Promise` + + + + +```go +func (s *Sandbox) Start(ctx context.Context, options *StartOptions) error +``` + +Starts a stopped sandbox and stores the updated metadata. + +**Parameters** + +| Name | Type | Description | +| --------- | ----------------- | ----------------------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `options` | `*StartOptions` | Optional `Timeout` via `sandbox.Duration` | + +**Returns** + +`error` + + + + +### Stop + + + + +```ts +stop(options?: StopCallOptions): Promise +``` + +Stops a sandbox. Set `force: true` for an immediate stop. + +**Parameters** + +| Name | Type | Description | +| ---------- | ----------------- | -------------------------------------------------- | +| `options?` | `StopCallOptions` | `OperationOptions` plus optional `force?: boolean` | + +**Returns** + +`Promise` + +**Example** + +```ts +await sandbox.stop({ force: true, timeout: 60_000 }); // call deadline (ms) +``` + + + + +```go +func (s *Sandbox) Stop(ctx context.Context, options *StopOptions) error +``` + +Stops a sandbox. Set `Force` with `Value(true)` for an immediate stop. + +**Parameters** + +| Name | Type | Description | +| --------- | ----------------- | --------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `options` | `*StopOptions` | `Timeout`, optional `Force` | + +**Returns** + +`error` + +**Example** + +```go +err := instance.Stop(ctx, &sandbox.StopOptions{ + Force: sandbox.Value(true), + Timeout: sandbox.Duration(time.Minute), // call deadline +}) +``` + + + + +### Pause + + + + +```ts +pause(options?: OperationOptions): Promise +``` + +Pauses a sandbox and stores the updated metadata. + +**Returns** + +`Promise` + + + + +```go +func (s *Sandbox) Pause(ctx context.Context, options *PauseOptions) error +``` + +Pauses a sandbox and stores the updated metadata. + +**Returns** + +`error` + + + + +### Archive + + + + +```ts +archive(options?: OperationOptions): Promise +``` + +Archives retained sandbox state and stores the updated metadata. + +**Returns** + +`Promise` + + + + +```go +func (s *Sandbox) Archive(ctx context.Context, options *ArchiveOptions) error +``` + +Archives retained sandbox state and stores the updated metadata. + +**Returns** + +`error` + + + + +### Delete + + + + +```ts +delete(options?: DeleteCallOptions): Promise +``` + +Deletes the cloud sandbox. Set `wait: true` to wait until deletion finishes. Unlike `close`, this removes the sandbox. + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------- | ------------------------------------------------- | +| `options?` | `DeleteCallOptions` | `OperationOptions` plus optional `wait?: boolean` | + +**Returns** + +`Promise` + + + + +```go +func (s *Sandbox) Delete(ctx context.Context, options *DeleteOptions) error +``` + +Deletes the cloud sandbox. Set `Wait` to wait until deletion finishes. + +**Parameters** + +| Name | Type | Description | +| --------- | ----------------- | -------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `options` | `*DeleteOptions` | `Timeout`, optional `Wait` | + +**Returns** + +`error` + + + + +If you omit the timeout (or pass `0`), Daytona still uses its own **60s** default for create, start, stop, pause, and delete. Pass a positive whole-second timeout to override. Fractional seconds are rejected. + + + + + +### Terminate + + + + +```ts +terminate(options?: TerminateCallOptions): Promise +``` + +Ends the sandbox. With `wait: true`, the result may include `exitCode`. + +**Parameters** + +| Name | Type | Description | +| ---------- | ---------------------- | ------------------------------------------------- | +| `options?` | `TerminateCallOptions` | `OperationOptions` plus optional `wait?: boolean` | + +**Returns** + +`Promise` — `{ exitCode?: number }` + +**Example** + +```ts +const result = await sandbox.terminate({ wait: true, timeout: 60_000 }); +// result: { exitCode? } — timeout is how long this call may wait +console.log(result); +``` + + + + +```go +func (s *Sandbox) Terminate(ctx context.Context, options *TerminateOptions) (*ExitResult, error) +``` + +Ends the sandbox. With `Wait`, the result may include an exit code. + +**Parameters** + +| Name | Type | Description | +| --------- | ------------------- | -------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `options` | `*TerminateOptions` | `Timeout`, optional `Wait` | + +**Returns** + +`(*ExitResult, error)` — `ExitCode *int32` when reported + +**Example** + +```go +result, err := instance.Terminate(ctx, &sandbox.TerminateOptions{ + Wait: sandbox.Value(true), + Timeout: sandbox.Duration(time.Minute), // how long this call may wait +}) +// result: ExitCode +fmt.Println(result) +``` + + + + +`timeout` / `Timeout` caps how long this call waits. It is not sent to Modal as a separate terminate setting. + + + +### Close + + + + +```ts +close(options?: OperationOptions): Promise +``` + +Releases local handle resources. The cloud sandbox is unchanged; you can keep using the handle afterward for provider calls until the sandbox is gone. + +**Returns** + +`Promise` + + + + +```go +func (s *Sandbox) Close(ctx context.Context) error +``` + +Releases local handle resources. The cloud sandbox is unchanged. + +**Returns** + +`error` + + + + +## Operation options + +Shared call options for TypeScript lifecycle and filesystem/process calls. + + + + +```ts +interface OperationOptions { + signal?: AbortSignal; + timeout?: number; // milliseconds +} + +type StopCallOptions = OperationOptions & { force?: boolean }; +type TerminateCallOptions = OperationOptions & { wait?: boolean }; +type DeleteCallOptions = OperationOptions & { wait?: boolean }; +``` + +| Field | Description | +| --------- | ---------------------------------------------------------------------------- | +| `signal` | Abort the wait. The handle stays busy until the in-flight call finishes. | +| `timeout` | Deadline for this call (ms). Omitted → client default. `0` → no deadline. | +| `force` | On stop: request an immediate stop. | +| `wait` | On terminate/delete: wait until the operation finishes. | + + + + +Lifecycle option messages carry `Timeout *durationpb.Duration` (via `sandbox.Duration`). `StopOptions` adds `Force *bool`. `TerminateOptions` and `DeleteOptions` add `Wait *bool`. + +| Field | Description | +| --------- | -------------------------------------------------------------------------- | +| `Timeout` | Deadline for this call. Nil → client default. Explicit zero → no deadline. | +| `Force` | On stop: `Value(true)` requests an immediate stop. | +| `Wait` | On terminate/delete: wait until finished. | + + + diff --git a/website/package-lock.json b/website/package-lock.json index 90191166..e74fe3d9 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -17,8 +17,7 @@ "fumadocs-ui": "npm:@fumadocs/base-ui@16.17.9", "lucide-react": "^1.61.0", "react": "^19.3.1", - "react-dom": "^19.3.1", - "takumi-js": "^2.17.1" + "react-dom": "^19.3.1" }, "devDependencies": { "@tailwindcss/vite": "^4.3.3", @@ -1625,217 +1624,6 @@ "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, - "node_modules/@takumi-rs/core": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core/-/core-2.19.0.tgz", - "integrity": "sha512-cYxOTb4pOu25Y8GBm3JTyIwenHNAZF11pxEmTxA3Q7VabC0pqeWDo/nOjTc2slTHORaglkvwK4SMmnpzab5BjQ==", - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "@takumi-rs/helpers": "2.19.0" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@takumi-rs/core-darwin-arm64": "2.19.0", - "@takumi-rs/core-darwin-x64": "2.19.0", - "@takumi-rs/core-linux-arm64-gnu": "2.19.0", - "@takumi-rs/core-linux-arm64-musl": "2.19.0", - "@takumi-rs/core-linux-x64-gnu": "2.19.0", - "@takumi-rs/core-linux-x64-musl": "2.19.0", - "@takumi-rs/core-win32-arm64-msvc": "2.19.0", - "@takumi-rs/core-win32-x64-msvc": "2.19.0" - }, - "peerDependencies": { - "csstype": "*" - }, - "peerDependenciesMeta": { - "csstype": { - "optional": true - } - } - }, - "node_modules/@takumi-rs/core-darwin-arm64": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core-darwin-arm64/-/core-darwin-arm64-2.19.0.tgz", - "integrity": "sha512-W/SI7Zrxo70dIaKNo+/kOLVr97S3ezkqsi7OM0RWz2CtP+cFgdBTPHXNsrB561gJFPHsuWN/LZ0u8oZxeAYtGw==", - "cpu": [ - "arm64" - ], - "license": "(MIT OR Apache-2.0)", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@takumi-rs/core-darwin-x64": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core-darwin-x64/-/core-darwin-x64-2.19.0.tgz", - "integrity": "sha512-xO/eTwPLrfUZRybMnG4lb6fDa7BjFryFi1TSkRF5KjPhkv21Lr5ovFEYz1y2P5k/4psrk66FJbU2EJDOEjdXiw==", - "cpu": [ - "x64" - ], - "license": "(MIT OR Apache-2.0)", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@takumi-rs/core-linux-arm64-gnu": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core-linux-arm64-gnu/-/core-linux-arm64-gnu-2.19.0.tgz", - "integrity": "sha512-0PvL5bNTZTkLExjKveI3pOz3mtfjJtECqK49f/KGnp7J0bBmdoi2muszCh/dQQEEFKXcWNe27yfK1tzsO5fm0g==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "(MIT OR Apache-2.0)", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@takumi-rs/core-linux-arm64-musl": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core-linux-arm64-musl/-/core-linux-arm64-musl-2.19.0.tgz", - "integrity": "sha512-nwKZMGcNVdmCP+U7+uINtXpgOeyk5m7g5D9EAtttpWc6ImjJm7MaqNK4Q2YBkOFeAIbRkY5dDL8Xcu19ATSj0w==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "(MIT OR Apache-2.0)", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@takumi-rs/core-linux-x64-gnu": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core-linux-x64-gnu/-/core-linux-x64-gnu-2.19.0.tgz", - "integrity": "sha512-gf/U6qxWZk6uX5MFnl5/PTOzxBbISj89UQwbliHa1sflcq2d+SeITDqnGtkUueF/4TlgPVV7JvrPuhAbINy2Gg==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "(MIT OR Apache-2.0)", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@takumi-rs/core-linux-x64-musl": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core-linux-x64-musl/-/core-linux-x64-musl-2.19.0.tgz", - "integrity": "sha512-zHD0EdIA2ZMCnKV/KtMgEerUcdvjYU4hGiru9I/x96u+BJXaQGMtHCxXt9Srhj+dlqhPyWdQTMAE3wpflN+0nw==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "(MIT OR Apache-2.0)", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@takumi-rs/core-win32-arm64-msvc": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core-win32-arm64-msvc/-/core-win32-arm64-msvc-2.19.0.tgz", - "integrity": "sha512-v1CrPsmzhIe0+v+kIAdaSwrNMqiOpUXgMxHLUo0xDQdPMNML0EefDsL9ADtKDpT1pjhY/r9tvwVl3RhKsdottw==", - "cpu": [ - "arm64" - ], - "license": "(MIT OR Apache-2.0)", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@takumi-rs/core-win32-x64-msvc": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/core-win32-x64-msvc/-/core-win32-x64-msvc-2.19.0.tgz", - "integrity": "sha512-9HJb2LTDSlehFrGfPw+uKgxM3XCnpy/PYnwtyzcgAiSFJVjwv0zCLl3IvJREakj5Y9rJCg0mMtPje9hlPbJL/A==", - "cpu": [ - "x64" - ], - "license": "(MIT OR Apache-2.0)", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@takumi-rs/helpers": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/helpers/-/helpers-2.19.0.tgz", - "integrity": "sha512-3ycCjYCP31HACXBvlpv4fMLw9vneCVGuttFBfZr4jpL0nHNCt2wlXvt0XFt05FscLMUY2s+5PGKPE6EatXI/mg==", - "license": "(MIT OR Apache-2.0)", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "preact": "^10.0.0 || ^11.0.0", - "react": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "preact": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/@takumi-rs/wasm": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@takumi-rs/wasm/-/wasm-2.19.0.tgz", - "integrity": "sha512-V9LKPOFVHKZ7lANVQ94WmOkGvgW0EqXUeLEpsWB2Wnnx7WpOWHnbAI9Yb0VnAYeyRicp7nAfG3vF/M7vsw0s2w==", - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "@takumi-rs/helpers": "2.19.0" - }, - "engines": { - "node": ">=20.19" - }, - "peerDependencies": { - "csstype": "*" - }, - "peerDependenciesMeta": { - "csstype": { - "optional": true - } - } - }, "node_modules/@tanstack/history": { "version": "1.162.5", "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.5.tgz", @@ -6683,20 +6471,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/takumi-js": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/takumi-js/-/takumi-js-2.19.0.tgz", - "integrity": "sha512-WUgzukIpAtoDUeFkdKvT0o4pterWoyM9qcXpIRUQIqUZHriIBdPdPZV42UyzEbNyXvg/1aUaVhBGEyivraT8NQ==", - "license": "(MIT OR Apache-2.0)", - "dependencies": { - "@takumi-rs/core": "2.19.0", - "@takumi-rs/helpers": "2.19.0", - "@takumi-rs/wasm": "2.19.0" - }, - "engines": { - "node": ">=20.19" - } - }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", diff --git a/website/package.json b/website/package.json index 839c1ea0..43190e18 100644 --- a/website/package.json +++ b/website/package.json @@ -23,8 +23,7 @@ "fumadocs-ui": "npm:@fumadocs/base-ui@16.17.9", "lucide-react": "^1.61.0", "react": "^19.3.1", - "react-dom": "^19.3.1", - "takumi-js": "^2.17.1" + "react-dom": "^19.3.1" }, "devDependencies": { "@tailwindcss/vite": "^4.3.3", diff --git a/website/scripts/publish-pages.mjs b/website/scripts/publish-pages.mjs index 53c23656..a2d3dbe6 100644 --- a/website/scripts/publish-pages.mjs +++ b/website/scripts/publish-pages.mjs @@ -12,15 +12,17 @@ const paths = [ "/llms-full.txt", ...docFiles .filter((name) => name.endsWith(".mdx")) - .flatMap((name) => { + .map((name) => { const slug = name.replace(/\.mdx$/, ""); const page = slug === "index" ? "" : `/${slug}`; - return [`/docs${page === "" ? "/index.md" : `${page}.md`}`, `/og/docs${page}/image.webp`]; + return `/docs${page === "" ? "/index.md" : `${page}.md`}`; }), ]; for (const path of paths) { - const response = await server.fetch(new Request(`http://localhost${base}${path}`)); + const response = await server.fetch( + new Request(`http://localhost${base}${path}`), + ); if (!response.ok) { throw new Error(`${path} returned ${response.status}`); } diff --git a/website/src/components/brand-mark.tsx b/website/src/components/brand-mark.tsx new file mode 100644 index 00000000..f601a895 --- /dev/null +++ b/website/src/components/brand-mark.tsx @@ -0,0 +1,34 @@ +import { cn } from "@/lib/cn"; +import { LogoMark } from "@/components/logo-mark"; + +/** Nav / sidebar wordmark — white/black cube badge + lowercase name. */ +export function BrandMark({ + className, + logoClassName, + showLogo = true, +}: { + className?: string; + logoClassName?: string; + showLogo?: boolean; +}) { + return ( + + {showLogo ? ( + + ) : null} + + sandbox-kit + + + ); +} diff --git a/website/src/components/language-context.tsx b/website/src/components/language-context.tsx new file mode 100644 index 00000000..0f310fe5 --- /dev/null +++ b/website/src/components/language-context.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { + createContext, + useCallback, + useContext, + useLayoutEffect, + useMemo, + useState, + type ReactNode, +} from "react"; +import { + DEFAULT_DOC_LANGUAGE, + DOC_LANGUAGE_STORAGE_KEY, + DOC_LANGUAGES, + isDocLanguageId, + type DocLanguageId, +} from "@/lib/languages"; + +type LanguageContextValue = { + language: DocLanguageId; + setLanguage: (id: DocLanguageId) => void; + languages: typeof DOC_LANGUAGES; +}; + +const LanguageContext = createContext(null); + +function readStoredLanguage(): DocLanguageId { + if (typeof window === "undefined") return DEFAULT_DOC_LANGUAGE; + try { + const stored = + localStorage.getItem(DOC_LANGUAGE_STORAGE_KEY) ?? + sessionStorage.getItem(DOC_LANGUAGE_STORAGE_KEY); + if (stored && isDocLanguageId(stored)) return stored; + } catch { + // ignore + } + return DEFAULT_DOC_LANGUAGE; +} + +export function LanguageProvider({ children }: { children: ReactNode }) { + const [language, setLanguageState] = + useState(DEFAULT_DOC_LANGUAGE); + + useLayoutEffect(() => { + setLanguageState(readStoredLanguage()); + }, []); + + const setLanguage = useCallback((id: DocLanguageId) => { + setLanguageState(id); + try { + sessionStorage.setItem(DOC_LANGUAGE_STORAGE_KEY, id); + localStorage.setItem(DOC_LANGUAGE_STORAGE_KEY, id); + } catch { + // ignore + } + }, []); + + const value = useMemo( + () => ({ + language, + setLanguage, + languages: DOC_LANGUAGES, + }), + [language, setLanguage], + ); + + return ( + + {children} + + ); +} + +export function useDocLanguage(): LanguageContextValue { + const ctx = useContext(LanguageContext); + if (!ctx) { + throw new Error("useDocLanguage must be used within LanguageProvider"); + } + return ctx; +} + +export function useDocLanguageOptional(): LanguageContextValue | null { + return useContext(LanguageContext); +} + +export function resolveDocLanguageId(value: string): DocLanguageId | null { + const normalized = value.toLowerCase().replace(/\s+/g, "-"); + if (isDocLanguageId(normalized)) return normalized; + return null; +} + +/** Show children only for the active docs language. No language switcher. */ +export function ForLanguage({ + id, + children, +}: { + id: DocLanguageId | DocLanguageId[] | string | string[]; + children: ReactNode; +}) { + const { language } = useDocLanguage(); + const raw = Array.isArray(id) ? id : [id]; + const ids = raw + .map((item) => resolveDocLanguageId(String(item))) + .filter((item): item is DocLanguageId => item !== null); + if (!ids.includes(language)) return null; + return children; +} diff --git a/website/src/components/logo-mark.tsx b/website/src/components/logo-mark.tsx new file mode 100644 index 00000000..ffa49b77 --- /dev/null +++ b/website/src/components/logo-mark.tsx @@ -0,0 +1,37 @@ +import { cn } from "@/lib/cn"; + +/** White square badge with a black isometric cube. */ +export function LogoMark({ + className, + title, +}: { + className?: string; + title?: string; +}) { + return ( + + {title ? {title} : null} + + {/* Cube sized like the original mark — room around it on the white badge */} + + + + + + ); +} diff --git a/website/src/components/mdx.tsx b/website/src/components/mdx.tsx index ea0c5b3f..8e304c07 100644 --- a/website/src/components/mdx.tsx +++ b/website/src/components/mdx.tsx @@ -1,5 +1,7 @@ import defaultMdxComponents from "fumadocs-ui/mdx"; import type { MDXComponents } from "mdx/types"; +import { ForLanguage } from "@/components/language-context"; +import { Note } from "@/components/note"; import { ForProvider } from "@/components/provider-context"; import { Tab, Tabs } from "@/components/tabs"; @@ -9,6 +11,8 @@ export function getMDXComponents(components?: MDXComponents) { Tabs, Tab, ForProvider, + ForLanguage, + Note, ...components, } satisfies MDXComponents; } diff --git a/website/src/components/note.tsx b/website/src/components/note.tsx new file mode 100644 index 00000000..eae323e2 --- /dev/null +++ b/website/src/components/note.tsx @@ -0,0 +1,29 @@ +"use client"; + +import type { ReactNode } from "react"; +import { cn } from "@/lib/cn"; + +/** + * Compact language/provider-aware note. Content swaps with the page language + * tabs and provider select — this box has no switchers of its own. + */ +export function Note({ + children, + className, +}: { + children: ReactNode; + className?: string; +}) { + return ( + + ); +} diff --git a/website/src/components/tabs.tsx b/website/src/components/tabs.tsx index ee7f258c..081d4da8 100644 --- a/website/src/components/tabs.tsx +++ b/website/src/components/tabs.tsx @@ -16,6 +16,11 @@ import { TabsTrigger as RootTabsTrigger, } from "fumadocs-ui/components/ui/tabs"; import { cn } from "@/lib/cn"; +import { DOC_LANGUAGE_STORAGE_KEY } from "@/lib/languages"; +import { + resolveDocLanguageId, + useDocLanguageOptional, +} from "@/components/language-context"; type TabsContextValue = { items?: string[]; @@ -49,10 +54,16 @@ export function Tabs({ label, defaultIndex = 0, defaultValue = items ? escapeValue(items[defaultIndex]!) : undefined, + groupId, + persist, children, ...props }: TabsProps) { const collection = useMemo(() => [] as string[], []); + const languageCtx = useDocLanguageOptional(); + const isLanguageGroup = groupId === DOC_LANGUAGE_STORAGE_KEY; + const languageValue = + isLanguageGroup && languageCtx ? languageCtx.language : undefined; return ( { + const id = resolveDocLanguageId(value); + if (id) languageCtx.setLanguage(id); + } + : undefined + } {...props} > {items && ( diff --git a/website/src/lib/languages.ts b/website/src/lib/languages.ts new file mode 100644 index 00000000..97773413 --- /dev/null +++ b/website/src/lib/languages.ts @@ -0,0 +1,15 @@ +export const DOC_LANGUAGES = [ + { id: "typescript", label: "TypeScript" }, + { id: "go", label: "Go" }, +] as const; + +export type DocLanguageId = (typeof DOC_LANGUAGES)[number]["id"]; + +export const DEFAULT_DOC_LANGUAGE: DocLanguageId = "typescript"; + +/** Matches `` storage key in fumadocs tabs. */ +export const DOC_LANGUAGE_STORAGE_KEY = "language"; + +export function isDocLanguageId(value: string): value is DocLanguageId { + return DOC_LANGUAGES.some((language) => language.id === value); +} diff --git a/website/src/lib/layout.shared.tsx b/website/src/lib/layout.shared.tsx index f050509c..ca9909b5 100644 --- a/website/src/lib/layout.shared.tsx +++ b/website/src/lib/layout.shared.tsx @@ -1,21 +1,11 @@ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"; -import { appName, assetUrl, gitConfig } from "./shared"; +import { BrandMark } from "@/components/brand-mark"; +import { gitConfig } from "./shared"; export function baseOptions(): BaseLayoutProps { return { nav: { - title: ( - - - {appName} - - ), + title: , }, githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`, }; diff --git a/website/src/lib/providers.ts b/website/src/lib/providers.ts index 01ade209..4a65eee5 100644 --- a/website/src/lib/providers.ts +++ b/website/src/lib/providers.ts @@ -12,3 +12,48 @@ export const DOC_PROVIDER_STORAGE_KEY = "sandbox-kit.docs.provider"; export function isDocProviderId(value: string): value is DocProviderId { return DOC_PROVIDERS.some((provider) => provider.id === value); } + +/** + * TOC entries that only exist for some providers. Keyed by docs page slug + * (filename without extension), then by heading id. Keep in sync with + * headings wrapped in `` in `content/docs`. + */ +export const PROVIDER_SCOPED_HEADINGS: Record< + string, + Record +> = { + lifecycle: { + "stop-start-pause-and-archive": ["daytona"], + delete: ["daytona"], + terminate: ["modal"], + }, + client: { + get: ["daytona"], + attach: ["modal"], + }, + sandbox: { + refresh: ["daytona"], + start: ["daytona"], + stop: ["daytona"], + pause: ["daytona"], + archive: ["daytona"], + delete: ["daytona"], + terminate: ["modal"], + }, +}; + +export function filterTocForProvider( + pageSlug: string, + toc: T[], + provider: DocProviderId, +): T[] { + const scoped = PROVIDER_SCOPED_HEADINGS[pageSlug]; + if (!scoped) return toc; + + return toc.filter((item) => { + const id = item.url.replace(/^#/, ""); + const providers = scoped[id]; + if (!providers) return true; + return providers.includes(provider); + }); +} diff --git a/website/src/lib/seo.ts b/website/src/lib/seo.ts new file mode 100644 index 00000000..f6915327 --- /dev/null +++ b/website/src/lib/seo.ts @@ -0,0 +1,62 @@ +import { absoluteSiteUrl, appName } from "./shared"; + +export const siteDescription = + "SDK for cloud sandboxes in TypeScript or Go. Plug in a provider; the client talks to it for create, files, and commands."; + +/** Same asset as the site favicon/nav mark (`public/logo.png`). */ +export const ogImagePath = "/logo.png"; +export const ogImageMime = "image/png"; +export const ogImageSize = { width: 500, height: 500 } as const; + +export type SocialHeadInput = { + title: string; + description: string; + /** Site path such as `/` or `/docs/create` (BASE_URL applied once). */ + path: string; + type?: "website" | "article"; +}; + +/** Open Graph + Twitter Card tags with absolute URLs for static GitHub Pages. */ +export function socialHead({ + title, + description, + path, + type = "website", +}: SocialHeadInput) { + const url = absoluteSiteUrl(path); + const image = absoluteSiteUrl(ogImagePath); + const fullTitle = title === appName ? title : `${title} | ${appName}`; + + return { + meta: [ + { title: fullTitle }, + { name: "description", content: description }, + { property: "og:title", content: fullTitle }, + { property: "og:description", content: description }, + { property: "og:url", content: url }, + { property: "og:type", content: type }, + { property: "og:site_name", content: appName }, + { property: "og:locale", content: "en_US" }, + { property: "og:image", content: image }, + { property: "og:image:secure_url", content: image }, + { property: "og:image:type", content: ogImageMime }, + { property: "og:image:width", content: String(ogImageSize.width) }, + { property: "og:image:height", content: String(ogImageSize.height) }, + { property: "og:image:alt", content: appName }, + { name: "twitter:card", content: "summary" }, + { name: "twitter:title", content: fullTitle }, + { name: "twitter:description", content: description }, + { name: "twitter:image", content: image }, + { name: "twitter:image:alt", content: appName }, + ], + links: [{ rel: "canonical", href: url }], + }; +} + +export function siteSocialHead() { + return socialHead({ + title: appName, + description: siteDescription, + path: "/", + }); +} diff --git a/website/src/lib/shared.ts b/website/src/lib/shared.ts index d6f8e996..5a808e92 100644 --- a/website/src/lib/shared.ts +++ b/website/src/lib/shared.ts @@ -2,7 +2,8 @@ import { createGetUrl } from "fumadocs-core/source"; export const appName = "Sandbox Kit"; export const docsRoute = "/docs"; -export const docsImageRoute = "/og/docs"; +/** Production origin for absolute OG/canonical URLs on GitHub Pages. */ +export const siteOrigin = "https://sandbox-kit.github.io"; export const gitConfig = { user: "sandbox-kit", @@ -52,15 +53,7 @@ export function absoluteSiteUrl(path: string) { } // Static prerender for GitHub Pages project site. if (base === "/kit") { - return `https://sandbox-kit.github.io${withBase}`; + return `${siteOrigin}${withBase}`; } return withBase; } - -const getImageUrl = createGetUrl(docsImageRoute); - -export function getPageImageUrl(page: { slugs: string[]; locale?: string }) { - const segments = [...page.slugs, "image.webp"]; - - return { segments, url: getImageUrl(segments, page.locale) }; -} diff --git a/website/src/routeTree.gen.ts b/website/src/routeTree.gen.ts index 7188c595..7e58a5d1 100644 --- a/website/src/routeTree.gen.ts +++ b/website/src/routeTree.gen.ts @@ -15,7 +15,6 @@ import { Route as LlmsDottxtRouteImport } from './routes/llms[.]txt' import { Route as ApiSearchRouteImport } from './routes/api/search' import { Route as DocsSplatRouteImport } from './routes/docs/$' import { Route as DocsChar123Char125DotmdRouteImport } from './routes/docs/{$}[.]md' -import { Route as OgDocsSplatRouteImport } from './routes/og/docs/$' const IndexRoute = IndexRouteImport.update({ id: '/', @@ -47,11 +46,6 @@ const DocsChar123Char125DotmdRoute = DocsChar123Char125DotmdRouteImport.update({ path: '/docs/{$}.md', getParentRoute: () => rootRouteImport, } as any) -const OgDocsSplatRoute = OgDocsSplatRouteImport.update({ - id: '/og/docs/$', - path: '/og/docs/$', - getParentRoute: () => rootRouteImport, -} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute @@ -60,7 +54,6 @@ export interface FileRoutesByFullPath { '/api/search': typeof ApiSearchRoute '/docs/$': typeof DocsSplatRoute '/docs/{$}.md': typeof DocsChar123Char125DotmdRoute - '/og/docs/$': typeof OgDocsSplatRoute } export interface FileRoutesByTo { '/': typeof IndexRoute @@ -69,7 +62,6 @@ export interface FileRoutesByTo { '/api/search': typeof ApiSearchRoute '/docs/$': typeof DocsSplatRoute '/docs/{$}.md': typeof DocsChar123Char125DotmdRoute - '/og/docs/$': typeof OgDocsSplatRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -79,7 +71,6 @@ export interface FileRoutesById { '/api/search': typeof ApiSearchRoute '/docs/$': typeof DocsSplatRoute '/docs/{$}.md': typeof DocsChar123Char125DotmdRoute - '/og/docs/$': typeof OgDocsSplatRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -90,7 +81,6 @@ export interface FileRouteTypes { | '/api/search' | '/docs/$' | '/docs/{$}.md' - | '/og/docs/$' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -99,7 +89,6 @@ export interface FileRouteTypes { | '/api/search' | '/docs/$' | '/docs/{$}.md' - | '/og/docs/$' id: | '__root__' | '/' @@ -108,7 +97,6 @@ export interface FileRouteTypes { | '/api/search' | '/docs/$' | '/docs/{$}.md' - | '/og/docs/$' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -118,7 +106,6 @@ export interface RootRouteChildren { ApiSearchRoute: typeof ApiSearchRoute DocsSplatRoute: typeof DocsSplatRoute DocsChar123Char125DotmdRoute: typeof DocsChar123Char125DotmdRoute - OgDocsSplatRoute: typeof OgDocsSplatRoute } declare module '@tanstack/react-router' { @@ -165,13 +152,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DocsChar123Char125DotmdRouteImport parentRoute: typeof rootRouteImport } - '/og/docs/$': { - id: '/og/docs/$' - path: '/og/docs/$' - fullPath: '/og/docs/$' - preLoaderRoute: typeof OgDocsSplatRouteImport - parentRoute: typeof rootRouteImport - } } } @@ -182,7 +162,6 @@ const rootRouteChildren: RootRouteChildren = { ApiSearchRoute: ApiSearchRoute, DocsSplatRoute: DocsSplatRoute, DocsChar123Char125DotmdRoute: DocsChar123Char125DotmdRoute, - OgDocsSplatRoute: OgDocsSplatRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/website/src/routes/__root.tsx b/website/src/routes/__root.tsx index b8f3b4e9..4725b307 100644 --- a/website/src/routes/__root.tsx +++ b/website/src/routes/__root.tsx @@ -7,8 +7,10 @@ import { import * as React from "react"; import appCss from "@/styles/app.css?url"; import { RootProvider } from "fumadocs-ui/provider/tanstack"; +import { LanguageProvider } from "@/components/language-context"; import { ProviderProvider } from "@/components/provider-context"; import { assetUrl } from "@/lib/shared"; +import { siteDescription } from "@/lib/seo"; export const Route = createRootRoute({ head: () => ({ @@ -19,11 +21,7 @@ export const Route = createRootRoute({ content: "width=device-width, initial-scale=1", }, { title: "Sandbox Kit" }, - { - name: "description", - content: - "Lightweight TypeScript and Go SDK for cloud sandboxes. Pluggable providers, one client for create, lifecycle, files, and commands.", - }, + { name: "description", content: siteDescription }, ], links: [ { rel: "stylesheet", href: appCss }, @@ -45,7 +43,9 @@ function RootComponent() { search={{ options: { type: "static" } }} > - + + + diff --git a/website/src/routes/docs/$.tsx b/website/src/routes/docs/$.tsx index dd025520..46fa1eef 100644 --- a/website/src/routes/docs/$.tsx +++ b/website/src/routes/docs/$.tsx @@ -10,16 +10,16 @@ import { ViewOptionsPopover, } from "fumadocs-ui/layouts/docs/page"; import { baseOptions } from "@/lib/layout.shared"; +import { absoluteSiteUrl, getPageMarkdownUrl, gitConfig } from "@/lib/shared"; +import { socialHead } from "@/lib/seo"; + +import { Suspense, use, useEffect, useMemo, useState } from "react"; import { - absoluteSiteUrl, - assetUrl, - getPageImageUrl, - getPageMarkdownUrl, - gitConfig, -} from "@/lib/shared"; -import { Suspense, use, useEffect, useState } from "react"; -import { DocsProviderSelect } from "@/components/provider-context"; + DocsProviderSelect, + useDocProvider, +} from "@/components/provider-context"; import { useMDXComponents } from "@/components/mdx"; +import { filterTocForProvider } from "@/lib/providers"; export const Route = createFileRoute("/docs/$")({ component: Page, @@ -31,22 +31,22 @@ export const Route = createFileRoute("/docs/$")({ return { path: page.path, + pageUrl: page.url, title: page.data.title, - description: page.data.description, + description: page.data.description ?? "", // Root-relative without BASE_URL — fumadocs page actions add BASE_URL once. markdownUrl: getPageMarkdownUrl(page).url, - imageUrl: assetUrl(getPageImageUrl(page).url), }; }, - head: ({ loaderData }) => ({ - meta: loaderData - ? [ - { title: `${loaderData.title} | Sandbox Kit` }, - { name: "description", content: loaderData.description ?? "" }, - { property: "og:image", content: loaderData.imageUrl }, - ] - : [], - }), + head: ({ loaderData }) => { + if (!loaderData) return {}; + return socialHead({ + title: loaderData.title, + description: loaderData.description, + path: loaderData.pageUrl, + type: "article", + }); + }, }); function Content({ path, markdownUrl }: { path: string; markdownUrl: string }) { @@ -54,6 +54,16 @@ function Content({ path, markdownUrl }: { path: string; markdownUrl: string }) { if (!page) throw new Error(`unknown page: ${path}`); const { toc } = use(page.load()); + const { provider } = useDocProvider(); + const pageSlug = + path + .replace(/\.mdx?$/, "") + .split("/") + .pop() ?? ""; + const visibleToc = useMemo( + () => filterTocForProvider(pageSlug, toc, provider), + [pageSlug, toc, provider], + ); const MDX = page.body; // AI tools fetch this URL; point at the static .md file, not the SPA HTML page. const [markdownPageUrl, setMarkdownPageUrl] = useState(markdownUrl); @@ -62,7 +72,7 @@ function Content({ path, markdownUrl }: { path: string; markdownUrl: string }) { }, [markdownUrl]); return ( - + {page.title} {page.description}
diff --git a/website/src/routes/index.tsx b/website/src/routes/index.tsx index 4dea1676..fec9c258 100644 --- a/website/src/routes/index.tsx +++ b/website/src/routes/index.tsx @@ -2,9 +2,10 @@ import { createFileRoute, Link } from "@tanstack/react-router"; import { HomeLayout } from "fumadocs-ui/layouts/home"; import { HomeCodeSample } from "@/components/home-code-sample"; import { baseOptions } from "@/lib/layout.shared"; -import { assetUrl } from "@/lib/shared"; +import { siteSocialHead } from "@/lib/seo"; export const Route = createFileRoute("/")({ + head: () => siteSocialHead(), component: Home, }); @@ -28,48 +29,44 @@ function Home() { className="pointer-events-none absolute inset-0 opacity-[0.35] [background-image:linear-gradient(to_right,rgba(17,17,16,0.045)_1px,transparent_1px),linear-gradient(to_bottom,rgba(17,17,16,0.045)_1px,transparent_1px)] [background-size:48px_48px] dark:opacity-[0.18]" /> -
+
- -

- Sandbox Kit +

+ sandbox-kit

-

- Lightweight TypeScript and Go SDK for cloud sandboxes. Plug in a - provider—one client for create, files, and commands. + +

+ SDK for cloud sandboxes in TypeScript or Go. Plug in a provider; + the client talks to it for create, files, and commands.

-
+ +
Quickstart Concepts
-

- Daytona · Modal · your provider next + +

+ One client · many providers

-

- Same call shape on every provider +

+ Create, then run

-
+
diff --git a/website/src/routes/og/docs/$.ts b/website/src/routes/og/docs/$.ts deleted file mode 100644 index 4a514b60..00000000 --- a/website/src/routes/og/docs/$.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; -import { generateOGImage } from 'fumadocs-ui/og/takumi'; -import { source } from '@/lib/source'; -import { appName } from '@/lib/shared'; - -export const Route = createFileRoute('/og/docs/$')({ - server: { - handlers: { - GET: ({ params }) => { - const slugs = (params._splat?.split('/') ?? []).filter((v) => v.length > 0).slice(0, -1); - const page = source.getPage(slugs); - if (!page) return new Response(undefined, { status: 404 }); - - return generateOGImage({ - title: page.data.title, - description: page.data.description, - site: appName, - format: 'webp', - }); - }, - }, - }, -}); diff --git a/website/src/styles/app.css b/website/src/styles/app.css index 1dd2801b..a6b22f4c 100644 --- a/website/src/styles/app.css +++ b/website/src/styles/app.css @@ -72,24 +72,43 @@ body { max-height: 100dvh; } -.home-copy, +.home-brand, +.home-lede, +.home-cta, +.home-meta, .home-sample, .home-code { - animation: home-rise 700ms cubic-bezier(0.22, 1, 0.36, 1) both; + animation: home-rise 720ms cubic-bezier(0.22, 1, 0.36, 1) both; +} + +.home-brand { + animation-delay: 40ms; +} + +.home-lede { + animation-delay: 130ms; +} + +.home-cta { + animation-delay: 210ms; +} + +.home-meta { + animation-delay: 290ms; } .home-sample { - animation-delay: 90ms; + animation-delay: 160ms; } .home-code { - animation-delay: 160ms; + animation-delay: 240ms; } @keyframes home-rise { from { opacity: 0; - transform: translateY(12px); + transform: translateY(14px); } to { opacity: 1; @@ -98,7 +117,10 @@ body { } @media (prefers-reduced-motion: reduce) { - .home-copy, + .home-brand, + .home-lede, + .home-cta, + .home-meta, .home-sample, .home-code { animation: none;